2020-04-10 23:20:34 +00:00
/ *
* Copyright ( c ) 2019 - 2020 GeyserMC . http : //geysermc.org
*
* Permission is hereby granted , free of charge , to any person obtaining a copy
* of this software and associated documentation files ( the " Software " ) , to deal
* in the Software without restriction , including without limitation the rights
* to use , copy , modify , merge , publish , distribute , sublicense , and / or sell
* copies of the Software , and to permit persons to whom the Software is
* furnished to do so , subject to the following conditions :
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software .
*
* THE SOFTWARE IS PROVIDED " AS IS " , WITHOUT WARRANTY OF ANY KIND , EXPRESS OR
* IMPLIED , INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY ,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT . IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM , DAMAGES OR OTHER
* LIABILITY , WHETHER IN AN ACTION OF CONTRACT , TORT OR OTHERWISE , ARISING FROM ,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE .
*
* @author GeyserMC
* @link https : //github.com/GeyserMC/Geyser
* /
package org.geysermc.connector.network.translators.java ;
2020-06-24 16:16:30 +00:00
import com.github.steveice10.mc.protocol.data.game.command.CommandNode ;
import com.github.steveice10.mc.protocol.data.game.command.CommandParser ;
2020-04-10 23:20:34 +00:00
import com.github.steveice10.mc.protocol.packet.ingame.server.ServerDeclareCommandsPacket ;
2020-06-24 16:16:30 +00:00
import com.nukkitx.protocol.bedrock.data.command.CommandData ;
import com.nukkitx.protocol.bedrock.data.command.CommandEnumData ;
import com.nukkitx.protocol.bedrock.data.command.CommandParamData ;
import com.nukkitx.protocol.bedrock.data.command.CommandParamType ;
import com.nukkitx.protocol.bedrock.packet.AvailableCommandsPacket ;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap ;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap ;
import lombok.Getter ;
import org.geysermc.connector.GeyserConnector ;
2020-04-10 23:20:34 +00:00
import org.geysermc.connector.network.session.GeyserSession ;
import org.geysermc.connector.network.translators.PacketTranslator ;
import org.geysermc.connector.network.translators.Translator ;
2020-06-24 16:16:30 +00:00
import java.util.ArrayList ;
import java.util.Arrays ;
import java.util.Collections ;
import java.util.List ;
2020-04-10 23:20:34 +00:00
@Translator ( packet = ServerDeclareCommandsPacket . class )
2020-05-21 03:43:22 +00:00
public class JavaDeclareCommandsTranslator extends PacketTranslator < ServerDeclareCommandsPacket > {
2020-04-10 23:20:34 +00:00
@Override
public void translate ( ServerDeclareCommandsPacket packet , GeyserSession session ) {
2020-05-21 03:43:22 +00:00
// Don't send command suggestions if they are disabled
2020-06-24 16:16:30 +00:00
if ( ! session . getConnector ( ) . getConfig ( ) . isCommandSuggestions ( ) ) {
2020-12-17 19:10:58 +00:00
session . getConnector ( ) . getLogger ( ) . debug ( " Not sending translated command suggestions as they are disabled. " ) ;
// Send an empty packet so Bedrock doesn't override /help with its own, built-in help command.
AvailableCommandsPacket emptyPacket = new AvailableCommandsPacket ( ) ;
session . sendUpstreamPacket ( emptyPacket ) ;
2020-06-24 16:16:30 +00:00
return ;
}
2020-12-17 19:10:58 +00:00
2020-06-24 16:16:30 +00:00
List < CommandData > commandData = new ArrayList < > ( ) ;
Int2ObjectMap < String > commands = new Int2ObjectOpenHashMap < > ( ) ;
Int2ObjectMap < List < CommandNode > > commandArgs = new Int2ObjectOpenHashMap < > ( ) ;
// Get the first node, it should be a root node
CommandNode rootNode = packet . getNodes ( ) [ packet . getFirstNodeIndex ( ) ] ;
// Loop through the root nodes to get all commands
for ( int nodeIndex : rootNode . getChildIndices ( ) ) {
CommandNode node = packet . getNodes ( ) [ nodeIndex ] ;
// Make sure we don't have duplicated commands (happens if there is more than 1 root node)
if ( commands . containsKey ( nodeIndex ) ) { continue ; }
if ( commands . containsValue ( node . getName ( ) ) ) { continue ; }
// Get and update the commandArgs list with the found arguments
if ( node . getChildIndices ( ) . length > = 1 ) {
for ( int childIndex : node . getChildIndices ( ) ) {
commandArgs . putIfAbsent ( nodeIndex , new ArrayList < > ( ) ) ;
commandArgs . get ( nodeIndex ) . add ( packet . getNodes ( ) [ childIndex ] ) ;
}
}
// Insert the command name into the list
commands . put ( nodeIndex , node . getName ( ) ) ;
}
// The command flags, not sure what these do apart from break things
2020-12-17 19:10:58 +00:00
List < CommandData . Flag > flags = Collections . emptyList ( ) ;
2020-06-24 16:16:30 +00:00
// Loop through all the found commands
for ( int commandID : commands . keySet ( ) ) {
String commandName = commands . get ( commandID ) ;
// Create a basic alias
2020-12-17 19:10:58 +00:00
CommandEnumData aliases = new CommandEnumData ( commandName + " Aliases " , new String [ ] { commandName . toLowerCase ( ) } , false ) ;
2020-06-24 16:16:30 +00:00
// Get and parse all params
CommandParamData [ ] [ ] params = getParams ( packet . getNodes ( ) [ commandID ] , packet . getNodes ( ) ) ;
// Build the completed command and add it to the final list
CommandData data = new CommandData ( commandName , session . getConnector ( ) . getCommandManager ( ) . getDescription ( commandName ) , flags , ( byte ) 0 , aliases , params ) ;
commandData . add ( data ) ;
}
// Add our commands to the AvailableCommandsPacket for the bedrock client
AvailableCommandsPacket availableCommandsPacket = new AvailableCommandsPacket ( ) ;
2020-12-17 19:10:58 +00:00
availableCommandsPacket . getCommands ( ) . addAll ( commandData ) ;
2020-06-24 16:16:30 +00:00
GeyserConnector . getInstance ( ) . getLogger ( ) . debug ( " Sending command packet of " + commandData . size ( ) + " commands " ) ;
// Finally, send the commands to the client
session . sendUpstreamPacket ( availableCommandsPacket ) ;
}
/ * *
* Build the command parameter array for the given command
*
* @param commandNode The command to build the parameters for
* @param allNodes Every command node
*
* @return An array of parameter option arrays
* /
private CommandParamData [ ] [ ] getParams ( CommandNode commandNode , CommandNode [ ] allNodes ) {
// Check if the command is an alias and redirect it
if ( commandNode . getRedirectIndex ( ) ! = - 1 ) {
GeyserConnector . getInstance ( ) . getLogger ( ) . debug ( " Redirecting command " + commandNode . getName ( ) + " to " + allNodes [ commandNode . getRedirectIndex ( ) ] . getName ( ) ) ;
commandNode = allNodes [ commandNode . getRedirectIndex ( ) ] ;
}
if ( commandNode . getChildIndices ( ) . length > = 1 ) {
// Create the root param node and build all the children
ParamInfo rootParam = new ParamInfo ( commandNode , null ) ;
rootParam . buildChildren ( allNodes ) ;
List < CommandParamData [ ] > treeData = rootParam . getTree ( ) ;
CommandParamData [ ] [ ] params = new CommandParamData [ treeData . size ( ) ] [ ] ;
// Fill the nested params array
int i = 0 ;
for ( CommandParamData [ ] tree : treeData ) {
params [ i ] = tree ;
i + + ;
}
return params ;
}
return new CommandParamData [ 0 ] [ 0 ] ;
}
/ * *
* Convert Java edition command types to Bedrock edition
*
* @param parser Command type to convert
*
* @return Bedrock parameter data type
* /
private CommandParamType mapCommandType ( CommandParser parser ) {
if ( parser = = null ) { return CommandParamType . STRING ; }
switch ( parser ) {
case FLOAT :
return CommandParamType . FLOAT ;
case INTEGER :
return CommandParamType . INT ;
case ENTITY :
case GAME_PROFILE :
return CommandParamType . TARGET ;
case BLOCK_POS :
return CommandParamType . BLOCK_POSITION ;
case COLUMN_POS :
case VEC3 :
return CommandParamType . POSITION ;
case MESSAGE :
return CommandParamType . MESSAGE ;
case NBT :
case NBT_COMPOUND_TAG :
case NBT_TAG :
case NBT_PATH :
return CommandParamType . JSON ;
case RESOURCE_LOCATION :
return CommandParamType . FILE_PATH ;
case INT_RANGE :
return CommandParamType . INT_RANGE ;
case BOOL :
case DOUBLE :
case STRING :
case VEC2 :
case BLOCK_STATE :
case BLOCK_PREDICATE :
case ITEM_STACK :
case ITEM_PREDICATE :
case COLOR :
case COMPONENT :
case OBJECTIVE :
case OBJECTIVE_CRITERIA :
case OPERATION : // Possibly OPERATOR
case PARTICLE :
case ROTATION :
case SCOREBOARD_SLOT :
case SCORE_HOLDER :
case SWIZZLE :
case TEAM :
case ITEM_SLOT :
case MOB_EFFECT :
case FUNCTION :
case ENTITY_ANCHOR :
case RANGE :
case FLOAT_RANGE :
case ITEM_ENCHANTMENT :
case ENTITY_SUMMON :
case DIMENSION :
case TIME :
default :
return CommandParamType . STRING ;
}
}
@Getter
private class ParamInfo {
private CommandNode paramNode ;
private CommandParamData paramData ;
private List < ParamInfo > children ;
/ * *
* Create a new parameter info object
*
* @param paramNode CommandNode the parameter is for
* @param paramData The existing parameters for the command
* /
public ParamInfo ( CommandNode paramNode , CommandParamData paramData ) {
this . paramNode = paramNode ;
this . paramData = paramData ;
this . children = new ArrayList < > ( ) ;
}
/ * *
* Build the array of all the child parameters ( recursive )
*
* @param allNodes Every command node
* /
public void buildChildren ( CommandNode [ ] allNodes ) {
int enumIndex = - 1 ;
for ( int paramID : paramNode . getChildIndices ( ) ) {
CommandNode paramNode = allNodes [ paramID ] ;
if ( paramNode . getParser ( ) = = null ) {
if ( enumIndex = = - 1 ) {
enumIndex = children . size ( ) ;
// Create the new enum command
CommandEnumData enumData = new CommandEnumData ( paramNode . getName ( ) , new String [ ] { paramNode . getName ( ) } , false ) ;
children . add ( new ParamInfo ( paramNode , new CommandParamData ( paramNode . getName ( ) , false , enumData , mapCommandType ( paramNode . getParser ( ) ) , null , Collections . emptyList ( ) ) ) ) ;
} else {
// Get the existing enum
ParamInfo enumParamInfo = children . get ( enumIndex ) ;
// Extend the current list of enum values
String [ ] enumOptions = Arrays . copyOf ( enumParamInfo . getParamData ( ) . getEnumData ( ) . getValues ( ) , enumParamInfo . getParamData ( ) . getEnumData ( ) . getValues ( ) . length + 1 ) ;
enumOptions [ enumOptions . length - 1 ] = paramNode . getName ( ) ;
// Re-create the command using the updated values
CommandEnumData enumData = new CommandEnumData ( enumParamInfo . getParamData ( ) . getEnumData ( ) . getName ( ) , enumOptions , false ) ;
children . set ( enumIndex , new ParamInfo ( enumParamInfo . getParamNode ( ) , new CommandParamData ( enumParamInfo . getParamData ( ) . getName ( ) , false , enumData , enumParamInfo . getParamData ( ) . getType ( ) , null , Collections . emptyList ( ) ) ) ) ;
}
} else {
// Put the non-enum param into the list
children . add ( new ParamInfo ( paramNode , new CommandParamData ( paramNode . getName ( ) , false , null , mapCommandType ( paramNode . getParser ( ) ) , null , Collections . emptyList ( ) ) ) ) ;
}
}
// Recursively build all child options
for ( ParamInfo child : children ) {
child . buildChildren ( allNodes ) ;
}
}
/ * *
* Get the tree of every parameter node ( recursive )
*
* @return List of parameter options arrays for the command
* /
public List < CommandParamData [ ] > getTree ( ) {
List < CommandParamData [ ] > treeParamData = new ArrayList < > ( ) ;
for ( ParamInfo child : children ) {
// Get the tree from the child
List < CommandParamData [ ] > childTree = child . getTree ( ) ;
// Un-pack the tree append the child node to it and push into the list
for ( CommandParamData [ ] subchild : childTree ) {
CommandParamData [ ] tmpTree = new ArrayList < CommandParamData > ( ) {
{
add ( child . getParamData ( ) ) ;
addAll ( Arrays . asList ( subchild ) ) ;
}
} . toArray ( new CommandParamData [ 0 ] ) ;
treeParamData . add ( tmpTree ) ;
}
// If we have no more child parameters just the child
if ( childTree . size ( ) = = 0 ) {
treeParamData . add ( new CommandParamData [ ] { child . getParamData ( ) } ) ;
}
}
return treeParamData ;
}
2020-04-11 13:57:29 +00:00
}
2020-04-10 23:20:34 +00:00
}