@@ -293,69 +293,140 @@ private static string[] ParseCommand(string input)
293293 return result . ToArray ( ) ;
294294 }
295295
296- public bool Execute ( IRocketPlayer player , string command )
296+ private static void Reply ( IRocketPlayer player , string message , Color color )
297297 {
298- // Strip leading slash without allocating when absent
299- if ( command . Length > 0 && command [ 0 ] == '/' )
300- command = command . Substring ( 1 ) ;
301-
302- string [ ] commandParts = ParseCommand ( command ) ; // skip expensive Regex entirely.
303-
304- name = commandParts [ 0 ] ;
305-
306- // Build parameters array without Skip(1) which allocates an iterator.
307- string [ ] parameters ;
308- if ( commandParts . Length > 1 )
298+ if ( player is ConsolePlayer )
309299 {
310- parameters = new string [ commandParts . Length - 1 ] ;
311- System . Array . Copy ( commandParts , 1 , parameters , 0 , parameters . Length ) ;
300+ Logging . Logger . Log ( message , ConsoleColor . Gray ) ;
301+ return ;
312302 }
313- else
303+
304+ SDG . Unturned . SteamPlayer steamPlayer = SDG . Unturned . PlayerTool . getSteamPlayer (
305+ new Steamworks . CSteamID ( ulong . Parse ( player . Id ) ) ) ;
306+
307+ if ( steamPlayer != null )
314308 {
315- parameters = System . Array . Empty < string > ( ) ;
309+ SDG . Unturned . ChatManager . serverSendMessage (
310+ message ,
311+ color ,
312+ null ,
313+ steamPlayer
314+ ) ;
316315 }
316+ }
317+ private bool TryResolveCommand (
318+ ref IRocketPlayer player ,
319+ string command ,
320+ out string [ ] commandParts ,
321+ out IRocketCommand rocketCommand )
322+ {
323+ commandParts = null ;
324+ rocketCommand = null ;
325+
326+ if ( string . IsNullOrEmpty ( command ) )
327+ return false ;
328+
329+ // Strip leading slash only when present
330+ if ( command [ 0 ] == '/' )
331+ command = command . Substring ( 1 ) ;
332+
333+ commandParts = ParseCommand ( command ) ; // skip expensive Regex entirely.
334+
335+ string name = commandParts [ 0 ] ;
317336
318337 if ( player == null )
319338 player = new ConsolePlayer ( ) ;
320339
321- IRocketCommand rocketCommand = GetCommand ( name ) ;
340+ rocketCommand = GetCommand ( name ) ;
341+
322342 if ( rocketCommand == null )
343+ {
344+ Reply ( player , R . Translate ( "command_not_found" ) , Color . red ) ;
323345 return false ;
346+ }
324347
325- // AllowedCaller checks — unchanged logic, early-exit order preserved.
326- if ( rocketCommand . AllowedCaller == AllowedCaller . Player && player is ConsolePlayer )
348+ // AllowedCaller validation only.
349+ // This is fundamental command validity, not permission policy.
350+ if ( rocketCommand . AllowedCaller == AllowedCaller . Player &&
351+ player is ConsolePlayer )
327352 {
328353 Logging . Logger . Log ( "This command can't be called from console" ) ;
329354 return false ;
330355 }
331- if ( rocketCommand . AllowedCaller == AllowedCaller . Console && ! ( player is ConsolePlayer ) )
356+
357+ if ( rocketCommand . AllowedCaller == AllowedCaller . Console &&
358+ ! ( player is ConsolePlayer ) )
359+ {
360+ Reply ( player , "This command can only be called from console" , Color . red ) ;
361+ return false ;
362+ }
363+
364+ return true ;
365+ }
366+
367+ private bool ValidateCommand (
368+ IRocketPlayer player ,
369+ IRocketCommand rocketCommand )
370+ {
371+ if ( ! ( player is ConsolePlayer ) &&
372+ ! R . Permissions . HasPermission ( player , rocketCommand ) )
332373 {
333- Logging . Logger . Log ( "This command can only be called from console" ) ;
374+ Reply ( player , R . Translate ( "command_no_permission" ) , Color . red ) ;
334375 return false ;
335376 }
336377
337378 double cooldown = GetCooldown ( player , rocketCommand ) ;
338- if ( cooldown != - 1 )
379+
380+ if ( cooldown > 0 )
339381 {
340- Logging . Logger . Log ( "This command is still on cooldown" ) ;
382+ Reply ( player ,
383+ R . Translate ( "command_cooldown" , cooldown ) ,
384+ Color . red ) ;
385+
341386 return false ;
342387 }
343388
389+ return true ;
390+ }
391+
392+ public bool ExecuteResolved (
393+ IRocketPlayer player ,
394+ string [ ] commandParts ,
395+ IRocketCommand rocketCommand )
396+ {
397+ string [ ] parameters ;
398+
399+ if ( commandParts . Length > 1 )
400+ {
401+ int parameterCount = commandParts . Length - 1 ;
402+
403+ parameters = new string [ parameterCount ] ;
404+
405+ Array . Copy ( commandParts , 1 , parameters , 0 , parameterCount ) ;
406+ }
407+ else
408+ {
409+ parameters = Array . Empty < string > ( ) ;
410+ }
411+
344412 try
345413 {
346414 bool cancelCommand = false ;
347415
348- // Snapshot the delegate once — avoids repeated null checks and
349- // repeated GetInvocationList() + Cast<> allocations per handler.
350416 ExecuteCommand snapshot = OnExecuteCommand ;
417+
351418 if ( snapshot != null )
352419 {
353420 Delegate [ ] handlers = snapshot . GetInvocationList ( ) ;
421+
354422 for ( int i = 0 ; i < handlers . Length ; i ++ )
355423 {
356424 try
357425 {
358- ( ( ExecuteCommand ) handlers [ i ] ) ( player , rocketCommand , ref cancelCommand ) ;
426+ ( ( ExecuteCommand ) handlers [ i ] ) (
427+ player ,
428+ rocketCommand ,
429+ ref cancelCommand ) ;
359430 }
360431 catch ( Exception ex )
361432 {
@@ -371,6 +442,7 @@ public bool Execute(IRocketPlayer player, string command)
371442 {
372443 rocketCommand . Execute ( player , parameters ) ;
373444
445+ // Preserve Rocket compatibility behavior
374446 if ( ! player . HasPermission ( "*" ) )
375447 SetCooldown ( player , rocketCommand ) ;
376448 }
@@ -382,16 +454,48 @@ public bool Execute(IRocketPlayer player, string command)
382454 {
383455 // intentionally swallowed
384456 }
385- // Other exceptions propagate to the outer catch as before.
386457 }
387458 catch ( Exception ex )
388459 {
389460 Logging . Logger . LogError (
390- "An error occured while executing " + rocketCommand . Name +
391- " [" + string . Join ( ", " , parameters ) + "]: " + ex . ToString ( ) ) ;
461+ "An error occured while executing " +
462+ rocketCommand . Name +
463+ " [" + string . Join ( ", " , parameters ) + "]: " +
464+ ex ) ;
392465 }
393466
394467 return true ;
468+ }
469+
470+ public bool ExecuteWithCheck ( IRocketPlayer player , string command )
471+ {
472+ if ( ! TryResolveCommand (
473+ ref player ,
474+ command ,
475+ out string [ ] commandParts ,
476+ out IRocketCommand rocketCommand ) )
477+ {
478+ return false ;
479+ }
480+
481+ if ( ! ValidateCommand ( player , rocketCommand ) )
482+ return false ;
483+
484+ return ExecuteResolved ( player , commandParts , rocketCommand ) ;
485+ }
486+
487+ public bool Execute ( IRocketPlayer player , string command )
488+ {
489+ if ( ! TryResolveCommand (
490+ ref player ,
491+ command ,
492+ out string [ ] commandParts ,
493+ out IRocketCommand rocketCommand ) )
494+ {
495+ return false ;
496+ }
497+
498+ return ExecuteResolved ( player , commandParts , rocketCommand ) ;
395499 }
396500
397501 public void RegisterFromAssembly ( Assembly assembly )
0 commit comments