diff --git a/extension/src/main/java/de/jeremystartm/pickshadow/extension/BuildOptions.java b/extension/src/main/java/de/jeremystartm/pickshadow/extension/BuildOptions.java index a18a2fc..84ac322 100644 --- a/extension/src/main/java/de/jeremystartm/pickshadow/extension/BuildOptions.java +++ b/extension/src/main/java/de/jeremystartm/pickshadow/extension/BuildOptions.java @@ -19,6 +19,8 @@ package de.jeremystartm.pickshadow.extension; +import de.jeremystartm.pickshadow.extension.misc.TabListHandler; +import net.luckperms.api.util.Tristate; import org.bukkit.potion.PotionEffectType; import org.jetbrains.annotations.NotNull; @@ -36,10 +38,58 @@ import java.util.List; * @since v1-release0 */ public final class BuildOptions { + // -----> Links + /** + * Contains the link to the repository + * hosting PickShadow's server-side code. + * + * @since v1-release0 + */ + public static final @NotNull String LINKS_PICKSHADOW_WEBSITE = ""; + + /** + * Contains the link to the repository + * hosting PickShadow's server-side code. + * + * @since v1-release0 + */ + public static final @NotNull String LINKS_PICKSHADOW_FORUM = ""; + + /** + * Contains the link to the repository + * hosting PickShadow's server-side code. + * + * @since v1-release0 + */ + public static final @NotNull String LINKS_PICKSHADOW_REPOSITORY = "https://git.staropensource.de/JeremyStarTM/PickShadow"; + + /** + * Contains the link to LuckPerms' website. + * + * @since v1-release0 + */ + public static final @NotNull String LINKS_LUCKPERMS = "https://luckperms.net"; + + /** + * Contains the link to ViaVersions' website. + * + * @since v1-release0 + */ + public static final @NotNull String LINKS_VIAVERSION = "https://viaversion.com"; + + /** + * Contains the link to FreedomChat's Modrinth site. + * + * @since v1-release0 + */ + public static final @NotNull String LINKS_FREEDOMCHAT = "https://modrinth.com/plugin/freedomchat"; + + // -----> Settings /** - * Contains an array of all bad effects. + * An array containing all bad effects. * + * @see PotionEffectType * @since v1-release0 */ public static final @NotNull List<@NotNull PotionEffectType> SETTINGS_EFFECTS_BAD = List.of( @@ -58,9 +108,11 @@ public final class BuildOptions { PotionEffectType.RAID_OMEN, PotionEffectType.INFESTED ); + /** - * Contains an array of all damaging effects. + * An array containing all damaging effects. * + * @see PotionEffectType * @since v1-release0 */ public static final @NotNull List<@NotNull PotionEffectType> SETTINGS_EFFECTS_DAMAGING = List.of( @@ -69,6 +121,15 @@ public final class BuildOptions { PotionEffectType.POISON ); + /** + * The rate at which the tab list shall be updated. + * + * @see TabListHandler + * @since v1-release0 + */ + public static final long SETTINGS_TABLIST_UPDATERATE = 20L; + + // -----> Fixes and unfixes /** * Unfixes MC-212 (fixed in 24w45a), @@ -82,6 +143,7 @@ public final class BuildOptions { */ public static final boolean UNFIX_FALLDAMAGE_CANCELLING = true; + // -----> Small stuff /** * Hides all messages starting with {@code #}. @@ -105,4 +167,24 @@ public final class BuildOptions { * @since v1-release0 */ public static final boolean SMALLSTUFF_STONECUTTER_DAMAGE = true; + + /** + * Determines if and how + * bats shall be killed. + *

+ * {@link Tristate#TRUE} causes all bats to be killed. + * {@link Tristate#UNDEFINED} will kill all naturally spawned bats. + * {@link Tristate#FALSE} disables this setting. + * + * @since v1-release0 + */ + public static final @NotNull Tristate SMALLSTUFF_KILL_BATS = Tristate.UNDEFINED; + + /** + * Enables a faster walking + * if walking on a path block. + * + * @since v1-release0 + */ + public static final boolean SMALLSTUFF_FASTER_PATH_WALKING = true; } diff --git a/extension/src/main/java/de/jeremystartm/pickshadow/extension/Extension.java b/extension/src/main/java/de/jeremystartm/pickshadow/extension/Extension.java index 5c48bbe..21882f9 100644 --- a/extension/src/main/java/de/jeremystartm/pickshadow/extension/Extension.java +++ b/extension/src/main/java/de/jeremystartm/pickshadow/extension/Extension.java @@ -20,7 +20,6 @@ package de.jeremystartm.pickshadow.extension; import de.jeremystartm.pickshadow.common.CommonLibrary; -import de.jeremystartm.pickshadow.extension.api.entity.player.PlayerDataFactory; import de.jeremystartm.pickshadow.extension.api.translation.TranslationManager; import de.jeremystartm.pickshadow.extension.command.general.AnnounceCommand; import de.jeremystartm.pickshadow.extension.command.general.replacement.GamemodeCommand; @@ -97,7 +96,6 @@ public final class Extension extends JavaPlugin { TranslationManager.loadTranslations(); TranslationManager.processTranslations(); //TabListHandler.initialize(); - PlayerDataFactory.initialize(); Logger.info("Bootstrapped in " + Miscellaneous.measureExecutionTime(() -> {}) + "ms"); } @@ -132,7 +130,9 @@ public final class Extension extends JavaPlugin { new ClearChatCommand(); new ExtensionCommand(); new LanguageCommand(); + new LegacyExtensionCommand(); new LinkCommand(); + new SpeedCommand(); new TrollCommand(); @@ -150,8 +150,8 @@ public final class Extension extends JavaPlugin { Logger.verb("Starting schedulers"); Bukkit.getServer().getGlobalRegionScheduler().runAtFixedRate(Extension.getInstance(), Scheduler::server, 1L, 1L); }) + "ms"); - } catch (Exception exception) { - Logger.crash("Initialization failed", exception); + } catch (Throwable throwable) { + Logger.crash("Initialization failed", throwable); } } diff --git a/extension/src/main/java/de/jeremystartm/pickshadow/extension/api/command/Command.java b/extension/src/main/java/de/jeremystartm/pickshadow/extension/api/command/Command.java new file mode 100644 index 0000000..c39820f --- /dev/null +++ b/extension/src/main/java/de/jeremystartm/pickshadow/extension/api/command/Command.java @@ -0,0 +1,453 @@ +/* + * PICKSHADOW SERVER KIT SOURCE FILE + * Copyright (c) 2024 The PickShadow Server Kit authors + * Licensed under the GNU Affero General Public License v3 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.jeremystartm.pickshadow.extension.api.command; + +import de.jeremystartm.pickshadow.extension.ExtensionConfiguration; +import de.jeremystartm.pickshadow.extension.api.entity.player.PSPlayer; +import de.jeremystartm.pickshadow.extension.api.entity.player.PSPlayerFactory; +import de.jeremystartm.pickshadow.extension.api.translation.LanguageString; +import de.jeremystartm.pickshadow.extension.api.translation.TranslationManager; +import de.staropensource.engine.base.logging.Logger; +import lombok.Getter; +import org.bukkit.Bukkit; +import org.bukkit.command.*; +import org.bukkit.craftbukkit.command.ServerCommandSender; +import org.bukkit.entity.Player; +import org.bukkit.util.StringUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Range; + +import java.util.*; + +import static java.util.Map.entry; + +/** + * Abstract class for implementing commands. + * + * @see CommandForced + * @since v1-release0 + */ +@Getter +@SuppressWarnings({ "JavadocDeclaration" }) +public abstract class Command implements CommandExecutor { + /** + * Contains a list of all registered commands. + * + * @since v1-release0 + */ + private static final @NotNull List<@NotNull Command> REGISTERED = new ArrayList<>(); + + /** + * Contains a {@link CommandExecutor} implementation + * for disabled commands (caused by a disabled mode). + * + * @see ExtensionConfiguration#getEnabledModes() + * @since v1-release0 + */ + public static final @NotNull CommandExecutor disallowedExecutor = (sender, command, alias, arguments) -> { + sender.sendRichMessage(TranslationManager.get(LanguageString.ERROR_INVALID_MODE, sender, true)); + return true; + }; + + /** + * Contains information about this command. + * + * @since v1-release0 + * -- GETTER -- + * Returns information about this command. + * + * @return command information + * @since v1-release0 + */ + private final @NotNull Command.Information information; + + // -----> Constructors + /** + * Initializes this abstract class + * and registers the command. + * + * @param information information about the command + * @param commands all commands this class should handle + * @throws IllegalArgumentException if a command does not exist + * @since v1-release0 + */ + public Command(@NotNull Command.Information information, @NotNull String... commands) throws IllegalArgumentException { + boolean disallowedByMode = !Arrays.stream(ExtensionConfiguration.getInstance().getEnabledModes()).toList().contains(information.mode()); + this.information = information; + + for (String command : commands) { + PluginCommand pluginCommand = Bukkit.getPluginCommand(command); + if (pluginCommand == null) + throw new IllegalArgumentException("Command registration failed: The command \"" + command + "\" does not exist"); + + if (disallowedByMode) { + pluginCommand.setExecutor(disallowedExecutor); + pluginCommand.setTabCompleter(null); + } else { + pluginCommand.setExecutor(this); + pluginCommand.setTabCompleter(new TabCompleter() { + @Override + public @NotNull List onTabComplete(@NotNull CommandSender sender, @NotNull org.bukkit.command.Command command, @NotNull String label, @NotNull String[] args) { + return StringUtil.copyPartialMatches(args[args.length - 1], getCompletion().complete(sender, label, args), new ArrayList<>()); + } + }); + } + } + + REGISTERED.add(this); + } + + /** + * Initializes this abstract class + * and registers the command. + *

+ * Using this constructor instead of + * {@link #Command(Information, String...)} + * causes the creation of a dummy command + * which must be registered manually. + * Not recommended, use only when needed. + * + * @since v1-release0 + */ + public Command(@NotNull Command.Information information) { + this.information = information; + REGISTERED.add(this); + } + + + // -----> Static methods + /** + * Returns a list of all registered commands. + * + * @return list of registered commands + * @since v1-release0 + */ + public static List<@NotNull Command> getREGISTERED() { + return Collections.unmodifiableList(REGISTERED); + } + + + // -----> Getters + /** + * Provides tab completions for this command. + * + * @return completion + * @since v1-release0 + */ + public abstract @NotNull TabCompletion getCompletion(); + + + // -----> Command invocation + /** + * Required by the {@link CommandExecutor} interface. + * + * @deprecated use {@link #invoke(CommandSender, String, String[])} instead + * @see #invoke(CommandSender, String, String[]) + * @since v1-release0 + */ + @Deprecated + @Override + public final boolean onCommand(@NotNull CommandSender sender, @NotNull org.bukkit.command.Command command, @NotNull String alias, @NotNull String @NotNull [] arguments) { + invoke(sender, alias, arguments); + return true; + } + + /** + * Executes this command. + * + * @since v1-release0 + */ + public final void invoke(@NotNull CommandSender sender, @NotNull String alias, @NotNull String @NotNull [] arguments) { + try { + invokeAll(sender, alias, arguments); + + if (sender instanceof Player player) + invokePlayer(PSPlayerFactory.get(player), alias, arguments); + else if (sender instanceof ServerCommandSender consoleSender) + invokeConsole(consoleSender, alias, arguments); + else + throw new IllegalStateException("The command was neither executed by a player nor the server console"); + } catch (Exception exception) { + sender.sendRichMessage(TranslationManager.get(LanguageString.ERROR_UNKNOWN, sender, true)); + Logger.crash( + "Command /" + + information.name() + + " (under alias /" + + alias + + ") failed for sender " + + sender.getName() + + " (console=" + + (sender instanceof ConsoleCommandSender) + + ") with the following arguments:\n" + + Arrays.toString(arguments), + exception, + false); + } + } + + /** + * Command handler, regardless of whether the + * caller is a player or the server console. + * + * @param sender command sender which invoked this command + * @param alias alias used + * @param arguments command arguments + * @throws Exception on error + * @since v1-release0 + */ + @SuppressWarnings("RedundantThrows") + protected void invokeAll(@NotNull CommandSender sender, @NotNull String alias, @NotNull String @NotNull [] arguments) throws Exception {} + + /** + * Command handler, invoked if the command + * has been invoked by the server console. + * + * @param console console command sender + * @param alias alias used + * @param arguments command arguments + * @throws Exception on error + * @since v1-release0 + */ + @SuppressWarnings("RedundantThrows") + protected void invokeConsole(@NotNull ServerCommandSender console, @NotNull String alias, @NotNull String @NotNull [] arguments) throws Exception { + if (information.executionTarget == ExecutionTarget.PLAYERS_ONLY) + console.sendRichMessage(TranslationManager.get(LanguageString.ERROR_NOT_A_PLAYER, console, true)); + } + + /** + * Command handler, invoked if the + * command has been invoked by a player. + * + * @param player player which invoked this command + * @param alias alias used + * @param arguments command arguments + * @throws Exception on error + * @since v1-release0 + */ + @SuppressWarnings("RedundantThrows") + protected void invokePlayer(@NotNull PSPlayer player, @NotNull String alias, @NotNull String @NotNull [] arguments) throws Exception { + if (information.executionTarget == ExecutionTarget.CONSOLE_ONLY) + player.messageTranslatable(LanguageString.ERROR_NOT_SERVER_CONSOLE, true); + } + + // -----> Utility methods + /** + * Performs a permission check, sends the sender + * an error message and then returns. + * Useful for easy permission checks. + * + * @param sender sender to check + * @param permission permission to check for + * @return {@code true} if the permission is missing, {@code false} otherwise + * @since v1-release0 + */ + protected static boolean checkPermission(@NotNull CommandSender sender, @NotNull String permission) { + if (sender instanceof ServerCommandSender) + return false; + + if (!sender.hasPermission(permission)) { + sender.sendRichMessage( + TranslationManager.get(LanguageString.ERROR_MISSING_PERM, sender, true) + .replace("%permission%", permission) + ); + return true; + } + + return false; + } + + /** + * Performs a permission check, sends the sender + * an error message and then returns. + * Useful for easy permission checks. + * + * @param player {@link PSPlayer} to check + * @param permission permission to check for + * @return {@code true} if the permission is missing, {@code false} otherwise + * @since v1-release0 + */ + protected static boolean checkPermission(@NotNull PSPlayer player, @NotNull String permission) { + if (!player.hasPermission(permission)) { + player.messageTranslatable( + LanguageString.ERROR_MISSING_PERM, + true, + entry("permission", permission) + ); + return true; + } + + return false; + } + + /** + * Determines the target player. + * + * @param sender command sender + * @param arguments array of arguments + * @param index index of the player name to check at + * @return target player and if the supplied command sender was used + * @throws IndexOutOfBoundsException if the size of {@code arguments} is smaller than the specified {@code index} + * @throws IllegalCallerException if no target was specified in {@code arguments} and the sender is the server console + * @throws IllegalArgumentException if the specified target in {@code arguments} is invalid or does not exist + * @since v1-release0 + */ + public static @NotNull Map.Entry<@NotNull PSPlayer, @NotNull Boolean> determineTarget(@NotNull CommandSender sender, @NotNull String @NotNull [] arguments, int index) throws IndexOutOfBoundsException, IllegalCallerException, IllegalArgumentException { + if (arguments.length == index) { + if (sender instanceof Player bukkitPlayer) + return entry(PSPlayerFactory.get(bukkitPlayer), true); + else { + sender.sendRichMessage(TranslationManager.get(LanguageString.ERROR_NOT_A_PLAYER, sender, true)); + throw new IllegalCallerException("No target player was specified and the command sender is the server console"); + } + } else if (arguments.length > index) { + Player bukkitPlayer = Bukkit.getPlayer(arguments[index]); + PSPlayer player; + + if (bukkitPlayer == null) { + sender.sendRichMessage(TranslationManager.get( + LanguageString.ERROR_PLAYER_NOT_FOUND, + sender, + true, + entry("player", arguments[index]) + )); + throw new IllegalArgumentException("The specified target player '" + arguments[index] + "' isn't online or is invalid"); + } else + player = PSPlayerFactory.get(bukkitPlayer); + + return entry(player, player.getUsername().equals(sender.getName())); + } else { + throw new IndexOutOfBoundsException("Size of 'arguments' is smaller than the specified 'index'"); + } + } + + // -----> Inner classes + /** + * Represents by whom a command can be used. + * + * @since v1-release0 + */ + public enum ExecutionTarget { + /** + * Restricts the command to players only. + * + * @since v1-release0 + */ + PLAYERS_ONLY, + + /** + * Restricts the command to the server console only. + * + * @since v1-release0 + */ + CONSOLE_ONLY, + + /** + * Restricts certain actions of the command to players only. + * + * @since v1-release0 + */ + CONSOLE_PARTIAL, + + /** + * Restricts certain actions of the command to the server console only. + * + * @since v1-release0 + */ + PLAYERS_PARTIAL, + + /** + * Allows the command to be executed by players and the server console. + * + * @since v1-release0 + */ + ALL + } + + /** + * Provides information about a command. + * + * @param name Name of the command + * @param aliases Array of acceptable aliases + * @param description Description of the command + * @param syntax Syntax of the command + * @param mode Plugin mode of the command + * @param executionOrder Execution order in which the three execution methods shall be invoked. + *

+ * Must contain exactly three elements, representing this order: + *

    + *
  1. {@link #invokeAll(CommandSender, String, String[])}
  2. + *
  3. {@link #invokeConsole(ServerCommandSender, String, String[])}
  4. + *
  5. {@link #invokePlayer(PSPlayer, String, String[])}
  6. + *
+ * Values are required to be between {@code 0} and {@code 2}, with + * each value being used only once. + *

+ * Examples: + *

+ *

+ * If set to {@code null}, {@code new int[]{ 0, 1, 2 }} will be used. + * @param executionTarget information about who can execute the command + * @since v1-release0 + */ + public record Information ( + @NotNull String name, + @NotNull String @NotNull [] aliases, + @NotNull String description, + @NotNull String syntax, + @NotNull String mode, + @Range(from = 0, to = 2) int @Nullable [] executionOrder, + @NotNull Command.ExecutionTarget executionTarget + ) { + /** + * Creates and initializes an + * instance of this record. + */ + public Information { + if (executionOrder == null) + executionOrder = new int[]{ 0, 1, 2 }; + + // Verify 'executionOrder' + // -> Amount of items + if (executionOrder.length != 3) + throw new IllegalStateException("'executionOrder' does not contain exactly three items"); + + // -> Bounds + if ( + (executionOrder[0] < 0 || executionOrder[0] > 2) + || (executionOrder[1] < 0 || executionOrder[1] > 2) + || (executionOrder[2] < 0 || executionOrder[2] > 2) + ) + throw new IllegalStateException("Some item in 'executionOrder' is either smaller than '0' or bigger than '2'"); + + // -> Duplicate values + if ( + executionOrder[0] == executionOrder[1] + || executionOrder[0] == executionOrder[2] + || executionOrder[1] == executionOrder[2] + ) + throw new IllegalStateException("Two or more items in 'executionOrder' have the same value"); + } + } +} diff --git a/extension/src/main/java/de/jeremystartm/pickshadow/extension/api/command/CommandBase.java b/extension/src/main/java/de/jeremystartm/pickshadow/extension/api/command/CommandBase.java deleted file mode 100644 index dc7b836..0000000 --- a/extension/src/main/java/de/jeremystartm/pickshadow/extension/api/command/CommandBase.java +++ /dev/null @@ -1,198 +0,0 @@ -/* - * PICKSHADOW SERVER KIT SOURCE FILE - * Copyright (c) 2024 The PickShadow Server Kit authors - * Licensed under the GNU Affero General Public License v3 - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package de.jeremystartm.pickshadow.extension.api.command; - -import de.jeremystartm.pickshadow.extension.ExtensionConfiguration; -import de.jeremystartm.pickshadow.extension.api.translation.LanguageString; -import de.jeremystartm.pickshadow.extension.api.translation.TranslationManager; -import de.staropensource.engine.base.logging.Logger; -import lombok.Getter; -import org.bukkit.Bukkit; -import org.bukkit.command.*; -import org.bukkit.craftbukkit.command.ServerCommandSender; -import org.bukkit.util.StringUtil; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -/** - * Abstract class for implementing commands. - * - * @since v1-release0 - */ -@Getter -public abstract class CommandBase implements CommandExecutor { - /** - * Contains a list of all registered commands. - * - * @since v1-release0 - */ - private static final @NotNull List<@NotNull CommandBase> REGISTERED = new ArrayList<>(); - - /** - * Contains a {@link CommandExecutor} implementation - * for disabled commands (caused by a disabled mode). - * - * @see ExtensionConfiguration#getEnabledModes() - * @since v1-release0 - */ - public static final @NotNull CommandExecutor disallowedExecutor = (sender, command, alias, arguments) -> { - sender.sendRichMessage(TranslationManager.get(LanguageString.ERROR_INVALID_MODE, sender, true)); - return true; - }; - - /** - * Contains a list of all {@link Command}s this - * command has been registered for. - * - * @since v1-release0 - */ - private final @NotNull List<@NotNull PluginCommand> commands = new ArrayList<>(); - - /** - * Initializes this abstract class - * and registers the command. - * - * @param mode mode to register this command in - * @param commands all commands this class should handle - * @throws IllegalArgumentException if a command does not exist - * @since v1-release0 - */ - public CommandBase(@NotNull String mode, @NotNull String... commands) throws IllegalArgumentException { - boolean disallowedByMode = !Arrays.stream(ExtensionConfiguration.getInstance().getEnabledModes()).toList().contains(mode); - - for (String command : commands) { - PluginCommand pluginCommand = Bukkit.getPluginCommand(command); - if (pluginCommand == null) - throw new IllegalArgumentException("Command registration failed: The command \"" + command + "\" does not exist"); - - if (disallowedByMode) { - pluginCommand.setExecutor(disallowedExecutor); - pluginCommand.setTabCompleter(null); - } else { - pluginCommand.setExecutor(this); - pluginCommand.setTabCompleter(new TabCompleter() { - @Override - public @NotNull List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { - return StringUtil.copyPartialMatches(args[args.length - 1], getCompletion().complete(sender, label, args), new ArrayList<>()); - } - }); - } - - this.commands.add(pluginCommand); - } - - REGISTERED.add(this); - } - - /** - * Initializes this abstract class - * and registers the command. - *

- * Using this constructor instead of - * {@link #CommandBase(String, String...)} - * causes the creation of a dummy command - * which must be registered manually. - * Not recommended, use only when needed. - * - * @since v1-release0 - */ - public CommandBase() { - REGISTERED.add(this); - } - - /** {@inheritDoc} */ - @Override - public final boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) { - try { - invoke(sender, command, alias, args); - } catch (Exception exception) { - Logger.crash( - "Command /" - + command.getName() - + " (under alias /" - + alias - + ") failed for sender " - + sender.getName() - + " (console=" - + (sender instanceof ConsoleCommandSender) - + ") with the following arguments:\n" - + Arrays.toString(args), - exception, - false); - sender.sendRichMessage(TranslationManager.get(LanguageString.ERROR_UNKNOWN, sender, true)); - } - return true; - } - - /** - * Executes this command. - * - * @since v1-release0 - */ - @SuppressWarnings("NullableProblems") // intentional, see CommandBaseWithNull - public abstract void invoke(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] arguments); - - /** - * Provides tab completions for this command. - * - * @return completion - * @since v1-release0 - */ - public abstract @NotNull TabCompletion getCompletion(); - - /** - * Performs a permission check, sends the sender - * an error message and then returns. - * Useful for easy permission checks. - * - * @param sender sender to check - * @param permission permission to check for - * @return {@code true} if the permission is missing, {@code false} otherwise - * @since v1-release0 - */ - protected static boolean checkPermission(@NotNull CommandSender sender, @NotNull String permission) { - if (sender instanceof ServerCommandSender) - return false; - - if (!sender.hasPermission(permission)) { - sender.sendRichMessage( - TranslationManager.get(LanguageString.ERROR_MISSING_PERM, sender, true) - .replace("%permission%", permission) - ); - return true; - } - - return false; - } - - /** - * Returns a list of all registered commands. - * - * @return list of registered commands - * @since v1-release0 - */ - public static List<@NotNull CommandBase> getREGISTERED() { - return Collections.unmodifiableList(REGISTERED); - } -} diff --git a/extension/src/main/java/de/jeremystartm/pickshadow/extension/api/command/CommandForced.java b/extension/src/main/java/de/jeremystartm/pickshadow/extension/api/command/CommandForced.java new file mode 100644 index 0000000..56a72fc --- /dev/null +++ b/extension/src/main/java/de/jeremystartm/pickshadow/extension/api/command/CommandForced.java @@ -0,0 +1,83 @@ +/* + * PICKSHADOW SERVER KIT SOURCE FILE + * Copyright (c) 2024 The PickShadow Server Kit authors + * Licensed under the GNU Affero General Public License v3 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.jeremystartm.pickshadow.extension.api.command; + +import de.jeremystartm.pickshadow.extension.api.entity.player.PSPlayer; +import lombok.Getter; +import org.bukkit.command.*; +import org.bukkit.craftbukkit.command.ServerCommandSender; +import org.jetbrains.annotations.NotNull; + +/** + * Abstract class for implementing commands. + *

+ * Forces {@link #invokeAll(CommandSender, String, String[])}, + * {@link #invokeConsole(ServerCommandSender, String, String[])} + * and {@link #invokePlayer(PSPlayer, String, String[])} + * on the implementation class. + *

+ * Very useful during development. + * + * @see Command + * @since v1-release0 + */ +@Getter +public abstract class CommandForced extends Command { + /** + * Initializes this abstract class + * and registers the command. + * + * @param information information about the command + * @param commands all commands this class should handle + * @throws IllegalArgumentException if a command does not exist + * @since v1-release0 + */ + public CommandForced(@NotNull Command.Information information, @NotNull String... commands) throws IllegalArgumentException { + super(information, commands); + } + + /** + * Initializes this abstract class + * and registers the command. + *

+ * Using this constructor instead of + * {@link #CommandForced(Information, String...)} + * causes the creation of a dummy command + * which must be registered manually. + * Not recommended, use only when needed. + * + * @since v1-release0 + */ + public CommandForced(@NotNull Command.Information information) { + super(information); + } + + /** {@inheritDoc} */ + @Override + protected abstract void invokeAll(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] arguments) throws Exception; + + /** {@inheritDoc} */ + @Override + protected abstract void invokeConsole(@NotNull ServerCommandSender console, @NotNull String alias, @NotNull String[] arguments) throws Exception; + + /** {@inheritDoc} */ + @Override + protected abstract void invokePlayer(@NotNull PSPlayer player, @NotNull String alias, @NotNull String[] arguments) throws Exception; +} diff --git a/extension/src/main/java/de/jeremystartm/pickshadow/extension/api/entity/player/PSPlayer.java b/extension/src/main/java/de/jeremystartm/pickshadow/extension/api/entity/player/PSPlayer.java new file mode 100644 index 0000000..4c1f8ec --- /dev/null +++ b/extension/src/main/java/de/jeremystartm/pickshadow/extension/api/entity/player/PSPlayer.java @@ -0,0 +1,1005 @@ +/* + * PICKSHADOW SERVER KIT SOURCE FILE + * Copyright (c) 2024 The PickShadow Server Kit authors + * Licensed under the GNU Affero General Public License v3 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.jeremystartm.pickshadow.extension.api.entity.player; + +import com.google.gson.*; +import com.google.gson.reflect.TypeToken; +import de.jeremystartm.pickshadow.extension.Extension; +import de.jeremystartm.pickshadow.extension.api.translation.LanguageString; +import de.jeremystartm.pickshadow.extension.api.translation.TranslationManager; +import de.jeremystartm.pickshadow.extension.api.type.PlayerAttribute; +import de.jeremystartm.pickshadow.extension.api.type.PlayerDamageState; +import de.staropensource.engine.base.logging.Logger; +import fr.mrmicky.fastboard.adventure.FastBoard; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.minecraft.server.MinecraftServer; +import org.bukkit.Bukkit; +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.damage.DamageSource; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.player.PlayerKickEvent; +import org.bukkit.permissions.Permission; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.*; + +import static java.util.Map.entry; + +/** + * Bukkit's {@link Player} class, + * but it's a lot nicer to work with. + * + * @since v1-release0 + */ +@Getter +@SuppressWarnings({ "UnusedReturnValue", "unused" }) +public final class PSPlayer { + /** + * Contains the {@link Data} instance + * associated to this player. + * + * @since v1-release0 + * -- GETTER -- + * Returns the {@link Data} instance + * associated to this player. + * + * @since v1-release0 + */ + private final @NotNull PSPlayer.Data data; + + /** + * Contains the {@link Player} instance + * associated to this player. + * + * @since v1-release0 + * -- GETTER -- + * Returns the {@link Player} instance + * associated to this player. + * + * @since v1-release0 + */ + @ApiStatus.Obsolete + private final @NotNull Player bukkitPlayer; + + /** + * Creates and initializes an + * instance of this class. + * + * @param player Bukkit {@link Player} + * @since v1-release0 + */ + PSPlayer(@NotNull Player player) { + // Set final variables + bukkitPlayer = player; + data = new Data(this); + } + + + // -----> Static + /** + * Returns an array of all online players. + * + * @return online players array + * @since v1-release0 + */ + public static @NotNull PSPlayer[] getOnline() { + List players = new ArrayList<>(); + + for (Player player : Bukkit.getOnlinePlayers()) + try { + players.add(PSPlayerFactory.get(player)); + } catch (NullPointerException ignored) {} + + return players.toArray(new PSPlayer[0]); + } + + /** + * Broadcasts the specified message + * to all online players. + *

+ * This method will run the specified + * message through MiniMessage. + * + * @param message message to broadcast + * @since v1-release0 + */ + public static void broadcast(@NotNull String message) { + Component messageFormatted = MiniMessage.miniMessage().deserialize(message); + + for (PSPlayer player : getOnline()) + player.messageRaw(message); + } + + /** + * Broadcasts the specified message + * to all online players. + *

+ * This method will automatically translate + * the specified {@link LanguageString}. + * + * @param languageString language string to get + * @param includePrefix if {@link LanguageString#PREFIX} should be prepended + * @param placeholders placeholders + * @since v1-release0 + */ + @SafeVarargs + public static void broadcastTranslatable(@NotNull LanguageString languageString, boolean includePrefix, @NotNull Map.Entry<@NotNull String, @NotNull String> @Nullable ... placeholders) { + for (PSPlayer player : getOnline()) + player.messageTranslatable(languageString, includePrefix, placeholders); + } + + /** + * Broadcasts the specified message + * to all online players. + *

+ * This method causes the message + * to be sent directly, without + * any MiniMessage formatting. + * + * @param message message to broadcast + * @since v1-release0 + */ + public static void broadcastRaw(@NotNull String message) { + for (PSPlayer player : getOnline()) + player.messageRaw(message); + } + + + // -----> Information + /** + * Returns if this player is online. + *

+ * This method will pretty much + * always return {@code true}. + * + * @return online? + * @since v1-release0 + */ + public boolean isOnline() { + return bukkitPlayer.isOnline(); + } + + /** + * Returns this player's {@link UUID}. + * + * @return player {@link UUID} + * @since v1-release0 + */ + public @NotNull UUID getUUID() { + return bukkitPlayer.getUniqueId(); + } + + /** + * Returns this player's username. + * + * @return username + * @since v1-release0 + */ + public @NotNull String getUsername() { + return bukkitPlayer.getName(); + } + + /** + * Returns the full player identification + * for use in log messages and other strings. + * + * @return human-friendly player identification string + * @since v1-release0 + */ + public @NotNull String getIdentificationString() { + return getUsername() + " [" + getUUID() + "]"; + } + + /** + * Returns the position of this player. + * + * @return position + * @since v1-release0 + */ + public @NotNull Location getPosition() { + return bukkitPlayer.getLocation(); + } + + /** + * Returns the world this player is in. + * + * @return world + * @since v1-release0 + */ + public @NotNull World getWorld() { + return bukkitPlayer.getWorld(); + } + + + // -----> Connection + /** + * Terminates the connection to the player. + *

+ * This will instantaneously remove the player + * from the game without informing them. As if + * their internet connection died. + * + * @since v1-release0 + */ + public void terminate() { + Objects.requireNonNull(MinecraftServer.getServer().getPlayerList().getPlayer(bukkitPlayer.getUniqueId())).disconnect(); + } + + /** + * Kicks the player. + *

+ * This method will run the specified + * kick reason through MiniMessage. + * + * @param reason kick reason + * @since v1-release0 + */ + public void kick(@NotNull String reason) { + bukkitPlayer.kick(MiniMessage.miniMessage().deserialize(reason)); + } + + /** + * Kicks the player. + *

+ * This method will run the specified + * kick reason through MiniMessage. + * + * @param reason kick reason + * @param cause cause of the kick + * @since v1-release0 + */ + public void kick(@NotNull String reason, @NotNull PlayerKickEvent.Cause cause) { + bukkitPlayer.kick(MiniMessage.miniMessage().deserialize(reason), cause); + } + + /** + * Kicks the player. + *

+ * This method will automatically translate + * the specified {@link LanguageString}. + * + * @param languageString language string to get + * @param placeholders placeholders + * @since v1-release0 + */ + @SafeVarargs + public final void kickTranslatable(@NotNull LanguageString languageString, @NotNull Map.Entry<@NotNull String, @NotNull String> @Nullable ... placeholders) { + bukkitPlayer.kick(MiniMessage.miniMessage().deserialize(TranslationManager.get(languageString, this, false, placeholders))); + } + /** + * Kicks the player. + *

+ * This method will automatically translate + * the specified {@link LanguageString}. + * + * @param cause cause of the kick + * @param languageString language string to get + * @param placeholders placeholders + * @since v1-release0 + */ + @SafeVarargs + public final void kickTranslatable(@NotNull PlayerKickEvent.Cause cause, @NotNull LanguageString languageString, @NotNull Map.Entry<@NotNull String, @NotNull String> @Nullable ... placeholders) { + bukkitPlayer.kick(MiniMessage.miniMessage().deserialize(TranslationManager.get(languageString, this, false, placeholders)), cause); + } + + /** + * Kicks the player. + *

+ * This method causes the message + * to be sent directly, without + * any MiniMessage formatting. + * + * @param reason kick reason + * @since v1-release0 + */ + public void kickRaw(@NotNull String reason) { + bukkitPlayer.kick(Component.text(reason)); + } + + /** + * Kicks the player. + *

+ * This method causes the message + * to be sent directly, without + * any MiniMessage formatting. + * + * @param reason kick reason + * @param cause cause of the kick + * @since v1-release0 + */ + public void kickRaw(@NotNull String reason, @NotNull PlayerKickEvent.Cause cause) { + bukkitPlayer.kick(Component.text(reason), cause); + } + + + // -----> Messaging + /** + * Messages this player. + *

+ * This method will run the specified + * message through MiniMessage. + * + * @param message message to send + * @return this instance + * @since v1-release0 + */ + public @NotNull PSPlayer message(@NotNull String message) { + bukkitPlayer.sendRichMessage(message); + return this; + } + + /** + * Messages this player. + *

+ * This method will automatically translate + * the specified {@link LanguageString}. + * + * @param languageString language string to get + * @param includePrefix if {@link LanguageString#PREFIX} should be prepended + * @param placeholders placeholders + * @return this instance + * @since v1-release0 + */ + @SafeVarargs + public final @NotNull PSPlayer messageTranslatable(@NotNull LanguageString languageString, boolean includePrefix, @NotNull Map.Entry<@NotNull String, @NotNull String> @Nullable ... placeholders) { + message(TranslationManager.get(languageString, this, includePrefix, placeholders)); + return this; + } + + /** + * Messages the player. + *

+ * This method causes the message + * to be sent directly, without + * any MiniMessage formatting. + * + * @param message message to send + * @return this instance + * @since v1-release0 + */ + public @NotNull PSPlayer messageRaw(@NotNull String message) { + bukkitPlayer.sendPlainMessage(message); + return this; + } + + + // -----> Scheduling + /** + * Schedules an action. + * + * @param task {@link Runnable} to invoke after the set delay + * @param delay delay in ticks; values lower than {@code 1} will be treated as {@code 0} + * @since v1-release0 + */ + public void schedule(@NotNull Runnable task, long delay) { + if (delay < 1L) + delay = 1L; + + bukkitPlayer.getScheduler().execute(Extension.getInstance(), task, null, delay); + } + + /** + * Schedules an action. + * + * @param task {@link Runnable} to invoke after the set delay + * @param retired {@link Runnable} to invoke if the player disconnects during the delay + * @param delay delay in ticks; values lower than {@code 1} will be treated as {@code 0} + * @since v1-release0 + */ + public void schedule(@NotNull Runnable task, @NotNull Runnable retired, long delay) { + if (delay < 1L) + delay = 1L; + + bukkitPlayer.getScheduler().execute(Extension.getInstance(), task, retired, delay); + } + + /** + * Schedules an action to execute repeatedly + * until this player disconnects. + * + * @param task {@link Runnable} to invoke after the set delay + * @param delay delay in ticks; values lower than {@code 1} will be treated as {@code 0} + * @since v1-release0 + */ + public void scheduleRepeatedly(@NotNull Runnable task, long delay) { + if (delay < 1L) + delay = 1L; + + long finalDelay = delay; + bukkitPlayer.getScheduler().execute(Extension.getInstance(), () -> { + task.run(); + + scheduleRepeatedly(task, finalDelay); + }, null, delay); + } + + + // -----> Miscellaneous + /** + * Checks whether the specified permission is set. + * + * @param permission permission to check + * @return set? + * @since v1-release0 + */ + public boolean hasPermission(@NotNull Permission permission) { + return bukkitPlayer.hasPermission(permission); + } + /** + * Checks whether the specified permission is set. + * + * @param permission permission to check + * @return set? + * @since v1-release0 + */ + public boolean hasPermission(@NotNull String permission) { + return bukkitPlayer.hasPermission(permission); + } + + /** + * Returns the amount of height accumulated during a fall. + * + * @return fall distance + * @since v1-release0 + */ + public float getFallDistance() { + return bukkitPlayer.getFallDistance(); + } + + /** + * Sets the amount of height accumulated during a fall. + * + * @param fallDistance new fall distance + * @return this instance + * @since v1-release0 + */ + public @NotNull PSPlayer setFallDistance(float fallDistance) { + bukkitPlayer.setFallDistance(fallDistance); + return this; + } + + /** + * Returns this player's gamemode. + * + * @return gamemode + * @since v1-release0 + */ + public @NotNull GameMode getGamemode() { + return bukkitPlayer.getGameMode(); + } + + /** + * Sets this player's gamemode. + * + * @param gamemode new gamemode + * @return this instance + * @since v1-release0 + */ + public @NotNull PSPlayer setGamemode(@NotNull GameMode gamemode) { + bukkitPlayer.setGameMode(gamemode); + return this; + } + + /** + * Damages this player. + * + * @param healthPoints amount of damage to deal in health points + * @return this instance + * @since v1-release0 + */ + public @NotNull PSPlayer damage(double healthPoints) { + bukkitPlayer.damage(healthPoints); + return this; + } + + /** + * Damages this player. + * + * @param healthPoints amount of damage to deal in health points + * @param entity entity which dealt the damage + * @return this instance + * @since v1-release0 + */ + public @NotNull PSPlayer damage(double healthPoints, @NotNull Entity entity) { + bukkitPlayer.damage(healthPoints, entity); + return this; + } + + /** + * Damages this player. + * + * @param healthPoints amount of damage to deal in health points + * @param damageSource information about what the damage caused + * @return this instance + * @since v1-release0 + */ + @SuppressWarnings("UnstableApiUsage") + public @NotNull PSPlayer damage(double healthPoints, @NotNull DamageSource damageSource) { + bukkitPlayer.damage(healthPoints, damageSource); + return this; + } + + + // -----> Data + /** + * Applies all player attributes. + * + * @return this instance + * @since v1-release0 + */ + public @NotNull PSPlayer applyPlayerAttributes() { + for (PlayerAttribute attribute : PlayerAttribute.values()) + switch (attribute) { + case WALKING_SPEED, FLY_SPEED -> { + // Calculate + float speed = (float) data.getPlayerAttribute(attribute) + (float) data.getPlayerAttributeOverride(attribute); + + // Keep within bounds + if (speed > 1f) + speed = 1f; + if (speed < 0f) + speed = 0f; + + // Apply + if (attribute == PlayerAttribute.WALKING_SPEED) + bukkitPlayer.setWalkSpeed(speed); + else + bukkitPlayer.setFlySpeed(speed); + } + } + + return this; + } + + @Setter + @Getter + @SuppressWarnings({ "JavadocDeclaration" }) + public static final class Data { + /** + * Contains a static {@link Gson} instance + * used for (de-)serializing {@link Data} + * instances using JSON. + * + * @since v1-release0 + */ + private static final @NotNull Gson gson = new GsonBuilder() + .disableJdkUnsafe() + //.generateNonExecutableJson() + .serializeNulls() + .setDateFormat("dd.MM.yyyy HH:mm:ss:SSSS Z") + .setFieldNamingPolicy(FieldNamingPolicy.IDENTITY) + //.setFormattingStyle(FormattingStyle.COMPACT) + .setLongSerializationPolicy(LongSerializationPolicy.STRING) + //.setStrictness(Strictness.STRICT) + .create(); + + /** + * Contains the associated {@link PSPlayer} instance. + *

+ * Will be saved. + * + * @since v1-release0 + * -- GETTER -- + * Returns the associated {@link PSPlayer} instance. + *

+ * Will be saved. + * + * @return associated {@link PSPlayer} instance + * @since v1-release0 + */ + @Setter(value = AccessLevel.NONE) + private final PSPlayer player; + + /** + * Contains the player list scoreboard. + *

+ * Will not be saved. + * + * @since v1-release0 + * -- GETTER -- + * Returns the player list scoreboard. + *

+ * Will not be saved. + * + * @return player list scoreboard + * @since v1-release0 + * -- SETTER -- + * Sets the player list scoreboard. + *

+ * Will not be saved. + * + * @param playerListScoreboard new player list scoreboard + * @since v1-release0 + */ + @ApiStatus.Experimental + private @NotNull FastBoard playerListScoreboard; + + /** + * Contains the preferred language + * the player has set. + *

+ * Will be saved. + * + * @since v1-release0 + * -- GETTER -- + * Returns the preferred language + * the player has set. + *

+ * Will be saved. + * + * @return preferred language + * @since v1-release0 + * -- SETTER -- + * Sets the preferred language + * the player has set. + *

+ * Will be saved. + * + * @param language new preferred language + * @since v1-release0 + */ + private String language; + + /** + * Contains when the player has + * first been seen on the server. + *

+ * Will be saved. + * + * @since v1-release0 + * -- GETTER -- + * Returns when the player has + * first been seen on the server. + *

+ * Will be saved. + * + * @return first played date and time + * @since v1-release0 + */ + private ZonedDateTime firstSeen; + + /** + * Contains the UUID of the player + * this player has messaged last. + *

+ * Will be saved. + * + * @since v1-release0 + * -- GETTER -- + * Returns the UUID of the player + * this player has messaged last. + *

+ * Will be saved. + * + * @return UUID of the player last messaged + * @since v1-release0 + */ + private UUID lastMessaged; + + /** + * Contains a map of attributes. + * + * @see #playerAttributeOverrides + * @since v1-release0 + */ + @Getter(value = AccessLevel.NONE) + @Setter(value = AccessLevel.NONE) + private @NotNull Map<@NotNull PlayerAttribute, @NotNull Object> playerAttributes = Collections.synchronizedMap(new HashMap<>()); + + /** + * Contains a map of attributes overrides. + *

+ * Note: This map doesn't serve actual + * overrides. Instead, numbered attributes + * are either added or subtracted by the + * number present in this map. + * + * @see #playerAttributes + * @since v1-release0 + */ + @Getter(value = AccessLevel.NONE) + @Setter(value = AccessLevel.NONE) + private @NotNull Map<@NotNull PlayerAttribute, @NotNull Object> playerAttributeOverrides = Collections.synchronizedMap(new HashMap<>()); + + /** + * Contains a map of damage states. + * + * @since v1-release0 + */ + @Getter(value = AccessLevel.NONE) + @Setter(value = AccessLevel.NONE) + private @NotNull Map<@NotNull PlayerDamageState, @NotNull Integer> playerDamageStates = Collections.synchronizedMap(new HashMap<>()); + + + // -----> Constructors + /** + * Creates and initializes an instance of this class. + *

+ * This constructor creates a completely + * new instance without any data. + * + * @param player {@link PSPlayer} instance to associate with + * @since v1-release0 + */ + Data(@NotNull PSPlayer player) { + Logger.info("Initializing fresh player data for player " + player.getIdentificationString()); + + this.player = player; + //TabListHandler.getInstance().initializeTabList(this); + language = "en"; + firstSeen = ZonedDateTime.now(); + lastMessaged = null; + } + + /** + * Creates and initializes an instance of this class. + *

+ * This constructor creates a new instance + * from already existing player data. + * + * @param player {@link PSPlayer} instance to associate with + * @param firstSeen when the player was first seen + * @since v1-release0 + */ + private Data( + @NotNull PSPlayer player, + @NotNull String language, + @NotNull ZonedDateTime firstSeen, + @Nullable UUID lastMessaged, + @NotNull Map<@NotNull PlayerAttribute, @NotNull Object> playerAttributes, + @NotNull Map<@NotNull PlayerAttribute, @NotNull Object> playerAttributeOverrides + ) { + Logger.info("Initializing player data from existing data for player " + player.getIdentificationString()); + + this.player = player; + //TabListHandler.getInstance().initializeTabList(this); + this.language = language; + this.firstSeen = firstSeen; + this.lastMessaged = lastMessaged; + this.playerAttributes = playerAttributes; + this.playerAttributeOverrides = playerAttributeOverrides; + } + + + // -----> Attributes + /** + * Returns the override for the specified player attribute. + * + * @param value type + * @param attribute attribute to return + * @return attribute value + * @since v1-release0 + */ + public @NotNull T getPlayerAttribute(@NotNull PlayerAttribute attribute) { + return (T) playerAttributes.getOrDefault(attribute, attribute.getDefault()); + } + + /** + * Updates the specified player attribute. + * + * @param value type + * @param attribute attribute to update + * @param value value to set the attribute to + * @since v1-release0 + */ + public void setPlayerAttribute(@NotNull PlayerAttribute attribute, @Nullable T value) { + if (value == null || attribute.getDefault().equals(value)) + playerAttributes.remove(attribute); + else + playerAttributes.put(attribute, value); + } + + /** + * Nullifies and resets the specified + * player attribute to it's default value. + * + * @param attribute attribute to nullify + * @since v1-release0 + */ + public void nullifyPlayerAttribute(@NotNull PlayerAttribute attribute) { + playerAttributes.remove(attribute); + } + + /** + * Returns if a override for the + * specified player attribute exists. + * + * @param attribute attribute to check + * @return overridden? + * @since v1-release0 + */ + public boolean isPlayerAttributeOverrideSet(@NotNull PlayerAttribute attribute) { + return playerAttributeOverrides.containsKey(attribute); + } + + /** + * Returns the overridden value for + * the specified player attribute. + * + * @param value type + * @param attribute attribute to return + * @return overridden attribute value + * @since v1-release0 + */ + public @NotNull T getPlayerAttributeOverride(@NotNull PlayerAttribute attribute) { + return (T) playerAttributeOverrides.getOrDefault(attribute, attribute.getZero()); + } + + /** + * Updates the override for the + * specified player attribute. + * + * @param value type + * @param attribute attribute to update + * @param value value to set the override to + * @since v1-release0 + */ + public void setPlayerAttributeOverride(@NotNull PlayerAttribute attribute, @Nullable T value) { + if ( + value == null + || (value instanceof Byte && (Byte) value == 0) + || (value instanceof Short && (Short) value == 0) + || (value instanceof Integer && (Integer) value == 0) + || (value instanceof Float && (Float) value == 0f) + || (value instanceof Double && (Double) value == 0d) + ) + playerAttributeOverrides.remove(attribute); + else + playerAttributeOverrides.put(attribute, value); + } + + /** + * Nullifies and removes the override + * for the specified player attribute. + * + * @param attribute attribute to nullify + * @since v1-release0 + */ + public void nullifyPlayerAttributeOverride(@NotNull PlayerAttribute attribute) { + playerAttributeOverrides.remove(attribute); + } + + + // -----> Damage states + /** + * Returns the specified damage state. + * + * @param damageState damage state to return + * @return damage state + * @since v1-release0 + */ + public int getDamageState(@NotNull PlayerDamageState damageState) { + return playerDamageStates.getOrDefault(damageState, damageState.getDefault()); + } + + /** + * Updates the specified damage state. + * + * @param damageState damageState to update + * @param value value to set the damageState to + * @since v1-release0 + */ + public void setDamageState(@NotNull PlayerDamageState damageState, @Nullable Integer value) { + if (value == null || damageState.getDefault() == value) + playerDamageStates.remove(damageState); + else + playerDamageStates.put(damageState, value); + } + + /** + * Increases the specified damage state by one. + * + * @param damageState damageState to increase + * @since v1-release0 + */ + public void increaseDamageState(@NotNull PlayerDamageState damageState) { + setDamageState(damageState, getDamageState(damageState) + 1); + } + + /** + * Nullifies and resets the specified + * damage state to it's default value. + * + * @param damageState damageState to nullify + * @since v1-release0 + */ + public void nullifyDamageState(@NotNull PlayerDamageState damageState) { + playerDamageStates.remove(damageState); + } + + + // -----> Serialization + /** + * Deserializes a JSON object and creates + * a new {@link Data} instance. + * + * @param jsonString JSON string + * @throws JsonSyntaxException on invalid JSON + * @throws RuntimeException on invalid PlayerData serialization + * @since v1-release0 + */ + public static @NotNull PSPlayer.Data fromJSON(@NotNull String jsonString) throws JsonSyntaxException, NullPointerException { + try { + // Parse data + Map<@NotNull String, @NotNull String> data = gson.fromJson(jsonString, new TypeToken>() {}.getType()); + + // Map to classes + // -> playerUUID + PSPlayer player = PSPlayerFactory.get(UUID.fromString(data.get("playerUUID"))); + // -> playerAttributes + Map<@NotNull PlayerAttribute, @NotNull Object> playerAttributes = gson.fromJson(data.get("playerAttributes"), new TypeToken>() {}.getType()); + // -> playerAttributeOverrides + Map<@NotNull PlayerAttribute, @NotNull Object> playerAttributeOverrides = gson.fromJson(data.get("playerAttributeOverrides"), new TypeToken>() {}.getType()); + + // Instantiate PlayerData instance + return new Data( + player, + data.get("language"), + ZonedDateTime.parse(data.get("firstPlayed")), + data.get("lastMessaged").isBlank() ? null : UUID.fromString(data.get("lastMessaged")), + playerAttributes, + playerAttributeOverrides + ); + } catch (Exception exception) { + if (exception instanceof JsonSyntaxException) + throw exception; + else + throw new RuntimeException("A conversion method failed", exception); + } + } + + /** + * Serializes this instance into a JSON object. + * + * @return serialized {@link Data} instance in JSON + * @since v1-release0 + */ + public @NotNull String toJSON() { + // Serialize classes + // -> playerAttributes + String playerAttributes = gson.toJson(this.playerAttributes, new TypeToken>() {}.getType()); + // -> playerAttributeOverrides + String playerAttributeOverrides = gson.toJson(this.playerAttributeOverrides, new TypeToken>() {}.getType()); + + // Serialize data + return gson.toJson( + Map.ofEntries( + entry("playerUUID", player.getUUID().toString()), + entry("language", language), + entry("firstPlayed", firstSeen.format(DateTimeFormatter.ISO_ZONED_DATE_TIME)), + entry("lastMessaged", lastMessaged == null ? "" : lastMessaged.toString()), + entry("playerAttributes", playerAttributes), + entry("playerAttributeOverrides", playerAttributeOverrides) + ), + new TypeToken>(){}.getType()); + } + } +} diff --git a/extension/src/main/java/de/jeremystartm/pickshadow/extension/api/entity/player/PSPlayerFactory.java b/extension/src/main/java/de/jeremystartm/pickshadow/extension/api/entity/player/PSPlayerFactory.java new file mode 100644 index 0000000..9821572 --- /dev/null +++ b/extension/src/main/java/de/jeremystartm/pickshadow/extension/api/entity/player/PSPlayerFactory.java @@ -0,0 +1,129 @@ +/* + * PICKSHADOW SERVER KIT SOURCE FILE + * Copyright (c) 2024 The PickShadow Server Kit authors + * Licensed under the GNU Affero General Public License v3 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.jeremystartm.pickshadow.extension.api.entity.player; + +import de.staropensource.engine.base.logging.Logger; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +/** + * A factory for {@link PSPlayer} instances. + * + * @since v1-release0 + */ +public final class PSPlayerFactory { + /** + * Contains all registered {@link PSPlayer.Data} instances. + * + * @since v1-release0 + */ + private static final @NotNull List<@NotNull PSPlayer> instances = new ArrayList<>(); + + /** + * Creates and initializes an instance of this class. + * + * @since v1-release0 + */ + private PSPlayerFactory() {} + + /** + * Returns an already existing {@link PSPlayer.Data} instance. + * + * @param uuid player {@link UUID} + * @return {@link PSPlayer} instance + * @throws NullPointerException on error + * @since v1-release0 + */ + public static @NotNull PSPlayer get(@NotNull UUID uuid) throws NullPointerException { + for (PSPlayer player : instances) + if (player.getUUID() == uuid) + return player; + + throw new NullPointerException(); + } + + /** + * Returns an already existing {@link PSPlayer.Data} instance. + * + * @param username player username + * @return {@link PSPlayer} instance + * @throws NullPointerException on error + * @since v1-release0 + */ + public static PSPlayer get(@NotNull String username) throws NullPointerException { + for (PSPlayer player : instances) + if (player.getUsername().equals(username)) + return player; + + throw new NullPointerException(); + } + + /** + * Returns an already existing {@link PSPlayer.Data} instance. + * + * @param bukkitPlayer Bukkit's {@link Player} instance + * @return {@link PSPlayer} instance + * @throws NullPointerException on error + * @since v1-release0 + */ + public static @NotNull PSPlayer get(@NotNull Player bukkitPlayer) throws NullPointerException { + for (PSPlayer player : instances) + if (player.getUUID() == bukkitPlayer.getUniqueId()) + return player; + + throw new NullPointerException(); + } + + /** + * Registers a new {@link PSPlayer.Data} instance. + * + * @param bukkitPlayer Bukkit's {@link Player} instance + * @throws Exception on error + * @since v1-release0 + */ + public static @NotNull PSPlayer registerPlayer(@NotNull Player bukkitPlayer) throws Exception { + Logger.verb("Registering player " + bukkitPlayer.getName() + " [" + bukkitPlayer.getUniqueId() + "]"); + + try { + PSPlayer player = new PSPlayer(bukkitPlayer); + instances.add(player); + return player; + } catch (Exception exception) { + Logger.crash("Unable to create PlayerData instance for player " + bukkitPlayer.getName() + " [" + bukkitPlayer.getUniqueId() + "]", exception, true); + throw exception; + } + } + + /** + * Unregisters a {@link PSPlayer.Data} instance. + * + * @param player {@link PSPlayer} instance + * @since v1-release0 + */ + public static void unregisterPlayer(@NotNull PSPlayer player) { + Logger.verb("Unregistering player " + player.getUsername() + " [" + player.getUUID() + "]"); + + instances.remove(player); + } +} diff --git a/extension/src/main/java/de/jeremystartm/pickshadow/extension/api/entity/player/PlayerData.java b/extension/src/main/java/de/jeremystartm/pickshadow/extension/api/entity/player/PlayerData.java deleted file mode 100644 index efa3d37..0000000 --- a/extension/src/main/java/de/jeremystartm/pickshadow/extension/api/entity/player/PlayerData.java +++ /dev/null @@ -1,258 +0,0 @@ -/* - * PICKSHADOW SERVER KIT SOURCE FILE - * Copyright (c) 2024 The PickShadow Server Kit authors - * Licensed under the GNU Affero General Public License v3 - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package de.jeremystartm.pickshadow.extension.api.entity.player; - -import com.google.gson.Gson; -import com.google.gson.JsonSyntaxException; -import com.google.gson.reflect.TypeToken; -import de.jeremystartm.pickshadow.extension.misc.Scheduler; -import de.staropensource.engine.base.logging.Logger; -import fr.mrmicky.fastboard.adventure.FastBoard; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.Setter; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.util.Map; -import java.util.UUID; - -import static java.util.Map.entry; - -@Setter -@Getter -@SuppressWarnings({ "JavadocDeclaration" }) -public final class PlayerData { - /** - * Contains the associated {@link Player}. - *

- * Will be saved. - * - * @since v1-release0 - * -- GETTER -- - * Returns the associated {@link Player}. - *

- * Will be saved. - * - * @return associated {@link Player} - * @since v1-release0 - */ - @Setter(value = AccessLevel.NONE) - private final Player player; - - /** - * Contains the player list scoreboard. - *

- * Will not be saved. - * - * @since v1-release0 - * -- GETTER -- - * Returns the player list scoreboard. - *

- * Will not be saved. - * - * @return player list scoreboard - * @since v1-release0 - * -- SETTER -- - * Sets the player list scoreboard. - *

- * Will not be saved. - * - * @param playerListScoreboard new player list scoreboard - * @since v1-release0 - */ - @ApiStatus.Experimental - private @NotNull FastBoard playerListScoreboard; - - /** - * Contains the preferred language - * the player has set. - *

- * Will be saved. - * - * @since v1-release0 - * -- GETTER -- - * Returns the preferred language - * the player has set. - *

- * Will be saved. - * - * @return preferred language - * @since v1-release0 - * -- SETTER -- - * Sets the preferred language - * the player has set. - *

- * Will be saved. - * - * @param language new preferred language - * @since v1-release0 - */ - private String language; - - /** - * Contains when the player has - * first been seen on the server. - *

- * Will be saved. - * - * @since v1-release0 - * -- GETTER -- - * Returns when the player has - * first been seen on the server. - *

- * Will be saved. - * - * @return first played date and time - * @since v1-release0 - */ - private ZonedDateTime firstSeen; - - /** - * Contains the UUID of the player - * this player has messaged last. - *

- * Will be saved. - * - * @since v1-release0 - * -- GETTER -- - * Returns the UUID of the player - * this player has messaged last. - *

- * Will be saved. - * - * @return UUID of the player last messaged - * @since v1-release0 - */ - private UUID lastMessaged; - - /** - * Contains the state for - * stone cutter damage, as - * assigned by the {@link Scheduler}. - *

- * Will not be saved. - * - * @since v1-release0 - * -- GETTER -- - * Returns the state for - * stone cutter damage, as - * assigned by the {@link Scheduler}. - *

- * Will not be saved. - * - * @return stone cutter damage state - * @since v1-release0 - */ - private int stonecutterDamageState = -1; - - /** - * Creates and initializes an instance of this class. - *

- * This constructor creates a completely - * new instance without any data. - * - * @param player {@link Player} to use - * @since v1-release0 - */ - PlayerData(@NotNull Player player) { - Logger.info("Initializing fresh player data for player " + player.getName() + " [" + player.getUniqueId() + "]"); - - this.player = player; - //TabListHandler.getInstance().initializeTabList(this); - language = "en"; - firstSeen = ZonedDateTime.now(); - lastMessaged = null; - } - - /** - * Creates and initializes an instance of this class. - *

- * This constructor creates a new instance - * from already existing player data. - * - * @param player {@link Player} to use - * @param firstSeen when the player was first seen - * @since v1-release0 - */ - private PlayerData(@NotNull Player player, @NotNull String language, @NotNull ZonedDateTime firstSeen, @Nullable UUID lastMessaged) { - Logger.info("Initializing player data from existing data for player " + player.getName() + " [" + player.getUniqueId() + "]"); - - this.player = player; - //TabListHandler.getInstance().initializeTabList(this); - this.language = language; - this.firstSeen = firstSeen; - this.lastMessaged = lastMessaged; - } - - /** - * Deserializes a JSON object and creates - * a new {@link PlayerData} instance. - * - * @param jsonString JSON string - * @throws JsonSyntaxException on invalid JSON - * @throws RuntimeException on invalid PlayerData serialization - * @since v1-release0 - */ - public static @NotNull PlayerData fromJSON(@NotNull String jsonString) throws JsonSyntaxException, NullPointerException { - try { - Map<@NotNull String, @NotNull String> data = new Gson().fromJson(jsonString, new TypeToken>() { - }.getType()); - Player player = Bukkit.getPlayer(UUID.fromString(data.get("playerUUID"))); - - if (player == null) - throw new NullPointerException("Player is null"); - - return new PlayerData( - player, - data.get("language"), - ZonedDateTime.parse(data.get("firstPlayed")), - data.get("lastMessaged").isBlank() ? null : UUID.fromString(data.get("lastMessaged")) - ); - } catch (Exception exception) { - if (exception instanceof JsonSyntaxException) - throw exception; - else - throw new RuntimeException("A conversion method failed", exception); - } - } - - /** - * Serializes this instance into a JSON object. - * - * @return serialized {@link PlayerData} instance in JSON - * @since v1-release0 - */ - public @NotNull String toJSON() { - return new Gson().toJson( - Map.ofEntries( - entry("playerUUID", player.getUniqueId().toString()), - entry("language", language), - entry("firstPlayed", firstSeen.format(DateTimeFormatter.ISO_ZONED_DATE_TIME)), - entry("lastMessaged", lastMessaged == null ? "" : lastMessaged.toString()) - ), - new TypeToken>(){}.getType()); - } -} diff --git a/extension/src/main/java/de/jeremystartm/pickshadow/extension/api/entity/player/PlayerDataFactory.java b/extension/src/main/java/de/jeremystartm/pickshadow/extension/api/entity/player/PlayerDataFactory.java deleted file mode 100644 index 7c57f77..0000000 --- a/extension/src/main/java/de/jeremystartm/pickshadow/extension/api/entity/player/PlayerDataFactory.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * PICKSHADOW SERVER KIT SOURCE FILE - * Copyright (c) 2024 The PickShadow Server Kit authors - * Licensed under the GNU Affero General Public License v3 - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package de.jeremystartm.pickshadow.extension.api.entity.player; - -import de.staropensource.engine.base.logging.Logger; -import lombok.Getter; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -/** - * A factory for {@link PlayerData} instances. - * - * @since v1-release0 - */ -@SuppressWarnings({ "JavadocDeclaration" }) -public final class PlayerDataFactory { - /** - * Contains the global instance of this class. - * - * @since v1-release0 - * -- GETTER -- - * Returns the global instance of this class. - * - * @return global instance - * @since v1-release0 - */ - @Getter - private static PlayerDataFactory instance; - - /** - * Contains all registered {@link PlayerData} instances. - * - * @since v1-release0 - */ - private final @NotNull List<@NotNull PlayerData> playerList = new ArrayList<>(); - - /** - * Creates and initializes an instance of this class. - * - * @since v1-release0 - */ - private PlayerDataFactory() {} - - /** - * Initializes this factory, - * if it isn't already. - * - * @since v1-release0 - */ - public static void initialize() { - if (instance == null) - instance = new PlayerDataFactory(); - } - - /** - * Returns an already existing {@link PlayerData} instance. - * - * @param uuid player {@link UUID} - * @return {@link PlayerData} instance - * @throws NullPointerException on error - * @since v1-release0 - */ - public PlayerData get(@NotNull UUID uuid) throws NullPointerException { - for (PlayerData playerData : playerList) - if (playerData.getPlayer().getUniqueId() == uuid) - return playerData; - - throw new NullPointerException(); - } - - /** - * Returns an already existing {@link PlayerData} instance. - * - * @param username player username - * @return {@link PlayerData} instance - * @throws NullPointerException on error - * @since v1-release0 - */ - public PlayerData get(@NotNull String username) throws NullPointerException { - for (PlayerData playerData : playerList) - if (playerData.getPlayer().getName().equals(username)) - return playerData; - - throw new NullPointerException(); - } - - /** - * Returns an already existing {@link PlayerData} instance. - * - * @param player {@link Player} instance - * @return player instance - * @throws NullPointerException on error - * @since v1-release0 - */ - public @NotNull PlayerData get(@NotNull Player player) throws NullPointerException { - for (PlayerData playerData : playerList) - if (playerData.getPlayer().getUniqueId() == player.getUniqueId()) - return playerData; - - throw new NullPointerException(); - } - - /** - * Registers a new {@link PlayerData} instance. - * - * @param player {@link Player} instance - * @throws Exception on error - * @since v1-release0 - */ - public void registerPlayer(@NotNull Player player) throws Exception { - Logger.verb("Registering player " + player.getName() + " (" + player.getUniqueId() + ")"); - - try { - playerList.add(new PlayerData(player)); - } catch (Exception exception) { - Logger.crash("Unable to create PlayerData instance for player " + player.getName() + " (" + player.getUniqueId() + ")", exception, true); - throw exception; - } - } - - /** - * Unregisters a {@link PlayerData} instance. - * - * @param playerData {@link Player} instance - * @since v1-release0 - */ - public void unregisterPlayer(@NotNull PlayerData playerData) { - Logger.verb("Unregistering player " + playerData.getPlayer().getName() + " (" + playerData.getPlayer().getUniqueId() + ")"); - - playerList.remove(playerData); - } -} diff --git a/extension/src/main/java/de/jeremystartm/pickshadow/extension/api/translation/LanguageString.java b/extension/src/main/java/de/jeremystartm/pickshadow/extension/api/translation/LanguageString.java index 01d7429..32c2d67 100644 --- a/extension/src/main/java/de/jeremystartm/pickshadow/extension/api/translation/LanguageString.java +++ b/extension/src/main/java/de/jeremystartm/pickshadow/extension/api/translation/LanguageString.java @@ -45,6 +45,7 @@ public enum LanguageString { ERROR_UNIMPLEMENTED, ERROR_INVALID_MODE, ERROR_NOT_A_PLAYER, + ERROR_NOT_SERVER_CONSOLE, ERROR_MISSING_PERM, ERROR_TOO_FEW_ARGUMENTS, ERROR_TOO_MANY_ARGUMENTS, @@ -64,7 +65,9 @@ public enum LanguageString { EXTENSIONCMD_KILLJVM, EXTENSIONCMD_LICENSE, EXTENSIONCMD_SOURCE, - EXTENSIONCMD_ERROR_OLDCMD, + + // Command /pssp + LEGACYEXTENSIONCMD, // Command group 'messaging' MESSAGING_SERVER, @@ -114,6 +117,10 @@ public enum LanguageString { HEAL, HEAL_REMOTE, + // Command /feed + FEED, + FEED_REMOTE, + // Command /plugins PLUGINS, PLUGINS_FAKE, @@ -133,6 +140,12 @@ public enum LanguageString { GAMEMODE_ADVENTURE, GAMEMODE_SPECTATOR, + // Command /speed + SPEED, + SPEED_REMOTE, + SPEED_FLY, + SPEED_WALK, + // Event for chat commands CHATCOMMAND_ERROR_NAMESPACE, } diff --git a/extension/src/main/java/de/jeremystartm/pickshadow/extension/api/translation/TranslationManager.java b/extension/src/main/java/de/jeremystartm/pickshadow/extension/api/translation/TranslationManager.java index 67dbc38..6419b4f 100644 --- a/extension/src/main/java/de/jeremystartm/pickshadow/extension/api/translation/TranslationManager.java +++ b/extension/src/main/java/de/jeremystartm/pickshadow/extension/api/translation/TranslationManager.java @@ -22,7 +22,8 @@ package de.jeremystartm.pickshadow.extension.api.translation; import com.google.gson.Gson; import com.google.gson.JsonSyntaxException; import com.google.gson.reflect.TypeToken; -import de.jeremystartm.pickshadow.extension.api.entity.player.PlayerDataFactory; +import de.jeremystartm.pickshadow.extension.api.entity.player.PSPlayer; +import de.jeremystartm.pickshadow.extension.api.entity.player.PSPlayerFactory; import de.staropensource.engine.base.logging.Logger; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @@ -60,6 +61,40 @@ public final class TranslationManager { return translations.keySet().toArray(new String[0]); } + /** + * Returns the translation for the specified language + * string for the specified {@link CommandSender}. + * + * @param languageString language string to get + * @param player player to translate for + * @param includePrefix if {@link LanguageString#PREFIX} should be prepended + * @param placeholders placeholders + * @since v1-release0 + */ + @SafeVarargs + public static @NotNull String get( + @NotNull LanguageString languageString, + @NotNull PSPlayer player, + boolean includePrefix, + @NotNull Map.Entry<@NotNull String, @NotNull String> @Nullable ... placeholders + ) { + String temp = player.getData().getLanguage(); + + // Check if the prefix shall be included + if (includePrefix) + temp = get(LanguageString.PREFIX, "en") + get(languageString, temp).replace("\n", "\n" + get(LanguageString.PREFIX_NEWLINE, "en")); + else + temp = get(languageString, temp); + + // Replace placeholders + if (placeholders != null) + for (Map.Entry<@NotNull String, @NotNull String> placeholder : placeholders) + temp = temp.replace("%" + placeholder.getKey() + "%", placeholder.getValue()); + + // Return + return temp; + } + /** * Returns the translation for the specified language * string for the specified {@link CommandSender}. @@ -71,13 +106,18 @@ public final class TranslationManager { * @since v1-release0 */ @SafeVarargs - public static @NotNull String get(@NotNull LanguageString languageString, @NotNull CommandSender sender, boolean includePrefix, @NotNull Map.Entry<@NotNull String, @NotNull String> @Nullable ... placeholders) { + public static @NotNull String get( + @NotNull LanguageString languageString, + @NotNull CommandSender sender, + boolean includePrefix, + @NotNull Map.Entry<@NotNull String, @NotNull String> @Nullable ... placeholders + ) { String temp = "en"; // Determine language to use if (sender instanceof Player player) try { - temp = PlayerDataFactory.getInstance().get(player).getLanguage(); + temp = PSPlayerFactory.get(player).getData().getLanguage(); } catch (NullPointerException ignored) {} // Check if the prefix shall be included diff --git a/extension/src/main/java/de/jeremystartm/pickshadow/extension/api/command/CommandBaseWithNull.java b/extension/src/main/java/de/jeremystartm/pickshadow/extension/api/type/PlayerAttribute.java similarity index 55% rename from extension/src/main/java/de/jeremystartm/pickshadow/extension/api/command/CommandBaseWithNull.java rename to extension/src/main/java/de/jeremystartm/pickshadow/extension/api/type/PlayerAttribute.java index 5fa09d0..e2f7512 100644 --- a/extension/src/main/java/de/jeremystartm/pickshadow/extension/api/command/CommandBaseWithNull.java +++ b/extension/src/main/java/de/jeremystartm/pickshadow/extension/api/type/PlayerAttribute.java @@ -17,44 +17,56 @@ * along with this program. If not, see . */ -package de.jeremystartm.pickshadow.extension.api.command; +package de.jeremystartm.pickshadow.extension.api.type; -import de.jeremystartm.pickshadow.extension.api.command.completion.StubTabCompletion; -import lombok.Getter; -import org.bukkit.command.*; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; /** - * Abstract class for implementing commands - * without the {@code command} parameter. + * Represents various attributes players can have. * * @since v1-release0 */ -@Getter -public abstract class CommandBaseWithNull extends CommandBase { +public enum PlayerAttribute { /** - * Initializes this abstract class - * and registers the command. + * A player's walking speed. + *

+ * Must be a {@link Float}. * * @since v1-release0 */ - public CommandBaseWithNull() {} + WALKING_SPEED, /** - * Executes this command. + * A player's flying speed. + *

+ * Must be a {@link Float}. * * @since v1-release0 */ - public abstract void invoke(@NotNull CommandSender sender, @Nullable Command command, @NotNull String alias, @NotNull String[] arguments); + FLY_SPEED; /** - * Provides tab completions for this command. + * Returns the default value for the specified attribute. * - * @return completion + * @return default value * @since v1-release0 */ - public @NotNull TabCompletion getCompletion() { - return StubTabCompletion.completion(); + public @NotNull Object getDefault() { + return switch (this) { + case WALKING_SPEED -> 0.2f; + case FLY_SPEED -> 0.1f; + }; + } + + /** + * Returns the zero value for the specified attribute. + * + * @return {@code 0} + * @since v1-release0 + */ + public @NotNull Object getZero() { + return switch (this) { + case WALKING_SPEED, FLY_SPEED -> 0f; + }; } } diff --git a/extension/src/main/java/de/jeremystartm/pickshadow/extension/api/type/PlayerDamageState.java b/extension/src/main/java/de/jeremystartm/pickshadow/extension/api/type/PlayerDamageState.java new file mode 100644 index 0000000..4113701 --- /dev/null +++ b/extension/src/main/java/de/jeremystartm/pickshadow/extension/api/type/PlayerDamageState.java @@ -0,0 +1,46 @@ +/* + * PICKSHADOW SERVER KIT SOURCE FILE + * Copyright (c) 2024 The PickShadow Server Kit authors + * Licensed under the GNU Affero General Public License v3 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.jeremystartm.pickshadow.extension.api.type; + +/** + * Represents damage states of players. + * + * @since v1-release0 + */ +public enum PlayerDamageState { + /** + * For stonecutter damage detection. + * + * @since v1-release0 + */ + STONECUTTER; + + /** + * Returns the default value for the specified damage state. + * + * @return default value + * @since v1-release0 + */ + public int getDefault() { + return switch (this) { + case STONECUTTER -> -1; + }; + } +} diff --git a/extension/src/main/java/de/jeremystartm/pickshadow/extension/api/type/package-info.java b/extension/src/main/java/de/jeremystartm/pickshadow/extension/api/type/package-info.java new file mode 100644 index 0000000..2cf13ef --- /dev/null +++ b/extension/src/main/java/de/jeremystartm/pickshadow/extension/api/type/package-info.java @@ -0,0 +1,25 @@ +/* + * PICKSHADOW SERVER KIT SOURCE FILE + * Copyright (c) 2024 The PickShadow Server Kit authors + * Licensed under the GNU Affero General Public License v3 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * Data types in form of enums and classes. + * + * @since v1-release0 + */ +package de.jeremystartm.pickshadow.extension.api.type; diff --git a/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/TemplateCommand.java b/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/TemplateCommand.java index b439367..86a9b3e 100644 --- a/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/TemplateCommand.java +++ b/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/TemplateCommand.java @@ -19,14 +19,15 @@ package de.jeremystartm.pickshadow.extension.command; -import de.jeremystartm.pickshadow.extension.api.command.CommandBase; +import de.jeremystartm.pickshadow.extension.api.command.CommandForced; import de.jeremystartm.pickshadow.extension.api.command.TabCompletion; import de.jeremystartm.pickshadow.extension.api.command.completion.StaticTabCompletion; +import de.jeremystartm.pickshadow.extension.api.entity.player.PSPlayer; import de.jeremystartm.pickshadow.extension.api.translation.LanguageString; import de.jeremystartm.pickshadow.extension.api.translation.TranslationManager; import lombok.Getter; -import org.bukkit.command.Command; import org.bukkit.command.CommandSender; +import org.bukkit.craftbukkit.command.ServerCommandSender; import org.jetbrains.annotations.NotNull; /** @@ -36,7 +37,7 @@ import org.jetbrains.annotations.NotNull; */ @Getter @SuppressWarnings({ "JavadocDeclaration" }) -public final class TemplateCommand extends CommandBase { +public final class TemplateCommand extends CommandForced { /** * Contains the tab completion for this command. * @@ -61,14 +62,40 @@ public final class TemplateCommand extends CommandBase { * @since v1-release0 */ public TemplateCommand() throws IllegalArgumentException { - super("mode", "cmd"); + super(new Information( + "name", + new String[]{ "alias 1", "alias 2" }, + "An example command", + "/cmd [--verbose] ", + "mode", + null, + ExecutionTarget.ALL + ), "cmd"); } /** {@inheritDoc} */ @Override - public void invoke(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] arguments) { + public void invokeAll(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] arguments) { if (checkPermission(sender, "pickshadow.command.cmd")) return; sender.sendRichMessage(TranslationManager.get(LanguageString.ERROR_UNIMPLEMENTED, sender, true)); } + + /** {@inheritDoc} */ + @Override + public void invokeConsole(@NotNull ServerCommandSender console, @NotNull String alias, @NotNull String[] arguments) { + // No permission check is performed here + // here as the server console will + // always have all permission set. + + console.sendPlainMessage("This command seems to have been invoked by the server console"); + } + + /** {@inheritDoc} */ + @Override + public void invokePlayer(@NotNull PSPlayer player, @NotNull String alias, @NotNull String[] arguments) { + if (checkPermission(player, "pickshadow.command.cmd")) return; + + player.messageRaw("This command seems to have been invoked by a player"); + } } diff --git a/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/AnnounceCommand.java b/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/AnnounceCommand.java index c14d682..815a3df 100644 --- a/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/AnnounceCommand.java +++ b/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/AnnounceCommand.java @@ -19,7 +19,7 @@ package de.jeremystartm.pickshadow.extension.command.general; -import de.jeremystartm.pickshadow.extension.api.command.CommandBase; +import de.jeremystartm.pickshadow.extension.api.command.Command; import de.jeremystartm.pickshadow.extension.api.command.completion.StaticTabCompletion; import de.jeremystartm.pickshadow.extension.api.command.TabCompletion; import de.jeremystartm.pickshadow.extension.api.translation.LanguageString; @@ -28,7 +28,6 @@ import de.staropensource.engine.base.utility.PlaceholderEngine; import lombok.Getter; import net.kyori.adventure.text.minimessage.MiniMessage; import org.bukkit.Bukkit; -import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.jetbrains.annotations.NotNull; @@ -39,7 +38,7 @@ import org.jetbrains.annotations.NotNull; */ @Getter @SuppressWarnings({ "JavadocDeclaration" }) -public final class AnnounceCommand extends CommandBase { +public final class AnnounceCommand extends Command { /** * Contains the tab completion for this command. * @@ -61,12 +60,20 @@ public final class AnnounceCommand extends CommandBase { * @since v1-release0 */ public AnnounceCommand() throws IllegalArgumentException { - super("general", "announce"); + super(new Information( + "announce", + new String[]{ "announce", "announcement", "broadcast", "bc" }, + "", + "", + "general", + null, + ExecutionTarget.ALL + ), "announce"); } /** {@inheritDoc} */ @Override - public void invoke(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] arguments) { + protected void invokeAll(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] arguments) throws Exception { if (checkPermission(sender, "pickshadow.command.announce")) return; if (arguments.length == 0) { @@ -106,6 +113,8 @@ public final class AnnounceCommand extends CommandBase { if (!checkPermission(sender, "pickshadow.command.announce.network")) sender.sendRichMessage(TranslationManager.get(LanguageString.ERROR_UNIMPLEMENTED, sender, true)); } else - Bukkit.broadcast(MiniMessage.miniMessage().deserialize(PlaceholderEngine.getInstance().process(argumentsFinalized))); + // TODO broken + //Bukkit.broadcast(MiniMessage.miniMessage().deserialize(PlaceholderEngine.getInstance().process(argumentsFinalized))); + Bukkit.broadcast(MiniMessage.miniMessage().deserialize(argumentsFinalized)); } } diff --git a/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/ClearChatCommand.java b/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/ClearChatCommand.java index 77733ef..8dc8d94 100644 --- a/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/ClearChatCommand.java +++ b/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/ClearChatCommand.java @@ -19,14 +19,13 @@ package de.jeremystartm.pickshadow.extension.command.general; -import de.jeremystartm.pickshadow.extension.api.command.CommandBase; +import de.jeremystartm.pickshadow.extension.api.command.Command; import de.jeremystartm.pickshadow.extension.api.command.completion.StaticTabCompletion; import de.jeremystartm.pickshadow.extension.api.command.TabCompletion; import de.jeremystartm.pickshadow.extension.api.translation.LanguageString; import de.jeremystartm.pickshadow.extension.api.translation.TranslationManager; import lombok.Getter; import org.bukkit.Bukkit; -import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; @@ -40,7 +39,7 @@ import static java.util.Map.entry; */ @Getter @SuppressWarnings({ "JavadocDeclaration" }) -public final class ClearChatCommand extends CommandBase { +public final class ClearChatCommand extends Command { /** * Contains the tab completion for this command. * @@ -68,12 +67,20 @@ public final class ClearChatCommand extends CommandBase { * @since v1-release0 */ public ClearChatCommand() throws IllegalArgumentException { - super("general", "clearchat"); + super(new Information( + "clearchat", + new String[]{ "clearchat", "chatclear", "cc" }, + "", + "", + "general", + null, + ExecutionTarget.ALL + ), "clearchat"); } /** {@inheritDoc} */ @Override - public void invoke(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] arguments) { + protected void invokeAll(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] arguments) throws Exception { if (checkPermission(sender, "pickshadow.command.clearchat")) return; if (arguments.length == 1 && arguments[0].equals("--network")) { diff --git a/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/ExtensionCommand.java b/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/ExtensionCommand.java index cb0cfc6..6f7c975 100644 --- a/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/ExtensionCommand.java +++ b/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/ExtensionCommand.java @@ -19,15 +19,17 @@ package de.jeremystartm.pickshadow.extension.command.general; +import com.google.gson.Gson; import de.jeremystartm.pickshadow.common.PSSKInformation; -import de.jeremystartm.pickshadow.extension.api.command.CommandBase; +import de.jeremystartm.pickshadow.extension.api.command.Command; import de.jeremystartm.pickshadow.extension.api.command.completion.StaticTabCompletion; import de.jeremystartm.pickshadow.extension.api.command.TabCompletion; -import de.jeremystartm.pickshadow.extension.api.entity.player.PlayerDataFactory; +import de.jeremystartm.pickshadow.extension.api.entity.player.PSPlayerFactory; import de.jeremystartm.pickshadow.extension.api.translation.LanguageString; import de.jeremystartm.pickshadow.extension.api.translation.TranslationManager; +import de.jeremystartm.pickshadow.extension.api.type.PlayerAttribute; +import de.staropensource.engine.base.utility.PlaceholderEngine; import lombok.Getter; -import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; @@ -41,7 +43,7 @@ import static java.util.Map.entry; */ @Getter @SuppressWarnings({ "JavadocDeclaration" }) -public final class ExtensionCommand extends CommandBase { +public final class ExtensionCommand extends Command { /** * Contains the tab completion for this command. * @@ -53,12 +55,10 @@ public final class ExtensionCommand extends CommandBase { * @since v1-release0 */ private final @NotNull TabCompletion completion = new StaticTabCompletion() - .add("psse", 0, "license", "pickshadow.command.extension") - .add("psse", 0, "source", "pickshadow.command.extension") - .add("psse", 0, "killjvm", "pickshadow.command.extension.advanced") - .add("psse", 0, "debug", "pickshadow.command.extension.advanced") - .copy("psse", "pickshadow") - .copy("psse", "server"); + .add("", 0, "license", "pickshadow.command.extension") + .add("", 0, "source", "pickshadow.command.extension") + .add("", 0, "killjvm", "pickshadow.command.extension.advanced") + .add("", 0, "debug", "pickshadow.command.extension.advanced"); /** * Creates and initializes an instance of @@ -67,64 +67,70 @@ public final class ExtensionCommand extends CommandBase { * @since v1-release0 */ public ExtensionCommand() throws IllegalArgumentException { - super("general", "psse", "pssp"); + super(new Information( + "pssp", + new String[]{ "psse", "pickshadow", "server", "about", "version", "ver" }, + "", + "", + "general", + null, + ExecutionTarget.ALL + ), "psse"); } /** {@inheritDoc} */ @Override - public void invoke(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] arguments) { - switch (alias) { - case "psse", "pickshadow", "server" -> { - if (checkPermission(sender, "pickshadow.command.extension")) return; + protected void invokeAll(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] arguments) throws Exception { + if (checkPermission(sender, "pickshadow.command.extension")) return; - if (arguments.length == 0) - sender.sendRichMessage(TranslationManager.get(LanguageString.EXTENSIONCMD_GREETER, - sender, - true, - entry("codename", PSSKInformation.getVersioningCodename()), - entry("version", PSSKInformation.getVersioningString()), - entry("commit", PSSKInformation.getGitCommitIdentifierShort()), - entry("dirty", String.valueOf(PSSKInformation.isGitDirty())) - )); - else { - switch (arguments[0]) { - case "license" -> sender.sendRichMessage(TranslationManager.get( - LanguageString.EXTENSIONCMD_LICENSE, - sender, - true, - entry("license", "GNU Affero General Public License v3"), - entry("license_url", "https://gnu.org/licenses/agpl-3.0.en.html") - )); - case "source" -> sender.sendRichMessage(TranslationManager.get( - LanguageString.EXTENSIONCMD_SOURCE, - sender, - true, - entry("source", "https://git.staropensource.de/JeremyStarTM/PSSE") - )); - case "killjvm" -> { - if (checkPermission(sender, "pickshadow.command.extension.advanced")) return; + if (arguments.length == 0) + sender.sendRichMessage(TranslationManager.get(LanguageString.EXTENSIONCMD_GREETER, + sender, + true, + entry("codename", PSSKInformation.getVersioningCodename()), + entry("version", PSSKInformation.getVersioningString()), + entry("commit", PSSKInformation.getGitCommitIdentifierShort()), + entry("dirty", String.valueOf(PSSKInformation.isGitDirty())) + )); + else { + switch (arguments[0]) { + case "license" -> sender.sendRichMessage(TranslationManager.get( + LanguageString.EXTENSIONCMD_LICENSE, + sender, + true, + entry("license", "GNU Affero General Public License v3"), + entry("license_url", "https://gnu.org/licenses/agpl-3.0.en.html") + )); + case "source" -> sender.sendRichMessage(TranslationManager.get( + LanguageString.EXTENSIONCMD_SOURCE, + sender, + true, + entry("source", "https://git.staropensource.de/JeremyStarTM/PSSE") + )); + case "killjvm" -> { + if (checkPermission(sender, "pickshadow.command.extension.advanced")) return; - sender.sendRichMessage(TranslationManager.get(LanguageString.EXTENSIONCMD_KILLJVM, sender, true)); - Runtime.getRuntime().halt(0); - } - case "debug" -> { - if (checkPermission(sender, "pickshadow.command.extension.advanced")) return; - - if (sender instanceof Player player) - sender.sendMessage(PlayerDataFactory.getInstance().get(player).toJSON()); - else - sender.sendRichMessage(TranslationManager.get(LanguageString.ERROR_NOT_A_PLAYER, sender, true)); - } - default -> sender.sendRichMessage(TranslationManager.get(LanguageString.ERROR_INVALID_ARGUMENT, sender, true, entry("argument", arguments[0]))); - } - if (arguments[0].equals("killjvm")) - Runtime.getRuntime().halt(0); + sender.sendRichMessage(TranslationManager.get(LanguageString.EXTENSIONCMD_KILLJVM, sender, true)); + Runtime.getRuntime().halt(0); } + case "debug" -> { + if (checkPermission(sender, "pickshadow.command.extension.advanced")) return; + + if (sender instanceof Player player) { + if (PlaceholderEngine.getInstance() == null) { + PlaceholderEngine.initialize(); + sender.sendMessage("Initialized the PlaceholderEngine"); + } + + // Send serialized player data + sender.sendMessage(PSPlayerFactory.get(player).getData().toJSON()); + } else + sender.sendRichMessage(TranslationManager.get(LanguageString.ERROR_NOT_A_PLAYER, sender, true)); + } + default -> sender.sendRichMessage(TranslationManager.get(LanguageString.ERROR_INVALID_ARGUMENT, sender, true, entry("argument", arguments[0]))); } - case "pssp" -> { - if (!checkPermission(sender, "pickshadow.command.extension.legacy")) - sender.sendRichMessage(TranslationManager.get(LanguageString.EXTENSIONCMD_ERROR_OLDCMD, sender, true)); - } + if (arguments[0].equals("killjvm")) + Runtime.getRuntime().halt(0); } } } diff --git a/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/LanguageCommand.java b/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/LanguageCommand.java index 3280be9..6816ae6 100644 --- a/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/LanguageCommand.java +++ b/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/LanguageCommand.java @@ -19,14 +19,13 @@ package de.jeremystartm.pickshadow.extension.command.general; -import de.jeremystartm.pickshadow.extension.api.command.CommandBase; +import de.jeremystartm.pickshadow.extension.api.command.Command; import de.jeremystartm.pickshadow.extension.api.command.TabCompletion; import de.jeremystartm.pickshadow.extension.api.command.completion.StaticTabCompletion; -import de.jeremystartm.pickshadow.extension.api.entity.player.PlayerDataFactory; +import de.jeremystartm.pickshadow.extension.api.entity.player.PSPlayerFactory; import de.jeremystartm.pickshadow.extension.api.translation.LanguageString; import de.jeremystartm.pickshadow.extension.api.translation.TranslationManager; import lombok.Getter; -import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; @@ -42,7 +41,7 @@ import static java.util.Map.entry; */ @Getter @SuppressWarnings({ "JavadocDeclaration" }) -public final class LanguageCommand extends CommandBase { +public final class LanguageCommand extends Command { /** * Contains the tab completion for this command. * @@ -62,7 +61,15 @@ public final class LanguageCommand extends CommandBase { * @since v1-release0 */ public LanguageCommand() throws IllegalArgumentException { - super("general", "language"); + super(new Information( + "language", + new String[]{ "language", "lang", "setlanguage", "setlang" }, + "", + "", + "general", + null, + ExecutionTarget.PLAYERS_ONLY + ), "language"); // Initialize completion StaticTabCompletion staticTabCompletion = new StaticTabCompletion(); @@ -79,7 +86,7 @@ public final class LanguageCommand extends CommandBase { /** {@inheritDoc} */ @Override - public void invoke(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] arguments) { + protected void invokeAll(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] arguments) throws Exception { if (checkPermission(sender, "pickshadow.command.language")) return; if (sender instanceof Player player) { @@ -89,7 +96,7 @@ public final class LanguageCommand extends CommandBase { LanguageString.LANGUAGE_CURRENT, sender, true, - entry("language", PlayerDataFactory.getInstance().get(player).getLanguage()) + entry("language", PSPlayerFactory.get(player).getData().getLanguage()) )); else if (arguments.length == 1) {// Check if language is loaded if (!Arrays.asList(TranslationManager.getLanguages()).contains(arguments[0])) { @@ -102,7 +109,7 @@ public final class LanguageCommand extends CommandBase { } // Change language - PlayerDataFactory.getInstance().get(player).setLanguage(arguments[0]); + PSPlayerFactory.get(player).getData().setLanguage(arguments[0]); // Send success message sender.sendRichMessage(TranslationManager.get( diff --git a/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/LegacyExtensionCommand.java b/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/LegacyExtensionCommand.java new file mode 100644 index 0000000..eadeebb --- /dev/null +++ b/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/LegacyExtensionCommand.java @@ -0,0 +1,75 @@ +/* + * PICKSHADOW SERVER KIT SOURCE FILE + * Copyright (c) 2024 The PickShadow Server Kit authors + * Licensed under the GNU Affero General Public License v3 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.jeremystartm.pickshadow.extension.command.general; + +import de.jeremystartm.pickshadow.extension.api.command.Command; +import de.jeremystartm.pickshadow.extension.api.command.TabCompletion; +import de.jeremystartm.pickshadow.extension.api.command.completion.StubTabCompletion; +import de.jeremystartm.pickshadow.extension.api.entity.player.PSPlayer; +import de.jeremystartm.pickshadow.extension.api.translation.LanguageString; +import lombok.Getter; +import org.jetbrains.annotations.NotNull; + +/** + * Handles the {@code /pssp} command. + * + * @since v1-release0 + */ +@Getter +@SuppressWarnings({ "JavadocDeclaration" }) +public final class LegacyExtensionCommand extends Command { + /** + * Contains the tab completion for this command. + * + * @since v1-release0 + * -- GETTER -- + * Returns the tab completion for this command. + * + * @return tab completion + * @since v1-release0 + */ + private final @NotNull TabCompletion completion = StubTabCompletion.completion(); + + /** + * Creates and initializes an instance of + * this class and registers this command. + * + * @since v1-release0 + */ + public LegacyExtensionCommand() throws IllegalArgumentException { + super(new Command.Information( + "pssp", + new String[]{ "pssp" }, + "", + "/pssp", + "general", + null, + ExecutionTarget.PLAYERS_ONLY + ), "pssp"); + } + + /** {@inheritDoc} */ + @Override + public void invokePlayer(@NotNull PSPlayer player, @NotNull String alias, @NotNull String[] arguments) { + if (checkPermission(player, "pickshadow.command.extension.legacy")) return; + + player.messageTranslatable(LanguageString.LEGACYEXTENSIONCMD, true); + } +} diff --git a/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/LinkCommand.java b/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/LinkCommand.java index cd0a967..0338a29 100644 --- a/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/LinkCommand.java +++ b/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/LinkCommand.java @@ -19,13 +19,12 @@ package de.jeremystartm.pickshadow.extension.command.general; -import de.jeremystartm.pickshadow.extension.api.command.CommandBase; +import de.jeremystartm.pickshadow.extension.api.command.Command; import de.jeremystartm.pickshadow.extension.api.command.completion.StubTabCompletion; import de.jeremystartm.pickshadow.extension.api.command.TabCompletion; import de.jeremystartm.pickshadow.extension.api.translation.LanguageString; import de.jeremystartm.pickshadow.extension.api.translation.TranslationManager; import lombok.Getter; -import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.jetbrains.annotations.NotNull; @@ -36,7 +35,7 @@ import org.jetbrains.annotations.NotNull; */ @Getter @SuppressWarnings({ "JavadocDeclaration" }) -public final class LinkCommand extends CommandBase { +public final class LinkCommand extends Command { /** * Contains the tab completion for this command. * @@ -56,12 +55,20 @@ public final class LinkCommand extends CommandBase { * @since v1-release0 */ public LinkCommand() throws IllegalArgumentException { - super("general", "website", "forum", "discord", "teamspeak", "mumble"); + super(new Information( + "link", + new String[]{ "website", "forum", "discord", "teamspeak", "mumble" }, + "", + "", + "general", + null, + ExecutionTarget.ALL + ), "website", "forum", "discord", "teamspeak", "mumble"); } /** {@inheritDoc} */ @Override - public void invoke(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] arguments) { + protected void invokeAll(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] arguments) { if (checkPermission(sender, "pickshadow.command.link")) return; switch (alias) { diff --git a/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/SpeedCommand.java b/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/SpeedCommand.java new file mode 100644 index 0000000..dbe5d49 --- /dev/null +++ b/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/SpeedCommand.java @@ -0,0 +1,227 @@ +/* + * PICKSHADOW SERVER KIT SOURCE FILE + * Copyright (c) 2024 The PickShadow Server Kit authors + * Licensed under the GNU Affero General Public License v3 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.jeremystartm.pickshadow.extension.command.general; + +import de.jeremystartm.pickshadow.extension.api.command.Command; +import de.jeremystartm.pickshadow.extension.api.command.TabCompletion; +import de.jeremystartm.pickshadow.extension.api.command.completion.StaticTabCompletion; +import de.jeremystartm.pickshadow.extension.api.entity.player.PSPlayer; +import de.jeremystartm.pickshadow.extension.api.entity.player.PSPlayerFactory; +import de.jeremystartm.pickshadow.extension.api.translation.LanguageString; +import de.jeremystartm.pickshadow.extension.api.translation.TranslationManager; +import de.jeremystartm.pickshadow.extension.api.type.PlayerAttribute; +import lombok.Getter; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import static java.util.Map.entry; + +/** + * Handles the {@code /speed} command. + * + * @since v1-release0 + */ +@Getter +@SuppressWarnings({ "JavadocDeclaration" }) +public final class SpeedCommand extends Command { + /** + * Contains the tab completion for this command. + * + * @since v1-release0 + * -- GETTER -- + * Returns the tab completion for this command. + * + * @return tab completion + * @since v1-release0 + */ + private final @NotNull TabCompletion completion = new StaticTabCompletion() + .add("speed", 0, "walk", "pickshadow.command.speed.walk") + .add("speed", 0, "fly", "pickshadow.command.speed.fly") + .add("speed", 1, "default", "pickshadow.command.speed") + .add("speed", 1, "0", "pickshadow.command.speed") + .add("speed", 1, "0.1", "pickshadow.command.speed") + .add("speed", 1, "0.2", "pickshadow.command.speed") + .add("speed", 1, "0.25", "pickshadow.command.speed") + .add("speed", 1, "0.3", "pickshadow.command.speed") + .add("speed", 1, "0.5", "pickshadow.command.speed") + .add("speed", 1, "0.5", "pickshadow.command.speed") + .add("speed", 1, "0.6", "pickshadow.command.speed") + .add("speed", 1, "0.7", "pickshadow.command.speed") + .add("speed", 1, "0.75", "pickshadow.command.speed") + .add("speed", 1, "0.8", "pickshadow.command.speed") + .add("speed", 1, "0.9", "pickshadow.command.speed") + .add("speed", 1, "1", "pickshadow.command.speed") + .players("speed", 2); + + /** + * Creates and initializes an instance of + * this class and registers this command. + * + * @since v1-release0 + */ + public SpeedCommand() throws IllegalArgumentException { + super(new Information( + "speed", + new String[]{ "speed" }, + "", + "", + "general", + null, + ExecutionTarget.CONSOLE_PARTIAL + ), "speed"); + } + + /** {@inheritDoc} */ + @Override + protected void invokeAll(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] arguments) { + if (checkPermission(sender, "pickshadow.command.speed")) return; + + // Check length of 'arguments' + if (arguments.length < 2) { + sender.sendRichMessage(TranslationManager.get(LanguageString.ERROR_TOO_FEW_ARGUMENTS, sender, true)); + return; + } else if (arguments.length > 3) { + sender.sendRichMessage(TranslationManager.get(LanguageString.ERROR_TOO_MANY_ARGUMENTS, sender, true)); + return; + } + + boolean onSelf; + PSPlayer target; + float speed; + boolean mode; + + // Determine 'mode' + switch (arguments[0]) { + case "fly" -> { + if (checkPermission(sender, "pickshadow.command.speed.fly")) + return; + else + mode = true; + } + case "walk" -> { + if (checkPermission(sender, "pickshadow.command.speed.walk")) + return; + else + mode = false; + } + default -> { + sender.sendRichMessage(TranslationManager.get( + LanguageString.ERROR_INVALID_ARGUMENT, + sender, + true, + entry("argument", arguments[0]) + )); + return; + } + } + + // Determine 'speed' + if (arguments[1].equals("default")) + if (mode) + speed = 0.1f; + else + speed = 0.2f; + else + speed = Float.parseFloat(arguments[1]); + + // Cap 'speed' + if (speed > 1f) + speed = 1f; + if (speed < 0f) + speed = 0f; + + // Get player/'target' + switch (arguments.length) { + case 2 -> { + onSelf = true; + + // Check if command sender is a player + if (sender instanceof Player senderPlayer) + target = PSPlayerFactory.get(senderPlayer); + else { + sender.sendRichMessage(TranslationManager.get(LanguageString.ERROR_NOT_A_PLAYER, sender, true)); + return; + } + } + case 3 -> { + Player bukkitPlayer = Bukkit.getPlayer(arguments[2]); + + // Check if 'bukkitPlayer' is null + if (bukkitPlayer == null) { + sender.sendRichMessage(TranslationManager.get(LanguageString.ERROR_PLAYER_NOT_FOUND, + sender, + true, + entry("player", arguments[2]) + )); + return; + } + + // Set 'onSelf' + try { + onSelf = bukkitPlayer.getName().equals(sender.getName()); + } catch (NullPointerException exception) { + onSelf = false; + } + + // Set 'target' + target = PSPlayerFactory.get(bukkitPlayer); + } + default -> { + sender.sendRichMessage(TranslationManager.get(LanguageString.ERROR_TOO_MANY_ARGUMENTS, sender, true)); + return; + } + } + + // Set speed + try { + if (mode) + target.getData().setPlayerAttribute(PlayerAttribute.FLY_SPEED, speed); + else + target.getData().setPlayerAttribute(PlayerAttribute.WALKING_SPEED, speed); + } catch (IllegalArgumentException exception) { + sender.sendRichMessage(TranslationManager.get( + LanguageString.ERROR_INVALID_ARGUMENT, + sender, + true, + entry("argument", arguments[1]) + )); + return; + } + + // Send message + target.messageTranslatable( + LanguageString.SPEED, + true, + entry("mode", TranslationManager.get(mode ? LanguageString.SPEED_FLY : LanguageString.SPEED_WALK, sender, false)), + entry("speed", String.valueOf(speed)) + ); + if (!onSelf) + sender.sendRichMessage(TranslationManager.get( + LanguageString.SPEED_REMOTE, + sender, + true, + entry("player", target.getUsername()), + entry("mode", TranslationManager.get(mode ? LanguageString.SPEED_FLY : LanguageString.SPEED_WALK, sender, false)), + entry("speed", String.valueOf(speed)) + )); + } +} diff --git a/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/TrollCommand.java b/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/TrollCommand.java index 1aa2703..5b12515 100644 --- a/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/TrollCommand.java +++ b/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/TrollCommand.java @@ -20,7 +20,7 @@ package de.jeremystartm.pickshadow.extension.command.general; import de.jeremystartm.pickshadow.extension.Extension; -import de.jeremystartm.pickshadow.extension.api.command.CommandBase; +import de.jeremystartm.pickshadow.extension.api.command.Command; import de.jeremystartm.pickshadow.extension.api.command.completion.StaticTabCompletion; import de.jeremystartm.pickshadow.extension.api.command.TabCompletion; import de.jeremystartm.pickshadow.extension.api.translation.LanguageString; @@ -34,7 +34,6 @@ import net.kyori.adventure.title.Title; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Particle; -import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; @@ -50,7 +49,7 @@ import static java.util.Map.entry; */ @Getter @SuppressWarnings({ "JavadocDeclaration" }) -public final class TrollCommand extends CommandBase { +public final class TrollCommand extends Command { /** * Contains the tab completion for this command. * @@ -78,12 +77,20 @@ public final class TrollCommand extends CommandBase { * @since v1-release0 */ public TrollCommand() throws IllegalArgumentException { - super("general", "troll"); + super(new Information( + "troll", + new String[]{ "troll" }, + "", + "", + "general", + null, + ExecutionTarget.ALL + ), "troll"); } /** {@inheritDoc} */ @Override - public void invoke(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] arguments) { + protected void invokeAll(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] arguments) { if (checkPermission(sender, "pickshadow.command.troll")) return; diff --git a/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/replacement/GamemodeCommand.java b/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/replacement/GamemodeCommand.java index 22659f1..819349e 100644 --- a/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/replacement/GamemodeCommand.java +++ b/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/replacement/GamemodeCommand.java @@ -19,7 +19,7 @@ package de.jeremystartm.pickshadow.extension.command.general.replacement; -import de.jeremystartm.pickshadow.extension.api.command.CommandBase; +import de.jeremystartm.pickshadow.extension.api.command.Command; import de.jeremystartm.pickshadow.extension.api.command.TabCompletion; import de.jeremystartm.pickshadow.extension.api.command.completion.StaticTabCompletion; import de.jeremystartm.pickshadow.extension.api.translation.LanguageString; @@ -27,7 +27,6 @@ import de.jeremystartm.pickshadow.extension.api.translation.TranslationManager; import lombok.Getter; import org.bukkit.Bukkit; import org.bukkit.GameMode; -import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; @@ -37,13 +36,13 @@ import java.util.Objects; import static java.util.Map.entry; /** - * Handles the {@code /cmd} command. + * Handles the {@code /gamemode} command. * * @since v1-release0 */ @Getter @SuppressWarnings({ "JavadocDeclaration" }) -public final class GamemodeCommand extends CommandBase { +public final class GamemodeCommand extends Command { /** * Contains the tab completion for this command. * @@ -73,12 +72,20 @@ public final class GamemodeCommand extends CommandBase { * @since v1-release0 */ public GamemodeCommand() throws IllegalArgumentException { - super("general", "gamemode"); + super(new Information( + "gamemode", + new String[]{ "gamemode", "gm" }, + "", + "", + "general", + null, + ExecutionTarget.CONSOLE_PARTIAL + ), "gamemode"); } /** {@inheritDoc} */ @Override - public void invoke(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] arguments) { + protected void invokeAll(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] arguments) throws Exception { if (checkPermission(sender, "pickshadow.command.gamemode")) return; // Check length of 'arguments' diff --git a/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/replacement/HelpCommand.java b/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/replacement/HelpCommand.java index 4f24bde..9edb943 100644 --- a/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/replacement/HelpCommand.java +++ b/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/replacement/HelpCommand.java @@ -19,13 +19,12 @@ package de.jeremystartm.pickshadow.extension.command.general.replacement; -import de.jeremystartm.pickshadow.extension.api.command.CommandBase; +import de.jeremystartm.pickshadow.extension.api.command.Command; import de.jeremystartm.pickshadow.extension.api.command.TabCompletion; import de.jeremystartm.pickshadow.extension.api.command.completion.StubTabCompletion; import de.jeremystartm.pickshadow.extension.api.translation.LanguageString; import de.jeremystartm.pickshadow.extension.api.translation.TranslationManager; import lombok.Getter; -import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.jetbrains.annotations.NotNull; @@ -36,7 +35,7 @@ import org.jetbrains.annotations.NotNull; */ @Getter @SuppressWarnings({ "JavadocDeclaration" }) -public final class HelpCommand extends CommandBase { +public final class HelpCommand extends Command { /** * Contains the tab completion for this command. * @@ -56,12 +55,20 @@ public final class HelpCommand extends CommandBase { * @since v1-release0 */ public HelpCommand() throws IllegalArgumentException { - super("general", "help"); + super(new Information( + "help", + new String[]{ "help", "?" }, + "", + "", + "general", + null, + ExecutionTarget.ALL + ), "help"); } /** {@inheritDoc} */ @Override - public void invoke(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] arguments) { + protected void invokeAll(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] arguments) throws Exception { if (checkPermission(sender, "pickshadow.command.help")) return; sender.sendRichMessage(TranslationManager.get(LanguageString.ERROR_UNIMPLEMENTED, sender, true)); diff --git a/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/replacement/MessageCommand.java b/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/replacement/MessageCommand.java index ce09652..2b96c7d 100644 --- a/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/replacement/MessageCommand.java +++ b/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/replacement/MessageCommand.java @@ -19,15 +19,14 @@ package de.jeremystartm.pickshadow.extension.command.general.replacement; -import de.jeremystartm.pickshadow.extension.api.command.CommandBase; +import de.jeremystartm.pickshadow.extension.api.command.Command; import de.jeremystartm.pickshadow.extension.api.command.completion.StaticTabCompletion; import de.jeremystartm.pickshadow.extension.api.command.TabCompletion; -import de.jeremystartm.pickshadow.extension.api.entity.player.PlayerDataFactory; +import de.jeremystartm.pickshadow.extension.api.entity.player.PSPlayerFactory; import de.jeremystartm.pickshadow.extension.api.translation.LanguageString; import de.jeremystartm.pickshadow.extension.api.translation.TranslationManager; import lombok.Getter; import org.bukkit.Bukkit; -import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; @@ -43,7 +42,7 @@ import static java.util.Map.entry; */ @Getter @SuppressWarnings({ "JavadocDeclaration" }) -public final class MessageCommand extends CommandBase { +public final class MessageCommand extends Command { /** * Contains the tab completion for this command. * @@ -67,12 +66,20 @@ public final class MessageCommand extends CommandBase { * @since v1-release0 */ public MessageCommand() throws IllegalArgumentException { - super("general", "message", "reply"); + super(new Information( + "message", + new String[]{ "message", "msg", "whisper", "w", "reply", "r" }, + "", + "", + "general", + null, + ExecutionTarget.PLAYERS_ONLY + ), "message", "reply"); } /** {@inheritDoc} */ @Override - public void invoke(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] arguments) { + protected void invokeAll(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] arguments) throws Exception { if (checkPermission(sender, "pickshadow.command.message")) return; if (sender instanceof Player player) @@ -122,10 +129,10 @@ public final class MessageCommand extends CommandBase { receiver.sendRichMessage(output); // Mark receiver as last contact - PlayerDataFactory.getInstance().get(player).setLastMessaged(receiver.getUniqueId()); + PSPlayerFactory.get(player).getData().setLastMessaged(receiver.getUniqueId()); } case "reply", "r" -> { - UUID lastMessaged = PlayerDataFactory.getInstance().get(player).getLastMessaged(); + UUID lastMessaged = PSPlayerFactory.get(player).getData().getLastMessaged(); if (lastMessaged == null) player.sendRichMessage(TranslationManager.get(LanguageString.MESSAGING_ERROR_NOLASTCONTACT, sender, true)); diff --git a/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/replacement/PluginsCommand.java b/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/replacement/PluginsCommand.java index 8a6768e..d904872 100644 --- a/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/replacement/PluginsCommand.java +++ b/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/replacement/PluginsCommand.java @@ -19,16 +19,17 @@ package de.jeremystartm.pickshadow.extension.command.general.replacement; -import de.jeremystartm.pickshadow.extension.api.command.CommandBaseWithNull; +import de.jeremystartm.pickshadow.extension.BuildOptions; +import de.jeremystartm.pickshadow.extension.api.command.Command; +import de.jeremystartm.pickshadow.extension.api.command.TabCompletion; +import de.jeremystartm.pickshadow.extension.api.command.completion.StubTabCompletion; import de.jeremystartm.pickshadow.extension.api.translation.LanguageString; import de.jeremystartm.pickshadow.extension.api.translation.TranslationManager; import lombok.Getter; import org.bukkit.Bukkit; -import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.plugin.Plugin; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import static java.util.Map.entry; @@ -38,18 +39,41 @@ import static java.util.Map.entry; * @since v1-release0 */ @Getter -public final class PluginsCommand extends CommandBaseWithNull { +@SuppressWarnings({ "JavadocDeclaration" }) +public final class PluginsCommand extends Command { + /** + * Contains the tab completion for this command. + * + * @since v1-release0 + * -- GETTER -- + * Returns the tab completion for this command. + * + * @return tab completion + * @since v1-release0 + */ + private final @NotNull TabCompletion completion = StubTabCompletion.completion(); + /** * Creates and initializes an instance of * this class and registers this command. * * @since v1-release0 */ - public PluginsCommand() {} + public PluginsCommand() { + super(new Information( + "plugins", + new String[]{ "plugins", "pl" }, + "", + "", + "general", + null, + ExecutionTarget.ALL + )); + } /** {@inheritDoc} */ @Override - public void invoke(@NotNull CommandSender sender, @Nullable Command command, @NotNull String alias, @NotNull String[] arguments) { + protected void invokeAll(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] arguments) throws Exception { if (sender.hasPermission("pickshadow.command.plugins.real")) { StringBuilder output = new StringBuilder(); Plugin[] plugins = Bukkit.getPluginManager().getPlugins(); @@ -80,15 +104,18 @@ public final class PluginsCommand extends CommandBaseWithNull { .append(plugin.isEnabled() ? "enabled": "disabled") .append(plugin.isNaggable() ? "": ", was nagged") .append(")") - .append(entrySuffix) - ; + .append(entrySuffix); sender.sendRichMessage(output.toString()); } else if (!checkPermission(sender, "pickshadow.command.plugins")) sender.sendRichMessage(TranslationManager.get( - LanguageString.PLUGINS, + LanguageString.PLUGINS_FAKE, sender, - true + true, + entry("link_psse", BuildOptions.LINKS_PICKSHADOW_REPOSITORY), + entry("link_luckperms", BuildOptions.LINKS_LUCKPERMS), + entry("link_viaversion", BuildOptions.LINKS_VIAVERSION), + entry("link_freedomchat", BuildOptions.LINKS_FREEDOMCHAT) )); } } diff --git a/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/replacement/ReloadCommand.java b/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/replacement/ReloadCommand.java index 89b7e76..a079b83 100644 --- a/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/replacement/ReloadCommand.java +++ b/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/general/replacement/ReloadCommand.java @@ -19,14 +19,14 @@ package de.jeremystartm.pickshadow.extension.command.general.replacement; -import de.jeremystartm.pickshadow.extension.api.command.CommandBaseWithNull; +import de.jeremystartm.pickshadow.extension.api.command.Command; +import de.jeremystartm.pickshadow.extension.api.command.TabCompletion; +import de.jeremystartm.pickshadow.extension.api.command.completion.StubTabCompletion; import de.jeremystartm.pickshadow.extension.api.translation.LanguageString; import de.jeremystartm.pickshadow.extension.api.translation.TranslationManager; import lombok.Getter; -import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; /** * Handles the {@code /reload} command. @@ -34,18 +34,41 @@ import org.jetbrains.annotations.Nullable; * @since v1-release0 */ @Getter -public final class ReloadCommand extends CommandBaseWithNull { +@SuppressWarnings({ "JavadocDeclaration" }) +public final class ReloadCommand extends Command { + /** + * Contains the tab completion for this command. + * + * @since v1-release0 + * -- GETTER -- + * Returns the tab completion for this command. + * + * @return tab completion + * @since v1-release0 + */ + private final @NotNull TabCompletion completion = StubTabCompletion.completion(); + /** * Creates and initializes an instance of * this class and registers this command. * * @since v1-release0 */ - public ReloadCommand() {} + public ReloadCommand() { + super(new Information( + "reload", + new String[]{ "reload", "rl" }, + "", + "", + "general", + null, + ExecutionTarget.ALL + )); + } /** {@inheritDoc} */ @Override - public void invoke(@NotNull CommandSender sender, @Nullable Command command, @NotNull String alias, @NotNull String[] arguments) { + protected void invokeAll(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] arguments) throws Exception { if (checkPermission(sender, "pickshadow.command.reload")) return; sender.sendRichMessage(TranslationManager.get(LanguageString.RELOAD, sender, true)); diff --git a/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/survival/HomeCommand.java b/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/survival/HomeCommand.java index 4114086..efedad5 100644 --- a/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/survival/HomeCommand.java +++ b/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/survival/HomeCommand.java @@ -19,14 +19,13 @@ package de.jeremystartm.pickshadow.extension.command.survival; -import de.jeremystartm.pickshadow.extension.api.command.CommandBase; +import de.jeremystartm.pickshadow.extension.api.command.Command; import de.jeremystartm.pickshadow.extension.api.command.completion.StubTabCompletion; import de.jeremystartm.pickshadow.extension.api.command.TabCompletion; import de.jeremystartm.pickshadow.extension.api.translation.LanguageString; import de.jeremystartm.pickshadow.extension.api.translation.TranslationManager; import lombok.Getter; import org.bukkit.Bukkit; -import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; @@ -42,7 +41,7 @@ import static java.util.Map.entry; */ @Getter @SuppressWarnings({ "JavadocDeclaration" }) -public final class HomeCommand extends CommandBase { +public final class HomeCommand extends Command { /** * Contains the tab completion for this command. * @@ -62,12 +61,20 @@ public final class HomeCommand extends CommandBase { * @since v1-release0 */ public HomeCommand() throws IllegalArgumentException { - super("survival", "home"); + super(new Information( + "home", + new String[]{ "home", "bed", "spawnpoint", "sp" }, + "", + "", + "survival", + null, + ExecutionTarget.CONSOLE_PARTIAL + ), "home"); } /** {@inheritDoc} */ @Override - public void invoke(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] arguments) { + protected void invokeAll(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] arguments) { if (checkPermission(sender, "pickshadow.command.home")) return; boolean onSelf; diff --git a/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/survivalcheat/FeedCommand.java b/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/survivalcheat/FeedCommand.java index 60dc41b..93c5579 100644 --- a/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/survivalcheat/FeedCommand.java +++ b/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/survivalcheat/FeedCommand.java @@ -19,14 +19,13 @@ package de.jeremystartm.pickshadow.extension.command.survivalcheat; -import de.jeremystartm.pickshadow.extension.api.command.CommandBase; +import de.jeremystartm.pickshadow.extension.api.command.Command; import de.jeremystartm.pickshadow.extension.api.command.TabCompletion; import de.jeremystartm.pickshadow.extension.api.command.completion.StaticTabCompletion; import de.jeremystartm.pickshadow.extension.api.translation.LanguageString; import de.jeremystartm.pickshadow.extension.api.translation.TranslationManager; import lombok.Getter; import org.bukkit.Bukkit; -import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; @@ -42,7 +41,7 @@ import static java.util.Map.entry; */ @Getter @SuppressWarnings({ "JavadocDeclaration" }) -public final class FeedCommand extends CommandBase { +public final class FeedCommand extends Command { /** * Contains the tab completion for this command. * @@ -63,12 +62,20 @@ public final class FeedCommand extends CommandBase { * @since v1-release0 */ public FeedCommand() throws IllegalArgumentException { - super("survivalcheat", "feed"); + super(new Information( + "feed", + new String[]{ "feed" }, + "", + "", + "survivalcheat", + null, + ExecutionTarget.CONSOLE_PARTIAL + ), "feed"); } /** {@inheritDoc} */ @Override - public void invoke(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] arguments) { + protected void invokeAll(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] arguments) { if (checkPermission(sender, "pickshadow.command.feed")) return; Player player; @@ -116,10 +123,10 @@ public final class FeedCommand extends CommandBase { player.setFoodLevel(20); // Send success message - player.sendRichMessage(TranslationManager.get(LanguageString.HEAL, player, true)); + player.sendRichMessage(TranslationManager.get(LanguageString.FEED, player, true)); if (!onSelf) sender.sendRichMessage(TranslationManager.get( - LanguageString.HEAL_REMOTE, + LanguageString.FEED_REMOTE, sender, true, entry("player", player.getName()) diff --git a/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/survivalcheat/HealCommand.java b/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/survivalcheat/HealCommand.java index 235845d..d936aaa 100644 --- a/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/survivalcheat/HealCommand.java +++ b/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/survivalcheat/HealCommand.java @@ -20,7 +20,7 @@ package de.jeremystartm.pickshadow.extension.command.survivalcheat; import de.jeremystartm.pickshadow.extension.BuildOptions; -import de.jeremystartm.pickshadow.extension.api.command.CommandBase; +import de.jeremystartm.pickshadow.extension.api.command.Command; import de.jeremystartm.pickshadow.extension.api.command.TabCompletion; import de.jeremystartm.pickshadow.extension.api.command.completion.StaticTabCompletion; import de.jeremystartm.pickshadow.extension.api.translation.LanguageString; @@ -28,7 +28,6 @@ import de.jeremystartm.pickshadow.extension.api.translation.TranslationManager; import lombok.Getter; import org.bukkit.Bukkit; import org.bukkit.attribute.Attribute; -import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.bukkit.event.entity.EntityRegainHealthEvent; @@ -50,7 +49,7 @@ import static java.util.Map.entry; */ @Getter @SuppressWarnings({ "JavadocDeclaration" }) -public final class HealCommand extends CommandBase { +public final class HealCommand extends Command { /** * Contains the tab completion for this command. * @@ -73,12 +72,20 @@ public final class HealCommand extends CommandBase { * @since v1-release0 */ public HealCommand() throws IllegalArgumentException { - super("survivalcheat", "heal"); + super(new Information( + "heal", + new String[]{ "heal" }, + "", + "", + "survivalcheat", + null, + ExecutionTarget.CONSOLE_PARTIAL + ), "heal"); } /** {@inheritDoc} */ @Override - public void invoke(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] arguments) { + protected void invokeAll(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] arguments) { if (checkPermission(sender, "pickshadow.command.heal")) return; Player player; diff --git a/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/survivalcheat/ToggleDownfallCommand.java b/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/survivalcheat/ToggleDownfallCommand.java index 5ba83af..75bb82c 100644 --- a/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/survivalcheat/ToggleDownfallCommand.java +++ b/extension/src/main/java/de/jeremystartm/pickshadow/extension/command/survivalcheat/ToggleDownfallCommand.java @@ -19,17 +19,14 @@ package de.jeremystartm.pickshadow.extension.command.survivalcheat; -import de.jeremystartm.pickshadow.extension.api.command.CommandBase; +import de.jeremystartm.pickshadow.extension.api.command.Command; import de.jeremystartm.pickshadow.extension.api.command.completion.StubTabCompletion; import de.jeremystartm.pickshadow.extension.api.command.TabCompletion; +import de.jeremystartm.pickshadow.extension.api.entity.player.PSPlayer; import de.jeremystartm.pickshadow.extension.api.translation.LanguageString; -import de.jeremystartm.pickshadow.extension.api.translation.TranslationManager; import lombok.Getter; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import java.util.Objects; @@ -41,7 +38,7 @@ import java.util.Objects; */ @Getter @SuppressWarnings({ "JavadocDeclaration" }) -public final class ToggleDownfallCommand extends CommandBase { +public final class ToggleDownfallCommand extends Command { /** * Contains the tab completion for this command. * @@ -61,29 +58,35 @@ public final class ToggleDownfallCommand extends CommandBase { * @since v1-release0 */ public ToggleDownfallCommand() throws IllegalArgumentException { - super("survivalcheat", "toggledownfall"); + super(new Information( + "toggledownfall", + new String[]{ "toggledownfall" }, + "", + "", + "survivalcheat", + null, + ExecutionTarget.PLAYERS_ONLY + ), "toggledownfall"); } /** {@inheritDoc} */ @Override @SuppressWarnings("resource") - public void invoke(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] arguments) { - if (!checkPermission(sender, "pickshadow.command.toggledownfall")) - if (sender instanceof Player player) { - try { - ServerLevel level = Objects.requireNonNull(MinecraftServer.getServer().getPlayerList().getPlayer(player.getUniqueId())).serverLevel(); + protected void invokePlayer(@NotNull PSPlayer player, @NotNull String alias, @NotNull String @NotNull [] arguments) { + if (checkPermission(player, "pickshadow.command.toggledownfall")) return; - if (player.getWorld().isClearWeather()) - level.setWeatherParameters(0, getDuration(), true, false); - else - level.setWeatherParameters(getDuration(), 0, false, false); + try { + ServerLevel level = Objects.requireNonNull(MinecraftServer.getServer().getPlayerList().getPlayer(player.getUUID())).serverLevel(); - sender.sendRichMessage(TranslationManager.get(LanguageString.TOGGLEDOWNFALL, sender, false)); - } catch (Exception exception) { - sender.sendRichMessage(TranslationManager.get(LanguageString.ERROR_UNKNOWN, sender, true)); - } - } else - sender.sendRichMessage(TranslationManager.get(LanguageString.ERROR_NOT_A_PLAYER, sender, true)); + if (player.getWorld().isClearWeather()) + level.setWeatherParameters(0, getDuration(), true, false); + else + level.setWeatherParameters(getDuration(), 0, false, false); + + player.messageTranslatable(LanguageString.TOGGLEDOWNFALL, false); + } catch (Exception exception) { + player.messageTranslatable(LanguageString.ERROR_UNKNOWN, true); + } } /** diff --git a/extension/src/main/java/de/jeremystartm/pickshadow/extension/listener/ChatListener.java b/extension/src/main/java/de/jeremystartm/pickshadow/extension/listener/ChatListener.java index 36aa359..69c164e 100644 --- a/extension/src/main/java/de/jeremystartm/pickshadow/extension/listener/ChatListener.java +++ b/extension/src/main/java/de/jeremystartm/pickshadow/extension/listener/ChatListener.java @@ -20,7 +20,9 @@ package de.jeremystartm.pickshadow.extension.listener; import de.jeremystartm.pickshadow.extension.BuildOptions; -import de.jeremystartm.pickshadow.extension.api.command.CommandBaseWithNull; +import de.jeremystartm.pickshadow.extension.api.command.Command; +import de.jeremystartm.pickshadow.extension.api.entity.player.PSPlayer; +import de.jeremystartm.pickshadow.extension.api.entity.player.PSPlayerFactory; import de.jeremystartm.pickshadow.extension.api.translation.LanguageString; import de.jeremystartm.pickshadow.extension.api.translation.TranslationManager; import de.jeremystartm.pickshadow.extension.command.general.replacement.PluginsCommand; @@ -28,14 +30,13 @@ import de.jeremystartm.pickshadow.extension.command.general.replacement.ReloadCo import de.staropensource.engine.base.logging.Logger; import io.papermc.paper.event.player.AsyncChatEvent; import net.kyori.adventure.text.minimessage.MiniMessage; -import org.bukkit.Bukkit; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerCommandPreprocessEvent; import org.jetbrains.annotations.NotNull; -import java.util.Arrays; +import static java.util.Map.entry; /** * Listens on chat events. @@ -66,14 +67,21 @@ public final class ChatListener implements Listener { */ @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) public void handleChatMessage(@NotNull AsyncChatEvent event) { + PSPlayer player = PSPlayerFactory.get(event.getPlayer()); + + // Cancel chat message event.setCancelled(true); + // Broadcast the message ourselves if (!BuildOptions.SMALLSTUFF_CHAT_COMMENTS || !MiniMessage.miniMessage().serialize(event.originalMessage()).startsWith("#")) - Bukkit.broadcast( - MiniMessage.miniMessage().deserialize( - TranslationManager.get(LanguageString.MESSAGING_SERVER, event.getPlayer(), false) - .replace("%sender%", event.getPlayer().getName()) - ).append(MiniMessage.miniMessage().deserialize(MiniMessage.miniMessage().serialize(event.originalMessage()).replace("\\<", "<"))) + PSPlayer.broadcast( + TranslationManager.get( + LanguageString.MESSAGING_SERVER, + player.getBukkitPlayer(), + false, + entry("sender", player.getUsername()) + ) + + MiniMessage.miniMessage().serialize(event.originalMessage()).replace("\\<", "<") ); } @@ -86,15 +94,23 @@ public final class ChatListener implements Listener { @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) public void handleChatCommand(@NotNull PlayerCommandPreprocessEvent event) { try { + PSPlayer player = PSPlayerFactory.get(event.getPlayer()); String[] arguments = event.getMessage().split(" "); String command = arguments[0]; + // Disallow any usage of namespaces + // This prevents the use of deprecated, + // legacy, overriden or unsafe commands. + // Or in other words: It allows full control + // over what commands players can execute. if (command.contains(":")) { event.setCancelled(true); - event.getPlayer().sendRichMessage(TranslationManager.get(LanguageString.CHATCOMMAND_ERROR_NAMESPACE, event.getPlayer(), true)); + player.messageTranslatable(LanguageString.CHATCOMMAND_ERROR_NAMESPACE, true); } - // Override commands + // Override Bukkit commands + // These cannot be overridden by using the + // plugin.yml file and the PluginCommand class forceOverrideCommand(event, arguments, pluginsCommand, "plugins", "pl"); forceOverrideCommand(event, arguments, reloadCommand, "reload", "rl"); } catch (Exception exception) { @@ -109,11 +125,11 @@ public final class ChatListener implements Listener { * @param event event information * @since v1-release0 */ - private static void forceOverrideCommand(@NotNull PlayerCommandPreprocessEvent event, @NotNull String @NotNull [] arguments, @NotNull CommandBaseWithNull commandBase, @NotNull String... commands) { - for (String command : commands) - if (arguments[0].equals("/" + command)) { + private static void forceOverrideCommand(@NotNull PlayerCommandPreprocessEvent event, @NotNull String @NotNull [] arguments, @NotNull Command command, @NotNull String... commands) { + for (String commandOverride : commands) + if (arguments[0].equals("/" + commandOverride)) { event.setCancelled(true); - commandBase.invoke(event.getPlayer(), null, command.substring(1), Arrays.copyOf(arguments, 1)); + command.invoke(event.getPlayer(), commandOverride, arguments); } } } diff --git a/extension/src/main/java/de/jeremystartm/pickshadow/extension/listener/ConnectionListener.java b/extension/src/main/java/de/jeremystartm/pickshadow/extension/listener/ConnectionListener.java index b9bf97d..753b3e5 100644 --- a/extension/src/main/java/de/jeremystartm/pickshadow/extension/listener/ConnectionListener.java +++ b/extension/src/main/java/de/jeremystartm/pickshadow/extension/listener/ConnectionListener.java @@ -21,7 +21,8 @@ package de.jeremystartm.pickshadow.extension.listener; import de.jeremystartm.pickshadow.extension.BuildOptions; import de.jeremystartm.pickshadow.extension.Extension; -import de.jeremystartm.pickshadow.extension.api.entity.player.PlayerDataFactory; +import de.jeremystartm.pickshadow.extension.api.entity.player.PSPlayer; +import de.jeremystartm.pickshadow.extension.api.entity.player.PSPlayerFactory; import de.jeremystartm.pickshadow.extension.api.translation.LanguageString; import de.jeremystartm.pickshadow.extension.api.translation.TranslationManager; import de.jeremystartm.pickshadow.extension.command.general.ClearChatCommand; @@ -29,7 +30,6 @@ import de.jeremystartm.pickshadow.extension.misc.Scheduler; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.minimessage.MiniMessage; import org.bukkit.Bukkit; -import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; @@ -56,31 +56,29 @@ public final class ConnectionListener implements Listener { private void handlePlayerJoinEarly(@NotNull PlayerJoinEvent event) { try { // Register player - PlayerDataFactory.getInstance().registerPlayer(event.getPlayer()); + PSPlayer player = PSPlayerFactory.registerPlayer(event.getPlayer()); // Reset fall distance // This effectively reintroduces MC-212, // which was fixed in 24w45a. if (BuildOptions.UNFIX_FALLDAMAGE_CANCELLING) - event.getPlayer().setFallDistance(0f); + player.setFallDistance(0f); // Launch scheduler - Scheduler.player(event.getPlayer()); + Scheduler.player(player); // Empty join message event.joinMessage(Component.empty()); // Broadcast our own join message - for (Player player : Bukkit.getOnlinePlayers()) - player.sendRichMessage(TranslationManager.get( - LanguageString.CONNECTION_JOIN, - player, - false, - entry("player", event.getPlayer().getName()) - )); + PSPlayer.broadcastTranslatable( + LanguageString.CONNECTION_JOIN, + false, + entry("player", player.getUsername()) + ); // Schedule late join event - event.getPlayer().getScheduler().execute(Extension.getInstance(), () -> handlePlayerJoinLate(event.getPlayer()), null, 1); + event.getPlayer().getScheduler().execute(Extension.getInstance(), () -> handlePlayerJoinLate(player), null, 1); } catch (Exception exception) { event.getPlayer().kick(MiniMessage.miniMessage().deserialize(TranslationManager.get(LanguageString.CONNECTION_ERROR_REGISTRATION, event.getPlayer(), false)), PlayerKickEvent.Cause.UNKNOWN); } @@ -88,17 +86,18 @@ public final class ConnectionListener implements Listener { /** * Handles player joins late. * - * @param player {@link Player} which joined + * @param player {@link PSPlayer} instance of the player which joined * @since v1-release0 */ - private void handlePlayerJoinLate(@NotNull Player player) { + private void handlePlayerJoinLate(@NotNull PSPlayer player) { // Clear chat and display welcome message - player.sendRichMessage(ClearChatCommand.NEWLINES + "\n" + TranslationManager.get( + player.messageRaw(ClearChatCommand.NEWLINES); + player.messageTranslatable( LanguageString.CONNECTION_JOIN_WELCOME, - player, false, - entry("player", player.getName()), + false, + entry("player", player.getUsername()), entry("minecraftVersion", Bukkit.getMinecraftVersion()) - )); + ); } /** @@ -109,20 +108,19 @@ public final class ConnectionListener implements Listener { */ @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) private void handlePlayerLeave(@NotNull PlayerQuitEvent event) { - // Unregister player - PlayerDataFactory.getInstance().unregisterPlayer(PlayerDataFactory.getInstance().get(event.getPlayer())); + PSPlayer player = PSPlayerFactory.get(event.getPlayer()); // Empty quit message event.quitMessage(Component.empty()); // Broadcast our own quit message - for (Player player : Bukkit.getOnlinePlayers()) - if (!player.getUniqueId().equals(event.getPlayer().getUniqueId())) - player.sendRichMessage(TranslationManager.get( - LanguageString.CONNECTION_DISCONNECT, - player, - false, - entry("player", event.getPlayer().getName()) - )); + PSPlayer.broadcastTranslatable( + LanguageString.CONNECTION_DISCONNECT, + false, + entry("player", player.getUsername()) + ); + + // Unregister player + PSPlayerFactory.unregisterPlayer(player); } } diff --git a/extension/src/main/java/de/jeremystartm/pickshadow/extension/misc/DamageDetection.java b/extension/src/main/java/de/jeremystartm/pickshadow/extension/misc/DamageDetection.java new file mode 100644 index 0000000..ff282e7 --- /dev/null +++ b/extension/src/main/java/de/jeremystartm/pickshadow/extension/misc/DamageDetection.java @@ -0,0 +1,103 @@ +/* + * PICKSHADOW SERVER KIT SOURCE FILE + * Copyright (c) 2024 The PickShadow Server Kit authors + * Licensed under the GNU Affero General Public License v3 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.jeremystartm.pickshadow.extension.misc; + +import de.jeremystartm.pickshadow.extension.BuildOptions; +import de.jeremystartm.pickshadow.extension.api.entity.player.PSPlayer; +import de.jeremystartm.pickshadow.extension.api.type.PlayerDamageState; +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.damage.DamageSource; +import org.bukkit.damage.DamageType; +import org.jetbrains.annotations.NotNull; + +import static de.jeremystartm.pickshadow.extension.api.type.PlayerDamageState.*; + +/** + * Handles player damage detection. + * + * @since v1-release0 + */ +public final class DamageDetection { + /** + * Creates and initializes an + * instance of this class. + * + * @since v1-release0 + */ + private DamageDetection() {} + + /** + * Applies damage detection. + * + * @param player {@link PSPlayer} to apply damage detection to + * @since v1-release0 + */ + public static void apply(@NotNull PSPlayer player) { + if (player.getGamemode() == GameMode.SURVIVAL || player.getGamemode() == GameMode.ADVENTURE) + DamageDetection.applyStonecutterDamage(player); + else + DamageDetection.resetDamageStates(player); + } + + /** + * Applies stonecutter damage. + * + * @param player {@link PSPlayer} to apply stonecutter damage to + * @since v1-release0 + */ + public static void applyStonecutterDamage(@NotNull PSPlayer player) { + if (BuildOptions.SMALLSTUFF_STONECUTTER_DAMAGE) + switch (player.getData().getDamageState(STONECUTTER)) { + case -1 -> { + Location location = player.getPosition().clone(); + location.setX((long) location.getX() - 1); + location.setY(((long) location.getY())); + location.setZ((long) location.getZ()); + + if (player.getWorld().getBlockAt(location).getType() == Material.STONECUTTER) { + player.getData().setDamageState(STONECUTTER, 0); + //noinspection UnstableApiUsage + player.damage( + 4, + DamageSource + .builder(DamageType.CACTUS) + .withDamageLocation(location) + .build() + ); + } + } + case 11 -> player.getData().nullifyDamageState(STONECUTTER); + default -> player.getData().increaseDamageState(STONECUTTER); + } + } + + /** + * Resets all damage states. + * + * @param player {@link PSPlayer} to reset states of + * @since v1-release0 + */ + public static void resetDamageStates(@NotNull PSPlayer player) { + for (PlayerDamageState damageState : PlayerDamageState.values()) + player.getData().nullifyDamageState(damageState); + } +} diff --git a/extension/src/main/java/de/jeremystartm/pickshadow/extension/misc/Scheduler.java b/extension/src/main/java/de/jeremystartm/pickshadow/extension/misc/Scheduler.java index a40ac4b..7a05c18 100644 --- a/extension/src/main/java/de/jeremystartm/pickshadow/extension/misc/Scheduler.java +++ b/extension/src/main/java/de/jeremystartm/pickshadow/extension/misc/Scheduler.java @@ -21,18 +21,14 @@ package de.jeremystartm.pickshadow.extension.misc; import de.jeremystartm.pickshadow.extension.BuildOptions; import de.jeremystartm.pickshadow.extension.Extension; -import de.jeremystartm.pickshadow.extension.api.entity.player.PlayerData; -import de.jeremystartm.pickshadow.extension.api.entity.player.PlayerDataFactory; +import de.jeremystartm.pickshadow.extension.api.entity.player.PSPlayer; import de.jeremystartm.pickshadow.extension.api.translation.LanguageString; -import de.jeremystartm.pickshadow.extension.api.translation.TranslationManager; import de.staropensource.engine.base.logging.Logger; import io.papermc.paper.threadedregions.scheduler.ScheduledTask; -import net.kyori.adventure.text.minimessage.MiniMessage; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.World; -import org.bukkit.entity.Player; +import net.luckperms.api.util.Tristate; +import org.bukkit.*; +import org.bukkit.entity.Bat; +import org.bukkit.event.entity.CreatureSpawnEvent; import org.jetbrains.annotations.NotNull; /** @@ -49,6 +45,7 @@ public final class Scheduler { */ private Scheduler() {} + // -----> Scheduler methods /** * Invokes scheduler actions for the entire server. * @@ -56,8 +53,12 @@ public final class Scheduler { * @since v1-release0 */ public static void server(@NotNull ScheduledTask scheduledTask) { - for (World world : Bukkit.getServer().getWorlds()) - world(world); + try { + for (World world : Bukkit.getServer().getWorlds()) + world(world); + } catch (Exception exception) { + Logger.crash("Server scheduler failed", exception); + } } /** @@ -66,7 +67,11 @@ public final class Scheduler { * @since v1-release0 */ public static void world(@NotNull World world) { - + try { + killBats(world); + } catch (Exception exception) { + Logger.crash("World scheduler failed for '" + world.getName() + "'", exception); + } } /** @@ -75,35 +80,49 @@ public final class Scheduler { * @param player player to invoke scheduler actions on * @since v1-release0 */ - public static void player(@NotNull Player player) { + public static void player(@NotNull PSPlayer player) { try { - PlayerData data = PlayerDataFactory.getInstance().get(player); + // Apply all attributes + player.applyPlayerAttributes(); - // Apply stonecutter damage - if (BuildOptions.SMALLSTUFF_STONECUTTER_DAMAGE) - switch (data.getStonecutterDamageState()) { - case -1 -> { - Location location = player.getLocation().clone(); - location.setX((long) location.getX() - 1); - location.setY(((long) location.getY())); - location.setZ((long) location.getZ()); - - if (player.getWorld().getBlockAt(location).getType() == Material.STONECUTTER) { - data.setStonecutterDamageState(0); - player.damage(1); - } - } - case 11 -> data.setStonecutterDamageState(-1); - default -> data.setStonecutterDamageState(data.getStonecutterDamageState() + 1); - } + // Apply damage + DamageDetection.apply(player); // Reschedule - player.getScheduler().execute(Extension.getInstance(), () -> player(player), null, 1L); + player.schedule(() -> player(player), 1L); } catch (Exception exception) { if (player.isOnline()) - player.kick(MiniMessage.miniMessage().deserialize(TranslationManager.get(LanguageString.CONNECTION_ERROR_SCHEDULER, player, false))); + player.kickTranslatable(LanguageString.CONNECTION_ERROR_SCHEDULER); - Logger.crash("Player scheduler failed for " + player.getName() + " [" + player.getUniqueId() + "]", exception, false); + Logger.crash("Player scheduler failed for '" + player.getUsername() + "' [" + player.getUUID() + "]", exception, false); + } + } + + // -----> Scheduler actions + /** + * Kills all bats in the specified world. + * + * @param world world to kill all bats in + * @since v1-release0 + */ + private static void killBats(@NotNull World world) { + if (BuildOptions.SMALLSTUFF_KILL_BATS != Tristate.FALSE) { + Location location; + + for (Bat bat : world.getEntitiesByClass(Bat.class)) { + // Only include naturally spawned bats + if (BuildOptions.SMALLSTUFF_KILL_BATS == Tristate.UNDEFINED && bat.getEntitySpawnReason() != CreatureSpawnEvent.SpawnReason.NATURAL) + continue; + + // Teleport bat under the world + location = bat.getLocation().clone(); + location.setY(world.getMinHeight() - 4); + if (!bat.teleport(location)) + Logger.diag("Teleport of bat " + bat.getUniqueId() + " failed (currently at " + bat.getLocation() + ")"); + + // Kill bat + bat.getScheduler().execute(Extension.getInstance(), () -> bat.setHealth(0), null, 1L); + } } } } diff --git a/extension/src/main/java/de/jeremystartm/pickshadow/extension/misc/TabListHandler.java b/extension/src/main/java/de/jeremystartm/pickshadow/extension/misc/TabListHandler.java index b78aa47..36b2c25 100644 --- a/extension/src/main/java/de/jeremystartm/pickshadow/extension/misc/TabListHandler.java +++ b/extension/src/main/java/de/jeremystartm/pickshadow/extension/misc/TabListHandler.java @@ -19,8 +19,8 @@ package de.jeremystartm.pickshadow.extension.misc; -import de.jeremystartm.pickshadow.extension.Extension; -import de.jeremystartm.pickshadow.extension.api.entity.player.PlayerData; +import de.jeremystartm.pickshadow.extension.BuildOptions; +import de.jeremystartm.pickshadow.extension.api.entity.player.PSPlayer; import de.staropensource.engine.base.logging.Logger; import fr.mrmicky.fastboard.adventure.FastBoard; import net.kyori.adventure.text.minimessage.MiniMessage; @@ -88,43 +88,39 @@ public final class TabListHandler { } /** - * Initializes the tab list for - * some {@link PlayerData} instance. + * Initializes the tab list + * for the specified player. * + * @param player {@link PSPlayer} to initialize the tab list for * @since v1-release0 */ - public void initializeTabList(@NotNull PlayerData playerData) { - Logger.verb("Initializing player list for player " + playerData.getPlayer().getName() + " (" + playerData.getPlayer().getUniqueId() + ")"); + public void initializeTabList(@NotNull PSPlayer player) { + Logger.verb("Initializing player list for player " + player.getIdentificationString()); // Initialize FastBoard - FastBoard board = new FastBoard(playerData.getPlayer()); - playerData.setPlayerListScoreboard(board); + FastBoard board = new FastBoard(player.getBukkitPlayer()); + player.getData().setPlayerListScoreboard(board); // Configure FastBoard board.updateTitle(MiniMessage.miniMessage().deserialize("TabList yay")); // Schedule update - updateTabList(playerData, true); + player.scheduleRepeatedly(() -> updateTabList(player), BuildOptions.SETTINGS_TABLIST_UPDATERATE); + updateTabList(player); } /** * Updates the tab list. * + * @param player {@link PSPlayer} to update the tab list for * @since v1-release0 */ - public void updateTabList(@NotNull PlayerData playerData, boolean justSchedule) { - // Schedule next update - playerData.getPlayer().getScheduler().execute(Extension.getInstance(), () -> updateTabList(playerData, false), null, 20); - - // Stop further execution if 'justSchedule' is true - if (justSchedule) - return; - - Logger.verb("Updating player list for player " + playerData.getPlayer().getName() + " (" + playerData.getPlayer().getUniqueId() + ")"); + public void updateTabList(@NotNull PSPlayer player) { + Logger.verb("Updating player list for player " + player.getIdentificationString()); for (int line = 0; line < content.size(); line++) { - Logger.verb("Processing line " + line + " for player " + playerData.getPlayer().getName() + " (" + playerData.getPlayer().getUniqueId() + ")"); - playerData.getPlayerListScoreboard().updateLine(line, MiniMessage.miniMessage().deserialize(content.get(line))); + Logger.verb("Processing line " + line + " for player " + player.getIdentificationString()); + player.getData().getPlayerListScoreboard().updateLine(line, MiniMessage.miniMessage().deserialize(content.get(line))); } } } diff --git a/extension/src/main/resources/plugin.yml b/extension/src/main/resources/plugin.yml index d9303ac..51dd97f 100644 --- a/extension/src/main/resources/plugin.yml +++ b/extension/src/main/resources/plugin.yml @@ -74,6 +74,11 @@ commands: description: "Displays the IP address to PickShadow's Mumble server." usage: "/mumble" aliases: [] + # -> SpeedCommand + speed: + description: "Allows changing the walk and fly speed of players." + usage: "/speed [player]" + aliases: [] # -> TrollCommand troll: description: "Various torturing methods for misbehaving players ;)" @@ -184,6 +189,16 @@ permissions: pickshadow.command.link: description: "Provides access to multiple commands returning hyperlinks." default: false + # --> SpeedCommand + pickshadow.command.speed: + description: "Provides access to the '/speed' command." + default: false + pickshadow.command.speed.fly: + description: "Provides acess to '/speed''s 'fly' option." + default: false + pickshadow.command.speed.walk: + description: "Provides acess to '/speed''s 'walk' option." + default: false # --> TrollCommand pickshadow.command.troll: description: "Provides access to the '/troll' command." diff --git a/extension/src/main/resources/translations/de.json b/extension/src/main/resources/translations/de.json index 5873479..769e7b4 100644 --- a/extension/src/main/resources/translations/de.json +++ b/extension/src/main/resources/translations/de.json @@ -3,10 +3,11 @@ "ERROR_UNIMPLEMENTED": "Diese Aktion wurde noch nicht implementiert.", "ERROR_INVALID_MODE": "Diese Aktion ist nicht verfügbar, da sie deaktiviert wurde.", "ERROR_NOT_A_PLAYER": "Ein Spieler ist für diese Aktion benötigt.", + "ERROR_NOT_SERVER_CONSOLE": "Diese Aktion erfordert die Ausführung von der Serverkonsole.", "ERROR_MISSING_PERM": "Dir ist es nicht erlaubt, diese Aktion durchzuführen (%permission% fehlt).", "ERROR_TOO_FEW_ARGUMENTS": "Zu wenige Argumente.", "ERROR_TOO_MANY_ARGUMENTS": "Zu viele Argumente.", - "ERROR_INVALID_ARGUMENT": "Ungültige(s) Argument(e) %argument%.", + "ERROR_INVALID_ARGUMENT": "Ungültige(s) Argument(e) '%argument%'.", "ERROR_PLAYER_NOT_FOUND": "Der Spieler %player% konnte nicht gefunden werden.", "CONNECTION_JOIN": "%player% ist dem Spiel beigetreten", @@ -20,7 +21,8 @@ "EXTENSIONCMD_KILLJVM": "Bye bye!", "EXTENSIONCMD_LICENSE": "PSSE ist lizensiert unter der %license%.", "EXTENSIONCMD_SOURCE": "Du kannst PickShadow's serverseitigen Code auf sos!git finden.", - "EXTENSIONCMD_ERROR_OLDCMD": "Was ist PSSP...?\nDas PickShadow Server Plugin (PSSP) wurde in PickShadow Server Extension (PSSE) umbenannt.\nBitte verwende diesen Namen stattdessen, vielen Dank.", + + "LEGACYEXTENSIONCMD": "Was ist PSSP...?\nDas PickShadow Server Plugin (PSSP) wurde in PickShadow Server Extension (PSSE) umbenannt.\nBitte verwende diesen Namen stattdessen, vielen Dank.", "MESSAGING_LASTCONTACT": "Die letzte Person, die du angeschrieben hast ist %contact%.", "MESSAGING_ERROR_SELF": "Du kannst dich nicht selber anschreiben.", @@ -59,8 +61,11 @@ "HEAL": "Du wurdest geheilt.", "HEAL_REMOTE": "%player% wurde geheilt.", + "FEED": "Du wurdest gefüttert.", + "FEED_REMOTE": "%player% wurde gefüttert.", + "PLUGINS": "%count% Erweiterungen sind auf diesem Subserver installiert, welche sind:", - "PLUGINS_FAKE": "Denkst du wirklich, dass du interne Informationen so einfach bekommen kannst?\nGlücklicherweise sind wir keine Arschlöcher und geben an was wir verwenden.\nDas PickShadow Netzwerk verwendet PSSE, LuckPerms und FreedomChat um Subserver zu verwalten.", + "PLUGINS_FAKE": "Denkst du wirklich, dass du interne Informationen so einfach bekommen kannst?\nGlücklicherweise sind wir keine Arschlöcher und geben an was wir verwenden.\nDas PickShadow Team setzt PSSE, LuckPerms, ViaVersion und FreedomChat ein, um das Netzwerk zu verwalten.", "RELOAD": "Server reloads gelten als unsicher und werden daher von PSSE blockiert.\nBitte starte den Subserver stattdessen neu.\nFür mehr Informationen, lese bitte den folgenden Blogartikel (Englisch):\nhttps://madelinemiller.dev/blog/problem-with-reload/", @@ -71,5 +76,10 @@ "GAMEMODE_ADVENTURE": "Abenteuermodus", "GAMEMODE_SPECTATOR": "Zuschauermodus", + "SPEED": "Deine %mode% wurde auf %speed% geändert.", + "SPEED_REMOTE": "Die %mode% von %player% wurde auf %speed% geändert.", + "SPEED_FLY": "Fluggeschwindigkeit", + "SPEED_WALK": "Laufgeschwindigkeit", + "CHATCOMMAND_ERROR_NAMESPACE": "Namespaces zu verwenden ist nicht erlaubt, da es verwendet werden kann um Sicherheitsmaßnahmen zu umgehen." } diff --git a/extension/src/main/resources/translations/en.json b/extension/src/main/resources/translations/en.json index 7bc934a..63a0217 100644 --- a/extension/src/main/resources/translations/en.json +++ b/extension/src/main/resources/translations/en.json @@ -15,10 +15,11 @@ "ERROR_UNIMPLEMENTED": "This action is not yet implemented.", "ERROR_INVALID_MODE": "This action is unavailable due to it being disabled.", "ERROR_NOT_A_PLAYER": "You must be a player to perform this action.", + "ERROR_NOT_SERVER_CONSOLE": "This action must be invoked from the server console.", "ERROR_MISSING_PERM": "You aren't allowed to perform this action (lacking %permission%).", "ERROR_TOO_FEW_ARGUMENTS": "Too few arguments.", "ERROR_TOO_MANY_ARGUMENTS": "Too many arguments.", - "ERROR_INVALID_ARGUMENT": "Invalid argument(s) %argument%.", + "ERROR_INVALID_ARGUMENT": "Invalid argument(s) '%argument%'.", "ERROR_PLAYER_NOT_FOUND": "The player %player% could not be found.", "CONNECTION_JOIN": "%player% joined the game", @@ -32,7 +33,8 @@ "EXTENSIONCMD_KILLJVM": "Bye bye!", "EXTENSIONCMD_LICENSE": "PSSE is licensed under the %license%.", "EXTENSIONCMD_SOURCE": "You can find the source code of PickShadow's server-side code on sos!git.", - "EXTENSIONCMD_ERROR_OLDCMD": "What's PSSP...?\nThe PickShadow Server Plugin (PSSP) has been renamed into PickShadow Server Extension (PSSE).\nPlease use the new name from now on. Thank you.", + + "LEGACYEXTENSIONCMD": "What's PSSP...?\nThe PickShadow Server Plugin (PSSP) has been renamed into PickShadow Server Extension (PSSE).\nPlease use the new name from now on. Thank you.", "MESSAGING_SERVER": "%sender% <#d60532>» ", "MESSAGING_DIRECT": "%from% » %to% » %message%", @@ -75,7 +77,7 @@ "HEAL_REMOTE": "%player% has been healed.", "PLUGINS": "%count% extensions are installed on this subserver, these being:", - "PLUGINS_FAKE": "Do you really think you can get internal information so easily?\nLuckily, we aren't dicks and credit what we use.\nThe PickShadow network uses PSSE, LuckPerms and FreedomChat to manage subservers.", + "PLUGINS_FAKE": "Do you really think you can get internal information so easily?\nLuckily, we aren't dicks and credit what we use.\nThe PickShadow network uses PSSE, LuckPerms, ViaVersion and FreedomChat to manage subservers.", "PLUGINS_ENTRY_PREFIX": "- ", "PLUGINS_ENTRY_SUFFIX": "", "PLUGINS_ENTRY_NAME_PREFIX": "", @@ -90,5 +92,10 @@ "GAMEMODE_ADVENTURE": "Adventure mode", "GAMEMODE_SPECTATOR": "Spectator mode", + "SPEED": "Your %mode% was changed to %speed%.", + "SPEED_REMOTE": "The %mode% of %player% was changed to %speed%.", + "SPEED_FLY": "fly speed", + "SPEED_WALK": "walk speed", + "CHATCOMMAND_ERROR_NAMESPACE": "Using namespaces is not allowed, as it may be used to circumvent security measures." }