2022-04-20 12:21:38 +00:00
package emu.grasscutter.command ;
2022-04-18 22:24:08 +00:00
import emu.grasscutter.Grasscutter ;
2022-04-19 03:46:04 +00:00
import emu.grasscutter.game.Account ;
2022-04-27 04:24:25 +00:00
import emu.grasscutter.game.player.Player ;
2022-04-18 22:24:08 +00:00
import org.reflections.Reflections ;
import java.util.* ;
2022-04-20 12:21:38 +00:00
@SuppressWarnings ( { " UnusedReturnValue " , " unused " } )
2022-04-18 22:24:08 +00:00
public final class CommandMap {
2022-04-20 12:21:38 +00:00
private final Map < String , CommandHandler > commands = new HashMap < > ( ) ;
private final Map < String , Command > annotations = new HashMap < > ( ) ;
2022-05-05 05:03:08 +00:00
private final Map < String , Integer > targetPlayerIds = new HashMap < > ( ) ;
2022-05-04 06:32:09 +00:00
private static final String consoleId = " console " ;
2022-04-20 12:21:38 +00:00
public CommandMap ( ) {
this ( false ) ;
}
public CommandMap ( boolean scan ) {
if ( scan ) this . scan ( ) ;
}
2022-04-18 22:24:08 +00:00
public static CommandMap getInstance ( ) {
return Grasscutter . getGameServer ( ) . getCommandMap ( ) ;
}
/ * *
* Register a command handler .
2022-04-20 12:21:38 +00:00
*
* @param label The command label .
2022-04-18 22:24:08 +00:00
* @param command The command handler .
* @return Instance chaining .
* /
public CommandMap registerCommand ( String label , CommandHandler command ) {
2022-04-19 00:11:23 +00:00
Grasscutter . getLogger ( ) . debug ( " Registered command: " + label ) ;
2022-04-20 12:21:38 +00:00
2022-04-19 02:09:51 +00:00
// Get command data.
2022-04-19 00:11:23 +00:00
Command annotation = command . getClass ( ) . getAnnotation ( Command . class ) ;
2022-04-19 03:46:04 +00:00
this . annotations . put ( label , annotation ) ;
2022-04-19 02:09:51 +00:00
this . commands . put ( label , command ) ;
2022-04-20 12:21:38 +00:00
2022-04-19 02:09:51 +00:00
// Register aliases.
2022-04-20 12:21:38 +00:00
if ( annotation . aliases ( ) . length > 0 ) {
2022-04-19 02:09:51 +00:00
for ( String alias : annotation . aliases ( ) ) {
2022-04-19 00:11:23 +00:00
this . commands . put ( alias , command ) ;
2022-04-19 03:46:04 +00:00
this . annotations . put ( alias , annotation ) ;
2022-04-19 02:09:51 +00:00
}
2022-04-20 12:21:38 +00:00
}
return this ;
2022-04-18 22:24:08 +00:00
}
/ * *
* Removes a registered command handler .
2022-04-20 12:21:38 +00:00
*
2022-04-18 22:24:08 +00:00
* @param label The command label .
* @return Instance chaining .
* /
public CommandMap unregisterCommand ( String label ) {
2022-04-19 00:11:23 +00:00
Grasscutter . getLogger ( ) . debug ( " Unregistered command: " + label ) ;
CommandHandler handler = this . commands . get ( label ) ;
2022-04-20 12:21:38 +00:00
if ( handler = = null ) return this ;
2022-04-19 00:11:23 +00:00
Command annotation = handler . getClass ( ) . getAnnotation ( Command . class ) ;
2022-04-19 03:46:04 +00:00
this . annotations . remove ( label ) ;
2022-04-19 02:09:51 +00:00
this . commands . remove ( label ) ;
2022-04-20 12:21:38 +00:00
2022-04-19 02:09:51 +00:00
// Unregister aliases.
2022-04-20 12:21:38 +00:00
if ( annotation . aliases ( ) . length > 0 ) {
2022-04-19 02:09:51 +00:00
for ( String alias : annotation . aliases ( ) ) {
2022-04-19 00:11:23 +00:00
this . commands . remove ( alias ) ;
2022-04-19 03:46:04 +00:00
this . annotations . remove ( alias ) ;
2022-04-19 02:09:51 +00:00
}
}
2022-04-20 12:21:38 +00:00
2022-04-19 00:11:23 +00:00
return this ;
2022-04-18 22:24:08 +00:00
}
2022-05-07 21:20:12 +00:00
public List < Command > getAnnotationsAsList ( ) { return new LinkedList < > ( this . annotations . values ( ) ) ; }
public HashMap < String , Command > getAnnotations ( ) {
return new LinkedHashMap < > ( this . annotations ) ;
}
2022-04-19 03:06:03 +00:00
/ * *
* Returns a list of all registered commands .
2022-04-20 12:21:38 +00:00
*
2022-04-19 03:06:03 +00:00
* @return All command handlers as a list .
* /
2022-04-19 10:17:19 +00:00
public List < CommandHandler > getHandlersAsList ( ) {
2022-04-19 03:06:03 +00:00
return new LinkedList < > ( this . commands . values ( ) ) ;
}
2022-04-20 12:21:38 +00:00
public HashMap < String , CommandHandler > getHandlers ( ) {
return new LinkedHashMap < > ( this . commands ) ;
}
2022-04-19 10:17:19 +00:00
2022-04-19 03:06:03 +00:00
/ * *
* Returns a handler by label / alias .
2022-04-20 12:21:38 +00:00
*
2022-04-19 03:06:03 +00:00
* @param label The command label .
* @return The command handler .
* /
public CommandHandler getHandler ( String label ) {
return this . commands . get ( label ) ;
}
2022-04-18 22:24:08 +00:00
/ * *
* Invoke a command handler with the given arguments .
2022-04-20 12:21:38 +00:00
*
* @param player The player invoking the command or null for the server console .
2022-04-18 22:24:08 +00:00
* @param rawMessage The messaged used to invoke the command .
* /
2022-05-04 06:32:09 +00:00
public void invoke ( Player player , Player targetPlayer , String rawMessage ) {
2022-04-19 00:11:23 +00:00
rawMessage = rawMessage . trim ( ) ;
2022-04-24 13:52:50 +00:00
if ( rawMessage . length ( ) = = 0 ) {
2022-05-22 08:02:11 +00:00
CommandHandler . sendTranslatedMessage ( player , " commands.generic.not_specified " ) ;
2022-04-24 13:52:50 +00:00
return ;
2022-04-19 00:11:23 +00:00
}
2022-04-20 12:21:38 +00:00
2022-04-18 22:24:08 +00:00
// Parse message.
String [ ] split = rawMessage . split ( " " ) ;
2022-04-19 00:11:23 +00:00
List < String > args = new LinkedList < > ( Arrays . asList ( split ) ) ;
2022-04-18 22:24:08 +00:00
String label = args . remove ( 0 ) ;
2022-05-04 06:32:09 +00:00
String playerId = ( player = = null ) ? consoleId : player . getAccount ( ) . getId ( ) ;
2022-05-06 04:57:45 +00:00
// Check for special cases - currently only target command.
2022-05-04 09:16:42 +00:00
String targetUidStr = null ;
2022-05-06 04:57:45 +00:00
if ( label . startsWith ( " @ " ) ) { // @[UID]
2022-05-04 09:16:42 +00:00
targetUidStr = label . substring ( 1 ) ;
2022-05-06 04:57:45 +00:00
} else if ( label . equalsIgnoreCase ( " target " ) ) { // target [[@]UID]
2022-05-05 05:03:08 +00:00
if ( args . size ( ) > 0 ) {
targetUidStr = args . get ( 0 ) ;
if ( targetUidStr . startsWith ( " @ " ) ) {
targetUidStr = targetUidStr . substring ( 1 ) ;
}
} else {
targetUidStr = " " ;
2022-05-04 09:16:42 +00:00
}
}
2022-05-05 05:03:08 +00:00
if ( targetUidStr ! = null ) {
2022-05-06 04:57:45 +00:00
if ( targetUidStr . equals ( " " ) ) { // Clears the default targetPlayer.
2022-05-05 05:03:08 +00:00
targetPlayerIds . remove ( playerId ) ;
2022-05-22 08:02:11 +00:00
CommandHandler . sendTranslatedMessage ( player , " commands.execution.clear_target " ) ;
2022-05-06 04:57:45 +00:00
} else { // Sets default targetPlayer to the UID provided.
2022-05-05 05:03:08 +00:00
try {
int uid = Integer . parseInt ( targetUidStr ) ;
2022-05-22 08:02:11 +00:00
targetPlayer = Grasscutter . getGameServer ( ) . getPlayerByUid ( uid , true ) ;
2022-05-05 05:03:08 +00:00
if ( targetPlayer = = null ) {
2022-05-22 08:02:11 +00:00
CommandHandler . sendTranslatedMessage ( player , " commands.execution.player_exist_error " ) ;
2022-05-05 05:03:08 +00:00
} else {
targetPlayerIds . put ( playerId , uid ) ;
2022-05-22 08:02:11 +00:00
CommandHandler . sendTranslatedMessage ( player , " commands.execution.set_target " , targetUidStr ) ;
CommandHandler . sendTranslatedMessage ( player , targetPlayer . isOnline ( ) ? " commands.execution.set_target_online " : " commands.execution.set_target_offline " , targetUidStr ) ;
2022-05-05 05:03:08 +00:00
}
} catch ( NumberFormatException e ) {
2022-05-22 08:02:11 +00:00
CommandHandler . sendTranslatedMessage ( player , " commands.execution.uid_error " ) ;
2022-05-04 06:32:09 +00:00
}
}
2022-05-06 04:57:45 +00:00
return ;
2022-05-05 05:03:08 +00:00
}
2022-05-04 09:16:42 +00:00
2022-04-18 22:24:08 +00:00
// Get command handler.
CommandHandler handler = this . commands . get ( label ) ;
2022-04-20 12:21:38 +00:00
if ( handler = = null ) {
2022-05-22 08:02:11 +00:00
CommandHandler . sendTranslatedMessage ( player , " commands.generic.unknown_command " , label ) ;
2022-04-20 12:21:38 +00:00
return ;
2022-04-18 22:24:08 +00:00
}
2022-04-20 12:21:38 +00:00
2022-05-06 04:57:45 +00:00
// If any @UID argument is present, override targetPlayer with it.
2022-05-04 06:32:09 +00:00
for ( int i = 0 ; i < args . size ( ) ; i + + ) {
String arg = args . get ( i ) ;
2022-05-06 04:57:45 +00:00
if ( arg . startsWith ( " @ " ) ) {
2022-05-04 06:32:09 +00:00
arg = args . remove ( i ) . substring ( 1 ) ;
try {
int uid = Integer . parseInt ( arg ) ;
2022-05-22 08:02:11 +00:00
targetPlayer = Grasscutter . getGameServer ( ) . getPlayerByUid ( uid , true ) ;
2022-05-04 06:32:09 +00:00
if ( targetPlayer = = null ) {
2022-05-22 08:02:11 +00:00
CommandHandler . sendTranslatedMessage ( player , " commands.execution.player_exist_error " ) ;
2022-05-04 06:32:09 +00:00
return ;
}
break ;
} catch ( NumberFormatException e ) {
2022-05-22 08:02:11 +00:00
CommandHandler . sendTranslatedMessage ( player , " commands.execution.uid_error " ) ;
2022-05-04 06:32:09 +00:00
return ;
}
}
}
2022-05-06 04:57:45 +00:00
2022-05-04 06:32:09 +00:00
// If there's still no targetPlayer at this point, use previously-set target
if ( targetPlayer = = null ) {
2022-05-05 05:03:08 +00:00
if ( targetPlayerIds . containsKey ( playerId ) ) {
2022-05-22 08:02:11 +00:00
targetPlayer = Grasscutter . getGameServer ( ) . getPlayerByUid ( targetPlayerIds . get ( playerId ) , true ) ; // We check every time in case the target is deleted after being targeted
2022-05-05 05:03:08 +00:00
if ( targetPlayer = = null ) {
2022-05-22 08:02:11 +00:00
CommandHandler . sendTranslatedMessage ( player , " commands.execution.player_exist_error " ) ;
2022-05-05 05:03:08 +00:00
return ;
}
} else {
2022-05-06 04:57:45 +00:00
// If there's still no targetPlayer at this point, use executor.
targetPlayer = player ;
2022-05-05 05:03:08 +00:00
}
2022-05-04 06:32:09 +00:00
}
2022-04-19 03:46:04 +00:00
// Check for permission.
2022-04-20 12:21:38 +00:00
if ( player ! = null ) {
2022-04-19 03:46:04 +00:00
String permissionNode = this . annotations . get ( label ) . permission ( ) ;
2022-05-04 06:32:09 +00:00
String permissionNodeTargeted = this . annotations . get ( label ) . permissionTargeted ( ) ;
2022-04-19 03:46:04 +00:00
Account account = player . getAccount ( ) ;
2022-05-04 06:32:09 +00:00
if ( player ! = targetPlayer ) { // Additional permission required for targeting another player
if ( ! permissionNodeTargeted . isEmpty ( ) & & ! account . hasPermission ( permissionNodeTargeted ) ) {
2022-05-22 08:02:11 +00:00
CommandHandler . sendTranslatedMessage ( player , " commands.generic.permission_error " ) ;
2022-05-04 06:32:09 +00:00
return ;
}
}
2022-04-24 13:52:50 +00:00
if ( ! permissionNode . isEmpty ( ) & & ! account . hasPermission ( permissionNode ) ) {
2022-05-22 08:02:11 +00:00
CommandHandler . sendTranslatedMessage ( player , " commands.generic.permission_error " ) ;
return ;
}
}
// Check if command has unfulfilled constraints on targetPlayer
Command . TargetRequirement targetRequirement = this . annotations . get ( label ) . targetRequirement ( ) ;
if ( targetRequirement ! = Command . TargetRequirement . NONE ) {
if ( targetPlayer = = null ) {
CommandHandler . sendTranslatedMessage ( player , " commands.execution.need_target " ) ;
return ;
}
if ( ( targetRequirement = = Command . TargetRequirement . ONLINE ) & & ! targetPlayer . isOnline ( ) ) {
CommandHandler . sendTranslatedMessage ( player , " commands.execution.need_target_online " ) ;
return ;
}
if ( ( targetRequirement = = Command . TargetRequirement . OFFLINE ) & & targetPlayer . isOnline ( ) ) {
CommandHandler . sendTranslatedMessage ( player , " commands.execution.need_target_offline " ) ;
2022-04-20 12:21:38 +00:00
return ;
2022-04-19 03:46:04 +00:00
}
}
2022-04-20 12:21:38 +00:00
2022-04-18 22:24:08 +00:00
// Invoke execute method for handler.
2022-04-24 13:52:50 +00:00
boolean threading = this . annotations . get ( label ) . threading ( ) ;
2022-05-04 06:32:09 +00:00
final Player targetPlayerF = targetPlayer ; // Is there a better way to do this?
Runnable runnable = ( ) - > handler . execute ( player , targetPlayerF , args ) ;
2022-04-24 13:52:50 +00:00
if ( threading ) {
2022-05-06 04:57:45 +00:00
new Thread ( runnable ) . start ( ) ;
} else {
2022-04-24 13:52:50 +00:00
runnable . run ( ) ;
}
2022-04-18 22:24:08 +00:00
}
/ * *
* Scans for all classes annotated with { @link Command } and registers them .
* /
private void scan ( ) {
Reflections reflector = Grasscutter . reflector ;
2022-04-19 00:11:23 +00:00
Set < Class < ? > > classes = reflector . getTypesAnnotatedWith ( Command . class ) ;
2022-04-18 22:24:08 +00:00
classes . forEach ( annotated - > {
try {
2022-04-19 00:11:23 +00:00
Command cmdData = annotated . getAnnotation ( Command . class ) ;
Object object = annotated . newInstance ( ) ;
2022-04-18 22:24:08 +00:00
if ( object instanceof CommandHandler )
this . registerCommand ( cmdData . label ( ) , ( CommandHandler ) object ) ;
2022-04-19 00:11:23 +00:00
else Grasscutter . getLogger ( ) . error ( " Class " + annotated . getName ( ) + " is not a CommandHandler! " ) ;
} catch ( Exception exception ) {
Grasscutter . getLogger ( ) . error ( " Failed to register command handler for " + annotated . getSimpleName ( ) , exception ) ;
}
2022-04-18 22:24:08 +00:00
} ) ;
}
}