Skip to content

Commit

Permalink
Rework Intro Dropship Script to avoid player overlap (#809)
Browse files Browse the repository at this point in the history
This overhaul makes the Dropships behave consistently now like vanilla and players will only start overlapping each other when both Dropships are full, otherwise code will always attempt to populate them properly instead.
  • Loading branch information
Zanieon authored Jul 6, 2024
1 parent f3fa134 commit 852f3bc
Showing 1 changed file with 86 additions and 91 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,9 @@ 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<IntroDropship> militiaDropships
array<IntroDropship> imcDropships
table< entity, array<entity> > militiaDropships
table< entity, array<entity> > imcDropships

float introStartTime
} file
Expand All @@ -52,7 +43,12 @@ void function ClassicMP_DefaultDropshipIntro_Setup()
void function DropshipIntro_OnClientConnected( entity player )
{
if ( GetGameState() == eGameState.Prematch )
thread SpawnPlayerIntoDropship( player )
{
if( PlayerCanSpawn( player ) )
DoRespawnPlayer( player, null )

PutPlayerInDropship( player )
}
}

void function OnPrematchStart()
Expand All @@ -62,11 +58,11 @@ void function OnPrematchStart()
print( "starting dropship intro!" )
file.introStartTime = Time()

// make 2 empty dropship structs per team
IntroDropship emptyDropship
// 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<entity> validDropshipSpawns
array<entity> dropshipSpawns = GetEntArrayByClass_Expensive( "info_spawnpoint_dropship_start" )
foreach ( entity dropshipSpawn in dropshipSpawns )
Expand All @@ -78,47 +74,47 @@ void function OnPrematchStart()
validDropshipSpawns.append( dropshipSpawn )
}

// if no dropship spawns for this mode, just allow any dropship spawns
// 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 )
{
// 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<IntroDropship> teamDropships = createTeam == TEAM_MILITIA ? file.militiaDropships : file.imcDropships
table< entity, array<entity> > teamDropships = createTeam == TEAM_MILITIA ? file.militiaDropships : file.imcDropships

if ( teamDropships.len() >= 2 )
continue
break

// 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" )
if ( dropshipSpawn.GetTeam() == TEAM_IMC )
dropship.SetValueForModelKey( $"models/vehicle/goblin_dropship/goblin_dropship_hero.mdl" )

DispatchSpawn( dropship )

// have to do this after dispatch otherwise it won't work for some reason
// weirdly enough, tf2 actually does use different dropships for imc and militia, despite these concepts not really being a thing for players in tf2
// probably was just missed by devs, but keeping it in for accuracy
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" )
else
dropship.SetModel( $"models/vehicle/crow_dropship/crow_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 ) )
thread SpawnPlayerIntoDropship( player )
{
if( PlayerCanSpawn( player ) )
DoRespawnPlayer( player, null )

PutPlayerInDropship( player )
}
else
RespawnPrivateMatchSpectator( player )
}
Expand All @@ -128,68 +124,69 @@ void function OnPrematchStart()

void function EndIntroWhenFinished()
{
wait 15.0
wait DROPSHIP_INTRO_LENGTH
ClassicMP_OnIntroFinished()
}

void function SpawnPlayerIntoDropship( entity player )
void function PutPlayerInDropship( entity player )
{
player.EndSignal( "OnDestroy" )
//Find the player's dropship and seat
table< entity, array<entity> > 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 )
}

if ( IsAlive( player ) )
player.Die() // kill them so we don't have any issues respawning them later
void function SpawnPlayerIntoDropship( entity player, int playerDropshipIndex, entity playerDropship )
{
player.EndSignal( "OnDestroy" )
player.EndSignal( "OnDeath" )

player.s.dropshipIntroIsJumping <- false
OnThreadEnd( function() : ( player )
OnThreadEnd( function() : ( player, playerDropshipIndex, playerDropship )
{
if ( IsValid( player ) )
{
player.ClearParent()
ClearPlayerAnimViewEntity( player )
if ( !player.s.dropshipIntroIsJumping )
{
player.MovementEnable()
player.EnableWeaponViewModel()
RemoveCinematicFlag( player, CE_FLAG_CLASSIC_MP_SPAWNING )
}
}
if( IsAlive( playerDropship ) )
{
if ( playerDropship.GetTeam() == TEAM_MILITIA )
file.militiaDropships[ playerDropship ][ playerDropshipIndex ] = null
else
file.imcDropships[ playerDropship ][ playerDropshipIndex ] = null
}
})

WaitFrame()

player.EndSignal( "OnDeath" )

// find the player's dropship and seat
array<IntroDropship> 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
if ( !IsAlive( player ) )
player.RespawnPlayer( null )
HolsterAndDisableWeapons(player)
player.DisableWeaponViewModel()

Expand All @@ -208,23 +205,20 @@ void function SpawnPlayerIntoDropship( entity player )
idleSequence.viewConeFunction = ViewConeRampFree
idleSequence.hideProxy = true
idleSequence.setInitialTime = Time() - file.introStartTime
thread FirstPersonSequence( idleSequence, player, playerDropship.dropship )
WaittillAnimDone( player )

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

thread FirstPersonSequence( jumpSequence, player, playerDropship.dropship )
WaittillAnimDone( player ) // somehow this is better than just waiting for the blocking FirstPersonSequence call?
waitthread FirstPersonSequence( jumpSequence, player, playerDropship )

player.s.dropshipIntroIsJumping <- true
thread PlayerJumpsFromDropship( player )
}

Expand All @@ -244,16 +238,17 @@ void function PlayerJumpsFromDropship( entity 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
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()

TryGameModeAnnouncement( player )
if ( GetRoundsPlayed() == 0 ) //Intro is announced only for the first round in Vanilla as certain gamemodes have different announcements for rounds restarts
TryGameModeAnnouncement( player )
}

0 comments on commit 852f3bc

Please sign in to comment.