Documentation, Add BouncyPads

This commit is contained in:
Paul Reilly 2023-06-08 10:02:20 -05:00
parent 27dafd69e6
commit 1ea106d999
43 changed files with 2069 additions and 110 deletions

View File

@ -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()

View File

@ -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());

View File

@ -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.
* <br>
* <br>
* 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.
* <br>
* <br>
* 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.
* <br>
* <br>
* 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)
{

View File

@ -18,7 +18,7 @@ public class Fossil extends JavaPlugin
registration.getModuleRegistry()
.addModule(this);
registration.getServiceRegistry()
registration.getServiceTaskRegistry()
.registerService(
SubscriptionProvider.syncService(this, trailer));
}

View File

@ -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.
* <p>
* The type of the pad, defined by {@link #padType}, will determine how the player is bounced.
* <br>
* For type {@link PadType#NORMAL}, the player will be bounced if the face is {@link BlockFace#UP}.
* <br>
* For type {@link PadType#SIDES}, the player will be bounced if the face is not {@link BlockFace#UP} or
* {@link BlockFace#DOWN}.
* <br>
* For type {@link PadType#ALL}, the player will be bounced regardless of the face.
* <br>
* For type {@link PadType#EXTREME}, the player will be bounced with a velocity based on the formula:
* <br>
* <span color=#f07a21><code>(((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))</code></span>
* <br>
* For type {@link PadType#SPACE_CADET}, the player will be bounced with a velocity based on the formula:
* <br>
* <span color=#f07a21><code>Math.round(Math.abs((accel * 100.0) + Math.pow(y, Math.floor(accel)) /
* Math.exp(accel)))</code></span>
* <br>
* where <span color=#f07a21><code>y = Math.abs(random.nextGaussian(12, 5) * 0.5 + 0.5)</code></span> and <span
* color=#f07a21><code>accel = Math.sqrt(2 * 9.81 * y)</code></span>
* <br>
* <br>
* <b>NOTE:</b> 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:
* <br>
* <span color=#f07a21><code>(BlockFace direction + Player velocity * -1) * velocity</code></span>
* <br>
* <br>
* 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:
* <br>
* <span color=#f07a21><code>(((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))</code></span>
* <br>
* <br>
* <b>NOTE:</b> 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:
* <br>
* <span color=#f07a21><code>Math.round(Math.abs((accel * 100.0) + Math.pow(y, Math.floor(accel)) /
* Math.exp(accel)))</code></span>
* <br>
* where <span color=#f07a21><code>y = Math.abs(random.nextGaussian(12, 5) * 0.5 + 0.5)</code></span> and
* <span color=#f07a21><code>accel = Math.sqrt(2 * 9.81 * y)</code></span>
*
* @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);
}
}

View File

@ -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<UUID, BouncyPad> 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<UUID, BouncyPad> 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());
}
}

View File

@ -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;
}

View File

@ -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<EconomicEntity> 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();
}
}

View File

@ -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 <T> The type of the context.
* @see ContextProvider
*/
@FunctionalInterface
public interface Context<T>
{
/**
* Maps the context to another context.
*
* @param mapper The mapper function.
* @param <S> The type of the mapped context.
* @return The mapped context.
*/
default <S> Context<S> map(@NotNull final Function<T, S> 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<T>
}
}
/**
* 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<T>
}
}
/**
* @return The context as a {@link Double}.
*/
default @Nullable Double asDouble()
{
if (get() instanceof Double doub)
@ -56,6 +89,9 @@ public interface Context<T>
}
}
/**
* @return The context as a {@link Integer}.
*/
default @Nullable Integer asInt()
{
if (get() instanceof Integer integer)
@ -67,6 +103,9 @@ public interface Context<T>
}
}
/**
* @return The context as a {@link Byte}.
*/
default @Nullable Long asLong()
{
if (get() instanceof Long longg)
@ -78,6 +117,9 @@ public interface Context<T>
}
}
/**
* @return The context as a {@link Float}.
*/
default @Nullable Float asFloat()
{
if (get() instanceof Float floatt)
@ -89,6 +131,9 @@ public interface Context<T>
}
}
/**
* @return The context as a {@link Player}.
*/
default @Nullable Player asPlayer()
{
if (get() instanceof Player player)
@ -100,6 +145,9 @@ public interface Context<T>
}
}
/**
* @return The context as a {@link CommandSender}.
*/
default @Nullable CommandSender asCommandSender()
{
if (get() instanceof CommandSender commandSender)
@ -111,11 +159,19 @@ public interface Context<T>
}
}
/**
* 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<T>
}
}
/**
* @return The context as a {@link Location}.
*/
default @Nullable Location asLocation()
{
if (get() instanceof Location location)
@ -138,6 +197,9 @@ public interface Context<T>
}
}
/**
* @return The context as a {@link LivingEntity}.
*/
default @Nullable LivingEntity asLivingEntity()
{
if (get() instanceof LivingEntity livingEntity)
@ -149,6 +211,9 @@ public interface Context<T>
}
}
/**
* @return The context as a {@link Component}.
*/
default @Nullable Component asComponent()
{
if (get() instanceof Component component)
@ -160,6 +225,9 @@ public interface Context<T>
}
}
/**
* @return The context as a {@link Projectile}.
*/
default @Nullable Projectile asProjectile()
{
if (get() instanceof Projectile projectile)
@ -171,6 +239,9 @@ public interface Context<T>
}
}
/**
* @return The context as an {@link Action}.
*/
default @Nullable Action asAction()
{
if (get() instanceof Action action)
@ -182,6 +253,22 @@ public interface Context<T>
}
}
/**
* Gets the context as a custom class. This will cast the object to the class if it is an instance of it.
* <br>
* Typically, Context objects are useful when used to collect unknown data and then cast it to a known type.
* <br>
* 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.
* <p>
* For example, if we have a Context&lt;Object&gt; and we already know that the wrapped object should be of type X,
* we can use <code>X.class</code> 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 <U>
* @return
*/
default <U> @Nullable U asCustom(Class<U> clazz)
{
if (clazz.isInstance(get()))

View File

@ -1,7 +1,21 @@
package me.totalfreedom.api;
/**
* Interpolates a range of values and returns the results in a {@link Double} array.
* <br>
* 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);
}

View File

@ -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 <T> The type of object to serialize
*/
public interface Serializable<T>
{
/**
@ -11,5 +18,11 @@ public interface Serializable<T>
*/
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);
}

View File

@ -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.
* <p>
* 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<Audience> 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<? super Audience> 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<? super Audience> 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 <T> void sendTitlePart(@NotNull final TitlePart<T> 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));
}
}

View File

@ -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;

View File

@ -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.
* <br>
* 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;

View File

@ -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.
* <br>
* 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.
* <span color=#ff0000>
* <br>
* This class is not thread-safe.
* <br>
* This class is not meant to be extended.
* <br>
* This class is not meant to be instantiated.
* <br>
* This class is not meant to be used outside Patchwork.
* </span>
*/
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);
}

View File

@ -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.
* <br>
* 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 <T> The type of the command.
*/
public <T extends Commander> void registerCommand(final T command)
{
final BukkitDelegate delegate = new BukkitDelegate(command);

View File

@ -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}.
* <p>
* 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.
* <br>
* 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.
* <br>
* 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.
* <br>
* 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<Subcommand, Method> subcommands;
/**
* A set of all {@link Completion} annotations for this command.
*/
private final Set<Completion> 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.
* <p>
* 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.
* <br>
* 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.
* <br>
* 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.
* <br>
* 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<Subcommand, Method> getSubcommands()
{
return this.subcommands;
}
/**
* @return A set of all {@link Completion} annotations for this command.
*/
@Nullable
Set<Completion> getCompletions()
{

View File

@ -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.
* <p>
* 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();
}

View File

@ -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.
* <br>
* <u>This interface is <span color=#ff0000><b>NOT</b></span> intended for implementation and should
* <span color=#ff0000><b>NOT</b></span> be used.</u>
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Completions
{
/**
* @return The {@link Completion} annotations.
*/
Completion[] value();
}

View File

@ -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 <b><u>must</u></b> 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 <u>"This is the default command description."</u>
*
* @return The command's description.
*/
String description() default "This is the default command description.";
/**
* By default, this is set to <u>"/&lt;command&gt;"</u>
*
* @return The command's usage.
*/
String usage() default "/<command>";
/**
* By default, this returns an empty array.
*
* @return The command's aliases.
*/
String[] aliases() default {};
}

View File

@ -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.
* <p>
* Classes <u><b>MUST</b></u> 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 <u>false</u>.
*
* @return True if the command is only for players, false otherwise.
*/
boolean onlyPlayers() default false;
/**
* By default, this is set to <u>"You do not have permission to use this command."</u>
*
* @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.";
}

View File

@ -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}.
* <br>
* 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.
* <br>
* 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 {};
}

View File

@ -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 <T> The type of the objects in the list.
* @return The List object.
*/
<T> List<T> 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<String> 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 <T> The type of the value.
*/
<T> void set(String path, T value);
<T> T get(String path, Class<T> type);
/**
* Gets the value at the given path as the given type.
* <p>
* 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 <T> The type of the value.
* @return The value at the given path.
*/
<T> T get(String path);
<T> T getOrDefault(String path, Class<T> type, T fallback);
/**
* Gets the value at the given path as the given type.
* <p>
* 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 <T> The type of the value.
* @return The value at the given path.
*/
<T> T getOrDefault(String path, T fallback);
}

View File

@ -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<String, Configuration> 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);

View File

@ -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<FEvent> 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 <T> The event type.
*/
public <T extends FEvent> EventProvider<T> getEvent(final Class<T> clazz)
{
for (final FEvent event : this.events)

View File

@ -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<Group> 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<Group> getGroups()
{
return groups;

View File

@ -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<JavaPlugin> 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 <T extends JavaPlugin> ModuleProvider<T> getModule(final Class<T> clazz)
/**
* Gets a module from the registry wrapped in a {@link ModuleProvider}.
*
* @param clazz The class of the module.
* @param <T> The type of the module.
* @return The module.
*/
public <T extends JavaPlugin> ModuleProvider<T> getProvider(final Class<T> clazz)
{
for (final JavaPlugin plugin : plugins)
{
if (clazz.isInstance(plugin))
{
return () -> (T) plugin;
return () -> clazz.cast(plugin);
}
}

View File

@ -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<User, UserData> 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<User, UserData> getUserDataMap()
{
return userDataMap;

View File

@ -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<UUID, AbstractMenu> invByUUID = new HashMap<>();
/**
* A map of all open menus by the player's UUID.
*/
private static final Map<UUID, UUID> openInvs = new HashMap<>();
private final Map<Integer, ClickAction> 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<UUID, AbstractMenu> getInvByUUID()
{
return invByUUID;
}
/**
* @return A map of all open menus by the player's UUID.
*/
public static Map<UUID, UUID> 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<Integer, ClickAction> 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();

View File

@ -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<BossBar.Flag> 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);

View File

@ -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++;
}
}

View File

@ -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);
}

View File

@ -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<Integer, ItemStack> addItem(final @NotNull ItemStack... items)
throws IllegalArgumentException
throws IllegalArgumentException
{
final HashMap<Integer, ItemStack> remainingItems = new HashMap<>();
@ -121,7 +138,7 @@ public final class Displayable implements Inventory, InventoryHolder
@Override
public @NotNull HashMap<Integer, ItemStack> removeItem(final @NotNull ItemStack... items)
throws IllegalArgumentException
throws IllegalArgumentException
{
final HashMap<Integer, ItemStack> 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<Integer, ItemStack> 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<Integer, ? extends ItemStack> all(final @NotNull Material material)
throws IllegalArgumentException
throws IllegalArgumentException
{
final HashMap<Integer, ItemStack> matchingItems = new HashMap<>();
for (int i = 0; i < size; i++)

View File

@ -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.
* <p>
* 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());
}
}

View File

@ -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:
* <ul>
* <li>Empty main title</li>
* <li>Empty subtitle</li>
* <li>Default fade in time</li>
* <li>Default fade out time</li>
* <li>Default display duration</li>
* </ul>
* @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(

View File

@ -16,7 +16,23 @@ public final class ServiceSubscription<T extends Service>
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<T extends Service>
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<T extends Service>
this.executor = r ->
{
final BukkitTask task = Bukkit.getScheduler()
.runTaskTimer(plugin, r, 0, 1);
.runTaskTimer(plugin, r, 0, interval);
tempId[0] = task.getTaskId();
};
}

View File

@ -16,7 +16,16 @@ public final class SubscriptionProvider
public static final <S extends Service> ServiceSubscription<S> 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 <S extends Service> ServiceSubscription<S> 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 <S extends Service> ServiceSubscription<S> 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 <T extends Task> TaskSubscription<T> runAsyncTask(@NotNull final JavaPlugin plugin,

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View File

@ -2,5 +2,5 @@ package me.totalfreedom.shop;
public enum ReactionType
{
COPYCAT, UNSCRAMBLE;
COPYCAT, UNSCRAMBLE, MATH;
}

View File

@ -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);
}
}