From f0b10e1d05a23562e0af7a76af520a3f40ef730c Mon Sep 17 00:00:00 2001 From: DBmaoha <56738369+DBmaoha@users.noreply.github.com> Date: Sat, 29 Apr 2023 01:55:50 +0800 Subject: Improve AI spawn functions (#626) * initial commit * remove modded contents * Update _ai_gamemodes.gnut * restore HandleScoreEvent() checks * fix squad minimap icon * fix compile error * Update _gamemode_aitdm.nut * update formatting as requested changes * add missing reaper highlight * Formatting * Newline test * Did this work? --------- Co-authored-by: F1F7Y <64418963+F1F7Y@users.noreply.github.com> (cherry picked from commit 5fbd1edf7942a542605f6274604ed0e0ba9f20b7) --- .../scripts/vscripts/gamemodes/_gamemode_fw.nut | 4 +- .../scripts/vscripts/gamemodes/_ai_gamemodes.gnut | 115 +++++++++------- .../scripts/vscripts/gamemodes/_gamemode_aitdm.nut | 146 +++++++++++++++------ 3 files changed, 176 insertions(+), 89 deletions(-) diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_fw.nut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_fw.nut index b025ff0a1..8a26f8bff 100644 --- a/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_fw.nut +++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_fw.nut @@ -117,8 +117,8 @@ void function GamemodeFW_Init() // _battery_port.gnut needs this RegisterSignal( "BatteryActivate" ) - AiGameModes_SetGruntWeapons( [ "mp_weapon_rspn101", "mp_weapon_dmr", "mp_weapon_r97", "mp_weapon_lmg" ] ) - AiGameModes_SetSpectreWeapons( [ "mp_weapon_hemlok_smg", "mp_weapon_doubletake", "mp_weapon_mastiff" ] ) + AiGameModes_SetNPCWeapons( "npc_soldier", [ "mp_weapon_rspn101", "mp_weapon_dmr", "mp_weapon_r97", "mp_weapon_lmg" ] ) + AiGameModes_SetNPCWeapons( "npc_spectre", [ "mp_weapon_hemlok_smg", "mp_weapon_doubletake", "mp_weapon_mastiff" ] ) AddCallback_EntitiesDidLoad( LoadEntities ) AddCallback_GameStateEnter( eGameState.Prematch, OnFWGamePrematch ) diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_ai_gamemodes.gnut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_ai_gamemodes.gnut index 0fad768c5..78a9a208e 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_ai_gamemodes.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_ai_gamemodes.gnut @@ -1,7 +1,6 @@ global function AiGameModes_Init -global function AiGameModes_SetGruntWeapons -global function AiGameModes_SetSpectreWeapons +global function AiGameModes_SetNPCWeapons global function AiGameModes_SpawnDropShip global function AiGameModes_SpawnDropPod @@ -15,25 +14,20 @@ const INTRO_DROPSHIP_CUTOFF = 2000 struct { - array< string > gruntWeapons = [ "mp_weapon_rspn101" ] - array< string > spectreWeapons = [ "mp_weapon_hemlok_smg" ] + table< string, array > npcWeaponsTable // npcs have their default weapon in aisettings file } file void function AiGameModes_Init() { - } //------------------------------------------------------ -void function AiGameModes_SetGruntWeapons( array< string > weapons ) +void function AiGameModes_SetNPCWeapons( string npcClass, array weapons ) { - file.gruntWeapons = weapons -} - -void function AiGameModes_SetSpectreWeapons( array< string > weapons ) -{ - file.spectreWeapons = weapons + if ( !( npcClass in file.npcWeaponsTable ) ) + file.npcWeaponsTable[ npcClass ] <- [] + file.npcWeaponsTable[ npcClass ] = weapons } //------------------------------------------------------ @@ -59,7 +53,7 @@ void function AiGameModes_SpawnDropShip( vector pos, vector rot, int team, int c foreach ( guy in guys ) { - ReplaceWeapon( guy, file.gruntWeapons[ RandomInt( file.gruntWeapons.len() ) ], [] ) + SetUpNPCWeapons( guy ) guy.EnableNPCFlag( NPC_ALLOW_PATROL | NPC_ALLOW_INVESTIGATE | NPC_ALLOW_HAND_SIGNALS | NPC_ALLOW_FLEE ) } @@ -70,29 +64,21 @@ void function AiGameModes_SpawnDropShip( vector pos, vector rot, int team, int c void function AiGameModes_SpawnDropPod( vector pos, vector rot, int team, string content /*( ͡° ͜ʖ ͡°)*/, void functionref( array guys ) squadHandler = null ) { - string squadName = MakeSquadName( team, UniqueString( "" ) ) - array guys - entity pod = CreateDropPod( pos, <0,0,0> ) InitFireteamDropPod( pod ) - + + waitthread LaunchAnimDropPod( pod, "pod_testpath", pos, rot ) + + string squadName = MakeSquadName( team, UniqueString( "" ) ) + array guys for ( int i = 0; i < 4 ;i++ ) { entity npc = CreateNPC( content, team, pos,<0,0,0> ) DispatchSpawn( npc ) SetSquad( npc, squadName ) - switch ( content ) - { - case "npc_soldier": - ReplaceWeapon( npc, file.gruntWeapons[ RandomInt( file.gruntWeapons.len() ) ], [] ) - break - - case "npc_spectre": - ReplaceWeapon( npc, file.spectreWeapons[ RandomInt( file.spectreWeapons.len() ) ], [] ) - break - } + SetUpNPCWeapons( npc ) npc.SetParent( pod, "ATTACH", true ) @@ -100,25 +86,26 @@ void function AiGameModes_SpawnDropPod( vector pos, vector rot, int team, string guys.append( npc ) } - // The order here is different so we can show on minimap while were still falling + ActivateFireteamDropPod( pod, guys ) + + // start searching for enemies if ( squadHandler != null ) thread squadHandler( guys ) - - waitthread LaunchAnimDropPod( pod, "pod_testpath", pos, rot ) - - ActivateFireteamDropPod( pod, guys ) } +const float REAPER_WARPFALL_DELAY = 4.7 // same as fd does void function AiGameModes_SpawnReaper( vector pos, vector rot, int team, string aiSettings = "", void functionref( entity reaper ) reaperHandler = null ) { - thread Reaper_Spawnpoint( pos, team, 11.2 ) + float reaperLandTime = REAPER_WARPFALL_DELAY + 1.2 // reaper takes ~1.2s to warpfall + thread HotDrop_Spawnpoint( pos, team, reaperLandTime, false, damagedef_reaper_fall ) - wait 10 - // spawn reapers right before it warpfalls, or round_end clean up will crash the game + wait REAPER_WARPFALL_DELAY entity reaper = CreateSuperSpectre( team, pos, rot ) + reaper.EndSignal( "OnDestroy" ) // reaper highlight Highlight_SetFriendlyHighlight( reaper, "sp_enemy_pilot" ) - reaper.Highlight_SetParam( 1, 0, < 3,3,3 > ) + reaper.Highlight_SetParam( 1, 0, < 1,1,1 > ) + SetDefaultMPEnemyHighlight( reaper ) Highlight_SetEnemyHighlight( reaper, "enemy_titan" ) SetSpawnOption_Titanfall( reaper ) @@ -127,15 +114,18 @@ void function AiGameModes_SpawnReaper( vector pos, vector rot, int team, string if ( aiSettings != "" ) SetSpawnOption_AISettings( reaper, aiSettings ) + HideName( reaper ) // prevent flash a name onto it DispatchSpawn( reaper ) - + + reaper.WaitSignal( "WarpfallComplete" ) + ShowName( reaper ) // show name again after drop if ( reaperHandler != null ) thread reaperHandler( reaper ) } // copied from cl_replacement_titan_hud.gnut -void function Reaper_Spawnpoint( vector origin, int team, float impactTime, bool hasFriendlyWarning = false ) +void function HotDrop_Spawnpoint( vector origin, int team, float impactTime, bool hasFriendlyWarning = false, int damageDef = -1 ) { array targetEffects = [] vector surfaceNormal = < 0, 0, 1 > @@ -146,32 +136,50 @@ void function Reaper_Spawnpoint( vector origin, int team, float impactTime, bool { entity effectFriendly = StartParticleEffectInWorld_ReturnEntity( index, origin, surfaceNormal ) SetTeam( effectFriendly, team ) - EffectSetControlPointVector( effectFriendly, 1, < 128,188,255 > ) + EffectSetControlPointVector( effectFriendly, 1, FRIENDLY_COLOR_FX ) effectFriendly.kv.VisibilityFlags = ENTITY_VISIBLE_TO_FRIENDLY + effectFriendly.DisableHibernation() // prevent it from fading out targetEffects.append( effectFriendly ) } entity effectEnemy = StartParticleEffectInWorld_ReturnEntity( index, origin, surfaceNormal ) SetTeam( effectEnemy, team ) - EffectSetControlPointVector( effectEnemy, 1, < 255,99,0 > ) + EffectSetControlPointVector( effectEnemy, 1, ENEMY_COLOR_FX ) effectEnemy.kv.VisibilityFlags = ENTITY_VISIBLE_TO_ENEMY + effectEnemy.DisableHibernation() // prevent it from fading out targetEffects.append( effectEnemy ) - + + // so enemy npcs will mostly avoid them + entity damageAreaInfo + if ( damageDef > -1 ) + { + damageAreaInfo = CreateEntity( "info_target" ) + DispatchSpawn( damageAreaInfo ) + AI_CreateDangerousArea_DamageDef( damageDef, damageAreaInfo, team, true, true ) + } + wait impactTime + // clean up foreach( entity targetEffect in targetEffects ) { if ( IsValid( targetEffect ) ) EffectStop( targetEffect ) } + if ( IsValid( damageAreaInfo ) ) + damageAreaInfo.Destroy() } // including aisettings stuff specifically for at bounty titans +const float TITANFALL_WARNING_DURATION = 5.0 void function AiGameModes_SpawnTitan( vector pos, vector rot, int team, string setFile, string aiSettings = "", void functionref( entity titan ) titanHandler = null ) { entity titan = CreateNPCTitan( setFile, TEAM_BOTH, pos, rot ) SetSpawnOption_Titanfall( titan ) SetSpawnOption_Warpfall( titan ) + + // modified: do a hotdrop spawnpoint warning + thread HotDrop_Spawnpoint( pos, team, TITANFALL_WARNING_DURATION, false, damagedef_titan_fall ) if ( aiSettings != "" ) SetSpawnOption_AISettings( titan, aiSettings ) @@ -182,12 +190,27 @@ void function AiGameModes_SpawnTitan( vector pos, vector rot, int team, string s thread titanHandler( titan ) } -// entity.ReplaceActiveWeapon gave grunts archers sometimes, this is my replacement for it -void function ReplaceWeapon( entity guy, string weapon, array mods ) +void function SetUpNPCWeapons( entity guy ) { - guy.TakeActiveWeapon() - guy.GiveWeapon( weapon, mods ) - guy.SetActiveWeaponByName( weapon ) + string className = guy.GetClassName() + + array mainWeapons + if ( className in file.npcWeaponsTable ) + mainWeapons = file.npcWeaponsTable[ className ] + + if ( mainWeapons.len() == 0 ) // no valid weapons + return + + // take off existing main weapons, or sometimes they'll have a archer by default + foreach ( entity weapon in guy.GetMainWeapons() ) + guy.TakeWeapon( weapon.GetWeaponClassName() ) + + if ( mainWeapons.len() > 0 ) + { + string weaponName = mainWeapons[ RandomInt( mainWeapons.len() ) ] + guy.GiveWeapon( weaponName ) + guy.SetActiveWeaponByName( weaponName ) + } } // Checks if we can spawn a dropship at a node, this should guarantee dropship ziplines diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_aitdm.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_aitdm.nut index fae778d6d..702e45002 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_aitdm.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_aitdm.nut @@ -1,22 +1,36 @@ untyped global function GamemodeAITdm_Init -const SQUADS_PER_TEAM = 3 +// these are now default settings +const int SQUADS_PER_TEAM = 4 -const REAPERS_PER_TEAM = 2 +const int REAPERS_PER_TEAM = 2 -const LEVEL_SPECTRES = 125 -const LEVEL_STALKERS = 380 -const LEVEL_REAPERS = 500 +const int LEVEL_SPECTRES = 125 +const int LEVEL_STALKERS = 380 +const int LEVEL_REAPERS = 500 + +// add settings +global function AITdm_SetSquadsPerTeam +global function AITdm_SetReapersPerTeam +global function AITdm_SetLevelSpectres +global function AITdm_SetLevelStalkers +global function AITdm_SetLevelReapers struct { // Due to team based escalation everything is an array - array< int > levels = [ LEVEL_SPECTRES, LEVEL_SPECTRES ] + array< int > levels = [] // Initilazed in `Spawner_Threaded` array< array< string > > podEntities = [ [ "npc_soldier" ], [ "npc_soldier" ] ] array< bool > reapers = [ false, false ] -} file + // default settings + int squadsPerTeam = SQUADS_PER_TEAM + int reapersPerTeam = REAPERS_PER_TEAM + int levelSpectres = LEVEL_SPECTRES + int levelStalkers = LEVEL_STALKERS + int levelReapers = LEVEL_REAPERS +} file void function GamemodeAITdm_Init() { @@ -34,18 +48,47 @@ void function GamemodeAITdm_Init() if ( GetCurrentPlaylistVarInt( "aitdm_archer_grunts", 0 ) == 0 ) { - AiGameModes_SetGruntWeapons( [ "mp_weapon_rspn101", "mp_weapon_dmr", "mp_weapon_r97", "mp_weapon_lmg" ] ) - AiGameModes_SetSpectreWeapons( [ "mp_weapon_hemlok_smg", "mp_weapon_doubletake", "mp_weapon_mastiff" ] ) + AiGameModes_SetNPCWeapons( "npc_soldier", [ "mp_weapon_rspn101", "mp_weapon_dmr", "mp_weapon_r97", "mp_weapon_lmg" ] ) + AiGameModes_SetNPCWeapons( "npc_spectre", [ "mp_weapon_hemlok_smg", "mp_weapon_doubletake", "mp_weapon_mastiff" ] ) + AiGameModes_SetNPCWeapons( "npc_stalker", [ "mp_weapon_hemlok_smg", "mp_weapon_lstar", "mp_weapon_mastiff" ] ) } else { - AiGameModes_SetGruntWeapons( [ "mp_weapon_rocket_launcher" ] ) - AiGameModes_SetSpectreWeapons( [ "mp_weapon_rocket_launcher" ] ) + AiGameModes_SetNPCWeapons( "npc_soldier", [ "mp_weapon_rocket_launcher" ] ) + AiGameModes_SetNPCWeapons( "npc_spectre", [ "mp_weapon_rocket_launcher" ] ) + AiGameModes_SetNPCWeapons( "npc_stalker", [ "mp_weapon_rocket_launcher" ] ) } ScoreEvent_SetupEarnMeterValuesForMixedModes() } +// add settings +void function AITdm_SetSquadsPerTeam( int squads ) +{ + file.squadsPerTeam = squads +} + +void function AITdm_SetReapersPerTeam( int reapers ) +{ + file.reapersPerTeam = reapers +} + +void function AITdm_SetLevelSpectres( int level ) +{ + file.levelSpectres = level +} + +void function AITdm_SetLevelStalkers( int level ) +{ + file.levelStalkers = level +} + +void function AITdm_SetLevelReapers( int level ) +{ + file.levelReapers = level +} +// + // Starts skyshow, this also requiers AINs but doesn't crash if they're missing void function OnPrematchStart() { @@ -74,11 +117,9 @@ void function HandleScoreEvent( entity victim, entity attacker, var damageInfo ) // Basic checks if ( victim == attacker || !( attacker.IsPlayer() || attacker.IsTitan() ) || GetGameState() != eGameState.Playing ) return - // Hacked spectre filter if ( victim.GetOwner() == attacker ) return - // NPC titans without an owner player will not count towards any team's score if ( attacker.IsNPC() && attacker.IsTitan() && !IsValid( GetPetTitanOwner( attacker ) ) ) return @@ -193,7 +234,7 @@ void function SpawnIntroBatch_Threaded( int team ) int ships = shipNodes.len() - for ( int i = 0; i < SQUADS_PER_TEAM; i++ ) + for ( int i = 0; i < file.squadsPerTeam; i++ ) { if ( pods != 0 || ships == 0 ) { @@ -238,6 +279,7 @@ void function Spawner_Threaded( int team ) // used to index into escalation arrays int index = team == TEAM_MILITIA ? 0 : 1 + file.levels = [ file.levelSpectres, file.levelSpectres ] // due we added settings, should init levels here! while( true ) { @@ -252,7 +294,7 @@ void function Spawner_Threaded( int team ) if ( file.reapers[ index ] ) { array< entity > points = SpawnPoints_GetDropPod() - if ( reaperCount < REAPERS_PER_TEAM ) + if ( reaperCount < file.reapersPerTeam ) { entity node = points[ GetSpawnPointIndex( points, team ) ] waitthread AiGameModes_SpawnReaper( node.GetOrigin(), node.GetAngles(), team, "npc_super_spectre_aitdm", ReaperHandler ) @@ -260,7 +302,7 @@ void function Spawner_Threaded( int team ) } // NORMAL SPAWNS - if ( count < SQUADS_PER_TEAM * 4 - 2 ) + if ( count < file.squadsPerTeam * 4 - 2 ) { string ent = file.podEntities[ index ][ RandomInt( file.podEntities[ index ].len() ) ] @@ -306,19 +348,19 @@ void function Escalate( int team ) // Based on score escalate a team switch ( file.levels[ index ] ) { - case LEVEL_SPECTRES: - file.levels[ index ] = LEVEL_STALKERS + case file.levelSpectres: + file.levels[ index ] = file.levelStalkers file.podEntities[ index ].append( "npc_spectre" ) SetGlobalNetInt( defcon, 2 ) return - case LEVEL_STALKERS: - file.levels[ index ] = LEVEL_REAPERS + case file.levelStalkers: + file.levels[ index ] = file.levelReapers file.podEntities[ index ].append( "npc_stalker" ) SetGlobalNetInt( defcon, 3 ) return - case LEVEL_REAPERS: + case file.levelReapers: file.reapers[ index ] = true SetGlobalNetInt( defcon, 4 ) return @@ -355,30 +397,47 @@ int function GetSpawnPointIndex( array< entity > points, int team ) // AI can also flee deeper into their zone suggesting someone spent way too much time on this void function SquadHandler( array guys ) { + int team = guys[0].GetTeam() + // show the squad enemy radar + array players = GetPlayerArrayOfEnemies( team ) + foreach ( entity guy in guys ) + { + if ( IsAlive( guy ) ) + { + foreach ( player in players ) + guy.Minimap_AlwaysShow( 0, player ) + } + } + // Not all maps have assaultpoints / have weird assault points ( looking at you ac ) // So we use enemies with a large radius - array< entity > points = GetNPCArrayOfEnemies( guys[0].GetTeam() ) - - if ( points.len() == 0 ) + while ( GetNPCArrayOfEnemies( team ).len() == 0 ) // if we can't find any enemy npcs, keep waiting + WaitFrame() + + // our waiting is end, check if any soldiers left + bool squadAlive = false + foreach ( entity guy in guys ) + { + if ( IsAlive( guy ) ) + squadAlive = true + else + guys.removebyvalue( guy ) + } + if ( !squadAlive ) return + + array points = GetNPCArrayOfEnemies( team ) vector point point = points[ RandomInt( points.len() ) ].GetOrigin() - array players = GetPlayerArrayOfEnemies( guys[0].GetTeam() ) - - // Setup AI + // Setup AI, first assault point foreach ( guy in guys ) { guy.EnableNPCFlag( NPC_ALLOW_PATROL | NPC_ALLOW_INVESTIGATE | NPC_ALLOW_HAND_SIGNALS | NPC_ALLOW_FLEE ) guy.AssaultPoint( point ) guy.AssaultSetGoalRadius( 1600 ) // 1600 is minimum for npc_stalker, works fine for others - - // show on enemy radar - foreach ( player in players ) - guy.Minimap_AlwaysShow( 0, player ) - - + //thread AITdm_CleanupBoredNPCThread( guy ) } @@ -396,16 +455,21 @@ void function SquadHandler( array guys ) // Stop func if our squad has been killed off if ( guys.len() == 0 ) return + } + + // Get point and send our whole squad to it + points = GetNPCArrayOfEnemies( team ) + if ( points.len() == 0 ) // can't find any points here + continue - // Get point and send guy to it - points = GetNPCArrayOfEnemies( guy.GetTeam() ) - if ( points.len() == 0 ) - continue - - point = points[ RandomInt( points.len() ) ].GetOrigin() - - guy.AssaultPoint( point ) + point = points[ RandomInt( points.len() ) ].GetOrigin() + + foreach ( guy in guys ) + { + if ( IsAlive( guy ) ) + guy.AssaultPoint( point ) } + wait RandomFloatRange(5.0,15.0) } } -- cgit v1.2.3