From 072c0b798a188995770d4db1ad67c95e99d46be0 Mon Sep 17 00:00:00 2001 From: BobTheBob <32057864+BobTheBob9@users.noreply.github.com> Date: Thu, 17 Feb 2022 00:39:55 +0000 Subject: new spawnzone-based ctf spawn algo --- .../scripts/vscripts/gamemodes/_gamemode_ctf.nut | 81 +----------- .../mod/scripts/vscripts/mp/spawn.nut | 138 ++++++++++++++++++++- 2 files changed, 142 insertions(+), 77 deletions(-) diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut index a0a7243b..b4ab26ea 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut @@ -40,6 +40,7 @@ void function CaptureTheFlag_Init() AddCallback_OnPlayerKilled( OnPlayerKilled ) AddCallback_OnPilotBecomesTitan( DropFlagForBecomingTitan ) + SetSpawnZoneRatingFunc( DecideSpawnZone_CTF ) AddSpawnpointValidationRule( VerifyCTFSpawnpoint ) RegisterSignal( "FlagReturnEnded" ) @@ -65,69 +66,7 @@ void function CaptureTheFlag_Init() void function RateSpawnpoints_CTF( int checkClass, array spawnpoints, int team, entity player ) { - // ctf spawn algo iteration 4 i despise extistence - array startSpawns = SpawnPoints_GetPilotStart( team ) - array enemyStartSpawns = SpawnPoints_GetPilotStart( GetOtherTeam( team ) ) - array enemyPlayers = GetPlayerArrayOfTeam_Alive( team ) - - // get average startspawn position and max dist between spawns - // could probably cache this, tbh, not like it should change outside of halftimes - vector averageFriendlySpawns - float averageFriendlySpawnDist - - int averageDistCount - - foreach ( entity spawn in startSpawns ) - { - foreach ( entity otherSpawn in startSpawns ) - { - float dist = Distance2D( spawn.GetOrigin(), otherSpawn.GetOrigin() ) - averageFriendlySpawnDist += dist - averageDistCount++ - } - - averageFriendlySpawns += spawn.GetOrigin() - } - - averageFriendlySpawns /= startSpawns.len() - averageFriendlySpawnDist /= averageDistCount - - // get average enemy startspawn position - vector averageEnemySpawns - - foreach ( entity spawn in enemyStartSpawns ) - averageEnemySpawns += spawn.GetOrigin() - - averageEnemySpawns /= enemyStartSpawns.len() - - // from here, rate spawns - float baseDistance = Distance2D( averageFriendlySpawns, averageEnemySpawns ) - float spawnIterations = ( baseDistance / averageFriendlySpawnDist ) / 2 - - foreach ( entity spawn in spawnpoints ) - { - // ratings should max/min out at 100 / -100 - // start by prioritizing closer spawns, but not so much that enemies won't really affect them - float rating = 10 * ( 1.0 - Distance2D( averageFriendlySpawns, spawn.GetOrigin() ) / baseDistance ) - float remainingZonePower = 1.0 // this is used to ensure that players that are in multiple zones at once shouldn't affect all those zones too hard - - for ( int i = 0; i < spawnIterations; i++ ) - { - vector zonePos = averageFriendlySpawns + Normalize( averageEnemySpawns - averageFriendlySpawns ) * ( i * averageFriendlySpawnDist ) - - float zonePower - foreach ( entity otherPlayer in enemyPlayers ) - if ( Distance2D( otherPlayer.GetOrigin(), zonePos ) < averageFriendlySpawnDist ) - zonePower += 1.0 / enemyPlayers.len() - - zonePower = min( zonePower, remainingZonePower ) - remainingZonePower -= zonePower - // scale rating based on distance between spawn and zone, baring in mind max 100 rating - rating -= ( zonePower * 100 ) * ( 1.0 - Distance2D( spawn.GetOrigin(), zonePos ) / baseDistance ) - } - - spawn.CalculateRating( checkClass, player.GetTeam(), rating, rating ) - } + RateSpawnpoints_SpawnZones( checkClass, spawnpoints, team, player ) } bool function VerifyCTFSpawnpoint( entity spawnpoint, int team ) @@ -179,13 +118,6 @@ void function OnPlayerKilled( entity victim, entity attacker, var damageInfo ) } } -void function SetupFlagMinimapIcon( entity flag ) -{ - flag.Minimap_AlwaysShow( TEAM_IMC, null ) - flag.Minimap_AlwaysShow( TEAM_MILITIA, null ) - flag.Minimap_SetAlignUpright( true ) -} - void function CreateFlags() { if ( IsValid( file.imcFlagSpawn ) ) @@ -205,7 +137,7 @@ void function CreateFlags() // 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 + bool shouldSwap = SWAP_FLAG_MAPS.contains( GetMapName() ) ? !switchedSides : switchedSides int flagTeam = spawn.GetTeam() if ( shouldSwap ) @@ -228,7 +160,6 @@ void function CreateFlags() 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 > ) - SetFlagStateForTeam( flag.GetTeam(), eFlagState.None ) // reset flag state to prevent half-time oddities flag.s.canTake <- true flag.s.playersReturning <- [] @@ -252,7 +183,6 @@ void function CreateFlags() { file.imcFlagSpawn = base file.imcFlag = flag - SetupFlagMinimapIcon( file.imcFlag ) file.imcFlagReturnTrigger = returnTrigger SetGlobalNetEnt( "imcFlag", file.imcFlag ) @@ -262,7 +192,6 @@ void function CreateFlags() { file.militiaFlagSpawn = base file.militiaFlag = flag - SetupFlagMinimapIcon( file.militiaFlag ) file.militiaFlagReturnTrigger = returnTrigger SetGlobalNetEnt( "milFlag", file.militiaFlag ) @@ -344,7 +273,7 @@ void function DropFlagIfPhased( entity player, entity flag ) DropFlag( player, true ) }) - while( IsValid( flag ) && flag.GetParent() == player ) + while( flag.GetParent() == player ) WaitFrame() } @@ -529,4 +458,4 @@ void function TryReturnFlag( entity player, entity flag ) 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 diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/spawn.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/spawn.nut index 2b1a0713..3cc4556b 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/spawn.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/spawn.nut @@ -21,6 +21,7 @@ global function SetShouldCreateMinimapSpawnZones global function CreateTeamSpawnZoneEntity global function RateSpawnpoints_SpawnZones global function DecideSpawnZone_Generic +global function DecideSpawnZone_CTF struct NoSpawnArea { @@ -590,7 +591,7 @@ entity function DecideSpawnZone_Generic( array spawnzones, int team ) array possibleZones foreach ( entity spawnzone in spawnStateSpawnzones.mapSpawnzoneTriggers ) { - // don't remeber if you can do a "value in table.values" sorta thing in squirrel so doing manual lookup + // don't remember if you can do a "value in table.values" sorta thing in squirrel so doing manual lookup bool spawnzoneTaken = false foreach ( int otherTeam, entity otherSpawnzone in spawnStateSpawnzones.activeTeamSpawnzones ) { @@ -669,5 +670,140 @@ entity function DecideSpawnZone_Generic( array spawnzones, int team ) spawnStateSpawnzones.activeTeamSpawnzones[ team ] <- chosenZone } + return spawnStateSpawnzones.activeTeamSpawnzones[ team ] +} + +// ideally this should be in the gamemode_ctf file, but would need refactors to expose more stuff that's not available there rn +entity function DecideSpawnZone_CTF( array spawnzones, int team ) +{ + if ( spawnzones.len() == 0 ) + return null + + int otherTeam = GetOtherTeam( team ) + array enemyPlayers = GetPlayerArrayOfTeam( otherTeam ) + + // get average team startspawn positions + int spawnCompareTeam = team + if ( HasSwitchedSides() ) + spawnCompareTeam = GetOtherTeam( team ) + + array startSpawns = SpawnPoints_GetPilotStart( spawnCompareTeam ) + array enemyStartSpawns = SpawnPoints_GetPilotStart( GetOtherTeam( spawnCompareTeam ) ) + + if ( startSpawns.len() == 0 || enemyStartSpawns.len() == 0 ) // ensure we don't crash + return null + + // get average startspawn position and max dist between spawns + // could probably cache this, tbh, not like it should change outside of halftimes + vector averageFriendlySpawns + foreach ( entity spawn in startSpawns ) + averageFriendlySpawns += spawn.GetOrigin() + + averageFriendlySpawns /= startSpawns.len() + + // get average enemy startspawn position + vector averageEnemySpawns + foreach ( entity spawn in enemyStartSpawns ) + averageEnemySpawns += spawn.GetOrigin() + + averageEnemySpawns /= enemyStartSpawns.len() + + float baseDistance = Distance2D( averageFriendlySpawns, averageEnemySpawns ) + + // find new zone + array possibleZones + foreach ( entity spawnzone in spawnStateSpawnzones.mapSpawnzoneTriggers ) + { + // can't choose zone if another team has it + if ( otherTeam in spawnStateSpawnzones.activeTeamSpawnzones && spawnStateSpawnzones.activeTeamSpawnzones[ otherTeam ] == spawnzone ) + continue + + // check zone validity + bool spawnzoneEvil = false + foreach ( entity player in enemyPlayers ) + { + // couldn't get IsTouching, GetTouchingEntities or enter callbacks to work in testing, so doing this + if ( spawnzone.ContainsPoint( player.GetOrigin() ) ) + { + spawnzoneEvil = true + break + } + } + + // don't choose spawnzones that are closer to enemy base than friendly base + if ( !spawnzoneEvil && Distance2D( spawnzone.GetOrigin(), averageFriendlySpawns ) > Distance2D( spawnzone.GetOrigin(), averageEnemySpawns ) ) + spawnzoneEvil = true + + if ( spawnzoneEvil ) + continue + + // rate spawnzone based on distance to frontline + Frontline frontline = GetFrontline( team ) + + // prefer spawns close to base pos + float rating = 10 * ( 1.0 - Distance2D( averageFriendlySpawns, spawnzone.GetOrigin() ) / baseDistance ) + + if ( frontline.friendlyCenter != < 0, 0, 0 > ) + { + // rate based on distance to frontline, and then prefer spawns in the same dir from the frontline as the combatdir + rating += rating * ( 1.0 - ( Distance2D( spawnzone.GetOrigin(), frontline.friendlyCenter ) / baseDistance ) ) + rating *= fabs( frontline.combatDir.y - Normalize( spawnzone.GetOrigin() - averageFriendlySpawns ).y ) + + // reduce rating based on players that can currently see the zone + bool hasAppliedInitialLoss = false + foreach ( entity player in enemyPlayers ) + { + // don't trace here, just do an angle check + if ( PlayerCanSee( player, spawnzone, false, 65 ) && Distance2D( player.GetOrigin(), spawnzone.GetOrigin() ) <= 2000.0 ) + { + float distFrac = TraceLineSimple( player.GetOrigin(), spawnzone.GetOrigin(), player ) + + if ( distFrac >= 0.65 ) + { + // give a fairly large loss if literally anyone can see it + if ( !hasAppliedInitialLoss ) + { + rating *= 0.8 + hasAppliedInitialLoss = true + } + + rating *= ( 1.0 / enemyPlayers.len() ) * distFrac + } + } + } + } + + spawnzone.s.spawnzoneRating = rating + possibleZones.append( spawnzone ) + } + + if ( possibleZones.len() == 0 ) + return null + + possibleZones.sort( int function( entity a, entity b ) + { + if ( a.s.spawnzoneRating > b.s.spawnzoneRating ) + return -1 + + if ( b.s.spawnzoneRating > a.s.spawnzoneRating ) + return 1 + + return 0 + } ) + entity chosenZone = possibleZones[ minint( RandomInt( 3 ), possibleZones.len() - 1 ) ] + + if ( spawnStateSpawnzones.shouldCreateMinimapSpawnzones ) + { + entity oldEnt + if ( team in spawnStateSpawnzones.activeTeamSpawnzoneMinimapEnts ) + oldEnt = spawnStateSpawnzones.activeTeamSpawnzoneMinimapEnts[ team ] + + spawnStateSpawnzones.activeTeamSpawnzoneMinimapEnts[ team ] <- CreateTeamSpawnZoneEntity( chosenZone, team ) + if ( IsValid( oldEnt ) ) + oldEnt.Destroy() + } + + spawnStateSpawnzones.activeTeamSpawnzones[ team ] <- chosenZone + return spawnStateSpawnzones.activeTeamSpawnzones[ team ] } \ No newline at end of file -- cgit v1.2.3