From dfa4cb8b375babb89da77e85a4f8b987088ef74b Mon Sep 17 00:00:00 2001 From: Felix Cravic Date: Mon, 3 Aug 2020 06:36:42 +0200 Subject: [PATCH] Cleanup + comments --- .../server/command/CommandManager.java | 106 +++----- .../server/command/builder/Arguments.java | 8 +- .../command/builder/CommandDispatcher.java | 68 +++-- .../builder/arguments/ArgumentStructure.java | 253 ------------------ .../builder/arguments/ArgumentType.java | 7 +- .../builder/condition/CommandCondition.java | 3 + .../command/builder/structure/Structure.java | 50 ---- .../minestom/server/entity/ExperienceOrb.java | 25 +- .../server/entity/damage/EntityDamage.java | 4 +- .../entity/damage/EntityProjectileDamage.java | 12 +- 10 files changed, 113 insertions(+), 423 deletions(-) delete mode 100644 src/main/java/net/minestom/server/command/builder/arguments/ArgumentStructure.java delete mode 100644 src/main/java/net/minestom/server/command/builder/structure/Structure.java diff --git a/src/main/java/net/minestom/server/command/CommandManager.java b/src/main/java/net/minestom/server/command/CommandManager.java index 4b9e4290e..891d5ab04 100644 --- a/src/main/java/net/minestom/server/command/CommandManager.java +++ b/src/main/java/net/minestom/server/command/CommandManager.java @@ -163,77 +163,12 @@ public class CommandManager { return buildPacket(player); } - /*private DeclareCommandsPacket buildPacket(Player player) { - DeclareCommandsPacket declareCommandsPacket = new DeclareCommandsPacket(); - - List commands = new ArrayList<>(); - for (Command command : dispatcher.getCommands()) { - CommandCondition commandCondition = command.getCondition(); - if (commandCondition != null) { - // Do not show command if return false - if (!commandCondition.apply(player)) { - continue; - } - } - commands.add(command.getName()); - for (String alias : command.getAliases()) { - commands.add(alias); - } - } - - for (CommandProcessor commandProcessor : commandProcessorMap.values()) { - // Do not show command if return false - if (!commandProcessor.hasAccess(player)) - continue; - - commands.add(commandProcessor.getCommandName()); - String[] aliases = commandProcessor.getAliases(); - if (aliases == null || aliases.length == 0) - continue; - for (String alias : aliases) { - commands.add(alias); - } - } - - - List nodes = new ArrayList<>(); - ArrayList rootChildren = new ArrayList<>(); - - DeclareCommandsPacket.Node argNode = new DeclareCommandsPacket.Node(); - argNode.flags = 0b10; - argNode.name = "arg"; - argNode.parser = "brigadier:string"; - argNode.properties = packetWriter -> { - packetWriter.writeVarInt(0); - }; - int argOffset = nodes.size(); - nodes.add(argNode); - argNode.children = new int[]{argOffset}; - - for (String commandName : commands) { - - DeclareCommandsPacket.Node literalNode = new DeclareCommandsPacket.Node(); - literalNode.flags = 0b1; - literalNode.name = commandName; - literalNode.children = new int[]{argOffset}; - - rootChildren.add(nodes.size()); - nodes.add(literalNode); - - } - - DeclareCommandsPacket.Node rootNode = new DeclareCommandsPacket.Node(); - rootNode.flags = 0; - rootNode.children = ArrayUtils.toArray(rootChildren); - - nodes.add(rootNode); - - declareCommandsPacket.nodes = nodes.toArray(new DeclareCommandsPacket.Node[0]); - declareCommandsPacket.rootIndex = nodes.size() - 1; - - return declareCommandsPacket; - }*/ - + /** + * Build the {@link DeclareCommandsPacket} for a player + * + * @param player the player to build the packet for + * @return the commands packet for the specific player + */ private DeclareCommandsPacket buildPacket(Player player) { DeclareCommandsPacket declareCommandsPacket = new DeclareCommandsPacket(); @@ -243,7 +178,7 @@ public class CommandManager { for (Command command : dispatcher.getCommands()) { // Check if player should see this command - CommandCondition commandCondition = command.getCondition(); + final CommandCondition commandCondition = command.getCondition(); if (commandCondition != null) { // Do not show command if return false if (!commandCondition.apply(player)) { @@ -273,7 +208,7 @@ public class CommandManager { continue; simpleCommands.add(commandProcessor.getCommandName()); - String[] aliases = commandProcessor.getAliases(); + final String[] aliases = commandProcessor.getAliases(); if (aliases == null || aliases.length == 0) continue; for (String alias : aliases) { @@ -305,6 +240,15 @@ public class CommandManager { return declareCommandsPacket; } + /** + * Add a command's syntaxes to the nodes list + * + * @param nodes the nodes of the packet + * @param cmdChildren the main root of this command + * @param name the name of the command (or the alias) + * @param syntaxes the syntaxes of the command + * @param rootChildren the children of the main node (all commands name) + */ private void createCommand(List nodes, ArrayList cmdChildren, String name, Collection syntaxes, ArrayList rootChildren) { DeclareCommandsPacket.Node literalNode = createMainNode(name, syntaxes.isEmpty()); @@ -378,9 +322,17 @@ public class CommandManager { return literalNode; } + /** + * Convert an argument to a node with the correct brigadier parser + * + * @param argument the argument to convert + * @param executable true if this is the last argument, false otherwise + * @return the list of nodes that the argument require + */ private List toNodes(Argument argument, boolean executable) { List nodes = new ArrayList<>(); + // You can uncomment this to test any brigadier parser on the client /*DeclareCommandsPacket.Node testNode = simpleArgumentNode(nodes, argument, executable); testNode.parser = "minecraft:entity"; testNode.properties = packetWriter -> packetWriter.writeByte((byte) 0x0); @@ -524,6 +476,14 @@ public class CommandManager { return result; } + /** + * Build an argument nod and add it to the nodes list + * + * @param nodes the current nodes list + * @param argument the argument + * @param executable true if this will be the last argument, false otherwise + * @return the created {@link DeclareCommandsPacket.Node} + */ private DeclareCommandsPacket.Node simpleArgumentNode(List nodes, Argument argument, boolean executable) { DeclareCommandsPacket.Node argumentNode = new DeclareCommandsPacket.Node(); diff --git a/src/main/java/net/minestom/server/command/builder/Arguments.java b/src/main/java/net/minestom/server/command/builder/Arguments.java index 2f8fa2021..4e3a1e853 100644 --- a/src/main/java/net/minestom/server/command/builder/Arguments.java +++ b/src/main/java/net/minestom/server/command/builder/Arguments.java @@ -1,7 +1,6 @@ package net.minestom.server.command.builder; import net.minestom.server.chat.ChatColor; -import net.minestom.server.command.builder.structure.Structure; import net.minestom.server.entity.Entity; import net.minestom.server.entity.EntityType; import net.minestom.server.item.Enchantment; @@ -15,14 +14,13 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +/** + * Class used to retrieve argument data + */ public class Arguments { private final Map args = new HashMap<>(); - public Structure getStructure(String id) { - return (Structure) getObject(id); - } - public boolean getBoolean(String id) { return (boolean) getObject(id); } diff --git a/src/main/java/net/minestom/server/command/builder/CommandDispatcher.java b/src/main/java/net/minestom/server/command/builder/CommandDispatcher.java index 1259309c1..c004d0600 100644 --- a/src/main/java/net/minestom/server/command/builder/CommandDispatcher.java +++ b/src/main/java/net/minestom/server/command/builder/CommandDispatcher.java @@ -20,21 +20,28 @@ public class CommandDispatcher { this.commands.add(command); } + /** + * Parse the given command + * + * @param commandString the command (containing the command name and the args if any) + * @return the result of the parsing, null if the command doesn't exist + */ public CommandResult parse(String commandString) { commandString = commandString.trim(); // Split space - String spaceRegex = " "; - String[] splitted = commandString.split(spaceRegex); - String commandName = splitted[0]; + final String spaceRegex = " "; + final String[] splitted = commandString.split(spaceRegex); + final String commandName = splitted[0]; - String[] args = commandString.replaceFirst(Pattern.quote(commandName), "").trim().split(spaceRegex); + final String[] args = commandString.replaceFirst(Pattern.quote(commandName), "").trim().split(spaceRegex); - Command command = findCommand(commandName); - // TODO change return + final Command command = findCommand(commandName); + // Check if the command exists if (command == null) return null; + // Find the used syntax, or check which argument is wrong return findCommandResult(command, args); } @@ -67,15 +74,15 @@ public class CommandDispatcher { // Find syntax - Collection syntaxes = command.getSyntaxes(); + final Collection syntaxes = command.getSyntaxes(); List validSyntaxes = new ArrayList<>(); Map syntaxesValues = new HashMap<>(); TreeMap syntaxesSuggestions = new TreeMap<>(Collections.reverseOrder()); for (CommandSyntax syntax : syntaxes) { - Argument[] arguments = syntax.getArguments(); - String[] argsValues = new String[arguments.length]; + final Argument[] arguments = syntax.getArguments(); + final String[] argsValues = new String[arguments.length]; boolean syntaxCorrect = true; int argIndex = 0; @@ -91,7 +98,7 @@ public class CommandDispatcher { if (useRemaining) { for (int i = argIndex; i < args.length; i++) { - String arg = args[i]; + final String arg = args[i]; if (argValue.length() > 0) argValue += " "; argValue += arg; @@ -104,7 +111,7 @@ public class CommandDispatcher { } } else { for (int i = argIndex; i < args.length; i++) { - String arg = args[i]; + final String arg = args[i]; argValue += arg; @@ -153,14 +160,14 @@ public class CommandDispatcher { // Verify args conditions of finalSyntax if (finalSyntax != null) { - Argument[] arguments = finalSyntax.getArguments(); - String[] argsValues = syntaxesValues.get(finalSyntax); + final Argument[] arguments = finalSyntax.getArguments(); + final String[] argsValues = syntaxesValues.get(finalSyntax); for (int i = 0; i < arguments.length; i++) { - Argument argument = arguments[i]; - String argValue = argsValues[i]; + final Argument argument = arguments[i]; + final String argValue = argsValues[i]; // Finally parse it - Object parsedValue = argument.parse(argValue); - int conditionResult = argument.getConditionResult(parsedValue); + final Object parsedValue = argument.parse(argValue); + final int conditionResult = argument.getConditionResult(parsedValue); if (conditionResult == Argument.SUCCESS) { executorArgs.setArg(argument.getId(), parsedValue); } else { @@ -177,12 +184,12 @@ public class CommandDispatcher { if (finalSyntax == null) { // Get closest valid syntax if (!syntaxesSuggestions.isEmpty()) { - int max = syntaxesSuggestions.firstKey(); - CommandSuggestionHolder suggestionHolder = syntaxesSuggestions.get(max); - CommandSyntax syntax = suggestionHolder.syntax; - String argValue = suggestionHolder.argValue; - int correctionResult = suggestionHolder.correctionResult; - int argIndex = suggestionHolder.argIndex; + final int max = syntaxesSuggestions.firstKey(); + final CommandSuggestionHolder suggestionHolder = syntaxesSuggestions.get(max); + final CommandSyntax syntax = suggestionHolder.syntax; + final String argValue = suggestionHolder.argValue; + final int correctionResult = suggestionHolder.correctionResult; + final int argIndex = suggestionHolder.argIndex; if (argValue.length() > 0) { Argument argument = syntax.getArguments()[argIndex]; @@ -227,18 +234,29 @@ public class CommandDispatcher { private String value; private int error; + /** + * Execute the command for the given source + *

+ * The command will not be executed if the {@link CommandCondition} of the command + * is not validated + * + * @param source the command source + */ public void execute(CommandSender source) { // Condition check - CommandCondition condition = command.getCondition(); + final CommandCondition condition = command.getCondition(); if (condition != null) { - boolean result = condition.apply(source); + final boolean result = condition.apply(source); if (!result) return; } // Condition is respected if (executor != null) { + // An executor has been found executor.apply(source, arguments); } else if (callback != null) { + // No syntax has been validated but the faulty argument has been found + // Execute the faulty argument callback callback.apply(source, value, error); } } diff --git a/src/main/java/net/minestom/server/command/builder/arguments/ArgumentStructure.java b/src/main/java/net/minestom/server/command/builder/arguments/ArgumentStructure.java deleted file mode 100644 index 4b7e2bfc1..000000000 --- a/src/main/java/net/minestom/server/command/builder/arguments/ArgumentStructure.java +++ /dev/null @@ -1,253 +0,0 @@ -package net.minestom.server.command.builder.arguments; - -import net.minestom.server.command.builder.structure.Structure; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -public class ArgumentStructure extends Argument { - - public static final int BRACKET_ERROR = 1; - public static final int UNKNOWN_MEMBER_ERROR = 2; - public static final int UNDEFINED_MEMBER_ERROR = 3; - public static final int INVALID_MEMBER_ERROR = 4; - - private Map argByKey; - private Map memberTypeByKey; - - private Map optionalDefaultValues; - - public ArgumentStructure(String id) { - super(id, true); - - this.argByKey = new HashMap<>(); - this.memberTypeByKey = new HashMap<>(); - - this.optionalDefaultValues = new HashMap<>(); - } - - public void addNecessaryMember(Argument argument) { - if (argument.useRemaining()) { - System.err.println("Array type is unsupported with structure"); - return; - } - - String key = argument.getId().toLowerCase(); - this.argByKey.put(key, argument); - this.memberTypeByKey.put(key, MemberType.NECESSARY); - } - - public void addOptionalMember(Argument argument, Object defaultValue) { - if (argument.useRemaining()) { - System.err.println("Array type is unsupported with structure"); - return; - } - - String key = argument.getId().toLowerCase(); - this.argByKey.put(key, argument); - this.memberTypeByKey.put(key, MemberType.OPTIONAL); - this.optionalDefaultValues.put(key, defaultValue); - } - - - @Override - public int getCorrectionResult(String value) { - // Check if value start and end with bracket - char first = value.charAt(0); - char last = value.charAt(value.length() - 1); - boolean bracket = first == '[' && last == ']'; - if (!bracket) - return BRACKET_ERROR; - // Remove them - value = value.substring(1, value.length() - 1); - - // Start analyze - - // Get all necessary keys - List necessaryMembers = memberTypeByKey.keySet().stream().filter(id -> memberTypeByKey.get(id).equals(MemberType.NECESSARY)).collect(Collectors.toList()); - - int index = 0; - - for (int i = 0; i < value.length(); i++) { - char c = value.charAt(i); - - if (c == '=') { - - if (index > i) - continue; - - String varName = value.substring(index, i).trim().toLowerCase(); - //System.out.println("VARNAME: " + varName); - int declarationStartIndex = i + 1; - - Argument arg = argByKey.getOrDefault(varName, null); - if (arg == null) { - // There isn't any member with varName name - return UNKNOWN_MEMBER_ERROR; - } - - int[] semicolons = getSemiColonsIndex(value, declarationStartIndex); - - - String argValue; - // There isn't any other initialization - if (semicolons[0] == -1) { - argValue = value.substring(i + 1).trim(); - if (!isValueValid(arg, argValue)) { - // No valid arg - return INVALID_MEMBER_ERROR; - } - necessaryMembers.remove(varName); - // END - break; - } else { - boolean correct = false; - boolean shouldBreak = false; - for (int semicolonIndex : semicolons) { - if (semicolonIndex == -1) { - argValue = value.substring(i + 1).trim(); - if (!isValueValid(arg, argValue)) { - // No valid arg - return INVALID_MEMBER_ERROR; - } - shouldBreak = true; - correct = true; - necessaryMembers.remove(varName); - break; - } - - argValue = value.substring(i + 1, semicolonIndex).trim(); - if (isValueValid(arg, argValue)) { - index = semicolonIndex + 1; - correct = true; - necessaryMembers.remove(varName); - break; - } - } - - if (!correct) { - return INVALID_MEMBER_ERROR; - } - - if (shouldBreak) { - break; - } - } - } - - } - - return necessaryMembers.isEmpty() ? SUCCESS : UNDEFINED_MEMBER_ERROR; - } - - @Override - public Structure parse(String value) { - - Structure struct = new Structure(); - - - // Remove first and last characters (bracket) - value = value.substring(1, value.length() - 1); - - List optionalMembers = memberTypeByKey.keySet().stream().filter(id -> memberTypeByKey.get(id).equals(MemberType.OPTIONAL)).collect(Collectors.toList()); - int index = 0; - - for (int i = 0; i < value.length(); i++) { - char c = value.charAt(i); - - if (c == '=') { - - if (index > i) - continue; - - String varName = value.substring(index, i).trim().toLowerCase(); - int declarationStartIndex = i + 1; - - Argument arg = argByKey.getOrDefault(varName, null); - - int[] semicolons = getSemiColonsIndex(value, declarationStartIndex); - - String argValue; - // There isn't any other initialization - if (semicolons[0] == -1) { - argValue = value.substring(i + 1).trim(); - struct.setValue(varName, arg.parse(argValue)); - optionalMembers.remove(varName); - // END - break; - } else { - boolean shouldBreak = false; - for (int semicolonIndex : semicolons) { - if (semicolonIndex == -1) { - argValue = value.substring(i + 1).trim(); - if (isValueValid(arg, argValue)) { - shouldBreak = true; - struct.setValue(varName, arg.parse(argValue)); - optionalMembers.remove(varName); - } - break; - } - - argValue = value.substring(i + 1, semicolonIndex).trim(); - if (isValueValid(arg, argValue)) { - index = semicolonIndex + 1; - struct.setValue(varName, arg.parse(argValue)); - optionalMembers.remove(varName); - break; - } - } - - if (shouldBreak) { - break; - } - } - - // Check where is next "valid" semicolon (after definition) - // Take value between member initialization as arg and check validity - // continue - } - - } - - if (!optionalMembers.isEmpty()) { - for (String id : optionalMembers) { - struct.setValue(id, optionalDefaultValues.get(id)); - } - } - - return struct; - } - - @Override - public int getConditionResult(Structure value) { - return SUCCESS; - } - - private boolean isValueValid(Argument argument, String argValue) { - return argument.getCorrectionResult(argValue) == SUCCESS && - argument.getConditionResult(argValue) == SUCCESS; - } - - private int[] getSemiColonsIndex(String value, int index) { - int[] array = new int[value.length() - index]; - - // Used to see if array has been modified - Arrays.fill(array, -1); - - int count = 0; - for (int i = index; i < value.length(); i++) { - if (value.charAt(i) == ';') { - array[count] = i; - count++; - } - } - return array; - } - - private enum MemberType { - NECESSARY, OPTIONAL - } -} diff --git a/src/main/java/net/minestom/server/command/builder/arguments/ArgumentType.java b/src/main/java/net/minestom/server/command/builder/arguments/ArgumentType.java index f99c6bc7c..e102079ea 100644 --- a/src/main/java/net/minestom/server/command/builder/arguments/ArgumentType.java +++ b/src/main/java/net/minestom/server/command/builder/arguments/ArgumentType.java @@ -10,12 +10,11 @@ import net.minestom.server.command.builder.arguments.number.ArgumentFloat; import net.minestom.server.command.builder.arguments.number.ArgumentInteger; import net.minestom.server.command.builder.arguments.number.ArgumentLong; +/** + * Class listing all the basic arguments + */ public class ArgumentType { - public static ArgumentStructure Structure(String id) { - return new ArgumentStructure(id); - } - public static ArgumentBoolean Boolean(String id) { return new ArgumentBoolean(id); } diff --git a/src/main/java/net/minestom/server/command/builder/condition/CommandCondition.java b/src/main/java/net/minestom/server/command/builder/condition/CommandCondition.java index e5ee5719c..7e7ce9576 100644 --- a/src/main/java/net/minestom/server/command/builder/condition/CommandCondition.java +++ b/src/main/java/net/minestom/server/command/builder/condition/CommandCondition.java @@ -2,6 +2,9 @@ package net.minestom.server.command.builder.condition; import net.minestom.server.command.CommandSender; +/** + * Used to know if the command source is allowed to run the command + */ public interface CommandCondition { boolean apply(CommandSender source); } diff --git a/src/main/java/net/minestom/server/command/builder/structure/Structure.java b/src/main/java/net/minestom/server/command/builder/structure/Structure.java deleted file mode 100644 index ba3719554..000000000 --- a/src/main/java/net/minestom/server/command/builder/structure/Structure.java +++ /dev/null @@ -1,50 +0,0 @@ -package net.minestom.server.command.builder.structure; - -import java.util.HashMap; -import java.util.Map; - -public class Structure { - - private Map values = new HashMap<>(); - - public Structure getStructure(String id) { - return (Structure) getObject(id); - } - - public boolean getBoolean(String id) { - return (boolean) getObject(id); - } - - public long getLong(String id) { - return (long) getObject(id); - } - - public int getInteger(String id) { - return (int) getObject(id); - } - - public double getDouble(String id) { - return (double) getObject(id); - } - - public float getFloat(String id) { - return (float) getObject(id); - } - - public String getString(String id) { - return (String) getObject(id); - } - - public String getWord(String id) { - return getString(id); - } - - public Object getObject(String id) { - return values.getOrDefault(id, null); - } - - public void setValue(String key, Object value) { - this.values.put(key, value); - } - -} diff --git a/src/main/java/net/minestom/server/entity/ExperienceOrb.java b/src/main/java/net/minestom/server/entity/ExperienceOrb.java index e729b12b3..7c7fc81be 100644 --- a/src/main/java/net/minestom/server/entity/ExperienceOrb.java +++ b/src/main/java/net/minestom/server/entity/ExperienceOrb.java @@ -3,9 +3,6 @@ package net.minestom.server.entity; import net.minestom.server.network.packet.server.play.SpawnExperienceOrbPacket; import net.minestom.server.network.player.PlayerConnection; -import java.util.HashSet; -import java.util.Set; - public class ExperienceOrb extends Entity { private short experienceCount; @@ -29,7 +26,11 @@ public class ExperienceOrb extends Entity { @Override public boolean addViewer(Player player) { - PlayerConnection playerConnection = player.getPlayerConnection(); + final boolean result = super.addViewer(player); // Add player to viewers list + if (!result) + return false; + + final PlayerConnection playerConnection = player.getPlayerConnection(); SpawnExperienceOrbPacket experienceOrbPacket = new SpawnExperienceOrbPacket(); experienceOrbPacket.entityId = getEntityId(); @@ -39,27 +40,29 @@ public class ExperienceOrb extends Entity { playerConnection.sendPacket(experienceOrbPacket); playerConnection.sendPacket(getVelocityPacket()); - return super.addViewer(player); // Add player to viewers list + return result; } /** - * @return the experience amount contained in the entity + * Get the experience count + * + * @return the experience count */ public short getExperienceCount() { return experienceCount; } /** - * @param experienceCount the new experience amount + * Change the experience count + * + * @param experienceCount the new experience count */ public void setExperienceCount(short experienceCount) { // Remove the entity in order to respawn it with the correct experience count - Set viewerCache = new HashSet<>(getViewers()); - - viewerCache.forEach(player -> removeViewer(player)); + getViewers().forEach(player -> removeViewer(player)); this.experienceCount = experienceCount; - viewerCache.forEach(player -> addViewer(player)); + getViewers().forEach(player -> addViewer(player)); } } diff --git a/src/main/java/net/minestom/server/entity/damage/EntityDamage.java b/src/main/java/net/minestom/server/entity/damage/EntityDamage.java index 1bc785b7c..7857b7e4e 100644 --- a/src/main/java/net/minestom/server/entity/damage/EntityDamage.java +++ b/src/main/java/net/minestom/server/entity/damage/EntityDamage.java @@ -15,7 +15,9 @@ public class EntityDamage extends DamageType { } /** - * @return the source of the damage + * Get the source of the damage + * + * @return the source */ public Entity getSource() { return source; diff --git a/src/main/java/net/minestom/server/entity/damage/EntityProjectileDamage.java b/src/main/java/net/minestom/server/entity/damage/EntityProjectileDamage.java index 1d9c2adb7..8c44e7e53 100644 --- a/src/main/java/net/minestom/server/entity/damage/EntityProjectileDamage.java +++ b/src/main/java/net/minestom/server/entity/damage/EntityProjectileDamage.java @@ -7,7 +7,7 @@ import net.minestom.server.entity.Entity; */ public class EntityProjectileDamage extends DamageType { - private Entity shooter; + private final Entity shooter; private final Entity projectile; public EntityProjectileDamage(Entity shooter, Entity projectile) { @@ -16,10 +16,20 @@ public class EntityProjectileDamage extends DamageType { this.projectile = projectile; } + /** + * Get the projectile responsive for the damage + * + * @return the projectile + */ public Entity getProjectile() { return projectile; } + /** + * Get the shooter of the projectile + * + * @return the shooter of the projectile, null if not any + */ public Entity getShooter() { return shooter; }