diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index a05d85a..fffd44b 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -22,13 +22,13 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - name: Set up JDK 17 - uses: actions/setup-java@v3 - with: - java-version: '17' - distribution: 'temurin' - - name: Build with Gradle - uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1 - with: - arguments: build + - uses: actions/checkout@v3 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + - name: Build with Gradle + uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1 + with: + arguments: build diff --git a/Corvo/src/main/java/me/totalfreedom/corvo/Corvo.java b/Corvo/src/main/java/me/totalfreedom/corvo/Corvo.java index eb9f414..ba1670b 100644 --- a/Corvo/src/main/java/me/totalfreedom/corvo/Corvo.java +++ b/Corvo/src/main/java/me/totalfreedom/corvo/Corvo.java @@ -6,18 +6,20 @@ import org.bukkit.plugin.java.JavaPlugin; public class Corvo extends JavaPlugin { @Override - public void onEnable() { + public void onDisable() + { CommonsBase.getInstance() - .getRegistrations() - .getModuleRegistry() - .addModule(this); + .getRegistrations() + .getModuleRegistry() + .removeModule(this); } @Override - public void onDisable() { + public void onEnable() + { CommonsBase.getInstance() - .getRegistrations() - .getModuleRegistry() - .removeModule(this); + .getRegistrations() + .getModuleRegistry() + .addModule(this); } } diff --git a/Corvo/src/main/java/me/totalfreedom/corvo/listener/PlayerInteractionListener.java b/Corvo/src/main/java/me/totalfreedom/corvo/listener/PlayerInteractionListener.java new file mode 100644 index 0000000..0704003 --- /dev/null +++ b/Corvo/src/main/java/me/totalfreedom/corvo/listener/PlayerInteractionListener.java @@ -0,0 +1,43 @@ +package me.totalfreedom.corvo.listener; + +import io.papermc.paper.event.block.BlockBreakBlockEvent; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.event.inventory.InventoryOpenEvent; +import org.bukkit.event.player.PlayerInteractEvent; + +public class PlayerInteractionListener implements Listener +{ + @EventHandler + public void playerBreakBlock(final BlockBreakEvent event) + { + + } + + @EventHandler + public void blockBreakBlock(final BlockBreakBlockEvent event) + { + + } + + @EventHandler + public void playerOpenContainer(final InventoryOpenEvent event) + { + + } + + @EventHandler + public void playerCloseContainer(final InventoryCloseEvent event) + { + + } + + @EventHandler + public void playerInteraction(final PlayerInteractEvent event) + { + + } + +} diff --git a/Datura/src/main/java/me/totalfreedom/datura/Datura.java b/Datura/src/main/java/me/totalfreedom/datura/Datura.java index e1006f6..da40da4 100644 --- a/Datura/src/main/java/me/totalfreedom/datura/Datura.java +++ b/Datura/src/main/java/me/totalfreedom/datura/Datura.java @@ -5,6 +5,7 @@ import me.totalfreedom.datura.punishment.Cager; import me.totalfreedom.datura.punishment.Halter; import me.totalfreedom.datura.punishment.Locker; import me.totalfreedom.datura.sql.MySQL; +import me.totalfreedom.service.SubscriptionProvider; import org.bukkit.Bukkit; import org.bukkit.plugin.java.JavaPlugin; @@ -20,18 +21,40 @@ public class Datura extends JavaPlugin public void onEnable() { CommonsBase.getInstance() - .getRegistrations() - .getModuleRegistry() - .addModule(this); + .getRegistrations() + .getModuleRegistry() + .addModule(this); - CommonsBase.getInstance().getRegistrations().getServiceRegistry().register(this, locker); - CommonsBase.getInstance().getRegistrations().getServiceRegistry().register(this, cager); + CommonsBase.getInstance() + .getRegistrations() + .getServiceTaskRegistry() + .registerService(SubscriptionProvider.syncService(this, locker)); + CommonsBase.getInstance() + .getRegistrations() + .getServiceTaskRegistry() + .registerService(SubscriptionProvider.syncService(this, cager)); - Bukkit.getPluginManager().registerEvents(halter, this); + Bukkit.getPluginManager() + .registerEvents(halter, this); } public MySQL getSQL() { return sql; } + + public Halter getHalter() + { + return halter; + } + + public Locker getLocker() + { + return locker; + } + + public Cager getCager() + { + return cager; + } } diff --git a/Datura/src/main/java/me/totalfreedom/datura/banning/BanUID.java b/Datura/src/main/java/me/totalfreedom/datura/banning/BanUID.java deleted file mode 100644 index 7f0c4b5..0000000 --- a/Datura/src/main/java/me/totalfreedom/datura/banning/BanUID.java +++ /dev/null @@ -1,66 +0,0 @@ -package me.totalfreedom.datura.banning; - -import me.totalfreedom.security.ban.BanID; - -import java.time.Instant; -import java.time.temporal.ChronoField; - -public final class BanUID implements BanID -{ - private final char prefix; - private final int numericalTag; - - private BanUID(final boolean permanent) - { - if (permanent) - { - prefix = 'P'; - } else - { - prefix = 'T'; - } - - final Instant instant = Instant.now(); - - final String stringBuilder = String.valueOf(instant.get(ChronoField.DAY_OF_YEAR)) + // The first three numbers between 001 -> 365 - instant.get(ChronoField.HOUR_OF_DAY) + // next two numbers between 00 -> 23 - instant.get(ChronoField.MINUTE_OF_HOUR) + // next two numbers between 00 -> 59 - instant.get(ChronoField.MILLI_OF_SECOND); // last three numbers between 000 -> 999 - - numericalTag = Integer.parseInt(stringBuilder); - } - - public static BanUID createTempID() - { - return new BanUID(false); - } - - public static BanUID createPermID() - { - return new BanUID(true); - } - - @Override - public String getID() - { - return getIDPrefix() + "-" + getNumericalTag(); - } - - @Override - public char getIDPrefix() - { - return prefix; - } - - @Override - public int getNumericalTag() - { - return numericalTag; - } - - @Override - public boolean isPermanent() - { - return getIDPrefix() == 'P'; - } -} diff --git a/Datura/src/main/java/me/totalfreedom/datura/banning/SimpleBan.java b/Datura/src/main/java/me/totalfreedom/datura/banning/SimpleBan.java deleted file mode 100644 index ff67d94..0000000 --- a/Datura/src/main/java/me/totalfreedom/datura/banning/SimpleBan.java +++ /dev/null @@ -1,82 +0,0 @@ -package me.totalfreedom.datura.banning; - -import me.totalfreedom.security.ban.Ban; -import me.totalfreedom.security.ban.BanID; -import org.jetbrains.annotations.Nullable; - -import java.time.Instant; -import java.util.UUID; - -public final class SimpleBan implements Ban -{ - private final BanID id; - private final UUID offenderID; - private final String reason; - private final String issuer; - private final Instant creationTime; - private final Instant expiry; - - public SimpleBan( - final UUID offenderID, - final String reason, - final String issuer, - final Instant creationTime, - final Instant expiry) - { - if (expiry == null) - { - this.id = BanUID.createPermID(); - } else - { - this.id = BanUID.createTempID(); - } - - this.offenderID = offenderID; - this.reason = reason; - this.issuer = issuer; - this.creationTime = creationTime; - this.expiry = expiry; - } - - @Override - public BanID getBanID() - { - return id; - } - - @Override - public UUID getOffenderID() - { - return offenderID; - } - - @Override - public String getReason() - { - return reason; - } - - @Override - public String getBanIssuer() - { - return issuer; - } - - @Override - public Instant getCreationTime() - { - return creationTime; - } - - @Override - public @Nullable Instant getExpiry() - { - return expiry; - } - - @Override - public boolean isExpired() - { - return Instant.now().compareTo(expiry) >= 0; - } -} diff --git a/Datura/src/main/java/me/totalfreedom/datura/cmd/CageCommand.java b/Datura/src/main/java/me/totalfreedom/datura/cmd/CageCommand.java new file mode 100644 index 0000000..38b2050 --- /dev/null +++ b/Datura/src/main/java/me/totalfreedom/datura/cmd/CageCommand.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2023 TotalFreedom + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the “Software”), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to + * whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package me.totalfreedom.datura.cmd; + +import me.totalfreedom.command.Commander; +import me.totalfreedom.command.annotation.Completion; +import me.totalfreedom.command.annotation.Info; +import me.totalfreedom.command.annotation.Permissive; +import me.totalfreedom.command.annotation.Subcommand; +import me.totalfreedom.datura.Datura; +import org.bukkit.Material; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.NotNull; + +@Info(name = "cage", description = "Cage a player.", + usage = "/cage [material]") +@Permissive(perm = "datura.cage") +public class CageCommand extends Commander +{ + protected CageCommand(final @NotNull JavaPlugin plugin) + { + super(plugin); + } + + @Completion(args = {"%player%"}, index = 0) + @Completion(args = {"on", "off"}, index = 1) + @Subcommand(permission = "datura.cage", args = {Player.class, String.class}) + public void cagePlayer(final CommandSender sender, final Player player, final String string) + { + switch (string.toLowerCase()) + { + case "on" -> + { + ((Datura) getPlugin()).getCager() + .cagePlayer(player.getUniqueId()); + sender.sendPlainMessage("Caged " + player.getName() + "."); + } + case "off" -> + { + ((Datura) getPlugin()).getCager() + .uncagePlayer(player.getUniqueId()); + sender.sendPlainMessage("Liberated " + player.getName() + "."); + } + } + } + + @Completion(args = {"[material]"}, index = 2) + @Subcommand(permission = "datura.cage.custom", args = {Player.class, String.class, Material.class}) + public void cagePlayer(final CommandSender sender, final Player player, final String string, + final Material material) + { + switch (string.toLowerCase()) + { + case "on" -> + { + ((Datura) getPlugin()).getCager() + .cagePlayer(player.getUniqueId(), material); + sender.sendPlainMessage("Caged " + player.getName() + "."); + } + case "off" -> + { + ((Datura) getPlugin()).getCager() + .uncagePlayer(player.getUniqueId()); + sender.sendPlainMessage("Liberated " + player.getName() + "."); + } + } + } +} diff --git a/Datura/src/main/java/me/totalfreedom/datura/cmd/KickCommand.java b/Datura/src/main/java/me/totalfreedom/datura/cmd/KickCommand.java deleted file mode 100644 index 0746fb2..0000000 --- a/Datura/src/main/java/me/totalfreedom/datura/cmd/KickCommand.java +++ /dev/null @@ -1,26 +0,0 @@ -package me.totalfreedom.datura.cmd; - -import me.totalfreedom.command.CommandBase; -import me.totalfreedom.command.annotation.Completion; -import me.totalfreedom.command.annotation.Info; -import me.totalfreedom.command.annotation.Permissive; -import me.totalfreedom.command.annotation.Subcommand; -import me.totalfreedom.datura.Datura; -import org.bukkit.entity.Player; - -@Completion(args = {"%player%"}, index = 0) -@Info(name = "kick", description = "Kick a player from the server.", usage = "/kick ") -@Permissive(perm = "datura.kick") -public class KickCommand extends CommandBase -{ - protected KickCommand(final Datura plugin) - { - super(plugin); - } - - @Subcommand(permission = "datura.kick", args = {Player.class}) - public void kick(final Player player) - { - player.kickPlayer("You have been kicked from the server."); - } -} diff --git a/Datura/src/main/java/me/totalfreedom/datura/cmd/LockerCommand.java b/Datura/src/main/java/me/totalfreedom/datura/cmd/LockerCommand.java new file mode 100644 index 0000000..38f9f2c --- /dev/null +++ b/Datura/src/main/java/me/totalfreedom/datura/cmd/LockerCommand.java @@ -0,0 +1,41 @@ +package me.totalfreedom.datura.cmd; + +import me.totalfreedom.command.Commander; +import me.totalfreedom.command.annotation.Completion; +import me.totalfreedom.command.annotation.Info; +import me.totalfreedom.command.annotation.Permissive; +import me.totalfreedom.command.annotation.Subcommand; +import me.totalfreedom.datura.Datura; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +@Info(name = "locker", description = "Lock a player, preventing them from interacting with their game client.", + usage = "/locker ", aliases = {"lock", "lockup"}) +@Permissive(perm = "datura.locker") +public final class LockerCommand extends Commander +{ + public LockerCommand(final @NotNull Datura plugin) + { + super(plugin); + } + + @Completion(args = {"%player%"}, index = 0) + @Completion(args = {"on", "off"}, index = 1) + @Subcommand(permission = "datura.locker", args = {Player.class, String.class}) + public void lockPlayer(final CommandSender sender, final Player player, final String string) + { + if (string.equalsIgnoreCase("on")) + { + ((Datura) getPlugin()).getLocker() + .lock(player); + + sender.sendPlainMessage("Locked " + player.getName() + "."); + } else if (string.equalsIgnoreCase("off")) + { + ((Datura) getPlugin()).getLocker() + .unlock(player); + sender.sendPlainMessage("Unlocked " + player.getName() + "."); + } + } +} diff --git a/Datura/src/main/java/me/totalfreedom/datura/perms/DefaultNodes.java b/Datura/src/main/java/me/totalfreedom/datura/perms/DefaultNodes.java index 42144f0..6357cdc 100644 --- a/Datura/src/main/java/me/totalfreedom/datura/perms/DefaultNodes.java +++ b/Datura/src/main/java/me/totalfreedom/datura/perms/DefaultNodes.java @@ -1,14 +1,10 @@ package me.totalfreedom.datura.perms; -import me.totalfreedom.security.perm.Node; -import me.totalfreedom.security.perm.NodeType; +import me.totalfreedom.security.Node; +import me.totalfreedom.security.NodeType; public class DefaultNodes { - private DefaultNodes() { - throw new AssertionError(); - } - public static final Node OP = new PermissionNodeBuilder() .key("freedom.master_key") .value(true) @@ -16,7 +12,6 @@ public class DefaultNodes .negated(false) .wildcard(true) .build(); - public static final Node NON_OP = new PermissionNodeBuilder() .key("freedom.default") .value(true) @@ -24,7 +19,6 @@ public class DefaultNodes .negated(false) .wildcard(false) .build(); - public static final Node ALL = new PermissionNodeBuilder() .key("*") .value(true) @@ -32,7 +26,6 @@ public class DefaultNodes .negated(false) .wildcard(true) .build(); - public static final Node NONE = new PermissionNodeBuilder() .key("freedom.none") .value(true) @@ -40,4 +33,9 @@ public class DefaultNodes .negated(false) .wildcard(false) .build(); + + private DefaultNodes() + { + throw new AssertionError(); + } } diff --git a/Datura/src/main/java/me/totalfreedom/datura/perms/FreedomGroup.java b/Datura/src/main/java/me/totalfreedom/datura/perms/FreedomGroup.java index c22f124..a2ccc5d 100644 --- a/Datura/src/main/java/me/totalfreedom/datura/perms/FreedomGroup.java +++ b/Datura/src/main/java/me/totalfreedom/datura/perms/FreedomGroup.java @@ -1,8 +1,8 @@ package me.totalfreedom.datura.perms; import me.totalfreedom.base.CommonsBase; -import me.totalfreedom.security.perm.Group; -import me.totalfreedom.security.perm.Node; +import me.totalfreedom.security.Group; +import me.totalfreedom.security.Node; import net.kyori.adventure.text.Component; import org.bukkit.permissions.Permission; import org.bukkit.permissions.PermissionAttachment; @@ -44,6 +44,12 @@ public class FreedomGroup implements Group this.attachment = new PermissionAttachment(CommonsBase.getInstance(), this); } + @Override + public UUID getUniqueId() + { + return UUID.nameUUIDFromBytes(getName().toString() + .getBytes()); + } @Override public Component getName() @@ -81,12 +87,6 @@ public class FreedomGroup implements Group return isHidden; } - @Override - public UUID getUniqueId() - { - return UUID.nameUUIDFromBytes(getName().toString().getBytes()); - } - @Override public Set permissions() { @@ -109,9 +109,10 @@ public class FreedomGroup implements Group public boolean isPermissionSet(@NotNull final String name) { final Node node = permissions().stream() - .filter(n -> n.key().equalsIgnoreCase(name)) - .findFirst() - .orElse(null); + .filter(n -> n.key() + .equalsIgnoreCase(name)) + .findFirst() + .orElse(null); return node != null && node.value(); } @@ -121,7 +122,8 @@ public class FreedomGroup implements Group { final Node node = permissions() .stream() - .filter(n -> n.bukkit().equals(perm)) + .filter(n -> n.bukkit() + .equals(perm)) .findFirst() .orElse(null); @@ -132,9 +134,10 @@ public class FreedomGroup implements Group public boolean hasPermission(@NotNull final String name) { final Node node = permissions().stream() - .filter(n -> n.key().equalsIgnoreCase(name)) - .findFirst() - .orElse(null); + .filter(n -> n.key() + .equalsIgnoreCase(name)) + .findFirst() + .orElse(null); return node != null && node.value(); } @@ -144,7 +147,8 @@ public class FreedomGroup implements Group { final Node node = permissions() .stream() - .filter(n -> n.bukkit().equals(perm)) + .filter(n -> n.bukkit() + .equals(perm)) .findFirst() .orElse(null); @@ -152,19 +156,19 @@ public class FreedomGroup implements Group } /** - * Adds a permission to the relative PermissionAttachment for this group. - * This method is not thread-safe and should not be called asynchronously. + * Adds a permission to the relative PermissionAttachment for this group. This method is not thread-safe and should + * not be called asynchronously. *

* This method is only here for compatibility with the Bukkit API. * - * @param plugin The plugin responsible for this attachment. May not be null - * or disabled. + * @param plugin The plugin responsible for this attachment. May not be null or disabled. * @param name Name of the permission to attach * @param value Value of the permission * @return This group's PermissionAttachment. */ @Override - public @NotNull PermissionAttachment addAttachment(@NotNull final Plugin plugin, @NotNull final String name, final boolean value) + public @NotNull PermissionAttachment addAttachment(@NotNull final Plugin plugin, @NotNull final String name, + final boolean value) { attachment.setPermission(name, value); return attachment; @@ -177,7 +181,8 @@ public class FreedomGroup implements Group } @Override - public @Nullable PermissionAttachment addAttachment(@NotNull final Plugin plugin, @NotNull final String name, final boolean value, final int ticks) + public @Nullable PermissionAttachment addAttachment(@NotNull final Plugin plugin, @NotNull final String name, + final boolean value, final int ticks) { attachment.setPermission(name, value); return attachment; diff --git a/Datura/src/main/java/me/totalfreedom/datura/perms/FreedomUser.java b/Datura/src/main/java/me/totalfreedom/datura/perms/FreedomUser.java index 3bef6b8..3f1510f 100644 --- a/Datura/src/main/java/me/totalfreedom/datura/perms/FreedomUser.java +++ b/Datura/src/main/java/me/totalfreedom/datura/perms/FreedomUser.java @@ -3,7 +3,7 @@ package me.totalfreedom.datura.perms; import me.totalfreedom.base.CommonsBase; import me.totalfreedom.datura.Datura; import me.totalfreedom.datura.user.SimpleUserData; -import me.totalfreedom.security.perm.Node; +import me.totalfreedom.security.Node; import me.totalfreedom.user.User; import me.totalfreedom.user.UserData; import net.kyori.adventure.text.Component; @@ -23,9 +23,9 @@ import java.util.Set; import java.util.UUID; /** - * The superinterface User extends PermissionHolder, - * which is an extension of {@link org.bukkit.permissions.Permissible}. - * This means that our permission data can be interchanged with other permission plugins. + * The superinterface User extends PermissionHolder, which is an extension of + * {@link org.bukkit.permissions.Permissible}. This means that our permission data can be interchanged with other + * permission plugins. */ public class FreedomUser implements User { @@ -43,10 +43,10 @@ public class FreedomUser implements User this.displayName = player.displayName(); final Datura datura = CommonsBase.getInstance() - .getRegistrations() - .getModuleRegistry() - .getModule(Datura.class) - .getModule(); + .getRegistrations() + .getModuleRegistry() + .getProvider(Datura.class) + .getModule(); UserData data = SimpleUserData.fromSQL(datura.getSQL(), uuid.toString()); @@ -58,16 +58,29 @@ public class FreedomUser implements User this.userData = data; CommonsBase.getInstance() - .getRegistrations() - .getUserRegistry() - .registerUserData(this, userData); + .getRegistrations() + .getUserRegistry() + .registerUserData(this, userData); } @Override - public UserData getUserData() { + public UserData getUserData() + { return userData; } + @Override + public Component getDisplayName() + { + return displayName; + } + + @Override + public boolean isOnline() + { + return Bukkit.getPlayer(uuid) != null; + } + @Override public UUID getUniqueId() { @@ -96,18 +109,6 @@ public class FreedomUser implements User return permissions.remove(node); } - @Override - public Component getDisplayName() - { - return displayName; - } - - @Override - public boolean isOnline() - { - return Bukkit.getPlayer(uuid) != null; - } - @Override public boolean isPermissionSet(@NotNull final String name) { @@ -137,7 +138,8 @@ public class FreedomUser implements User } @Override - public @NotNull PermissionAttachment addAttachment(@NotNull final Plugin plugin, @NotNull final String name, final boolean value) + public @NotNull PermissionAttachment addAttachment(@NotNull final Plugin plugin, @NotNull final String name, + final boolean value) { final Player player = Bukkit.getPlayer(uuid); if (player != null) @@ -161,7 +163,8 @@ public class FreedomUser implements User } @Override - public @Nullable PermissionAttachment addAttachment(@NotNull final Plugin plugin, @NotNull final String name, final boolean value, final int ticks) + public @Nullable PermissionAttachment addAttachment(@NotNull final Plugin plugin, @NotNull final String name, + final boolean value, final int ticks) { final Player player = Bukkit.getPlayer(uuid); if (player != null) diff --git a/Datura/src/main/java/me/totalfreedom/datura/perms/PermissionNode.java b/Datura/src/main/java/me/totalfreedom/datura/perms/PermissionNode.java index 1f2b91e..48870b9 100644 --- a/Datura/src/main/java/me/totalfreedom/datura/perms/PermissionNode.java +++ b/Datura/src/main/java/me/totalfreedom/datura/perms/PermissionNode.java @@ -1,7 +1,7 @@ package me.totalfreedom.datura.perms; -import me.totalfreedom.security.perm.Node; -import me.totalfreedom.security.perm.NodeType; +import me.totalfreedom.security.Node; +import me.totalfreedom.security.NodeType; import org.bukkit.permissions.Permission; import org.bukkit.permissions.PermissionDefault; @@ -9,21 +9,23 @@ record PermissionNode(String key, boolean value, long expiry, NodeType type, - boolean wildcard, - boolean negated) implements Node + boolean wildcard) implements Node { @Override public Permission bukkit() { return new Permission(key(), - value() ? PermissionDefault.TRUE : PermissionDefault.FALSE); + value() + ? PermissionDefault.TRUE + : PermissionDefault.FALSE); } @Override public boolean compare(final Node node) { - return node.key().equalsIgnoreCase(key()) + return node.key() + .equalsIgnoreCase(key()) && node.value() == value() && node.type() == type(); } @@ -31,7 +33,7 @@ record PermissionNode(String key, @Override public boolean isExpired() { - if (isPermanent()) + if (!isTemporary()) { return false; } @@ -39,15 +41,9 @@ record PermissionNode(String key, return System.currentTimeMillis() > expiry(); } - @Override - public boolean isPermanent() - { - return expiry() == -1; - } - @Override public boolean isTemporary() { - return !isPermanent(); + return expiry() > -1; } } diff --git a/Datura/src/main/java/me/totalfreedom/datura/perms/PermissionNodeBuilder.java b/Datura/src/main/java/me/totalfreedom/datura/perms/PermissionNodeBuilder.java index 3ac7361..a89e69b 100644 --- a/Datura/src/main/java/me/totalfreedom/datura/perms/PermissionNodeBuilder.java +++ b/Datura/src/main/java/me/totalfreedom/datura/perms/PermissionNodeBuilder.java @@ -1,8 +1,8 @@ package me.totalfreedom.datura.perms; -import me.totalfreedom.security.perm.Node; -import me.totalfreedom.security.perm.NodeBuilder; -import me.totalfreedom.security.perm.NodeType; +import me.totalfreedom.security.Node; +import me.totalfreedom.security.NodeBuilder; +import me.totalfreedom.security.NodeType; public class PermissionNodeBuilder implements NodeBuilder { diff --git a/Datura/src/main/java/me/totalfreedom/datura/punishment/Cager.java b/Datura/src/main/java/me/totalfreedom/datura/punishment/Cager.java index 7ffc02b..52920aa 100644 --- a/Datura/src/main/java/me/totalfreedom/datura/punishment/Cager.java +++ b/Datura/src/main/java/me/totalfreedom/datura/punishment/Cager.java @@ -2,7 +2,7 @@ package me.totalfreedom.datura.punishment; import me.totalfreedom.base.CommonsBase; import me.totalfreedom.service.Service; -import me.totalfreedom.utils.Shaper; +import me.totalfreedom.utils.ShapeUtils; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; @@ -14,8 +14,8 @@ import org.bukkit.event.player.PlayerQuitEvent; import java.util.HashMap; import java.util.HashSet; -import java.util.List; import java.util.LinkedList; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; @@ -28,10 +28,11 @@ public class Cager extends Service public Cager() { - super("cage_service"); + super("cager-service"); this.cagedPlayers = new HashSet<>(); this.cageLocations = new HashMap<>(); - Bukkit.getPluginManager().registerEvents(new CageListener(), CommonsBase.getInstance()); + Bukkit.getPluginManager() + .registerEvents(new CageListener(), CommonsBase.getInstance()); } /** @@ -49,6 +50,38 @@ public class Cager extends Service cageLocations.put(uuid, createCage(player.getLocation(), Material.GLASS)); } + /** + * This method generates a cube centered around the passed location, made of the provided material. This method + * returns the passed location object. We use the {@link ShapeUtils} class to generate the cube, which allows us to + * define custom shapes using {@link DoubleUnaryOperator}s. + * + * @param location The location to center the cube around. + * @param material The material to use for the cube. + * @return The center location of the cube (the passed location). + * @see ShapeUtils + * @see DoubleUnaryOperator + */ + public Location createCage(final Location location, final Material material) + { + final ShapeUtils shapeUtils = new ShapeUtils(location.getWorld(), 0.0, 4.0); + final List cubed = new LinkedList<>(); + cubed.addAll(shapeUtils.generate(5, t -> t, t -> 4.0, t -> t)); + cubed.addAll(shapeUtils.generate(5, t -> t, t -> 0.0, t -> t)); + cubed.addAll(shapeUtils.generate(5, t -> 0.0, t -> t, t -> t)); + cubed.addAll(shapeUtils.generate(5, t -> 4.0, t -> t, t -> t)); + cubed.addAll(shapeUtils.generate(5, t -> t, t -> t, t -> 0.0)); + cubed.addAll(shapeUtils.generate(5, t -> t, t -> t, t -> 4.0)); + + for (final Location l : cubed) + { + location.getWorld() + .getBlockAt(l) + .setType(material); + } + + return location.clone(); // Return the passed location as that is the center of the cube. + } + public void cagePlayer(final UUID uuid, final Material material) { final Player player = Bukkit.getPlayer(uuid); @@ -74,8 +107,7 @@ public class Cager extends Service } /** - * This method will check to make sure each caged player remains within their cage. - * We use + * This method will check to make sure each caged player remains within their cage. We use *

* {@link Location#distanceSquared(Location)} * {@link Math#pow(double, double)} *

@@ -92,12 +124,14 @@ public class Cager extends Service final Location cageLocation = getCageLocation(player); final boolean inside; - if (!player.getWorld().equals(cageLocation.getWorld())) + if (!player.getWorld() + .equals(cageLocation.getWorld())) { inside = false; } else { - inside = player.getLocation().distanceSquared(cageLocation) > (Math.pow(2.5, 2.0)); + inside = player.getLocation() + .distanceSquared(cageLocation) > (Math.pow(2.5, 2.0)); } if (!inside) @@ -119,43 +153,13 @@ public class Cager extends Service return cageLocations.get(player.getUniqueId()); } - /** - * This method generates a cube centered around the passed location, - * made of the provided material. This method returns the passed location object. - * We use the {@link Shaper} class to generate the cube, which allows us to define - * custom shapes using {@link DoubleUnaryOperator}s. - * - * @param location The location to center the cube around. - * @param material The material to use for the cube. - * @return The center location of the cube (the passed location). - * @see Shaper - * @see DoubleUnaryOperator - */ - public Location createCage(final Location location, final Material material) - { - final Shaper shaper = new Shaper(location.getWorld(), 0.0, 4.0); - final List cubed = new LinkedList<>(); - cubed.addAll(shaper.generate(5, t -> t, t -> 4.0, t -> t)); - cubed.addAll(shaper.generate(5, t -> t, t -> 0.0, t -> t)); - cubed.addAll(shaper.generate(5, t -> 0.0, t -> t, t -> t)); - cubed.addAll(shaper.generate(5, t -> 4.0, t -> t, t -> t)); - cubed.addAll(shaper.generate(5, t -> t, t -> t, t -> 0.0)); - cubed.addAll(shaper.generate(5, t -> t, t -> t, t -> 4.0)); - - for (final Location l : cubed) - { - location.getWorld().getBlockAt(l).setType(material); - } - - return location.clone(); // Return the passed location as that is the center of the cube. - } - private final class CageListener implements Listener { @EventHandler public void blockBreakEvent(final BlockBreakEvent event) { - if (cagedPlayers.contains(event.getPlayer().getUniqueId())) + if (cagedPlayers.contains(event.getPlayer() + .getUniqueId())) { event.setCancelled(true); } @@ -164,9 +168,11 @@ public class Cager extends Service @EventHandler public void playerLeaveEvent(final PlayerQuitEvent event) { - if (cagedPlayers.contains(event.getPlayer().getUniqueId())) + if (cagedPlayers.contains(event.getPlayer() + .getUniqueId())) { - uncagePlayer(event.getPlayer().getUniqueId()); + uncagePlayer(event.getPlayer() + .getUniqueId()); } } } diff --git a/Datura/src/main/java/me/totalfreedom/datura/punishment/Halter.java b/Datura/src/main/java/me/totalfreedom/datura/punishment/Halter.java index 73047d1..11ef9be 100644 --- a/Datura/src/main/java/me/totalfreedom/datura/punishment/Halter.java +++ b/Datura/src/main/java/me/totalfreedom/datura/punishment/Halter.java @@ -25,7 +25,8 @@ public class Halter implements Listener @EventHandler public void playerMove(final PlayerMoveEvent event) { - if (haltedPlayers.contains(event.getPlayer().getUniqueId())) + if (haltedPlayers.contains(event.getPlayer() + .getUniqueId())) { event.setCancelled(true); } diff --git a/Datura/src/main/java/me/totalfreedom/datura/punishment/Locker.java b/Datura/src/main/java/me/totalfreedom/datura/punishment/Locker.java index 36fda9b..9e1e027 100644 --- a/Datura/src/main/java/me/totalfreedom/datura/punishment/Locker.java +++ b/Datura/src/main/java/me/totalfreedom/datura/punishment/Locker.java @@ -22,15 +22,23 @@ public class Locker extends Service super("locker-service"); } - public void lock(final UUID uuid) + public void lock(final Player player) { - lockedPlayers.add(uuid); + lockedPlayers.add(player.getUniqueId()); + } + + public void unlock(final Player player) + { + lockedPlayers.remove(player.getUniqueId()); } @Override public void tick() { - lockedPlayers.removeIf(uuid -> !CommonsBase.getInstance().getServer().getOfflinePlayer(uuid).isOnline()); + lockedPlayers.removeIf(uuid -> !CommonsBase.getInstance() + .getServer() + .getOfflinePlayer(uuid) + .isOnline()); for (final UUID uuid : lockedPlayers) { @@ -43,8 +51,10 @@ public class Locker extends Service private void lockingMethod(@NotNull final Player player) { - final double x = player.getLocation().getX(); - final double z = player.getLocation().getZ(); + final double x = player.getLocation() + .getX(); + final double z = player.getLocation() + .getZ(); if ((x / z % 0.001) < 1) { @@ -65,13 +75,15 @@ public class Locker extends Service player.openInventory(Bukkit.createInventory(null, 54)); player.closeInventory(InventoryCloseEvent.Reason.UNKNOWN); - player.teleport(player.getLocation().clone()); + player.teleport(player.getLocation() + .clone()); final SplittableRandom random = new SplittableRandom(); - player.getEyeLocation().add(new Vector( - random.nextDouble(-1.0, 1.0), - random.nextDouble(-1.0, 1.0), - random.nextDouble(-1.0, 1.0) - )); + player.getEyeLocation() + .add(new Vector( + random.nextDouble(-1.0, 1.0), + random.nextDouble(-1.0, 1.0), + random.nextDouble(-1.0, 1.0) + )); } } diff --git a/Datura/src/main/java/me/totalfreedom/datura/sql/DBBan.java b/Datura/src/main/java/me/totalfreedom/datura/sql/DBBan.java deleted file mode 100644 index 2f2fb13..0000000 --- a/Datura/src/main/java/me/totalfreedom/datura/sql/DBBan.java +++ /dev/null @@ -1,89 +0,0 @@ -package me.totalfreedom.datura.sql; - -import me.totalfreedom.base.CommonsBase; -import me.totalfreedom.datura.banning.SimpleBan; -import me.totalfreedom.security.ban.Ban; -import me.totalfreedom.security.ban.BanID; -import me.totalfreedom.sql.SQL; -import me.totalfreedom.utils.FreedomLogger; - -import java.sql.SQLException; -import java.time.Instant; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; - -public class DBBan -{ - private final SQL sql; - - public DBBan(final SQL sql) - { - this.sql = sql; - } - - public CompletableFuture fromSQL(final BanID id) - { - return sql.executeQuery("SELECT * FROM bans WHERE id = ?", id.getID()) - .thenApplyAsync(result -> - { - try - { - if (result.next()) - { - final UUID uuid = UUID.fromString(result.getString("uuid")); - final Instant timestamp = Instant.parse(result.getString("timestamp")); - - final Instant expiry; - final String ex = result.getString("expiry"); - if (ex.equals("-1")) - { - expiry = null; - } else - { - expiry = Instant.parse(ex); - } - - return new SimpleBan(uuid, - result.getString("reason"), - result.getString("issuer"), - timestamp, - expiry); - } - } catch (SQLException e) - { - FreedomLogger.getLogger("Datura") - .error(e.getMessage()); - } - return null; - }, CommonsBase.getInstance().getExecutor().getAsync()); - } - - public void addBan(final Ban ban) - { - sql.executeUpdate("INSERT INTO bans (id, uuid, reason, issuer, timestamp, expiry) VALUES (?, ?, ?, ?, ?, ?)", - ban.getBanID().getID(), - ban.getOffenderID().toString(), - ban.getReason(), - ban.getBanIssuer(), - ban.getCreationTime().toString(), - (ban.getExpiry() != null ? ban.getExpiry().toString() : "-1") - ); - } - - public boolean hasEntry(final UUID uuid) { - return sql.executeQuery("SELECT * FROM bans WHERE uuid = ?", uuid.toString()) - .thenApplyAsync(result -> - { - try - { - return result.next(); - } catch (SQLException e) - { - FreedomLogger.getLogger("Datura") - .error(e.getMessage()); - } - return false; - }, CommonsBase.getInstance().getExecutor().getAsync()) - .join(); - } -} diff --git a/Datura/src/main/java/me/totalfreedom/datura/sql/MySQL.java b/Datura/src/main/java/me/totalfreedom/datura/sql/MySQL.java index 68ca550..9446e08 100644 --- a/Datura/src/main/java/me/totalfreedom/datura/sql/MySQL.java +++ b/Datura/src/main/java/me/totalfreedom/datura/sql/MySQL.java @@ -2,6 +2,7 @@ package me.totalfreedom.datura.sql; import me.totalfreedom.base.CommonsBase; import me.totalfreedom.sql.SQL; +import me.totalfreedom.utils.container.Identity; import java.sql.Connection; import java.sql.DriverManager; @@ -13,98 +14,145 @@ import java.util.concurrent.CompletionException; public class MySQL implements SQL { - private String url = "jdbc:mysql://"; + /** + * Using StringBuilder for finality. + */ + private final StringBuilder url = new StringBuilder("jdbc:mysql://"); - public MySQL(final String host, final int port, final String database) { - url += host + ":" + port + "/" + database; + public MySQL(final String host, final int port, final String database) + { + url.append(host) + .append(':') + .append(port) + .append('/') + .append(database); } /** - * Adds credentials to the MySQL URL. - * If the URL already contains credentials, they will be overwritten. + * Adds credentials to the MySQL URL. If the URL already contains credentials, they will be overwritten. * * @param username The username to add * @param password The password to add */ - public void addCredentials(final String username, final String password) { - if (url.contains("?user=")) { - url = url.split("\\x3f")[0]; + public void addCredentials(final String username, final String password) + { + if (url.toString() + .contains("?user=")) + { + final String split = url.toString() + .split("\\x3f")[0]; + url.setLength(0); + url.append(split); } - url += "?user=" + username + "&password=" + password; + url.append("?user=") + .append(username) + .append("&password=") + .append(password); } - @Override - public CompletableFuture getConnection(final String url) + public CompletableFuture getRow(final String table, final String column, final Identity identity) { - return CompletableFuture.supplyAsync(() -> { - try { - return DriverManager.getConnection(url); - } catch (SQLException ex) { - throw new CompletionException("Failed to connect to the database: " - + url + "\n", ex); - } - }, CommonsBase.getInstance().getExecutor().getAsync()); + return executeQuery("SELECT * FROM ? WHERE ? = ?", table, column, identity.getId()); } @Override public CompletableFuture prepareStatement(final String query, final Object... args) { - return getConnection(url) - .thenApplyAsync(connection -> { - try { + return getConnection() + .thenApplyAsync(connection -> + { + try + { final PreparedStatement statement = connection.prepareStatement(query); - for (int i = 0; i < args.length; i++) { + for (int i = 0; i < args.length; i++) + { statement.setObject(i + 1, args[i]); } return statement; - } catch (SQLException ex) { + } catch (SQLException ex) + { throw new CompletionException("Failed to prepare statement: " + query + "\n", ex); } - }, CommonsBase.getInstance().getExecutor().getAsync()); + }, CommonsBase.getInstance() + .getExecutor() + .getAsync()); + } + + private CompletableFuture getConnection() + { + return CompletableFuture.supplyAsync(() -> + { + try + { + return DriverManager.getConnection(url.toString()); + } catch (SQLException ex) + { + throw new CompletionException("Failed to connect to the database: " + + url.toString() + "\n", ex); + } + }, CommonsBase.getInstance() + .getExecutor() + .getAsync()); } @Override public CompletableFuture executeQuery(final String query, final Object... args) { return prepareStatement(query, args) - .thenApplyAsync(statement -> { - try { + .thenApplyAsync(statement -> + { + try + { return statement.executeQuery(); - } catch (SQLException ex) { - throw new CompletionException("Failed to retrieve a result set from query: " - + query + "\n", ex); + } catch (SQLException ex) + { + throw new CompletionException( + "Failed to retrieve a result set from query: " + + query + "\n", ex); } - }, CommonsBase.getInstance().getExecutor().getAsync()); + }, CommonsBase.getInstance() + .getExecutor() + .getAsync()); } @Override public CompletableFuture executeUpdate(final String query, final Object... args) { return prepareStatement(query, args) - .thenApplyAsync(statement -> { - try { + .thenApplyAsync(statement -> + { + try + { return statement.executeUpdate(); - } catch (SQLException ex) { + } catch (SQLException ex) + { throw new CompletionException("Failed to execute update: " + query + "\n", ex); } - }, CommonsBase.getInstance().getExecutor().getAsync()); + }, CommonsBase.getInstance() + .getExecutor() + .getAsync()); } @Override public CompletableFuture execute(final String query, final Object... args) { return prepareStatement(query, args) - .thenApplyAsync(statement -> { - try { + .thenApplyAsync(statement -> + { + try + { return statement.execute(); - } catch (SQLException ex) { + } catch (SQLException ex) + { throw new CompletionException("Failed to execute statement: " + query + "\n", ex); } - }, CommonsBase.getInstance().getExecutor().getAsync()); + }, CommonsBase.getInstance() + .getExecutor() + .getAsync()); } @Override @@ -113,9 +161,11 @@ public class MySQL implements SQL final StringBuilder query = new StringBuilder(); query.append("CREATE TABLE IF NOT EXISTS ? ("); - for (int i = 0; i < columns.length; i++) { + for (int i = 0; i < columns.length; i++) + { query.append("?"); - if (i != columns.length - 1) { + if (i != columns.length - 1) + { query.append(", "); } } @@ -123,4 +173,89 @@ public class MySQL implements SQL return execute(query.toString(), table, columns); } + + public CompletableFuture getColumn(final String table, final String column, final String key, + final Identity identity, final Class type) + { + return executeQuery("SELECT ? FROM ? WHERE ? = ?", column, table, key, identity.getId()) + .thenApplyAsync(resultSet -> + { + try + { + if (resultSet.next()) + { + return resultSet.getObject(column, type); + } + } catch (SQLException ex) + { + throw new CompletionException( + "Failed to retrieve column: " + column + " from table: " + table + " " + + "where primary key: " + key + " is equal to: " + identity.getId() + "\n", + ex); + } + return null; + }, CommonsBase.getInstance() + .getExecutor() + .getAsync()); + } + + public CompletableFuture updateColumn(final String table, final String column, final Object value, + final String key, final Identity identity) + { + return executeUpdate("UPDATE ? SET ? = ? WHERE ? = ?", table, column, value, key, identity.getId()) + .thenApplyAsync(result -> result > 0, CommonsBase.getInstance() + .getExecutor() + .getAsync()); + } + + public CompletableFuture deleteRow(final String table, final String key, final Identity identity) + { + return executeUpdate("DELETE FROM ? WHERE ? = ?", table, key, identity.getId()) + .thenApplyAsync(result -> result > 0, CommonsBase.getInstance() + .getExecutor() + .getAsync()); + } + + public CompletableFuture insertRow(final String table, final Object... values) + { + final StringBuilder query = new StringBuilder(); + query.append("INSERT INTO ? VALUES ("); + for (int i = 0; i < values.length; i++) + { + query.append("?"); + if (i != values.length - 1) + { + query.append(", "); + } + } + query.append(")"); + return execute(query.toString(), table, values); + } + + public CompletableFuture insertRow(final String table, final String[] columns, final Object... values) + { + final StringBuilder query = new StringBuilder(); + query.append("INSERT INTO ? ("); + for (int i = 0; i < columns.length; i++) + { + query.append("?"); + if (i != columns.length - 1) + { + query.append(", "); + } + } + query.append(") VALUES ("); + for (int i = 0; i < values.length; i++) + { + query.append("?"); + if (i != values.length - 1) + { + query.append(", "); + } + } + query.append(")"); + return execute(query.toString(), table, columns, values); + } + + } diff --git a/Datura/src/main/java/me/totalfreedom/datura/sql/Result.java b/Datura/src/main/java/me/totalfreedom/datura/sql/Result.java new file mode 100644 index 0000000..9fd214b --- /dev/null +++ b/Datura/src/main/java/me/totalfreedom/datura/sql/Result.java @@ -0,0 +1,14 @@ +package me.totalfreedom.datura.sql; + + +import com.google.errorprone.annotations.Immutable; + +/** + * Represents a single result from a result set. + */ +@Immutable +public record Result(String name, Object value) +{ +} + + diff --git a/Datura/src/main/java/me/totalfreedom/datura/user/ServerEconomyHolder.java b/Datura/src/main/java/me/totalfreedom/datura/user/ServerEconomyHolder.java new file mode 100644 index 0000000..768ac91 --- /dev/null +++ b/Datura/src/main/java/me/totalfreedom/datura/user/ServerEconomyHolder.java @@ -0,0 +1,128 @@ +package me.totalfreedom.datura.user; + +import me.totalfreedom.economy.EconomicEntity; +import me.totalfreedom.economy.EconomicEntityData; + +/** + * Represents the server's economy holder. + *
+ *
+ * This is effectively a Bank object which is meant to represent the server itself, which can store a balance and + * perform transactions with other EconomicEntity objects. + *
+ *
+ * The server is initially given a maximum balance of {@link Long#MAX_VALUE}, though this can be changed using the + * constructor {@link #ServerEconomyHolder(String, long)}. The value that this bank object holds is persistent, which + * means that the total economic resources available are of limited supply. + *
+ *
+ * Please be aware, if the server's economy falls below 0, it will have drastic consequences. + */ +public class ServerEconomyHolder implements EconomicEntity, EconomicEntityData +{ + private final String name; + private long balance; + + /** + * Constructs a new ServerEconomyHolder with the specified name and a balance of {@link Long#MAX_VALUE}. + * + * @param name The name of this server economy holder. + */ + public ServerEconomyHolder(final String name) + { + this.name = name; + this.balance = Long.MAX_VALUE; + } + + /** + * Constructs a new ServerEconomyHolder with the specified name and balance. + * + * @param name The name of this server economy holder. + * @param balance The balance of this server economy holder. + */ + public ServerEconomyHolder(final String name, final long balance) + { + this.name = name; + this.balance = balance; + } + + /** + * This method will return this object, as it is both the EconomicEntity and the EconomicEntityData. This is due to + * the fact that the server should only ever have one singular concrete representation of it's economic entity and + * the respective data. + * + * @return this object. + */ + @Override + public EconomicEntityData getEconomicData() + { + return this; + } + + /** + * @return The name of this server economy holder. + */ + @Override + public String getName() + { + return name; + } + + /** + * This method will always return false, as the server should not ever be prevented from performing transactions. + * + * @return false + */ + @Override + public boolean areTransactionsFrozen() + { + return false; + } + + /** + * @return The server's current available balance. + */ + @Override + public long getBalance() + { + return balance; + } + + /** + * Sets the server's balance to the specified value. + * + * @param newBalance The new balance to set. + */ + @Override + public void setBalance(final long newBalance) + { + balance = newBalance; + } + + /** + * Adds the specified amount to the server's balance. This method mutates the balance and returns the new balance. + * + * @param amount The amount to add. + * @return The new balance. + */ + @Override + public long addToBalance(final long amount) + { + balance += amount; + return balance; + } + + /** + * Removes the specified amount from the server's balance. This method mutates the balance and returns the new + * balance. + * + * @param amount The amount to remove. + * @return The new balance. + */ + @Override + public long removeFromBalance(final long amount) + { + balance -= amount; + return balance; + } +} diff --git a/Datura/src/main/java/me/totalfreedom/datura/user/SimpleUserData.java b/Datura/src/main/java/me/totalfreedom/datura/user/SimpleUserData.java index b1c7c0d..014876c 100644 --- a/Datura/src/main/java/me/totalfreedom/datura/user/SimpleUserData.java +++ b/Datura/src/main/java/me/totalfreedom/datura/user/SimpleUserData.java @@ -3,11 +3,11 @@ package me.totalfreedom.datura.user; import me.totalfreedom.base.CommonsBase; import me.totalfreedom.datura.event.UserDataUpdateEvent; import me.totalfreedom.datura.perms.FreedomUser; -import me.totalfreedom.security.perm.Group; +import me.totalfreedom.security.Group; import me.totalfreedom.sql.SQL; import me.totalfreedom.user.User; import me.totalfreedom.user.UserData; -import me.totalfreedom.utils.FreedomLogger; +import me.totalfreedom.utils.logging.FreedomLogger; import org.apache.commons.lang3.exception.ExceptionUtils; import org.bukkit.Bukkit; import org.bukkit.entity.Player; @@ -26,9 +26,7 @@ public class SimpleUserData implements UserData private final UserDataUpdateEvent event = new UserDataUpdateEvent(this); private Group group; private long playtime; - private boolean frozen; private boolean canInteract; - private boolean caged; private AtomicLong balance; private boolean transactionsFrozen; @@ -38,7 +36,9 @@ public class SimpleUserData implements UserData this.username = player.getName(); this.user = new FreedomUser(player); - CommonsBase.getInstance().getEventBus().addEvent(event); + CommonsBase.getInstance() + .getEventBus() + .addEvent(event); } private SimpleUserData( @@ -47,9 +47,7 @@ public class SimpleUserData implements UserData final User user, final Group group, final long playtime, - final boolean frozen, final boolean canInteract, - final boolean caged, final long balance, final boolean transactionsFrozen) { @@ -58,9 +56,7 @@ public class SimpleUserData implements UserData this.user = user; this.group = group; this.playtime = playtime; - this.frozen = frozen; this.canInteract = canInteract; - this.caged = caged; this.balance = new AtomicLong(balance); this.transactionsFrozen = transactionsFrozen; } @@ -68,57 +64,59 @@ public class SimpleUserData implements UserData public static SimpleUserData fromSQL(final SQL sql, final String uuid) { return sql.executeQuery("SELECT * FROM users WHERE UUID = ?", uuid) - .thenApplyAsync(result -> - { - try - { - if (result.next()) - { - final String g = result.getString("group"); + .thenApplyAsync(result -> + { + try + { + if (result.next()) + { + final String g = result.getString("group"); - final UUID u = UUID.fromString(uuid); - final String username = result.getString("username"); + final UUID u = UUID.fromString(uuid); + final String username = result.getString("username"); - final Player player = Bukkit.getPlayer(u); + final Player player = Bukkit.getPlayer(u); - if (player == null) - throw new IllegalStateException("Player should be online but they are not!"); + if (player == null) + throw new IllegalStateException("Player should be online but they are not!"); - final User user = new FreedomUser(player); - final Group group = CommonsBase.getInstance() - .getRegistrations() - .getGroupRegistry() - .getGroup(g); + final User user = new FreedomUser(player); + final Group group = CommonsBase.getInstance() + .getRegistrations() + .getGroupRegistry() + .getGroup(g); - final long playtime = result.getLong("playtime"); - final boolean frozen = result.getBoolean("frozen"); - final boolean canInteract = result.getBoolean("canInteract"); - final boolean caged = result.getBoolean("caged"); - final long balance = result.getLong("balance"); - final boolean transactionsFrozen = result.getBoolean("transactionsFrozen"); - return new SimpleUserData(u, username, user, group, playtime, frozen, canInteract, caged, balance, transactionsFrozen); - } - } catch (SQLException ex) - { - final String sb = "An error occurred while trying to retrieve user data for UUID " + - uuid + - " from the database." + - "\nCaused by: " + - ExceptionUtils.getRootCauseMessage(ex) + - "\nStack trace: " + - ExceptionUtils.getStackTrace(ex); + final long playtime = result.getLong("playtime"); + final boolean canInteract = result.getBoolean("canInteract"); + final long balance = result.getLong("balance"); + final boolean transactionsFrozen = result.getBoolean("transactionsFrozen"); - FreedomLogger.getLogger("Datura") - .error(sb); - } + return new SimpleUserData(u, username, user, group, playtime, + canInteract, balance, transactionsFrozen); + } + } catch (SQLException ex) + { + final String sb = "An error occurred while trying to retrieve user data for" + + " UUID " + + uuid + + " from the database." + + "\nCaused by: " + + ExceptionUtils.getRootCauseMessage(ex) + + "\nStack trace: " + + ExceptionUtils.getStackTrace(ex); - final Player player = Bukkit.getPlayer(UUID.fromString(uuid)); - if (player == null) throw new IllegalStateException("Player should be online but they are not!"); - return new SimpleUserData(player); - }, CommonsBase.getInstance() - .getExecutor() - .getAsync()) - .join(); + FreedomLogger.getLogger("Datura") + .error(sb); + } + + final Player player = Bukkit.getPlayer(UUID.fromString(uuid)); + if (player == null) throw new IllegalStateException("Player should be online but they are not!"); + + return new SimpleUserData(player); + }, CommonsBase.getInstance() + .getExecutor() + .getAsync()) + .join(); } @Override @@ -179,19 +177,6 @@ public class SimpleUserData implements UserData this.playtime = 0L; } - @Override - public boolean isFrozen() - { - return frozen; - } - - @Override - public void setFrozen(final boolean frozen) - { - event.ping(); - this.frozen = true; - } - @Override public boolean canInteract() { @@ -205,19 +190,6 @@ public class SimpleUserData implements UserData this.canInteract = canInteract; } - @Override - public boolean isCaged() - { - return caged; - } - - @Override - public void setCaged(final boolean caged) - { - event.ping(); - this.caged = caged; - } - @Override public boolean areTransactionsFrozen() { @@ -230,6 +202,12 @@ public class SimpleUserData implements UserData return balance.get(); } + @Override + public void setBalance(final long newBalance) + { + balance.set(newBalance); + } + @Override public long addToBalance(final long amount) { @@ -241,10 +219,4 @@ public class SimpleUserData implements UserData { return balance.addAndGet(-amount); } - - @Override - public void setBalance(final long newBalance) - { - balance.set(newBalance); - } } diff --git a/Fossil/src/main/java/me/totalfreedom/fossil/Fossil.java b/Fossil/src/main/java/me/totalfreedom/fossil/Fossil.java index 06e9b8a..18dc7fb 100644 --- a/Fossil/src/main/java/me/totalfreedom/fossil/Fossil.java +++ b/Fossil/src/main/java/me/totalfreedom/fossil/Fossil.java @@ -1,16 +1,25 @@ package me.totalfreedom.fossil; import me.totalfreedom.base.CommonsBase; +import me.totalfreedom.base.Registration; +import me.totalfreedom.fossil.trail.Trailer; +import me.totalfreedom.service.SubscriptionProvider; import org.bukkit.plugin.java.JavaPlugin; public class Fossil extends JavaPlugin { + private final Trailer trailer = new Trailer(); + private final Registration registration = CommonsBase.getInstance() + .getRegistrations(); + @Override public void onEnable() { - CommonsBase.getInstance() - .getRegistrations() - .getModuleRegistry() - .addModule(this); + registration.getModuleRegistry() + .addModule(this); + + registration.getServiceTaskRegistry() + .registerService( + SubscriptionProvider.syncService(this, trailer)); } } diff --git a/Fossil/src/main/java/me/totalfreedom/fossil/bouncypads/BouncyPad.java b/Fossil/src/main/java/me/totalfreedom/fossil/bouncypads/BouncyPad.java new file mode 100644 index 0000000..cacd37a --- /dev/null +++ b/Fossil/src/main/java/me/totalfreedom/fossil/bouncypads/BouncyPad.java @@ -0,0 +1,211 @@ +package me.totalfreedom.fossil.bouncypads; + +import com.google.errorprone.annotations.Immutable; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; + +import java.util.SplittableRandom; + +/** + * Represents a bouncy pad. Has a velocity and a type. + */ +@Immutable +public class BouncyPad +{ + /** + * The velocity of the pad. + */ + private final double velocity; + /** + * The type of the pad. + */ + private final PadType padType; + + /** + * Creates a new bouncy pad. + * + * @param velocity The velocity of the pad. + * @param padType The type of the pad. + */ + public BouncyPad(final double velocity, final PadType padType) + { + this.velocity = velocity; + this.padType = padType; + } + + /** + * Creates a new bouncy pad with a type of {@link PadType#NORMAL}. + * + * @param velocity The velocity of the pad. + */ + public BouncyPad(final double velocity) + { + this(velocity, PadType.NORMAL); + } + + /** + * Creates a new bouncy pad with a velocity of 1.1 and a type of {@link PadType#NORMAL}. + */ + public BouncyPad() + { + this(1.0 + 0.1F); + } + + /** + * This method will bounce the player based on the type of the pad. + *

+ * The type of the pad, defined by {@link #padType}, will determine how the player is bounced. + *
+ * For type {@link PadType#NORMAL}, the player will be bounced if the face is {@link BlockFace#UP}. + *
+ * For type {@link PadType#SIDES}, the player will be bounced if the face is not {@link BlockFace#UP} or + * {@link BlockFace#DOWN}. + *
+ * For type {@link PadType#ALL}, the player will be bounced regardless of the face. + *
+ * For type {@link PadType#EXTREME}, the player will be bounced with a velocity based on the formula: + *
+ * (((173.31 + 0.5 * velocity) - (31.2 + 0.5 * Math.pow(velocity, 2.0)) + (0.5 * + * Math.pow(velocity, 3.0))) - 173.31) / (velocity * (velocity - 1)) + *
+ * For type {@link PadType#SPACE_CADET}, the player will be bounced with a velocity based on the formula: + *
+ * Math.round(Math.abs((accel * 100.0) + Math.pow(y, Math.floor(accel)) / + * Math.exp(accel))) + *
+ * where y = Math.abs(random.nextGaussian(12, 5) * 0.5 + 0.5) and accel = Math.sqrt(2 * 9.81 * y) + *
+ *
+ * NOTE: The velocity of the pad is added with the inverse velocity of the player. The inverse + * velocity of the player is acquired by multiplying the velocity of the player by -1. + * + * @param player The player to bounce. + * @param face The face of the block the player is bouncing on. + */ + public void bouncePad(final Player player, final BlockFace face) + { + switch (padType) + { + case NORMAL -> bounceNormal(player, face); + case SIDES -> bounceSides(player, face); + case ALL -> bounceAll(player, face); + case EXTREME -> bounceExtreme(player, face); + case SPACE_CADET -> bounceSpaceCadet(player, face); + } + } + + /** + * This method returns a vector based on the following: + *
+ * (BlockFace direction + Player velocity * -1) * velocity + *
+ *
+ * We retrieve a vector representing the direction in which this block face is facing. This is then added with the + * inverse velocity of the player, which is the direction and speed in which the player is moving multiplied by -1. + * This is then multiplied by the velocity of the pad. + * + * @param player The moving player + * @param face The face of the block the player is bouncing on. + * @return A vector representing the direction and speed in which the player should be bounced. + */ + private Vector getVector(final Player player, final BlockFace face) + { + return face.getDirection() + .add(player.getVelocity() + .multiply(-1)) + .multiply(velocity); + } + + /** + * This method will bounce the player if the face is {@link BlockFace#UP}. + * + * @param player The player to bounce. + * @param face The face of the block the player is bouncing on. + */ + private void bounceNormal(final Player player, final BlockFace face) + { + if (!face.equals(BlockFace.UP)) + return; + + player.setVelocity(getVector(player, face)); + } + + /** + * This method will bounce the player if the face is not {@link BlockFace#UP} or {@link BlockFace#DOWN}. + * + * @param player The player to bounce. + * @param face The face of the block the player is bouncing on. + */ + private void bounceSides(final Player player, final BlockFace face) + { + if (face == BlockFace.UP || face == BlockFace.DOWN) + return; + + player.setVelocity(getVector(player, face)); + } + + /** + * This method will bounce the player regardless of the face. + * + * @param player The player to bounce. + * @param face The face of the block the player is bouncing on. + */ + private void bounceAll(final Player player, final BlockFace face) + { + player.setVelocity(getVector(player, face)); + } + + /** + * This method will bounce the player with a velocity based on the formula: + *
+ * (((173.31 + 0.5 * velocity) - (31.2 + 0.5 * Math.pow(velocity, 2.0)) + (0.5 * + * Math.pow(velocity, 3.0))) - 173.31) / (velocity * (velocity - 1)) + *
+ *
+ * NOTE: The velocity of the pad is added with the inverse velocity of the player. The inverse + * velocity of the player is acquired by multiplying the velocity of the player by -1. + * + * @param player The player to bounce. + * @param face The face of the block the player is bouncing on. + */ + private void bounceExtreme(final Player player, final BlockFace face) + { + final double extremeVelocity = (((173.31 + 0.5 * velocity) - (31.2 + 0.5 * Math.pow(velocity, 2.0)) + (0.5 * Math.pow(velocity, 3.0))) - 173.31) / (velocity * (velocity - 1)); + player.setVelocity(face.getDirection() + .add(player.getVelocity() + .multiply(-1)) + .multiply(extremeVelocity * velocity)); + } + + /** + * This method will bounce the player with a velocity based on the formula: + *
+ * Math.round(Math.abs((accel * 100.0) + Math.pow(y, Math.floor(accel)) / + * Math.exp(accel))) + *
+ * where y = Math.abs(random.nextGaussian(12, 5) * 0.5 + 0.5) and + * accel = Math.sqrt(2 * 9.81 * y) + * + * @param player The player to bounce. + * @param face The face of the block the player is bouncing on. + */ + private void bounceSpaceCadet(final Player player, final BlockFace face) + { + final SplittableRandom random = new SplittableRandom(); + final double y = Math.abs(random.nextGaussian(12, 5) * 0.5 + 0.5); + final double accel = Math.sqrt(2 * 9.81 * y); + final double spaceVelocity = Math.round(Math.abs((accel * 100.0) + Math.pow(y, Math.floor(accel)) / Math.exp(accel))); + + final Vector accelVector = new Vector(0, y + accel, 0); + final Vector postVector = new Vector(0, spaceVelocity, 0); + + final Vector spaceVector = face.getDirection() + .add(player.getVelocity() + .multiply(-1)) + .multiply(accelVector.multiply(postVector)); + + player.setVelocity(spaceVector); + } +} diff --git a/Fossil/src/main/java/me/totalfreedom/fossil/bouncypads/PadHolder.java b/Fossil/src/main/java/me/totalfreedom/fossil/bouncypads/PadHolder.java new file mode 100644 index 0000000..cd4842e --- /dev/null +++ b/Fossil/src/main/java/me/totalfreedom/fossil/bouncypads/PadHolder.java @@ -0,0 +1,158 @@ +package me.totalfreedom.fossil.bouncypads; + +import me.totalfreedom.base.CommonsBase; +import me.totalfreedom.fossil.Fossil; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Tag; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerMoveEvent; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Stream; + +/** + * Holds all the active pads for each player, and also manages player pad interaction. + */ +public class PadHolder implements Listener +{ + /** + * A map of all the currently active pads, stored by {@link Player} {@link UUID}. + */ + private final Map pads = new HashMap<>(); + + /** + * Creates a new pad holder. + */ + public PadHolder() + { + Bukkit.getPluginManager() + .registerEvents(this, CommonsBase + .getInstance() + .getRegistrations() + .getModuleRegistry() + .getProvider(Fossil.class) + .getModule()); + } + + /** + * Adds a pad for the given player. If the player already has a pad stored in the map, it will be overwritten with + * the new pad. + * + * @param player The player to add the pad for. + * @param pad The pad to add. + */ + public void addPad(final Player player, final BouncyPad pad) + { + this.pads.put(player.getUniqueId(), pad); + } + + /** + * Removes the pad for the given player, if the player has one. + * + * @param player The player to remove the pad for. + */ + public void removePad(final Player player) + { + this.pads.remove(player.getUniqueId()); + } + + /** + * Gets the pad for the given player, if the player has one. If the player has no active pad, this will return + * null. + * + * @param player The player to get the pad for. + * @return The pad for the given player. + */ + @Nullable + public BouncyPad getPad(final Player player) + { + return this.pads.get(player.getUniqueId()); + } + + /** + * Checks if there is a pad active for the given player. + * + * @param player The player to check. + * @return True if the player has a pad, false otherwise. + */ + public boolean hasPad(final Player player) + { + return this.pads.containsKey(player.getUniqueId()); + } + + /** + * Gets a map of all the currently active pads, stored by {@link Player} {@link UUID}. + * + * @return A map of all the currently active pads. + */ + public Map getPads() + { + return this.pads; + } + + /** + * Handles player pad interaction. This will check the relative block for each acceptible direction, and pass the + * resulting block face (if any) to the bounce pad. See + * {@link BouncyPad#bouncePad(Player, org.bukkit.block.BlockFace)} for how the resulting block face is processed. + * + * @param event The event which gets called when a player moves. + */ + @EventHandler + public void onPlayerMove(final PlayerMoveEvent event) + { + final Player player = event.getPlayer(); + if (!this.hasPad(player)) + { + return; + } + + final BouncyPad pad = this.getPad(player); + final Location location = player.getLocation(); + + final Block xNeg1 = getRelative(location, -1, 0, 0); + final Block xPos1 = getRelative(location, 1, 0, 0); + final Block zNeg1 = getRelative(location, 0, 0, -1); + final Block zPos1 = getRelative(location, 0, 0, 1); + final Block yNeg1 = getRelative(location, 0, -1, 0); + + Stream.of(xNeg1, xPos1, zNeg1, zPos1, yNeg1) + .filter(this::isWool) + .map(block -> block.getFace(location.getBlock())) + .findFirst() + .ifPresent(face -> pad.bouncePad(player, face)); + } + + /** + * Gets the relative block at the given location. + * + * @param location The location to get the relative block from. + * @param x The x mod. + * @param y The y mod. + * @param z The z mod. + * @return The relative block. + */ + private Block getRelative(final Location location, final int x, final int y, final int z) + { + return location.getBlock() + .getRelative(x, y, z); + } + + /** + * Checks if the given block is wool. + * + * @param block The block to check. + * @return True if the block is wool, false otherwise. + * @see Tag#WOOL + */ + private boolean isWool(final Block block) + { + return Tag.WOOL.isTagged(block.getType()); + } +} diff --git a/Fossil/src/main/java/me/totalfreedom/fossil/bouncypads/PadType.java b/Fossil/src/main/java/me/totalfreedom/fossil/bouncypads/PadType.java new file mode 100644 index 0000000..2c58591 --- /dev/null +++ b/Fossil/src/main/java/me/totalfreedom/fossil/bouncypads/PadType.java @@ -0,0 +1,32 @@ +package me.totalfreedom.fossil.bouncypads; + +import org.bukkit.block.BlockFace; + +/** + * Represents a specific type of bouncy pad. + */ +public enum PadType +{ + /** + * A normal pad, which will only bounce the player if the face is {@link BlockFace#UP}. + */ + NORMAL, + /** + * A pad which will bounce the player on {@link BlockFace#NORTH}, {@link BlockFace#SOUTH}, {@link BlockFace#EAST} or + * {@link BlockFace#WEST}. + */ + SIDES, + /** + * A pad which will bounce the player if the face is {@link BlockFace#UP}, {@link BlockFace#NORTH}, + * {@link BlockFace#EAST}, {@link BlockFace#SOUTH} or {@link BlockFace#WEST}. + */ + ALL, + /** + * A pad which will bounce the player with an extreme velocity + */ + EXTREME, + /** + * A pad which will send the player to space. + */ + SPACE_CADET; +} diff --git a/Fossil/src/main/java/me/totalfreedom/fossil/cmd/CakeCommand.java b/Fossil/src/main/java/me/totalfreedom/fossil/cmd/CakeCommand.java index 7a6c437..7a5c7b0 100644 --- a/Fossil/src/main/java/me/totalfreedom/fossil/cmd/CakeCommand.java +++ b/Fossil/src/main/java/me/totalfreedom/fossil/cmd/CakeCommand.java @@ -21,37 +21,42 @@ package me.totalfreedom.fossil.cmd; -import me.totalfreedom.command.CommandBase; +import me.totalfreedom.command.Commander; import me.totalfreedom.command.annotation.Base; import me.totalfreedom.command.annotation.Info; import me.totalfreedom.command.annotation.Permissive; -import me.totalfreedom.utils.FreedomMiniMessage; +import me.totalfreedom.utils.kyori.FreedomMiniMessage; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.command.CommandSender; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.NotNull; @Info(name = "cake", description = "Gives everyone cake and broadcasts a message.", usage = "/cake") @Permissive(perm = "fossil.cake") -public class CakeCommand extends CommandBase +public class CakeCommand extends Commander { - protected CakeCommand(JavaPlugin plugin) + protected CakeCommand(final @NotNull JavaPlugin plugin) { super(plugin); } @Base - public void cake(final CommandSender sender) + public void broadcastCake(final CommandSender sender) { - Bukkit.broadcast(FreedomMiniMessage.deserialize(true, "But there's no sense crying over every mistake. You just keep on trying till you run out of cake.")); + Bukkit.broadcast(FreedomMiniMessage.deserialize(true, + "But there's no sense crying over every mistake. You just keep on trying till you run out of " + + "cake.")); final ItemStack stack = new ItemStack(Material.CAKE, 1); final ItemMeta meta = stack.getItemMeta(); meta.displayName(FreedomMiniMessage.deserialize(true, "The Lie")); stack.setItemMeta(meta); - Bukkit.getOnlinePlayers().forEach(player -> player.getInventory().addItem(stack)); + Bukkit.getOnlinePlayers() + .forEach(player -> player.getInventory() + .addItem(stack)); } } diff --git a/Fossil/src/main/java/me/totalfreedom/fossil/economy/SimpleTransaction.java b/Fossil/src/main/java/me/totalfreedom/fossil/economy/SimpleTransaction.java index 6c6f9ac..93bf7b7 100644 --- a/Fossil/src/main/java/me/totalfreedom/fossil/economy/SimpleTransaction.java +++ b/Fossil/src/main/java/me/totalfreedom/fossil/economy/SimpleTransaction.java @@ -7,9 +7,9 @@ import java.util.concurrent.atomic.AtomicLong; public class SimpleTransaction implements Transaction { + protected final AtomicLong balance; private final EconomicEntity source; private final EconomicEntity destination; - protected final AtomicLong balance; public SimpleTransaction(final EconomicEntity source, final EconomicEntity destination, final long balance) { diff --git a/Fossil/src/main/java/me/totalfreedom/fossil/economy/SimpleTransactionLogger.java b/Fossil/src/main/java/me/totalfreedom/fossil/economy/SimpleTransactionLogger.java index 8ef43e9..80ba2d2 100644 --- a/Fossil/src/main/java/me/totalfreedom/fossil/economy/SimpleTransactionLogger.java +++ b/Fossil/src/main/java/me/totalfreedom/fossil/economy/SimpleTransactionLogger.java @@ -1,11 +1,11 @@ package me.totalfreedom.fossil.economy; import me.totalfreedom.audience.MutableAudienceForwarder; -import me.totalfreedom.economy.TransactionResult; import me.totalfreedom.economy.CompletedTransaction; -import me.totalfreedom.economy.TransactionLogger; import me.totalfreedom.economy.EconomicEntity; -import me.totalfreedom.utils.FreedomLogger; +import me.totalfreedom.economy.TransactionLogger; +import me.totalfreedom.economy.TransactionResult; +import me.totalfreedom.utils.logging.FreedomLogger; import net.kyori.adventure.text.Component; public class SimpleTransactionLogger implements TransactionLogger @@ -24,17 +24,19 @@ public class SimpleTransactionLogger implements TransactionLogger final EconomicEntity destination = completedTransaction.getDestination(); final long transactionAmount = completedTransaction.getBalance(); - transactionLoggingStatementBuilder.append(resultSuccess ? "Successful" : "Unsuccessful") - .append(" (") - .append(resultMessage) - .append(") ") - .append(" transaction between ") - .append(source.getName()) - .append(" ") - .append(destination.getName()) - .append(" where the volume of currency transferred was $") - .append(transactionAmount) - .append("."); + transactionLoggingStatementBuilder.append(resultSuccess + ? "Successful" + : "Unsuccessful") + .append(" (") + .append(resultMessage) + .append(") ") + .append(" transaction between ") + .append(source.getName()) + .append(" ") + .append(destination.getName()) + .append(" where the volume of currency transferred was $") + .append(transactionAmount) + .append("."); final Component message = Component.text(transactionLoggingStatementBuilder.toString()); diff --git a/Fossil/src/main/java/me/totalfreedom/fossil/economy/SimpleTransactionResult.java b/Fossil/src/main/java/me/totalfreedom/fossil/economy/SimpleTransactionResult.java index 935ea22..e0ee389 100644 --- a/Fossil/src/main/java/me/totalfreedom/fossil/economy/SimpleTransactionResult.java +++ b/Fossil/src/main/java/me/totalfreedom/fossil/economy/SimpleTransactionResult.java @@ -7,16 +7,21 @@ import net.kyori.adventure.text.format.NamedTextColor; public class SimpleTransactionResult implements TransactionResult { public static final TransactionResult SUCCESSFUL = new SimpleTransactionResult("Successful transaction.", true); - public static final TransactionResult UNAUTHORIZED = new SimpleTransactionResult("Unauthorized transaction.", false); - public static final TransactionResult AMOUNT_TOO_SMALL = new SimpleTransactionResult("Transaction balance too small.", false); - public static final TransactionResult INSUFFICIENT_FUNDS = new SimpleTransactionResult("The source has an insufficient balance to carry out this transaction.", false); + public static final TransactionResult UNAUTHORIZED = new SimpleTransactionResult("Unauthorized transaction.", + false); + public static final TransactionResult AMOUNT_TOO_SMALL = new SimpleTransactionResult( + "Transaction balance too small.", false); + public static final TransactionResult INSUFFICIENT_FUNDS = new SimpleTransactionResult( + "The source has an insufficient balance to carry out this transaction.", false); private final String message; private final Component component; private final boolean successful; public SimpleTransactionResult(final String message, final boolean successful) { - this(message, Component.text(message, successful ? NamedTextColor.GREEN : NamedTextColor.RED), successful); + this(message, Component.text(message, successful + ? NamedTextColor.GREEN + : NamedTextColor.RED), successful); } public SimpleTransactionResult(final String message, final Component component, final boolean successful) diff --git a/Fossil/src/main/java/me/totalfreedom/fossil/items/ClownfishItem.java b/Fossil/src/main/java/me/totalfreedom/fossil/items/ClownfishItem.java new file mode 100644 index 0000000..0bf1131 --- /dev/null +++ b/Fossil/src/main/java/me/totalfreedom/fossil/items/ClownfishItem.java @@ -0,0 +1,30 @@ +package me.totalfreedom.fossil.items; + +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public final class ClownfishItem extends ShopItem +{ + public ClownfishItem() + { + super(Material.TROPICAL_FISH); + } + + @Override + public void runAction(final @NotNull Player user, final @Nullable Entity target) + { + if (target == null) return; + + final Location location = user.getEyeLocation() + .clone(); + final Vector vector = location.getDirection() + .multiply(2); + + target.setVelocity(vector); + } +} diff --git a/Fossil/src/main/java/me/totalfreedom/fossil/items/ShopItem.java b/Fossil/src/main/java/me/totalfreedom/fossil/items/ShopItem.java new file mode 100644 index 0000000..04f6bb9 --- /dev/null +++ b/Fossil/src/main/java/me/totalfreedom/fossil/items/ShopItem.java @@ -0,0 +1,34 @@ +package me.totalfreedom.fossil.items; + +import org.bukkit.Material; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public abstract class ShopItem +{ + private final ItemStack item; + private final ItemMeta meta; + + protected ShopItem(final Material material) + { + this.item = new ItemStack(material, 1); + + this.meta = this.item.getItemMeta(); + } + + public abstract void runAction(@NotNull final Player user, @Nullable final Entity target); + + public ItemStack getItem() + { + return this.item; + } + + public ItemMeta getMeta() + { + return this.meta; + } +} diff --git a/Fossil/src/main/java/me/totalfreedom/fossil/items/TrailItem.java b/Fossil/src/main/java/me/totalfreedom/fossil/items/TrailItem.java new file mode 100644 index 0000000..ea95de6 --- /dev/null +++ b/Fossil/src/main/java/me/totalfreedom/fossil/items/TrailItem.java @@ -0,0 +1,21 @@ +package me.totalfreedom.fossil.items; + +import org.bukkit.Material; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public final class TrailItem extends ShopItem +{ + public TrailItem() + { + super(Material.LINGERING_POTION); + } + + @Override + public void runAction(final @NotNull Player user, final @Nullable Entity target) + { + + } +} diff --git a/Fossil/src/main/java/me/totalfreedom/fossil/reactions/CopyCatReaction.java b/Fossil/src/main/java/me/totalfreedom/fossil/reactions/CopyCatReaction.java new file mode 100644 index 0000000..52460e8 --- /dev/null +++ b/Fossil/src/main/java/me/totalfreedom/fossil/reactions/CopyCatReaction.java @@ -0,0 +1,61 @@ +package me.totalfreedom.fossil.reactions; + +import me.totalfreedom.display.BossBarDisplay; +import me.totalfreedom.economy.EconomicEntity; +import me.totalfreedom.shop.Reaction; +import me.totalfreedom.shop.ReactionType; +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.bossbar.BossBar; + +import java.util.SplittableRandom; +import java.util.function.Consumer; + +/** + * Represents a single chat reaction that can be performed by a player. + */ +public final class CopyCatReaction extends Reaction +{ + private final long reward; + + public CopyCatReaction(final long reward) + { + super(ReactionType.COPYCAT); + this.reward = reward; + } + + @Override + public long getReward() + { + return reward; + } + + @Override + public void onReact(final Consumer entity) + { + entity.accept(null); + } + + @Override + public void display(final Audience audience) + { + final BossBar bossBar = BossBarDisplay.builder() + .setName(getRandomCharacterString()) + .setProgress(0.0F) + .build(); + } + + public String getRandomCharacterString() + { + final SplittableRandom random = new SplittableRandom(); + final StringBuilder sb = new StringBuilder(10); + + final String chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + + for (int i = 0; i < 10; i++) + { + sb.append(chars.charAt(random.nextInt(chars.length()))); + } + + return sb.toString(); + } +} diff --git a/Fossil/src/main/java/me/totalfreedom/fossil/shop/Shoppe.java b/Fossil/src/main/java/me/totalfreedom/fossil/shop/Shoppe.java new file mode 100644 index 0000000..d586412 --- /dev/null +++ b/Fossil/src/main/java/me/totalfreedom/fossil/shop/Shoppe.java @@ -0,0 +1,5 @@ +package me.totalfreedom.fossil.shop; + +public class Shoppe +{ +} diff --git a/Fossil/src/main/java/me/totalfreedom/fossil/shop/menus/MainMenu.java b/Fossil/src/main/java/me/totalfreedom/fossil/shop/menus/MainMenu.java new file mode 100644 index 0000000..c637c56 --- /dev/null +++ b/Fossil/src/main/java/me/totalfreedom/fossil/shop/menus/MainMenu.java @@ -0,0 +1,11 @@ +package me.totalfreedom.fossil.shop.menus; + +import me.totalfreedom.display.AbstractMenu; + +public final class MainMenu extends AbstractMenu +{ + protected MainMenu(final int size) + { + super(size); + } +} diff --git a/Fossil/src/main/java/me/totalfreedom/fossil/trail/Trailer.java b/Fossil/src/main/java/me/totalfreedom/fossil/trail/Trailer.java new file mode 100644 index 0000000..b530595 --- /dev/null +++ b/Fossil/src/main/java/me/totalfreedom/fossil/trail/Trailer.java @@ -0,0 +1,37 @@ +package me.totalfreedom.fossil.trail; + +import me.totalfreedom.particle.Trail; +import me.totalfreedom.service.Service; + +import java.util.ArrayList; +import java.util.List; + +public class Trailer extends Service +{ + private final List activeTrails = new ArrayList<>(); + + // Cannot be async due to interaction with the world, and API interactions MUST be synchronized. + public Trailer() + { + super("trailer_service"); + } + + public void addTrail(final Trail trail) + { + this.activeTrails.add(trail); + } + + public void removeTrail(final Trail trail) + { + this.activeTrails.remove(trail); + } + + @Override + public void tick() + { + for (final Trail trail : activeTrails) + { + trail.spawnParticle(); + } + } +} diff --git a/Fossil/src/main/java/me/totalfreedom/fossil/trail/types/BasicTrail.java b/Fossil/src/main/java/me/totalfreedom/fossil/trail/types/BasicTrail.java new file mode 100644 index 0000000..6d6ee9b --- /dev/null +++ b/Fossil/src/main/java/me/totalfreedom/fossil/trail/types/BasicTrail.java @@ -0,0 +1,33 @@ +package me.totalfreedom.fossil.trail.types; + +import me.totalfreedom.particle.TrailType; +import org.bukkit.Color; +import org.bukkit.Location; +import org.bukkit.Particle; +import org.bukkit.entity.Player; + +public final class BasicTrail extends SimpleTrail +{ + protected BasicTrail(final Player player) + { + super(player, TrailType.DEFAULT); + super.setColor(Color.RED); + } + + @Override + public void spawnParticle() + { + // Exit immediately if either condition is false. + if (!isActive() || !getAssociatedPlayer().isOnline()) return; + + // Trail is active and the player is online. + final Particle particle = getTrailType().getType(); + final Particle.DustOptions options = new Particle.DustOptions(getColor(), 3); + final Player player = (Player) getAssociatedPlayer(); + final Location location = player.getLocation() + .clone() + .subtract(0, 1, 0); + location.getWorld() + .spawnParticle(particle, location, 1, 0.0, 0.5, 0.0, options); + } +} diff --git a/Fossil/src/main/java/me/totalfreedom/fossil/trail/types/FlameTrail.java b/Fossil/src/main/java/me/totalfreedom/fossil/trail/types/FlameTrail.java new file mode 100644 index 0000000..2fe823e --- /dev/null +++ b/Fossil/src/main/java/me/totalfreedom/fossil/trail/types/FlameTrail.java @@ -0,0 +1,29 @@ +package me.totalfreedom.fossil.trail.types; + +import me.totalfreedom.particle.TrailType; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; + +public final class FlameTrail extends SimpleTrail +{ + public FlameTrail(final Player player) + { + super(player, TrailType.FLAME); + } + + @Override + public void spawnParticle() + { + if (!getAssociatedPlayer().isOnline() || !isActive()) return; + + final Player player = (Player) getAssociatedPlayer(); + final Location location = player.getLocation() + .clone() + .subtract(0, 1, 0); + final Vector direction = location.getDirection(); + location.getWorld() + .spawnParticle(getTrailType().getType(), location, 0, direction.getX(), direction.getY(), + direction.getZ(), 0.1); + } +} diff --git a/Fossil/src/main/java/me/totalfreedom/fossil/trail/types/HeartTrail.java b/Fossil/src/main/java/me/totalfreedom/fossil/trail/types/HeartTrail.java new file mode 100644 index 0000000..968a6ef --- /dev/null +++ b/Fossil/src/main/java/me/totalfreedom/fossil/trail/types/HeartTrail.java @@ -0,0 +1,26 @@ +package me.totalfreedom.fossil.trail.types; + +import me.totalfreedom.particle.TrailType; +import org.bukkit.Location; +import org.bukkit.entity.Player; + +public final class HeartTrail extends SimpleTrail +{ + public HeartTrail(final Player player) + { + super(player, TrailType.HEART); + } + + @Override + public void spawnParticle() + { + if (!getAssociatedPlayer().isOnline() || !isActive()) return; + + final Player player = (Player) getAssociatedPlayer(); + final Location location = player.getLocation() + .clone() + .subtract(0, 1, 0); + location.getWorld() + .spawnParticle(getTrailType().getType(), location, 1, 0.0, 0.5, 0.0); + } +} diff --git a/Fossil/src/main/java/me/totalfreedom/fossil/trail/types/RainbowTrail.java b/Fossil/src/main/java/me/totalfreedom/fossil/trail/types/RainbowTrail.java new file mode 100644 index 0000000..b020349 --- /dev/null +++ b/Fossil/src/main/java/me/totalfreedom/fossil/trail/types/RainbowTrail.java @@ -0,0 +1,46 @@ +package me.totalfreedom.fossil.trail.types; + +import me.totalfreedom.particle.TrailType; +import me.totalfreedom.utils.InterpolationUtils; +import org.bukkit.Color; +import org.bukkit.Location; +import org.bukkit.Particle; +import org.bukkit.entity.Player; + +import java.util.Iterator; + +public final class RainbowTrail extends SimpleTrail +{ + private Iterator currentColor; + + protected RainbowTrail(final Player player) + { + super(player, TrailType.DEFAULT); + setColors(InterpolationUtils.rainbow(40 % 7)); + this.currentColor = getColors().iterator(); + } + + @Override + public void spawnParticle() + { + // Exit immediately if either case is false. + if (!isActive() || !getAssociatedPlayer().isOnline()) return; + + // Re-initialize the color iterator if the iterator has previously reached the end of its index. + if (!currentColor.hasNext()) + { + this.currentColor = getColors().iterator(); + } + + final Color color = currentColor.next(); + final Player player = (Player) getAssociatedPlayer(); + final Particle particle = getTrailType().getType(); + final Particle.DustOptions options = new Particle.DustOptions(color, 3F); + final Location location = player.getLocation() + .clone() + .subtract(0, 1, 0); + + location.getWorld() + .spawnParticle(particle, location, 1, 0.0, 0.5, 0.0, options); + } +} \ No newline at end of file diff --git a/Fossil/src/main/java/me/totalfreedom/fossil/trail/types/SimpleTrail.java b/Fossil/src/main/java/me/totalfreedom/fossil/trail/types/SimpleTrail.java new file mode 100644 index 0000000..6988ba5 --- /dev/null +++ b/Fossil/src/main/java/me/totalfreedom/fossil/trail/types/SimpleTrail.java @@ -0,0 +1,91 @@ +package me.totalfreedom.fossil.trail.types; + +import me.totalfreedom.particle.Trail; +import me.totalfreedom.particle.TrailType; +import org.bukkit.Bukkit; +import org.bukkit.Color; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Set; +import java.util.UUID; + +public abstract class SimpleTrail implements Trail +{ + private final UUID associatedPlayerUUID; + private final TrailType trailType; + + private Color staticColor = null; + private Set gradientColor = null; + private boolean active = false; + + protected SimpleTrail(final Player player, final TrailType trailType) + { + this.associatedPlayerUUID = player.getUniqueId(); + this.trailType = trailType; + } + + @Override + public @NotNull UUID getAssociatedPlayerUUID() + { + return associatedPlayerUUID; + } + + @Override + public @NotNull OfflinePlayer getAssociatedPlayer() + { + return Bukkit.getOfflinePlayer(getAssociatedPlayerUUID()); + } + + @Override + public @NotNull TrailType getTrailType() + { + return trailType; + } + + @Override + public @Nullable Color getColor() + { + return staticColor; + } + + @Override + public void setColor(@NotNull final Color color) + { + this.gradientColor = null; + this.staticColor = color; + } + + @Override + public @Nullable Set getColors() + { + return this.gradientColor; + } + + @Override + public void setColors(@NotNull final Set colors) + { + this.staticColor = null; + this.gradientColor = colors; + } + + @Override + public boolean isGradient() + { + return gradientColor != null; + } + + @Override + public boolean isActive() + { + return active; + } + + @Override + public void setActive(final boolean active) + { + this.active = active; + } +} diff --git a/Fossil/src/main/java/me/totalfreedom/fossil/trail/types/StrobeTrail.java b/Fossil/src/main/java/me/totalfreedom/fossil/trail/types/StrobeTrail.java new file mode 100644 index 0000000..1e444c5 --- /dev/null +++ b/Fossil/src/main/java/me/totalfreedom/fossil/trail/types/StrobeTrail.java @@ -0,0 +1,31 @@ +package me.totalfreedom.fossil.trail.types; + +import me.totalfreedom.particle.TrailType; +import org.bukkit.Color; +import org.bukkit.Location; +import org.bukkit.Particle; +import org.bukkit.entity.Player; + +public final class StrobeTrail extends SimpleTrail +{ + private final Particle.DustTransition dustTransition; + + public StrobeTrail(final Player player, final Color from, final Color to) + { + super(player, TrailType.STROBE); + this.dustTransition = new Particle.DustTransition(from, to, 3F); + } + + @Override + public void spawnParticle() + { + if (!getAssociatedPlayer().isOnline() || !isActive()) return; + + final Player player = (Player) getAssociatedPlayer(); + final Location location = player.getLocation() + .clone() + .subtract(0, 1, 0); + location.getWorld() + .spawnParticle(getTrailType().getType(), location, 1, 0.0, 0.5, 0.0, dustTransition); + } +} diff --git a/Fossil/src/main/java/me/totalfreedom/fossil/trail/types/TrailProvider.java b/Fossil/src/main/java/me/totalfreedom/fossil/trail/types/TrailProvider.java new file mode 100644 index 0000000..1110266 --- /dev/null +++ b/Fossil/src/main/java/me/totalfreedom/fossil/trail/types/TrailProvider.java @@ -0,0 +1,12 @@ +package me.totalfreedom.fossil.trail.types; + +import org.bukkit.entity.Player; + +public final class TrailProvider +{ + public BasicTrail basicTrail(final Player player) + { + return new BasicTrail(player); + } + +} diff --git a/Patchwork/src/main/java/me/totalfreedom/api/Context.java b/Patchwork/src/main/java/me/totalfreedom/api/Context.java index 2a837d7..321cca6 100644 --- a/Patchwork/src/main/java/me/totalfreedom/api/Context.java +++ b/Patchwork/src/main/java/me/totalfreedom/api/Context.java @@ -1,5 +1,6 @@ package me.totalfreedom.api; +import me.totalfreedom.provider.ContextProvider; import net.kyori.adventure.text.Component; import org.bukkit.Location; import org.bukkit.World; @@ -13,16 +14,40 @@ import org.jetbrains.annotations.Nullable; import java.util.function.Function; +/** + * Represents an object context. This class is a simple generic type wrapper that can be used to ensure data types. This + * class is also used to provide a simple way to map data types. + * + * @param The type of the context. + * @see ContextProvider + */ @FunctionalInterface public interface Context { - T get(); - + /** + * Maps the context to another context. + * + * @param mapper The mapper function. + * @param The type of the mapped context. + * @return The mapped context. + */ default Context map(@NotNull final Function mapper) { return () -> mapper.apply(get()); } + /** + * Gets the context. + * + * @return The context. + */ + T get(); + + /** + * Gets the context as a string. + * + * @return The context as a string. + */ default @Nullable String asString() { if (get() instanceof String string) @@ -34,6 +59,11 @@ public interface Context } } + /** + * Gets the context as a boolean. + * + * @return The context as a boolean. + */ default @Nullable Boolean asBoolean() { if (get() instanceof Boolean bool) @@ -45,6 +75,9 @@ public interface Context } } + /** + * @return The context as a {@link Double}. + */ default @Nullable Double asDouble() { if (get() instanceof Double doub) @@ -56,30 +89,51 @@ public interface Context } } - default @Nullable Integer asInt() { - if (get() instanceof Integer integer) { + /** + * @return The context as a {@link Integer}. + */ + default @Nullable Integer asInt() + { + if (get() instanceof Integer integer) + { return integer; - } else { + } else + { return null; } } - default @Nullable Long asLong() { - if (get() instanceof Long longg) { + /** + * @return The context as a {@link Byte}. + */ + default @Nullable Long asLong() + { + if (get() instanceof Long longg) + { return longg; - } else { + } else + { return null; } } - default @Nullable Float asFloat() { - if (get() instanceof Float floatt) { + /** + * @return The context as a {@link Float}. + */ + default @Nullable Float asFloat() + { + if (get() instanceof Float floatt) + { return floatt; - } else { + } else + { return null; } } + /** + * @return The context as a {@link Player}. + */ default @Nullable Player asPlayer() { if (get() instanceof Player player) @@ -91,6 +145,9 @@ public interface Context } } + /** + * @return The context as a {@link CommandSender}. + */ default @Nullable CommandSender asCommandSender() { if (get() instanceof CommandSender commandSender) @@ -102,11 +159,19 @@ public interface Context } } + /** + * This is the same as calling {@link #get()} and then calling {@link Object#toString()} on the result. + * + * @return The context as a {@link String} literal. + */ default @NotNull String literal() { return get().toString(); } + /** + * @return The context as a {@link World}. + */ default @Nullable World asWorld() { if (get() instanceof World world) @@ -118,6 +183,9 @@ public interface Context } } + /** + * @return The context as a {@link Location}. + */ default @Nullable Location asLocation() { if (get() instanceof Location location) @@ -129,6 +197,9 @@ public interface Context } } + /** + * @return The context as a {@link LivingEntity}. + */ default @Nullable LivingEntity asLivingEntity() { if (get() instanceof LivingEntity livingEntity) @@ -140,6 +211,9 @@ public interface Context } } + /** + * @return The context as a {@link Component}. + */ default @Nullable Component asComponent() { if (get() instanceof Component component) @@ -151,6 +225,9 @@ public interface Context } } + /** + * @return The context as a {@link Projectile}. + */ default @Nullable Projectile asProjectile() { if (get() instanceof Projectile projectile) @@ -162,6 +239,9 @@ public interface Context } } + /** + * @return The context as an {@link Action}. + */ default @Nullable Action asAction() { if (get() instanceof Action action) @@ -173,6 +253,22 @@ public interface Context } } + /** + * Gets the context as a custom class. This will cast the object to the class if it is an instance of it. + *
+ * Typically, Context objects are useful when used to collect unknown data and then cast it to a known type. + *
+ * In the case where we know what the data should be but the compiler or the runtime does not, the object is wrapped + * in a Context which then exposes multiple methods to get the data as one of the known types. + *

+ * For example, if we have a Context<Object> and we already know that the wrapped object should be of type X, + * we can use X.class on this method to retrieve the actual object. That is, to say, if there is not + * already a predefined method to get the object as the type we want. + * + * @param clazz + * @param + * @return + */ default @Nullable U asCustom(Class clazz) { if (clazz.isInstance(get())) diff --git a/Patchwork/src/main/java/me/totalfreedom/api/Interpolator.java b/Patchwork/src/main/java/me/totalfreedom/api/Interpolator.java new file mode 100644 index 0000000..0a6c598 --- /dev/null +++ b/Patchwork/src/main/java/me/totalfreedom/api/Interpolator.java @@ -0,0 +1,21 @@ +package me.totalfreedom.api; + +/** + * Interpolates a range of values and returns the results in a {@link Double} array. + *
+ * This is a functional interface, to allow for lambda expressions, but also for anonymous custom interpolation + * implementations. + */ +@FunctionalInterface +public interface Interpolator +{ + /** + * Interpolates a range of values and returns the results in a {@link Double} array. + * + * @param from The starting value. + * @param to The ending value. + * @param max The number of values to interpolate. + * @return The interpolated values. + */ + double[] interpolate(final double from, final double to, final int max); +} diff --git a/Patchwork/src/main/java/me/totalfreedom/api/Serializable.java b/Patchwork/src/main/java/me/totalfreedom/api/Serializable.java index 499841b..3dc62a6 100644 --- a/Patchwork/src/main/java/me/totalfreedom/api/Serializable.java +++ b/Patchwork/src/main/java/me/totalfreedom/api/Serializable.java @@ -1,15 +1,27 @@ package me.totalfreedom.api; +/** + * This interface represents a Serializable object. Objects which require custom serialization and cannot simply + * override or call the default {@link Object#toString()} method should implement this interface. + * + * @param The type of object to serialize + */ public interface Serializable { /** - * Serialize an object to a string. - * Ideally, this should serialize to an SQL query for easy data transfer. + * Serialize an object to a string. Ideally, this should serialize to an SQL query for easy data transfer. * * @param object The object to serialize * @return The serialized object */ String serialize(T object); - T deserialize(Context... contexts); + /** + * Deserialize an object from a Serialized string.. + * + * @param serializedObject The serialized object + * @return The deserialized object + */ + + T deserialize(String serializedObject); } diff --git a/Patchwork/src/main/java/me/totalfreedom/audience/MutableAudienceForwarder.java b/Patchwork/src/main/java/me/totalfreedom/audience/MutableAudienceForwarder.java index 7ade549..f725d73 100644 --- a/Patchwork/src/main/java/me/totalfreedom/audience/MutableAudienceForwarder.java +++ b/Patchwork/src/main/java/me/totalfreedom/audience/MutableAudienceForwarder.java @@ -19,15 +19,25 @@ import java.util.function.Consumer; import java.util.function.Predicate; /** - * A replacement for {@link net.kyori.adventure.audience.ForwardingAudience} that allows for audiences to be removed & added at will. Not thread safe. + * A replacement for {@link net.kyori.adventure.audience.ForwardingAudience} that allows for audiences to be removed & + * added at will. Not thread safe. *

* This is intended for use in toggleable logging systems, for example, potion spy. */ // TODO: Work on thread-safety (or thread-safe alternative) public class MutableAudienceForwarder implements Audience { + /** + * The audiences that this forwards to. + */ private final Set audiences = new HashSet<>(); + /** + * Creates a new {@link MutableAudienceForwarder} with the given audiences. + * + * @param audiences The audiences to forward to. + * @return The new {@link MutableAudienceForwarder}. + */ public static MutableAudienceForwarder from(final Audience... audiences) { final MutableAudienceForwarder audienceForwarder = new MutableAudienceForwarder(); @@ -40,6 +50,11 @@ public class MutableAudienceForwarder implements Audience return audienceForwarder; } + /** + * Adds an audience to this forwarder. + * + * @param audience The audience to add. + */ public void addAudience(final Audience audience) { if (audiences.contains(audience) || audience == this /* Protect against honest self-referential calls */) @@ -50,67 +65,129 @@ public class MutableAudienceForwarder implements Audience audiences.add(audience); } + /** + * Removes an audience from this forwarder. + * + * @param audience The audience to remove. + * @return Whether the audience was removed. + */ public boolean removeAudience(final Audience audience) { return audiences.remove(audience); } - + /** + * Filters the audiences in the stream by the given predicate. + * + * @param filter a filter that determines if an audience should be included + * @return The first Audience found that matches the filter. + */ @Override public @NotNull Audience filterAudience(@NotNull final Predicate filter) { return audiences.stream() - .filter(filter) - .findFirst() - .orElseThrow(); + .filter(filter) + .findFirst() + .orElseThrow(); } + /** + * Applies a consumer to each audience in the stream. + * + * @param action the action to apply. + */ @Override public void forEachAudience(@NotNull final Consumer action) { audiences.forEach(action); } + /** + * Sends a {@link ComponentLike} to every audience within the stream. + * + * @param message The message to send. + * @see Audience#sendMessage(ComponentLike) + * @see #forEachAudience(Consumer) + */ @Override public void sendMessage(@NotNull final ComponentLike message) { - audiences.forEach(a -> a.sendMessage(message)); + forEachAudience(a -> a.sendMessage(message)); } + /** + * Sends a {@link Component} to every audience within the stream. + * + * @param message The message to send + * @see Audience#sendMessage(Component) + * @see #forEachAudience(Consumer) + */ @Override public void sendMessage(@NotNull final Component message) { - audiences.forEach(a -> a.sendMessage(message)); + forEachAudience(a -> a.sendMessage(message)); } + /** + * Sends a {@link SignedMessage} to every audience within the stream. + * + * @param message the component content of the message + * @param boundChatType the bound chat type of the message + * @see Audience#sendMessage(Component, ChatType.Bound) + * @see #forEachAudience(Consumer) + */ @Override public void sendMessage(@NotNull final Component message, final ChatType.@NotNull Bound boundChatType) { - audiences.forEach(a -> a.sendMessage(message, boundChatType)); + forEachAudience(a -> a.sendMessage(message, boundChatType)); } + /** + * Sends a {@link SignedMessage} to every audience within the stream. + * + * @param message the component content of the message + * @param boundChatType the bound chat type of the message + * @see Audience#sendMessage(ComponentLike, ChatType.Bound) + * @see #forEachAudience(Consumer) + */ @Override public void sendMessage(@NotNull final ComponentLike message, final ChatType.@NotNull Bound boundChatType) { - audiences.forEach(a -> a.sendMessage(message, boundChatType)); + forEachAudience(a -> a.sendMessage(message, boundChatType)); } + /** + * Sends a {@link SignedMessage} to every audience within the stream. + * + * @param signedMessage the signed message data to send + * @param boundChatType the bound chat type of the message + */ @Override public void sendMessage(@NotNull final SignedMessage signedMessage, final ChatType.@NotNull Bound boundChatType) { - audiences.forEach(a -> a.sendMessage(signedMessage, boundChatType)); + forEachAudience(a -> a.sendMessage(signedMessage, boundChatType)); } + /** + * Deletes a signed message from the audiences chat. + * + * @param signedMessage the message to delete + */ @Override public void deleteMessage(@NotNull final SignedMessage signedMessage) { - audiences.forEach(a -> a.deleteMessage(signedMessage)); + forEachAudience(a -> a.deleteMessage(signedMessage)); } + /** + * Deletes a signed message from the audiences chat using the provided chat signature. + * + * @param signature the signature associated with the message to delete. + */ @Override public void deleteMessage(final SignedMessage.@NotNull Signature signature) { - audiences.forEach(a -> a.deleteMessage(signature)); + forEachAudience(a -> a.deleteMessage(signature)); } // The methods below here will (probably) never be used, however it's good to keep them for completeness' sake. @@ -118,127 +195,127 @@ public class MutableAudienceForwarder implements Audience @Override public void sendActionBar(@NotNull final ComponentLike message) { - audiences.forEach(a -> a.sendActionBar(message)); + forEachAudience(a -> a.sendActionBar(message)); } @Override public void sendActionBar(@NotNull final Component message) { - audiences.forEach(a -> a.sendActionBar(message)); + forEachAudience(a -> a.sendActionBar(message)); } @Override public void sendPlayerListHeader(@NotNull final ComponentLike header) { - audiences.forEach(a -> a.sendPlayerListHeader(header)); + forEachAudience(a -> a.sendPlayerListHeader(header)); } @Override public void sendPlayerListHeader(@NotNull final Component header) { - audiences.forEach(a -> a.sendPlayerListHeader(header)); + forEachAudience(a -> a.sendPlayerListHeader(header)); } @Override public void sendPlayerListFooter(@NotNull final ComponentLike footer) { - audiences.forEach(a -> a.sendPlayerListFooter(footer)); + forEachAudience(a -> a.sendPlayerListFooter(footer)); } @Override public void sendPlayerListFooter(@NotNull final Component footer) { - audiences.forEach(a -> a.sendPlayerListFooter(footer)); + forEachAudience(a -> a.sendPlayerListFooter(footer)); } @Override public void sendPlayerListHeaderAndFooter(@NotNull final ComponentLike header, @NotNull final ComponentLike footer) { - audiences.forEach(a -> a.sendPlayerListHeaderAndFooter(header, footer)); + forEachAudience(a -> a.sendPlayerListHeaderAndFooter(header, footer)); } @Override public void sendPlayerListHeaderAndFooter(@NotNull final Component header, @NotNull final Component footer) { - audiences.forEach(a -> a.sendPlayerListHeaderAndFooter(header, footer)); + forEachAudience(a -> a.sendPlayerListHeaderAndFooter(header, footer)); } @Override public void showTitle(@NotNull final Title title) { - audiences.forEach(a -> a.showTitle(title)); + forEachAudience(a -> a.showTitle(title)); } @Override public void sendTitlePart(@NotNull final TitlePart part, @NotNull final T value) { - audiences.forEach(a -> a.sendTitlePart(part, value)); + forEachAudience(a -> a.sendTitlePart(part, value)); } @Override public void clearTitle() { - audiences.forEach(Audience::clearTitle); + forEachAudience(Audience::clearTitle); } @Override public void resetTitle() { - audiences.forEach(Audience::resetTitle); + forEachAudience(Audience::resetTitle); } @Override public void showBossBar(@NotNull final BossBar bar) { - audiences.forEach(a -> a.showBossBar(bar)); + forEachAudience(a -> a.showBossBar(bar)); } @Override public void hideBossBar(@NotNull final BossBar bar) { - audiences.forEach(a -> a.hideBossBar(bar)); + forEachAudience(a -> a.hideBossBar(bar)); } @Override public void playSound(@NotNull final Sound sound) { - audiences.forEach(a -> a.playSound(sound)); + forEachAudience(a -> a.playSound(sound)); } @Override public void playSound(@NotNull final Sound sound, final double x, final double y, final double z) { - audiences.forEach(a -> a.playSound(sound, x, y, z)); + forEachAudience(a -> a.playSound(sound, x, y, z)); } @Override public void playSound(@NotNull final Sound sound, final Sound.@NotNull Emitter emitter) { - audiences.forEach(a -> a.playSound(sound, emitter)); + forEachAudience(a -> a.playSound(sound, emitter)); } @Override public void stopSound(@NotNull final Sound sound) { - audiences.forEach(a -> a.stopSound(sound)); + forEachAudience(a -> a.stopSound(sound)); } @Override public void stopSound(@NotNull final SoundStop stop) { - audiences.forEach(a -> a.stopSound(stop)); + forEachAudience(a -> a.stopSound(stop)); } @Override public void openBook(final Book.@NotNull Builder book) { - audiences.forEach(a -> a.openBook(book)); + forEachAudience(a -> a.openBook(book)); } @Override public void openBook(@NotNull final Book book) { - audiences.forEach(a -> a.openBook(book)); + forEachAudience(a -> a.openBook(book)); } } diff --git a/Patchwork/src/main/java/me/totalfreedom/base/CommonsBase.java b/Patchwork/src/main/java/me/totalfreedom/base/CommonsBase.java index 999c940..69bade0 100644 --- a/Patchwork/src/main/java/me/totalfreedom/base/CommonsBase.java +++ b/Patchwork/src/main/java/me/totalfreedom/base/CommonsBase.java @@ -2,46 +2,89 @@ package me.totalfreedom.base; import me.totalfreedom.event.EventBus; import me.totalfreedom.service.FreedomExecutor; +import me.totalfreedom.service.SubscriptionProvider; +import org.bukkit.Bukkit; import org.bukkit.plugin.java.JavaPlugin; +/** + * The base class for Patchwork. + */ public class CommonsBase extends JavaPlugin { + /** + * The {@link EventBus} for this plugin. + */ private final EventBus eventBus = new EventBus(this); + /** + * The {@link Registration} object for this plugin. + */ private final Registration registration = new Registration(); + /** + * The {@link FreedomExecutor} for this plugin. + */ private final FreedomExecutor executor = new FreedomExecutor(); + /** + * Provides this plugin instance through a safe static method. This is effectively the same thing as using + * {@link JavaPlugin#getPlugin(Class)} + * + * @return the plugin instance + */ public static CommonsBase getInstance() { return JavaPlugin.getPlugin(CommonsBase.class); } @Override - public void onEnable() + public void onDisable() { - getRegistrations().getServiceRegistry().register(this, eventBus); - getExecutor().getSync() - .execute(() -> getRegistrations() - .getServiceRegistry() - .startAll()); + Bukkit.getScheduler() + .runTaskLater(this, () -> getRegistrations() + .getServiceTaskRegistry() + .stopAllServices(), 1L); + + getRegistrations().getServiceTaskRegistry() + .unregisterService(EventBus.class); } @Override - public void onDisable() + public void onEnable() { - getRegistrations().getServiceRegistry().stopAll(); - getRegistrations().getServiceRegistry().unregister(EventBus.class, eventBus); - } - - public Registration getRegistrations() - { - return registration; + getRegistrations().getServiceTaskRegistry() + .registerService(SubscriptionProvider.asyncService(this, eventBus)); + getExecutor().getSync() + .execute(() -> getRegistrations() + .getServiceTaskRegistry() + .startAllServices()); } + /** + * Gets the {@link FreedomExecutor} for this plugin. + * + * @return the {@link FreedomExecutor} + */ public FreedomExecutor getExecutor() { return executor; } + /** + * Get's the Registration object for this plugin. This object contains every registry class for the various features + * provided by this plugin. + * + * @return the Registration object + */ + public Registration getRegistrations() + { + return registration; + } + + /** + * Gets the {@link EventBus} for this plugin. The EventBus is used to register and listen to custom events provided + * by Freedom Network Suite. + * + * @return the {@link EventBus} + */ public EventBus getEventBus() { return eventBus; diff --git a/Patchwork/src/main/java/me/totalfreedom/base/Registration.java b/Patchwork/src/main/java/me/totalfreedom/base/Registration.java index 03c4dd3..e697fe9 100644 --- a/Patchwork/src/main/java/me/totalfreedom/base/Registration.java +++ b/Patchwork/src/main/java/me/totalfreedom/base/Registration.java @@ -1,64 +1,102 @@ package me.totalfreedom.base; -import me.totalfreedom.data.GroupRegistry; -import me.totalfreedom.data.BanRegistry; import me.totalfreedom.data.ConfigRegistry; -import me.totalfreedom.data.ModuleRegistry; -import me.totalfreedom.data.ServiceRegistry; -import me.totalfreedom.data.UserRegistry; import me.totalfreedom.data.EventRegistry; +import me.totalfreedom.data.GroupRegistry; +import me.totalfreedom.data.ModuleRegistry; +import me.totalfreedom.data.ServiceTaskRegistry; +import me.totalfreedom.data.UserRegistry; +/** + * This class is a holder for each registry in the data package. + *
+ * Registries such as {@link ModuleRegistry} and {@link ServiceTaskRegistry} can be found as final objects in this + * class. These registries should only ever be accessed through the single Registration object in CommonsBase using + * {@link CommonsBase#getRegistrations()} + */ public class Registration { + /** + * The {@link EventRegistry} + */ private final EventRegistry eventRegistry; + /** + * The {@link UserRegistry} + */ private final UserRegistry userRegistry; - private final ServiceRegistry serviceRegistry; + /** + * The {@link ServiceTaskRegistry} + */ + private final ServiceTaskRegistry serviceTaskRegistry; + /** + * The {@link ModuleRegistry} + */ private final ModuleRegistry moduleRegistry; + /** + * The {@link GroupRegistry} + */ private final GroupRegistry groupRegistry; - private final BanRegistry banRegistry; + /** + * The {@link ConfigRegistry} + */ private final ConfigRegistry configRegistry; - public Registration() + /** + * Constructs a new Registration object and initializes all registries. + */ + Registration() { this.eventRegistry = new EventRegistry(); this.userRegistry = new UserRegistry(); - this.serviceRegistry = new ServiceRegistry(); + this.serviceTaskRegistry = new ServiceTaskRegistry(); this.moduleRegistry = new ModuleRegistry(); this.groupRegistry = new GroupRegistry(); - this.banRegistry = new BanRegistry(); - this.configRegistry = new ConfigRegistry(); + this.configRegistry = new ConfigRegistry(); } + /** + * @return The {@link ModuleRegistry} + */ public ModuleRegistry getModuleRegistry() { return moduleRegistry; } + /** + * @return The {@link EventRegistry} + */ public EventRegistry getEventRegistry() { return eventRegistry; } + /** + * @return The {@link UserRegistry} + */ public UserRegistry getUserRegistry() { return userRegistry; } - public ServiceRegistry getServiceRegistry() + /** + * @return The {@link ServiceTaskRegistry} + */ + public ServiceTaskRegistry getServiceTaskRegistry() { - return serviceRegistry; + return serviceTaskRegistry; } + /** + * @return The {@link GroupRegistry} + */ public GroupRegistry getGroupRegistry() { return groupRegistry; } - public BanRegistry getBanRegistry() - { - return banRegistry; - } - + /** + * @return The {@link ConfigRegistry} + */ public ConfigRegistry getConfigRegistry() { return configRegistry; diff --git a/Patchwork/src/main/java/me/totalfreedom/command/BukkitDelegate.java b/Patchwork/src/main/java/me/totalfreedom/command/BukkitDelegate.java new file mode 100644 index 0000000..0a740d6 --- /dev/null +++ b/Patchwork/src/main/java/me/totalfreedom/command/BukkitDelegate.java @@ -0,0 +1,202 @@ +package me.totalfreedom.command; + +import me.totalfreedom.api.Context; +import me.totalfreedom.command.annotation.Completion; +import me.totalfreedom.command.annotation.Subcommand; +import me.totalfreedom.provider.ContextProvider; +import me.totalfreedom.utils.logging.FreedomLogger; +import net.kyori.adventure.text.Component; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.command.PluginIdentifiableCommand; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +/** + * This class is acts as a delegate between our custom command implementation and the Bukkit API. + *
+ * This class is not meant to be used directly, and is only public to allow for the Bukkit API to access it. As a + * result, this file will remain undocumented. + * + *
+ * This class is not thread-safe. + *
+ * This class is not meant to be extended. + *
+ * This class is not meant to be instantiated. + *
+ * This class is not meant to be used outside Patchwork. + *
+ */ +public final class BukkitDelegate extends Command implements PluginIdentifiableCommand +{ + private final JavaPlugin plugin; + private final Commander command; + private final boolean noConsole; + + BukkitDelegate(final Commander command) + { + super(command.getInfo() + .name()); + this.command = command; + this.plugin = command.getPlugin(); + this.setDescription(command.getInfo() + .description()); + this.setUsage(command.getInfo() + .usage()); + this.setPermission(command.getPerms() + .perm()); + this.setAliases(Arrays.asList(command.getInfo() + .aliases())); + this.permissionMessage(Component.text(command.getPerms() + .noPerms())); + this.noConsole = command.getPerms() + .onlyPlayers(); + } + + @Override + public boolean execute(@NotNull final CommandSender sender, + @NotNull final String commandLabel, + @NotNull final String[] args) + { + if (sender instanceof ConsoleCommandSender && noConsole) + { + sender.sendMessage(Component.text("This command can only be run by players.")); + return true; + } + + if (getPermission() != null && !sender.hasPermission(getPermission())) + { + Component permissionMessage = permissionMessage(); + if (permissionMessage == null) + permissionMessage = Component.text("You do not have permission to use this command."); + sender.sendMessage(permissionMessage); + return true; + } + + if (args.length > 0) + { + final ContextProvider provider = new ContextProvider(); + final Set nodes = command.getSubcommands() + .keySet(); + for (final Subcommand node : nodes) + { + processSubCommands(args, sender, provider, node); + } + + return true; + } + + if (command.getBaseMethod() != null) + { + try + { + command.getBaseMethod() + .invoke(command, sender); + } catch (Exception ex) + { + FreedomLogger.getLogger("Patchwork") + .error(ex); + } + + return true; + } + + return false; + } + + private void processSubCommands(final @NotNull String @NotNull [] args, + final CommandSender sender, final ContextProvider provider, + final Subcommand node) + { + final Class[] argTypes = node.args(); + if (argTypes.length != args.length) + return; + + final Object[] objects = new Object[argTypes.length + 1]; + + for (int i = 0; i < argTypes.length; i++) + { + final Class argType = argTypes[i]; + final String arg = args[i]; + + if (argType == String.class) + continue; + + final Context context = () -> provider.fromString(arg, argType); + objects[i] = context.get(); + } + try + { + command.getSubcommands() + .get(node) + .invoke(command, sender, objects); + } catch (Exception ex) + { + FreedomLogger.getLogger("Patchwork") + .error(ex); + } + } + + @Override + public List tabComplete(final CommandSender sender, final String alias, final String[] args) + { + final Set completions = command.getCompletions(); + final List results = new ArrayList<>(); + for (final Completion completion : completions) + { + if (completion.index() != args.length) + { + continue; + } + + for (final String p : completion.args()) + { + switch (p) + { + case "%player%" -> results.addAll(Bukkit.getOnlinePlayers() + .stream() + .map(Player::getName) + .toList()); + case "%world%" -> results.addAll(Bukkit.getWorlds() + .stream() + .map(World::getName) + .toList()); + case "%number%" -> results.addAll(List.of( + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9")); + case "%location%" -> results.add("world,x,y,z"); + default -> results.add(p); + } + } + } + + return results.stream() + .filter(s -> s.startsWith(args[args.length - 1])) + .toList(); + } + + @Override + public @NotNull Plugin getPlugin() + { + return this.plugin; + } +} diff --git a/Patchwork/src/main/java/me/totalfreedom/command/BukkitDelegator.java b/Patchwork/src/main/java/me/totalfreedom/command/BukkitDelegator.java deleted file mode 100644 index 93ca292..0000000 --- a/Patchwork/src/main/java/me/totalfreedom/command/BukkitDelegator.java +++ /dev/null @@ -1,171 +0,0 @@ -package me.totalfreedom.command; - -import me.totalfreedom.api.Context; -import me.totalfreedom.command.annotation.Completion; -import me.totalfreedom.command.annotation.Subcommand; -import me.totalfreedom.provider.ContextProvider; -import me.totalfreedom.utils.FreedomLogger; -import net.kyori.adventure.text.Component; -import org.bukkit.Bukkit; -import org.bukkit.World; -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; -import org.bukkit.command.ConsoleCommandSender; -import org.bukkit.command.PluginIdentifiableCommand; -import org.bukkit.entity.Player; -import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.java.JavaPlugin; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Set; - -public class BukkitDelegator extends Command implements PluginIdentifiableCommand -{ - private final JavaPlugin plugin; - private final CommandBase command; - private final boolean noConsole; - - BukkitDelegator(final JavaPlugin plugin, final CommandBase command) - { - super(command.getInfo().name()); - this.command = command; - this.plugin = command.getPlugin(); - this.setDescription(command.getInfo().description()); - this.setUsage(command.getInfo().usage()); - this.setPermission(command.getPerms().perm()); - this.setAliases(Arrays.asList(command.getInfo().aliases())); - this.permissionMessage(Component.text(command.getPerms().noPerms())); - this.noConsole = command.getPerms().onlyPlayers(); - } - - @Override - public boolean execute(@NotNull final CommandSender sender, - @NotNull final String commandLabel, - @NotNull final String[] args) - { - if (commandLabel.isEmpty() || !commandLabel.equalsIgnoreCase(getName())) - return false; - - if (sender instanceof ConsoleCommandSender && noConsole) - { - sender.sendMessage(Component.text("This command can only be run by players.")); - return true; - } - - if (getPermission() != null && !sender.hasPermission(getPermission())) - { - Component permissionMessage = permissionMessage(); - if (permissionMessage == null) - permissionMessage = Component.text("You do not have permission to use this command."); - sender.sendMessage(permissionMessage); - return true; - } - - if (args.length > 0) - { - final ContextProvider provider = new ContextProvider(); - final Set nodes = command.getSubcommands().keySet(); - for (final Subcommand node : nodes) - { - final Class[] argTypes = node.args(); - if (argTypes.length != args.length) - continue; - - Object[] objects = new Object[0]; - - for (int i = 0; i < argTypes.length; i++) - { - final Class argType = argTypes[i]; - final String arg = args[i]; - if (argType == String.class) - continue; - - final Context context = () -> provider.fromString(arg); - if (!argType.isInstance(context.get())) - { - throw new IllegalStateException(); // TODO: Change this. - } - objects = Arrays.copyOf(objects, objects.length + 1); - objects[objects.length - 1] = context.get(); - } - try - { - command.getSubcommands().get(node).invoke(command, objects); - } catch (Exception ex) - { - FreedomLogger.getLogger("Patchwork") - .error(ex); - } - } - - return false; - } - - if (command.getBaseMethodPair() != null) - { - try - { - command.getBaseMethodPair().value().invoke(command, sender); - } catch (Exception ex) - { - FreedomLogger.getLogger("Patchwork") - .error(ex); - } - } - - return true; - } - - @Override - public List tabComplete(final CommandSender sender, final String alias, final String[] args) - { - final Set completions = command.getCompletions(); - final List results = new ArrayList<>(); - for (final Completion completion : completions) - { - if (completion.index() != args.length) - { - continue; - } - - for (final String p : completion.args()) - { - switch (p) - { - case "%player%" -> results.addAll(Bukkit.getOnlinePlayers() - .stream() - .map(Player::getName) - .toList()); - case "%world%" -> results.addAll(Bukkit.getWorlds() - .stream() - .map(World::getName) - .toList()); - case "%number%" -> results.addAll(List.of( - "0", - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9")); - case "%location%" -> results.add("world,x,y,z"); - default -> results.add(p); - } - } - } - - return results.stream().filter(s -> s.startsWith(args[args.length - 1])).toList(); - } - - @Override - public @NotNull Plugin getPlugin() - { - return this.plugin; - } -} diff --git a/Patchwork/src/main/java/me/totalfreedom/command/CommandBase.java b/Patchwork/src/main/java/me/totalfreedom/command/CommandBase.java deleted file mode 100644 index 3e0accc..0000000 --- a/Patchwork/src/main/java/me/totalfreedom/command/CommandBase.java +++ /dev/null @@ -1,89 +0,0 @@ -package me.totalfreedom.command; - -import me.totalfreedom.command.annotation.*; -import me.totalfreedom.utils.Pair; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.NamedTextColor; -import org.bukkit.command.CommandSender; -import org.bukkit.plugin.java.JavaPlugin; - -import java.lang.reflect.Method; -import java.util.*; -import java.util.stream.Stream; - -public abstract class CommandBase -{ - private final JavaPlugin plugin; - private final Info info; - private final Permissive perms; - private final Map subcommands; - private final Set completions; - private final Pair baseMethodPair; - - protected CommandBase(final JavaPlugin plugin) - { - this.info = this.getClass().getDeclaredAnnotation(Info.class); - this.perms = this.getClass().getDeclaredAnnotation(Permissive.class); - this.plugin = plugin; - this.subcommands = new HashMap<>(); - this.completions = new HashSet<>(); - - if (this.getClass().isAnnotationPresent(Base.class)) - { - final Method method = Stream.of(this.getClass().getDeclaredMethods()) - .filter(m -> m.isAnnotationPresent(Base.class)) - .findFirst() - .orElseThrow(() -> new RuntimeException("Base annotation present but no method found.")); - - this.baseMethodPair = new Pair<>(method.getDeclaredAnnotation(Base.class), method); - } else - { - this.baseMethodPair = null; - } - - registerAnnotations(); - } - - private void registerAnnotations() - { - Stream.of(this.getClass().getDeclaredMethods()) - .filter(method -> method.isAnnotationPresent(Subcommand.class)) - .forEach(method -> this.subcommands.put( - method.getDeclaredAnnotation(Subcommand.class), - method)); - - List.of(this.getClass().getDeclaredAnnotationsByType(Completion.class)) - .stream() - .forEach(completions::add); - } - - public Pair getBaseMethodPair() - { - return baseMethodPair; - } - - Info getInfo() - { - return this.info; - } - - Permissive getPerms() - { - return this.perms; - } - - public JavaPlugin getPlugin() - { - return this.plugin; - } - - Map getSubcommands() - { - return this.subcommands; - } - - Set getCompletions() - { - return this.completions; - } -} diff --git a/Patchwork/src/main/java/me/totalfreedom/command/CommandHandler.java b/Patchwork/src/main/java/me/totalfreedom/command/CommandHandler.java index 9471dc7..cfcdd90 100644 --- a/Patchwork/src/main/java/me/totalfreedom/command/CommandHandler.java +++ b/Patchwork/src/main/java/me/totalfreedom/command/CommandHandler.java @@ -1,25 +1,44 @@ package me.totalfreedom.command; import org.bukkit.Bukkit; +import org.bukkit.command.CommandMap; import org.bukkit.plugin.java.JavaPlugin; +/** + * Handles the registration of commands. The plugin which initializes this class should be the plugin that is + * registering the commands. + */ public class CommandHandler { + /** + * The plugin that this command handler is registered to. + *
+ * This should be the plugin instance which is trying to register the commands. + */ private final JavaPlugin plugin; + /** + * Creates a new command handler. + * + * @param plugin The plugin that this command handler is registered to. + */ public CommandHandler(final JavaPlugin plugin) { this.plugin = plugin; } - // TODO: Figure out how to use CommandExecutor and TabCompleter. - // We need to find a way to resolve PluginCommands so we can - // set the executor and tab completer as necessary. - // OR we need to find an alternative way to process tab completions. - public void registerCommand(final T command) + /** + * Registers a command. This method will automatically delegate the command information to the Bukkit API and + * register with the {@link CommandMap}. + * + * @param command The command to register. + * @param The type of the command. + */ + public void registerCommand(final T command) { - final BukkitDelegator delegate = new BukkitDelegator(plugin, command); + final BukkitDelegate delegate = new BukkitDelegate(command); - Bukkit.getCommandMap().register(plugin.getName(), delegate); + Bukkit.getCommandMap() + .register(plugin.getName(), delegate); } } diff --git a/Patchwork/src/main/java/me/totalfreedom/command/Commander.java b/Patchwork/src/main/java/me/totalfreedom/command/Commander.java new file mode 100644 index 0000000..2830a84 --- /dev/null +++ b/Patchwork/src/main/java/me/totalfreedom/command/Commander.java @@ -0,0 +1,186 @@ +package me.totalfreedom.command; + +import me.totalfreedom.command.annotation.Base; +import me.totalfreedom.command.annotation.Completion; +import me.totalfreedom.command.annotation.Info; +import me.totalfreedom.command.annotation.Permissive; +import me.totalfreedom.command.annotation.Subcommand; +import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Stream; + +/** + * This is the base command class which should be extended when creating a new command. Commands must be annotated with + * the {@link Info} and {@link Permissive} annotations in order to be properly registered with the + * {@link CommandHandler}. + *

+ * One single method can be annotated with the {@link Base} annotation to specify that method should be called when the + * command is executed without any arguments. + *
+ * You are allowed to have as many methods as you want which are annotated with the {@link Subcommand} annotation. These + * methods will be called when the command is executed with the specified subcommand. + *
+ * You are also allowed to use multiple {@link Completion} annotations per method to define multiple tab completions for + * a single subcommand. This would be useful in the case where you would like to include specific completion cases, but + * also support basic String completion cases. + *
+ * When creating {@link Completion} annotations, you only need to register arguments a single time per class. For more + * information, see {@link Subcommand}. + */ +public abstract class Commander +{ + /** + * The plugin which owns this command. + */ + private final JavaPlugin plugin; + /** + * The {@link Info} annotation for this command. + */ + private final Info info; + /** + * The {@link Permissive} annotation for this command. + */ + private final Permissive perms; + /** + * A map of all subcommands and their related methods for this command. + */ + private final Map subcommands; + /** + * A set of all {@link Completion} annotations for this command. + */ + private final Set completions; + /** + * The method which should be called when the command is executed without any arguments. + */ + private final Method baseMethod; + + /** + * Initializes this command object. The provided {@link JavaPlugin} should be the plugin which contains the + * command. + *

+ * This constructor will automatically register all subcommands and completions for this command. It will also + * automatically infer all required information from the provided {@link Info} and {@link Permissive} annotations. + * + * @param plugin The plugin which contains this command. + */ + protected Commander(final @NotNull JavaPlugin plugin) + { + this.info = this.getClass() + .getDeclaredAnnotation(Info.class); + this.perms = this.getClass() + .getDeclaredAnnotation(Permissive.class); + this.plugin = plugin; + this.subcommands = new HashMap<>(); + this.completions = new HashSet<>(); + + if (this.getClass() + .isAnnotationPresent(Base.class)) + { + final Method method = Stream.of(this.getClass() + .getDeclaredMethods()) + .filter(m -> m.isAnnotationPresent(Base.class)) + .findFirst() + .orElseThrow(() -> new RuntimeException( + "Base annotation present but no method found.")); + + this.baseMethod = method; + } else + { + this.baseMethod = null; + } + + registerAnnotations(); + } + + /** + * Registers all subcommands and completions for this command. + */ + private void registerAnnotations() + { + Stream.of(this.getClass() + .getDeclaredMethods()) + .filter(method -> method.isAnnotationPresent(Subcommand.class)) + .forEach(method -> this.subcommands.put( + method.getDeclaredAnnotation(Subcommand.class), + method)); + + List.of(this.getClass() + .getDeclaredAnnotationsByType(Completion.class)) + .stream() + .forEach(completions::add); + } + + /** + * Gets the method which should be called when the command is executed without any arguments. + *
+ * This method will return null if the command does not have a base method. + * + * @return The base method for this command, or null if the command does not have a base method. + */ + @Nullable + public Method getBaseMethod() + { + return baseMethod; + } + + /** + * Gets the {@link Info} annotation for this command. + *
+ * This method will never return null as this annotation is required for the command to be registered. + * + * @return The {@link Info} annotation for this command. + */ + @NotNull + Info getInfo() + { + return this.info; + } + + /** + * Gets the {@link Permissive} annotation for this command. + *
+ * This method will never return null as this annotation is required for the command to be registered. + * + * @return The {@link Permissive} annotation for this command. + */ + @NotNull + Permissive getPerms() + { + return this.perms; + } + + /** + * @return The plugin which owns this command. + */ + @NotNull + public JavaPlugin getPlugin() + { + return this.plugin; + } + + /** + * @return A map of all subcommands and their related methods for this command. + */ + @NotNull + Map getSubcommands() + { + return this.subcommands; + } + + /** + * @return A set of all {@link Completion} annotations for this command. + */ + @Nullable + Set getCompletions() + { + return this.completions; + } +} diff --git a/Patchwork/src/main/java/me/totalfreedom/command/annotation/Base.java b/Patchwork/src/main/java/me/totalfreedom/command/annotation/Base.java index cd76252..c230186 100644 --- a/Patchwork/src/main/java/me/totalfreedom/command/annotation/Base.java +++ b/Patchwork/src/main/java/me/totalfreedom/command/annotation/Base.java @@ -4,8 +4,8 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** - * This annotation is used to mark a method as the command's default method. - * This is the method that will be run to execute the command when a user inputs /{command} + * This annotation is used to mark a method as the command's default method. This is the method that will be run to + * execute the command when a user inputs /{command} */ @Retention(RetentionPolicy.RUNTIME) public @interface Base diff --git a/Patchwork/src/main/java/me/totalfreedom/command/annotation/Completion.java b/Patchwork/src/main/java/me/totalfreedom/command/annotation/Completion.java index 82002af..d32efa4 100644 --- a/Patchwork/src/main/java/me/totalfreedom/command/annotation/Completion.java +++ b/Patchwork/src/main/java/me/totalfreedom/command/annotation/Completion.java @@ -1,15 +1,31 @@ package me.totalfreedom.command.annotation; import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -@Target(ElementType.TYPE) +/** + * Represents a tab completion for a command. + *

+ * This will register at class level, and does not retain method information. As a result, you only need to register the + * arguments a single time, and it will always be used in tab completions. + */ +@Target(ElementType.METHOD) +@Repeatable(Completions.class) @Retention(RetentionPolicy.RUNTIME) public @interface Completion { + /** + * An array of possible arguments for this particular index, represented by {@link #index()}. + * + * @return An array of possible arguments for tab completion. + */ String[] args(); + /** + * @return The index in which these arguments should be shown. + */ int index(); } diff --git a/Patchwork/src/main/java/me/totalfreedom/command/annotation/Completions.java b/Patchwork/src/main/java/me/totalfreedom/command/annotation/Completions.java new file mode 100644 index 0000000..694804e --- /dev/null +++ b/Patchwork/src/main/java/me/totalfreedom/command/annotation/Completions.java @@ -0,0 +1,22 @@ +package me.totalfreedom.command.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A marker interface which represents a holder for multiple {@link Completion} annotations. + *
+ * This interface is NOT intended for implementation and should + * NOT be used. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Completions +{ + /** + * @return The {@link Completion} annotations. + */ + Completion[] value(); +} diff --git a/Patchwork/src/main/java/me/totalfreedom/command/annotation/Info.java b/Patchwork/src/main/java/me/totalfreedom/command/annotation/Info.java index 478a5ad..5ab0bbc 100644 --- a/Patchwork/src/main/java/me/totalfreedom/command/annotation/Info.java +++ b/Patchwork/src/main/java/me/totalfreedom/command/annotation/Info.java @@ -3,14 +3,39 @@ package me.totalfreedom.command.annotation; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +/** + * This interface holds the information for each command. This annotation defines the command's name, description, + * usage, and aliases. Commands must have this annotation present to be registered with the handler. + */ @Retention(RetentionPolicy.RUNTIME) public @interface Info { + /** + * Technically, this is the only required value you must supply yourself. However, it is HIGHLY recommended you + * supply the other optional values as well, for better customization of your command. + * + * @return The command's name. + */ String name(); + /** + * By default, this is set to "This is the default command description." + * + * @return The command's description. + */ String description() default "This is the default command description."; + /** + * By default, this is set to "/<command>" + * + * @return The command's usage. + */ String usage() default "/"; + /** + * By default, this returns an empty array. + * + * @return The command's aliases. + */ String[] aliases() default {}; } diff --git a/Patchwork/src/main/java/me/totalfreedom/command/annotation/Permissive.java b/Patchwork/src/main/java/me/totalfreedom/command/annotation/Permissive.java index 470569d..90c5739 100644 --- a/Patchwork/src/main/java/me/totalfreedom/command/annotation/Permissive.java +++ b/Patchwork/src/main/java/me/totalfreedom/command/annotation/Permissive.java @@ -3,12 +3,31 @@ package me.totalfreedom.command.annotation; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +/** + * This annotation holds the permission information for each command. This annotation defines the command's permission, + * whether it is only for players, and the message to send if the sender does not have permission to use the command. + *

+ * Classes MUST have this annotation present to be registered with the handler. + */ @Retention(RetentionPolicy.RUNTIME) public @interface Permissive { + /** + * @return The command's permission. + */ String perm(); + /** + * By default, this is set to false. + * + * @return True if the command is only for players, false otherwise. + */ boolean onlyPlayers() default false; + /** + * By default, this is set to "You do not have permission to use this command." + * + * @return The message to send if the sender does not have permission to use the command. + */ String noPerms() default "You do not have permission to use this command."; } diff --git a/Patchwork/src/main/java/me/totalfreedom/command/annotation/Subcommand.java b/Patchwork/src/main/java/me/totalfreedom/command/annotation/Subcommand.java index 76fb1d3..b203761 100644 --- a/Patchwork/src/main/java/me/totalfreedom/command/annotation/Subcommand.java +++ b/Patchwork/src/main/java/me/totalfreedom/command/annotation/Subcommand.java @@ -1,12 +1,40 @@ package me.totalfreedom.command.annotation; +import me.totalfreedom.command.CommandHandler; +import me.totalfreedom.provider.ContextProvider; + +import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +/** + * This annotation should be used to mark methods as subcommand methods. Subcommand methods can have custom arguments + * (current supported arguments can be found in the {@link ContextProvider}), and can also have a custom permission. + * These subcommands can also be annotated with {@link Completions} to provide tab completions for the subcommand. The + * subcommand method must be public, and must be in a class that is registered with the {@link CommandHandler}. + *
+ * Tab completions with the {@link Completions} annotation are only supported for subcommands. When registering + * completions, you only need to define the completion arguments a single time. If there are other methods which + * function as optional additional arguments for the subcommand, the previously registered arguments will still be + * present when the user does their tab completion. + *
+ * For example, if you have a subcommand method with the arguments {@code (Player, String)}, and another method which + * has the arguments {@code (Player, String, String)}, the tab completions for the second method will still have the + * {@code Player} and {@code String} arguments registered from the first method. You will only need to provide a + * {@link Completion} for the additional 3rd argument. + */ +@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Subcommand { + /** + * @return The permission to use when executing this subcommand. + */ String permission(); + /** + * @return The arguments, as classes, to use when registering this subcommand. + */ Class[] args() default {}; } diff --git a/Patchwork/src/main/java/me/totalfreedom/config/Configuration.java b/Patchwork/src/main/java/me/totalfreedom/config/Configuration.java index 41e9674..728ead2 100644 --- a/Patchwork/src/main/java/me/totalfreedom/config/Configuration.java +++ b/Patchwork/src/main/java/me/totalfreedom/config/Configuration.java @@ -1,40 +1,132 @@ package me.totalfreedom.config; -import org.bukkit.configuration.file.YamlConfiguration; +import me.totalfreedom.api.Context; +import me.totalfreedom.provider.ContextProvider; import java.io.File; import java.io.IOException; import java.util.List; +/** + * Represents a configuration file of any type. + */ public interface Configuration { - YamlConfiguration asYaml(); - + /** + * Saves the configuration to the file. + * + * @throws IOException If the operation cannot be completed. + */ void save() throws IOException; + /** + * Loads the configuration from the file. + * + * @throws IOException If the operation cannot be completed. + */ void load() throws IOException; + /** + * @return The name of the file. + */ String getFileName(); + /** + * @return The actual Configuration {@link File}. + */ File getConfigurationFile(); + /** + * Gets a String object from the associated path. + * + * @param path The path to get the String from. + * @return The String object. + */ String getString(String path); + /** + * Gets a Boolean object from the associated path. + * + * @param path The path to get the Boolean from. + * @return The Boolean object. + */ Boolean getBoolean(String path); + /** + * Gets a List object from the associated path. This method will use {@link Context}s and the + * {@link ContextProvider} to get the object types in the list. If the objects cannot be inferred, the method will + * return a list of generic {@link Object}s. + * + * @param path The path to get the List from. + * @param The type of the objects in the list. + * @return The List object. + */ List getList(String path); + /** + * Gets a List object from the associated path. The List that is returned will be the String values which are stored + * within the configuration file at the given path. + * + * @param path The path to get the List from. + * @return The List object. + */ List getStringList(String path); + /** + * Gets an Integer from the associated path. + * + * @param path The path to get the Integer from. + * @return The Integer object. + */ Integer getInt(String path); + /** + * Gets a Long from the associated path. + * + * @param path The path to get the Long from. + * @return The Long object. + */ Long getLong(String path); + /** + * Gets a Double from the associated path. + * + * @param path The path to get the Double from. + * @return The Double object. + */ Double getDouble(String path); + /** + * Sets the value at the given path to the given value. + * + * @param path The path to set the value at. + * @param value The value to set. + * @param The type of the value. + */ void set(String path, T value); - T get(String path, Class type); + /** + * Gets the value at the given path as the given type. + *

+ * This method will use {@link Context}s and the {@link ContextProvider} to get the object type. If the object type + * cannot be inferred, the method will return a generic {@link Object}. + * + * @param path The path to get the value from. + * @param The type of the value. + * @return The value at the given path. + */ + T get(String path); - T getOrDefault(String path, Class type, T fallback); + /** + * Gets the value at the given path as the given type. + *

+ * This method will use {@link Context}s and the {@link ContextProvider} to get the object type. If the object type + * cannot be inferred, the method will return the given fallback value. + * + * @param path The path to get the value from. + * @param fallback The fallback value to return if the value at the given path is null. + * @param The type of the value. + * @return The value at the given path. + */ + T getOrDefault(String path, T fallback); } \ No newline at end of file diff --git a/Patchwork/src/main/java/me/totalfreedom/config/YamlWrapper.java b/Patchwork/src/main/java/me/totalfreedom/config/YamlWrapper.java new file mode 100644 index 0000000..eefd27c --- /dev/null +++ b/Patchwork/src/main/java/me/totalfreedom/config/YamlWrapper.java @@ -0,0 +1,6 @@ +package me.totalfreedom.config; + +public final class YamlWrapper +{ + +} diff --git a/Patchwork/src/main/java/me/totalfreedom/data/BanRegistry.java b/Patchwork/src/main/java/me/totalfreedom/data/BanRegistry.java deleted file mode 100644 index ad2c159..0000000 --- a/Patchwork/src/main/java/me/totalfreedom/data/BanRegistry.java +++ /dev/null @@ -1,34 +0,0 @@ -package me.totalfreedom.data; - -import me.totalfreedom.security.ban.Ban; -import me.totalfreedom.security.ban.BanID; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.List; - -public class BanRegistry -{ - private final List bansList = new ArrayList<>(); - - public boolean addBan(final Ban ban) { - return bansList.add(ban); - } - - public boolean removeBan(final Ban ban) { - return bansList.remove(ban); - } - - @Nullable - public Ban getBan(final BanID banID) - { - for (final Ban ban : bansList) - { - if (ban.getBanID().matches(banID)) - { - return ban; - } - } - return null; - } -} diff --git a/Patchwork/src/main/java/me/totalfreedom/data/ConfigRegistry.java b/Patchwork/src/main/java/me/totalfreedom/data/ConfigRegistry.java index 2c54b02..7db90f2 100644 --- a/Patchwork/src/main/java/me/totalfreedom/data/ConfigRegistry.java +++ b/Patchwork/src/main/java/me/totalfreedom/data/ConfigRegistry.java @@ -5,20 +5,43 @@ import me.totalfreedom.config.Configuration; import java.util.HashMap; import java.util.Map; +/** + * A registry for all the configurations. + */ public class ConfigRegistry { + /** + * A map of all the configurations. + */ private final Map configurationList = new HashMap<>(); + /** + * Registers a configuration. + * + * @param name The name of the configuration. + * @param configuration The configuration. + */ public void register(final String name, final Configuration configuration) { configurationList.put(name, configuration); } + /** + * Unregisters a configuration. + * + * @param name The name of the configuration. + */ public void unregister(final String name) { configurationList.remove(name); } + /** + * Gets a configuration. + * + * @param name The name of the configuration. + * @return The configuration. + */ public Configuration getConfiguration(final String name) { return configurationList.get(name); diff --git a/Patchwork/src/main/java/me/totalfreedom/data/EventRegistry.java b/Patchwork/src/main/java/me/totalfreedom/data/EventRegistry.java index b98d6cb..c5fb292 100644 --- a/Patchwork/src/main/java/me/totalfreedom/data/EventRegistry.java +++ b/Patchwork/src/main/java/me/totalfreedom/data/EventRegistry.java @@ -6,25 +6,51 @@ import me.totalfreedom.provider.EventProvider; import java.util.ArrayList; import java.util.List; +/** + * A registry for {@link FEvent}s. + */ public class EventRegistry { + /** + * The list of events. + */ private final List events; + /** + * Creates a new event registry. + */ public EventRegistry() { this.events = new ArrayList<>(); } + /** + * Registers an event. + * + * @param event The event to register. + */ public void register(final FEvent event) { this.events.add(event); } + /** + * Unregisters an event. + * + * @param event The event to unregister. + */ public void unregister(final FEvent event) { this.events.remove(event); } + /** + * Gets an {@link EventProvider} for the specified event class which contains the actual {@link FEvent} instance. + * + * @param clazz The event class. + * @param The event type. + * @return The event provider. + */ public EventProvider getEvent(final Class clazz) { for (final FEvent event : this.events) diff --git a/Patchwork/src/main/java/me/totalfreedom/data/GroupRegistry.java b/Patchwork/src/main/java/me/totalfreedom/data/GroupRegistry.java index 990e878..aec9b76 100644 --- a/Patchwork/src/main/java/me/totalfreedom/data/GroupRegistry.java +++ b/Patchwork/src/main/java/me/totalfreedom/data/GroupRegistry.java @@ -1,40 +1,76 @@ package me.totalfreedom.data; -import me.totalfreedom.security.perm.Group; +import me.totalfreedom.security.Group; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import java.util.ArrayList; import java.util.List; +/** + * A registry for {@link Group}s. + */ public class GroupRegistry { + /** + * The list of groups. + */ private final List groups; + /** + * Creates a new group registry. + */ public GroupRegistry() { this.groups = new ArrayList<>(); } - public boolean registerGroup(final Group group) { + /** + * Registers a group. + * + * @param group The group to register. + * @return {@code true} if the group was registered, {@code false} otherwise. + */ + public boolean registerGroup(final Group group) + { return groups.add(group); } - public boolean unregisterGroup(final Group group) { + /** + * Unregisters a group. + * + * @param group The group to unregister. + * @return {@code true} if the group was unregistered, {@code false} otherwise. + */ + public boolean unregisterGroup(final Group group) + { return groups.remove(group); } - public Group getGroup(final String name) { + /** + * Gets a group by name. + * + * @param name The name of the group. + * @return The group, or {@code null} if no group was found. + */ + public Group getGroup(final String name) + { final PlainTextComponentSerializer s = PlainTextComponentSerializer.plainText(); - for (final Group group : groups) { + for (final Group group : groups) + { final String n = s.serialize(group.getName()); - if (n.equalsIgnoreCase(name)) { + if (n.equalsIgnoreCase(name)) + { return group; } } return null; } - public List getGroups() { + /** + * @return The list of groups. + */ + public List getGroups() + { return groups; } } diff --git a/Patchwork/src/main/java/me/totalfreedom/data/ModuleRegistry.java b/Patchwork/src/main/java/me/totalfreedom/data/ModuleRegistry.java index 06d3e19..cdc26c0 100644 --- a/Patchwork/src/main/java/me/totalfreedom/data/ModuleRegistry.java +++ b/Patchwork/src/main/java/me/totalfreedom/data/ModuleRegistry.java @@ -6,15 +6,29 @@ import org.bukkit.plugin.java.JavaPlugin; import java.util.ArrayList; import java.util.List; +/** + * A registry for modules. + */ public class ModuleRegistry { + /** + * The list of modules. + */ private final List plugins; + /** + * Creates a new module registry. + */ public ModuleRegistry() { this.plugins = new ArrayList<>(); } + /** + * Adds a module to the registry. + * + * @param plugin The module to add. + */ public void addModule(final JavaPlugin plugin) { if (this.plugins.contains(plugin)) @@ -24,18 +38,30 @@ public class ModuleRegistry this.plugins.add(plugin); } - public void removeModule(final JavaPlugin plugin) { + /** + * Removes a module from the registry. + * + * @param plugin The module to remove. + */ + public void removeModule(final JavaPlugin plugin) + { this.plugins.remove(plugin); } - @SuppressWarnings("unchecked") - public ModuleProvider getModule(final Class clazz) + /** + * Gets a module from the registry wrapped in a {@link ModuleProvider}. + * + * @param clazz The class of the module. + * @param The type of the module. + * @return The module. + */ + public ModuleProvider getProvider(final Class clazz) { for (final JavaPlugin plugin : plugins) { if (clazz.isInstance(plugin)) { - return () -> (T) plugin; + return () -> clazz.cast(plugin); } } diff --git a/Patchwork/src/main/java/me/totalfreedom/data/ServiceRegistry.java b/Patchwork/src/main/java/me/totalfreedom/data/ServiceRegistry.java deleted file mode 100644 index 7381779..0000000 --- a/Patchwork/src/main/java/me/totalfreedom/data/ServiceRegistry.java +++ /dev/null @@ -1,70 +0,0 @@ -package me.totalfreedom.data; - -import me.totalfreedom.service.Service; -import org.bukkit.Bukkit; -import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.RegisteredServiceProvider; -import org.bukkit.plugin.ServicePriority; - -import java.util.ArrayList; -import java.util.List; - -public class ServiceRegistry -{ - private final List services; - - public ServiceRegistry() - { - this.services = new ArrayList<>(); - } - - public void startAll() - { - for (final Service service : this.services) - { - service.start(); - } - } - - public void stopAll() - { - for (final Service service : this.services) - { - service.stop(); - } - } - - @SuppressWarnings("unchecked") - // Suppressing is fine here; we know that the service is of type T extends Service, - // and calling getClass() on this object would effectively be Class, though we may lose - // the identity of the code signature in the process. - // In this case, that is fine. - public void register(final Plugin plugin, final T service) - { - this.services.add(service); - if (!service.getClass().isInstance(service)) - { - throw new UnknownError(""" - A critical issue has been encountered: - The service %s is not an instance of itself. - This is a critical issue and should be reported immediately. - """.formatted(service.getClass().getName())); - } - Bukkit.getServicesManager().register( - (Class) service.getClass(), - service, - plugin, - ServicePriority.Normal); - } - - public RegisteredServiceProvider getService(final Class clazz) - { - return Bukkit.getServicesManager().getRegistration(clazz); - } - - public void unregister(final Class clazz, final Service service) - { - this.services.remove(service); - Bukkit.getServicesManager().unregister(clazz, service); - } -} diff --git a/Patchwork/src/main/java/me/totalfreedom/data/ServiceTaskRegistry.java b/Patchwork/src/main/java/me/totalfreedom/data/ServiceTaskRegistry.java new file mode 100644 index 0000000..2cf3e8d --- /dev/null +++ b/Patchwork/src/main/java/me/totalfreedom/data/ServiceTaskRegistry.java @@ -0,0 +1,309 @@ +package me.totalfreedom.data; + +import me.totalfreedom.service.Service; +import me.totalfreedom.service.ServiceSubscription; +import me.totalfreedom.service.SubscriptionProvider; +import me.totalfreedom.service.Task; +import me.totalfreedom.service.TaskSubscription; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +/** + * A registry for all services and tasks registered with Patchwork. + *
+ * This class is not thread-safe, and should only be accessed from the main server thread. + *
+ *
+ * Services are tickable tasks which execute every single game tick. They are registered using + * {@link #registerService(ServiceSubscription)} and can be started using {@link #startService(Class)}. + *
+ *
+ * Tasks are runnable tasks which execute at the provided times in the {@link Task} and + * {@link TaskSubscription} classes. These define whether the Task is repeating, delayed, or just a one-time task. Tasks + * are registered using {@link #registerTask(TaskSubscription)} and can be started using {@link #startTask(Class)}. + *
+ *
+ * ServiceSubscriptions and TaskSubscriptions can both be easily obtained using the + * {@link SubscriptionProvider} utility class. + * + * @see Service + * @see Task + * @see ServiceSubscription + * @see TaskSubscription + * @see SubscriptionProvider + */ +public class ServiceTaskRegistry +{ + /** + * A list of all services registered with the registry. + */ + private final List> services; + /** + * A list of all tasks registered with the registry. + */ + private final List> tasks; + + /** + * Creates a new instance of the registry and initializes the service and task lists to new empty + * {@link ArrayList}s. + */ + public ServiceTaskRegistry() + { + this.services = new ArrayList<>(); + this.tasks = new ArrayList<>(); + } + + /** + * Starts all services registered with the registry. + *
+ * This method should be avoided, due to the fact that modules may have registered their services after + * this method has already been called. In this case, it is preferred to start each service using + * {@link #startService(Class)}. + *
+ * However, Patchwork calls this method when the server is starting up, as Patchwork is the central + * resource manager for registered tasks and services. Patchwork will call this method on the first server tick, so + * unless you are registering services AND starting them POST WORLD, you do not need to worry about + * starting your services. + */ + public void startAllServices() + { + for (final ServiceSubscription service : this.services) + { + service.start(); + } + } + + /** + * Starts all tasks registered with the registry. + *
+ * This method should be avoided, due to the fact that modules may have registered their tasks after + * this method has already been called. In this case, it is preferred to start each task using + * {@link #startTask(Class)}. + *
+ * However, Patchwork calls this method when the server is starting up, as Patchwork is the central + * resource manager for registered tasks and services. Patchwork will call this method on the first server tick, so + * unless you are registering tasks AND starting them POST WORLD, you do not need to worry about + * starting your tasks. + */ + public void startAllTasks() + { + for (final TaskSubscription task : this.tasks) + { + task.start(); + } + } + + /** + * Stops all services registered with the registry. + *
+ * This method should be avoided, due to the fact that modules should be handling their own + * registrations. It is preferred to use {@link #stopService(Class)} for each service you would like to + * stop. + *
+ * However, Patchwork calls this method when the server is shutting down, or when Patchwork is being + * disabled, as Patchwork is the central resource manager for registered tasks and services. Unless you are + * modifying service states while the server is running, you do not need to worry about disabling or + * unregistering your services. + */ + public void stopAllServices() + { + for (final ServiceSubscription service : this.services) + { + service.stop(); + } + } + + /** + * Stops all tasks registered with the registry. + *
+ * This method should be avoided, due to the fact that modules should be handling their own + * registrations. It is preferred to use {@link #stopTask(Class)} for each task you would like to stop. + *
+ * However, Patchwork calls this method when the server is shutting down, or when Patchwork is being + * disabled, as Patchwork is the central resource manager for registered tasks and services. Unless you are + * modifying task states while the server is running, you do not need to worry about disabling or + * unregistering your tasks. + */ + public void stopAllTasks() + { + for (final TaskSubscription task : this.tasks) + { + task.stop(); + } + } + + /** + * Registers a service with the registry. + *
+ * Services must be registered using ServiceSubscriptions, which can be easily obtained through the + * {@link SubscriptionProvider} utility class. + * + * @param service The service you are trying to register. + * @param A generic type for type inference of the service being registered. + */ + public void registerService(final ServiceSubscription service) + { + this.services.add(service); + } + + /** + * Registers a task with the registry. + *
+ * Tasks must be registered using TaskSubscriptions, which can be easily obtained through the + * {@link SubscriptionProvider} utility class. + * + * @param task The task you are trying to register. + * @param A generic type for type inference of the task being registered. + */ + public void registerTask(final TaskSubscription task) + { + this.tasks.add(task); + } + + /** + * Starts a service using the specified {@link Service} class. + *
+ * The service should already be registered with the registry as a ServiceSubscription. + * + * @param clazz The class of the service you are trying to start. + * @see ServiceSubscription + * @see #registerService(ServiceSubscription) + */ + public void startService(final Class clazz) + { + this.getService(clazz) + .start(); + } + + /** + * Gets a {@link ServiceSubscription} from the registry using the specified class. + *
+ * The class should be the service class you are trying to locate, not the class for the subscription + * itself. + *
+ * The service should have been registered previously as a ServiceSubscription. + * + * @param clazz The class of the service you are trying to locate. + * @param A generic type for type inference of the service requested. + * @return The {@link ServiceSubscription} for the specified class, or null if it could not be found. + * @see #registerService(ServiceSubscription) + * @see ServiceSubscription + */ + @Nullable + public ServiceSubscription getService(final Class clazz) + { + for (final ServiceSubscription service : this.services) + { + if (service.getService() + .getClass() + .equals(clazz)) + { + return (ServiceSubscription) service; + } + } + return null; + } + + /** + * Stops a service using the specified {@link Service} class. + *
+ * The service should already be registered with the registry as a ServiceSubscription. + * + * @param clazz The class of the service you are trying to stop. + * @see #registerService(ServiceSubscription) + * @see ServiceSubscription + */ + public void stopService(final Class clazz) + { + this.getService(clazz) + .stop(); + } + + /** + * Starts a task using the specified {@link Task} class. + *
+ * The task should already be registered with the registry as a TaskSubscription. + * + * @param clazz The class of the task you are trying to start. + * @see #registerTask(TaskSubscription) + * @see TaskSubscription + */ + public void startTask(final Class clazz) + { + this.getTask(clazz) + .start(); + } + + /** + * Gets a {@link TaskSubscription} from the registry using the specified class. + *
+ * The class should be the task class you are trying to locate, not the class for the subscription + * itself. + *
+ * The task should have been registered previously as a TaskSubscription. + * + * @param clazz The class of the task you are trying to locate. + * @param A generic type for type inference of the task requested. + * @return The {@link TaskSubscription} for the specified class, or null if it could not be found. + * @see #registerTask(TaskSubscription) + * @see TaskSubscription + */ + public TaskSubscription getTask(final Class clazz) + { + for (final TaskSubscription task : this.tasks) + { + if (task.getTask() + .getClass() + .equals(clazz)) + { + return (TaskSubscription) task; + } + } + return null; + } + + /** + * Stops a task using the specified {@link Task} class. + *
+ * The task should already be registered with the registry as a TaskSubscription. + * + * @param clazz The class of the task you are trying to stop. + * @see #registerTask(TaskSubscription) + * @see TaskSubscription + */ + public void stopTask(final Class clazz) + { + this.getTask(clazz) + .stop(); + } + + /** + * Unregisters a service from the registry. + *
+ * The service should have been registered previously as a ServiceSubscription. + * + * @param service The service you are trying to unregister. + * @see #registerService(ServiceSubscription) + * @see ServiceSubscription + */ + public void unregisterService(final Class clazz) + { + this.services.remove(getService(clazz)); + } + + /** + * Unregisters a task from the registry. + *
+ * The task should have been registered previously as a TaskSubscription. + * + * @param clazz The task you are trying to unregister. + * @see #registerTask(TaskSubscription) + * @see TaskSubscription + */ + public void unregisterTask(final Class clazz) + { + this.tasks.remove(getTask(clazz)); + } +} diff --git a/Patchwork/src/main/java/me/totalfreedom/data/UserRegistry.java b/Patchwork/src/main/java/me/totalfreedom/data/UserRegistry.java index 24bffa1..64bf824 100644 --- a/Patchwork/src/main/java/me/totalfreedom/data/UserRegistry.java +++ b/Patchwork/src/main/java/me/totalfreedom/data/UserRegistry.java @@ -2,34 +2,99 @@ package me.totalfreedom.data; import me.totalfreedom.user.User; import me.totalfreedom.user.UserData; +import org.bukkit.entity.Player; import java.util.HashMap; import java.util.Map; +/** + * A registry for {@link UserData} objects. + */ public class UserRegistry { + /** + * A map of {@link User} objects to {@link UserData} objects. + */ private final Map userDataMap; + /** + * Creates a new {@link UserRegistry}. + */ public UserRegistry() { this.userDataMap = new HashMap<>(); } + /** + * Gets the {@link UserData} object for the given {@link User}. + * + * @param user The {@link User} to get the {@link UserData} for. + * @return The {@link UserData} object for the given {@link User}. + */ public UserData getUserData(final User user) { return userDataMap.get(user); } + /** + * Gets the {@link UserData} object for the given {@link Player}. + */ + public UserData fromPlayer(final Player player) + { + return userDataMap.entrySet() + .stream() + .filter(entry -> entry.getKey() + .getUniqueId() + .equals(player.getUniqueId())) + .findFirst() + .map(Map.Entry::getValue) + .orElse(null); + } + + /** + * Gets the {@link User} object for the given {@link Player}. + * + * @param player The {@link Player} to get the {@link User} for. + * @return The {@link User} object for the given {@link Player}. + */ + public User getUser(final Player player) + { + return userDataMap.entrySet() + .stream() + .filter(entry -> entry.getKey() + .getUniqueId() + .equals(player.getUniqueId())) + .findFirst() + .map(Map.Entry::getKey) + .orElse(null); + } + + /** + * Registers the given {@link User} and {@link UserData} objects. + * + * @param user The {@link User} to register. + * @param userData The {@link UserData} to register. + */ public void registerUserData(final User user, final UserData userData) { userDataMap.put(user, userData); } + /** + * Unregisters the given {@link User} and {@link UserData} objects. + * + * @param user The {@link User} to unregister. + */ public void unregisterUserData(final User user) { userDataMap.remove(user); } + /** + * Gets the map of {@link User} objects to {@link UserData} objects. + * + * @return The map of {@link User} objects to {@link UserData} objects. + */ public Map getUserDataMap() { return userDataMap; diff --git a/Patchwork/src/main/java/me/totalfreedom/display/AbstractMenu.java b/Patchwork/src/main/java/me/totalfreedom/display/AbstractMenu.java new file mode 100644 index 0000000..9baa4df --- /dev/null +++ b/Patchwork/src/main/java/me/totalfreedom/display/AbstractMenu.java @@ -0,0 +1,197 @@ +package me.totalfreedom.display; + +import net.kyori.adventure.text.Component; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +/** + * Represents a menu that can be opened by a player. + */ +public abstract class AbstractMenu +{ + /** + * A map of all menus by their UUID. + */ + private static final Map invByUUID = new HashMap<>(); + /** + * A map of all open menus by the player's UUID. + */ + private static final Map openInvs = new HashMap<>(); + private final Map actionMap; + /** + * The displayable that represents this menu. + */ + private final Displayable displayable; + /** + * The UUID of the displayable that represents this menu. + */ + private final UUID displayableUUID; + + /** + * Creates a new menu with the specified size. + * + * @param size The size of the menu. + */ + protected AbstractMenu(final int size) + { + actionMap = new HashMap<>(); + this.displayable = new Displayable(size); + this.displayableUUID = UUID.randomUUID(); + + invByUUID.put(getDisplayableUUID(), this); + } + + /** + * @return A map of all menus by their UUID. + */ + public static Map getInvByUUID() + { + return invByUUID; + } + + /** + * @return A map of all open menus by the player's UUID. + */ + public static Map getOpenInvs() + { + return openInvs; + } + + /** + * @return The displayable UUID of this menu. + */ + public UUID getDisplayableUUID() + { + return displayableUUID; + } + + /** + * Sets the item at the specified slot. + * + * @param slot The slot to set the item at. + * @param stack The item to set. + */ + public void setItem(final int slot, final @NotNull ItemStack stack) + { + setItem(slot, stack, null); + } + + /** + * Sets the item at the specified slot. + * + * @param slot The slot to set the item at. + * @param stack The item to set. + * @param action The action to perform when the item is clicked. + */ + public void setItem(final int slot, final @NotNull ItemStack stack, final @Nullable ClickAction action) + { + getDisplayable().setItem(slot, stack); + if (action != null) + { + actionMap.put(slot, action); + } + } + + /** + * @return The displayable that represents this menu. + */ + public Displayable getDisplayable() + { + return displayable; + } + + /** + * Opens this menu for the specified player. + * + * @param player The player to open the menu for. + */ + public void open(final @NotNull Player player) + { + player.openInventory(getDisplayable()); + openInvs.put(player.getUniqueId(), getDisplayableUUID()); + } + + /** + * Deletes this menu. + */ + public void delete() + { + Bukkit.getOnlinePlayers() + .forEach(player -> + { + if (openInvs.get(player.getUniqueId()) + .equals(getDisplayableUUID())) + { + close(player); + } + }); + + invByUUID.remove(getDisplayableUUID()); + } + + /** + * Closes this menu for the specified player. + * + * @param player The player to close the menu for. + */ + public void close(final @NotNull Player player) + { + player.closeInventory(); + openInvs.remove(player.getUniqueId()); + } + + /** + * @return A map of all actions by their slot. + */ + public Map getActions() + { + return actionMap; + } + + /** + * Creates a new item with the specified material and name. + * + * @param material The material of the item. + * @param name The name of the item. + * @return The created item. + */ + public ItemStack newItem(final @NotNull Material material, final @NotNull Component name) + { + return this.newItem(material, name, new Component[0]); + } + + /** + * Creates a new item with the specified material, name, and lore. + * + * @param material The material of the item. + * @param name The name of the item. + * @param lore The lore of the item. + * @return The created item. + */ + public ItemStack newItem(final @NotNull Material material, final @NotNull Component name, + final @NotNull Component... lore) + { + final ItemStack item = new ItemStack(material, 1); + final ItemMeta meta = item.getItemMeta(); + if (meta == null) + { + return item; + } + meta.displayName(name); + final List metaLore = Arrays.asList(lore); + meta.lore(metaLore); + item.setItemMeta(meta); + return item; + } +} diff --git a/Patchwork/src/main/java/me/totalfreedom/display/BossBarDisplay.java b/Patchwork/src/main/java/me/totalfreedom/display/BossBarDisplay.java new file mode 100644 index 0000000..2239b4b --- /dev/null +++ b/Patchwork/src/main/java/me/totalfreedom/display/BossBarDisplay.java @@ -0,0 +1,381 @@ +package me.totalfreedom.display; + +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.audience.ForwardingAudience; +import net.kyori.adventure.bossbar.BossBar; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.TextColor; +import org.jetbrains.annotations.Range; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * This class is a wrapper for {@link BossBar} objects. It provides some handy methods for changing the boss bar's + * properties, displaying the bar to {@link Audience}s, and a {@link BossBarBuilder} to easily create new boss bars. + */ +public class BossBarDisplay +{ + private BossBar bossBar; + + /** + * Creates a new {@link BossBarDisplay} object. + * + * @param bossBar The {@link BossBar} to wrap. + */ + public BossBarDisplay(final BossBar bossBar) + { + this.bossBar = bossBar; + } + + /** + * @return A new {@link BossBarBuilder} object. + */ + public static BossBarBuilder builder() + { + return new BossBarBuilder(); + } + + /** + * Changes the boss bar's color. + * + * @param color The new color. + */ + public void changeColor(final BossBar.Color color) + { + this.bossBar.color(color); + } + + /** + * Changes the boss bar's color. + * + * @param overlay The new overlay. + */ + public void changeOverlay(final BossBar.Overlay overlay) + { + this.bossBar.overlay(overlay); + } + + /** + * Changes the boss bar's name using a {@link Component}. + * + * @param name The new name. + */ + public void changeName(final Component name) + { + this.bossBar.name(name); + } + + /** + * Changes the boss bar's name with a String and a {@link TextColor}. + * + * @param name The new name. + * @param color The name color. + */ + public void changeName(final String name, final TextColor color) + { + this.bossBar.name(Component.text(name, color)); + } + + /** + * Changes the boss bar's name with a String. + * + * @param name The new name. + */ + public void changeName(final String name) + { + this.bossBar.name(Component.text(name)); + } + + /** + * Shows this Boss Bar to the specified {@link Audience}. + * + * @param audience The {@link Audience} to show the Boss Bar to. + */ + public void showTo(final Audience audience) + { + audience.showBossBar(getBossBar()); + } + + /** + * @return The {@link BossBar} object that this class wraps. + */ + public BossBar getBossBar() + { + return this.bossBar; + } + + /** + * Sets the {@link BossBar} object that this class wraps. + * + * @param bossBar The new {@link BossBar} object. + */ + public void setBossBar(final BossBar bossBar) + { + this.bossBar = bossBar; + } + + /** + * Hides this Boss Bar from the specified {@link Audience}. + * + * @param audience The {@link Audience} to hide the Boss Bar from. + */ + public void hideFrom(final Audience audience) + { + audience.hideBossBar(getBossBar()); + } + + /** + * Increments the Bar's progress by the specified amount. This must be a range from 0 to 100. + * + * @param progress The new progress. + */ + public void incrementProgress(final @Range(from = 0, to = 100) float progress) + { + final float currentProgress = this.bossBar.progress(); + final float newProgress = currentProgress + (progress / 100.0F); + + if (newProgress > 1) this.bossBar.progress(1.0F); + else this.bossBar.progress(newProgress); + } + + /** + * Decrements the Bar's progress by the specified amount. This must be a range from 0 to 100. + * + * @param progress The new progress. + */ + public void decrementProgress(final @Range(from = 0, to = 100) float progress) + { + final float currentProgress = this.bossBar.progress(); + final float newProgress = currentProgress - (progress / 100.0F); + + if (newProgress < 0) this.bossBar.progress(0.0F); + else this.bossBar.progress(newProgress); + } + + /** + * Sets the Bar's progress to the maximum amount (full bar). + */ + public void maximumProgress() + { + this.bossBar.progress(1.0F); + } + + /** + * Sets the Bar's progress to half of the maximum amount (half bar). + */ + public void halfProgress() + { + this.bossBar.progress(0.5F); + } + + /** + * Sets the Bar's progress to the minimum amount (empty bar). + */ + public void minimumProgress() + { + this.bossBar.progress(0.0F); + } + + /** + * Shows this Boss Bar to the specified {@link ForwardingAudience}. + * + * @param forwardingAudience The {@link ForwardingAudience} to show the Boss Bar to. + */ + public void showForwarded(final ForwardingAudience forwardingAudience) + { + forwardingAudience.showBossBar(getBossBar()); + } + + /** + * Hides this Boss Bar from the specified {@link ForwardingAudience}. + * + * @param forwardingAudience The {@link ForwardingAudience} to hide the Boss Bar from. + */ + public void hideForwarded(final ForwardingAudience forwardingAudience) + { + forwardingAudience.hideBossBar(getBossBar()); + } + + /** + * A Builder class for {@link BossBar} objects. + */ + public static final class BossBarBuilder + { + /** + * The flags that this Boss Bar will have. + */ + private final Set flags = new HashSet<>(); + /** + * The Boss Bar's name. + */ + private Component name; + /** + * The Boss Bar's color. + */ + private BossBar.Color color; + /** + * The Boss Bar's overlay. + */ + private BossBar.Overlay overlay; + /** + * The Boss Bar's progress. + */ + @Range(from = 0, to = 1) + private float progress; + + /** + * Initializes this Builder object. + */ + public BossBarBuilder() + { + this.name = Component.empty(); + this.color = BossBar.Color.GREEN; + this.overlay = BossBar.Overlay.PROGRESS; + this.progress = 0.0F; + } + + /** + * Sets the name of the boss bar. + * + * @param name The name of the boss bar. + * @return The builder. + */ + public BossBarBuilder setName(final Component name) + { + this.name = name; + return this; + } + + /** + * Sets the name of the boss bar using a String and a {@link TextColor}. + * + * @param name The name of the boss bar. + * @param color The color of the boss bar. + * @return The builder. + */ + public BossBarBuilder setName(final String name, final TextColor color) + { + this.name = Component.text(name, color); + return this; + } + + /** + * Sets the name of the boss bar using a String. + * + * @param name The name of the boss bar. + * @return The builder. + */ + public BossBarBuilder setName(final String name) + { + this.name = Component.text(name); + return this; + } + + /** + * Adds a flag to the boss bar. + * + * @param flag The flag to add. + * @return The builder. + */ + public BossBarBuilder addFlag(final BossBar.Flag flag) + { + this.flags.add(flag); + return this; + } + + /** + * Adds multiple flags to the boss bar. + * + * @param flags The flags to add. + * @return The builder. + */ + public BossBarBuilder addFlags(final BossBar.Flag... flags) + { + this.flags.addAll(List.of(flags)); + return this; + } + + /** + * Removes a flag from the boss bar. + * + * @param flag The flag to remove. + * @return The builder. + */ + public BossBarBuilder removeFlag(final BossBar.Flag flag) + { + this.flags.remove(flag); + return this; + } + + /** + * Removes multiple flags from the boss bar. + * + * @param flags The flags to remove. + * @return The builder. + */ + public BossBarBuilder removeFlags(final BossBar.Flag... flags) + { + this.flags.removeAll(List.of(flags)); + return this; + } + + /** + * Clears all flags from the boss bar. + * + * @return The builder. + */ + public BossBarBuilder clearFlags() + { + this.flags.clear(); + return this; + } + + /** + * Sets the color of the boss bar. + * + * @param color The color of the boss bar. + * @return The builder. + */ + public BossBarBuilder setColor(final BossBar.Color color) + { + this.color = color; + return this; + } + + /** + * Sets the overlay of the boss bar. + * + * @param overlay The overlay of the boss bar. + * @return The builder. + */ + public BossBarBuilder setOverlay(final BossBar.Overlay overlay) + { + this.overlay = overlay; + return this; + } + + /** + * Sets the progress of the boss bar. This must satisfy {@code 0 <= progress <= 100}. + * + * @param progress The progress of the boss bar. + * @return The builder. + */ + public BossBarBuilder setProgress(final @Range(from = 0, to = 100) float progress) + { + this.progress = progress / 100.0F; + return this; + } + + /** + * Builds the boss bar. + * + * @return The {@link BossBar}. + */ + public BossBar build() + { + return BossBar.bossBar(this.name, this.progress, this.color, this.overlay, this.flags); + } + } +} diff --git a/Patchwork/src/main/java/me/totalfreedom/display/BossBarTimer.java b/Patchwork/src/main/java/me/totalfreedom/display/BossBarTimer.java new file mode 100644 index 0000000..dfa3eb1 --- /dev/null +++ b/Patchwork/src/main/java/me/totalfreedom/display/BossBarTimer.java @@ -0,0 +1,39 @@ +package me.totalfreedom.display; + +import me.totalfreedom.service.Task; +import org.bukkit.Bukkit; + +import java.time.Duration; + +public class BossBarTimer extends Task +{ + private final BossBarDisplay bossBarDisplay; + private final Duration duration; + private double seconds = 0; + + public BossBarTimer(final BossBarDisplay bossBarDisplay, final Duration duration) + { + super("BossBarTimer", -1L, 20L); + this.bossBarDisplay = bossBarDisplay; + this.duration = duration; + bossBarDisplay.minimumProgress(); + bossBarDisplay.showTo(Bukkit.getServer()); + } + + @Override + public void run() + { + if (this.isCancelled()) return; + + if (seconds >= duration.getSeconds()) + { + bossBarDisplay.hideFrom(Bukkit.getServer()); + this.cancel(); + return; + } + + final float percentage = (float) (seconds / duration.getSeconds()) * 100L; + bossBarDisplay.incrementProgress(percentage); + seconds++; + } +} diff --git a/Patchwork/src/main/java/me/totalfreedom/display/ClickAction.java b/Patchwork/src/main/java/me/totalfreedom/display/ClickAction.java new file mode 100644 index 0000000..70aa1ad --- /dev/null +++ b/Patchwork/src/main/java/me/totalfreedom/display/ClickAction.java @@ -0,0 +1,18 @@ +package me.totalfreedom.display; + +import org.bukkit.entity.Player; + +/** + * Represents an action to be performed when a player clicks on an inventory slot in the respective + * {@link AbstractMenu}. + */ +@FunctionalInterface +public interface ClickAction +{ + /** + * Called when a player clicks on an inventory slot in the respective {@link AbstractMenu}. + * + * @param player The player who clicked. + */ + void onClick(final Player player); +} diff --git a/Patchwork/src/main/java/me/totalfreedom/display/Displayable.java b/Patchwork/src/main/java/me/totalfreedom/display/Displayable.java new file mode 100644 index 0000000..56a5fff --- /dev/null +++ b/Patchwork/src/main/java/me/totalfreedom/display/Displayable.java @@ -0,0 +1,498 @@ +package me.totalfreedom.display; + +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.ListIterator; + +/** + * A class that represents an inventory that can be displayed to players. This class also represents the inventory + * holder which contains the inventory. + */ +public final class Displayable implements Inventory, InventoryHolder +{ + + /** + * The size of the inventory. This is always a multiple of 9. + */ + private final int size; + /** + * The contents of the inventory. + */ + private ItemStack[] contents; + + /** + * Creates a new Displayable inventory with the given size. You are free to supply any size you want, but it will + * always be rounded up to the next multiple of 9. The maximum size allowed is 54. Any number higher than that will + * be rounded down to 54. + * + * @param size The size of the inventory. + */ + protected Displayable(final int size) + { + if (size < 1 || size > 54) + { + throw new IllegalArgumentException("Invalid size for Displayable inventory"); + } + + // If the size is not a multiple of nine, find the difference to the next highest multiple of 9 and make up + // the difference. + this.size = (size % 9 == 0) + ? size + : size + (9 - size % 9); + + this.contents = new ItemStack[size]; + } + + @Override + public int getSize() + { + return size; + } + + @Override + public int getMaxStackSize() + { + return 64; + } + + /** + * @param size The new maximum stack size for items in this inventory. + * @deprecated This method is not supported by Displayable inventories. + */ + @Override + @Deprecated(since = "1.19.4") + public void setMaxStackSize(final int size) + { + // No implementation required + } + + @Override + public @Nullable ItemStack getItem(final int index) + { + if (index < 0 || index >= size) + { + return null; + } + return contents[index]; + } + + @Override + public void setItem(final int index, final @Nullable ItemStack item) + { + if (index < 0 || index >= size) + { + return; + } + contents[index] = item; + } + + @Override + public @NotNull HashMap addItem(final @NotNull ItemStack... items) + throws IllegalArgumentException + { + final HashMap remainingItems = new HashMap<>(); + + for (final ItemStack item : items) + { + int remainingAmount = item.getAmount(); + + for (int i = 0; i < size; i++) + { + final ItemStack current = contents[i]; + + if (current == null) + { + final int maxStackSize = item.getMaxStackSize(); + final int amountToAdd = Math.min(remainingAmount, maxStackSize); + final ItemStack clone = item.clone(); + clone.setAmount(amountToAdd); + contents[i] = clone; + remainingAmount -= amountToAdd; + + if (remainingAmount == 0) + { + break; + } + } + } + + if (remainingAmount > 0) + { + remainingItems.put(remainingItems.size(), new ItemStack(item.getType(), remainingAmount)); + } + } + + return remainingItems; + } + + @Override + public @NotNull HashMap removeItem(final @NotNull ItemStack... items) + throws IllegalArgumentException + { + final HashMap removedItems = new HashMap<>(); + + for (final ItemStack item : items) + { + int remainingAmount = item.getAmount(); + + for (int i = 0; i < size; i++) + { + final ItemStack current = contents[i]; + + if (current != null && current.isSimilar(item)) + { + final int amountToRemove = Math.min(remainingAmount, current.getAmount()); + current.setAmount(current.getAmount() - amountToRemove); + remainingAmount -= amountToRemove; + + if (current.getAmount() <= 0) + { + contents[i] = null; + } + + if (remainingAmount == 0) + { + break; + } + } + } + + if (remainingAmount < item.getAmount()) + { + removedItems.put(removedItems.size(), + new ItemStack(item.getType(), item.getAmount() - remainingAmount)); + } + } + + return removedItems; + } + + @Override + public @NotNull HashMap removeItemAnySlot(final @NotNull ItemStack... items) + throws IllegalArgumentException + { + return removeItem(items); + } + + @Override + public @Nullable ItemStack @NotNull [] getContents() + { + return contents.clone(); + } + + @Override + public void setContents(final @Nullable ItemStack @NotNull [] items) throws IllegalArgumentException + { + if (items == null) + { + throw new IllegalArgumentException("Items cannot be null"); + } + if (items.length != size) + { + throw new IllegalArgumentException("Invalid size of items array"); + } + System.arraycopy(items, 0, contents, 0, size); + } + + @Override + public @Nullable ItemStack @NotNull [] getStorageContents() + { + return contents; + } + + @Override + public void setStorageContents(final @Nullable ItemStack @NotNull [] items) throws IllegalArgumentException + { + this.contents = items; + } + + @Override + public boolean contains(final @NotNull Material material) throws IllegalArgumentException + { + for (final ItemStack item : contents) + { + if (item != null && item.getType() == material) + { + return true; + } + } + return false; + } + + @Override + public boolean contains(final @Nullable ItemStack item) + { + if (item == null) + { + return false; + } + for (final ItemStack content : contents) + { + if (content != null && content.isSimilar(item)) + { + return true; + } + } + return false; + } + + @Override + public boolean contains(final @NotNull Material material, final int amount) throws IllegalArgumentException + { + int totalAmount = 0; + for (final ItemStack item : contents) + { + if (item != null && item.getType() == material) + { + totalAmount += item.getAmount(); + if (totalAmount >= amount) + { + return true; + } + } + } + return false; + } + + @Override + public boolean contains(final @Nullable ItemStack item, final int amount) + { + if (item == null) + { + return false; + } + int totalAmount = 0; + for (final ItemStack content : contents) + { + if (content != null && content.isSimilar(item)) + { + totalAmount += content.getAmount(); + if (totalAmount == amount) + { + return true; + } + } + } + return false; + } + + @Override + public boolean containsAtLeast(final @Nullable ItemStack item, final int amount) + { + if (item == null) + { + return false; + } + int totalAmount = 0; + for (final ItemStack content : contents) + { + if (content != null && content.isSimilar(item)) + { + totalAmount += content.getAmount(); + if (totalAmount >= amount) + { + return true; + } + } + } + return false; + } + + @Override + public @NotNull HashMap all(final @NotNull Material material) + throws IllegalArgumentException + { + final HashMap matchingItems = new HashMap<>(); + for (int i = 0; i < size; i++) + { + final ItemStack item = contents[i]; + if (item != null && item.getType() == material) + { + matchingItems.put(i, item); + } + } + return matchingItems; + } + + @Override + public @NotNull HashMap all(final @Nullable ItemStack item) + { + final HashMap matchingItems = new HashMap<>(); + if (item != null) + { + for (int i = 0; i < size; i++) + { + final ItemStack content = contents[i]; + if (content != null && content.isSimilar(item)) + { + matchingItems.put(i, content); + } + } + } + return matchingItems; + } + + @Override + public int first(final @NotNull Material material) throws IllegalArgumentException + { + for (int i = 0; i < size; i++) + { + final ItemStack item = contents[i]; + if (item != null && item.getType() == material) + { + return i; + } + } + return -1; + } + + @Override + public int first(final @NotNull ItemStack item) + { + for (int i = 0; i < size; i++) + { + final ItemStack content = contents[i]; + if (content != null && content.isSimilar(item)) + { + return i; + } + } + return -1; + } + + @Override + public int firstEmpty() + { + for (int i = 0; i < size; i++) + { + if (contents[i] == null) + { + return i; + } + } + return -1; + } + + @Override + public boolean isEmpty() + { + for (final ItemStack content : contents) + { + if (content != null) + { + return false; + } + } + return true; + } + + @Override + public void remove(final @NotNull Material material) throws IllegalArgumentException + { + for (int i = 0; i < size; i++) + { + final ItemStack item = contents[i]; + if (item != null && item.getType() == material) + { + contents[i] = null; + } + } + } + + @Override + public void remove(final @NotNull ItemStack item) + { + for (int i = 0; i < size; i++) + { + final ItemStack content = contents[i]; + if (content != null && content.isSimilar(item)) + { + contents[i] = null; + } + } + } + + @Override + public void clear(final int index) + { + if (index >= 0 && index < size) + { + contents[index] = null; + } + } + + @Override + public void clear() + { + for (int i = 0; i < size; i++) + { + contents[i] = null; + } + } + + @Override + public int close() + { + return 0; + } + + @Override + public @NotNull List getViewers() + { + return new ArrayList<>(); + } + + @Override + public @NotNull InventoryType getType() + { + return InventoryType.CHEST; + } + + @Override + public @Nullable InventoryHolder getHolder() + { + return this; + } + + @Override + public @Nullable InventoryHolder getHolder(final boolean useSnapshot) + { + return this; + } + + @Override + public @NotNull ListIterator iterator() + { + return iterator(0); + } + + @Override + public @NotNull ListIterator iterator(final int index) + { + return List.of(contents) + .listIterator(index); + } + + @Override + public @Nullable Location getLocation() + { + return null; + } + + @Override + public @NotNull Inventory getInventory() + { + return this; + } +} diff --git a/Patchwork/src/main/java/me/totalfreedom/display/DisplayableView.java b/Patchwork/src/main/java/me/totalfreedom/display/DisplayableView.java new file mode 100644 index 0000000..26c5138 --- /dev/null +++ b/Patchwork/src/main/java/me/totalfreedom/display/DisplayableView.java @@ -0,0 +1,97 @@ +package me.totalfreedom.display; + +import me.totalfreedom.utils.kyori.FreedomAdventure; +import org.bukkit.entity.HumanEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryView; +import org.jetbrains.annotations.NotNull; + +/** + * A view of a {@link Displayable} inventory. + *

+ * This class can be used to display two separate {@link Displayable} objects to the Player. + */ +public class DisplayableView extends InventoryView +{ + /** + * The upper inventory involved in this transaction. + */ + private final Displayable top; + /** + * The lower inventory involved in this transaction. + */ + private final Displayable bottom; + /** + * The player viewing the inventories involved in this transaction. + */ + private final Player player; + /** + * The type of inventory this transaction is for. + */ + private final InventoryType type; + /** + * The title of the main inventory involved in this transaction. The main inventory should always be the top + * inventory. + */ + private String title; + + /** + * Creates a new DisplayableView. + * + * @param player The player viewing the inventories involved in this transaction. + * @param top The upper inventory involved in this transaction. + * @param bottom The lower inventory involved in this transaction. + */ + public DisplayableView(final Player player, final Displayable top, final Displayable bottom) + { + this.player = player; + this.top = top; + this.bottom = bottom; + this.type = InventoryType.CHEST; + this.title = FreedomAdventure.toPlainText(type.defaultTitle()); + } + + @Override + public @NotNull Inventory getTopInventory() + { + return top; + } + + @Override + public @NotNull Inventory getBottomInventory() + { + return bottom; + } + + @Override + public @NotNull HumanEntity getPlayer() + { + return player; + } + + @Override + public @NotNull InventoryType getType() + { + return type; + } + + @Override + public @NotNull String getTitle() + { + return title; + } + + @Override + public void setTitle(final @NotNull String title) + { + this.title = title; + } + + @Override + public @NotNull String getOriginalTitle() + { + return FreedomAdventure.toPlainText(type.defaultTitle()); + } +} diff --git a/Patchwork/src/main/java/me/totalfreedom/display/TitleDisplay.java b/Patchwork/src/main/java/me/totalfreedom/display/TitleDisplay.java new file mode 100644 index 0000000..b6bf7cc --- /dev/null +++ b/Patchwork/src/main/java/me/totalfreedom/display/TitleDisplay.java @@ -0,0 +1,250 @@ +package me.totalfreedom.display; + +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.audience.ForwardingAudience; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.TextColor; +import net.kyori.adventure.title.Title; + +import java.time.Duration; + +/** + * A wrapper class for {@link Title}s that allows for easy display to an {@link Audience}. + */ +public class TitleDisplay +{ + /** + * The {@link Title} to display. + */ + private Title title; + + /** + * Creates a new {@link TitleDisplay} with the given {@link Title}. + * + * @param title The {@link Title} to display. + */ + public TitleDisplay(final Title title) + { + this.title = title; + } + + /** + * @return A new {@link TitleBuilder} which can be used to create new {@link Title}s. + */ + public static TitleBuilder builder() + { + return new TitleBuilder(); + } + + /** + * Displays the {@link Title} to the given {@link Audience}. + * + * @param audience The {@link Audience} to display the {@link Title} to. + */ + public void displayTo(final Audience audience) + { + audience.clearTitle(); + audience.showTitle(getTitle()); + } + + /** + * @return The {@link Title} to display. + */ + public Title getTitle() + { + return this.title; + } + + /** + * Sets the {@link Title} to display. + * + * @param title The {@link Title} to display. + */ + public void setTitle(final Title title) + { + this.title = title; + } + + /** + * Displays the {@link Title} to the given {@link ForwardingAudience}. + * + * @param forwardingAudience The {@link ForwardingAudience} to display the {@link Title} to. + */ + public void displayForwarded(final ForwardingAudience forwardingAudience) + { + forwardingAudience.clearTitle(); + forwardingAudience.showTitle(getTitle()); + } + + /** + * A builder class for {@link Title}s. + */ + public static final class TitleBuilder + { + /** + * The main title of the {@link Title}. + */ + private Component mainTitle; + /** + * The subtitle of the {@link Title}. + */ + private Component subTitle; + /** + * How long the Title should fade in for. + */ + private Duration fadeIn; + /** + * How long the Title should fade out for. + */ + private Duration fadeOut; + /** + * How long the Title should be displayed for. + */ + private Duration displayDuration; + + /** + * Creates a new {@link TitleBuilder} with default values. The default values are: + *

    + *
  • Empty main title
  • + *
  • Empty subtitle
  • + *
  • Default fade in time
  • + *
  • Default fade out time
  • + *
  • Default display duration
  • + *
+ * + * @see Title#DEFAULT_TIMES + */ + public TitleBuilder() + { + this.mainTitle = Component.empty(); + this.subTitle = Component.empty(); + this.fadeIn = Title.DEFAULT_TIMES.fadeIn(); + this.fadeOut = Title.DEFAULT_TIMES.fadeOut(); + this.displayDuration = Title.DEFAULT_TIMES.stay(); + } + + /** + * Sets the main title of the {@link Title}. + * + * @param title The main title of the {@link Title}. + * @return The {@link TitleBuilder} instance. + */ + public TitleBuilder setMainTitle(final String title) + { + this.mainTitle = Component.text(title); + return this; + } + + /** + * Sets the main title of the {@link Title}. + * + * @param title The main title of the {@link Title}. + * @param titleColor The color of the main title. + * @return The {@link TitleBuilder} instance. + */ + public TitleBuilder setMainTitle(final String title, final TextColor titleColor) + { + this.mainTitle = Component.text(title, titleColor); + return this; + } + + /** + * Sets the main title of the {@link Title}. + * + * @param mainTitle The main title of the {@link Title}. + * @return The {@link TitleBuilder} instance. + */ + public TitleBuilder setMainTitle(final Component mainTitle) + { + this.mainTitle = mainTitle; + return this; + } + + /** + * Sets the subtitle of the {@link Title}. + * + * @param title The subtitle of the {@link Title}. + * @return The {@link TitleBuilder} instance. + */ + public TitleBuilder setSubTitle(final String title) + { + this.subTitle = Component.text(title); + return this; + } + + /** + * Sets the subtitle of the {@link Title}. + * + * @param title The subtitle of the {@link Title}. + * @param titleColor The color of the subtitle. + * @return The {@link TitleBuilder} instance. + */ + public TitleBuilder setSubTitle(final String title, final TextColor titleColor) + { + this.subTitle = Component.text(title, titleColor); + return this; + } + + /** + * Sets the subtitle of the {@link Title}. + * + * @param subTitle The subtitle of the {@link Title}. + * @return The {@link TitleBuilder} instance. + */ + public TitleBuilder setSubTitle(final Component subTitle) + { + this.subTitle = subTitle; + return this; + } + + /** + * Sets the fade in time of the {@link Title}. + * + * @param duration The fade in time of the {@link Title}. + * @return The {@link TitleBuilder} instance. + */ + public TitleBuilder setFadeIn(final Duration duration) + { + this.fadeIn = duration; + return this; + } + + /** + * Sets the fade out time of the {@link Title}. + * + * @param duration The fade out time of the {@link Title}. + * @return The {@link TitleBuilder} instance. + */ + public TitleBuilder setFadeOut(final Duration duration) + { + this.fadeOut = duration; + return this; + } + + /** + * Sets the display duration of the {@link Title}. + * + * @param duration The display duration of the {@link Title}. + * @return The {@link TitleBuilder} instance. + */ + public TitleBuilder setDisplayDuration(final Duration duration) + { + this.displayDuration = duration; + return this; + } + + /** + * Builds the {@link Title} with the given parameters. + * + * @return The built {@link Title}. + */ + public Title build() + { + return Title.title( + this.mainTitle, + this.subTitle, + Title.Times.times(this.fadeIn, this.displayDuration, this.fadeOut) + ); + } + } +} diff --git a/Patchwork/src/main/java/me/totalfreedom/economy/EconomicEntity.java b/Patchwork/src/main/java/me/totalfreedom/economy/EconomicEntity.java index 59b11c2..f2b2c69 100644 --- a/Patchwork/src/main/java/me/totalfreedom/economy/EconomicEntity.java +++ b/Patchwork/src/main/java/me/totalfreedom/economy/EconomicEntity.java @@ -6,7 +6,8 @@ package me.totalfreedom.economy; public interface EconomicEntity { /** - * Gets the {@link EconomicEntityData} (which contains various common metadata about this {@link EconomicEntity}) associated with this class + * Gets the {@link EconomicEntityData} (which contains various common metadata about this {@link EconomicEntity}) + * associated with this class * * @return the {@link EconomicEntityData} */ diff --git a/Patchwork/src/main/java/me/totalfreedom/economy/EconomicEntityData.java b/Patchwork/src/main/java/me/totalfreedom/economy/EconomicEntityData.java index 0f853bd..ac3d372 100644 --- a/Patchwork/src/main/java/me/totalfreedom/economy/EconomicEntityData.java +++ b/Patchwork/src/main/java/me/totalfreedom/economy/EconomicEntityData.java @@ -15,6 +15,13 @@ public interface EconomicEntityData */ long getBalance(); + /** + * Sets the balance of the associated instance + * + * @param newBalance the new balance + */ + void setBalance(final long newBalance); + /** * Adds the provided amount to the associated instance's balance * @@ -30,11 +37,4 @@ public interface EconomicEntityData * @return the new balance */ long removeFromBalance(final long amount); - - /** - * Sets the balance of the associated instance - * - * @param newBalance the new balance - */ - void setBalance(final long newBalance); } diff --git a/Patchwork/src/main/java/me/totalfreedom/economy/MutableTransaction.java b/Patchwork/src/main/java/me/totalfreedom/economy/MutableTransaction.java index 0deebc8..89a14a4 100644 --- a/Patchwork/src/main/java/me/totalfreedom/economy/MutableTransaction.java +++ b/Patchwork/src/main/java/me/totalfreedom/economy/MutableTransaction.java @@ -3,7 +3,8 @@ package me.totalfreedom.economy; /** * A transaction that can be changed. *

- * IMPORTANT NOTE: Please ensure that all modifications of {@link MutableTransaction} happen BEFORE it is passed to a {@link Transactor} implementation + * IMPORTANT NOTE: Please ensure that all modifications of {@link MutableTransaction} happen BEFORE it is passed to a + * {@link Transactor} implementation */ public interface MutableTransaction extends Transaction { diff --git a/Patchwork/src/main/java/me/totalfreedom/event/EventBus.java b/Patchwork/src/main/java/me/totalfreedom/event/EventBus.java index ad4c006..67b8514 100644 --- a/Patchwork/src/main/java/me/totalfreedom/event/EventBus.java +++ b/Patchwork/src/main/java/me/totalfreedom/event/EventBus.java @@ -27,9 +27,10 @@ public class EventBus extends Service public T getEvent(final Class eventClass) { final FEvent e = eventSet.stream() - .filter(event -> event.getEventClass().equals(eventClass)) - .findFirst() - .orElse(null); + .filter(event -> event.getEventClass() + .equals(eventClass)) + .findFirst() + .orElse(null); return eventClass.cast(e); } @@ -37,10 +38,11 @@ public class EventBus extends Service public EventSubscription subscribe(final Class eventClass, final Callback callback) { final Context eventContext = () -> eventSet.stream() - .filter(event -> event.getEventClass().equals(eventClass)) - .findFirst() - .map(eventClass::cast) - .orElse(null); + .filter(event -> event.getEventClass() + .equals(eventClass)) + .findFirst() + .map(eventClass::cast) + .orElse(null); if (eventContext.get() == null) { diff --git a/Patchwork/src/main/java/me/totalfreedom/event/SubscriptionBox.java b/Patchwork/src/main/java/me/totalfreedom/event/SubscriptionBox.java index f072e3c..c93a230 100644 --- a/Patchwork/src/main/java/me/totalfreedom/event/SubscriptionBox.java +++ b/Patchwork/src/main/java/me/totalfreedom/event/SubscriptionBox.java @@ -7,24 +7,32 @@ class SubscriptionBox { private final List> subscriptions; - public SubscriptionBox() { + public SubscriptionBox() + { this.subscriptions = new ArrayList<>(); } - public void addSubscription(final EventSubscription subscription) { + public void addSubscription(final EventSubscription subscription) + { subscriptions.add(subscription); } - public void removeSubscription(final EventSubscription subscription) { + public void removeSubscription(final EventSubscription subscription) + { subscriptions.remove(subscription); } - public void tick() { - subscriptions.forEach(s -> { - if (!s.event().shouldCall()) return; + public void tick() + { + subscriptions.forEach(s -> + { + if (!s.event() + .shouldCall()) return; - s.callback().call(s.event()); - s.event().reset(); + s.callback() + .call(s.event()); + s.event() + .reset(); }); } } diff --git a/Patchwork/src/main/java/me/totalfreedom/logging/BlockInteraction.java b/Patchwork/src/main/java/me/totalfreedom/logging/BlockInteraction.java new file mode 100644 index 0000000..ae64341 --- /dev/null +++ b/Patchwork/src/main/java/me/totalfreedom/logging/BlockInteraction.java @@ -0,0 +1,57 @@ +package me.totalfreedom.logging; + +import org.bukkit.Location; +import org.bukkit.block.data.BlockData; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.time.Instant; +import java.util.UUID; + +public final class BlockInteraction implements Interaction +{ + private final Location location; + private final UUID whoClicked; + private final Instant when; + private final BlockData originalState; + private final BlockData newState; + + public BlockInteraction(final Player player, final BlockData originalState, final BlockData newState) + { + this.location = player.getLocation(); + this.whoClicked = player.getUniqueId(); + this.when = Instant.now(); + this.originalState = originalState; + this.newState = newState; + } + + @Override + public @NotNull UUID getWhoClicked() + { + return whoClicked; + } + + @Override + public @NotNull BlockData getOriginalState() + { + return originalState; + } + + @Override + public @NotNull BlockData getNewState() + { + return newState; + } + + @Override + public @NotNull Instant getWhen() + { + return when; + } + + @Override + public @NotNull Location getLocation() + { + return location; + } +} diff --git a/Patchwork/src/main/java/me/totalfreedom/logging/ContainerInteraction.java b/Patchwork/src/main/java/me/totalfreedom/logging/ContainerInteraction.java new file mode 100644 index 0000000..c09efee --- /dev/null +++ b/Patchwork/src/main/java/me/totalfreedom/logging/ContainerInteraction.java @@ -0,0 +1,63 @@ +package me.totalfreedom.logging; + +import org.bukkit.Location; +import org.bukkit.block.Container; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +import java.time.Instant; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +public final class ContainerInteraction implements Interaction> +{ + private final UUID whoClicked; + private final List originalState; + private final List newState; + private final Instant when; + private final Location location; + + public ContainerInteraction(final Player player, final Container originalState, final Container newState) + { + this.whoClicked = player.getUniqueId(); + this.originalState = Collections.unmodifiableList(Arrays.asList(originalState.getInventory() + .getContents())); + this.newState = Collections.unmodifiableList(Arrays.asList(newState.getInventory() + .getContents())); + this.location = originalState.getLocation(); + this.when = Instant.now(); + } + + @Override + public @NotNull UUID getWhoClicked() + { + return whoClicked; + } + + @Override + public @NotNull List getOriginalState() + { + return originalState; + } + + @Override + public @NotNull List getNewState() + { + return newState; + } + + @Override + public @NotNull Instant getWhen() + { + return when; + } + + @Override + public @NotNull Location getLocation() + { + return location; + } +} diff --git a/Patchwork/src/main/java/me/totalfreedom/logging/Interaction.java b/Patchwork/src/main/java/me/totalfreedom/logging/Interaction.java new file mode 100644 index 0000000..75aeb63 --- /dev/null +++ b/Patchwork/src/main/java/me/totalfreedom/logging/Interaction.java @@ -0,0 +1,33 @@ +package me.totalfreedom.logging; + +import com.google.errorprone.annotations.Immutable; +import org.bukkit.Location; +import org.jetbrains.annotations.NotNull; + +import java.time.Instant; +import java.util.UUID; + +@Immutable +public interface Interaction +{ + @NotNull + static String format(@NotNull final Interaction interaction) + { + return new InteractionFormatter().formatInteraction(interaction); + } + + @NotNull + UUID getWhoClicked(); + + @NotNull + T getOriginalState(); + + @NotNull + T getNewState(); + + @NotNull + Instant getWhen(); + + @NotNull + Location getLocation(); +} diff --git a/Patchwork/src/main/java/me/totalfreedom/logging/InteractionFormatter.java b/Patchwork/src/main/java/me/totalfreedom/logging/InteractionFormatter.java new file mode 100644 index 0000000..19a5f01 --- /dev/null +++ b/Patchwork/src/main/java/me/totalfreedom/logging/InteractionFormatter.java @@ -0,0 +1,138 @@ +package me.totalfreedom.logging; + +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.inventory.ItemStack; + +import java.time.Instant; + +public final class InteractionFormatter +{ + public String formatInteraction(final Interaction interaction) + { + final String location = formatLocation(interaction.getLocation()); + final String world = formatWorld(interaction.getLocation() + .getWorld()); + final String player = interaction.getWhoClicked() + .toString(); + final String block = formatBlock(interaction.getLocation() + .getBlock()); + final String when = formatTime(interaction.getWhen()); + + if (interaction instanceof ContainerInteraction container) + { + final StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("Container ") + .append(block) + .append(" at location ") + .append(location) + .append(" in world ") + .append(world) + .append(" was opened by ") + .append(player) + .append(" at ") + .append(when) + .append("\nHere is a list of items changed:\n"); + + container.getOriginalState() + .stream() + .filter(item -> + { + final ItemStack newItem = container.getNewState() + .stream() + .filter(item2 -> item2.isSimilar(item)) + .findFirst() + .orElse(null); + return newItem == null || newItem.getAmount() != item.getAmount(); + }) + .forEach(item -> + { + final ItemStack newItem = container.getNewState() + .stream() + .filter(item2 -> item2.isSimilar(item)) + .findFirst() + .orElse(null); + stringBuilder.append("Item ") + .append(formatItemStack(item)) + .append(" was changed to ") + .append(formatItemStack(newItem)) + .append("\n"); + }); + + stringBuilder.append("."); + + return stringBuilder.toString(); + } else if (interaction instanceof BlockInteraction blockData) + { + final StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("Block ") + .append(block) + .append(" at location ") + .append(location) + .append(" in world ") + .append(world) + .append(" was changed by ") + .append(player) + .append(" at ") + .append(when) + .append("\nBlock was changed from ") + .append(blockData.getOriginalState() + .getAsString()) + .append(" to ") + .append(blockData.getNewState() + .getAsString()) + .append("."); + + return stringBuilder.toString(); + } else + { + throw new IllegalArgumentException("Unknown interaction type: " + interaction.getClass() + .getName()); + } + } + + // Format: ,, + public String formatLocation(final Location location) + { + return String.format("%s,%s,%s", location.getBlockX(), location.getBlockY(), location.getBlockZ()); + } + + // Format: + public String formatWorld(final World world) + { + return world.getName(); + } + + // Format: + public String formatBlock(final Block block) + { + return block.getType() + .toString() + .toLowerCase(); + } + + public String formatTime(final Instant instant) + { + final String trimmed = instant.toString() + .replaceAll("[TZ]", " "); + final int dotIndex = trimmed.indexOf('.'); + + return (dotIndex != -1) + ? trimmed.substring(0, dotIndex) + : trimmed; + } + + // Format: , + public String formatItemStack(final ItemStack stack) + { + if (stack == null) + { + return String.format("%s,%s", "empty", "0"); + } + + return String.format("%s,%s", stack.getType() + .toString() + .toLowerCase(), stack.getAmount()); + } +} diff --git a/Patchwork/src/main/java/me/totalfreedom/particle/NoteColorUtil.java b/Patchwork/src/main/java/me/totalfreedom/particle/NoteColorUtil.java new file mode 100644 index 0000000..a415118 --- /dev/null +++ b/Patchwork/src/main/java/me/totalfreedom/particle/NoteColorUtil.java @@ -0,0 +1,50 @@ +package me.totalfreedom.particle; + +import org.bukkit.Particle; +import org.bukkit.World; + +/** + * A utility class for the 24 different note colors available in Minecraft. Each note is represented as a double value + * between 0 and 1. Furthermore, each note is a multiple of 1/24 within that range of 0 to 1. + *

+ * For example, the note G is represented as 1/24, or 0.042. The note C is represented as 6/24, or 0.25. + *

+ * When spawning particles, the count must be set to 0 and the extra value set between 0 and 1. The extra value is the + * size of the note particle. To add a color, use one of the provided methods in this class for the xOffset value in + * {@link World#spawnParticle(Particle, double, double, double, int, double, double, double, double)}. The xOffset value + * is the note color, with the yOffset and zOffset values set to 0. + */ +public final class NoteColorUtil +{ + public static final double CYAN_NOTE_F_SHARP_LOW = 0; + public static final double CYAN_NOTE_G = 0.042; + public static final double GRAY_NOTE_G_SHARP = 0.083; + public static final double GRAY_NOTE_A = 0.125; + public static final double GRAY_NOTE_A_SHARP = 0.167; + public static final double MAGENTA_NOTE_B = 0.208; + public static final double RED_NOTE_C = 0.25; + public static final double YELLOW_NOTE_C_SHARP = 0.292; + public static final double YELLOW_NOTE_D = 0.333; + public static final double YELLOW_NOTE_D_SHARP_LOW = 0.375; + public static final double GRAY_NOTE_E = 0.417; + public static final double GRAY_NOTE_F = 0.458; + public static final double GRAY_NOTE_F_SHARP = 0.5; + public static final double LIGHT_BLUE_NOTE_G = 0.542; + public static final double BLUE_NOTE_G_SHARP = 0.583; + public static final double PURPLE_NOTE_A = 0.625; + public static final double PURPLE_NOTE_A_SHARP = 0.667; + public static final double PURPLE_NOTE_B = 0.708; + public static final double GRAY_NOTE_C = 0.75; + public static final double GRAY_NOTE_C_SHARP = 0.792; + public static final double GRAY_NOTE_D = 0.833; + public static final double YELLOW_NOTE_D_SHARP_HIGH = 0.875; + public static final double YELLOW_NOTE_E = 0.917; + public static final double YELLOW_NOTE_F = 0.958; + public static final double CYAN_NOTE_F_SHARP_HIGH = 1; + public static final double BLACK_NOTE_NA = 32768; + + private NoteColorUtil() + { + throw new AssertionError(); + } +} diff --git a/Patchwork/src/main/java/me/totalfreedom/particle/Trail.java b/Patchwork/src/main/java/me/totalfreedom/particle/Trail.java index 68707fb..b6ef17f 100644 --- a/Patchwork/src/main/java/me/totalfreedom/particle/Trail.java +++ b/Patchwork/src/main/java/me/totalfreedom/particle/Trail.java @@ -1,11 +1,124 @@ package me.totalfreedom.particle; +import me.totalfreedom.api.Interpolator; +import me.totalfreedom.utils.InterpolationUtils; import org.bukkit.Color; +import org.bukkit.OfflinePlayer; +import org.bukkit.Particle; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Set; +import java.util.UUID; + +/** + * Represents a Trail instance for a specific player. + */ public interface Trail { + /** + * Returns the UUID of the player associated with the trail. This is for usage with our persistant storage container + * so that we can safely send and retrieve the trails without having to directly reference a player object. + *
+ * TL;DR Memory optimization! + * + * @return The UUID of the player associated with this trail. + */ + @NotNull + UUID getAssociatedPlayerUUID(); + + /** + * Returns the player associated with this trail. Trails are user specific, and should be persistent across all + * usages. This is also used when displaying the particles, as they will be relative to the player's back, which is + * an inverse offset of the player's eye location. We use OfflinePlayer as we can make a simple check and cast to + * determine if the player is online when spawning trails. + * + * @return The player associated with this Trail. + */ + @NotNull + OfflinePlayer getAssociatedPlayer(); + + /** + * Gets the Trail Type of this trail. This is used to determine what type of trail this is, and what + * {@link Particle} it should use. + * + * @return The Trail Type of this trail. + * @see TrailType + */ + @NotNull TrailType getTrailType(); - @Nullable Color getColor(); + /** + * This method is nullable because if the value of {@link #isGradient()} is true, then {@link #getColors()} should + * be used instead, as that will contain the color data for our trail. + *
+ * However, this method will also be null if the particle type is not colorable. + * + * @return The color of the trail, or null if the trail is a gradient or non-colorable. + * @see Particle + * @see #getColors(); + */ + @Nullable + Color getColor(); + + /** + * Sets the static color of the trail. If you are trying to use a gradient, use {@link #setColors(Set)} instead. + *
+ * + * @param color The color to set the trail to. + */ + void setColor(@NotNull Color color); + + /** + * This method is nullable because if the value of {@link #isGradient()} is false, then {@link #getColor()} should + * be used instead, as our trail is a single static color. + *
+ * However, this method will also be null if the particle type is not colorable. + * + * @return The colors of the trail, or null if the trail is not a gradient or non-colorable. + * @see #getColor() + * @see Particle + * @see InterpolationUtils + * @see Interpolator + */ + @Nullable + Set getColors(); + + /** + * Sets the colors of the trail. If you are trying to use a static color, use {@link #setColor(Color)} instead. + *
+ * This should be used for trails that iterate over a set of colors, such as a rainbow trail. + * + * @param colors The colors to set the trail to. It is recommended to use {@link InterpolationUtils} to generate + * interpolated gradients for this. + */ + void setColors(@NotNull Set colors); + + /** + * Validates whether this Trail is a gradient or a static trail. + *
+ * This is entirely based on whether {@link #getColors()} returns null or not. + * + * @return True if {@link #getColors()} is not null, false otherwise. + */ + boolean isGradient(); + + /** + * Gets whether the trail is active. + * + * @return True if the trail is active, false if it is not. + */ + boolean isActive(); + + /** + * Turn the trail on or off. + * + * @param active True if the trail should be active, false if it should not. + */ + void setActive(final boolean active); + + /** + * Spawns a particle (if gradient, the next particle) on the supplied location object. + */ + void spawnParticle(); } diff --git a/Patchwork/src/main/java/me/totalfreedom/particle/TrailType.java b/Patchwork/src/main/java/me/totalfreedom/particle/TrailType.java index e37fae4..e7afbe2 100644 --- a/Patchwork/src/main/java/me/totalfreedom/particle/TrailType.java +++ b/Patchwork/src/main/java/me/totalfreedom/particle/TrailType.java @@ -4,10 +4,30 @@ import org.bukkit.Particle; public enum TrailType { + /** + * Default trail type. Uses {@link Particle#REDSTONE}. This trail is colorable. Use {@link Particle.DustOptions} to + * set the particle properties. + */ DEFAULT(Particle.REDSTONE), + /** + * A trail that uses {@link Particle#HEART}. This is not modifiable and will always have the same size shape and + * color. + */ HEART(Particle.HEART), + /** + * A trail that uses {@link Particle#FLAME}. This is not modifiable and will always have the same size shape and + * color. + */ FLAME(Particle.FLAME), + /** + * A trail that uses {@link Particle#REDSTONE}. This particle however is rainbow-colored by default and cannot have + * additional options set. + */ RAINBOW(Particle.REDSTONE), + /** + * A trail that uses {@link Particle#NOTE}. This is colorable, however you are limited to the 24 different note + * colors available in Minecraft. + */ MUSIC(Particle.NOTE), SNOW(Particle.SNOWBALL), SPELL(Particle.SPELL_MOB), diff --git a/Patchwork/src/main/java/me/totalfreedom/provider/ContextProvider.java b/Patchwork/src/main/java/me/totalfreedom/provider/ContextProvider.java index 9c278f5..680219d 100644 --- a/Patchwork/src/main/java/me/totalfreedom/provider/ContextProvider.java +++ b/Patchwork/src/main/java/me/totalfreedom/provider/ContextProvider.java @@ -3,6 +3,7 @@ package me.totalfreedom.provider; import net.kyori.adventure.text.Component; import org.bukkit.Bukkit; import org.bukkit.Location; +import org.bukkit.Material; import org.bukkit.World; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @@ -14,32 +15,33 @@ import java.util.stream.Stream; public class ContextProvider { - public Object fromString(final String string) + public T fromString(final String string, final Class clazz) { return Stream.of(toBoolean(string), - toDouble(string), - toInt(string), - toLong(string), - toFloat(string), - toPlayer(string), - toWorld(string), - toLocation(string), - toCommandSender(string), - toComponent(string)) - .filter(Objects::nonNull) - .findFirst() - .orElse(string); + toDouble(string), + toInt(string), + toLong(string), + toFloat(string), + toMaterial(string), + toPlayer(string), + toWorld(string), + toLocation(string), + toCommandSender(string), + toComponent(string)) + .filter(Objects::nonNull) + .findFirst() + .map(clazz::cast) + .orElse(null); } private @Nullable Boolean toBoolean(final String string) { - try - { - return Boolean.parseBoolean(string); - } catch (Exception e) - { - return null; - } + // Previously we used Boolean#parseBoolean, but that will always return a value and does not throw + // an exception. This means that if the string is not "true" or "false", it will return false. + if (string.equalsIgnoreCase("true")) return true; + if (string.equalsIgnoreCase("false")) return false; + + return null; } private @Nullable Double toDouble(final String string) @@ -47,7 +49,7 @@ public class ContextProvider try { return Double.parseDouble(string); - } catch (Exception e) + } catch (NumberFormatException ignored) { return null; } @@ -58,7 +60,7 @@ public class ContextProvider try { return Integer.parseInt(string); - } catch (Exception e) + } catch (NumberFormatException ignored) { return null; } @@ -69,7 +71,7 @@ public class ContextProvider try { return Long.parseLong(string); - } catch (Exception e) + } catch (NumberFormatException ignored) { return null; } @@ -80,31 +82,27 @@ public class ContextProvider try { return Float.parseFloat(string); - } catch (Exception e) + } catch (NumberFormatException ignored) { return null; } } + private @Nullable Material toMaterial(final String string) + { + return Material.matchMaterial(string); + } + private @Nullable Player toPlayer(final String string) { return Bukkit.getPlayer(string); } - private @Nullable CommandSender toCommandSender(final String string) - { - if (toPlayer(string) == null) return null; - - return toPlayer(string); - } - private @Nullable World toWorld(final String string) { return Bukkit.getWorld(string); } - // If we decide to, we can "modify" this to use spaces - // and adjust our inputs accordingly. /** * When using this method, the input string must be formatted as *
@@ -127,6 +125,13 @@ public class ContextProvider return new Location(toWorld(split[0]), x, y, z); } + private @Nullable CommandSender toCommandSender(final String string) + { + if (toPlayer(string) == null) return null; + + return toPlayer(string); + } + private @NotNull Component toComponent(final String string) { return Component.text(string); diff --git a/Patchwork/src/main/java/me/totalfreedom/security/Group.java b/Patchwork/src/main/java/me/totalfreedom/security/Group.java new file mode 100644 index 0000000..41b2825 --- /dev/null +++ b/Patchwork/src/main/java/me/totalfreedom/security/Group.java @@ -0,0 +1,41 @@ +package me.totalfreedom.security; + +import net.kyori.adventure.text.Component; + +/** + * Represents a permissible group which holds a set of permissions that can then be applied to a User / Player. + */ +public interface Group extends PermissionHolder +{ + /** + * @return The name of the group. + */ + Component getName(); + + /** + * @return The prefix of the group. + */ + Component getPrefix(); + + /** + * @return The abbreviation of the group. + */ + Component getAbbreviation(); + + /** + * @return The weight of the group. + */ + int getWeight(); + + /** + * If more than one group is marked as default, the first retrieved default group will be used. + * + * @return Whether this is the default group. + */ + boolean isDefault(); + + /** + * @return Whether the group is hidden. + */ + boolean isHidden(); +} diff --git a/Patchwork/src/main/java/me/totalfreedom/security/perm/Node.java b/Patchwork/src/main/java/me/totalfreedom/security/Node.java similarity index 79% rename from Patchwork/src/main/java/me/totalfreedom/security/perm/Node.java rename to Patchwork/src/main/java/me/totalfreedom/security/Node.java index d31f054..8af9348 100644 --- a/Patchwork/src/main/java/me/totalfreedom/security/perm/Node.java +++ b/Patchwork/src/main/java/me/totalfreedom/security/Node.java @@ -1,4 +1,4 @@ -package me.totalfreedom.security.perm; +package me.totalfreedom.security; import org.bukkit.permissions.Permission; @@ -21,11 +21,7 @@ public interface Node boolean isExpired(); - boolean isPermanent(); - boolean isTemporary(); boolean wildcard(); - - boolean negated(); } diff --git a/Patchwork/src/main/java/me/totalfreedom/security/perm/NodeBuilder.java b/Patchwork/src/main/java/me/totalfreedom/security/NodeBuilder.java similarity index 88% rename from Patchwork/src/main/java/me/totalfreedom/security/perm/NodeBuilder.java rename to Patchwork/src/main/java/me/totalfreedom/security/NodeBuilder.java index 6769556..c0503ff 100644 --- a/Patchwork/src/main/java/me/totalfreedom/security/perm/NodeBuilder.java +++ b/Patchwork/src/main/java/me/totalfreedom/security/NodeBuilder.java @@ -1,4 +1,4 @@ -package me.totalfreedom.security.perm; +package me.totalfreedom.security; public interface NodeBuilder { diff --git a/Patchwork/src/main/java/me/totalfreedom/security/perm/NodeType.java b/Patchwork/src/main/java/me/totalfreedom/security/NodeType.java similarity index 70% rename from Patchwork/src/main/java/me/totalfreedom/security/perm/NodeType.java rename to Patchwork/src/main/java/me/totalfreedom/security/NodeType.java index 31c5b83..11293a9 100644 --- a/Patchwork/src/main/java/me/totalfreedom/security/perm/NodeType.java +++ b/Patchwork/src/main/java/me/totalfreedom/security/NodeType.java @@ -1,4 +1,4 @@ -package me.totalfreedom.security.perm; +package me.totalfreedom.security; public enum NodeType { diff --git a/Patchwork/src/main/java/me/totalfreedom/security/perm/PermissionHolder.java b/Patchwork/src/main/java/me/totalfreedom/security/PermissionHolder.java similarity index 87% rename from Patchwork/src/main/java/me/totalfreedom/security/perm/PermissionHolder.java rename to Patchwork/src/main/java/me/totalfreedom/security/PermissionHolder.java index f15ec15..05f7e7c 100644 --- a/Patchwork/src/main/java/me/totalfreedom/security/perm/PermissionHolder.java +++ b/Patchwork/src/main/java/me/totalfreedom/security/PermissionHolder.java @@ -1,4 +1,4 @@ -package me.totalfreedom.security.perm; +package me.totalfreedom.security; import org.bukkit.permissions.Permissible; diff --git a/Patchwork/src/main/java/me/totalfreedom/security/ban/Ban.java b/Patchwork/src/main/java/me/totalfreedom/security/ban/Ban.java deleted file mode 100644 index 438b5f8..0000000 --- a/Patchwork/src/main/java/me/totalfreedom/security/ban/Ban.java +++ /dev/null @@ -1,78 +0,0 @@ -package me.totalfreedom.security.ban; - -import org.jetbrains.annotations.Nullable; - -import java.time.Instant; -import java.util.UUID; - -/** - * Represents a physical ban entry. This is used to store information about a ban, - * such as the player who was banned, the reason for why they were banned, the individual who issued the ban, - * when the ban expires, and when the ban was created. - *
- * Ban information is stored in the Database with the {@link BanID} as the PRIMARY KEY. - */ -public interface Ban -{ - /** - * Gets the ID of this ban. This is an object which represents a string prefixed with either a T or a P, - * and suffixed with a 6-10 digit numerical code. This is used to identify the ban in the database, and for - * easier ban referencing. - * - * @return The ID of this ban. - */ - BanID getBanID(); - - /** - * Gets the UUID of the player who was banned. This is formatted as a UUID - * which allows us to retrieve the particular player instance, if applicable, and also - * have a true identifier to check against user logins. - * - * @return The UUID of the player who was banned. - */ - UUID getOffenderID(); - - /** - * Gets the reason that the player was banned for. Typically, the default reason is "You are banned!". - * We've forced implementations to require a reason, as it's important to know why a player was banned. - * - * @return The reason that the player was banned for. - */ - String getReason(); - - /** - * Gets the username of the individual who issued the ban. This is not a reliable way to store data, but - * in our case, we should not need to interact with the ban issuer from the code. This is simply for - * reference purposes. - * - * @return The username of the individual who issued the ban. - */ - String getBanIssuer(); - - /** - * Gets the {@link Instant} which this ban was created. - * - * @return The ban's creation time. - */ - Instant getCreationTime(); - - /** - * Gets the {@link Instant} which this ban is due to expire, if applicable. - * This method is annotated as {@link Nullable}, as permanent bans do not have an expiry date. - * - * @return The ban's expiry time, or null if the ban is permanent. - */ - @Nullable - Instant getExpiry(); - - /** - * Checks if the ban has expired. This will return false if: - *

    - *
  • The {@link Instant} returned by {@link #getExpiry()} is null.
  • - *
  • The {@link Instant} returned by {@link #getExpiry()} is after the current time.
  • - *
- * - * @return True if the ban has expired, false otherwise. - */ - boolean isExpired(); -} diff --git a/Patchwork/src/main/java/me/totalfreedom/security/ban/BanID.java b/Patchwork/src/main/java/me/totalfreedom/security/ban/BanID.java deleted file mode 100644 index 79c175f..0000000 --- a/Patchwork/src/main/java/me/totalfreedom/security/ban/BanID.java +++ /dev/null @@ -1,52 +0,0 @@ -package me.totalfreedom.security.ban; - -/** - * Represents an ID for a ban. These are formatted either as: - *

- * P-00129381 - *
- * T-00128381 - *
- *

- * Where P marks a ban as permanent, and T marks a ban as temporary. - */ -public interface BanID -{ - /** - * This method returns the full Ban ID. - * - * @return The actual ID. - */ - String getID(); - - /** - * This method returns the ban type denominator character for the Ban ID. - * This would either be T or P, where T = temporary and P = permanent. - * - * @return The ban type denominator character for the Ban ID. - */ - char getIDPrefix(); - - /** - * Gets the numerical tag of this ban ID. - * This would be the numerical section of the full Ban ID. - * - * @return The numerical tag of this ban ID. - */ - int getNumericalTag(); - - /** - * Checks the prefix of the Ban ID to see whether if it is permanent. - * - * @return true if the Ban ID is prefixed with a P, false otherwise. - */ - boolean isPermanent(); - - default boolean matches(BanID other) { - if (other == null) { - return false; - } - return (getIDPrefix() == other.getIDPrefix()) - && (getNumericalTag() == other.getNumericalTag()); - } -} diff --git a/Patchwork/src/main/java/me/totalfreedom/security/perm/Group.java b/Patchwork/src/main/java/me/totalfreedom/security/perm/Group.java deleted file mode 100644 index a0f1dcd..0000000 --- a/Patchwork/src/main/java/me/totalfreedom/security/perm/Group.java +++ /dev/null @@ -1,18 +0,0 @@ -package me.totalfreedom.security.perm; - -import net.kyori.adventure.text.Component; - -public interface Group extends PermissionHolder -{ - Component getName(); - - Component getPrefix(); - - Component getAbbreviation(); - - int getWeight(); - - boolean isDefault(); - - boolean isHidden(); -} diff --git a/Patchwork/src/main/java/me/totalfreedom/service/FreedomExecutor.java b/Patchwork/src/main/java/me/totalfreedom/service/FreedomExecutor.java index bc728ff..f090314 100644 --- a/Patchwork/src/main/java/me/totalfreedom/service/FreedomExecutor.java +++ b/Patchwork/src/main/java/me/totalfreedom/service/FreedomExecutor.java @@ -2,58 +2,141 @@ package me.totalfreedom.service; import me.totalfreedom.base.CommonsBase; import org.bukkit.Bukkit; -import org.jetbrains.annotations.NotNull; +import org.bukkit.plugin.java.JavaPlugin; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; +/** + * This is a holder class for {@link Executor} objects that are used to delegate runnable tasks to the Bukkit Scheduler. + * This class is here for both convenience purposes, and also for the sake of providing easy access to executors for + * {@link CompletableFuture} invocations. + */ public class FreedomExecutor { + /** + * An executor which runs tasks synchronously. + */ private final Executor syncExecutor; + /** + * An executor which runs tasks asynchronously. + */ private final Executor asyncExecutor; + /** + * Creates a new {@link FreedomExecutor} instance. + */ public FreedomExecutor() { - syncExecutor = r -> Bukkit.getScheduler().runTask(CommonsBase.getInstance(), r); - asyncExecutor = r -> Bukkit.getScheduler().runTaskAsynchronously(CommonsBase.getInstance(), r); + syncExecutor = r -> Bukkit.getScheduler() + .runTask(CommonsBase.getInstance(), r); + asyncExecutor = r -> Bukkit.getScheduler() + .runTaskAsynchronously(CommonsBase.getInstance(), r); } + /** + * Creates a new {@link Executor} that is capable of executing a runnable one singular time, synchronously. + * + * @param plugin The plugin to run the task for. + * @return A new {@link Executor} instance. + */ + public Executor singleExecutor(final JavaPlugin plugin) + { + return r -> Bukkit.getScheduler() + .runTask(plugin, r); + } + + /** + * Creates a new {@link Executor} that is capable of executing a runnable one singular time, synchronously. This + * Executor will wait for the supplied delay before executing the runnable. + * + * @param plugin The plugin to run the task for. + * @param delay The delay to wait before executing the runnable. + * @return A new {@link Executor} instance. + */ + public Executor delayedExecutor(final JavaPlugin plugin, final long delay) + { + return r -> Bukkit.getScheduler() + .runTaskLater(plugin, r, delay); + } + + /** + * Creates a new {@link Executor} tthat is capable of executing a runnable on a periodic basis, synchronously. This + * executor can also be supplied a delay to indicate it should wait the specified amount of time before executing + * the runnable for the first time. + * + * @param plugin The plugin to run the task for. + * @param initialDelay The delay to wait before executing the runnable for the first time. + * @param period The period to wait between each execution of the runnable. + * @return A new {@link Executor} instance. + */ + public Executor periodicExecutor(final JavaPlugin plugin, final long initialDelay, final long period) + { + return r -> Bukkit.getScheduler() + .runTaskTimer(plugin, r, initialDelay, period); + } + + /** + * Creates a new {@link Executor} that is capable of executing a runnable one singular time, asynchronously. + * + * @param plugin The plugin to run the task for. + * @return A new {@link Executor} instance. + */ + public Executor asynchronousSingleExecutor(final JavaPlugin plugin) + { + return r -> Bukkit.getScheduler() + .runTaskAsynchronously(plugin, r); + } + + /** + * Creates a new {@link Executor} that is capable of executing a runnable one singular time, asynchronously. This + * Executor will wait for the supplied delay before executing the runnable. + * + * @param plugin The plugin to run the task for. + * @param delay The delay to wait before executing the runnable. + * @return A new {@link Executor} instance. + */ + public Executor asynchronousDelayedExecutor(final JavaPlugin plugin, final long delay) + { + return r -> Bukkit.getScheduler() + .runTaskLaterAsynchronously(plugin, r, delay); + } + + /** + * Creates a new {@link Executor} tthat is capable of executing a runnable on a periodic basis, asynchronously. This + * executor can also be supplied a delay to indicate it should wait the specified amount of time before executing + * the runnable for the first time. + * + * @param plugin The plugin to run the task for. + * @param delay The delay to wait before executing the runnable for the first time. + * @param period The period to wait between each execution of the runnable. + * @return A new {@link Executor} instance. + */ + public Executor asynchronousPeriodicExecutor(final JavaPlugin plugin, final long delay, final long period) + { + return r -> Bukkit.getScheduler() + .runTaskTimerAsynchronously(plugin, r, delay, period); + } + + /** + * Gets the synchronous executor instance. This is a convenience for {@link CompletableFuture} invocations, when + * defining a custom executor for the {@link CompletableFuture}. + * + * @return The synchronous executor instance. + */ public Executor getSync() { return syncExecutor; } + /** + * Gets the asynchronous executor instance. This is a convenience for {@link CompletableFuture} invocations, when + * defining a custom executor for the {@link CompletableFuture}. + * + * @return The asynchronous executor instance. + */ public Executor getAsync() { return asyncExecutor; } - - public Executor scheduled(final long delay, final long period) - { - return r -> Bukkit.getScheduler() - .runTaskTimer( - CommonsBase.getInstance(), - r, - delay, - period); - } - - public Executor scheduledAsync(final long delay, final long period) - { - return r -> Bukkit.getScheduler() - .runTaskTimerAsynchronously( - CommonsBase.getInstance(), - r, - delay, - period); - } - - public void runSync(@NotNull final Task task) - { - getSync().execute(task); - } - - public void runAsync(@NotNull final Task task) - { - getAsync().execute(task); - } } diff --git a/Patchwork/src/main/java/me/totalfreedom/service/Service.java b/Patchwork/src/main/java/me/totalfreedom/service/Service.java index ff4c9fc..927fb36 100644 --- a/Patchwork/src/main/java/me/totalfreedom/service/Service.java +++ b/Patchwork/src/main/java/me/totalfreedom/service/Service.java @@ -1,47 +1,47 @@ package me.totalfreedom.service; -import me.totalfreedom.base.CommonsBase; - +/** + * Represents a ticking service. Services may be asynchronous or synchronous, however there are some restrictions: + *
    + *
  • Sync services may not have a complexity greater than 5.
  • + *
  • Async services may not interact with the Bukkit API in any form.
  • + *
+ */ public abstract class Service { + /** + * The name of the service. + */ private final String name; - private boolean isActive = false; + /** + * Creates a new service with the given name. + * + * @param name The name of the service. + */ protected Service(final String name) { this.name = name; - - } - - public void start() - { - isActive = true; - CommonsBase.getInstance() - .getExecutor() - .getSync() - .execute(() -> - { - while (isActive) - { - tick(); - } - }); - } - - public void stop() - { - isActive = false; } + /** + * This method is called every single tick, or at least, every tick interval described by the holding + * {@link ServiceSubscription}. Since this runs every single tick, the body of this method should not have a + * complexity higher than 5. This includes Cyclomatic, Cognitive, and NPath complexities. If the service is + * asynchronous, there is a bit more flexibility with the complexity rating, extending to no more than 10. However, + * it's generally good practice to keep the complexity of ticking services as low as possible to avoid extensive + * resource consumption. + * + * @see ServiceSubscription + * @see SubscriptionProvider + */ public abstract void tick(); + /** + * @return The name of the service. + */ public String getName() { return name; } - - public boolean isActive() - { - return isActive; - } } diff --git a/Patchwork/src/main/java/me/totalfreedom/service/ServiceSubscription.java b/Patchwork/src/main/java/me/totalfreedom/service/ServiceSubscription.java new file mode 100644 index 0000000..dd8b543 --- /dev/null +++ b/Patchwork/src/main/java/me/totalfreedom/service/ServiceSubscription.java @@ -0,0 +1,189 @@ +package me.totalfreedom.service; + +import org.bukkit.Bukkit; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.scheduler.BukkitTask; +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.Executor; + +/** + * Represents a subscription to a {@link Service}. + *

+ * Subscriptions contain some information about the service itself and it's presence on the scheduler. For example, + * {@link #getServiceId()} will return the ID of the task which was returned by the scheduler. Subscriptions also manage + * the state of the service, using {@link #isActive()} to determine if the service is currently running. + *
+ *
+ * The subscription itself provides type inference to safely store the actual service instance. This is useful for when + * we need to access the service itself, without calling to the service directly. + * + * @param The type of service this subscription is for. + */ +public final class ServiceSubscription +{ + /** + * The service this subscription is for. + */ + private final T service; + /** + * Whether this is an asynchronous service. + */ + private final boolean async; + /** + * The executor used to schedule the service. + */ + private final Executor executor; + /** + * The ID of the service from the associated {@link BukkitTask} which was returned by the Scheduler. + */ + private final int serviceId; + + /** + * Whether the service is currently running. + */ + private boolean isActive = false; + + /** + * Creates a new subscription for the given service. By default, this method will mark this service as a synchronous + * service. This will also initialize the default interval to a single tick. + *
+ * If you are trying to create an asynchronous service, use + * {@link #ServiceSubscription(JavaPlugin, Service, boolean)} instead. + *
+ * If you would like to define a custom interval, use either {@link #ServiceSubscription(JavaPlugin, Service, long)} + * or {@link #ServiceSubscription(JavaPlugin, Service, long, boolean)} (for asynchronous services). + * + * @param plugin The plugin which owns the service. + * @param service The service to subscribe to. + */ + ServiceSubscription(@NotNull final JavaPlugin plugin, @NotNull final T service) + { + this(plugin, service, 1L, false); + } + + /** + * Creates a new subscription for the given service. This will initialize the default interval to a single tick. + *
+ * If you would like to define a custom interval, use either {@link #ServiceSubscription(JavaPlugin, Service, long)} + * or {@link #ServiceSubscription(JavaPlugin, Service, long, boolean)} (for asynchronous services). + * + * @param plugin The plugin which owns the service. + * @param service The service to subscribe to. + * @param async Whether the service should be scheduled asynchronously. + */ + ServiceSubscription(@NotNull final JavaPlugin plugin, @NotNull final T service, final boolean async) + { + this(plugin, service, 1L, async); + } + + /** + * Creates a new subscription for the given service. By default, this will mark the service as synchronous. When + * defining a custom interval, the interval should be less than 20L (the number of ticks in a second). For anything + * that requires an interval greater than 1 second, use a {@link Task} instead. + *
+ * If you are trying to create an asynchronous service, use + * {@link #ServiceSubscription(JavaPlugin, Service, long, boolean)} instead. + * + * @param plugin The plugin which owns the service. + * @param service The service to subscribe to. + * @param interval The interval at which the service should be scheduled. + */ + ServiceSubscription(@NotNull final JavaPlugin plugin, @NotNull final T service, final long interval) + { + this(plugin, service, interval, false); + } + + /** + * Creates a new subscription for the given service. When defining a custom interval, the interval should be less + * than 20L (the number of ticks in a second). For anything that requires an interval greater than 1 second, use a + * {@link Task} instead. + * + * @param plugin The plugin which owns the service. + * @param service The service to subscribe to. + * @param interval The interval at which the service should be scheduled. + * @param async Whether the service should be scheduled asynchronously. + */ + ServiceSubscription(@NotNull final JavaPlugin plugin, @NotNull final T service, + final long interval, final boolean async) + { + this.service = service; + this.async = async; + + final int[] tempId = new int[1]; + + if (async) + { + this.executor = r -> + { + final BukkitTask task = Bukkit.getScheduler() + .runTaskTimerAsynchronously(plugin, r, 0, interval); + tempId[0] = task.getTaskId(); + }; + } else + { + this.executor = r -> + { + final BukkitTask task = Bukkit.getScheduler() + .runTaskTimer(plugin, r, 0, interval); + tempId[0] = task.getTaskId(); + }; + } + + this.serviceId = tempId[0]; + } + + /** + * Starts the service. + */ + public void start() + { + this.isActive = true; + this.executor.execute(service::tick); + } + + /** + * Stops the service. + */ + public void stop() + { + this.isActive = false; + Bukkit.getScheduler() + .cancelTask(this.getServiceId()); + } + + /** + * Returns the ID of the service from the associated {@link BukkitTask} which was returned by the Scheduler. + * + * @return The ID of the service. + */ + public int getServiceId() + { + return serviceId; + } + + /** + * @return The service this subscription is for. + */ + @NotNull + public T getService() + { + return service; + } + + /** + * @return Whether this is an asynchronous service. + */ + public boolean isAsync() + { + return async; + } + + /** + * @return Whether the service is currently running. + */ + public boolean isActive() + { + return isActive; + } +} diff --git a/Patchwork/src/main/java/me/totalfreedom/service/SubscriptionProvider.java b/Patchwork/src/main/java/me/totalfreedom/service/SubscriptionProvider.java new file mode 100644 index 0000000..3a3c8ab --- /dev/null +++ b/Patchwork/src/main/java/me/totalfreedom/service/SubscriptionProvider.java @@ -0,0 +1,125 @@ +package me.totalfreedom.service; + +import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +/** + * Provides static methods for creating {@link ServiceSubscription} and {@link TaskSubscription} objects. + */ +public final class SubscriptionProvider +{ + /** + * Prevents instantiation of this class. + */ + private SubscriptionProvider() + { + throw new AssertionError(); + } + + /** + * Creates a new {@link ServiceSubscription} object that will run the given {@link Service} object on the main + * thread a single time. + * + * @param plugin The plugin that owns the service. + * @param service The service to run. + * @param Type inference to maintain the service type. + * @return The new {@link ServiceSubscription} object. + */ + @NotNull + @Contract(value = "_, _ -> new", pure = false) + public static final ServiceSubscription syncService(@NotNull final JavaPlugin plugin, + @NotNull final S service) + { + return new ServiceSubscription<>(plugin, service); + } + + /** + * Creates a new {@link ServiceSubscription} object that will run the given {@link Service} object on the main + * thread at the given interval. + * + * @param plugin The plugin that owns the service. + * @param interval The interval to run the service at. + * @param service The service to run. + * @param Type inference to maintain the service type. + * @return The new {@link ServiceSubscription} object. + */ + @NotNull + @Contract(value = "_,_,_ -> new", pure = false) + public static final ServiceSubscription syncService(@NotNull final JavaPlugin plugin, + final long interval, + @NotNull final S service) + { + return new ServiceSubscription<>(plugin, service, interval); + } + + /** + * Creates a new {@link ServiceSubscription} object that will run the given {@link Service} object on the default + * tick interval, which is a single tick. This method will create an asynchronous service. + * + * @param plugin The plugin that owns the service. + * @param service The service to run. + * @param Type inference to maintain the service type. + * @return The new {@link ServiceSubscription} object. + */ + @NotNull + @Contract(value = "_, _ -> new", pure = false) + public static final ServiceSubscription asyncService(@NotNull final JavaPlugin plugin, + @NotNull final S service) + { + return new ServiceSubscription<>(plugin, service, true); + } + + /** + * Creates a new {@link ServiceSubscription} object that will run the given {@link Service} object on the given + * interval. This method will create an asynchronous service. + * + * @param plugin The plugin that owns the service. + * @param interval The interval to run the service at. + * @param service The service to run. + * @param Type inference to maintain the service type. + * @return The new {@link ServiceSubscription} object. + */ + @NotNull + @Contract(value = "_,_,_ -> new", pure = false) + public static final ServiceSubscription asyncService(@NotNull final JavaPlugin plugin, + final long interval, + @NotNull final S service) + { + return new ServiceSubscription<>(plugin, service, interval, true); + } + + /** + * Creates a new {@link TaskSubscription} object that will run the given {@link Task} object synchronously on the + * main thread. + * + * @param plugin The plugin that owns the task. + * @param task The task to run. + * @param Type inference to maintain the task type. + * @return The new {@link TaskSubscription} object. + */ + @NotNull + @Contract(value = "_, _ -> new", pure = false) + public static final TaskSubscription runSyncTask(@NotNull final JavaPlugin plugin, + @NotNull final T task) + { + return new TaskSubscription<>(plugin, task, false); + } + + /** + * Creates a new {@link TaskSubscription} object that will run the given {@link Task} object asynchronously on the + * main thread. + * + * @param plugin The plugin that owns the task. + * @param task The task to run. + * @param Type inference to maintain the task type. + * @return The new {@link TaskSubscription} object. + */ + @NotNull + @Contract(value = "_, _ -> new", pure = false) + public static final TaskSubscription runAsyncTask(@NotNull final JavaPlugin plugin, + @NotNull final T task) + { + return new TaskSubscription<>(plugin, task, true); + } +} diff --git a/Patchwork/src/main/java/me/totalfreedom/service/Task.java b/Patchwork/src/main/java/me/totalfreedom/service/Task.java index 941ae6f..3275705 100644 --- a/Patchwork/src/main/java/me/totalfreedom/service/Task.java +++ b/Patchwork/src/main/java/me/totalfreedom/service/Task.java @@ -1,24 +1,154 @@ package me.totalfreedom.service; -public interface Task extends Runnable +import me.totalfreedom.utils.DurationTools; +import org.bukkit.scheduler.BukkitRunnable; + +import java.time.Duration; + +/** + * Represents a task that can be run asynchronously or synchronously. + */ +public abstract class Task extends BukkitRunnable { - void start(); + /** + * The name of the task. + */ + private final String name; + /** + * The delay of the task. + */ + private final long delay; + /** + * The interval of the task. + */ + private final long interval; - void stop(); + /** + * Creates a new task with the given name. This will initialize a task with no initail delay and no interval. + * + * @param name The name of the task. + */ + protected Task(final String name) + { + this(name, -1L, -1L); + } - boolean isRunning(); + /** + * Creates a new task with the given name, delay, and interval. + *
+ * It's important to note that the delay and interval are in ticks. One tick is equal to 1/20th of a second, which + * means there are 20 ticks are in one second. If your interval is intended to be anything less than 20 ticks, you + * should use a {@link Service} instead. + * + * @param name The name of the task. + * @param delay The delay of the task. + * @param interval The interval of the task. + */ + protected Task(final String name, final long delay, final long interval) + { + this.name = name; + this.delay = delay; + this.interval = interval; + } - String getName(); + /** + * Creates a new task with the given name and delay. This will intialize a single execute task with an initial delay + * before execution. + * + * @param name The name of the task. + * @param delay How long the task should wait before executing. + */ + protected Task(final String name, final long delay) + { + this(name, delay, -1L); + } - boolean isRepeating(); + /** + * Creates a new task with the given name and delay. This is the same as longs, except that here, we naturally + * support durations which are automatically converted to ticks for you. This means that using + * {@link Duration#ofSeconds(long)} will work as expected. + * + * @param name The name of the task. + * @param delay How long the task should wait before executing. + */ + protected Task(final String name, final Duration delay) + { + this(name, DurationTools.getTicks(delay), -1L); + } - void setRepeating(long interval); + /** + * Creates a new task with the given name, delay, and interval. This is the same as longs, except that here, we + * naturally support durations which are automatically converted to ticks for you. This means that using + * {@link Duration#ofSeconds(long)} will work as expected. + * + * @param name The name of the task. + * @param delay How long the task should wait before executing. + * @param interval How long the task should wait between executions. + */ + protected Task(final String name, final Duration delay, final Duration interval) + { + this(name, DurationTools.getTicks(delay), DurationTools.getTicks(interval)); + } - boolean isDelayed(); + /** + * Creates a new task with the given name, delay, and interval. This method is a convenience method to use a + * {@link Duration} for the interval, while also being able to specify the delay as -1L so the task does not have an + * initial delay before execution. + * + * @param name The name of the task. + * @param delay The delay of the task. + * @param interval The interval of the task. + */ + protected Task(final String name, final long delay, final Duration interval) + { + this(name, delay, DurationTools.getTicks(interval)); + } - void setDelayed(long delay); + /** + * @return True if the task is running, false otherwise. + */ + public boolean isRunning() + { + return !isCancelled(); + } - long getInterval(); + /** + * @return The name of the task. + */ + public String getName() + { + return name; + } - long getDelay(); + /** + * @return True if the task is repeating, false otherwise. + */ + public boolean isRepeating() + { + return this.interval > 0L; + } + + /** + * @return True if the task is delayed, false otherwise. + */ + public boolean isDelayed() + { + return this.delay > 0L; + } + + /** + * @return The interval between each task execution. + */ + public long getInterval() + { + return interval; + } + + /** + * @return The initial delay before the first execution of this task. + */ + public long getDelay() + { + return delay; + } } diff --git a/Patchwork/src/main/java/me/totalfreedom/service/TaskSubscription.java b/Patchwork/src/main/java/me/totalfreedom/service/TaskSubscription.java new file mode 100644 index 0000000..9e5c3e1 --- /dev/null +++ b/Patchwork/src/main/java/me/totalfreedom/service/TaskSubscription.java @@ -0,0 +1,218 @@ +package me.totalfreedom.service; + +import me.totalfreedom.utils.container.Pair; +import org.bukkit.Bukkit; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.scheduler.BukkitScheduler; +import org.bukkit.scheduler.BukkitTask; + +import java.util.concurrent.Executor; + +/** + * Represents a subscription to a task. Task subscriptions offer a nice wrapper for managing tasks, which are inevitably + * just bukkit runnables with a bit more lenience in terms of instantiation modification and execution. It also offers a + * more intuitive way to manage our tasks; rather than having to keep track of task ids for each {@link BukkitTask} + * object that gets returned by the {@link BukkitScheduler}. + * + * @param The type of task. + */ +public final class TaskSubscription +{ + /** + * The task that is being subscribed to. + */ + private final T task; + /** + * The task id of the task. + */ + private final int taskId; + /** + * True if the task is async, false otherwise. + */ + private final boolean async; + /** + * The executor that will execute the task. + */ + private final Executor executor; + + /** + * True if the task is active, false otherwise. By default, this is set to false, and will be marked as true when + * the task is started. + */ + private boolean isActive = false; + + /** + * Creates a new task subscription. + * + * @param plugin The plugin which owns the task. + * @param task The task that is being subscribed to. + * @param async True if the task is async, false otherwise. + */ + TaskSubscription(final JavaPlugin plugin, final T task, final boolean async) + { + this.task = task; + this.async = async; + + final long delay = (task.isDelayed() + ? task.getDelay() + : 0); + final long period = (task.isRepeating() + ? task.getInterval() + : 0); + + final Pair integerExecutorPair = async + ? getAsync(plugin, delay, period) + : getSync(plugin, delay, period); + + this.executor = integerExecutorPair.value(); + this.taskId = integerExecutorPair.key(); + } + + /** + * Gets the executor and task id for an async task, wrapped in a {@link Pair}<{@link Integer}, + * {@link Executor}>. + *
+ * This will return a Pair where {@link Pair#value()} is an asynchronous executor. + * + * @param plugin The plugin which owns the task. + * @param delay The delay of the task. + * @param period The period of the task. + * @return The executor and task id for an asynchronous task. + */ + private Pair getAsync(final JavaPlugin plugin, final long delay, final long period) + { + final Executor executor1; + final int[] tempId = new int[1]; + if (period != 0) + { + executor1 = r -> + { + final BukkitTask task1 = Bukkit.getScheduler() + .runTaskTimerAsynchronously(plugin, r, delay, period); + tempId[0] = task1.getTaskId(); + }; + } else if (delay != 0) + { + executor1 = r -> + { + final BukkitTask task1 = Bukkit.getScheduler() + .runTaskLaterAsynchronously(plugin, r, delay); + tempId[0] = task1.getTaskId(); + }; + } else + { + executor1 = r -> + { + final BukkitTask task1 = Bukkit.getScheduler() + .runTaskAsynchronously(plugin, r); + tempId[0] = task1.getTaskId(); + }; + } + + return new Pair<>(tempId[0], executor1); + } + + /** + * Gets the executor and task id for a sync task, wrapped in a {@link Pair}<{@link Integer}, + * {@link Executor}>. + *
+ * This will return a Pair where {@link Pair#value()} is a synchronous executor. + * + * @param plugin The plugin which owns the task. + * @param delay The delay of the task. + * @param period The period of the task. + * @return The executor and task id for a synchronous task. + */ + private Pair getSync(final JavaPlugin plugin, final long delay, final long period) + { + final Executor executor1; + final int[] tempId = new int[1]; + + if (period != 0) + { + executor1 = r -> + { + final BukkitTask task1 = Bukkit.getScheduler() + .runTaskTimer(plugin, r, delay, period); + tempId[0] = task1.getTaskId(); + }; + } else if (delay != 0) + { + executor1 = r -> + { + final BukkitTask task1 = Bukkit.getScheduler() + .runTaskLater(plugin, r, delay); + tempId[0] = task1.getTaskId(); + }; + } else + { + executor1 = r -> + { + final BukkitTask task1 = Bukkit.getScheduler() + .runTask(plugin, r); + tempId[0] = task1.getTaskId(); + }; + } + + return new Pair<>(tempId[0], executor1); + } + + /** + * Starts the task. + */ + public void start() + { + this.isActive = true; + executor.execute(task); + } + + /** + * Stops the task. + */ + public void stop() + { + this.isActive = false; + Bukkit.getScheduler() + .cancelTask(this.getTaskId()); + } + + /** + * @return The task id of the task. + */ + public int getTaskId() + { + return taskId; + } + + /** + * @return The task that is being subscribed to. + */ + public T getTask() + { + return task; + } + + /** + * @return True if the task is async, false otherwise. + */ + public boolean isAsync() + { + return async; + } + + /** + * @return The executor that will execute the task. + */ + public Executor getExecutor() + { + return executor; + } + + /** + * @return True if the task is active, false otherwise. + */ + public boolean isActive() + { + return isActive; + } +} diff --git a/Patchwork/src/main/java/me/totalfreedom/shop/Reactable.java b/Patchwork/src/main/java/me/totalfreedom/shop/Reactable.java new file mode 100644 index 0000000..86ddec4 --- /dev/null +++ b/Patchwork/src/main/java/me/totalfreedom/shop/Reactable.java @@ -0,0 +1,22 @@ +package me.totalfreedom.shop; + +import me.totalfreedom.economy.EconomicEntity; +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.text.Component; + +import java.time.Duration; + +public interface Reactable +{ + Component getReactionMessage(); + + Duration getReactionDuration(); + + ReactionType getReactionType(); + + long getReward(); + + void display(final Audience audience); + + void onReact(final EconomicEntity entity); +} diff --git a/Patchwork/src/main/java/me/totalfreedom/shop/Reaction.java b/Patchwork/src/main/java/me/totalfreedom/shop/Reaction.java new file mode 100644 index 0000000..0b96cc7 --- /dev/null +++ b/Patchwork/src/main/java/me/totalfreedom/shop/Reaction.java @@ -0,0 +1,68 @@ +package me.totalfreedom.shop; + +import me.totalfreedom.display.BossBarDisplay; +import net.kyori.adventure.text.Component; + +import java.time.Duration; + +/** + * Represents a chat reaction that can be performed by a player. + */ +public abstract class Reaction implements Reactable +{ + private final Duration reactionDuration; + private final ReactionType reactionType; + private final long reward; + private Component reactionMessage = Component.empty(); + + protected Reaction(final ReactionType type) + { + this(50L, type); + } + + protected Reaction(final long reward, final ReactionType type) + { + this(30L, reward, type); + } + + protected Reaction(final long seconds, final long reward, final ReactionType reactionType) + { + this(Duration.ofSeconds(seconds), reward, reactionType); + } + + protected Reaction(final Duration duration, final long reward, final ReactionType reactionType) + { + this.reward = reward; + this.reactionDuration = duration; + this.reactionType = reactionType; + } + + @Override + public Component getReactionMessage() + { + return reactionMessage; + } + + public void setReactionMessage(final Component message) + { + this.reactionMessage = message; + } + + @Override + public Duration getReactionDuration() + { + return reactionDuration; + } + + @Override + public ReactionType getReactionType() + { + return reactionType; + } + + @Override + public BossBarDisplay getBossBarDisplay() + { + + } +} diff --git a/Patchwork/src/main/java/me/totalfreedom/shop/ReactionTask.java b/Patchwork/src/main/java/me/totalfreedom/shop/ReactionTask.java new file mode 100644 index 0000000..cceb27e --- /dev/null +++ b/Patchwork/src/main/java/me/totalfreedom/shop/ReactionTask.java @@ -0,0 +1,56 @@ +package me.totalfreedom.shop; + +import io.papermc.paper.event.player.AsyncChatEvent; +import me.totalfreedom.base.CommonsBase; +import me.totalfreedom.display.BossBarDisplay; +import me.totalfreedom.display.BossBarTimer; +import me.totalfreedom.economy.EconomicEntity; +import me.totalfreedom.service.Task; +import net.kyori.adventure.bossbar.BossBar; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + +public class ReactionTask extends Task implements Listener +{ + private final Reaction reaction; + private final BossBarDisplay bossBarDisplay; + + public ReactionTask(final String name, final Reaction reaction) + { + super(name, -1L, -1); + this.reaction = reaction; + final BossBar bossBar = BossBarDisplay.builder() + .setName(reaction.getReactionMessage()) + .setColor(BossBar.Color.GREEN) + .setProgress(0.0F) + .build(); + + this.bossBarDisplay = new BossBarDisplay(bossBar); + } + + @Override + public void run() + { + if (isCancelled()) + { + } + + final BossBarTimer timer = new BossBarTimer(bossBarDisplay, reaction.getReactionDuration()); + timer.runTaskTimer(CommonsBase.getInstance(), 0L, timer.getInterval()); + } + + @EventHandler + public void onPlayerChat(final AsyncChatEvent event) + { + if (event.message() + .equals(reaction.getReactionMessage())) + { + final EconomicEntity entity = CommonsBase.getInstance() + .getRegistrations() + .getUserRegistry() + .getUser(event.getPlayer()); + + reaction.onReact(entity); + } + } +} diff --git a/Patchwork/src/main/java/me/totalfreedom/shop/ReactionType.java b/Patchwork/src/main/java/me/totalfreedom/shop/ReactionType.java new file mode 100644 index 0000000..e36a3da --- /dev/null +++ b/Patchwork/src/main/java/me/totalfreedom/shop/ReactionType.java @@ -0,0 +1,6 @@ +package me.totalfreedom.shop; + +public enum ReactionType +{ + COPYCAT, UNSCRAMBLE, MATH; +} diff --git a/Patchwork/src/main/java/me/totalfreedom/sql/SQL.java b/Patchwork/src/main/java/me/totalfreedom/sql/SQL.java index 717f685..4e9adc1 100644 --- a/Patchwork/src/main/java/me/totalfreedom/sql/SQL.java +++ b/Patchwork/src/main/java/me/totalfreedom/sql/SQL.java @@ -1,14 +1,11 @@ package me.totalfreedom.sql; -import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.concurrent.CompletableFuture; public interface SQL { - CompletableFuture getConnection(final String url); - CompletableFuture prepareStatement(final String query, final Object... args); CompletableFuture executeQuery(final String query, final Object... args); diff --git a/Patchwork/src/main/java/me/totalfreedom/sql/SQLProperties.java b/Patchwork/src/main/java/me/totalfreedom/sql/SQLProperties.java index b7ee58d..5a0566b 100644 --- a/Patchwork/src/main/java/me/totalfreedom/sql/SQLProperties.java +++ b/Patchwork/src/main/java/me/totalfreedom/sql/SQLProperties.java @@ -19,18 +19,6 @@ public interface SQLProperties return properties; } - String getDriver(); - - String getHost(); - - String getPort(); - - String getDatabase(); - - String getUsername(); - - String getPassword(); - default String toURLPlain() { return String.format("jdbc:%s://%s:%s/%s", @@ -40,6 +28,14 @@ public interface SQLProperties this.getDatabase()); } + String getDriver(); + + String getHost(); + + String getPort(); + + String getDatabase(); + default String toURLWithLogin() { return String.format("jdbc:%s://%s:%s/%s?user=%s&password=%s", @@ -50,4 +46,8 @@ public interface SQLProperties this.getUsername(), this.getPassword()); } + + String getUsername(); + + String getPassword(); } diff --git a/Patchwork/src/main/java/me/totalfreedom/user/User.java b/Patchwork/src/main/java/me/totalfreedom/user/User.java index b83876e..f925492 100644 --- a/Patchwork/src/main/java/me/totalfreedom/user/User.java +++ b/Patchwork/src/main/java/me/totalfreedom/user/User.java @@ -1,12 +1,18 @@ package me.totalfreedom.user; -import me.totalfreedom.security.perm.PermissionHolder; import me.totalfreedom.economy.EconomicEntity; import me.totalfreedom.economy.EconomicEntityData; +import me.totalfreedom.security.PermissionHolder; import net.kyori.adventure.text.Component; public interface User extends PermissionHolder, EconomicEntity { + @Override + default EconomicEntityData getEconomicData() + { + return getUserData(); + } + // Implement a few EconomicEntity methods in the User interface @Override default String getName() @@ -14,12 +20,6 @@ public interface User extends PermissionHolder, EconomicEntity return getUserData().getUsername(); } - @Override - default EconomicEntityData getEconomicData() - { - return getUserData(); - } - UserData getUserData(); Component getDisplayName(); diff --git a/Patchwork/src/main/java/me/totalfreedom/user/UserData.java b/Patchwork/src/main/java/me/totalfreedom/user/UserData.java index e7b4253..2cfb8d1 100644 --- a/Patchwork/src/main/java/me/totalfreedom/user/UserData.java +++ b/Patchwork/src/main/java/me/totalfreedom/user/UserData.java @@ -1,7 +1,7 @@ package me.totalfreedom.user; -import me.totalfreedom.security.perm.Group; import me.totalfreedom.economy.EconomicEntityData; +import me.totalfreedom.security.Group; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -27,15 +27,7 @@ public interface UserData extends EconomicEntityData void resetPlaytime(); - boolean isFrozen(); - - void setFrozen(boolean frozen); - boolean canInteract(); void setInteractionState(boolean canInteract); - - boolean isCaged(); - - void setCaged(boolean caged); } diff --git a/Patchwork/src/main/java/me/totalfreedom/utils/DurationTools.java b/Patchwork/src/main/java/me/totalfreedom/utils/DurationTools.java new file mode 100644 index 0000000..b629f09 --- /dev/null +++ b/Patchwork/src/main/java/me/totalfreedom/utils/DurationTools.java @@ -0,0 +1,28 @@ +package me.totalfreedom.utils; + +import java.time.Duration; + +public final class DurationTools +{ + // One tick is 1/20th of a second which is about 50ms. + public static final Duration TICK = Duration.ofMillis(50L); + // One second is 20 ticks. + public static final Duration SECOND = TICK.multipliedBy(20L); + // One minute is 60 seconds. + public static final Duration MINUTE = SECOND.multipliedBy(60L); + + private DurationTools() + { + throw new AssertionError(); + } + + public static final long getTicks(final Duration duration) + { + return duration.toMillis() / 50L; + } + + public static final Duration getTickedSeconds(final long seconds) + { + return SECOND.multipliedBy(seconds); + } +} diff --git a/Patchwork/src/main/java/me/totalfreedom/utils/InterpolationUtils.java b/Patchwork/src/main/java/me/totalfreedom/utils/InterpolationUtils.java new file mode 100644 index 0000000..7be41c0 --- /dev/null +++ b/Patchwork/src/main/java/me/totalfreedom/utils/InterpolationUtils.java @@ -0,0 +1,136 @@ +package me.totalfreedom.utils; + +import me.totalfreedom.api.Interpolator; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextColor; +import org.bukkit.Color; + +import java.util.LinkedHashSet; +import java.util.Set; + +public final class InterpolationUtils +{ + private InterpolationUtils() + { + throw new AssertionError(); + } + + public static Set rainbow(final int length) + { + final LinkedHashSet base = new LinkedHashSet<>(); + final Set redToOrange = hsvGradient(length, Color.RED, Color.ORANGE, InterpolationUtils::linear); + final Set orangeToYellow = hsvGradient(length, Color.ORANGE, Color.YELLOW, InterpolationUtils::linear); + final Set yellowToGreen = hsvGradient(length, Color.YELLOW, Color.GREEN, InterpolationUtils::linear); + final Set greenToBlue = hsvGradient(length, Color.GREEN, Color.BLUE, InterpolationUtils::linear); + final Set blueToPurple = hsvGradient(length, Color.BLUE, Color.PURPLE, InterpolationUtils::linear); + final Set purpleToRed = hsvGradient(length, Color.PURPLE, Color.RED, InterpolationUtils::linear); + base.addAll(redToOrange); + base.addAll(orangeToYellow); + base.addAll(yellowToGreen); + base.addAll(greenToBlue); + base.addAll(blueToPurple); + base.addAll(purpleToRed); + return base; + } + + private static Set hsvGradient(final int length, final Color from, final Color to, + final Interpolator interpolator) + { + // returns a float-array where hsv[0] = hue, hsv[1] = saturation, hsv[2] = value/brightness + final float[] hsvFrom = java.awt.Color.RGBtoHSB(from.getRed(), from.getGreen(), from.getBlue(), null); + final float[] hsvTo = java.awt.Color.RGBtoHSB(to.getRed(), to.getGreen(), to.getBlue(), null); + + final double[] h = interpolator.interpolate(hsvFrom[0], hsvTo[0], length); + final double[] s = interpolator.interpolate(hsvFrom[1], hsvTo[1], length); + final double[] v = interpolator.interpolate(hsvFrom[2], hsvTo[2], length); + + final LinkedHashSet gradient = new LinkedHashSet<>(); + + for (int i = 0; i < length; i++) + { + final int rgb = java.awt.Color.HSBtoRGB((float) h[i], (float) s[i], (float) v[i]); + final Color color = Color.fromRGB(rgb); + gradient.add(color); + } + return gradient; + } + + private static double[] linear(final double from, final double to, final int max) + { + final double[] res = new double[max]; + for (int i = 0; i < max; i++) + { + res[i] = from + i * ((to - from) / (max - 1)); + } + return res; + } + + public static Set rainbowComponent(final int length) + { + final LinkedHashSet base = new LinkedHashSet<>(); + final Set redToOrange = componentRGBGradient(length, NamedTextColor.RED, + NamedTextColor.GOLD, InterpolationUtils::linear); + final Set orangeToYellow = componentRGBGradient(length, NamedTextColor.GOLD, + NamedTextColor.YELLOW, InterpolationUtils::linear); + final Set yellowToGreen = componentRGBGradient(length, NamedTextColor.YELLOW, + NamedTextColor.GREEN, InterpolationUtils::linear); + final Set greenToBlue = componentRGBGradient(length, NamedTextColor.GREEN, + NamedTextColor.BLUE, InterpolationUtils::linear); + final Set blueToPurple = componentRGBGradient(length, NamedTextColor.BLUE, + NamedTextColor.LIGHT_PURPLE, + InterpolationUtils::linear); + final Set purpleToRed = componentRGBGradient(length, TextColor.color(75, 0, 130), + TextColor.color(255, 0, 0), InterpolationUtils::linear); + base.addAll(redToOrange); + base.addAll(orangeToYellow); + base.addAll(yellowToGreen); + base.addAll(greenToBlue); + base.addAll(blueToPurple); + base.addAll(purpleToRed); + return base; + } + + private static Set componentRGBGradient(final int length, final TextColor from, final TextColor to, + final Interpolator interpolator) + { + final double[] r = interpolator.interpolate(from.red(), to.red(), length); + final double[] g = interpolator.interpolate(from.green(), to.green(), length); + final double[] b = interpolator.interpolate(from.blue(), to.blue(), length); + + final LinkedHashSet gradient = new LinkedHashSet<>(); + + for (int i = 0; i < length; i++) + { + final TextColor color = TextColor.color((int) r[i], (int) g[i], (int) b[i]); + gradient.add(color); + } + return gradient; + } + + public static Set standardGradient(final int length, final Color from, final Color to) + { + return rgbGradient(length, from, to, InterpolationUtils::linear); + } + + private static Set rgbGradient(final int length, final Color from, final Color to, + final Interpolator interpolator) + { + final double[] r = interpolator.interpolate(from.getRed(), to.getRed(), length); + final double[] g = interpolator.interpolate(from.getGreen(), to.getGreen(), length); + final double[] b = interpolator.interpolate(from.getBlue(), to.getBlue(), length); + + final LinkedHashSet gradient = new LinkedHashSet<>(); + + for (int i = 0; i < length; i++) + { + final Color color = Color.fromRGB((int) r[i], (int) g[i], (int) b[i]); + gradient.add(color); + } + return gradient; + } + + public static Set standardComponentGradient(final int length, final TextColor from, final TextColor to) + { + return componentRGBGradient(length, from, to, InterpolationUtils::linear); + } +} diff --git a/Patchwork/src/main/java/me/totalfreedom/utils/Shaper.java b/Patchwork/src/main/java/me/totalfreedom/utils/ShapeUtils.java similarity index 80% rename from Patchwork/src/main/java/me/totalfreedom/utils/Shaper.java rename to Patchwork/src/main/java/me/totalfreedom/utils/ShapeUtils.java index 66ecd9d..e4b533c 100644 --- a/Patchwork/src/main/java/me/totalfreedom/utils/Shaper.java +++ b/Patchwork/src/main/java/me/totalfreedom/utils/ShapeUtils.java @@ -7,20 +7,21 @@ import java.util.LinkedList; import java.util.List; import java.util.function.DoubleUnaryOperator; -public class Shaper +public class ShapeUtils { private final double start; private final double end; private final World world; - public Shaper(final World world, final double start, final double end) + public ShapeUtils(final World world, final double start, final double end) { this.start = start; this.end = end; this.world = world; } - public List generate(final int count, final DoubleUnaryOperator x, final DoubleUnaryOperator y, final DoubleUnaryOperator z) + public List generate(final int count, final DoubleUnaryOperator x, final DoubleUnaryOperator y, + final DoubleUnaryOperator z) { final double step = (start - end) / (count - 1); final LinkedList lset = new LinkedList<>(); diff --git a/Patchwork/src/main/java/me/totalfreedom/utils/Identity.java b/Patchwork/src/main/java/me/totalfreedom/utils/container/Identity.java similarity index 89% rename from Patchwork/src/main/java/me/totalfreedom/utils/Identity.java rename to Patchwork/src/main/java/me/totalfreedom/utils/container/Identity.java index d186093..0770962 100644 --- a/Patchwork/src/main/java/me/totalfreedom/utils/Identity.java +++ b/Patchwork/src/main/java/me/totalfreedom/utils/container/Identity.java @@ -1,4 +1,4 @@ -package me.totalfreedom.utils; +package me.totalfreedom.utils.container; import java.util.UUID; diff --git a/Patchwork/src/main/java/me/totalfreedom/utils/Pair.java b/Patchwork/src/main/java/me/totalfreedom/utils/container/Pair.java similarity index 52% rename from Patchwork/src/main/java/me/totalfreedom/utils/Pair.java rename to Patchwork/src/main/java/me/totalfreedom/utils/container/Pair.java index 9ab5e2f..0cf4e76 100644 --- a/Patchwork/src/main/java/me/totalfreedom/utils/Pair.java +++ b/Patchwork/src/main/java/me/totalfreedom/utils/container/Pair.java @@ -1,4 +1,4 @@ -package me.totalfreedom.utils; +package me.totalfreedom.utils.container; public record Pair(K key, V value) { diff --git a/Patchwork/src/main/java/me/totalfreedom/utils/container/Trio.java b/Patchwork/src/main/java/me/totalfreedom/utils/container/Trio.java new file mode 100644 index 0000000..90bf673 --- /dev/null +++ b/Patchwork/src/main/java/me/totalfreedom/utils/container/Trio.java @@ -0,0 +1,30 @@ +package me.totalfreedom.utils.container; + +public final class Trio +{ + private final A a; + private final B b; + private final C c; + + public Trio(final A a, final B b, final C c) + { + this.a = a; + this.b = b; + this.c = c; + } + + public A getPrimary() + { + return a; + } + + public B getSecondary() + { + return b; + } + + public C getTertiary() + { + return c; + } +} diff --git a/Patchwork/src/main/java/me/totalfreedom/utils/container/UnaryTrio.java b/Patchwork/src/main/java/me/totalfreedom/utils/container/UnaryTrio.java new file mode 100644 index 0000000..cf2e27f --- /dev/null +++ b/Patchwork/src/main/java/me/totalfreedom/utils/container/UnaryTrio.java @@ -0,0 +1,30 @@ +package me.totalfreedom.utils.container; + +public class UnaryTrio +{ + private final T primary; + private final T secondary; + private final T tertiary; + + public UnaryTrio(final T primary, final T secondary, final T tertiary) + { + this.primary = primary; + this.secondary = secondary; + this.tertiary = tertiary; + } + + public T getPrimary() + { + return primary; + } + + public T getSecondary() + { + return secondary; + } + + public T getTertiary() + { + return tertiary; + } +} diff --git a/Patchwork/src/main/java/me/totalfreedom/utils/FreedomAdventure.java b/Patchwork/src/main/java/me/totalfreedom/utils/kyori/FreedomAdventure.java similarity index 88% rename from Patchwork/src/main/java/me/totalfreedom/utils/FreedomAdventure.java rename to Patchwork/src/main/java/me/totalfreedom/utils/kyori/FreedomAdventure.java index fc28035..3dc548a 100644 --- a/Patchwork/src/main/java/me/totalfreedom/utils/FreedomAdventure.java +++ b/Patchwork/src/main/java/me/totalfreedom/utils/kyori/FreedomAdventure.java @@ -1,4 +1,4 @@ -package me.totalfreedom.utils; +package me.totalfreedom.utils.kyori; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; @@ -6,27 +6,29 @@ import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import java.util.function.Supplier; /** - * This class contains the only reference to plain text component serializer, and allows access to it via wrapper functions. + * This class contains the only reference to plain text component serializer, and allows access to it via wrapper + * functions. */ public class FreedomAdventure { + private static final PlainTextComponentSerializer PLAIN_TEXT_COMPONENT_SERIALIZER = + PlainTextComponentSerializer.plainText(); + private FreedomAdventure() { throw new UnsupportedOperationException("Instantiation of a static utility class is not supported."); } - private static final PlainTextComponentSerializer PLAIN_TEXT_COMPONENT_SERIALIZER = PlainTextComponentSerializer.plainText(); + public static String toPlainText(final Supplier supplier) + { + return toPlainText(supplier.get()); + } public static String toPlainText(final Component component) { return PLAIN_TEXT_COMPONENT_SERIALIZER.serialize(component); } - public static String toPlainText(final Supplier supplier) - { - return toPlainText(supplier.get()); - } - public static Supplier supplyPlainText(final Supplier supplier) { return new StringRepresentationSupplier(supplier.get()); diff --git a/Patchwork/src/main/java/me/totalfreedom/utils/FreedomMiniMessage.java b/Patchwork/src/main/java/me/totalfreedom/utils/kyori/FreedomMiniMessage.java similarity index 54% rename from Patchwork/src/main/java/me/totalfreedom/utils/FreedomMiniMessage.java rename to Patchwork/src/main/java/me/totalfreedom/utils/kyori/FreedomMiniMessage.java index e615906..3db5cdc 100644 --- a/Patchwork/src/main/java/me/totalfreedom/utils/FreedomMiniMessage.java +++ b/Patchwork/src/main/java/me/totalfreedom/utils/kyori/FreedomMiniMessage.java @@ -19,7 +19,7 @@ * THE SOFTWARE. */ -package me.totalfreedom.utils; +package me.totalfreedom.utils.kyori; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.TextDecoration; @@ -32,46 +32,53 @@ import net.kyori.adventure.text.minimessage.tag.standard.StandardTags; */ public class FreedomMiniMessage { + private static final MiniMessage unsafe = MiniMessage.miniMessage(); + private static final MiniMessage safe = MiniMessage.builder() + .tags(TagResolver.resolver( + StandardTags.color(), + StandardTags.rainbow(), + StandardTags.gradient(), + StandardTags.newline(), + StandardTags.decorations(TextDecoration.ITALIC), + StandardTags.decorations(TextDecoration.BOLD), + StandardTags.decorations(TextDecoration.STRIKETHROUGH), + StandardTags.decorations(TextDecoration.UNDERLINED) + )) + .build(); + private FreedomMiniMessage() { throw new UnsupportedOperationException("Instantiation of a static utility class is not supported."); } - private static final MiniMessage unsafe = MiniMessage.miniMessage(); - - private static final MiniMessage safe = MiniMessage.builder().tags(TagResolver.resolver( - StandardTags.color(), - StandardTags.rainbow(), - StandardTags.gradient(), - StandardTags.newline(), - StandardTags.decorations(TextDecoration.ITALIC), - StandardTags.decorations(TextDecoration.BOLD), - StandardTags.decorations(TextDecoration.STRIKETHROUGH), - StandardTags.decorations(TextDecoration.UNDERLINED) - )).build(); - /** - * Deserializes an input string using an instance of MiniMessage that is either safe (resolves only a specific set of tags) - * or unsafe (resolves all tags). - * @param safe Whether to use a safe instance of MiniMessage - * @param input An input string formatted with MiniMessage's input - * @param placeholders Custom placeholders to use when processing the input - * @return A processed Component + * Deserializes an input string using an instance of MiniMessage that is either safe (resolves only a specific set + * of tags) or unsafe (resolves all tags). + * + * @param safe Whether to use a safe instance of MiniMessage + * @param input An input string formatted with MiniMessage's input + * @param placeholders Custom placeholders to use when processing the input + * @return A processed Component */ public static Component deserialize(boolean safe, String input, TagResolver... placeholders) { - return (safe ? FreedomMiniMessage.safe : unsafe).deserialize(input, placeholders); + return (safe + ? FreedomMiniMessage.safe + : unsafe).deserialize(input, placeholders); } /** * Serializes an input component using an instance of MiniMessage that is either safe (resolves only a specific set - * of tags) or unsafe (resolves all tags). - * @param safe Whether to use a safe instance of MiniMessage - * @param input An already processed component - * @return A processed Component + * of tags) or unsafe (resolves all tags). + * + * @param safe Whether to use a safe instance of MiniMessage + * @param input An already processed component + * @return A processed Component */ public static String serialize(boolean safe, Component input) { - return (safe ? FreedomMiniMessage.safe : unsafe).serialize(input); + return (safe + ? FreedomMiniMessage.safe + : unsafe).serialize(input); } } diff --git a/Patchwork/src/main/java/me/totalfreedom/utils/KyoriConstants.java b/Patchwork/src/main/java/me/totalfreedom/utils/kyori/KyoriConstants.java similarity index 76% rename from Patchwork/src/main/java/me/totalfreedom/utils/KyoriConstants.java rename to Patchwork/src/main/java/me/totalfreedom/utils/kyori/KyoriConstants.java index 6748faf..0ffbe29 100644 --- a/Patchwork/src/main/java/me/totalfreedom/utils/KyoriConstants.java +++ b/Patchwork/src/main/java/me/totalfreedom/utils/kyori/KyoriConstants.java @@ -1,4 +1,4 @@ -package me.totalfreedom.utils; +package me.totalfreedom.utils.kyori; import me.totalfreedom.base.CommonsBase; import net.kyori.adventure.chat.ChatType; @@ -9,11 +9,11 @@ import org.bukkit.entity.Player; import org.bukkit.plugin.java.JavaPlugin; /** - * This class contains different methods to provide {@link ChatType.Bound} instances for sending messages to players in game. - * This is now a requirement for all message requests to players due to the new chat signature system. + * This class contains different methods to provide {@link ChatType.Bound} instances for sending messages to players in + * game. This is now a requirement for all message requests to players due to the new chat signature system. *
- * Even though Scissors has this feature disabled, upstream (Paper) and Kyori Adventure - * have made the appropriate API changes to accomodate chat signatures. + * Even though Scissors has this feature disabled, upstream (Paper) and Kyori Adventure have made the appropriate API + * changes to accomodate chat signatures. *
* As a result, we need to conform to those specifications even if we do not use this feature. */ @@ -30,18 +30,11 @@ public final class KyoriConstants { } - public static ChatType.Bound fromPlugin(final JavaPlugin plugin) - { - final String name = plugin.getName(); - final Component component = Component.text(name, NamedTextColor.GOLD); - return type.bind(component); - } - /** * Represents a Chat Bound for a plugin. *
- * This is a convenience method so you are not required to dependency inject - * your plugin instance any time that you need a Bound. + * This is a convenience method so you are not required to dependency inject your plugin instance any time that you + * need a Bound. * * @param pluginClass The plugin class to get the plugin instance from. * @return A ChatType.Bound instance for the plugin. @@ -53,12 +46,18 @@ public final class KyoriConstants return fromPlugin(plugin); } + public static ChatType.Bound fromPlugin(final JavaPlugin plugin) + { + final String name = plugin.getName(); + final Component component = Component.text(name, NamedTextColor.GOLD); + return type.bind(component); + } + /** - * Represents a Chat Bound for a player. - * Chat bounds are required for sending messages to players. + * Represents a Chat Bound for a player. Chat bounds are required for sending messages to players. *
- * The chat bound is a representation of a validated chat signature, - * which is tied directly to the user's account name. In our case, this is the player's name. + * The chat bound is a representation of a validated chat signature, which is tied directly to the user's account + * name. In our case, this is the player's name. * * @param player The player to get the bound for. * @return A ChatType.Bound instance for the player. @@ -71,8 +70,9 @@ public final class KyoriConstants /** * Represents a Chat Bound for the console. *
- * The chat bound is a representation of a validated chat signature, - * which is tied directly to the user's account name. In our case, this is the player's name. + * The chat bound is a representation of a validated chat signature, which is tied directly to the user's account + * name. In our case, this is the player's name. + * * @param sender The console to get the bound for. * @return A ChatType.Bound instance for the console. */ diff --git a/Patchwork/src/main/java/me/totalfreedom/utils/FreedomLogger.java b/Patchwork/src/main/java/me/totalfreedom/utils/logging/FreedomLogger.java similarity index 80% rename from Patchwork/src/main/java/me/totalfreedom/utils/FreedomLogger.java rename to Patchwork/src/main/java/me/totalfreedom/utils/logging/FreedomLogger.java index 4ec4b82..97cec23 100644 --- a/Patchwork/src/main/java/me/totalfreedom/utils/FreedomLogger.java +++ b/Patchwork/src/main/java/me/totalfreedom/utils/logging/FreedomLogger.java @@ -1,5 +1,6 @@ -package me.totalfreedom.utils; +package me.totalfreedom.utils.logging; +import me.totalfreedom.utils.kyori.FreedomAdventure; import net.kyori.adventure.audience.Audience; import net.kyori.adventure.chat.ChatType; import net.kyori.adventure.chat.SignedMessage; @@ -32,13 +33,28 @@ public class FreedomLogger implements Audience } /** - * This method allows you to log a message to the console. + * This method allows you to log a message to the console, while also returning a Component that could be used to + * message a player. * * @param message The message to send. + * @return A component representation of the message. */ - public void info(final String message) + public Component info(final Supplier message) { - logger.info(message); + logger.info(message.get()); + return Component.text(message.get()); + } + + /** + * This method allows you to log a component to the console, while also returning a String representation of the + * component + * + * @param component The component to send. + * @return A string representation of the message. + */ + public String infoComponent(final Supplier component) + { + return this.infoComponent(component.get()); } /** @@ -56,30 +72,13 @@ public class FreedomLogger implements Audience } /** - * This method allows you to log a message to the console, - * while also returning a Component that could be used to - * message a player. + * This method allows you to log a message to the console. * * @param message The message to send. - * @return A component representation of the message. */ - public Component info(final Supplier message) + public void info(final String message) { - logger.info(message.get()); - return Component.text(message.get()); - } - - /** - * This method allows you to log a component to the console, - * while also returning a String representation of the - * component - * - * @param component The component to send. - * @return A string representation of the message. - */ - public String infoComponent(final Supplier component) - { - return this.infoComponent(component.get()); + logger.info(message); } /** @@ -105,9 +104,8 @@ public class FreedomLogger implements Audience } /** - * This method logs an error message to the console. - * It is highly recommended to deconstruct the stack trace and pass it - * in a more readable format to this method. + * This method logs an error message to the console. It is highly recommended to deconstruct the stack trace and + * pass it in a more readable format to this method. * * @param message The message to send. */ @@ -116,6 +114,42 @@ public class FreedomLogger implements Audience logger.error(message); } + /** + * This method allows you to log an exception directly to the console. + * + * @param th The exception to log. + */ + public void error(final Throwable th) + { + logger.error("An error occurred:\n", th); + } + + /** + * This method allows you to log an error message to the console, while also returning a Component that could be + * used to message a player. It is highly recommended that you deconstruct and limit the stack trace before passing + * it to this method, if you are intending to use it for player communication. + * + * @param message The message to send. + * @return A component representation of the message. + */ + public Component error(final Supplier message) + { + logger.error(message.get()); + return Component.text(message.get()); + } + + /** + * This method allows you to log an error component to the console, while also returning a String representation of + * the error component. + * + * @param component The component to send. + * @return A String representation of the component. + */ + public String errorComponent(final Supplier component) + { + return this.errorComponent(component.get()); + } + /** * This method logs an error component to the console. * @@ -131,76 +165,9 @@ public class FreedomLogger implements Audience } /** - * This method allows you to log an exception directly to the console. - * - * @param th The exception to log. - */ - public void error(final Throwable th) - { - logger.error("An error occurred:\n", th); - } - - /** - * This method allows you to log an error message to the console, - * while also returning a Component that could be used to - * message a player. It is highly recommended that you deconstruct and limit - * the stack trace before passing it to this method, if you are intending to - * use it for player communication. - * - * @param message The message to send. - * @return A component representation of the message. - */ - public Component error(final Supplier message) - { - logger.error(message.get()); - return Component.text(message.get()); - } - - /** - * This method allows you to log an error component to the console, - * while also returning a String representation of the error - * component. - * - * @param component The component to send. - * @return A String representation of the component. - */ - public String errorComponent(final Supplier component) - { - return this.errorComponent(component.get()); - } - - /** - * This method allows you to log a debug message to the console. - * This method will only log if debug mode is enabled. - * - * @param message The message to send. - */ - public void debug(final String message) - { - if (debug) - logger.debug(message); - } - - /** - * This method allows you to log a debug component to the console. - * This method will only log if debug mode is enabled. - * - * @param component The component to send. - */ - public String debugComponent(final Component component) - { - final String plainText = FreedomAdventure.toPlainText(component); - - this.debug(plainText); - - return plainText; - } - - /** - * This method allows you to log a debug message to the console, - * while also returning a Component that could be used to - * message a player. This method will only log if debug mode is enabled. - * If debug mode is not enabled, this method will return an empty component. + * This method allows you to log a debug message to the console, while also returning a Component that could be used + * to message a player. This method will only log if debug mode is enabled. If debug mode is not enabled, this + * method will return an empty component. * * @param message The message to send. * @return A component representation of the message. @@ -216,8 +183,8 @@ public class FreedomLogger implements Audience } /** - * This method allows you to log a debug component to the console, - * while also returning a String representation of the debug component. + * This method allows you to log a debug component to the console, while also returning a String representation of + * the debug component. * * @param component The component to send. * @return A String representation of the message. @@ -231,6 +198,33 @@ public class FreedomLogger implements Audience return ""; } + /** + * This method allows you to log a debug component to the console. This method will only log if debug mode is + * enabled. + * + * @param component The component to send. + */ + public String debugComponent(final Component component) + { + final String plainText = FreedomAdventure.toPlainText(component); + + this.debug(plainText); + + return plainText; + } + + /** + * This method allows you to log a debug message to the console. This method will only log if debug mode is + * enabled. + * + * @param message The message to send. + */ + public void debug(final String message) + { + if (debug) + logger.debug(message); + } + @Override public void sendMessage(@NotNull final ComponentLike message) @@ -267,6 +261,9 @@ public class FreedomLogger implements Audience @Override public void sendMessage(@NotNull final SignedMessage signedMessage, final ChatType.@NotNull Bound boundChatType) { - this.info(signedMessage.message()); // TODO: We might want to investigate whether this logs the ENTIRE message, including unsigned & signed content, or only the signed part. This method was written in the assumption that it provided all content. + this.info( + signedMessage.message()); // TODO: We might want to investigate whether this logs the ENTIRE message, + // including unsigned & signed content, or only the signed part. This method was written in the assumption + // that it provided all content. } } diff --git a/README.md b/README.md index 22cfc0d..2b2bbba 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,27 @@ [Google GSON]: https://github.com/google/gson "Google GSON" + [Jetbrains Annotations]: https://github.com/JetBrains/JetBrains.Annotations "JetBrains Annotations" + [Lombok]: https://github.com/projectlombok/lombok "Lombok" + [Apache Commons]: https://github.com/apache/commons-lang "Apache Commons" + [SLF4J]: https://github.com/qos-ch/slf4j "SLF4J" + [Paper]: https://github.com/PaperMC/Paper "Paper" + [Kyori Adventure]: https://github.com/KyoriPowered/adventure "Kyori Adventure" + [Reflections API]: https://github.com/ronmamo/reflections "Reflections API" + [TotalFreedomMod]: https://github.com/AtlasMediaGroup/TotalFreedomMod "TotalFreedomMod" ##### + ![Header Image](https://media.discordapp.net/attachments/436759124953399296/1107175759941996544/20230514_002037_0000.png) ### + [](https://discord.gg/4PdtmrVNRx) ![GitHub contributors](https://img.shields.io/github/contributors/AtlasMediaGroup/Freedom-Network-Suite?style=for-the-badge) ![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/AtlasMediaGroup/Freedom-Network-Suite?style=for-the-badge) @@ -21,6 +31,7 @@ ![Codacy grade](https://img.shields.io/codacy/grade/176b8003312c4602afb9be7706aef146?style=for-the-badge) ### + [](https://docs.google.com/document/d/197fwNo076RsCiPW6e6QWaGEzTGnDcRuf5FBA6lNeiPE) [](https://github.com/AtlasMediaGroup/Freedom-Network-Suite/blob/kitchen-sink/LICENSE.md) ![GitHub top language](https://img.shields.io/github/languages/top/AtlasMediaGroup/Freedom-Network-Suite?style=for-the-badge&logo=github) @@ -41,16 +52,18 @@ Honorable mention: [](https://github.com/plexusorg/Plex) This proof-of-concept also uses the following libraries: - - [Google GSON] for Json interpretation - - [Jetbrains Annotations] for additional compiler annotations - - [Lombok] for boilerplate generation - - [Apache Commons] for various utilities - - [SLF4J] for logging - - [Paper] for the server implementation - - [Kyori Adventure] for chat formatting - - [Reflections API] for reflections + +- [Google GSON] for Json interpretation +- [Jetbrains Annotations] for additional compiler annotations +- [Lombok] for boilerplate generation +- [Apache Commons] for various utilities +- [SLF4J] for logging +- [Paper] for the server implementation +- [Kyori Adventure] for chat formatting +- [Reflections API] for reflections # Developers + [](https://github.com/Paldiu)
[](https://github.com/VideoGameSmash12) @@ -58,7 +71,9 @@ This proof-of-concept also uses the following libraries: [](https://github.com/allinkdev) # To Do List + Patchwork: + - [x] Logging System - [x] SQL API - [x] Economy API @@ -72,22 +87,25 @@ Patchwork: - [ ] Event API *(In Progress...)* Datura: + - [ ] Permission Handling *(In Progress...)* - [ ] Permission Registration & Assignment *(In Progress...)* - [ ] SQL Data Handling *(In Progress...)* -- [ ] Configuration Implementations +- [ ] Configuration Implementations - [ ] User Data Implementations *(In Progress...)* - [x] Punishment Systems (e.x. Locker, Halter, Cager) Fossil: + - [x] Economy Implementation - [ ] Particle Implementation / Trails *(In Progress...)* - [ ] Command Implementations *(In Progress...)* - [ ] Implement a shop for the economy *(In Progress...)* -- [ ] Chat reaction / game system +- [ ] Chat reaction / game system - [ ] Jumppads Corvo: + - [ ] Service Implementation - [ ] Service Handling - [ ] Task Implementation