diff options
Diffstat (limited to 'Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut')
-rw-r--r-- | Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut | 518 |
1 files changed, 518 insertions, 0 deletions
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut new file mode 100644 index 000000000..704f55d3b --- /dev/null +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut @@ -0,0 +1,518 @@ +untyped +// this needs a refactor lol + +global function CaptureTheFlag_Init +global function RateSpawnpoints_CTF + +const array<string> SWAP_FLAG_MAPS = [ + "mp_forwardbase_kodai", + "mp_lf_meadow" +] + +struct { + entity imcFlagSpawn + entity imcFlag + entity imcFlagReturnTrigger + + entity militiaFlagSpawn + entity militiaFlag + entity militiaFlagReturnTrigger + + array<entity> imcCaptureAssistList + array<entity> militiaCaptureAssistList +} file + +void function CaptureTheFlag_Init() +{ + PrecacheModel( CTF_FLAG_MODEL ) + PrecacheModel( CTF_FLAG_BASE_MODEL ) + + CaptureTheFlagShared_Init() + SetSwitchSidesBased( true ) + SetSuddenDeathBased( true ) + SetShouldUseRoundWinningKillReplay( true ) + SetRoundWinningKillReplayKillClasses( false, false ) // make these fully manual + + AddCallback_OnClientConnected( CTFInitPlayer ) + + AddCallback_GameStateEnter( eGameState.Prematch, CreateFlags ) + AddCallback_OnTouchHealthKit( "item_flag", OnFlagCollected ) + AddCallback_OnPlayerKilled( OnPlayerKilled ) + AddCallback_OnPilotBecomesTitan( DropFlagForBecomingTitan ) + + RegisterSignal( "FlagReturnEnded" ) + RegisterSignal( "ResetDropTimeout" ) + + // setup stuff for the functions in sh_gamemode_ctf + // don't really like using level for stuff but just how it be + level.teamFlags <- {} + + // setup score event earnmeter values + ScoreEvent_SetEarnMeterValues( "KillPilot", 0.05, 0.20 ) + ScoreEvent_SetEarnMeterValues( "Headshot", 0.0, 0.02 ) + ScoreEvent_SetEarnMeterValues( "FirstStrike", 0.0, 0.05 ) + ScoreEvent_SetEarnMeterValues( "KillTitan", 0.0, 0.25 ) + ScoreEvent_SetEarnMeterValues( "PilotBatteryStolen", 0.0, 0.35 ) + + ScoreEvent_SetEarnMeterValues( "FlagCarrierKill", 0.0, 0.20 ) + ScoreEvent_SetEarnMeterValues( "FlagTaken", 0.0, 0.10 ) + ScoreEvent_SetEarnMeterValues( "FlagCapture", 0.0, 0.30 ) + ScoreEvent_SetEarnMeterValues( "FlagCaptureAssist", 0.0, 0.20 ) + ScoreEvent_SetEarnMeterValues( "FlagReturn", 0.0, 0.20 ) +} + +void function RateSpawnpoints_CTF( int checkClass, array<entity> spawnpoints, int team, entity player ) +{ + // ok this is the 3rd time rewriting this due to not understanding ctf spawns properly + // legit just + // if there are no enemies in base, spawn them in base + // if there are, spawn them outside of it ( but ideally still close ) + // max distance away should be like, angel city markets + + int spawnTeam = team + if ( HasSwitchedSides() ) + spawnTeam = GetOtherTeam( team ) + + array<entity> startSpawns = SpawnPoints_GetPilotStart( spawnTeam ) + array<entity> enemyPlayers = GetPlayerArrayOfTeam_Alive( GetOtherTeam( spawnTeam ) ) + + vector startSpawnAverage + bool enemyInBase = false + foreach ( entity startSpawn in startSpawns ) + { + startSpawnAverage += startSpawn.GetOrigin() + + foreach ( entity enemy in enemyPlayers ) + { + if ( Distance( startSpawn.GetOrigin(), enemy.GetOrigin() ) <= 1000.0 ) + { + enemyInBase = true + break + } + } + } + + startSpawnAverage /= startSpawns.len() + + print( "spawn for " + player + " is there an enemy in base?" + enemyInBase ) + + foreach ( entity spawn in spawnpoints ) + { + float rating = 0.0 + + bool isStart = false + foreach ( entity startSpawn in startSpawns ) + { + if ( Distance2D( spawn.GetOrigin(), startSpawn.GetOrigin() ) < 1500.0 ) // this was for some reason the only distance i could get to work + { + isStart = true + break + } + } + + if ( isStart ) + { + if ( !enemyInBase ) + rating = 1000 + RandomFloat( 100.0 ) + else + rating = -1000.0 + } + else if ( !isStart && enemyInBase ) + { + entity friendlyFlag + entity enemyFlag + if ( team == TEAM_IMC ) + { + friendlyFlag = file.imcFlagSpawn + enemyFlag = file.militiaFlagSpawn + } + else + { + friendlyFlag = file.militiaFlagSpawn + enemyFlag = file.imcFlagSpawn + } + + float dist = Distance2D( spawn.GetOrigin(), enemyFlag.GetOrigin() ) + float flagDist = Distance2D( startSpawnAverage, enemyFlag.GetOrigin() ) + + if ( dist < ( flagDist / 2 ) ) // spawns shouldn't be closer to enemies than they are to us + rating = -1000.0 + if ( dist > flagDist * 1.1 ) // spawn is behind startspawns + rating = -1000.0 + else + { + rating = dist // closer spawns are better + + foreach( entity enemy in enemyPlayers ) // reduce rating if enemies are near by + if ( Distance( enemy.GetOrigin(), spawn.GetOrigin() ) < 500.0 ) + rating /= 2 + } + } + + spawn.CalculateRating( checkClass, team, rating, rating ) + } +} + +void function CTFInitPlayer( entity player ) +{ + if ( !IsValid( file.imcFlagSpawn ) ) + return + + vector imcSpawn = file.imcFlagSpawn.GetOrigin() + Remote_CallFunction_NonReplay( player, "ServerCallback_SetFlagHomeOrigin", TEAM_IMC, imcSpawn.x, imcSpawn.y, imcSpawn.z ) + + vector militiaSpawn = file.militiaFlagSpawn.GetOrigin() + Remote_CallFunction_NonReplay( player, "ServerCallback_SetFlagHomeOrigin", TEAM_MILITIA, militiaSpawn.x, militiaSpawn.y, militiaSpawn.z ) +} + +void function OnPlayerKilled( entity victim, entity attacker, var damageInfo ) +{ + if ( GetFlagForTeam( GetOtherTeam( victim.GetTeam() ) ).GetParent() == victim ) + { + if ( victim != attacker && attacker.IsPlayer() ) + AddPlayerScore( attacker, "FlagCarrierKill", victim ) + + DropFlag( victim ) + } +} + +void function CreateFlags() +{ + if ( IsValid( file.imcFlagSpawn ) ) + { + file.imcFlagSpawn.Destroy() + file.imcFlag.Destroy() + file.imcFlagReturnTrigger.Destroy() + + file.militiaFlagSpawn.Destroy() + file.militiaFlag.Destroy() + file.militiaFlagReturnTrigger.Destroy() + } + + foreach ( entity spawn in GetEntArrayByClass_Expensive( "info_spawnpoint_flag" ) ) + { + // on some maps flags are on the opposite side from what they should be + // likely this is because respawn uses distance checks from spawns to check this in official + // but i don't like doing that so just using a list of maps to swap them on lol + bool switchedSides = HasSwitchedSides() == 1 + bool shouldSwap = SWAP_FLAG_MAPS.contains( GetMapName() ) ? !switchedSides : switchedSides + + int flagTeam = spawn.GetTeam() + if ( shouldSwap ) + { + flagTeam = GetOtherTeam( flagTeam ) + SetTeam( spawn, flagTeam ) + } + + // create flag base + entity base = CreatePropDynamic( CTF_FLAG_BASE_MODEL, spawn.GetOrigin(), spawn.GetAngles(), 0 ) + SetTeam( base, spawn.GetTeam() ) + svGlobal.flagSpawnPoints[ flagTeam ] = base + + // create flag + entity flag = CreateEntity( "item_flag" ) + flag.SetValueForModelKey( CTF_FLAG_MODEL ) + SetTeam( flag, flagTeam ) + flag.MarkAsNonMovingAttachment() + DispatchSpawn( flag ) + flag.SetModel( CTF_FLAG_MODEL ) + flag.SetOrigin( spawn.GetOrigin() + < 0, 0, base.GetBoundingMaxs().z * 2 > ) // ensure flag doesn't spawn clipped into geometry + flag.SetVelocity( < 0, 0, 1 > ) + + flag.s.canTake <- true + flag.s.playersReturning <- [] + + level.teamFlags[ flag.GetTeam() ] <- flag + + entity returnTrigger = CreateEntity( "trigger_cylinder" ) + SetTeam( returnTrigger, flagTeam ) + returnTrigger.SetRadius( CTF_GetFlagReturnRadius() ) + returnTrigger.SetAboveHeight( CTF_GetFlagReturnRadius() ) + returnTrigger.SetBelowHeight( CTF_GetFlagReturnRadius() ) + + returnTrigger.SetEnterCallback( OnPlayerEntersFlagReturnTrigger ) + returnTrigger.SetLeaveCallback( OnPlayerExitsFlagReturnTrigger ) + + DispatchSpawn( returnTrigger ) + + thread TrackFlagReturnTrigger( flag, returnTrigger ) + + if ( flagTeam == TEAM_IMC ) + { + file.imcFlagSpawn = base + file.imcFlag = flag + file.imcFlagReturnTrigger = returnTrigger + + SetGlobalNetEnt( "imcFlag", file.imcFlag ) + SetGlobalNetEnt( "imcFlagHome", file.imcFlagSpawn ) + } + else + { + file.militiaFlagSpawn = base + file.militiaFlag = flag + file.militiaFlagReturnTrigger = returnTrigger + + SetGlobalNetEnt( "milFlag", file.militiaFlag ) + SetGlobalNetEnt( "milFlagHome", file.militiaFlagSpawn ) + } + } + + foreach ( entity player in GetPlayerArray() ) + CTFInitPlayer( player ) +} + +void function TrackFlagReturnTrigger( entity flag, entity returnTrigger ) +{ + // this is a bit of a hack, it seems parenting the return trigger to the flag actually sets the pickup radius of the flag to be the same as the trigger + // this isn't wanted since only pickups should use that additional radius + flag.EndSignal( "OnDestroy" ) + + while ( true ) + { + returnTrigger.SetOrigin( flag.GetOrigin() ) + WaitFrame() + } +} + +void function SetFlagStateForTeam( int team, int state ) +{ + if ( state == eFlagState.Away ) // we tell the client the flag is the player carrying it if they're carrying it + SetGlobalNetEnt( team == TEAM_IMC ? "imcFlag" : "milFlag", ( team == TEAM_IMC ? file.imcFlag : file.militiaFlag ).GetParent() ) + else + SetGlobalNetEnt( team == TEAM_IMC ? "imcFlag" : "milFlag", team == TEAM_IMC ? file.imcFlag : file.militiaFlag ) + + SetGlobalNetInt( team == TEAM_IMC ? "imcFlagState" : "milFlagState", state ) +} + +bool function OnFlagCollected( entity player, entity flag ) +{ + if ( !IsAlive( player ) || flag.GetParent() != null || player.IsTitan() || player.IsPhaseShifted() ) + return false + + if ( player.GetTeam() != flag.GetTeam() && flag.s.canTake ) + GiveFlag( player, flag ) // pickup enemy flag + else if ( player.GetTeam() == flag.GetTeam() && IsFlagHome( flag ) && PlayerHasEnemyFlag( player ) ) + CaptureFlag( player, GetFlagForTeam( GetOtherTeam( flag.GetTeam() ) ) ) // cap the flag + + return false // don't wanna delete the flag entity +} + +void function GiveFlag( entity player, entity flag ) +{ + print( player + " picked up the flag!" ) + flag.Signal( "ResetDropTimeout" ) + + flag.SetParent( player, "FLAG" ) + thread DropFlagIfPhased( player, flag ) + + // do notifications + MessageToPlayer( player, eEventNotifications.YouHaveTheEnemyFlag ) + EmitSoundOnEntityOnlyToPlayer( player, player, "UI_CTF_1P_GrabFlag" ) + AddPlayerScore( player, "FlagTaken", player ) + PlayFactionDialogueToPlayer( "ctf_flagPickupYou", player ) + + MessageToTeam( player.GetTeam(), eEventNotifications.PlayerHasEnemyFlag, player, player ) + EmitSoundOnEntityToTeamExceptPlayer( flag, "UI_CTF_3P_TeamGrabFlag", player.GetTeam(), player ) + PlayFactionDialogueToTeamExceptPlayer( "ctf_flagPickupFriendly", player.GetTeam(), player ) + + MessageToTeam( flag.GetTeam(), eEventNotifications.PlayerHasFriendlyFlag, player, player ) + EmitSoundOnEntityToTeam( flag, "UI_CTF_EnemyGrabFlag", flag.GetTeam() ) + + SetFlagStateForTeam( flag.GetTeam(), eFlagState.Away ) // used for held +} + +void function DropFlagIfPhased( entity player, entity flag ) +{ + player.EndSignal( "StartPhaseShift" ) + + OnThreadEnd( function() : ( player ) + { + DropFlag( player, true ) + }) + + while( flag.GetParent() == player ) + WaitFrame() +} + +void function DropFlagForBecomingTitan( entity pilot, entity titan ) +{ + DropFlag( pilot, true ) +} + +void function DropFlag( entity player, bool realDrop = true ) +{ + entity flag = GetFlagForTeam( GetOtherTeam( player.GetTeam() ) ) + + if ( flag.GetParent() != player ) + return + + print( player + " dropped the flag!" ) + + flag.ClearParent() + flag.SetAngles( < 0, 0, 0 > ) + flag.SetVelocity( < 0, 0, 0 > ) + + if ( realDrop ) + { + // start drop timeout countdown + thread TrackFlagDropTimeout( flag ) + + // add to capture assists + if ( player.GetTeam() == TEAM_IMC ) + file.imcCaptureAssistList.append( player ) + else + file.militiaCaptureAssistList.append( player ) + + // do notifications + MessageToPlayer( player, eEventNotifications.YouDroppedTheEnemyFlag ) + EmitSoundOnEntityOnlyToPlayer( player, player, "UI_CTF_1P_FlagDrop" ) + + MessageToTeam( player.GetTeam(), eEventNotifications.PlayerDroppedEnemyFlag, player, player ) + // todo need a sound here maybe + + MessageToTeam( GetOtherTeam( player.GetTeam() ), eEventNotifications.PlayerDroppedFriendlyFlag, player, player ) + // todo need a sound here maybe + } + + SetFlagStateForTeam( flag.GetTeam(), eFlagState.Home ) // used for return prompt +} + +void function TrackFlagDropTimeout( entity flag ) +{ + flag.EndSignal( "ResetDropTimeout" ) + + wait CTF_GetDropTimeout() + + ResetFlag( flag ) +} + +void function ResetFlag( entity flag ) +{ + // ensure we can't pickup the flag after it's been dropped but before it's been reset + flag.s.canTake = false + + if ( flag.GetParent() != null ) + DropFlag( flag.GetParent(), false ) + + entity spawn + if ( flag.GetTeam() == TEAM_IMC ) + spawn = file.imcFlagSpawn + else + spawn = file.militiaFlagSpawn + + flag.SetOrigin( spawn.GetOrigin() + < 0, 0, spawn.GetBoundingMaxs().z + 1 > ) + + // we can take it again now + flag.s.canTake = true + + SetFlagStateForTeam( flag.GetTeam(), eFlagState.None ) // used for home + + flag.Signal( "ResetDropTimeout" ) +} + +void function CaptureFlag( entity player, entity flag ) +{ + // reset flag + ResetFlag( flag ) + + print( player + " captured the flag!" ) + + // score + int team = player.GetTeam() + AddTeamScore( team, 1 ) + AddPlayerScore( player, "FlagCapture", player ) + player.AddToPlayerGameStat( PGS_ASSAULT_SCORE, 1 ) // add 1 to captures on scoreboard + SetRoundWinningKillReplayAttacker( player ) // set attacker for last cap replay + + array<entity> assistList + if ( player.GetTeam() == TEAM_IMC ) + assistList = file.imcCaptureAssistList + else + assistList = file.militiaCaptureAssistList + + foreach( entity assistPlayer in assistList ) + if ( player != assistPlayer ) + AddPlayerScore( assistPlayer, "FlagCaptureAssist", player ) + + assistList.clear() + + // notifs + MessageToPlayer( player, eEventNotifications.YouCapturedTheEnemyFlag ) + EmitSoundOnEntityOnlyToPlayer( player, player, "UI_CTF_1P_PlayerScore" ) + + MessageToTeam( team, eEventNotifications.PlayerCapturedEnemyFlag, player, player ) + EmitSoundOnEntityToTeamExceptPlayer( flag, "UI_CTF_3P_TeamScore", player.GetTeam(), player ) + + MessageToTeam( GetOtherTeam( team ), eEventNotifications.PlayerCapturedFriendlyFlag, player, player ) + EmitSoundOnEntityToTeam( flag, "UI_CTF_3P_EnemyScore", flag.GetTeam() ) + + if ( GameRules_GetTeamScore( team ) == GameMode_GetRoundScoreLimit( GAMETYPE ) - 1 ) + { + PlayFactionDialogueToTeam( "ctf_notifyWin1more", team ) + PlayFactionDialogueToTeam( "ctf_notifyLose1more", GetOtherTeam( team ) ) + } +} + +void function OnPlayerEntersFlagReturnTrigger( entity trigger, entity player ) +{ + entity flag + if ( trigger.GetTeam() == TEAM_IMC ) + flag = file.imcFlag + else + flag = file.militiaFlag + + if ( !player.IsPlayer() || player.IsTitan() || player.GetTeam() != flag.GetTeam() || IsFlagHome( flag ) || flag.GetParent() != null ) + return + + thread TryReturnFlag( player, flag ) +} + +void function OnPlayerExitsFlagReturnTrigger( entity trigger, entity player ) +{ + entity flag + if ( trigger.GetTeam() == TEAM_IMC ) + flag = file.imcFlag + else + flag = file.militiaFlag + + if ( !player.IsPlayer() || player.IsTitan() || player.GetTeam() != flag.GetTeam() || IsFlagHome( flag ) || flag.GetParent() != null ) + return + + player.Signal( "FlagReturnEnded" ) +} + +void function TryReturnFlag( entity player, entity flag ) +{ + // start return progress bar + Remote_CallFunction_NonReplay( player, "ServerCallback_CTF_StartReturnFlagProgressBar", Time() + CTF_GetFlagReturnTime() ) + EmitSoundOnEntityOnlyToPlayer( player, player, "UI_CTF_1P_FlagReturnMeter" ) + + OnThreadEnd( function() : ( player ) + { + // cleanup + Remote_CallFunction_NonReplay( player, "ServerCallback_CTF_StopReturnFlagProgressBar" ) + StopSoundOnEntity( player, "UI_CTF_1P_FlagReturnMeter" ) + }) + + player.EndSignal( "FlagReturnEnded" ) + player.EndSignal( "OnDeath" ) + + wait CTF_GetFlagReturnTime() + + // flag return succeeded + // return flag + ResetFlag( flag ) + + // do notifications for return + MessageToPlayer( player, eEventNotifications.YouReturnedFriendlyFlag ) + AddPlayerScore( player, "FlagReturn", player ) + player.AddToPlayerGameStat( PGS_DEFENSE_SCORE, 1 ) + + MessageToTeam( flag.GetTeam(), eEventNotifications.PlayerReturnedFriendlyFlag, null, player ) + EmitSoundOnEntityToTeam( flag, "UI_CTF_3P_TeamReturnsFlag", flag.GetTeam() ) + PlayFactionDialogueToTeam( "ctf_flagReturnedFriendly", flag.GetTeam() ) + + MessageToTeam( GetOtherTeam( flag.GetTeam() ), eEventNotifications.PlayerReturnedEnemyFlag, null, player ) + EmitSoundOnEntityToTeam( flag, "UI_CTF_3P_EnemyReturnsFlag", GetOtherTeam( flag.GetTeam() ) ) + PlayFactionDialogueToTeam( "ctf_flagReturnedEnemy", GetOtherTeam( flag.GetTeam() ) ) +}
\ No newline at end of file |