diff --git a/Datura/src/main/java/me/totalfreedom/datura/Datura.java b/Datura/src/main/java/me/totalfreedom/datura/Datura.java index 5ef1440..da40da4 100644 --- a/Datura/src/main/java/me/totalfreedom/datura/Datura.java +++ b/Datura/src/main/java/me/totalfreedom/datura/Datura.java @@ -27,11 +27,11 @@ public class Datura extends JavaPlugin CommonsBase.getInstance() .getRegistrations() - .getServiceRegistry() + .getServiceTaskRegistry() .registerService(SubscriptionProvider.syncService(this, locker)); CommonsBase.getInstance() .getRegistrations() - .getServiceRegistry() + .getServiceTaskRegistry() .registerService(SubscriptionProvider.syncService(this, cager)); Bukkit.getPluginManager() 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 cdd00f1..50a716b 100644 --- a/Datura/src/main/java/me/totalfreedom/datura/perms/FreedomUser.java +++ b/Datura/src/main/java/me/totalfreedom/datura/perms/FreedomUser.java @@ -45,7 +45,7 @@ public class FreedomUser implements User final Datura datura = CommonsBase.getInstance() .getRegistrations() .getModuleRegistry() - .getModule(Datura.class) + .getProvider(Datura.class) .getModule(); UserData data = SimpleUserData.fromSQL(datura.getSQL(), uuid.toString()); diff --git a/Datura/src/main/java/me/totalfreedom/datura/user/ServerEconomyHolder.java b/Datura/src/main/java/me/totalfreedom/datura/user/ServerEconomyHolder.java index 6db83ee..e9771f3 100644 --- a/Datura/src/main/java/me/totalfreedom/datura/user/ServerEconomyHolder.java +++ b/Datura/src/main/java/me/totalfreedom/datura/user/ServerEconomyHolder.java @@ -3,40 +3,112 @@ 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 long balance = Long.MAX_VALUE; + 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 "TotalFreedom-Bank"; + 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) { @@ -44,6 +116,13 @@ public class ServerEconomyHolder implements EconomicEntity, EconomicEntityData 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) { diff --git a/Fossil/src/main/java/me/totalfreedom/fossil/Fossil.java b/Fossil/src/main/java/me/totalfreedom/fossil/Fossil.java index a638249..8d789f8 100644 --- a/Fossil/src/main/java/me/totalfreedom/fossil/Fossil.java +++ b/Fossil/src/main/java/me/totalfreedom/fossil/Fossil.java @@ -18,7 +18,7 @@ public class Fossil extends JavaPlugin registration.getModuleRegistry() .addModule(this); - registration.getServiceRegistry() + 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..829739d --- /dev/null +++ b/Fossil/src/main/java/me/totalfreedom/fossil/bouncypads/PadHolder.java @@ -0,0 +1,156 @@ +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..1cc0f66 --- /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/reactions/CopyCatReaction.java b/Fossil/src/main/java/me/totalfreedom/fossil/reactions/CopyCatReaction.java new file mode 100644 index 0000000..983e5b4 --- /dev/null +++ b/Fossil/src/main/java/me/totalfreedom/fossil/reactions/CopyCatReaction.java @@ -0,0 +1,58 @@ +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/Patchwork/src/main/java/me/totalfreedom/api/Context.java b/Patchwork/src/main/java/me/totalfreedom/api/Context.java index e4e7731..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 { + /** + * 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,6 +89,9 @@ public interface Context } } + /** + * @return The context as a {@link Integer}. + */ default @Nullable Integer asInt() { if (get() instanceof Integer integer) @@ -67,6 +103,9 @@ public interface Context } } + /** + * @return The context as a {@link Byte}. + */ default @Nullable Long asLong() { if (get() instanceof Long longg) @@ -78,6 +117,9 @@ public interface Context } } + /** + * @return The context as a {@link Float}. + */ default @Nullable Float asFloat() { if (get() instanceof Float floatt) @@ -89,6 +131,9 @@ public interface Context } } + /** + * @return The context as a {@link Player}. + */ default @Nullable Player asPlayer() { if (get() instanceof Player player) @@ -100,6 +145,9 @@ public interface Context } } + /** + * @return The context as a {@link CommandSender}. + */ default @Nullable CommandSender asCommandSender() { if (get() instanceof CommandSender commandSender) @@ -111,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) @@ -127,6 +183,9 @@ public interface Context } } + /** + * @return The context as a {@link Location}. + */ default @Nullable Location asLocation() { if (get() instanceof Location location) @@ -138,6 +197,9 @@ public interface Context } } + /** + * @return The context as a {@link LivingEntity}. + */ default @Nullable LivingEntity asLivingEntity() { if (get() instanceof LivingEntity livingEntity) @@ -149,6 +211,9 @@ public interface Context } } + /** + * @return The context as a {@link Component}. + */ default @Nullable Component asComponent() { if (get() instanceof Component component) @@ -160,6 +225,9 @@ public interface Context } } + /** + * @return The context as a {@link Projectile}. + */ default @Nullable Projectile asProjectile() { if (get() instanceof Projectile projectile) @@ -171,6 +239,9 @@ public interface Context } } + /** + * @return The context as an {@link Action}. + */ default @Nullable Action asAction() { if (get() instanceof Action action) @@ -182,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 index 7e71edc..0a6c598 100644 --- a/Patchwork/src/main/java/me/totalfreedom/api/Interpolator.java +++ b/Patchwork/src/main/java/me/totalfreedom/api/Interpolator.java @@ -1,7 +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..41ab562 100644 --- a/Patchwork/src/main/java/me/totalfreedom/api/Serializable.java +++ b/Patchwork/src/main/java/me/totalfreedom/api/Serializable.java @@ -1,5 +1,12 @@ 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 { /** @@ -11,5 +18,11 @@ public interface Serializable */ 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 4048c49..f725d73 100644 --- a/Patchwork/src/main/java/me/totalfreedom/audience/MutableAudienceForwarder.java +++ b/Patchwork/src/main/java/me/totalfreedom/audience/MutableAudienceForwarder.java @@ -19,16 +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(); @@ -41,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 */) @@ -51,12 +65,23 @@ 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) { @@ -66,52 +91,103 @@ public class MutableAudienceForwarder implements Audience .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. @@ -119,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 b75d402..911086e 100644 --- a/Patchwork/src/main/java/me/totalfreedom/base/CommonsBase.java +++ b/Patchwork/src/main/java/me/totalfreedom/base/CommonsBase.java @@ -6,12 +6,30 @@ 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); @@ -22,34 +40,51 @@ public class CommonsBase extends JavaPlugin { Bukkit.getScheduler() .runTaskLater(this, () -> getRegistrations() - .getServiceRegistry() - .stopAllServices(), 1L); + .getServiceTaskRegistry() + .stopAllServices(), 1L); - getRegistrations().getServiceRegistry() + getRegistrations().getServiceTaskRegistry() .unregisterService(EventBus.class); } @Override public void onEnable() { - getRegistrations().getServiceRegistry() + getRegistrations().getServiceTaskRegistry() .registerService(SubscriptionProvider.asyncService(this, eventBus)); getExecutor().getSync() .execute(() -> getRegistrations() - .getServiceRegistry() - .startAllServices()); + .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 e23609a..1f5cf6c 100644 --- a/Patchwork/src/main/java/me/totalfreedom/base/Registration.java +++ b/Patchwork/src/main/java/me/totalfreedom/base/Registration.java @@ -7,16 +7,44 @@ 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; + /** + * The {@link ServiceTaskRegistry} + */ private final ServiceTaskRegistry serviceTaskRegistry; + /** + * The {@link ModuleRegistry} + */ private final ModuleRegistry moduleRegistry; + /** + * The {@link GroupRegistry} + */ private final GroupRegistry groupRegistry; + /** + * 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(); @@ -26,31 +54,49 @@ public class Registration 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 ServiceTaskRegistry getServiceRegistry() + /** + * @return The {@link ServiceTaskRegistry} + */ + public ServiceTaskRegistry getServiceTaskRegistry() { return serviceTaskRegistry; } + /** + * @return The {@link GroupRegistry} + */ public GroupRegistry getGroupRegistry() { return groupRegistry; } + /** + * @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 index a888edb..b6d6276 100644 --- a/Patchwork/src/main/java/me/totalfreedom/command/BukkitDelegate.java +++ b/Patchwork/src/main/java/me/totalfreedom/command/BukkitDelegate.java @@ -22,7 +22,23 @@ import java.util.Arrays; import java.util.List; import java.util.Set; -public class BukkitDelegate extends Command implements PluginIdentifiableCommand +/** + * 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; @@ -50,8 +66,8 @@ public class BukkitDelegate extends Command implements PluginIdentifiableCommand @Override public boolean execute(@NotNull final CommandSender sender, - @NotNull final String commandLabel, - @NotNull final String[] args) + @NotNull final String commandLabel, + @NotNull final String[] args) { if (sender instanceof ConsoleCommandSender && noConsole) { @@ -87,8 +103,7 @@ public class BukkitDelegate extends Command implements PluginIdentifiableCommand { command.getBaseMethod() .invoke(command, sender); - } - catch (Exception ex) + } catch (Exception ex) { FreedomLogger.getLogger("Patchwork") .error(ex); @@ -101,8 +116,8 @@ public class BukkitDelegate extends Command implements PluginIdentifiableCommand } private void processSubCommands(final @NotNull String @NotNull [] args, - final CommandSender sender, final ContextProvider provider, - final Subcommand node) + final CommandSender sender, final ContextProvider provider, + final Subcommand node) { final Class[] argTypes = node.args(); if (argTypes.length != args.length) @@ -126,8 +141,7 @@ public class BukkitDelegate extends Command implements PluginIdentifiableCommand command.getSubcommands() .get(node) .invoke(command, sender, objects); - } - catch (Exception ex) + } catch (Exception ex) { FreedomLogger.getLogger("Patchwork") .error(ex); @@ -159,16 +173,16 @@ public class BukkitDelegate extends Command implements PluginIdentifiableCommand .map(World::getName) .toList()); case "%number%" -> results.addAll(List.of( - "0", - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9")); + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9")); case "%location%" -> results.add("world,x,y,z"); default -> results.add(p); } diff --git a/Patchwork/src/main/java/me/totalfreedom/command/CommandHandler.java b/Patchwork/src/main/java/me/totalfreedom/command/CommandHandler.java index c2714bf..34c754d 100644 --- a/Patchwork/src/main/java/me/totalfreedom/command/CommandHandler.java +++ b/Patchwork/src/main/java/me/totalfreedom/command/CommandHandler.java @@ -1,17 +1,39 @@ 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; } + /** + * 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 BukkitDelegate delegate = new BukkitDelegate(command); diff --git a/Patchwork/src/main/java/me/totalfreedom/command/Commander.java b/Patchwork/src/main/java/me/totalfreedom/command/Commander.java index 62f6d56..2830a84 100644 --- a/Patchwork/src/main/java/me/totalfreedom/command/Commander.java +++ b/Patchwork/src/main/java/me/totalfreedom/command/Commander.java @@ -17,15 +17,60 @@ 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() @@ -44,7 +89,7 @@ public abstract class Commander .filter(m -> m.isAnnotationPresent(Base.class)) .findFirst() .orElseThrow(() -> new RuntimeException( - "Base annotation present but no method found.")); + "Base annotation present but no method found.")); this.baseMethod = method; } else @@ -55,14 +100,17 @@ public abstract class Commander 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)); + method.getDeclaredAnnotation(Subcommand.class), + method)); List.of(this.getClass() .getDeclaredAnnotationsByType(Completion.class)) @@ -70,36 +118,66 @@ public abstract class Commander .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() { 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 253fc86..d32efa4 100644 --- a/Patchwork/src/main/java/me/totalfreedom/command/annotation/Completion.java +++ b/Patchwork/src/main/java/me/totalfreedom/command/annotation/Completion.java @@ -6,12 +6,26 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * 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 index 632156d..694804e 100644 --- a/Patchwork/src/main/java/me/totalfreedom/command/annotation/Completions.java +++ b/Patchwork/src/main/java/me/totalfreedom/command/annotation/Completions.java @@ -5,9 +5,18 @@ 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..fd90d82 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,40 @@ 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 3010844..9a3b8c7 100644 --- a/Patchwork/src/main/java/me/totalfreedom/command/annotation/Subcommand.java +++ b/Patchwork/src/main/java/me/totalfreedom/command/annotation/Subcommand.java @@ -1,15 +1,41 @@ 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 6b551c1..728ead2 100644 --- a/Patchwork/src/main/java/me/totalfreedom/config/Configuration.java +++ b/Patchwork/src/main/java/me/totalfreedom/config/Configuration.java @@ -1,36 +1,132 @@ package me.totalfreedom.config; +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 { + /** + * 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/data/ConfigRegistry.java b/Patchwork/src/main/java/me/totalfreedom/data/ConfigRegistry.java index 2c54b02..8f6b9e9 100644 --- a/Patchwork/src/main/java/me/totalfreedom/data/ConfigRegistry.java +++ b/Patchwork/src/main/java/me/totalfreedom/data/ConfigRegistry.java @@ -5,20 +5,40 @@ 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..99f7271 100644 --- a/Patchwork/src/main/java/me/totalfreedom/data/EventRegistry.java +++ b/Patchwork/src/main/java/me/totalfreedom/data/EventRegistry.java @@ -6,25 +6,49 @@ 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. + * @return The event provider. + * @param The event type. + */ 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 12e6c57..96ae921 100644 --- a/Patchwork/src/main/java/me/totalfreedom/data/GroupRegistry.java +++ b/Patchwork/src/main/java/me/totalfreedom/data/GroupRegistry.java @@ -6,25 +6,49 @@ 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<>(); } + /** + * 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); } + /** + * 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); } + /** + * 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(); @@ -39,6 +63,9 @@ public class GroupRegistry return null; } + /** + * @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 34f024a..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,19 +38,30 @@ public class ModuleRegistry this.plugins.add(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/UserRegistry.java b/Patchwork/src/main/java/me/totalfreedom/data/UserRegistry.java index b5f47e4..184e140 100644 --- a/Patchwork/src/main/java/me/totalfreedom/data/UserRegistry.java +++ b/Patchwork/src/main/java/me/totalfreedom/data/UserRegistry.java @@ -7,39 +7,91 @@ 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); } - public UserData fromPlayer(final Player player) { + /** + * 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())) + .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 index 697f7eb..9baa4df 100644 --- a/Patchwork/src/main/java/me/totalfreedom/display/AbstractMenu.java +++ b/Patchwork/src/main/java/me/totalfreedom/display/AbstractMenu.java @@ -15,14 +15,34 @@ 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<>(); @@ -32,26 +52,48 @@ public abstract class AbstractMenu invByUUID.put(getDisplayableUUID(), this); } - public UUID getDisplayableUUID() - { - return displayableUUID; - } - + /** + * @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); @@ -61,17 +103,28 @@ public abstract class AbstractMenu } } + /** + * @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() @@ -87,24 +140,47 @@ public abstract class AbstractMenu 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 @NotNull Component... lore) { final ItemStack item = new ItemStack(material, 1); final ItemMeta meta = item.getItemMeta(); diff --git a/Patchwork/src/main/java/me/totalfreedom/display/BossBarDisplay.java b/Patchwork/src/main/java/me/totalfreedom/display/BossBarDisplay.java index 3c59ccd..6ef8948 100644 --- a/Patchwork/src/main/java/me/totalfreedom/display/BossBarDisplay.java +++ b/Patchwork/src/main/java/me/totalfreedom/display/BossBarDisplay.java @@ -11,65 +11,126 @@ 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(); @@ -79,6 +140,11 @@ public class BossBarDisplay 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(); @@ -88,40 +154,80 @@ public class BossBarDisplay 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()); } - private static final class BossBarBuilder + /** + * 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(); @@ -130,72 +236,134 @@ public class BossBarDisplay 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..91cf858 --- /dev/null +++ b/Patchwork/src/main/java/me/totalfreedom/display/BossBarTimer.java @@ -0,0 +1,40 @@ +package me.totalfreedom.display; + +import me.totalfreedom.service.Task; +import net.kyori.adventure.audience.Audience; +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 index 2d6d21f..a7816c7 100644 --- a/Patchwork/src/main/java/me/totalfreedom/display/ClickAction.java +++ b/Patchwork/src/main/java/me/totalfreedom/display/ClickAction.java @@ -2,8 +2,15 @@ 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 index c550877..56a5fff 100644 --- a/Patchwork/src/main/java/me/totalfreedom/display/Displayable.java +++ b/Patchwork/src/main/java/me/totalfreedom/display/Displayable.java @@ -15,12 +15,29 @@ 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) @@ -31,8 +48,8 @@ public final class Displayable implements Inventory, InventoryHolder // 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); + ? size + : size + (9 - size % 9); this.contents = new ItemStack[size]; } @@ -82,7 +99,7 @@ public final class Displayable implements Inventory, InventoryHolder @Override public @NotNull HashMap addItem(final @NotNull ItemStack... items) - throws IllegalArgumentException + throws IllegalArgumentException { final HashMap remainingItems = new HashMap<>(); @@ -121,7 +138,7 @@ public final class Displayable implements Inventory, InventoryHolder @Override public @NotNull HashMap removeItem(final @NotNull ItemStack... items) - throws IllegalArgumentException + throws IllegalArgumentException { final HashMap removedItems = new HashMap<>(); @@ -154,7 +171,7 @@ public final class Displayable implements Inventory, InventoryHolder if (remainingAmount < item.getAmount()) { removedItems.put(removedItems.size(), - new ItemStack(item.getType(), item.getAmount() - remainingAmount)); + new ItemStack(item.getType(), item.getAmount() - remainingAmount)); } } @@ -163,7 +180,7 @@ public final class Displayable implements Inventory, InventoryHolder @Override public @NotNull HashMap removeItemAnySlot(final @NotNull ItemStack... items) - throws IllegalArgumentException + throws IllegalArgumentException { return removeItem(items); } @@ -294,7 +311,7 @@ public final class Displayable implements Inventory, InventoryHolder @Override public @NotNull HashMap all(final @NotNull Material material) - throws IllegalArgumentException + throws IllegalArgumentException { final HashMap matchingItems = new HashMap<>(); for (int i = 0; i < size; i++) diff --git a/Patchwork/src/main/java/me/totalfreedom/display/DisplayableView.java b/Patchwork/src/main/java/me/totalfreedom/display/DisplayableView.java index 9a5eb2a..2a923b1 100644 --- a/Patchwork/src/main/java/me/totalfreedom/display/DisplayableView.java +++ b/Patchwork/src/main/java/me/totalfreedom/display/DisplayableView.java @@ -8,14 +8,42 @@ 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; @@ -55,15 +83,15 @@ public class DisplayableView extends InventoryView return title; } - @Override - public @NotNull String getOriginalTitle() - { - return FreedomAdventure.toPlainText(type.defaultTitle()); - } - @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 index 1a7a4e4..a5110bd 100644 --- a/Patchwork/src/main/java/me/totalfreedom/display/TitleDisplay.java +++ b/Patchwork/src/main/java/me/totalfreedom/display/TitleDisplay.java @@ -8,50 +8,108 @@ 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()); } - private static final class TitleBuilder + /** + * 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: + *

+ * @see Title#DEFAULT_TIMES + */ public TitleBuilder() { this.mainTitle = Component.empty(); @@ -61,60 +119,111 @@ public class TitleDisplay 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( diff --git a/Patchwork/src/main/java/me/totalfreedom/service/ServiceSubscription.java b/Patchwork/src/main/java/me/totalfreedom/service/ServiceSubscription.java index f6c4a83..25bcb8e 100644 --- a/Patchwork/src/main/java/me/totalfreedom/service/ServiceSubscription.java +++ b/Patchwork/src/main/java/me/totalfreedom/service/ServiceSubscription.java @@ -16,7 +16,23 @@ public final class ServiceSubscription private boolean isActive = false; + ServiceSubscription(@NotNull final JavaPlugin plugin, @NotNull final T service) + { + this(plugin, service, 1L, false); + } + ServiceSubscription(@NotNull final JavaPlugin plugin, @NotNull final T service, final boolean async) + { + this(plugin, service, 1L, async); + } + + ServiceSubscription(@NotNull final JavaPlugin plugin, @NotNull final T service, final long interval) + { + this(plugin, service, interval, false); + } + + ServiceSubscription(@NotNull final JavaPlugin plugin, @NotNull final T service, + final long interval, final boolean async) { this.service = service; this.async = async; @@ -28,7 +44,7 @@ public final class ServiceSubscription this.executor = r -> { final BukkitTask task = Bukkit.getScheduler() - .runTaskTimerAsynchronously(plugin, r, 0, 1); + .runTaskTimerAsynchronously(plugin, r, 0, interval); tempId[0] = task.getTaskId(); }; } else @@ -36,7 +52,7 @@ public final class ServiceSubscription this.executor = r -> { final BukkitTask task = Bukkit.getScheduler() - .runTaskTimer(plugin, r, 0, 1); + .runTaskTimer(plugin, r, 0, interval); tempId[0] = task.getTaskId(); }; } diff --git a/Patchwork/src/main/java/me/totalfreedom/service/SubscriptionProvider.java b/Patchwork/src/main/java/me/totalfreedom/service/SubscriptionProvider.java index a1478c0..cd2e846 100644 --- a/Patchwork/src/main/java/me/totalfreedom/service/SubscriptionProvider.java +++ b/Patchwork/src/main/java/me/totalfreedom/service/SubscriptionProvider.java @@ -16,7 +16,16 @@ public final class SubscriptionProvider public static final ServiceSubscription syncService(@NotNull final JavaPlugin plugin, @NotNull final S service) { - return new ServiceSubscription<>(plugin, service, false); + return new ServiceSubscription<>(plugin, service); + } + + @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); } @NotNull @@ -35,6 +44,15 @@ public final class SubscriptionProvider return new TaskSubscription<>(plugin, task, false); } + @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); + } + @NotNull @Contract(value = "_, _ -> new", pure = false) public static final TaskSubscription runAsyncTask(@NotNull final JavaPlugin plugin, diff --git a/Patchwork/src/main/java/me/totalfreedom/service/Task.java b/Patchwork/src/main/java/me/totalfreedom/service/Task.java index b072b26..88049e1 100644 --- a/Patchwork/src/main/java/me/totalfreedom/service/Task.java +++ b/Patchwork/src/main/java/me/totalfreedom/service/Task.java @@ -1,20 +1,85 @@ package me.totalfreedom.service; -public interface Task extends Runnable +import me.totalfreedom.utils.DurationTools; +import org.bukkit.scheduler.BukkitRunnable; + +import java.time.Duration; + +public abstract class Task extends BukkitRunnable { - boolean isRunning(); + private final String name; + private long delay; + private long interval; - String getName(); + protected Task(final String name) + { + this(name, -1L, -1L); + } - boolean isRepeating(); + protected Task(final String name, final long delay, final long interval) + { + this.name = name; + this.delay = delay; + this.interval = interval; + } - void setRepeating(long interval); + protected Task(final String name, final long delay) + { + this(name, delay, -1L); + } - boolean isDelayed(); + protected Task(final String name, final Duration delay) + { + this(name, DurationTools.getTickedSeconds(delay), -1L); + } - void setDelayed(long delay); + protected Task(final String name, final Duration delay, final Duration interval) + { + this(name, DurationTools.getTickedSeconds(delay), DurationTools.getTickedSeconds(interval)); + } - long getInterval(); + protected Task(final String name, final long delay, final Duration interval) + { + this(name, delay, DurationTools.getTickedSeconds(interval)); + } - long getDelay(); + public boolean isRunning() + { + return !isCancelled(); + } + + public String getName() + { + return name; + } + + public boolean isRepeating() + { + return interval != -1L; + } + + public void setRepeating(final long interval) + { + this.interval = interval; + } + + public boolean isDelayed() + { + return this.delay != -1; + } + + public void setDelayed(final long delay) + { + this.delay = delay; + } + + public long getInterval() + { + return interval; + } + + public long getDelay() + { + return delay; + } } diff --git a/Patchwork/src/main/java/me/totalfreedom/shop/Reactable.java b/Patchwork/src/main/java/me/totalfreedom/shop/Reactable.java index c8dacd2..6a00d84 100644 --- a/Patchwork/src/main/java/me/totalfreedom/shop/Reactable.java +++ b/Patchwork/src/main/java/me/totalfreedom/shop/Reactable.java @@ -1,16 +1,25 @@ package me.totalfreedom.shop; +import me.totalfreedom.display.BossBarDisplay; import me.totalfreedom.economy.EconomicEntity; +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.text.Component; +import org.bukkit.event.Listener; import java.time.Duration; +import java.util.function.Consumer; public interface Reactable { - String getReactionMessage(); + 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..6124f8b --- /dev/null +++ b/Patchwork/src/main/java/me/totalfreedom/shop/Reaction.java @@ -0,0 +1,69 @@ +package me.totalfreedom.shop; + +import me.totalfreedom.display.BossBarDisplay; +import net.kyori.adventure.bossbar.BossBar; +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; + } + + @Override + public Duration getReactionDuration() + { + return reactionDuration; + } + + @Override + public ReactionType getReactionType() + { + return reactionType; + } + + @Override + public BossBarDisplay getBossBarDisplay() + { + + } + + public void setReactionMessage(final Component message) + { + this.reactionMessage = message; + } +} 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..4af9233 --- /dev/null +++ b/Patchwork/src/main/java/me/totalfreedom/shop/ReactionTask.java @@ -0,0 +1,50 @@ +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 index b2b308d..e36a3da 100644 --- a/Patchwork/src/main/java/me/totalfreedom/shop/ReactionType.java +++ b/Patchwork/src/main/java/me/totalfreedom/shop/ReactionType.java @@ -2,5 +2,5 @@ package me.totalfreedom.shop; public enum ReactionType { - COPYCAT, UNSCRAMBLE; + COPYCAT, UNSCRAMBLE, MATH; } 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..1f833bc --- /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 getTickedSeconds(final Duration duration) + { + return duration.toMillis() / 50L; + } + + public static final Duration getTickedSeconds(final long seconds) + { + return SECOND.multipliedBy(seconds); + } +}