From c315600b13c92372d0ae0765ce9c1fc5bbd16dc3 Mon Sep 17 00:00:00 2001 From: BobTheBob <32057864+BobTheBob9@users.noreply.github.com> Date: Wed, 19 Jan 2022 04:55:38 +0000 Subject: spawnpoint algo refactors and somewhat functional spawnzones algo --- .../scripts/vscripts/gamemodes/_gamemode_mfd.nut | 56 --- .../scripts/vscripts/gamemodes/_gamemode_ps.nut | 200 +---------- .../mod/scripts/vscripts/mp/spawn.nut | 386 ++++++++++++++++++--- 3 files changed, 345 insertions(+), 297 deletions(-) (limited to 'Northstar.CustomServers/mod') diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_mfd.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_mfd.nut index 1c776ede..11cb71f6 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_mfd.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_mfd.nut @@ -1,6 +1,5 @@ untyped global function GamemodeMfd_Init -global function RateSpawnpoints_Frontline struct { entity imcLastMark @@ -191,59 +190,4 @@ void function UpdateMarksForKill( entity victim, entity attacker, var damageInfo if ( attacker.IsPlayer() ) attacker.SetPlayerGameStat( PGS_ASSAULT_SCORE, attacker.GetPlayerGameStat( PGS_ASSAULT_SCORE ) + 1 ) } -} - -// could probably put this in spawn.nut? only here because i believe it's the main mode that uses this func -void function RateSpawnpoints_Frontline( int checkClass, array spawnpoints, int team, entity player ) -{ - Frontline frontline = GetFrontline( player.GetTeam() ) - - // heavily based on ctf spawn algo iteration 4, only changes it at the end - 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 maxFriendlySpawnDist - - foreach ( entity spawn in startSpawns ) - { - foreach ( entity otherSpawn in startSpawns ) - { - float dist = Distance2D( spawn.GetOrigin(), otherSpawn.GetOrigin() ) - if ( dist > maxFriendlySpawnDist ) - maxFriendlySpawnDist = dist - } - - averageFriendlySpawns += spawn.GetOrigin() - } - - averageFriendlySpawns /= startSpawns.len() - - // 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 / maxFriendlySpawnDist ) / 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 ) - - // 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( spawn.GetOrigin(), frontline.friendlyCenter ) / baseDistance ) ) - rating *= fabs( frontline.combatDir.y - Normalize( spawn.GetOrigin() - averageFriendlySpawns ).y ) - - spawn.CalculateRating( checkClass, player.GetTeam(), rating, rating ) - } } \ No newline at end of file diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ps.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ps.nut index a02b9072..57355ad8 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ps.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ps.nut @@ -1,6 +1,6 @@ untyped global function GamemodePs_Init -global function RateSpawnpoints_SpawnZones +//global function RateSpawnpoints_SpawnZones struct { array spawnzones @@ -21,8 +21,10 @@ void function GamemodePs_Init() SetTimeoutWinnerDecisionFunc( CheckScoreForDraw ) // spawnzone stuff - AddCallback_OnPlayerKilled( CheckSpawnzoneSuspiciousDeaths ) - AddSpawnCallbackEditorClass( "trigger_multiple", "trigger_mp_spawn_zone", SpawnzoneTriggerInit ) + SetShouldCreateMinimapSpawnZones( true ) + + //AddCallback_OnPlayerKilled( CheckSpawnzoneSuspiciousDeaths ) + //AddSpawnCallbackEditorClass( "trigger_multiple", "trigger_mp_spawn_zone", SpawnzoneTriggerInit ) file.militiaPreviousSpawnZones = [ null, null, null ] file.imcPreviousSpawnZones = [ null, null, null ] @@ -30,20 +32,8 @@ void function GamemodePs_Init() void function GiveScoreForPlayerKill( entity victim, entity attacker, var damageInfo ) { - if ( victim != attacker && victim.IsPlayer() && attacker.IsPlayer() && GetGameState() == eGameState.Playing ) + if ( victim != attacker && victim.IsPlayer() && attacker.IsPlayer() || GetGameState() != eGameState.Playing ) AddTeamScore( attacker.GetTeam(), 1 ) - - table alreadyAssisted - foreach( DamageHistoryStruct attackerInfo in victim.e.recentDamageHistory ) - { - bool exists = attackerInfo.attacker.GetEncodedEHandle() in alreadyAssisted ? true : false - if( attackerInfo.attacker != attacker && !exists ) - { - alreadyAssisted[attackerInfo.attacker.GetEncodedEHandle()] <- true - attackerInfo.attacker.AddToPlayerGameStat( PGS_ASSISTS, 1 ) - } - } - } int function CheckScoreForDraw() @@ -54,180 +44,4 @@ int function CheckScoreForDraw() return TEAM_MILITIA return TEAM_UNASSIGNED -} - -// spawnzone logic -void function SpawnzoneTriggerInit( entity spawnzone ) -{ - // initialise spawnzone script vars - spawnzone.s.lastDeathRateCheck <- 0.0 - spawnzone.s.numRecentSuspiciousDeaths <- 0 - spawnzone.s.minimapObj <- null - - file.spawnzones.append( spawnzone ) -} - -void function SetNewSpawnzoneForTeam( int team, entity spawnzone ) -{ - entity minimapObj = CreatePropScript( $"models/dev/empty_model.mdl", spawnzone.GetOrigin() ) - SetTeam( minimapObj, team ) - minimapObj.Minimap_SetObjectScale( 100.0 / Distance2D( < 0, 0, 0 >, spawnzone.GetBoundingMaxs() ) ) - minimapObj.Minimap_SetAlignUpright( true ) - minimapObj.Minimap_AlwaysShow( TEAM_IMC, null ) - minimapObj.Minimap_AlwaysShow( TEAM_MILITIA, null ) - minimapObj.Minimap_SetHeightTracking( true ) - minimapObj.Minimap_SetZOrder( MINIMAP_Z_OBJECT ) - - if ( team == TEAM_IMC ) - { - if ( IsValid( file.imcActiveSpawnZone ) ) - file.imcActiveSpawnZone.s.minimapObj.Destroy() - - // update last 3 zones - file.imcPreviousSpawnZones[ 2 ] = file.imcPreviousSpawnZones[ 1 ] - file.imcPreviousSpawnZones[ 1 ] = file.imcPreviousSpawnZones[ 0 ] - file.imcPreviousSpawnZones[ 0 ] = file.imcActiveSpawnZone - - file.imcActiveSpawnZone = spawnzone - minimapObj.Minimap_SetCustomState( eMinimapObject_prop_script.SPAWNZONE_IMC ) - } - else - { - if ( IsValid( file.militiaActiveSpawnZone ) ) - file.militiaActiveSpawnZone.s.minimapObj.Destroy() - - // update last 3 zones - file.militiaPreviousSpawnZones[ 2 ] = file.militiaPreviousSpawnZones[ 1 ] - file.militiaPreviousSpawnZones[ 1 ] = file.militiaPreviousSpawnZones[ 0 ] - file.militiaPreviousSpawnZones[ 0 ] = file.militiaActiveSpawnZone - - file.militiaActiveSpawnZone = spawnzone - minimapObj.Minimap_SetCustomState( eMinimapObject_prop_script.SPAWNZONE_MIL ) - } - - minimapObj.DisableHibernation() - - spawnzone.s.minimapObj = minimapObj - spawnzone.s.lastDeathRateCheck = 0.0 - spawnzone.s.numRecentSuspiciousDeaths = Time() -} - -void function CheckSpawnzoneSuspiciousDeaths( entity victim, entity attacker, var damageInfo ) -{ - if ( victim.s.respawnTime + 10.0 < Time() ) - return - - entity spawnzone - if ( victim.GetTeam() == TEAM_IMC ) - spawnzone = file.imcActiveSpawnZone - else - spawnzone = file.militiaActiveSpawnZone - - if ( Distance2D( victim.GetOrigin(), spawnzone.GetOrigin() ) <= Distance2D( < 0, 0, 0 >, spawnzone.GetBoundingMaxs() ) * 1.2 ) - spawnzone.s.numRecentSuspiciousDeaths++ -} - -entity function FindNewSpawnZone( int team ) -{ - array startSpawns = SpawnPoints_GetPilotStart( team ) - array enemyStartSpawns = SpawnPoints_GetPilotStart( GetOtherTeam( team ) ) - - // get average friendly startspawn position - 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() - - array validZones - array enemyPlayers = GetPlayerArrayOfTeam( GetOtherTeam( team ) ) - float averageFriendlySpawnDist - - foreach ( entity spawnzone in file.spawnzones ) - { - if ( team == TEAM_IMC && file.imcPreviousSpawnZones.contains( spawnzone ) ) - continue - else if ( file.militiaPreviousSpawnZones.contains( spawnzone ) ) - continue - - // check if it's too far from startspawns - float friendlySpawnDist = Distance2D( spawnzone.GetOrigin(), averageFriendlySpawns ) - if ( friendlySpawnDist > Distance2D( averageFriendlySpawns, averageEnemySpawns ) * 1.2 ) - continue - - // check if it's safe atm - bool safe = true - foreach ( entity enemy in enemyPlayers ) - { - if ( Distance2D( enemy.GetOrigin(), spawnzone.GetOrigin() ) < Distance2D( < 0, 0, 0 >, spawnzone.GetBoundingMaxs() ) * 1.2 ) - { - safe = false - break - } - } - - if ( !safe ) - continue - - averageFriendlySpawnDist += friendlySpawnDist - validZones.append( spawnzone ) - } - - averageFriendlySpawnDist /= validZones.len() - - array realValidZones = clone validZones - foreach ( entity validzone in validZones ) - { - if ( Distance2D( averageFriendlySpawns, validzone.GetOrigin() ) < averageFriendlySpawnDist * 1.4 ) - realValidZones.append( validzone ) - } - - entity spawnzone = realValidZones.getrandom() - SetNewSpawnzoneForTeam( team, spawnzone ) - - return spawnzone -} - -void function RateSpawnpoints_SpawnZones( int checkClass, array spawnpoints, int team, entity player ) -{ - entity spawnzone - if ( player.GetTeam() == TEAM_IMC ) - spawnzone = file.imcActiveSpawnZone - else - spawnzone = file.militiaActiveSpawnZone - - // spawnzones don't exist yet, create them now - if ( !IsValid( spawnzone ) ) - spawnzone = FindNewSpawnZone( player.GetTeam() ) - - // check if we should shift spawnzones - // if it's been more than 15 seconds since last check, reset - if ( spawnzone.s.lastDeathRateCheck + 15.0 < Time() ) - { - spawnzone.s.numRecentSuspiciousDeaths = 0 - spawnzone.s.lastDeathRateCheck = Time() - } - - // check if we've gone over the threshold for recent deaths too close to our current spawnzone - if ( spawnzone.s.numRecentSuspiciousDeaths >= GetPlayerArrayOfTeam( player.GetTeam() ).len() * 0.4 ) - // over the threshold, find a new spawn zone - spawnzone = FindNewSpawnZone( player.GetTeam() ) - - // rate spawnpoints - foreach ( entity spawn in spawnpoints ) - { - float rating = 0.0 - float distance = Distance2D( spawn.GetOrigin(), spawnzone.GetOrigin() ) - if ( distance < Distance2D( < 0, 0, 0 >, spawnzone.GetBoundingMaxs() ) ) - rating = 100.0 - else // max 35 rating if not in zone, rate by closest - rating = 35.0 * ( 1 - ( distance / 5000.0 ) ) - - spawn.CalculateRating( checkClass, player.GetTeam(), rating, rating ) - } -} +} \ 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 9b293524..5b8b2401 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/spawn.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/spawn.nut @@ -14,6 +14,13 @@ global function DeleteNoSpawnArea global function FindSpawnPoint global function RateSpawnpoints_Generic +global function RateSpawnpoints_Frontline + +global function SetSpawnZoneRatingFunc +global function SetShouldCreateMinimapSpawnZones +global function CreateTeamSpawnZoneEntity +global function RateSpawnpoints_SpawnZones +global function DecideSpawnZone_Generic struct NoSpawnArea { @@ -31,8 +38,6 @@ struct { array< bool functionref( entity, int ) > customSpawnpointValidationRules table noSpawnAreas - - array preferSpawnNodes } file void function Spawn_Init() @@ -42,24 +47,12 @@ void function Spawn_Init() AddSpawnCallback( "info_spawnpoint_titan", InitSpawnpoint ) AddSpawnCallback( "info_spawnpoint_titan_start", InitSpawnpoint ) + // callbacks for generic spawns AddCallback_EntitiesDidLoad( InitPreferSpawnNodes ) -} - -void function InitPreferSpawnNodes() -{ - foreach ( entity hardpoint in GetEntArrayByClass_Expensive( "info_hardpoint" ) ) - { - if ( !hardpoint.HasKey( "hardpointGroup" ) ) - continue - - if ( hardpoint.kv.hardpointGroup != "A" && hardpoint.kv.hardpointGroup != "B" && hardpoint.kv.hardpointGroup != "C" ) - continue - - file.preferSpawnNodes.append( hardpoint.GetOrigin() ) - } - //foreach ( entity frontline in GetEntArrayByClass_Expensive( "info_frontline" ) ) - // file.preferSpawnNodes.append( frontline.GetOrigin() ) + // callbacks for spawnzone spawns + AddCallback_GameStateEnter( eGameState.Prematch, ResetSpawnzones ) + AddSpawnCallbackEditorClass( "trigger_multiple", "trigger_mp_spawn_zone", AddSpawnZoneTrigger ) } void function InitSpawnpoint( entity spawnpoint ) @@ -273,36 +266,32 @@ bool function IsSpawnpointValid( entity spawnpoint, int team ) return false // los check - array enemyLosPlayers - if ( IsFFAGame() ) - enemyLosPlayers = GetPlayerArray() - else - enemyLosPlayers = GetPlayerArrayOfTeam( GetOtherTeam( team ) ) - - foreach ( entity enemyPlayer in enemyLosPlayers ) - { - if ( enemyPlayer.GetTeam() == team || !IsAlive( enemyPlayer ) ) - continue - - float dist = 1000.0 - // check fov, constant here is stolen from every other place this is done - if ( VectorDot_PlayerToOrigin( enemyPlayer, spawnpoint.GetOrigin() ) > 0.8 ) - dist /= 0.75 - - // check distance, constant here is basically arbitrary - if ( Distance( enemyPlayer.GetOrigin(), spawnpoint.GetOrigin() ) > dist ) - continue - - // check actual los - if ( TraceLineSimple( enemyPlayer.EyePosition(), spawnpoint.GetOrigin() + < 0, 0, 48 >, enemyPlayer ) == 1.0 ) - return false - } - - return true + return !spawnpoint.IsVisibleToEnemies( team ) } + +// SPAWNPOINT RATING FUNCS BELOW + +// generic +struct { + array preferSpawnNodes +} spawnStateGeneric + void function RateSpawnpoints_Generic( int checkClass, array spawnpoints, int team, entity player ) { + if ( !IsFFAGame() ) + { + // use frontline spawns in 2-team modes + RateSpawnpoints_Frontline( checkClass, spawnpoints, team, player ) + return + } + else + { + // todo: ffa spawns :terror: + } + + // old algo: keeping until we have a better ffa spawn algo + // i'm not a fan of this func, but i really don't have a better way to do this rn, and it's surprisingly good with los checks implemented now // calculate ratings for preferred nodes @@ -311,7 +300,7 @@ void function RateSpawnpoints_Generic( int checkClass, array spawnpoints // especially in ffa modes i could deffo see this falling apart a bit rn // perhaps dead players could be used to calculate some sort of activity rating? so high-activity points with an even balance of friendly/unfriendly players are preferred array preferSpawnNodeRatings - foreach ( vector preferSpawnNode in file.preferSpawnNodes ) + foreach ( vector preferSpawnNode in spawnStateGeneric.preferSpawnNodes ) { float currentRating @@ -354,13 +343,13 @@ void function RateSpawnpoints_Generic( int checkClass, array spawnpoints float petTitanModifier // scale how much a given spawnpoint matters to us based on how far it is from each node bool spawnHasRecievedInitialBonus = false - for ( int i = 0; i < file.preferSpawnNodes.len(); i++ ) + for ( int i = 0; i < spawnStateGeneric.preferSpawnNodes.len(); i++ ) { // bonus if autotitan is nearish - if ( IsAlive( player.GetPetTitan() ) && Distance( player.GetPetTitan().GetOrigin(), file.preferSpawnNodes[ i ] ) < 1200.0 ) + if ( IsAlive( player.GetPetTitan() ) && Distance( player.GetPetTitan().GetOrigin(), spawnStateGeneric.preferSpawnNodes[ i ] ) < 1200.0 ) petTitanModifier += 10.0 - float dist = Distance2D( spawnpoint.GetOrigin(), file.preferSpawnNodes[ i ] ) + float dist = Distance2D( spawnpoint.GetOrigin(), spawnStateGeneric.preferSpawnNodes[ i ] ) if ( dist > 750.0 ) continue @@ -384,4 +373,305 @@ void function RateSpawnpoints_Generic( int checkClass, array spawnpoints if ( rating != 0.0 || currentRating != 0.0 ) print( "rating = " + rating + ", internal rating = " + currentRating ) } +} + +void function InitPreferSpawnNodes() +{ + foreach ( entity hardpoint in GetEntArrayByClass_Expensive( "info_hardpoint" ) ) + { + if ( !hardpoint.HasKey( "hardpointGroup" ) ) + continue + + if ( hardpoint.kv.hardpointGroup != "A" && hardpoint.kv.hardpointGroup != "B" && hardpoint.kv.hardpointGroup != "C" ) + continue + + spawnStateGeneric.preferSpawnNodes.append( hardpoint.GetOrigin() ) + } + + //foreach ( entity frontline in GetEntArrayByClass_Expensive( "info_frontline" ) ) + // spawnStateGeneric.preferSpawnNodes.append( frontline.GetOrigin() ) +} + +// frontline +void function RateSpawnpoints_Frontline( int checkClass, array spawnpoints, int team, entity player ) +{ + Frontline frontline = GetFrontline( player.GetTeam() ) + + // heavily based on ctf spawn algo iteration 4, only changes it at the end + array startSpawns = SpawnPoints_GetPilotStart( team ) + array enemyStartSpawns = SpawnPoints_GetPilotStart( GetOtherTeam( team ) ) + + if ( startSpawns.len() == 0 || enemyStartSpawns.len() == 0 ) // ensure we don't crash + return + + // 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 maxFriendlySpawnDist + + foreach ( entity spawn in startSpawns ) + { + foreach ( entity otherSpawn in startSpawns ) + { + float dist = Distance2D( spawn.GetOrigin(), otherSpawn.GetOrigin() ) + if ( dist > maxFriendlySpawnDist ) + maxFriendlySpawnDist = dist + } + + averageFriendlySpawns += spawn.GetOrigin() + } + + averageFriendlySpawns /= startSpawns.len() + + // 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 ) + 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 ) + + // 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( spawn.GetOrigin(), frontline.friendlyCenter ) / baseDistance ) ) + rating *= fabs( frontline.combatDir.y - Normalize( spawn.GetOrigin() - averageFriendlySpawns ).y ) + + spawn.CalculateRating( checkClass, player.GetTeam(), rating, rating ) + } +} + +// spawnzones +struct { + array mapSpawnzoneTriggers + entity functionref( array, int ) spawnzoneRatingFunc + bool shouldCreateMinimapSpawnzones = false + + // for DecideSpawnZone_Generic + table activeTeamSpawnzones + table activeTeamSpawnzoneMinimapEnts +} spawnStateSpawnzones + +void function ResetSpawnzones() +{ + spawnStateSpawnzones.activeTeamSpawnzones.clear() + + foreach ( int team, entity minimapEnt in spawnStateSpawnzones.activeTeamSpawnzoneMinimapEnts ) + if ( IsValid( minimapEnt ) ) + minimapEnt.Destroy() + + spawnStateSpawnzones.activeTeamSpawnzoneMinimapEnts.clear() +} + +void function AddSpawnZoneTrigger( entity trigger ) +{ + trigger.s.spawnzoneRating <- 0.0 + spawnStateSpawnzones.mapSpawnzoneTriggers.append( trigger ) +} + +void function SetSpawnZoneRatingFunc( entity functionref( array, int ) ratingFunc ) +{ + spawnStateSpawnzones.spawnzoneRatingFunc = ratingFunc +} + +void function SetShouldCreateMinimapSpawnZones( bool shouldCreateMinimapSpawnzones ) +{ + spawnStateSpawnzones.shouldCreateMinimapSpawnzones = shouldCreateMinimapSpawnzones +} + +entity function CreateTeamSpawnZoneEntity( entity spawnzone, int team ) +{ + entity minimapObj = CreatePropScript( $"models/dev/empty_model.mdl", spawnzone.GetOrigin() ) + SetTeam( minimapObj, team ) + minimapObj.Minimap_SetObjectScale( 100.0 / Distance2D( < 0, 0, 0 >, spawnzone.GetBoundingMaxs() ) ) + minimapObj.Minimap_SetAlignUpright( true ) + minimapObj.Minimap_AlwaysShow( TEAM_IMC, null ) + minimapObj.Minimap_AlwaysShow( TEAM_MILITIA, null ) + minimapObj.Minimap_SetHeightTracking( true ) + minimapObj.Minimap_SetZOrder( MINIMAP_Z_OBJECT ) + + if ( team == TEAM_IMC ) + minimapObj.Minimap_SetCustomState( eMinimapObject_prop_script.SPAWNZONE_IMC ) + else + minimapObj.Minimap_SetCustomState( eMinimapObject_prop_script.SPAWNZONE_MIL ) + + minimapObj.DisableHibernation() + return minimapObj +} + +void function RateSpawnpoints_SpawnZones( int checkClass, array spawnpoints, int team, entity player ) +{ + if ( spawnStateSpawnzones.spawnzoneRatingFunc == null ) + spawnStateSpawnzones.spawnzoneRatingFunc = DecideSpawnZone_Generic + + // don't use spawnzones if we're using start spawns + if ( ShouldStartSpawn( player ) ) + { + RateSpawnpoints_Generic( checkClass, spawnpoints, team, player ) + return + } + + entity spawnzone = spawnStateSpawnzones.spawnzoneRatingFunc( spawnStateSpawnzones.mapSpawnzoneTriggers, player.GetTeam() ) + if ( !IsValid( spawnzone ) ) // no spawn zone, use generic algo + { + RateSpawnpoints_Generic( checkClass, spawnpoints, team, player ) + return + } + + // rate spawnpoints + foreach ( entity spawn in spawnpoints ) + { + float rating = 0.0 + float distance = Distance2D( spawn.GetOrigin(), spawnzone.GetOrigin() ) + if ( distance < Distance2D( < 0, 0, 0 >, spawnzone.GetBoundingMaxs() ) ) + rating = 100.0 + else // max 35 rating if not in zone, rate by closest + rating = 35.0 * ( 1 - ( distance / 5000.0 ) ) + + spawn.CalculateRating( checkClass, player.GetTeam(), rating, rating ) + } +} + +entity function DecideSpawnZone_Generic( array spawnzones, int team ) +{ + if ( spawnzones.len() == 0 ) + return null + + // 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 ) + + bool needNewZone = true + if ( team in spawnStateSpawnzones.activeTeamSpawnzones ) + { + foreach ( entity player in GetPlayerArray() ) + { + // couldn't get IsTouching, GetTouchingEntities or enter callbacks to work in testing, so doing this + if ( player.GetTeam() != team && spawnStateSpawnzones.activeTeamSpawnzones[ team ].ContainsPoint( player.GetOrigin() ) ) + break + } + + needNewZone = false + } + + if ( needNewZone ) + { + // find new zone + 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 + bool spawnzoneTaken = false + foreach ( int otherTeam, entity otherSpawnzone in spawnStateSpawnzones.activeTeamSpawnzones ) + { + if ( otherSpawnzone == spawnzone ) + { + spawnzoneTaken = true + break + } + } + + if ( spawnzoneTaken ) + continue + + // check zone validity + bool spawnzoneEvil = false + foreach ( entity player in GetPlayerArray() ) + { + // couldn't get IsTouching, GetTouchingEntities or enter callbacks to work in testing, so doing this + if ( player.GetTeam() != team && spawnzone.ContainsPoint( player.GetOrigin() ) ) + { + spawnzoneEvil = true + break + } + } + + print( averageFriendlySpawns ) + print( averageEnemySpawns ) + print( Distance2D( spawnzone.GetOrigin(), averageFriendlySpawns ) ) + print( Distance2D( spawnzone.GetOrigin(), averageEnemySpawns ) ) + // 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 ) + } + + 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() ) ] + + 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