@@ -18,7 +18,7 @@ public static class ConfigValidator
1818 /// If the config is invalid, the reasons are logged.
1919 /// </summary>
2020 /// <returns>Whether the given match is valid and can be started without issues.</returns>
21- public static bool Validate ( MatchConfigurationT config )
21+ public static bool Validate ( MatchConfigurationT config , bool surpressWarnings = false )
2222 {
2323 bool valid = true ;
2424 PsyonixLoadouts . Reset ( ) ;
@@ -48,7 +48,7 @@ public static bool Validate(MatchConfigurationT config)
4848 config . PlayerConfigurations ??= new ( ) ;
4949 config . ScriptConfigurations ??= new ( ) ;
5050
51- valid = ValidatePlayers ( ctx , config . PlayerConfigurations ) && valid ;
51+ valid = ValidatePlayers ( ctx , config . PlayerConfigurations , surpressWarnings ) && valid ;
5252 valid = ValidateScripts ( ctx , config . ScriptConfigurations ) && valid ;
5353
5454 Logger . LogDebug ( valid ? "Match config is valid." : "Match config is invalid!" ) ;
@@ -57,13 +57,17 @@ public static bool Validate(MatchConfigurationT config)
5757
5858 private static bool ValidatePlayers (
5959 ConfigContextTracker ctx ,
60- List < PlayerConfigurationT > players
60+ List < PlayerConfigurationT > players ,
61+ bool surpressWarnings
6162 )
6263 {
6364 bool valid = true ;
6465 int humanCount = 0 ;
6566 int humanIndex = - 1 ;
6667
68+ // map of agentid -> rootdir,runcmd
69+ Dictionary < string , ( string rootDir , string runCmd ) > agentIdTracker = new ( ) ;
70+
6771 for ( int i = 0 ; i < players . Count ; i ++ )
6872 {
6973 using var _ = ctx . Begin ( $ "{ Fields . CarsList } [{ i } ]") ;
@@ -99,6 +103,46 @@ List<PlayerConfigurationT> players
99103 bot . Loadout . LoadoutPaint ??= new ( ) ;
100104
101105 player . PlayerId = $ "{ bot . AgentId } /{ player . Team } /{ i } ". GetHashCode ( ) ;
106+
107+ // Dont validate agent id for bots that will be manually started
108+ if ( ! surpressWarnings && ! string . IsNullOrEmpty ( bot . RunCommand ) )
109+ {
110+ // Reduce user confusion around how agent ids should be used
111+ // Same bot == same agent id, different bot == different agent id
112+ // This is not a hard requirement, so we just log a warning
113+ // We check for "same bot" by comparing RootDir and RunCommand
114+ if ( agentIdTracker . TryGetValue ( bot . AgentId , out var existing ) )
115+ {
116+ if (
117+ existing . rootDir != bot . RootDir
118+ || existing . runCmd != bot . RunCommand
119+ )
120+ {
121+ string errorStr ;
122+
123+ if ( existing . rootDir != bot . RootDir )
124+ {
125+ errorStr =
126+ existing . runCmd != bot . RunCommand
127+ ? "RootDirs and RunCommands"
128+ : "RootDirs" ;
129+ }
130+ else
131+ {
132+ errorStr = "RunCommands" ;
133+ }
134+
135+ Logger . LogWarning (
136+ $ "Potential agent ID conflict: { bot . AgentId } is used by multiple bots with different { errorStr } .\n "
137+ + "Agent configs using the same ID may get used interchangeably. Agents that behave differently should have unique IDs."
138+ ) ;
139+ }
140+ }
141+ else
142+ {
143+ agentIdTracker [ bot . AgentId ] = ( bot . RootDir , bot . RunCommand ) ;
144+ }
145+ }
102146 break ;
103147 case PsyonixBotT bot :
104148 string skill = bot . BotSkill switch
@@ -172,6 +216,7 @@ List<ScriptConfigurationT> scripts
172216 )
173217 {
174218 bool valid = true ;
219+ HashSet < string > agentIds = new ( ) ;
175220
176221 for ( int i = 0 ; i < scripts . Count ; i ++ )
177222 {
@@ -192,6 +237,16 @@ List<ScriptConfigurationT> scripts
192237 script . RunCommand ??= "" ;
193238 script . RootDir ??= "" ;
194239 script . ScriptId = $ "{ script . AgentId } /{ Team . Scripts } /{ i } ". GetHashCode ( ) ;
240+
241+ // Scripts must have unique agent ids
242+ if ( ! agentIds . Add ( script . AgentId ) )
243+ {
244+ Logger . LogError (
245+ $ "{ ctx . ToStringWithEnd ( Fields . AgentAgentId ) } \" { script . AgentId } \" is already in use. "
246+ + "Each script must have a unique agent ID."
247+ ) ;
248+ valid = false ;
249+ }
195250 }
196251
197252 return valid ;
0 commit comments