mirror of
https://github.com/SimplexDevelopment/FreedomNetworkSuite.git
synced 2025-07-01 05:06:42 +00:00
Migrates the entire package nomenclature to be more direct and straightforward. (#17)
Signed-off-by: Paul Reilly <pawereus@gmail.com>
This commit is contained in:
281
Patchwork/src/main/java/fns/patchwork/api/Context.java
Normal file
281
Patchwork/src/main/java/fns/patchwork/api/Context.java
Normal file
@ -0,0 +1,281 @@
|
||||
package fns.patchwork.api;
|
||||
|
||||
import fns.patchwork.provider.ContextProvider;
|
||||
import java.util.function.Function;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.LivingEntity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.entity.Projectile;
|
||||
import org.bukkit.event.block.Action;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
return string;
|
||||
} else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the context as a boolean.
|
||||
*
|
||||
* @return The context as a boolean.
|
||||
*/
|
||||
default @Nullable Boolean asBoolean()
|
||||
{
|
||||
if (get() instanceof Boolean bool)
|
||||
{
|
||||
return bool;
|
||||
} else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The context as a {@link Double}.
|
||||
*/
|
||||
default @Nullable Double asDouble()
|
||||
{
|
||||
if (get() instanceof Double doub)
|
||||
{
|
||||
return doub;
|
||||
} else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The context as a {@link Integer}.
|
||||
*/
|
||||
default @Nullable Integer asInt()
|
||||
{
|
||||
if (get() instanceof Integer integer)
|
||||
{
|
||||
return integer;
|
||||
} else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The context as a {@link Byte}.
|
||||
*/
|
||||
default @Nullable Long asLong()
|
||||
{
|
||||
if (get() instanceof Long longg)
|
||||
{
|
||||
return longg;
|
||||
} else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The context as a {@link Float}.
|
||||
*/
|
||||
default @Nullable Float asFloat()
|
||||
{
|
||||
if (get() instanceof Float floatt)
|
||||
{
|
||||
return floatt;
|
||||
} else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The context as a {@link Player}.
|
||||
*/
|
||||
default @Nullable Player asPlayer()
|
||||
{
|
||||
if (get() instanceof Player player)
|
||||
{
|
||||
return player;
|
||||
} else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The context as a {@link CommandSender}.
|
||||
*/
|
||||
default @Nullable CommandSender asCommandSender()
|
||||
{
|
||||
if (get() instanceof CommandSender commandSender)
|
||||
{
|
||||
return commandSender;
|
||||
} else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
return world;
|
||||
} else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The context as a {@link Location}.
|
||||
*/
|
||||
default @Nullable Location asLocation()
|
||||
{
|
||||
if (get() instanceof Location location)
|
||||
{
|
||||
return location;
|
||||
} else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The context as a {@link LivingEntity}.
|
||||
*/
|
||||
default @Nullable LivingEntity asLivingEntity()
|
||||
{
|
||||
if (get() instanceof LivingEntity livingEntity)
|
||||
{
|
||||
return livingEntity;
|
||||
} else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The context as a {@link Component}.
|
||||
*/
|
||||
default @Nullable Component asComponent()
|
||||
{
|
||||
if (get() instanceof Component component)
|
||||
{
|
||||
return component;
|
||||
} else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The context as a {@link Projectile}.
|
||||
*/
|
||||
default @Nullable Projectile asProjectile()
|
||||
{
|
||||
if (get() instanceof Projectile projectile)
|
||||
{
|
||||
return projectile;
|
||||
} else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The context as an {@link Action}.
|
||||
*/
|
||||
default @Nullable Action asAction()
|
||||
{
|
||||
if (get() instanceof Action action)
|
||||
{
|
||||
return action;
|
||||
} else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<Object> 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()))
|
||||
{
|
||||
return clazz.cast(get());
|
||||
} else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
21
Patchwork/src/main/java/fns/patchwork/api/Interpolator.java
Normal file
21
Patchwork/src/main/java/fns/patchwork/api/Interpolator.java
Normal file
@ -0,0 +1,21 @@
|
||||
package fns.patchwork.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);
|
||||
}
|
27
Patchwork/src/main/java/fns/patchwork/api/Serializable.java
Normal file
27
Patchwork/src/main/java/fns/patchwork/api/Serializable.java
Normal file
@ -0,0 +1,27 @@
|
||||
package fns.patchwork.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>
|
||||
{
|
||||
/**
|
||||
* Serialize an object to a string. Ideally, this should serialize to an SQL query for easy data transfer.
|
||||
*
|
||||
* @param object The object to serialize
|
||||
* @return The serialized object
|
||||
*/
|
||||
String serialize(T object);
|
||||
|
||||
/**
|
||||
* Deserialize an object from a Serialized string..
|
||||
*
|
||||
* @param serializedObject The serialized object
|
||||
* @return The deserialized object
|
||||
*/
|
||||
|
||||
T deserialize(String serializedObject);
|
||||
}
|
@ -0,0 +1,320 @@
|
||||
package fns.patchwork.audience;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
import net.kyori.adventure.bossbar.BossBar;
|
||||
import net.kyori.adventure.chat.ChatType;
|
||||
import net.kyori.adventure.chat.SignedMessage;
|
||||
import net.kyori.adventure.inventory.Book;
|
||||
import net.kyori.adventure.sound.Sound;
|
||||
import net.kyori.adventure.sound.SoundStop;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.ComponentLike;
|
||||
import net.kyori.adventure.title.Title;
|
||||
import net.kyori.adventure.title.TitlePart;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* A replacement for {@link net.kyori.adventure.audience.ForwardingAudience} that allows for audiences to be removed and
|
||||
* 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();
|
||||
|
||||
for (final Audience audience : audiences)
|
||||
{
|
||||
audienceForwarder.addAudience(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 */)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
return audiences.stream()
|
||||
.filter(filter)
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a consumer to each audience in the stream.
|
||||
*
|
||||
* @param action the action to apply.
|
||||
*/
|
||||
@Override
|
||||
public void forEachAudience(@NotNull final Consumer<? 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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
forEachAudience(a -> a.deleteMessage(signature));
|
||||
}
|
||||
|
||||
// The methods below here will (probably) never be used, however it's good to keep them for completeness' sake.
|
||||
|
||||
@Override
|
||||
public void sendActionBar(@NotNull final ComponentLike message)
|
||||
{
|
||||
forEachAudience(a -> a.sendActionBar(message));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendActionBar(@NotNull final Component message)
|
||||
{
|
||||
forEachAudience(a -> a.sendActionBar(message));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPlayerListHeader(@NotNull final ComponentLike header)
|
||||
{
|
||||
forEachAudience(a -> a.sendPlayerListHeader(header));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPlayerListHeader(@NotNull final Component header)
|
||||
{
|
||||
forEachAudience(a -> a.sendPlayerListHeader(header));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPlayerListFooter(@NotNull final ComponentLike footer)
|
||||
{
|
||||
forEachAudience(a -> a.sendPlayerListFooter(footer));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPlayerListFooter(@NotNull final Component footer)
|
||||
{
|
||||
forEachAudience(a -> a.sendPlayerListFooter(footer));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPlayerListHeaderAndFooter(@NotNull final ComponentLike header, @NotNull final ComponentLike footer)
|
||||
{
|
||||
forEachAudience(a -> a.sendPlayerListHeaderAndFooter(header, footer));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPlayerListHeaderAndFooter(@NotNull final Component header, @NotNull final Component footer)
|
||||
{
|
||||
forEachAudience(a -> a.sendPlayerListHeaderAndFooter(header, footer));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showTitle(@NotNull final Title title)
|
||||
{
|
||||
forEachAudience(a -> a.showTitle(title));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void sendTitlePart(@NotNull final TitlePart<T> part, @NotNull final T value)
|
||||
{
|
||||
forEachAudience(a -> a.sendTitlePart(part, value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearTitle()
|
||||
{
|
||||
forEachAudience(Audience::clearTitle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetTitle()
|
||||
{
|
||||
forEachAudience(Audience::resetTitle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showBossBar(@NotNull final BossBar bar)
|
||||
{
|
||||
forEachAudience(a -> a.showBossBar(bar));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hideBossBar(@NotNull final BossBar bar)
|
||||
{
|
||||
forEachAudience(a -> a.hideBossBar(bar));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playSound(@NotNull final Sound sound)
|
||||
{
|
||||
forEachAudience(a -> a.playSound(sound));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playSound(@NotNull final Sound sound, final double x, final double y, final double z)
|
||||
{
|
||||
|
||||
forEachAudience(a -> a.playSound(sound, x, y, z));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playSound(@NotNull final Sound sound, final Sound.@NotNull Emitter emitter)
|
||||
{
|
||||
forEachAudience(a -> a.playSound(sound, emitter));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopSound(@NotNull final Sound sound)
|
||||
{
|
||||
forEachAudience(a -> a.stopSound(sound));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopSound(@NotNull final SoundStop stop)
|
||||
{
|
||||
forEachAudience(a -> a.stopSound(stop));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openBook(final Book.@NotNull Builder book)
|
||||
{
|
||||
forEachAudience(a -> a.openBook(book));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openBook(@NotNull final Book book)
|
||||
{
|
||||
forEachAudience(a -> a.openBook(book));
|
||||
}
|
||||
}
|
108
Patchwork/src/main/java/fns/patchwork/base/Patchwork.java
Normal file
108
Patchwork/src/main/java/fns/patchwork/base/Patchwork.java
Normal file
@ -0,0 +1,108 @@
|
||||
package fns.patchwork.base;
|
||||
|
||||
import fns.patchwork.display.adminchat.AdminChatDisplay;
|
||||
import fns.patchwork.event.EventBus;
|
||||
import fns.patchwork.service.FreedomExecutor;
|
||||
import fns.patchwork.service.SubscriptionProvider;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
/**
|
||||
* The base class for Patchwork.
|
||||
*/
|
||||
public class Patchwork 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();
|
||||
/**
|
||||
* The {@link AdminChatDisplay} for this plugin.
|
||||
*/
|
||||
private final AdminChatDisplay acdisplay = new AdminChatDisplay();
|
||||
|
||||
/**
|
||||
* 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 Patchwork getInstance()
|
||||
{
|
||||
return JavaPlugin.getPlugin(Patchwork.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable()
|
||||
{
|
||||
Bukkit.getScheduler()
|
||||
.runTaskLater(this, () -> getRegistrations()
|
||||
.getServiceTaskRegistry()
|
||||
.stopAllServices(), 1L);
|
||||
|
||||
getRegistrations().getServiceTaskRegistry()
|
||||
.unregisterService(EventBus.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable()
|
||||
{
|
||||
getRegistrations().getServiceTaskRegistry()
|
||||
.registerService(SubscriptionProvider.asyncService(this, eventBus));
|
||||
getExecutor().getSync()
|
||||
.execute(() -> getRegistrations()
|
||||
.getServiceTaskRegistry()
|
||||
.startAllServices());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link FreedomExecutor} for this plugin.
|
||||
*
|
||||
* @return the {@link FreedomExecutor}
|
||||
*/
|
||||
public FreedomExecutor getExecutor()
|
||||
{
|
||||
return executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get's the Registration object for this plugin. This object contains every registry class for the various features
|
||||
* provided by this plugin.
|
||||
*
|
||||
* @return the Registration object
|
||||
*/
|
||||
public Registration getRegistrations()
|
||||
{
|
||||
return registration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link EventBus} for this plugin. The EventBus is used to register and listen to custom events provided
|
||||
* by Freedom Network Suite.
|
||||
*
|
||||
* @return the {@link EventBus}
|
||||
*/
|
||||
public EventBus getEventBus()
|
||||
{
|
||||
return eventBus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link AdminChatDisplay} for this plugin. The AdminChatDisplay is used to display messages sent in
|
||||
* adminchat.
|
||||
*
|
||||
* @return the {@link AdminChatDisplay}
|
||||
*/
|
||||
public AdminChatDisplay getAdminChatDisplay()
|
||||
{
|
||||
return acdisplay;
|
||||
}
|
||||
}
|
104
Patchwork/src/main/java/fns/patchwork/base/Registration.java
Normal file
104
Patchwork/src/main/java/fns/patchwork/base/Registration.java
Normal file
@ -0,0 +1,104 @@
|
||||
package fns.patchwork.base;
|
||||
|
||||
import fns.patchwork.data.ConfigRegistry;
|
||||
import fns.patchwork.data.EventRegistry;
|
||||
import fns.patchwork.data.GroupRegistry;
|
||||
import fns.patchwork.data.ModuleRegistry;
|
||||
import fns.patchwork.data.ServiceTaskRegistry;
|
||||
import fns.patchwork.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 Patchwork#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;
|
||||
|
||||
/**
|
||||
* Constructs a new Registration object and initializes all registries.
|
||||
*/
|
||||
Registration()
|
||||
{
|
||||
this.eventRegistry = new EventRegistry();
|
||||
this.userRegistry = new UserRegistry();
|
||||
this.serviceTaskRegistry = new ServiceTaskRegistry();
|
||||
this.moduleRegistry = new ModuleRegistry();
|
||||
this.groupRegistry = new GroupRegistry();
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
}
|
30
Patchwork/src/main/java/fns/patchwork/base/Shortcuts.java
Normal file
30
Patchwork/src/main/java/fns/patchwork/base/Shortcuts.java
Normal file
@ -0,0 +1,30 @@
|
||||
package fns.patchwork.base;
|
||||
|
||||
import fns.patchwork.user.User;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
public final class Shortcuts
|
||||
{
|
||||
private Shortcuts()
|
||||
{
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
public static <T extends JavaPlugin> T provideModule(final Class<T> pluginClass)
|
||||
{
|
||||
return Patchwork.getInstance()
|
||||
.getRegistrations()
|
||||
.getModuleRegistry()
|
||||
.getProvider(pluginClass)
|
||||
.getModule();
|
||||
}
|
||||
|
||||
public static User getUser(final Player player)
|
||||
{
|
||||
return Patchwork.getInstance()
|
||||
.getRegistrations()
|
||||
.getUserRegistry()
|
||||
.getUser(player);
|
||||
}
|
||||
}
|
@ -0,0 +1,242 @@
|
||||
package fns.patchwork.command;
|
||||
|
||||
import fns.patchwork.command.annotation.Completion;
|
||||
import fns.patchwork.command.annotation.Subcommand;
|
||||
import fns.patchwork.provider.ContextProvider;
|
||||
import fns.patchwork.utils.logging.FreedomLogger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.PluginIdentifiableCommand;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* <br>
|
||||
* <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.
|
||||
*/
|
||||
public final class BukkitDelegate extends Command implements PluginIdentifiableCommand
|
||||
{
|
||||
private final JavaPlugin plugin;
|
||||
private final Commander command;
|
||||
private final boolean noConsole;
|
||||
|
||||
BukkitDelegate(final Commander command)
|
||||
{
|
||||
super(command.getInfo()
|
||||
.name());
|
||||
this.command = command;
|
||||
this.plugin = command.getPlugin();
|
||||
this.setDescription(command.getInfo()
|
||||
.description());
|
||||
this.setUsage(command.getInfo()
|
||||
.usage());
|
||||
this.setPermission(command.getPerms()
|
||||
.perm());
|
||||
this.setAliases(Arrays.asList(command.getInfo()
|
||||
.aliases()));
|
||||
this.permissionMessage(Component.text(command.getPerms()
|
||||
.noPerms()));
|
||||
this.noConsole = command.getPerms()
|
||||
.onlyPlayers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean execute(@NotNull final CommandSender sender,
|
||||
@NotNull final String commandLabel,
|
||||
@NotNull final String[] args)
|
||||
{
|
||||
if (!(sender instanceof Player) && noConsole)
|
||||
{
|
||||
sender.sendMessage(Component.text("This command can only be run by players."));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (getPermission() != null && !sender.hasPermission(getPermission()))
|
||||
{
|
||||
Component permissionMessage = permissionMessage();
|
||||
if (permissionMessage == null)
|
||||
permissionMessage = Component.text("You do not have permission to use this command.");
|
||||
sender.sendMessage(permissionMessage);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (args.length > 0)
|
||||
{
|
||||
final ContextProvider provider = new ContextProvider();
|
||||
final Set<Subcommand> nodes = command.getSubcommands()
|
||||
.keySet();
|
||||
for (final Subcommand node : nodes)
|
||||
{
|
||||
processSubCommands(args, sender, provider, node);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (command.getBaseMethod() != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (noConsole)
|
||||
{
|
||||
command.getBaseMethod()
|
||||
.invoke(command, (Player) sender);
|
||||
}
|
||||
else
|
||||
{
|
||||
command.getBaseMethod()
|
||||
.invoke(command, sender);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
FreedomLogger.getLogger("Patchwork")
|
||||
.error(ex);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void processSubCommands(final @NotNull String @NotNull [] args,
|
||||
final CommandSender sender, final ContextProvider provider,
|
||||
final Subcommand node)
|
||||
{
|
||||
final Class<?>[] argTypes = node.args();
|
||||
if (argTypes.length > args.length)
|
||||
return;
|
||||
|
||||
final Object[] objects = new Object[argTypes.length + 1];
|
||||
|
||||
for (int i = 0; i < argTypes.length; i++)
|
||||
{
|
||||
final Class<?> argType = argTypes[i];
|
||||
final String arg = args[i];
|
||||
|
||||
if (argType.equals(String.class))
|
||||
{
|
||||
if (i == argTypes.length - 1)
|
||||
{
|
||||
final String[] reasonArgs = Arrays.copyOfRange(args, i, args.length - 1);
|
||||
final String reason = String.join(" ", reasonArgs);
|
||||
objects[i] = reason;
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (argType.equals(Location.class))
|
||||
{
|
||||
final String[] locationArgs = Arrays.copyOfRange(args, i, i + 3);
|
||||
final String location = String.join(" ", locationArgs);
|
||||
objects[i] = location;
|
||||
}
|
||||
|
||||
final Object obj = provider.fromString(arg, argType);
|
||||
if (obj == null)
|
||||
{
|
||||
FreedomLogger.getLogger("Datura")
|
||||
.error("Failed to parse argument " + arg + " for type " + argType.getName());
|
||||
return;
|
||||
}
|
||||
objects[i] = obj;
|
||||
}
|
||||
try
|
||||
{
|
||||
if (noConsole)
|
||||
{
|
||||
command.getSubcommands()
|
||||
.get(node)
|
||||
.invoke(command, (Player) sender, objects);
|
||||
}
|
||||
else
|
||||
{
|
||||
command.getSubcommands()
|
||||
.get(node)
|
||||
.invoke(command, sender, objects);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
FreedomLogger.getLogger("Patchwork")
|
||||
.error(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> tabComplete(final CommandSender sender, final String alias, final String[] args)
|
||||
{
|
||||
final Set<Completion> completions = command.getCompletions();
|
||||
final List<String> results = new ArrayList<>();
|
||||
for (final Completion completion : completions)
|
||||
{
|
||||
if (completion.index() != args.length)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (final String p : completion.args())
|
||||
{
|
||||
switch (p)
|
||||
{
|
||||
case "%player%" -> results.addAll(Bukkit.getOnlinePlayers()
|
||||
.stream()
|
||||
.map(Player::getName)
|
||||
.toList());
|
||||
case "%world%" -> results.addAll(Bukkit.getWorlds()
|
||||
.stream()
|
||||
.map(World::getName)
|
||||
.toList());
|
||||
case "%number%" -> results.addAll(List.of(
|
||||
"0",
|
||||
"1",
|
||||
"2",
|
||||
"3",
|
||||
"4",
|
||||
"5",
|
||||
"6",
|
||||
"7",
|
||||
"8",
|
||||
"9"));
|
||||
case "%location%" -> results.add("world x y z");
|
||||
default -> results.add(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results.stream()
|
||||
.filter(s -> s.startsWith(args[args.length - 1]))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Plugin getPlugin()
|
||||
{
|
||||
return this.plugin;
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package fns.patchwork.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);
|
||||
|
||||
Bukkit.getCommandMap()
|
||||
.register(plugin.getName(), delegate);
|
||||
}
|
||||
}
|
185
Patchwork/src/main/java/fns/patchwork/command/Commander.java
Normal file
185
Patchwork/src/main/java/fns/patchwork/command/Commander.java
Normal file
@ -0,0 +1,185 @@
|
||||
package fns.patchwork.command;
|
||||
|
||||
import fns.patchwork.command.annotation.Base;
|
||||
import fns.patchwork.command.annotation.Completion;
|
||||
import fns.patchwork.command.annotation.Info;
|
||||
import fns.patchwork.command.annotation.Permissive;
|
||||
import fns.patchwork.command.annotation.Subcommand;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* 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()
|
||||
.getDeclaredAnnotation(Info.class);
|
||||
this.perms = this.getClass()
|
||||
.getDeclaredAnnotation(Permissive.class);
|
||||
this.plugin = plugin;
|
||||
this.subcommands = new HashMap<>();
|
||||
this.completions = new HashSet<>();
|
||||
|
||||
if (this.getClass()
|
||||
.isAnnotationPresent(Base.class))
|
||||
{
|
||||
final Method method = Stream.of(this.getClass()
|
||||
.getDeclaredMethods())
|
||||
.filter(m -> m.isAnnotationPresent(Base.class))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new RuntimeException(
|
||||
"Base annotation present but no method found."));
|
||||
|
||||
this.baseMethod = method;
|
||||
} else
|
||||
{
|
||||
this.baseMethod = null;
|
||||
}
|
||||
|
||||
registerAnnotations();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers all subcommands and completions for this command.
|
||||
*/
|
||||
private void registerAnnotations()
|
||||
{
|
||||
Stream.of(this.getClass()
|
||||
.getDeclaredMethods())
|
||||
.filter(method -> method.isAnnotationPresent(Subcommand.class))
|
||||
.forEach(method -> this.subcommands.put(
|
||||
method.getDeclaredAnnotation(Subcommand.class),
|
||||
method));
|
||||
|
||||
List.of(this.getClass()
|
||||
.getDeclaredAnnotationsByType(Completion.class))
|
||||
.stream()
|
||||
.forEach(completions::add);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the method which should be called when the command is executed without any arguments.
|
||||
* <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()
|
||||
{
|
||||
return this.completions;
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package fns.patchwork.command.annotation;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* This annotation is used to mark a method as the command's default method. This is the method that will be run to
|
||||
* execute the command when a user inputs /{command}
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Base
|
||||
{
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package fns.patchwork.command.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Repeatable;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 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.TYPE)
|
||||
@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();
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package fns.patchwork.command.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* A marker interface which represents a holder for multiple {@link Completion} annotations.
|
||||
* <br>
|
||||
* <u>This interface is <b>NOT</b> intended for implementation and should
|
||||
* <b>NOT</b> be used.</u>
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Completions
|
||||
{
|
||||
/**
|
||||
* @return The {@link Completion} annotations.
|
||||
*/
|
||||
Completion[] value();
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package fns.patchwork.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>"/<command>"</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 {};
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package fns.patchwork.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.";
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package fns.patchwork.command.annotation;
|
||||
|
||||
import fns.patchwork.command.CommandHandler;
|
||||
import fns.patchwork.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
|
||||
* <i>(current supported arguments can be found in the {@link ContextProvider})</i>, 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}.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* Additionally, if the final argument is a String object, the BukkitDelegate will automatically append any additional
|
||||
* arguments to the end of the String. For example, if you have a subcommand method with the arguments
|
||||
* {@code (Player, String)}, and the user executes the command with the arguments {@code /command playerName arg2 arg3},
|
||||
* the {@code String} argument will be {@code "arg2 arg3"}. This allows for us to use a String at the end of our
|
||||
* subcommand arguments to allow for the user to input a reason.
|
||||
*/
|
||||
@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 {};
|
||||
}
|
131
Patchwork/src/main/java/fns/patchwork/config/Configuration.java
Normal file
131
Patchwork/src/main/java/fns/patchwork/config/Configuration.java
Normal file
@ -0,0 +1,131 @@
|
||||
package fns.patchwork.config;
|
||||
|
||||
import fns.patchwork.api.Context;
|
||||
import fns.patchwork.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);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package fns.patchwork.config;
|
||||
|
||||
public final class YamlWrapper
|
||||
{
|
||||
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package fns.patchwork.data;
|
||||
|
||||
import fns.patchwork.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);
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
package fns.patchwork.data;
|
||||
|
||||
import fns.patchwork.event.FEvent;
|
||||
import fns.patchwork.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.
|
||||
* @param <T> The event type.
|
||||
* @return The event provider.
|
||||
*/
|
||||
public <T extends FEvent> EventProvider<T> getEvent(final Class<T> clazz)
|
||||
{
|
||||
for (final FEvent event : this.events)
|
||||
{
|
||||
if (clazz.isInstance(event))
|
||||
{
|
||||
return () -> clazz.cast(event);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
package fns.patchwork.data;
|
||||
|
||||
import fns.patchwork.security.Group;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||
|
||||
/**
|
||||
* 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();
|
||||
for (final Group group : groups)
|
||||
{
|
||||
final String n = s.serialize(group.getName());
|
||||
if (n.equalsIgnoreCase(name))
|
||||
{
|
||||
return group;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The list of groups.
|
||||
*/
|
||||
public List<Group> getGroups()
|
||||
{
|
||||
return groups;
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
package fns.patchwork.data;
|
||||
|
||||
import fns.patchwork.provider.ModuleProvider;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
/**
|
||||
* 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))
|
||||
{
|
||||
return;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 () -> clazz.cast(plugin);
|
||||
}
|
||||
}
|
||||
|
||||
return () -> null;
|
||||
}
|
||||
}
|
@ -0,0 +1,308 @@
|
||||
package fns.patchwork.data;
|
||||
|
||||
import fns.patchwork.service.Service;
|
||||
import fns.patchwork.service.ServiceSubscription;
|
||||
import fns.patchwork.service.SubscriptionProvider;
|
||||
import fns.patchwork.service.Task;
|
||||
import fns.patchwork.service.TaskSubscription;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* A registry for all services and tasks registered with Patchwork.
|
||||
* <br>
|
||||
* This class is <b>not</b> thread-safe, and should only be accessed from the main server thread.
|
||||
* <br>
|
||||
* <br>
|
||||
* <b>Services</b> are tickable tasks which execute every single game tick. They are registered using
|
||||
* {@link #registerService(ServiceSubscription)} and can be started using {@link #startService(Class)}.
|
||||
* <br>
|
||||
* <br>
|
||||
* <b>Tasks</b> are runnable tasks which execute at the provided times in the {@link Task} and
|
||||
* {@link TaskSubscription} classes. These define whether the Task is repeating, delayed, or just a one-time task. Tasks
|
||||
* are registered using {@link #registerTask(TaskSubscription)} and can be started using {@link #startTask(Class)}.
|
||||
* <br>
|
||||
* <br>
|
||||
* <b>ServiceSubscriptions</b> and <b>TaskSubscriptions</b> can both be easily obtained using the
|
||||
* {@link SubscriptionProvider} utility class.
|
||||
*
|
||||
* @see Service
|
||||
* @see Task
|
||||
* @see ServiceSubscription
|
||||
* @see TaskSubscription
|
||||
* @see SubscriptionProvider
|
||||
*/
|
||||
public class ServiceTaskRegistry
|
||||
{
|
||||
/**
|
||||
* A list of all services registered with the registry.
|
||||
*/
|
||||
private final List<ServiceSubscription<?>> services;
|
||||
/**
|
||||
* A list of all tasks registered with the registry.
|
||||
*/
|
||||
private final List<TaskSubscription<?>> tasks;
|
||||
|
||||
/**
|
||||
* Creates a new instance of the registry and initializes the service and task lists to new empty
|
||||
* {@link ArrayList}s.
|
||||
*/
|
||||
public ServiceTaskRegistry()
|
||||
{
|
||||
this.services = new ArrayList<>();
|
||||
this.tasks = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts all services registered with the registry.
|
||||
* <br>
|
||||
* This method should be <i>avoided</i>, due to the fact that <b><i>modules may have registered their services after
|
||||
* this method has already been called.</i></b> In this case, it is preferred to start each service using
|
||||
* {@link #startService(Class)}.
|
||||
* <br>
|
||||
* However, <i><b>Patchwork calls this method when the server is starting up</b></i>, as Patchwork is the central
|
||||
* resource manager for registered tasks and services. Patchwork will call this method on the first server tick, so
|
||||
* unless you are registering services <b>AND</b> starting them <b>POST WORLD</b>, you do not need to worry about
|
||||
* starting your services.
|
||||
*/
|
||||
public void startAllServices()
|
||||
{
|
||||
for (final ServiceSubscription<?> service : this.services)
|
||||
{
|
||||
service.start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts all tasks registered with the registry.
|
||||
* <br>
|
||||
* This method should be <i>avoided</i>, due to the fact that <b><i>modules may have registered their tasks after
|
||||
* this method has already been called.</i></b> In this case, it is preferred to start each task using
|
||||
* {@link #startTask(Class)}.
|
||||
* <br>
|
||||
* However, <i><b>Patchwork calls this method when the server is starting up</b></i>, as Patchwork is the central
|
||||
* resource manager for registered tasks and services. Patchwork will call this method on the first server tick, so
|
||||
* unless you are registering tasks <b>AND</b> starting them <b>POST WORLD</b>, you do not need to worry about
|
||||
* starting your tasks.
|
||||
*/
|
||||
public void startAllTasks()
|
||||
{
|
||||
for (final TaskSubscription<?> task : this.tasks)
|
||||
{
|
||||
task.start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops all services registered with the registry.
|
||||
* <br>
|
||||
* This method should be <i>avoided</i>, due to the fact that <b><i>modules should be handling their own
|
||||
* registrations</i></b>. It is preferred to use {@link #stopService(Class)} for each service you would like to
|
||||
* stop.
|
||||
* <br>
|
||||
* However, <b><i>Patchwork calls this method when the server is shutting down</i></b>, or when Patchwork is being
|
||||
* disabled, as Patchwork is the central resource manager for registered tasks and services. Unless you are
|
||||
* <b>modifying service states while the server is running</b>, you do not need to worry about disabling or
|
||||
* unregistering your services.
|
||||
*/
|
||||
public void stopAllServices()
|
||||
{
|
||||
for (final ServiceSubscription<?> service : this.services)
|
||||
{
|
||||
service.stop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops all tasks registered with the registry.
|
||||
* <br>
|
||||
* This method should be <i>avoided</i>, due to the fact that <b><i>modules should be handling their own
|
||||
* registrations</i></b>. It is preferred to use {@link #stopTask(Class)} for each task you would like to stop.
|
||||
* <br>
|
||||
* However, <b><i>Patchwork calls this method when the server is shutting down</i></b>, or when Patchwork is being
|
||||
* disabled, as Patchwork is the central resource manager for registered tasks and services. Unless you are
|
||||
* <b>modifying task states while the server is running</b>, you do not need to worry about disabling or
|
||||
* unregistering your tasks.
|
||||
*/
|
||||
public void stopAllTasks()
|
||||
{
|
||||
for (final TaskSubscription<?> task : this.tasks)
|
||||
{
|
||||
task.stop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a service with the registry.
|
||||
* <br>
|
||||
* <i>Services must be registered using <b>ServiceSubscriptions</b></i>, which can be easily obtained through the
|
||||
* {@link SubscriptionProvider} utility class.
|
||||
*
|
||||
* @param service The service you are trying to register.
|
||||
* @param <T> A generic type for type inference of the service being registered.
|
||||
*/
|
||||
public <T extends Service> void registerService(final ServiceSubscription<T> service)
|
||||
{
|
||||
this.services.add(service);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a task with the registry.
|
||||
* <br>
|
||||
* <i>Tasks must be registered using <b>TaskSubscriptions</b></i>, which can be easily obtained through the
|
||||
* {@link SubscriptionProvider} utility class.
|
||||
*
|
||||
* @param task The task you are trying to register.
|
||||
* @param <T> A generic type for type inference of the task being registered.
|
||||
*/
|
||||
public <T extends Task> void registerTask(final TaskSubscription<T> task)
|
||||
{
|
||||
this.tasks.add(task);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a service using the specified {@link Service} class.
|
||||
* <br>
|
||||
* <i>The service should already be registered with the registry as a <b>ServiceSubscription</b></i>.
|
||||
*
|
||||
* @param clazz The class of the service you are trying to start.
|
||||
* @see ServiceSubscription
|
||||
* @see #registerService(ServiceSubscription)
|
||||
*/
|
||||
public void startService(final Class<? extends Service> clazz)
|
||||
{
|
||||
this.getService(clazz)
|
||||
.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a {@link ServiceSubscription} from the registry using the specified class.
|
||||
* <br>
|
||||
* <b>The class should be the <u>service class you are trying to locate</u>, not the class for the subscription
|
||||
* itself</b>.
|
||||
* <br>
|
||||
* <i>The service should have been registered previously as a <b>ServiceSubscription</b></i>.
|
||||
*
|
||||
* @param clazz The class of the service you are trying to locate.
|
||||
* @param <T> A generic type for type inference of the service requested.
|
||||
* @return The {@link ServiceSubscription} for the specified class, or null if it could not be found.
|
||||
* @see #registerService(ServiceSubscription)
|
||||
* @see ServiceSubscription
|
||||
*/
|
||||
@Nullable
|
||||
public <T extends Service> ServiceSubscription<T> getService(final Class<T> clazz)
|
||||
{
|
||||
for (final ServiceSubscription<?> service : this.services)
|
||||
{
|
||||
if (service.getService()
|
||||
.getClass()
|
||||
.equals(clazz))
|
||||
{
|
||||
return (ServiceSubscription<T>) service;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops a service using the specified {@link Service} class.
|
||||
* <br>
|
||||
* <i>The service should already be registered with the registry as a <b>ServiceSubscription</b></i>.
|
||||
*
|
||||
* @param clazz The class of the service you are trying to stop.
|
||||
* @see #registerService(ServiceSubscription)
|
||||
* @see ServiceSubscription
|
||||
*/
|
||||
public void stopService(final Class<? extends Service> clazz)
|
||||
{
|
||||
this.getService(clazz)
|
||||
.stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a task using the specified {@link Task} class.
|
||||
* <br>
|
||||
* <i>The task should already be registered with the registry as a <b>TaskSubscription</b></i>.
|
||||
*
|
||||
* @param clazz The class of the task you are trying to start.
|
||||
* @see #registerTask(TaskSubscription)
|
||||
* @see TaskSubscription
|
||||
*/
|
||||
public void startTask(final Class<? extends Task> clazz)
|
||||
{
|
||||
this.getTask(clazz)
|
||||
.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a {@link TaskSubscription} from the registry using the specified class.
|
||||
* <br>
|
||||
* <b>The class should be the <u>task class you are trying to locate</u>, not the class for the subscription
|
||||
* itself</b>.
|
||||
* <br>
|
||||
* <i>The task should have been registered previously as a <b>TaskSubscription</b></i>.
|
||||
*
|
||||
* @param clazz The class of the task you are trying to locate.
|
||||
* @param <T> A generic type for type inference of the task requested.
|
||||
* @return The {@link TaskSubscription} for the specified class, or null if it could not be found.
|
||||
* @see #registerTask(TaskSubscription)
|
||||
* @see TaskSubscription
|
||||
*/
|
||||
public <T extends Task> TaskSubscription<T> getTask(final Class<T> clazz)
|
||||
{
|
||||
for (final TaskSubscription<?> task : this.tasks)
|
||||
{
|
||||
if (task.getTask()
|
||||
.getClass()
|
||||
.equals(clazz))
|
||||
{
|
||||
return (TaskSubscription<T>) task;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops a task using the specified {@link Task} class.
|
||||
* <br>
|
||||
* <i>The task should already be registered with the registry as a <b>TaskSubscription</b></i>.
|
||||
*
|
||||
* @param clazz The class of the task you are trying to stop.
|
||||
* @see #registerTask(TaskSubscription)
|
||||
* @see TaskSubscription
|
||||
*/
|
||||
public void stopTask(final Class<? extends Task> clazz)
|
||||
{
|
||||
this.getTask(clazz)
|
||||
.stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters a service from the registry.
|
||||
* <br>
|
||||
* <i>The service should have been registered previously as a <b>ServiceSubscription</b></i>.
|
||||
*
|
||||
* @param clazz The service you are trying to unregister.
|
||||
* @see #registerService(ServiceSubscription)
|
||||
* @see ServiceSubscription
|
||||
*/
|
||||
public void unregisterService(final Class<? extends Service> clazz)
|
||||
{
|
||||
this.services.remove(getService(clazz));
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters a task from the registry.
|
||||
* <br>
|
||||
* <i>The task should have been registered previously as a <b>TaskSubscription</b></i>.
|
||||
*
|
||||
* @param clazz The task you are trying to unregister.
|
||||
* @see #registerTask(TaskSubscription)
|
||||
* @see TaskSubscription
|
||||
*/
|
||||
public void unregisterTask(final Class<? extends Task> clazz)
|
||||
{
|
||||
this.tasks.remove(getTask(clazz));
|
||||
}
|
||||
}
|
101
Patchwork/src/main/java/fns/patchwork/data/UserRegistry.java
Normal file
101
Patchwork/src/main/java/fns/patchwork/data/UserRegistry.java
Normal file
@ -0,0 +1,101 @@
|
||||
package fns.patchwork.data;
|
||||
|
||||
import fns.patchwork.user.User;
|
||||
import fns.patchwork.user.UserData;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link UserData} object for the given {@link Player}.
|
||||
*/
|
||||
public UserData fromPlayer(final Player player)
|
||||
{
|
||||
return userDataMap.entrySet()
|
||||
.stream()
|
||||
.filter(entry -> entry.getKey()
|
||||
.getUniqueId()
|
||||
.equals(player.getUniqueId()))
|
||||
.findFirst()
|
||||
.map(Map.Entry::getValue)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link User} object for the given {@link Player}.
|
||||
*
|
||||
* @param player The {@link Player} to get the {@link User} for.
|
||||
* @return The {@link User} object for the given {@link Player}.
|
||||
*/
|
||||
public User getUser(final Player player)
|
||||
{
|
||||
return userDataMap.entrySet()
|
||||
.stream()
|
||||
.filter(entry -> entry.getKey()
|
||||
.getUniqueId()
|
||||
.equals(player.getUniqueId()))
|
||||
.findFirst()
|
||||
.map(Map.Entry::getKey)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the given {@link User} and {@link UserData} objects.
|
||||
*
|
||||
* @param user The {@link User} to register.
|
||||
* @param userData The {@link UserData} to register.
|
||||
*/
|
||||
public void registerUserData(final User user, final UserData userData)
|
||||
{
|
||||
userDataMap.put(user, userData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters the given {@link User} and {@link UserData} objects.
|
||||
*
|
||||
* @param user The {@link User} to unregister.
|
||||
*/
|
||||
public void unregisterUserData(final User user)
|
||||
{
|
||||
userDataMap.remove(user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the map of {@link User} objects to {@link UserData} objects.
|
||||
*
|
||||
* @return The map of {@link User} objects to {@link UserData} objects.
|
||||
*/
|
||||
public Map<User, UserData> getUserDataMap()
|
||||
{
|
||||
return userDataMap;
|
||||
}
|
||||
}
|
196
Patchwork/src/main/java/fns/patchwork/display/AbstractMenu.java
Normal file
196
Patchwork/src/main/java/fns/patchwork/display/AbstractMenu.java
Normal file
@ -0,0 +1,196 @@
|
||||
package fns.patchwork.display;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* 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<>();
|
||||
this.displayable = new Displayable(size);
|
||||
this.displayableUUID = UUID.randomUUID();
|
||||
|
||||
invByUUID.put(getDisplayableUUID(), this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A map of all menus by their UUID.
|
||||
*/
|
||||
public static Map<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);
|
||||
if (action != null)
|
||||
{
|
||||
actionMap.put(slot, action);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The displayable that represents this menu.
|
||||
*/
|
||||
public Displayable getDisplayable()
|
||||
{
|
||||
return displayable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens this menu for the specified player.
|
||||
*
|
||||
* @param player The player to open the menu for.
|
||||
*/
|
||||
public void open(final @NotNull Player player)
|
||||
{
|
||||
player.openInventory(getDisplayable());
|
||||
openInvs.put(player.getUniqueId(), getDisplayableUUID());
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes this menu.
|
||||
*/
|
||||
public void delete()
|
||||
{
|
||||
Bukkit.getOnlinePlayers()
|
||||
.forEach(player ->
|
||||
{
|
||||
if (openInvs.get(player.getUniqueId())
|
||||
.equals(getDisplayableUUID()))
|
||||
{
|
||||
close(player);
|
||||
}
|
||||
});
|
||||
|
||||
invByUUID.remove(getDisplayableUUID());
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes this menu for the specified player.
|
||||
*
|
||||
* @param player The player to close the menu for.
|
||||
*/
|
||||
public void close(final @NotNull Player player)
|
||||
{
|
||||
player.closeInventory();
|
||||
openInvs.remove(player.getUniqueId());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A map of all actions by their slot.
|
||||
*/
|
||||
public Map<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 ItemStack item = new ItemStack(material, 1);
|
||||
final ItemMeta meta = item.getItemMeta();
|
||||
if (meta == null)
|
||||
{
|
||||
return item;
|
||||
}
|
||||
meta.displayName(name);
|
||||
final List<Component> metaLore = Arrays.asList(lore);
|
||||
meta.lore(metaLore);
|
||||
item.setItemMeta(meta);
|
||||
return item;
|
||||
}
|
||||
}
|
@ -0,0 +1,380 @@
|
||||
package fns.patchwork.display;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
import net.kyori.adventure.audience.ForwardingAudience;
|
||||
import net.kyori.adventure.bossbar.BossBar;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import org.jetbrains.annotations.Range;
|
||||
|
||||
/**
|
||||
* This class is a wrapper for {@link BossBar} objects. It provides some handy methods for changing the boss bar's
|
||||
* properties, displaying the bar to {@link Audience}s, and a {@link BossBarBuilder} to easily create new boss bars.
|
||||
*/
|
||||
public class BossBarDisplay
|
||||
{
|
||||
private BossBar bossBar;
|
||||
|
||||
/**
|
||||
* Creates a new {@link BossBarDisplay} object.
|
||||
*
|
||||
* @param bossBar The {@link BossBar} to wrap.
|
||||
*/
|
||||
public BossBarDisplay(final BossBar bossBar)
|
||||
{
|
||||
this.bossBar = bossBar;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A new {@link BossBarBuilder} object.
|
||||
*/
|
||||
public static BossBarBuilder builder()
|
||||
{
|
||||
return new BossBarBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the boss bar's color.
|
||||
*
|
||||
* @param color The new color.
|
||||
*/
|
||||
public void changeColor(final BossBar.Color color)
|
||||
{
|
||||
this.bossBar.color(color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the boss bar's color.
|
||||
*
|
||||
* @param overlay The new overlay.
|
||||
*/
|
||||
public void changeOverlay(final BossBar.Overlay overlay)
|
||||
{
|
||||
this.bossBar.overlay(overlay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the boss bar's name using a {@link Component}.
|
||||
*
|
||||
* @param name The new name.
|
||||
*/
|
||||
public void changeName(final Component name)
|
||||
{
|
||||
this.bossBar.name(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the boss bar's name with a String and a {@link TextColor}.
|
||||
*
|
||||
* @param name The new name.
|
||||
* @param color The name color.
|
||||
*/
|
||||
public void changeName(final String name, final TextColor color)
|
||||
{
|
||||
this.bossBar.name(Component.text(name, color));
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the boss bar's name with a String.
|
||||
*
|
||||
* @param name The new name.
|
||||
*/
|
||||
public void changeName(final String name)
|
||||
{
|
||||
this.bossBar.name(Component.text(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows this Boss Bar to the specified {@link Audience}.
|
||||
*
|
||||
* @param audience The {@link Audience} to show the Boss Bar to.
|
||||
*/
|
||||
public void showTo(final Audience audience)
|
||||
{
|
||||
audience.showBossBar(getBossBar());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The {@link BossBar} object that this class wraps.
|
||||
*/
|
||||
public BossBar getBossBar()
|
||||
{
|
||||
return this.bossBar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link BossBar} object that this class wraps.
|
||||
*
|
||||
* @param bossBar The new {@link BossBar} object.
|
||||
*/
|
||||
public void setBossBar(final BossBar bossBar)
|
||||
{
|
||||
this.bossBar = bossBar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides this Boss Bar from the specified {@link Audience}.
|
||||
*
|
||||
* @param audience The {@link Audience} to hide the Boss Bar from.
|
||||
*/
|
||||
public void hideFrom(final Audience audience)
|
||||
{
|
||||
audience.hideBossBar(getBossBar());
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments the Bar's progress by the specified amount. This must be a range from 0 to 100.
|
||||
*
|
||||
* @param progress The new progress.
|
||||
*/
|
||||
public void incrementProgress(final @Range(from = 0, to = 100) float progress)
|
||||
{
|
||||
final float currentProgress = this.bossBar.progress();
|
||||
final float newProgress = currentProgress + (progress / 100.0F);
|
||||
|
||||
if (newProgress > 1) this.bossBar.progress(1.0F);
|
||||
else this.bossBar.progress(newProgress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrements the Bar's progress by the specified amount. This must be a range from 0 to 100.
|
||||
*
|
||||
* @param progress The new progress.
|
||||
*/
|
||||
public void decrementProgress(final @Range(from = 0, to = 100) float progress)
|
||||
{
|
||||
final float currentProgress = this.bossBar.progress();
|
||||
final float newProgress = currentProgress - (progress / 100.0F);
|
||||
|
||||
if (newProgress < 0) this.bossBar.progress(0.0F);
|
||||
else this.bossBar.progress(newProgress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Bar's progress to the maximum amount (full bar).
|
||||
*/
|
||||
public void maximumProgress()
|
||||
{
|
||||
this.bossBar.progress(1.0F);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Bar's progress to half of the maximum amount (half bar).
|
||||
*/
|
||||
public void halfProgress()
|
||||
{
|
||||
this.bossBar.progress(0.5F);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Bar's progress to the minimum amount (empty bar).
|
||||
*/
|
||||
public void minimumProgress()
|
||||
{
|
||||
this.bossBar.progress(0.0F);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows this Boss Bar to the specified {@link ForwardingAudience}.
|
||||
*
|
||||
* @param forwardingAudience The {@link ForwardingAudience} to show the Boss Bar to.
|
||||
*/
|
||||
public void showForwarded(final ForwardingAudience forwardingAudience)
|
||||
{
|
||||
forwardingAudience.showBossBar(getBossBar());
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides this Boss Bar from the specified {@link ForwardingAudience}.
|
||||
*
|
||||
* @param forwardingAudience The {@link ForwardingAudience} to hide the Boss Bar from.
|
||||
*/
|
||||
public void hideForwarded(final ForwardingAudience forwardingAudience)
|
||||
{
|
||||
forwardingAudience.hideBossBar(getBossBar());
|
||||
}
|
||||
|
||||
/**
|
||||
* A Builder class for {@link BossBar} objects.
|
||||
*/
|
||||
public static final class BossBarBuilder
|
||||
{
|
||||
/**
|
||||
* The flags that this Boss Bar will have.
|
||||
*/
|
||||
private final Set<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();
|
||||
this.color = BossBar.Color.GREEN;
|
||||
this.overlay = BossBar.Overlay.PROGRESS;
|
||||
this.progress = 0.0F;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name of the boss bar.
|
||||
*
|
||||
* @param name The name of the boss bar.
|
||||
* @return The builder.
|
||||
*/
|
||||
public BossBarBuilder setName(final Component name)
|
||||
{
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name of the boss bar using a String and a {@link TextColor}.
|
||||
*
|
||||
* @param name The name of the boss bar.
|
||||
* @param color The color of the boss bar.
|
||||
* @return The builder.
|
||||
*/
|
||||
public BossBarBuilder setName(final String name, final TextColor color)
|
||||
{
|
||||
this.name = Component.text(name, color);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name of the boss bar using a String.
|
||||
*
|
||||
* @param name The name of the boss bar.
|
||||
* @return The builder.
|
||||
*/
|
||||
public BossBarBuilder setName(final String name)
|
||||
{
|
||||
this.name = Component.text(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a flag to the boss bar.
|
||||
*
|
||||
* @param flag The flag to add.
|
||||
* @return The builder.
|
||||
*/
|
||||
public BossBarBuilder addFlag(final BossBar.Flag flag)
|
||||
{
|
||||
this.flags.add(flag);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds multiple flags to the boss bar.
|
||||
*
|
||||
* @param flags The flags to add.
|
||||
* @return The builder.
|
||||
*/
|
||||
public BossBarBuilder addFlags(final BossBar.Flag... flags)
|
||||
{
|
||||
this.flags.addAll(List.of(flags));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a flag from the boss bar.
|
||||
*
|
||||
* @param flag The flag to remove.
|
||||
* @return The builder.
|
||||
*/
|
||||
public BossBarBuilder removeFlag(final BossBar.Flag flag)
|
||||
{
|
||||
this.flags.remove(flag);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes multiple flags from the boss bar.
|
||||
*
|
||||
* @param flags The flags to remove.
|
||||
* @return The builder.
|
||||
*/
|
||||
public BossBarBuilder removeFlags(final BossBar.Flag... flags)
|
||||
{
|
||||
this.flags.removeAll(List.of(flags));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all flags from the boss bar.
|
||||
*
|
||||
* @return The builder.
|
||||
*/
|
||||
public BossBarBuilder clearFlags()
|
||||
{
|
||||
this.flags.clear();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the color of the boss bar.
|
||||
*
|
||||
* @param color The color of the boss bar.
|
||||
* @return The builder.
|
||||
*/
|
||||
public BossBarBuilder setColor(final BossBar.Color color)
|
||||
{
|
||||
this.color = color;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the overlay of the boss bar.
|
||||
*
|
||||
* @param overlay The overlay of the boss bar.
|
||||
* @return The builder.
|
||||
*/
|
||||
public BossBarBuilder setOverlay(final BossBar.Overlay overlay)
|
||||
{
|
||||
this.overlay = overlay;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the progress of the boss bar. This must satisfy {@code 0 <= progress <= 100}.
|
||||
*
|
||||
* @param progress The progress of the boss bar.
|
||||
* @return The builder.
|
||||
*/
|
||||
public BossBarBuilder setProgress(final @Range(from = 0, to = 100) float progress)
|
||||
{
|
||||
this.progress = progress / 100.0F;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the boss bar.
|
||||
*
|
||||
* @return The {@link BossBar}.
|
||||
*/
|
||||
public BossBar build()
|
||||
{
|
||||
return BossBar.bossBar(this.name, this.progress, this.color, this.overlay, this.flags);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package fns.patchwork.display;
|
||||
|
||||
import fns.patchwork.service.Task;
|
||||
import java.time.Duration;
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
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++;
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package fns.patchwork.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);
|
||||
}
|
497
Patchwork/src/main/java/fns/patchwork/display/Displayable.java
Normal file
497
Patchwork/src/main/java/fns/patchwork/display/Displayable.java
Normal file
@ -0,0 +1,497 @@
|
||||
package fns.patchwork.display;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.HumanEntity;
|
||||
import org.bukkit.event.inventory.InventoryType;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.InventoryHolder;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* A class that represents an inventory that can be displayed to players. This class also represents the inventory
|
||||
* holder which contains the inventory.
|
||||
*/
|
||||
public final class Displayable implements Inventory, InventoryHolder
|
||||
{
|
||||
|
||||
/**
|
||||
* The size of the inventory. This is always a multiple of 9.
|
||||
*/
|
||||
private final int size;
|
||||
/**
|
||||
* The contents of the inventory.
|
||||
*/
|
||||
private ItemStack[] contents;
|
||||
|
||||
/**
|
||||
* Creates a new Displayable inventory with the given size. You are free to supply any size you want, but it will
|
||||
* always be rounded up to the next multiple of 9. The maximum size allowed is 54. Any number higher than that will
|
||||
* be rounded down to 54.
|
||||
*
|
||||
* @param size The size of the inventory.
|
||||
*/
|
||||
protected Displayable(final int size)
|
||||
{
|
||||
if (size < 1 || size > 54)
|
||||
{
|
||||
throw new IllegalArgumentException("Invalid size for Displayable inventory");
|
||||
}
|
||||
|
||||
// If the size is not a multiple of nine, find the difference to the next highest multiple of 9 and make up
|
||||
// the difference.
|
||||
this.size = (size % 9 == 0)
|
||||
? size
|
||||
: size + (9 - size % 9);
|
||||
|
||||
this.contents = new ItemStack[size];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize()
|
||||
{
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxStackSize()
|
||||
{
|
||||
return 64;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param size The new maximum stack size for items in this inventory.
|
||||
* @deprecated This method is not supported by Displayable inventories.
|
||||
*/
|
||||
@Override
|
||||
@Deprecated(since = "1.19.4")
|
||||
public void setMaxStackSize(final int size)
|
||||
{
|
||||
// No implementation required
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ItemStack getItem(final int index)
|
||||
{
|
||||
if (index < 0 || index >= size)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return contents[index];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem(final int index, final @Nullable ItemStack item)
|
||||
{
|
||||
if (index < 0 || index >= size)
|
||||
{
|
||||
return;
|
||||
}
|
||||
contents[index] = item;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull HashMap<Integer, ItemStack> addItem(final @NotNull ItemStack... items)
|
||||
throws IllegalArgumentException
|
||||
{
|
||||
final HashMap<Integer, ItemStack> remainingItems = new HashMap<>();
|
||||
|
||||
for (final ItemStack item : items)
|
||||
{
|
||||
int remainingAmount = item.getAmount();
|
||||
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
final ItemStack current = contents[i];
|
||||
|
||||
if (current == null)
|
||||
{
|
||||
final int maxStackSize = item.getMaxStackSize();
|
||||
final int amountToAdd = Math.min(remainingAmount, maxStackSize);
|
||||
final ItemStack clone = item.clone();
|
||||
clone.setAmount(amountToAdd);
|
||||
contents[i] = clone;
|
||||
remainingAmount -= amountToAdd;
|
||||
|
||||
if (remainingAmount == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (remainingAmount > 0)
|
||||
{
|
||||
remainingItems.put(remainingItems.size(), new ItemStack(item.getType(), remainingAmount));
|
||||
}
|
||||
}
|
||||
|
||||
return remainingItems;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull HashMap<Integer, ItemStack> removeItem(final @NotNull ItemStack... items)
|
||||
throws IllegalArgumentException
|
||||
{
|
||||
final HashMap<Integer, ItemStack> removedItems = new HashMap<>();
|
||||
|
||||
for (final ItemStack item : items)
|
||||
{
|
||||
int remainingAmount = item.getAmount();
|
||||
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
final ItemStack current = contents[i];
|
||||
|
||||
if (current != null && current.isSimilar(item))
|
||||
{
|
||||
final int amountToRemove = Math.min(remainingAmount, current.getAmount());
|
||||
current.setAmount(current.getAmount() - amountToRemove);
|
||||
remainingAmount -= amountToRemove;
|
||||
|
||||
if (current.getAmount() <= 0)
|
||||
{
|
||||
contents[i] = null;
|
||||
}
|
||||
|
||||
if (remainingAmount == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (remainingAmount < item.getAmount())
|
||||
{
|
||||
removedItems.put(removedItems.size(),
|
||||
new ItemStack(item.getType(), item.getAmount() - remainingAmount));
|
||||
}
|
||||
}
|
||||
|
||||
return removedItems;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull HashMap<Integer, ItemStack> removeItemAnySlot(final @NotNull ItemStack... items)
|
||||
throws IllegalArgumentException
|
||||
{
|
||||
return removeItem(items);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ItemStack @NotNull [] getContents()
|
||||
{
|
||||
return contents.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContents(final @Nullable ItemStack @NotNull [] items) throws IllegalArgumentException
|
||||
{
|
||||
if (items == null)
|
||||
{
|
||||
throw new IllegalArgumentException("Items cannot be null");
|
||||
}
|
||||
if (items.length != size)
|
||||
{
|
||||
throw new IllegalArgumentException("Invalid size of items array");
|
||||
}
|
||||
System.arraycopy(items, 0, contents, 0, size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ItemStack @NotNull [] getStorageContents()
|
||||
{
|
||||
return contents;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStorageContents(final @Nullable ItemStack @NotNull [] items) throws IllegalArgumentException
|
||||
{
|
||||
this.contents = items;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(final @NotNull Material material) throws IllegalArgumentException
|
||||
{
|
||||
for (final ItemStack item : contents)
|
||||
{
|
||||
if (item != null && item.getType() == material)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(final @Nullable ItemStack item)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for (final ItemStack content : contents)
|
||||
{
|
||||
if (content != null && content.isSimilar(item))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(final @NotNull Material material, final int amount) throws IllegalArgumentException
|
||||
{
|
||||
int totalAmount = 0;
|
||||
for (final ItemStack item : contents)
|
||||
{
|
||||
if (item != null && item.getType() == material)
|
||||
{
|
||||
totalAmount += item.getAmount();
|
||||
if (totalAmount >= amount)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(final @Nullable ItemStack item, final int amount)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
int totalAmount = 0;
|
||||
for (final ItemStack content : contents)
|
||||
{
|
||||
if (content != null && content.isSimilar(item))
|
||||
{
|
||||
totalAmount += content.getAmount();
|
||||
if (totalAmount == amount)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAtLeast(final @Nullable ItemStack item, final int amount)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
int totalAmount = 0;
|
||||
for (final ItemStack content : contents)
|
||||
{
|
||||
if (content != null && content.isSimilar(item))
|
||||
{
|
||||
totalAmount += content.getAmount();
|
||||
if (totalAmount >= amount)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull HashMap<Integer, ? extends ItemStack> all(final @NotNull Material material)
|
||||
throws IllegalArgumentException
|
||||
{
|
||||
final HashMap<Integer, ItemStack> matchingItems = new HashMap<>();
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
final ItemStack item = contents[i];
|
||||
if (item != null && item.getType() == material)
|
||||
{
|
||||
matchingItems.put(i, item);
|
||||
}
|
||||
}
|
||||
return matchingItems;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull HashMap<Integer, ? extends ItemStack> all(final @Nullable ItemStack item)
|
||||
{
|
||||
final HashMap<Integer, ItemStack> matchingItems = new HashMap<>();
|
||||
if (item != null)
|
||||
{
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
final ItemStack content = contents[i];
|
||||
if (content != null && content.isSimilar(item))
|
||||
{
|
||||
matchingItems.put(i, content);
|
||||
}
|
||||
}
|
||||
}
|
||||
return matchingItems;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int first(final @NotNull Material material) throws IllegalArgumentException
|
||||
{
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
final ItemStack item = contents[i];
|
||||
if (item != null && item.getType() == material)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int first(final @NotNull ItemStack item)
|
||||
{
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
final ItemStack content = contents[i];
|
||||
if (content != null && content.isSimilar(item))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int firstEmpty()
|
||||
{
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
if (contents[i] == null)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty()
|
||||
{
|
||||
for (final ItemStack content : contents)
|
||||
{
|
||||
if (content != null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(final @NotNull Material material) throws IllegalArgumentException
|
||||
{
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
final ItemStack item = contents[i];
|
||||
if (item != null && item.getType() == material)
|
||||
{
|
||||
contents[i] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(final @NotNull ItemStack item)
|
||||
{
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
final ItemStack content = contents[i];
|
||||
if (content != null && content.isSimilar(item))
|
||||
{
|
||||
contents[i] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear(final int index)
|
||||
{
|
||||
if (index >= 0 && index < size)
|
||||
{
|
||||
contents[index] = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear()
|
||||
{
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
contents[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int close()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull List<HumanEntity> getViewers()
|
||||
{
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull InventoryType getType()
|
||||
{
|
||||
return InventoryType.CHEST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable InventoryHolder getHolder()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable InventoryHolder getHolder(final boolean useSnapshot)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ListIterator<ItemStack> iterator()
|
||||
{
|
||||
return iterator(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ListIterator<ItemStack> iterator(final int index)
|
||||
{
|
||||
return List.of(contents)
|
||||
.listIterator(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Location getLocation()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Inventory getInventory()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
package fns.patchwork.display;
|
||||
|
||||
import fns.patchwork.utils.kyori.FreedomAdventure;
|
||||
import org.bukkit.entity.HumanEntity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.inventory.InventoryType;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.InventoryView;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* A view of a {@link Displayable} inventory.
|
||||
* <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;
|
||||
this.top = top;
|
||||
this.bottom = bottom;
|
||||
this.type = InventoryType.CHEST;
|
||||
this.title = FreedomAdventure.toPlainText(type.defaultTitle());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Inventory getTopInventory()
|
||||
{
|
||||
return top;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Inventory getBottomInventory()
|
||||
{
|
||||
return bottom;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull HumanEntity getPlayer()
|
||||
{
|
||||
return player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull InventoryType getType()
|
||||
{
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated(forRemoval = true, since = "1.16")
|
||||
public @NotNull String getTitle()
|
||||
{
|
||||
return title;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTitle(final @NotNull String title)
|
||||
{
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getOriginalTitle()
|
||||
{
|
||||
return FreedomAdventure.toPlainText(type.defaultTitle());
|
||||
}
|
||||
}
|
249
Patchwork/src/main/java/fns/patchwork/display/TitleDisplay.java
Normal file
249
Patchwork/src/main/java/fns/patchwork/display/TitleDisplay.java
Normal file
@ -0,0 +1,249 @@
|
||||
package fns.patchwork.display;
|
||||
|
||||
import java.time.Duration;
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
import net.kyori.adventure.audience.ForwardingAudience;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import net.kyori.adventure.title.Title;
|
||||
|
||||
/**
|
||||
* A wrapper class for {@link Title}s that allows for easy display to an {@link Audience}.
|
||||
*/
|
||||
public class TitleDisplay
|
||||
{
|
||||
/**
|
||||
* The {@link Title} to display.
|
||||
*/
|
||||
private Title title;
|
||||
|
||||
/**
|
||||
* Creates a new {@link TitleDisplay} with the given {@link Title}.
|
||||
*
|
||||
* @param title The {@link Title} to display.
|
||||
*/
|
||||
public TitleDisplay(final Title title)
|
||||
{
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A new {@link TitleBuilder} which can be used to create new {@link Title}s.
|
||||
*/
|
||||
public static TitleBuilder builder()
|
||||
{
|
||||
return new TitleBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the {@link Title} to the given {@link Audience}.
|
||||
*
|
||||
* @param audience The {@link Audience} to display the {@link Title} to.
|
||||
*/
|
||||
public void displayTo(final Audience audience)
|
||||
{
|
||||
audience.clearTitle();
|
||||
audience.showTitle(getTitle());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The {@link Title} to display.
|
||||
*/
|
||||
public Title getTitle()
|
||||
{
|
||||
return this.title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Title} to display.
|
||||
*
|
||||
* @param title The {@link Title} to display.
|
||||
*/
|
||||
public void setTitle(final Title title)
|
||||
{
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the {@link Title} to the given {@link ForwardingAudience}.
|
||||
*
|
||||
* @param forwardingAudience The {@link ForwardingAudience} to display the {@link Title} to.
|
||||
*/
|
||||
public void displayForwarded(final ForwardingAudience forwardingAudience)
|
||||
{
|
||||
forwardingAudience.clearTitle();
|
||||
forwardingAudience.showTitle(getTitle());
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder class for {@link Title}s.
|
||||
*/
|
||||
public static final class TitleBuilder
|
||||
{
|
||||
/**
|
||||
* The main title of the {@link Title}.
|
||||
*/
|
||||
private Component mainTitle;
|
||||
/**
|
||||
* The subtitle of the {@link Title}.
|
||||
*/
|
||||
private Component subTitle;
|
||||
/**
|
||||
* How long the Title should fade in for.
|
||||
*/
|
||||
private Duration fadeIn;
|
||||
/**
|
||||
* How long the Title should fade out for.
|
||||
*/
|
||||
private Duration fadeOut;
|
||||
/**
|
||||
* How long the Title should be displayed for.
|
||||
*/
|
||||
private Duration displayDuration;
|
||||
|
||||
/**
|
||||
* Creates a new {@link TitleBuilder} with default values. The default values are:
|
||||
* <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();
|
||||
this.subTitle = Component.empty();
|
||||
this.fadeIn = Title.DEFAULT_TIMES.fadeIn();
|
||||
this.fadeOut = Title.DEFAULT_TIMES.fadeOut();
|
||||
this.displayDuration = Title.DEFAULT_TIMES.stay();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the main title of the {@link Title}.
|
||||
*
|
||||
* @param title The main title of the {@link Title}.
|
||||
* @return The {@link TitleBuilder} instance.
|
||||
*/
|
||||
public TitleBuilder setMainTitle(final String title)
|
||||
{
|
||||
this.mainTitle = Component.text(title);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the main title of the {@link Title}.
|
||||
*
|
||||
* @param title The main title of the {@link Title}.
|
||||
* @param titleColor The color of the main title.
|
||||
* @return The {@link TitleBuilder} instance.
|
||||
*/
|
||||
public TitleBuilder setMainTitle(final String title, final TextColor titleColor)
|
||||
{
|
||||
this.mainTitle = Component.text(title, titleColor);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the main title of the {@link Title}.
|
||||
*
|
||||
* @param mainTitle The main title of the {@link Title}.
|
||||
* @return The {@link TitleBuilder} instance.
|
||||
*/
|
||||
public TitleBuilder setMainTitle(final Component mainTitle)
|
||||
{
|
||||
this.mainTitle = mainTitle;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the subtitle of the {@link Title}.
|
||||
*
|
||||
* @param title The subtitle of the {@link Title}.
|
||||
* @return The {@link TitleBuilder} instance.
|
||||
*/
|
||||
public TitleBuilder setSubTitle(final String title)
|
||||
{
|
||||
this.subTitle = Component.text(title);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the subtitle of the {@link Title}.
|
||||
*
|
||||
* @param title The subtitle of the {@link Title}.
|
||||
* @param titleColor The color of the subtitle.
|
||||
* @return The {@link TitleBuilder} instance.
|
||||
*/
|
||||
public TitleBuilder setSubTitle(final String title, final TextColor titleColor)
|
||||
{
|
||||
this.subTitle = Component.text(title, titleColor);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the subtitle of the {@link Title}.
|
||||
*
|
||||
* @param subTitle The subtitle of the {@link Title}.
|
||||
* @return The {@link TitleBuilder} instance.
|
||||
*/
|
||||
public TitleBuilder setSubTitle(final Component subTitle)
|
||||
{
|
||||
this.subTitle = subTitle;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the fade in time of the {@link Title}.
|
||||
*
|
||||
* @param duration The fade in time of the {@link Title}.
|
||||
* @return The {@link TitleBuilder} instance.
|
||||
*/
|
||||
public TitleBuilder setFadeIn(final Duration duration)
|
||||
{
|
||||
this.fadeIn = duration;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the fade out time of the {@link Title}.
|
||||
*
|
||||
* @param duration The fade out time of the {@link Title}.
|
||||
* @return The {@link TitleBuilder} instance.
|
||||
*/
|
||||
public TitleBuilder setFadeOut(final Duration duration)
|
||||
{
|
||||
this.fadeOut = duration;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the display duration of the {@link Title}.
|
||||
*
|
||||
* @param duration The display duration of the {@link Title}.
|
||||
* @return The {@link TitleBuilder} instance.
|
||||
*/
|
||||
public TitleBuilder setDisplayDuration(final Duration duration)
|
||||
{
|
||||
this.displayDuration = duration;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the {@link Title} with the given parameters.
|
||||
*
|
||||
* @return The built {@link Title}.
|
||||
*/
|
||||
public Title build()
|
||||
{
|
||||
return Title.title(
|
||||
this.mainTitle,
|
||||
this.subTitle,
|
||||
Title.Times.times(this.fadeIn, this.displayDuration, this.fadeOut)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,119 @@
|
||||
package fns.patchwork.display.adminchat;
|
||||
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
|
||||
public class ACFormatBuilder
|
||||
{
|
||||
private char openTag = '[';
|
||||
private char closeTag = ']';
|
||||
private TextColor prefixColor = NamedTextColor.DARK_RED;
|
||||
private TextColor bracketColor = NamedTextColor.WHITE;
|
||||
private TextColor nameColor = NamedTextColor.AQUA;
|
||||
private TextColor rankColor = NamedTextColor.GOLD;
|
||||
private String prefix = "Admin";
|
||||
private String chatSplitter = ">>";
|
||||
|
||||
private ACFormatBuilder()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public static ACFormatBuilder format()
|
||||
{
|
||||
return new ACFormatBuilder();
|
||||
}
|
||||
|
||||
public ACFormatBuilder openBracket(final char openTag)
|
||||
{
|
||||
this.openTag = openTag;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ACFormatBuilder closeBracket(final char closeTag)
|
||||
{
|
||||
this.closeTag = closeTag;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ACFormatBuilder prefixColor(final TextColor prefixColor)
|
||||
{
|
||||
this.prefixColor = prefixColor;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ACFormatBuilder bracketColor(final TextColor bracketColor)
|
||||
{
|
||||
this.bracketColor = bracketColor;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ACFormatBuilder prefix(final String prefix)
|
||||
{
|
||||
this.prefix = prefix;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ACFormatBuilder chatSplitter(final String chatSplitter)
|
||||
{
|
||||
this.chatSplitter = chatSplitter;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ACFormatBuilder nameColor(final TextColor nameColor)
|
||||
{
|
||||
this.nameColor = nameColor;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ACFormatBuilder rankColor(final TextColor rankColor)
|
||||
{
|
||||
this.rankColor = rankColor;
|
||||
return this;
|
||||
}
|
||||
|
||||
String openBracket()
|
||||
{
|
||||
return String.valueOf(openTag);
|
||||
}
|
||||
|
||||
String closeBracket()
|
||||
{
|
||||
return String.valueOf(closeTag);
|
||||
}
|
||||
|
||||
TextColor prefixColor()
|
||||
{
|
||||
return prefixColor;
|
||||
}
|
||||
|
||||
TextColor bracketColor()
|
||||
{
|
||||
return bracketColor;
|
||||
}
|
||||
|
||||
TextColor nameColor()
|
||||
{
|
||||
return nameColor;
|
||||
}
|
||||
|
||||
TextColor rankColor()
|
||||
{
|
||||
return rankColor;
|
||||
}
|
||||
|
||||
String prefix()
|
||||
{
|
||||
return prefix;
|
||||
}
|
||||
|
||||
String chatSplitter()
|
||||
{
|
||||
return chatSplitter;
|
||||
}
|
||||
|
||||
public AdminChatFormat build()
|
||||
{
|
||||
return new AdminChatFormat(this);
|
||||
}
|
||||
}
|
@ -0,0 +1,141 @@
|
||||
package fns.patchwork.display.adminchat;
|
||||
|
||||
import fns.patchwork.base.Patchwork;
|
||||
import fns.patchwork.base.Shortcuts;
|
||||
import fns.patchwork.security.Groups;
|
||||
import fns.patchwork.user.UserData;
|
||||
import io.papermc.paper.event.player.AsyncChatEvent;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
|
||||
public class AdminChatDisplay
|
||||
{
|
||||
protected static final String ACPERM = "patchwork.adminchat";
|
||||
private final Map<UUID, AdminChatFormat> adminChatFormat = new HashMap<>();
|
||||
private final Set<UUID> toggledChat = new HashSet<>();
|
||||
|
||||
public AdminChatDisplay()
|
||||
{
|
||||
new ACListener(this);
|
||||
}
|
||||
|
||||
public void addPlayer(final Player player, final AdminChatFormat format)
|
||||
{
|
||||
adminChatFormat.put(player.getUniqueId(), format);
|
||||
}
|
||||
|
||||
public void removePlayer(final Player player)
|
||||
{
|
||||
adminChatFormat.remove(player.getUniqueId());
|
||||
}
|
||||
|
||||
public boolean hasPlayer(final Player player)
|
||||
{
|
||||
return adminChatFormat.containsKey(player.getUniqueId());
|
||||
}
|
||||
|
||||
public void updateFormat(final Player player, final AdminChatFormat newFormat)
|
||||
{
|
||||
adminChatFormat.put(player.getUniqueId(), newFormat);
|
||||
}
|
||||
|
||||
public AdminChatFormat getFormat(final Player player)
|
||||
{
|
||||
return adminChatFormat.get(player.getUniqueId());
|
||||
}
|
||||
|
||||
public Set<UUID> getPlayers()
|
||||
{
|
||||
return adminChatFormat.keySet();
|
||||
}
|
||||
|
||||
public Map<UUID, AdminChatFormat> getAdminChatFormat()
|
||||
{
|
||||
return adminChatFormat;
|
||||
}
|
||||
|
||||
public boolean isToggled(final Player player)
|
||||
{
|
||||
return toggledChat.contains(player.getUniqueId());
|
||||
}
|
||||
|
||||
public void toggleChat(final Player player)
|
||||
{
|
||||
if (toggledChat.contains(player.getUniqueId()))
|
||||
{
|
||||
toggledChat.remove(player.getUniqueId());
|
||||
} else
|
||||
{
|
||||
toggledChat.add(player.getUniqueId());
|
||||
}
|
||||
}
|
||||
|
||||
public void adminChatMessage(final CommandSender sender, final Component message)
|
||||
{
|
||||
Bukkit.getOnlinePlayers()
|
||||
.forEach(player ->
|
||||
{
|
||||
if (player.hasPermission(ACPERM))
|
||||
{
|
||||
final Component formatted = Component.empty();
|
||||
formatted.append(getFormat(player).format(sender.getName(), Groups.fromSender(sender)))
|
||||
.append(Component.space())
|
||||
.append(message);
|
||||
|
||||
player.sendMessage(formatted);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static final class ACListener implements Listener
|
||||
{
|
||||
private final AdminChatDisplay display;
|
||||
|
||||
public ACListener(final AdminChatDisplay display)
|
||||
{
|
||||
this.display = display;
|
||||
Bukkit.getPluginManager()
|
||||
.registerEvents(this, Shortcuts.provideModule(Patchwork.class));
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void playerChat(final AsyncChatEvent event)
|
||||
{
|
||||
if (display.isToggled(event.getPlayer()))
|
||||
{
|
||||
event.setCancelled(true);
|
||||
display.adminChatMessage(event.getPlayer(), event.message());
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void playerJoin(final PlayerJoinEvent event)
|
||||
{
|
||||
final Player player = event.getPlayer();
|
||||
if (player.hasPermission(ACPERM))
|
||||
{
|
||||
final UserData data = Patchwork.getInstance()
|
||||
.getRegistrations()
|
||||
.getUserRegistry()
|
||||
.fromPlayer(player);
|
||||
if (data.hasCustomACFormat())
|
||||
{
|
||||
display.addPlayer(player, data.getCustomACFormat());
|
||||
} else
|
||||
{
|
||||
display.addPlayer(player, AdminChatFormat.DEFAULT);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
package fns.patchwork.display.adminchat;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
|
||||
public final class AdminChatFormat
|
||||
{
|
||||
public static final AdminChatFormat DEFAULT = ACFormatBuilder.format()
|
||||
.build();
|
||||
private final Component prefix;
|
||||
private final Component userName;
|
||||
private final Component rank;
|
||||
private final Component chatSplitter;
|
||||
private final Component fullFormat;
|
||||
|
||||
|
||||
AdminChatFormat(final ACFormatBuilder builder)
|
||||
{
|
||||
this.prefix = Component.text(builder.openBracket(), builder.bracketColor())
|
||||
.append(Component.text(builder.prefix(), builder.prefixColor()))
|
||||
.append(Component.text(builder.closeBracket(), builder.bracketColor()));
|
||||
this.userName = Component.text("%name%", builder.nameColor());
|
||||
this.rank = Component.text(builder.openBracket(), builder.bracketColor())
|
||||
.append(Component.text("%rank%", builder.rankColor()))
|
||||
.append(Component.text(builder.closeBracket(), builder.bracketColor()));
|
||||
|
||||
// Nice formatting :(
|
||||
if (builder.chatSplitter()
|
||||
.equals(":"))
|
||||
{
|
||||
this.chatSplitter = Component.text(":", NamedTextColor.WHITE);
|
||||
} else
|
||||
{
|
||||
this.chatSplitter = Component.space()
|
||||
.append(Component.text(builder.chatSplitter(), NamedTextColor.WHITE));
|
||||
}
|
||||
|
||||
// Formatting because []: is cleaner than [] :, but anything else such as [] >> or [] -> looks better with the space between.
|
||||
this.fullFormat = prefix.appendSpace()
|
||||
.append(userName)
|
||||
.appendSpace()
|
||||
.append(rank)
|
||||
.append(chatSplitter)
|
||||
.appendSpace();
|
||||
}
|
||||
|
||||
public static AdminChatFormat deserialize(final String serialized)
|
||||
{
|
||||
final Component dez = LegacyComponentSerializer.legacyAmpersand()
|
||||
.deserialize(serialized);
|
||||
final Component prefix = dez.children()
|
||||
.get(0);
|
||||
final Component userName = dez.children()
|
||||
.get(1);
|
||||
final Component rank = dez.children()
|
||||
.get(2);
|
||||
final Component chatSplitter = dez.children()
|
||||
.get(3);
|
||||
|
||||
return ACFormatBuilder.format()
|
||||
.prefix(((TextComponent) prefix).content())
|
||||
.prefixColor(prefix.color())
|
||||
.nameColor(userName.color())
|
||||
.rankColor(rank.color())
|
||||
.chatSplitter(((TextComponent) chatSplitter).content())
|
||||
.build();
|
||||
}
|
||||
|
||||
public Component getPrefix()
|
||||
{
|
||||
return prefix;
|
||||
}
|
||||
|
||||
public Component getUserName()
|
||||
{
|
||||
return userName;
|
||||
}
|
||||
|
||||
public Component getRank()
|
||||
{
|
||||
return rank;
|
||||
}
|
||||
|
||||
public Component getFullFormat()
|
||||
{
|
||||
return fullFormat;
|
||||
}
|
||||
|
||||
public Component format(final String name, final String rank)
|
||||
{
|
||||
return fullFormat.replaceText(b ->
|
||||
{
|
||||
b.matchLiteral("%name%")
|
||||
.replacement(name);
|
||||
b.matchLiteral("%rank%")
|
||||
.replacement(rank);
|
||||
});
|
||||
}
|
||||
|
||||
public String serialize()
|
||||
{
|
||||
return LegacyComponentSerializer.legacyAmpersand()
|
||||
.serialize(fullFormat);
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package fns.patchwork.economy;
|
||||
|
||||
/**
|
||||
* Represents an immutable transaction that has been fully handled by a {@link Transactor} instance
|
||||
*/
|
||||
public interface CompletedTransaction extends Transaction
|
||||
{
|
||||
/**
|
||||
* Gets the final result of the {@link Transaction}
|
||||
*
|
||||
* @return the result
|
||||
*/
|
||||
TransactionResult getResult();
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package fns.patchwork.economy;
|
||||
|
||||
/**
|
||||
* An entity that is able to transfer sums of currency between other {@link EconomicEntity}
|
||||
*/
|
||||
public interface EconomicEntity
|
||||
{
|
||||
/**
|
||||
* Gets the {@link EconomicEntityData} (which contains various common metadata about this {@link EconomicEntity})
|
||||
* associated with this class
|
||||
*
|
||||
* @return the {@link EconomicEntityData}
|
||||
*/
|
||||
EconomicEntityData getEconomicData();
|
||||
|
||||
/**
|
||||
* The name of the economic entity
|
||||
*
|
||||
* @return the economic entity's name
|
||||
*/
|
||||
String getName();
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package fns.patchwork.economy;
|
||||
|
||||
/**
|
||||
* Metadata associated with a {@link EconomicEntity}
|
||||
*/
|
||||
public interface EconomicEntityData
|
||||
{
|
||||
/***
|
||||
* @return the transaction freeze state
|
||||
*/
|
||||
boolean areTransactionsFrozen();
|
||||
|
||||
/***
|
||||
* @return the balance
|
||||
*/
|
||||
long getBalance();
|
||||
|
||||
/**
|
||||
* Sets the balance of the associated instance
|
||||
*
|
||||
* @param newBalance the new balance
|
||||
*/
|
||||
void setBalance(final long newBalance);
|
||||
|
||||
/**
|
||||
* Adds the provided amount to the associated instance's balance
|
||||
*
|
||||
* @param amount the amount to add
|
||||
* @return the new balance
|
||||
*/
|
||||
long addToBalance(final long amount);
|
||||
|
||||
/**
|
||||
* Subtracts the provided amount from the associated instance's balance
|
||||
*
|
||||
* @param amount the amount to subtract
|
||||
* @return the new balance
|
||||
*/
|
||||
long removeFromBalance(final long amount);
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package fns.patchwork.economy;
|
||||
|
||||
/**
|
||||
* A transaction that can be changed.
|
||||
* <p>
|
||||
* IMPORTANT NOTE: Please ensure that all modifications of {@link MutableTransaction} happen BEFORE it is passed to a
|
||||
* {@link Transactor} implementation
|
||||
*/
|
||||
public interface MutableTransaction extends Transaction
|
||||
{
|
||||
/**
|
||||
* Adds a number to the balance transferred in this transaction
|
||||
*
|
||||
* @param amount the amount to add
|
||||
* @return the new amount
|
||||
*/
|
||||
long addToBalance(final long amount);
|
||||
|
||||
/**
|
||||
* Subtracts a number from the balance transferred in this transaction
|
||||
*
|
||||
* @param amount the amount to remove
|
||||
* @return the new amount
|
||||
*/
|
||||
long removeFromBalance(final long amount);
|
||||
|
||||
/**
|
||||
* Sets the balance transferred in this transaction
|
||||
*
|
||||
* @param newBalance the new balance of the transaction
|
||||
*/
|
||||
void setBalance(final long newBalance);
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package fns.patchwork.economy;
|
||||
|
||||
/**
|
||||
* A class that denotes the transfer of currency between two EconomicEntity instances.
|
||||
*/
|
||||
public interface Transaction
|
||||
{
|
||||
/**
|
||||
* @return the initiating entity
|
||||
*/
|
||||
EconomicEntity getSource();
|
||||
|
||||
/**
|
||||
* @return the destination entity
|
||||
*/
|
||||
EconomicEntity getDestination();
|
||||
|
||||
/**
|
||||
* @return the balance transferred in this Transaction
|
||||
*/
|
||||
long getBalance();
|
||||
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package fns.patchwork.economy;
|
||||
|
||||
/**
|
||||
* A class that intercepts transactions after they are completed and logs them to a data point
|
||||
*/
|
||||
public interface TransactionLogger
|
||||
{
|
||||
/**
|
||||
* Logs a completed transaction
|
||||
*
|
||||
* @param completedTransaction the completed transaction to log
|
||||
*/
|
||||
void logTransaction(CompletedTransaction completedTransaction);
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package fns.patchwork.economy;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
/**
|
||||
* A class that represents the result of a transaction
|
||||
*/
|
||||
public interface TransactionResult
|
||||
{
|
||||
String getMessage();
|
||||
|
||||
boolean isSuccessful();
|
||||
|
||||
Component getComponent();
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package fns.patchwork.economy;
|
||||
|
||||
/**
|
||||
* A class that implements the completion of transactions.
|
||||
*/
|
||||
public interface Transactor
|
||||
{
|
||||
/**
|
||||
* Processes a transaction
|
||||
*
|
||||
* @param transaction the transaction to process
|
||||
* @return the completed transaction
|
||||
*/
|
||||
CompletedTransaction handleTransaction(MutableTransaction transaction);
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package fns.patchwork.event;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Callback<T extends FEvent>
|
||||
{
|
||||
void call(T event);
|
||||
}
|
69
Patchwork/src/main/java/fns/patchwork/event/EventBus.java
Normal file
69
Patchwork/src/main/java/fns/patchwork/event/EventBus.java
Normal file
@ -0,0 +1,69 @@
|
||||
package fns.patchwork.event;
|
||||
|
||||
import fns.patchwork.api.Context;
|
||||
import fns.patchwork.base.Patchwork;
|
||||
import fns.patchwork.service.Service;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class EventBus extends Service
|
||||
{
|
||||
private final Patchwork plugin;
|
||||
private final Set<FEvent> eventSet = new HashSet<>();
|
||||
private final SubscriptionBox<?> runningSubscriptions = new SubscriptionBox<>();
|
||||
|
||||
public EventBus(final Patchwork plugin)
|
||||
{
|
||||
super("event_bus");
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public void addEvent(final FEvent event)
|
||||
{
|
||||
eventSet.add(event);
|
||||
}
|
||||
|
||||
public <T extends FEvent> T getEvent(final Class<T> eventClass)
|
||||
{
|
||||
final FEvent e = eventSet.stream()
|
||||
.filter(event -> event.getEventClass()
|
||||
.equals(eventClass))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
return eventClass.cast(e);
|
||||
}
|
||||
|
||||
public <T extends FEvent> EventSubscription<T> subscribe(final Class<T> eventClass, final Callback<T> callback)
|
||||
{
|
||||
final Context<T> eventContext = () -> eventSet.stream()
|
||||
.filter(event -> event.getEventClass()
|
||||
.equals(eventClass))
|
||||
.findFirst()
|
||||
.map(eventClass::cast)
|
||||
.orElse(null);
|
||||
|
||||
if (eventContext.get() == null)
|
||||
{
|
||||
throw new IllegalArgumentException("Event class " + eventClass.getName() + " is not registered.");
|
||||
}
|
||||
|
||||
return new EventSubscription<>(eventContext.get(), callback);
|
||||
}
|
||||
|
||||
public void unsubscribe(final EventSubscription<?> subscription)
|
||||
{
|
||||
runningSubscriptions.removeSubscription(subscription);
|
||||
}
|
||||
|
||||
public Patchwork getCommonsBase()
|
||||
{
|
||||
return plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick()
|
||||
{
|
||||
runningSubscriptions.tick();
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package fns.patchwork.event;
|
||||
|
||||
public record EventSubscription<T extends FEvent>(T event, Callback<T> callback)
|
||||
{
|
||||
|
||||
public boolean cancel()
|
||||
{
|
||||
return event().cancel();
|
||||
}
|
||||
|
||||
public boolean isCancelled()
|
||||
{
|
||||
return event().isCancelled();
|
||||
}
|
||||
}
|
40
Patchwork/src/main/java/fns/patchwork/event/FEvent.java
Normal file
40
Patchwork/src/main/java/fns/patchwork/event/FEvent.java
Normal file
@ -0,0 +1,40 @@
|
||||
package fns.patchwork.event;
|
||||
|
||||
public abstract class FEvent
|
||||
{
|
||||
private boolean isCancelled;
|
||||
private boolean triggered;
|
||||
|
||||
protected FEvent()
|
||||
{
|
||||
this.isCancelled = false;
|
||||
}
|
||||
|
||||
public void ping()
|
||||
{
|
||||
this.triggered = true;
|
||||
}
|
||||
|
||||
public void reset()
|
||||
{
|
||||
this.triggered = false;
|
||||
}
|
||||
|
||||
boolean shouldCall()
|
||||
{
|
||||
return triggered;
|
||||
}
|
||||
|
||||
public boolean cancel()
|
||||
{
|
||||
this.isCancelled = true;
|
||||
return isCancelled();
|
||||
}
|
||||
|
||||
public boolean isCancelled()
|
||||
{
|
||||
return isCancelled;
|
||||
}
|
||||
|
||||
public abstract Class<? extends FEvent> getEventClass();
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package fns.patchwork.event;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
class SubscriptionBox<T extends FEvent>
|
||||
{
|
||||
private final List<EventSubscription<T>> subscriptions;
|
||||
|
||||
public SubscriptionBox()
|
||||
{
|
||||
this.subscriptions = new ArrayList<>();
|
||||
}
|
||||
|
||||
public void addSubscription(final EventSubscription<T> subscription)
|
||||
{
|
||||
subscriptions.add(subscription);
|
||||
}
|
||||
|
||||
public void removeSubscription(final EventSubscription<?> subscription)
|
||||
{
|
||||
subscriptions.remove(subscription);
|
||||
}
|
||||
|
||||
public void tick()
|
||||
{
|
||||
subscriptions.forEach(s ->
|
||||
{
|
||||
if (!s.event()
|
||||
.shouldCall()) return;
|
||||
|
||||
s.callback()
|
||||
.call(s.event());
|
||||
s.event()
|
||||
.reset();
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
package fns.patchwork.logging;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public final class BlockInteraction implements Interaction<BlockData>
|
||||
{
|
||||
private final Location location;
|
||||
private final UUID whoClicked;
|
||||
private final Instant when;
|
||||
private final BlockData originalState;
|
||||
private final BlockData newState;
|
||||
|
||||
public BlockInteraction(final Player player, final BlockData originalState, final BlockData newState)
|
||||
{
|
||||
this.location = player.getLocation();
|
||||
this.whoClicked = player.getUniqueId();
|
||||
this.when = Instant.now();
|
||||
this.originalState = originalState;
|
||||
this.newState = newState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull UUID getWhoClicked()
|
||||
{
|
||||
return whoClicked;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull BlockData getOriginalState()
|
||||
{
|
||||
return originalState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull BlockData getNewState()
|
||||
{
|
||||
return newState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Instant getWhen()
|
||||
{
|
||||
return when;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Location getLocation()
|
||||
{
|
||||
return location;
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
package fns.patchwork.logging;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.block.Container;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public final class ContainerInteraction implements Interaction<List<ItemStack>>
|
||||
{
|
||||
private final UUID whoClicked;
|
||||
private final List<ItemStack> originalState;
|
||||
private final List<ItemStack> newState;
|
||||
private final Instant when;
|
||||
private final Location location;
|
||||
|
||||
public ContainerInteraction(final Player player, final Container originalState, final Container newState)
|
||||
{
|
||||
this.whoClicked = player.getUniqueId();
|
||||
this.originalState = Collections.unmodifiableList(Arrays.asList(originalState.getInventory()
|
||||
.getContents()));
|
||||
this.newState = Collections.unmodifiableList(Arrays.asList(newState.getInventory()
|
||||
.getContents()));
|
||||
this.location = originalState.getLocation();
|
||||
this.when = Instant.now();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull UUID getWhoClicked()
|
||||
{
|
||||
return whoClicked;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull List<ItemStack> getOriginalState()
|
||||
{
|
||||
return originalState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull List<ItemStack> getNewState()
|
||||
{
|
||||
return newState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Instant getWhen()
|
||||
{
|
||||
return when;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Location getLocation()
|
||||
{
|
||||
return location;
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package fns.patchwork.logging;
|
||||
|
||||
import com.google.errorprone.annotations.Immutable;
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
import org.bukkit.Location;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@Immutable
|
||||
public interface Interaction<T>
|
||||
{
|
||||
@NotNull
|
||||
static String format(@NotNull final Interaction<?> interaction)
|
||||
{
|
||||
return new InteractionFormatter().formatInteraction(interaction);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
UUID getWhoClicked();
|
||||
|
||||
@NotNull
|
||||
T getOriginalState();
|
||||
|
||||
@NotNull
|
||||
T getNewState();
|
||||
|
||||
@NotNull
|
||||
Instant getWhen();
|
||||
|
||||
@NotNull
|
||||
Location getLocation();
|
||||
}
|
@ -0,0 +1,137 @@
|
||||
package fns.patchwork.logging;
|
||||
|
||||
import java.time.Instant;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
public final class InteractionFormatter
|
||||
{
|
||||
public String formatInteraction(final Interaction<?> interaction)
|
||||
{
|
||||
final String location = formatLocation(interaction.getLocation());
|
||||
final String world = formatWorld(interaction.getLocation()
|
||||
.getWorld());
|
||||
final String player = interaction.getWhoClicked()
|
||||
.toString();
|
||||
final String block = formatBlock(interaction.getLocation()
|
||||
.getBlock());
|
||||
final String when = formatTime(interaction.getWhen());
|
||||
|
||||
if (interaction instanceof ContainerInteraction container)
|
||||
{
|
||||
final StringBuilder stringBuilder = new StringBuilder();
|
||||
stringBuilder.append("Container ")
|
||||
.append(block)
|
||||
.append(" at location ")
|
||||
.append(location)
|
||||
.append(" in world ")
|
||||
.append(world)
|
||||
.append(" was opened by ")
|
||||
.append(player)
|
||||
.append(" at ")
|
||||
.append(when)
|
||||
.append("\nHere is a list of items changed:\n");
|
||||
|
||||
container.getOriginalState()
|
||||
.stream()
|
||||
.filter(item ->
|
||||
{
|
||||
final ItemStack newItem = container.getNewState()
|
||||
.stream()
|
||||
.filter(item2 -> item2.isSimilar(item))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
return newItem == null || newItem.getAmount() != item.getAmount();
|
||||
})
|
||||
.forEach(item ->
|
||||
{
|
||||
final ItemStack newItem = container.getNewState()
|
||||
.stream()
|
||||
.filter(item2 -> item2.isSimilar(item))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
stringBuilder.append("Item ")
|
||||
.append(formatItemStack(item))
|
||||
.append(" was changed to ")
|
||||
.append(formatItemStack(newItem))
|
||||
.append("\n");
|
||||
});
|
||||
|
||||
stringBuilder.append(".");
|
||||
|
||||
return stringBuilder.toString();
|
||||
} else if (interaction instanceof BlockInteraction blockData)
|
||||
{
|
||||
final StringBuilder stringBuilder = new StringBuilder();
|
||||
stringBuilder.append("Block ")
|
||||
.append(block)
|
||||
.append(" at location ")
|
||||
.append(location)
|
||||
.append(" in world ")
|
||||
.append(world)
|
||||
.append(" was changed by ")
|
||||
.append(player)
|
||||
.append(" at ")
|
||||
.append(when)
|
||||
.append("\nBlock was changed from ")
|
||||
.append(blockData.getOriginalState()
|
||||
.getAsString())
|
||||
.append(" to ")
|
||||
.append(blockData.getNewState()
|
||||
.getAsString())
|
||||
.append(".");
|
||||
|
||||
return stringBuilder.toString();
|
||||
} else
|
||||
{
|
||||
throw new IllegalArgumentException("Unknown interaction type: " + interaction.getClass()
|
||||
.getName());
|
||||
}
|
||||
}
|
||||
|
||||
// Format: <x>,<y>,<z>
|
||||
public String formatLocation(final Location location)
|
||||
{
|
||||
return String.format("%s,%s,%s", location.getBlockX(), location.getBlockY(), location.getBlockZ());
|
||||
}
|
||||
|
||||
// Format: <world>
|
||||
public String formatWorld(final World world)
|
||||
{
|
||||
return world.getName();
|
||||
}
|
||||
|
||||
// Format: <material>
|
||||
public String formatBlock(final Block block)
|
||||
{
|
||||
return block.getType()
|
||||
.toString()
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
public String formatTime(final Instant instant)
|
||||
{
|
||||
final String trimmed = instant.toString()
|
||||
.replaceAll("[TZ]", " ");
|
||||
final int dotIndex = trimmed.indexOf('.');
|
||||
|
||||
return (dotIndex != -1)
|
||||
? trimmed.substring(0, dotIndex)
|
||||
: trimmed;
|
||||
}
|
||||
|
||||
// Format: <item>,<amount>
|
||||
public String formatItemStack(final ItemStack stack)
|
||||
{
|
||||
if (stack == null)
|
||||
{
|
||||
return String.format("%s,%s", "empty", "0");
|
||||
}
|
||||
|
||||
return String.format("%s,%s", stack.getType()
|
||||
.toString()
|
||||
.toLowerCase(), stack.getAmount());
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package fns.patchwork.particle;
|
||||
|
||||
import org.bukkit.Particle;
|
||||
import org.bukkit.World;
|
||||
|
||||
/**
|
||||
* A utility class for the 24 different note colors available in Minecraft. Each note is represented as a double value
|
||||
* between 0 and 1. Furthermore, each note is a multiple of 1/24 within that range of 0 to 1.
|
||||
* <p>
|
||||
* For example, the note G is represented as 1/24, or 0.042. The note C is represented as 6/24, or 0.25.
|
||||
* <p>
|
||||
* When spawning particles, the count must be set to 0 and the extra value set between 0 and 1. The extra value is the
|
||||
* size of the note particle. To add a color, use one of the provided methods in this class for the xOffset value in
|
||||
* {@link World#spawnParticle(Particle, double, double, double, int, double, double, double, double)}. The xOffset value
|
||||
* is the note color, with the yOffset and zOffset values set to 0.
|
||||
*/
|
||||
public final class NoteColorUtil
|
||||
{
|
||||
public static final double CYAN_NOTE_F_SHARP_LOW = 0;
|
||||
public static final double CYAN_NOTE_G = 0.042;
|
||||
public static final double GRAY_NOTE_G_SHARP = 0.083;
|
||||
public static final double GRAY_NOTE_A = 0.125;
|
||||
public static final double GRAY_NOTE_A_SHARP = 0.167;
|
||||
public static final double MAGENTA_NOTE_B = 0.208;
|
||||
public static final double RED_NOTE_C = 0.25;
|
||||
public static final double YELLOW_NOTE_C_SHARP = 0.292;
|
||||
public static final double YELLOW_NOTE_D = 0.333;
|
||||
public static final double YELLOW_NOTE_D_SHARP_LOW = 0.375;
|
||||
public static final double GRAY_NOTE_E = 0.417;
|
||||
public static final double GRAY_NOTE_F = 0.458;
|
||||
public static final double GRAY_NOTE_F_SHARP = 0.5;
|
||||
public static final double LIGHT_BLUE_NOTE_G = 0.542;
|
||||
public static final double BLUE_NOTE_G_SHARP = 0.583;
|
||||
public static final double PURPLE_NOTE_A = 0.625;
|
||||
public static final double PURPLE_NOTE_A_SHARP = 0.667;
|
||||
public static final double PURPLE_NOTE_B = 0.708;
|
||||
public static final double GRAY_NOTE_C = 0.75;
|
||||
public static final double GRAY_NOTE_C_SHARP = 0.792;
|
||||
public static final double GRAY_NOTE_D = 0.833;
|
||||
public static final double YELLOW_NOTE_D_SHARP_HIGH = 0.875;
|
||||
public static final double YELLOW_NOTE_E = 0.917;
|
||||
public static final double YELLOW_NOTE_F = 0.958;
|
||||
public static final double CYAN_NOTE_F_SHARP_HIGH = 1;
|
||||
public static final double BLACK_NOTE_NA = 32768;
|
||||
|
||||
private NoteColorUtil()
|
||||
{
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
123
Patchwork/src/main/java/fns/patchwork/particle/Trail.java
Normal file
123
Patchwork/src/main/java/fns/patchwork/particle/Trail.java
Normal file
@ -0,0 +1,123 @@
|
||||
package fns.patchwork.particle;
|
||||
|
||||
import fns.patchwork.api.Interpolator;
|
||||
import fns.patchwork.utils.InterpolationUtils;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import org.bukkit.Color;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.Particle;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Represents a Trail instance for a specific player.
|
||||
*/
|
||||
public interface Trail
|
||||
{
|
||||
/**
|
||||
* Returns the UUID of the player associated with the trail. This is for usage with our persistant storage container
|
||||
* so that we can safely send and retrieve the trails without having to directly reference a player object.
|
||||
* <br>
|
||||
* TL;DR Memory optimization!
|
||||
*
|
||||
* @return The UUID of the player associated with this trail.
|
||||
*/
|
||||
@NotNull
|
||||
UUID getAssociatedPlayerUUID();
|
||||
|
||||
/**
|
||||
* Returns the player associated with this trail. Trails are user specific, and should be persistent across all
|
||||
* usages. This is also used when displaying the particles, as they will be relative to the player's back, which is
|
||||
* an inverse offset of the player's eye location. We use OfflinePlayer as we can make a simple check and cast to
|
||||
* determine if the player is online when spawning trails.
|
||||
*
|
||||
* @return The player associated with this Trail.
|
||||
*/
|
||||
@NotNull
|
||||
OfflinePlayer getAssociatedPlayer();
|
||||
|
||||
/**
|
||||
* Gets the Trail Type of this trail. This is used to determine what type of trail this is, and what
|
||||
* {@link Particle} it should use.
|
||||
*
|
||||
* @return The Trail Type of this trail.
|
||||
* @see TrailType
|
||||
*/
|
||||
@NotNull
|
||||
TrailType getTrailType();
|
||||
|
||||
/**
|
||||
* This method is nullable because if the value of {@link #isGradient()} is true, then {@link #getColors()} should
|
||||
* be used instead, as that will contain the color data for our trail.
|
||||
* <br>
|
||||
* However, this method will also be null if the particle type is not colorable.
|
||||
*
|
||||
* @return The color of the trail, or null if the trail is a gradient or non-colorable.
|
||||
* @see Particle
|
||||
* @see #getColors()
|
||||
*/
|
||||
@Nullable
|
||||
Color getColor();
|
||||
|
||||
/**
|
||||
* Sets the static color of the trail. If you are trying to use a gradient, use {@link #setColors(Set)} instead.
|
||||
* <br>
|
||||
*
|
||||
* @param color The color to set the trail to.
|
||||
*/
|
||||
void setColor(@NotNull Color color);
|
||||
|
||||
/**
|
||||
* This method is nullable because if the value of {@link #isGradient()} is false, then {@link #getColor()} should
|
||||
* be used instead, as our trail is a single static color.
|
||||
* <br>
|
||||
* However, this method will also be null if the particle type is not colorable.
|
||||
*
|
||||
* @return The colors of the trail, or null if the trail is not a gradient or non-colorable.
|
||||
* @see #getColor()
|
||||
* @see Particle
|
||||
* @see InterpolationUtils
|
||||
* @see Interpolator
|
||||
*/
|
||||
@Nullable
|
||||
Set<Color> getColors();
|
||||
|
||||
/**
|
||||
* Sets the colors of the trail. If you are trying to use a static color, use {@link #setColor(Color)} instead.
|
||||
* <br>
|
||||
* This should be used for trails that iterate over a set of colors, such as a rainbow trail.
|
||||
*
|
||||
* @param colors The colors to set the trail to. It is recommended to use {@link InterpolationUtils} to generate
|
||||
* interpolated gradients for this.
|
||||
*/
|
||||
void setColors(@NotNull Set<Color> colors);
|
||||
|
||||
/**
|
||||
* Validates whether this Trail is a gradient or a static trail.
|
||||
* <br>
|
||||
* This is entirely based on whether {@link #getColors()} returns null or not.
|
||||
*
|
||||
* @return True if {@link #getColors()} is not null, false otherwise.
|
||||
*/
|
||||
boolean isGradient();
|
||||
|
||||
/**
|
||||
* Gets whether the trail is active.
|
||||
*
|
||||
* @return True if the trail is active, false if it is not.
|
||||
*/
|
||||
boolean isActive();
|
||||
|
||||
/**
|
||||
* Turn the trail on or off.
|
||||
*
|
||||
* @param active True if the trail should be active, false if it should not.
|
||||
*/
|
||||
void setActive(final boolean active);
|
||||
|
||||
/**
|
||||
* Spawns a particle (if gradient, the next particle) on the supplied location object.
|
||||
*/
|
||||
void spawnParticle();
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package fns.patchwork.particle;
|
||||
|
||||
import org.bukkit.Particle;
|
||||
|
||||
public enum TrailType
|
||||
{
|
||||
/**
|
||||
* Default trail type. Uses {@link Particle#REDSTONE}. This trail is colorable. Use {@link Particle.DustOptions} to
|
||||
* set the particle properties.
|
||||
*/
|
||||
DEFAULT(Particle.REDSTONE),
|
||||
/**
|
||||
* A trail that uses {@link Particle#HEART}. This is not modifiable and will always have the same size shape and
|
||||
* color.
|
||||
*/
|
||||
HEART(Particle.HEART),
|
||||
/**
|
||||
* A trail that uses {@link Particle#FLAME}. This is not modifiable and will always have the same size shape and
|
||||
* color.
|
||||
*/
|
||||
FLAME(Particle.FLAME),
|
||||
/**
|
||||
* A trail that uses {@link Particle#REDSTONE}. This particle however is rainbow-colored by default and cannot have
|
||||
* additional options set.
|
||||
*/
|
||||
RAINBOW(Particle.REDSTONE),
|
||||
/**
|
||||
* A trail that uses {@link Particle#NOTE}. This is colorable, however you are limited to the 24 different note
|
||||
* colors available in Minecraft.
|
||||
*/
|
||||
MUSIC(Particle.NOTE),
|
||||
SNOW(Particle.SNOWBALL),
|
||||
SPELL(Particle.SPELL_MOB),
|
||||
SPELL_AMBIENT(Particle.SPELL_MOB_AMBIENT),
|
||||
PORTAL(Particle.PORTAL),
|
||||
ENCHANTMENT(Particle.ENCHANTMENT_TABLE),
|
||||
STROBE(Particle.DUST_COLOR_TRANSITION),
|
||||
VIBRATION(Particle.VIBRATION),
|
||||
SPARK(Particle.ELECTRIC_SPARK);
|
||||
|
||||
final Particle type;
|
||||
|
||||
TrailType(final Particle type)
|
||||
{
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public Particle getType()
|
||||
{
|
||||
return type;
|
||||
}
|
||||
}
|
@ -0,0 +1,205 @@
|
||||
package fns.patchwork.provider;
|
||||
|
||||
import fns.patchwork.command.BukkitDelegate;
|
||||
import fns.patchwork.command.annotation.Subcommand;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* This class is used to provide context to subcommand methods. This class is used by the BukkitDelegate to parse
|
||||
* arguments for subcommands. The following types are supported:
|
||||
* <ul>
|
||||
* <li>Boolean</li>
|
||||
* <li>Double</li>
|
||||
* <li>Integer</li>
|
||||
* <li>Long</li>
|
||||
* <li>Float</li>
|
||||
* <li>Material</li>
|
||||
* <li>Player</li>
|
||||
* <li>World</li>
|
||||
* <li>Location</li>
|
||||
* <li>CommandSender</li>
|
||||
* <li>Component</li>
|
||||
* </ul>
|
||||
* All of these types can be parsed from a String input. If the String cannot be parsed into any of
|
||||
* these types, then null will be returned.
|
||||
*
|
||||
* @see #fromString(String, Class)
|
||||
*/
|
||||
public class ContextProvider
|
||||
{
|
||||
public <T> T fromString(final String string, final Class<T> clazz)
|
||||
{
|
||||
return Stream.of(toBoolean(string, clazz),
|
||||
toDouble(string, clazz),
|
||||
toInt(string, clazz),
|
||||
toLong(string, clazz),
|
||||
toFloat(string, clazz),
|
||||
toMaterial(string, clazz),
|
||||
toPlayer(string, clazz),
|
||||
toWorld(string, clazz),
|
||||
toLocation(string, clazz),
|
||||
toComponent(string, clazz))
|
||||
.filter(Objects::nonNull)
|
||||
.findFirst()
|
||||
.map(clazz::cast)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private @Nullable Boolean toBoolean(final String string, final Class<?> clazz)
|
||||
{
|
||||
if (clazz != Boolean.class)
|
||||
return null;
|
||||
|
||||
// Previously we used Boolean#parseBoolean, but that will always return a value and does not throw
|
||||
// an exception. This means that if the string is not "true" or "false", it will return false.
|
||||
if (string.equalsIgnoreCase("true"))
|
||||
return true;
|
||||
if (string.equalsIgnoreCase("false"))
|
||||
return false;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private @Nullable Double toDouble(final String string, final Class<?> clazz)
|
||||
{
|
||||
if (clazz != Double.class)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
return Double.parseDouble(string);
|
||||
}
|
||||
catch (NumberFormatException ignored)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable Integer toInt(final String string, final Class<?> clazz)
|
||||
{
|
||||
if (clazz != Integer.class)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
return Integer.parseInt(string);
|
||||
}
|
||||
catch (NumberFormatException ignored)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable Long toLong(final String string, final Class<?> clazz)
|
||||
{
|
||||
if (clazz != Long.class)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
return Long.parseLong(string);
|
||||
}
|
||||
catch (NumberFormatException ignored)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable Float toFloat(final String string, final Class<?> clazz)
|
||||
{
|
||||
if (clazz != Float.class)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
return Float.parseFloat(string);
|
||||
}
|
||||
catch (NumberFormatException ignored)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable Material toMaterial(final String string, final Class<?> clazz)
|
||||
{
|
||||
if (clazz != Material.class)
|
||||
return null;
|
||||
return Material.matchMaterial(string);
|
||||
}
|
||||
|
||||
private @Nullable Player toPlayer(final String string, final Class<?> clazz)
|
||||
{
|
||||
if (clazz != Player.class)
|
||||
return null;
|
||||
return Bukkit.getPlayer(string);
|
||||
}
|
||||
|
||||
private @Nullable World toWorld(final String string, final Class<?> clazz)
|
||||
{
|
||||
if (clazz != World.class)
|
||||
return null;
|
||||
return Bukkit.getWorld(string);
|
||||
}
|
||||
|
||||
/**
|
||||
* When using this method, the next four arguments must be world, x, y, z.
|
||||
* The world must be a valid world name, and x, y, and z must be valid doubles.
|
||||
* If any of these are invalid, this will return null.
|
||||
*
|
||||
* @param string The string to parse
|
||||
* @return A location object if xyz is valid
|
||||
* @see BukkitDelegate#processSubCommands(String[], CommandSender, ContextProvider, Subcommand)
|
||||
*/
|
||||
private @Nullable Location toLocation(final String string, final Class<?> clazz)
|
||||
{
|
||||
if (clazz != Location.class)
|
||||
return null;
|
||||
|
||||
final String[] split = string.split(" ");
|
||||
|
||||
if (split.length != 4 || toWorld(split[0], World.class) == null)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
final double x = Double.parseDouble(split[1]);
|
||||
final double y = Double.parseDouble(split[2]);
|
||||
final double z = Double.parseDouble(split[3]);
|
||||
|
||||
return new Location(toWorld(split[0], World.class), x, y, z);
|
||||
}
|
||||
catch (NumberFormatException ex)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable Component toComponent(final String string, final Class<?> clazz)
|
||||
{
|
||||
if (clazz != Component.class)
|
||||
return null;
|
||||
return Component.text(string);
|
||||
}
|
||||
|
||||
public @NotNull <T> List<@Nullable T> getList(final List<String> resolvable, final Class<T> clazz)
|
||||
{
|
||||
final List<T> resolved = new ArrayList<>();
|
||||
for (final String entry : resolvable)
|
||||
{
|
||||
resolved.add(this.fromString(entry, clazz));
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package fns.patchwork.provider;
|
||||
|
||||
import fns.patchwork.event.FEvent;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface EventProvider<T extends FEvent>
|
||||
{
|
||||
T getEvent();
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package fns.patchwork.provider;
|
||||
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface ModuleProvider<T extends JavaPlugin>
|
||||
{
|
||||
T getModule();
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package fns.patchwork.provider;
|
||||
|
||||
import fns.patchwork.service.Service;
|
||||
|
||||
public interface ServiceProvider<T extends Service>
|
||||
{
|
||||
T getService();
|
||||
}
|
41
Patchwork/src/main/java/fns/patchwork/security/Group.java
Normal file
41
Patchwork/src/main/java/fns/patchwork/security/Group.java
Normal file
@ -0,0 +1,41 @@
|
||||
package fns.patchwork.security;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
/**
|
||||
* Represents a permissible group which holds a set of permissions that can then be applied to a User / Player.
|
||||
*/
|
||||
public interface Group extends PermissionHolder
|
||||
{
|
||||
/**
|
||||
* @return The name of the group.
|
||||
*/
|
||||
Component getName();
|
||||
|
||||
/**
|
||||
* @return The prefix of the group.
|
||||
*/
|
||||
Component getPrefix();
|
||||
|
||||
/**
|
||||
* @return The abbreviation of the group.
|
||||
*/
|
||||
Component getAbbreviation();
|
||||
|
||||
/**
|
||||
* @return The weight of the group.
|
||||
*/
|
||||
int getWeight();
|
||||
|
||||
/**
|
||||
* If more than one group is marked as default, the first retrieved default group will be used.
|
||||
*
|
||||
* @return Whether this is the default group.
|
||||
*/
|
||||
boolean isDefault();
|
||||
|
||||
/**
|
||||
* @return Whether the group is hidden.
|
||||
*/
|
||||
boolean isHidden();
|
||||
}
|
55
Patchwork/src/main/java/fns/patchwork/security/Groups.java
Normal file
55
Patchwork/src/main/java/fns/patchwork/security/Groups.java
Normal file
@ -0,0 +1,55 @@
|
||||
package fns.patchwork.security;
|
||||
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public enum Groups
|
||||
{
|
||||
NON_OP("patchwork.group.non-op", "Non-OP"),
|
||||
OP("patchwork.group.op", "OP"),
|
||||
SUPER_ADMIN("patchwork.group.super", "Super Admin"),
|
||||
SENIOR_ADMIN("patchwork.group.senior", "Senior Admin"),
|
||||
DEVELOPER("patchwork.group.dev", "Developer"),
|
||||
EXECUTIVE("patchwork.group.exec", "Executive"),
|
||||
OWNER("patchwork.group.owner", "Owner");
|
||||
|
||||
private final String permission;
|
||||
private final String name;
|
||||
|
||||
Groups(final String permission, final String name)
|
||||
{
|
||||
this.permission = permission;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName()
|
||||
{
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public String getPermission()
|
||||
{
|
||||
return this.permission;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return this.permission;
|
||||
}
|
||||
|
||||
public static String fromPlayer(final Player player) {
|
||||
for (final Groups group : values()) {
|
||||
if (player.hasPermission(group.getPermission())) {
|
||||
return group.getName();
|
||||
}
|
||||
}
|
||||
return Groups.NON_OP.getName();
|
||||
}
|
||||
|
||||
public static String fromSender(final CommandSender sender) {
|
||||
if (!(sender instanceof Player)) return "CONSOLE";
|
||||
|
||||
return fromPlayer((Player) sender);
|
||||
}
|
||||
}
|
24
Patchwork/src/main/java/fns/patchwork/security/Node.java
Normal file
24
Patchwork/src/main/java/fns/patchwork/security/Node.java
Normal file
@ -0,0 +1,24 @@
|
||||
package fns.patchwork.security;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import org.bukkit.permissions.Permission;
|
||||
|
||||
@Immutable
|
||||
public interface Node
|
||||
{
|
||||
String key();
|
||||
|
||||
Permission bukkit();
|
||||
|
||||
NodeType type();
|
||||
|
||||
boolean compare(Node node);
|
||||
|
||||
long expiry();
|
||||
|
||||
boolean isExpired();
|
||||
|
||||
boolean isTemporary();
|
||||
|
||||
boolean wildcard();
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package fns.patchwork.security;
|
||||
|
||||
public interface NodeBuilder
|
||||
{
|
||||
NodeBuilder key(String key);
|
||||
|
||||
NodeBuilder expiry(long expiry);
|
||||
|
||||
NodeBuilder type(NodeType type);
|
||||
|
||||
NodeBuilder wildcard(boolean wildcard);
|
||||
|
||||
Node build();
|
||||
}
|
10
Patchwork/src/main/java/fns/patchwork/security/NodeType.java
Normal file
10
Patchwork/src/main/java/fns/patchwork/security/NodeType.java
Normal file
@ -0,0 +1,10 @@
|
||||
package fns.patchwork.security;
|
||||
|
||||
public enum NodeType
|
||||
{
|
||||
INHERITANCE,
|
||||
PREFIX,
|
||||
SUFFIX,
|
||||
PERMISSION,
|
||||
WEIGHT
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package fns.patchwork.security;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import org.bukkit.permissions.Permissible;
|
||||
|
||||
public interface PermissionHolder extends Permissible
|
||||
{
|
||||
UUID getUniqueId();
|
||||
|
||||
Set<Node> permissions();
|
||||
|
||||
boolean addPermission(Node node);
|
||||
|
||||
boolean removePermission(Node node);
|
||||
}
|
@ -0,0 +1,141 @@
|
||||
package fns.patchwork.service;
|
||||
|
||||
import fns.patchwork.base.Patchwork;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
/**
|
||||
* This is a holder class for {@link Executor} objects that are used to delegate runnable tasks to the Bukkit Scheduler.
|
||||
* This class is here for both convenience purposes, and also for the sake of providing easy access to executors for
|
||||
* {@link CompletableFuture} invocations.
|
||||
*/
|
||||
public class FreedomExecutor
|
||||
{
|
||||
/**
|
||||
* An executor which runs tasks synchronously.
|
||||
*/
|
||||
private final Executor syncExecutor;
|
||||
/**
|
||||
* An executor which runs tasks asynchronously.
|
||||
*/
|
||||
private final Executor asyncExecutor;
|
||||
|
||||
/**
|
||||
* Creates a new {@link FreedomExecutor} instance.
|
||||
*/
|
||||
public FreedomExecutor()
|
||||
{
|
||||
syncExecutor = r -> Bukkit.getScheduler()
|
||||
.runTask(Patchwork.getInstance(), r);
|
||||
asyncExecutor = r -> Bukkit.getScheduler()
|
||||
.runTaskAsynchronously(Patchwork.getInstance(), r);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Executor} that is capable of executing a runnable one singular time, synchronously.
|
||||
*
|
||||
* @param plugin The plugin to run the task for.
|
||||
* @return A new {@link Executor} instance.
|
||||
*/
|
||||
public Executor singleExecutor(final JavaPlugin plugin)
|
||||
{
|
||||
return r -> Bukkit.getScheduler()
|
||||
.runTask(plugin, r);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Executor} that is capable of executing a runnable one singular time, synchronously. This
|
||||
* Executor will wait for the supplied delay before executing the runnable.
|
||||
*
|
||||
* @param plugin The plugin to run the task for.
|
||||
* @param delay The delay to wait before executing the runnable.
|
||||
* @return A new {@link Executor} instance.
|
||||
*/
|
||||
public Executor delayedExecutor(final JavaPlugin plugin, final long delay)
|
||||
{
|
||||
return r -> Bukkit.getScheduler()
|
||||
.runTaskLater(plugin, r, delay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Executor} tthat is capable of executing a runnable on a periodic basis, synchronously. This
|
||||
* executor can also be supplied a delay to indicate it should wait the specified amount of time before executing
|
||||
* the runnable for the first time.
|
||||
*
|
||||
* @param plugin The plugin to run the task for.
|
||||
* @param initialDelay The delay to wait before executing the runnable for the first time.
|
||||
* @param period The period to wait between each execution of the runnable.
|
||||
* @return A new {@link Executor} instance.
|
||||
*/
|
||||
public Executor periodicExecutor(final JavaPlugin plugin, final long initialDelay, final long period)
|
||||
{
|
||||
return r -> Bukkit.getScheduler()
|
||||
.runTaskTimer(plugin, r, initialDelay, period);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Executor} that is capable of executing a runnable one singular time, asynchronously.
|
||||
*
|
||||
* @param plugin The plugin to run the task for.
|
||||
* @return A new {@link Executor} instance.
|
||||
*/
|
||||
public Executor asynchronousSingleExecutor(final JavaPlugin plugin)
|
||||
{
|
||||
return r -> Bukkit.getScheduler()
|
||||
.runTaskAsynchronously(plugin, r);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Executor} that is capable of executing a runnable one singular time, asynchronously. This
|
||||
* Executor will wait for the supplied delay before executing the runnable.
|
||||
*
|
||||
* @param plugin The plugin to run the task for.
|
||||
* @param delay The delay to wait before executing the runnable.
|
||||
* @return A new {@link Executor} instance.
|
||||
*/
|
||||
public Executor asynchronousDelayedExecutor(final JavaPlugin plugin, final long delay)
|
||||
{
|
||||
return r -> Bukkit.getScheduler()
|
||||
.runTaskLaterAsynchronously(plugin, r, delay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Executor} tthat is capable of executing a runnable on a periodic basis, asynchronously. This
|
||||
* executor can also be supplied a delay to indicate it should wait the specified amount of time before executing
|
||||
* the runnable for the first time.
|
||||
*
|
||||
* @param plugin The plugin to run the task for.
|
||||
* @param delay The delay to wait before executing the runnable for the first time.
|
||||
* @param period The period to wait between each execution of the runnable.
|
||||
* @return A new {@link Executor} instance.
|
||||
*/
|
||||
public Executor asynchronousPeriodicExecutor(final JavaPlugin plugin, final long delay, final long period)
|
||||
{
|
||||
return r -> Bukkit.getScheduler()
|
||||
.runTaskTimerAsynchronously(plugin, r, delay, period);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the synchronous executor instance. This is a convenience for {@link CompletableFuture} invocations, when
|
||||
* defining a custom executor for the {@link CompletableFuture}.
|
||||
*
|
||||
* @return The synchronous executor instance.
|
||||
*/
|
||||
public Executor getSync()
|
||||
{
|
||||
return syncExecutor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the asynchronous executor instance. This is a convenience for {@link CompletableFuture} invocations, when
|
||||
* defining a custom executor for the {@link CompletableFuture}.
|
||||
*
|
||||
* @return The asynchronous executor instance.
|
||||
*/
|
||||
public Executor getAsync()
|
||||
{
|
||||
return asyncExecutor;
|
||||
}
|
||||
}
|
47
Patchwork/src/main/java/fns/patchwork/service/Service.java
Normal file
47
Patchwork/src/main/java/fns/patchwork/service/Service.java
Normal file
@ -0,0 +1,47 @@
|
||||
package fns.patchwork.service;
|
||||
|
||||
/**
|
||||
* Represents a ticking service. Services may be asynchronous or synchronous, however there are some restrictions:
|
||||
* <ul>
|
||||
* <li>Sync services may not have a complexity greater than 5.</li>
|
||||
* <li>Async services may not interact with the Bukkit API in any form.</li>
|
||||
* </ul>
|
||||
*/
|
||||
public abstract class Service
|
||||
{
|
||||
/**
|
||||
* The name of the service.
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* Creates a new service with the given name.
|
||||
*
|
||||
* @param name The name of the service.
|
||||
*/
|
||||
protected Service(final String name)
|
||||
{
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called every single tick, or at least, every tick interval described by the holding
|
||||
* {@link ServiceSubscription}. Since this runs every single tick, the body of this method should not have a
|
||||
* complexity higher than 5. This includes Cyclomatic, Cognitive, and NPath complexities. If the service is
|
||||
* asynchronous, there is a bit more flexibility with the complexity rating, extending to no more than 10. However,
|
||||
* it's generally good practice to keep the complexity of ticking services as low as possible to avoid extensive
|
||||
* resource consumption.
|
||||
*
|
||||
* @see ServiceSubscription
|
||||
* @see SubscriptionProvider
|
||||
*/
|
||||
public abstract void tick();
|
||||
|
||||
/**
|
||||
* @return The name of the service.
|
||||
*/
|
||||
public String getName()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
}
|
@ -0,0 +1,188 @@
|
||||
package fns.patchwork.service;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Represents a subscription to a {@link Service}.
|
||||
* <p>
|
||||
* Subscriptions contain some information about the service itself and it's presence on the scheduler. For example,
|
||||
* {@link #getServiceId()} will return the ID of the task which was returned by the scheduler. Subscriptions also manage
|
||||
* the state of the service, using {@link #isActive()} to determine if the service is currently running.
|
||||
* <br>
|
||||
* <br>
|
||||
* The subscription itself provides type inference to safely store the actual service instance. This is useful for when
|
||||
* we need to access the service itself, without calling to the service directly.
|
||||
*
|
||||
* @param <T> The type of service this subscription is for.
|
||||
*/
|
||||
public final class ServiceSubscription<T extends Service>
|
||||
{
|
||||
/**
|
||||
* The service this subscription is for.
|
||||
*/
|
||||
private final T service;
|
||||
/**
|
||||
* Whether this is an asynchronous service.
|
||||
*/
|
||||
private final boolean async;
|
||||
/**
|
||||
* The executor used to schedule the service.
|
||||
*/
|
||||
private final Executor executor;
|
||||
/**
|
||||
* The ID of the service from the associated {@link BukkitTask} which was returned by the Scheduler.
|
||||
*/
|
||||
private final int serviceId;
|
||||
|
||||
/**
|
||||
* Whether the service is currently running.
|
||||
*/
|
||||
private boolean isActive = false;
|
||||
|
||||
/**
|
||||
* Creates a new subscription for the given service. By default, this method will mark this service as a synchronous
|
||||
* service. This will also initialize the default interval to a single tick.
|
||||
* <br>
|
||||
* If you are trying to create an asynchronous service, use
|
||||
* {@link #ServiceSubscription(JavaPlugin, Service, boolean)} instead.
|
||||
* <br>
|
||||
* If you would like to define a custom interval, use either {@link #ServiceSubscription(JavaPlugin, Service, long)}
|
||||
* or {@link #ServiceSubscription(JavaPlugin, Service, long, boolean)} (for asynchronous services).
|
||||
*
|
||||
* @param plugin The plugin which owns the service.
|
||||
* @param service The service to subscribe to.
|
||||
*/
|
||||
ServiceSubscription(@NotNull final JavaPlugin plugin, @NotNull final T service)
|
||||
{
|
||||
this(plugin, service, 1L, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new subscription for the given service. This will initialize the default interval to a single tick.
|
||||
* <br>
|
||||
* If you would like to define a custom interval, use either {@link #ServiceSubscription(JavaPlugin, Service, long)}
|
||||
* or {@link #ServiceSubscription(JavaPlugin, Service, long, boolean)} (for asynchronous services).
|
||||
*
|
||||
* @param plugin The plugin which owns the service.
|
||||
* @param service The service to subscribe to.
|
||||
* @param async Whether the service should be scheduled asynchronously.
|
||||
*/
|
||||
ServiceSubscription(@NotNull final JavaPlugin plugin, @NotNull final T service, final boolean async)
|
||||
{
|
||||
this(plugin, service, 1L, async);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new subscription for the given service. By default, this will mark the service as synchronous. When
|
||||
* defining a custom interval, the interval should be less than 20L (the number of ticks in a second). For anything
|
||||
* that requires an interval greater than 1 second, use a {@link Task} instead.
|
||||
* <br>
|
||||
* If you are trying to create an asynchronous service, use
|
||||
* {@link #ServiceSubscription(JavaPlugin, Service, long, boolean)} instead.
|
||||
*
|
||||
* @param plugin The plugin which owns the service.
|
||||
* @param service The service to subscribe to.
|
||||
* @param interval The interval at which the service should be scheduled.
|
||||
*/
|
||||
ServiceSubscription(@NotNull final JavaPlugin plugin, @NotNull final T service, final long interval)
|
||||
{
|
||||
this(plugin, service, interval, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new subscription for the given service. When defining a custom interval, the interval should be less
|
||||
* than 20L (the number of ticks in a second). For anything that requires an interval greater than 1 second, use a
|
||||
* {@link Task} instead.
|
||||
*
|
||||
* @param plugin The plugin which owns the service.
|
||||
* @param service The service to subscribe to.
|
||||
* @param interval The interval at which the service should be scheduled.
|
||||
* @param async Whether the service should be scheduled asynchronously.
|
||||
*/
|
||||
ServiceSubscription(@NotNull final JavaPlugin plugin, @NotNull final T service,
|
||||
final long interval, final boolean async)
|
||||
{
|
||||
this.service = service;
|
||||
this.async = async;
|
||||
|
||||
final int[] tempId = new int[1];
|
||||
|
||||
if (async)
|
||||
{
|
||||
this.executor = r ->
|
||||
{
|
||||
final BukkitTask task = Bukkit.getScheduler()
|
||||
.runTaskTimerAsynchronously(plugin, r, 0, interval);
|
||||
tempId[0] = task.getTaskId();
|
||||
};
|
||||
} else
|
||||
{
|
||||
this.executor = r ->
|
||||
{
|
||||
final BukkitTask task = Bukkit.getScheduler()
|
||||
.runTaskTimer(plugin, r, 0, interval);
|
||||
tempId[0] = task.getTaskId();
|
||||
};
|
||||
}
|
||||
|
||||
this.serviceId = tempId[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the service.
|
||||
*/
|
||||
public void start()
|
||||
{
|
||||
this.isActive = true;
|
||||
this.executor.execute(service::tick);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the service.
|
||||
*/
|
||||
public void stop()
|
||||
{
|
||||
this.isActive = false;
|
||||
Bukkit.getScheduler()
|
||||
.cancelTask(this.getServiceId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ID of the service from the associated {@link BukkitTask} which was returned by the Scheduler.
|
||||
*
|
||||
* @return The ID of the service.
|
||||
*/
|
||||
public int getServiceId()
|
||||
{
|
||||
return serviceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The service this subscription is for.
|
||||
*/
|
||||
@NotNull
|
||||
public T getService()
|
||||
{
|
||||
return service;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Whether this is an asynchronous service.
|
||||
*/
|
||||
public boolean isAsync()
|
||||
{
|
||||
return async;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Whether the service is currently running.
|
||||
*/
|
||||
public boolean isActive()
|
||||
{
|
||||
return isActive;
|
||||
}
|
||||
}
|
@ -0,0 +1,125 @@
|
||||
package fns.patchwork.service;
|
||||
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Provides static methods for creating {@link ServiceSubscription} and {@link TaskSubscription} objects.
|
||||
*/
|
||||
public final class SubscriptionProvider
|
||||
{
|
||||
/**
|
||||
* Prevents instantiation of this class.
|
||||
*/
|
||||
private SubscriptionProvider()
|
||||
{
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ServiceSubscription} object that will run the given {@link Service} object on the main
|
||||
* thread a single time.
|
||||
*
|
||||
* @param plugin The plugin that owns the service.
|
||||
* @param service The service to run.
|
||||
* @param <S> Type inference to maintain the service type.
|
||||
* @return The new {@link ServiceSubscription} object.
|
||||
*/
|
||||
@NotNull
|
||||
@Contract(value = "_, _ -> new", pure = false)
|
||||
public static final <S extends Service> ServiceSubscription<S> syncService(@NotNull final JavaPlugin plugin,
|
||||
@NotNull final S service)
|
||||
{
|
||||
return new ServiceSubscription<>(plugin, service);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ServiceSubscription} object that will run the given {@link Service} object on the main
|
||||
* thread at the given interval.
|
||||
*
|
||||
* @param plugin The plugin that owns the service.
|
||||
* @param interval The interval to run the service at.
|
||||
* @param service The service to run.
|
||||
* @param <S> Type inference to maintain the service type.
|
||||
* @return The new {@link ServiceSubscription} object.
|
||||
*/
|
||||
@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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ServiceSubscription} object that will run the given {@link Service} object on the default
|
||||
* tick interval, which is a single tick. This method will create an asynchronous service.
|
||||
*
|
||||
* @param plugin The plugin that owns the service.
|
||||
* @param service The service to run.
|
||||
* @param <S> Type inference to maintain the service type.
|
||||
* @return The new {@link ServiceSubscription} object.
|
||||
*/
|
||||
@NotNull
|
||||
@Contract(value = "_, _ -> new", pure = false)
|
||||
public static final <S extends Service> ServiceSubscription<S> asyncService(@NotNull final JavaPlugin plugin,
|
||||
@NotNull final S service)
|
||||
{
|
||||
return new ServiceSubscription<>(plugin, service, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ServiceSubscription} object that will run the given {@link Service} object on the given
|
||||
* interval. This method will create an asynchronous service.
|
||||
*
|
||||
* @param plugin The plugin that owns the service.
|
||||
* @param interval The interval to run the service at.
|
||||
* @param service The service to run.
|
||||
* @param <S> Type inference to maintain the service type.
|
||||
* @return The new {@link ServiceSubscription} object.
|
||||
*/
|
||||
@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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link TaskSubscription} object that will run the given {@link Task} object synchronously on the
|
||||
* main thread.
|
||||
*
|
||||
* @param plugin The plugin that owns the task.
|
||||
* @param task The task to run.
|
||||
* @param <T> Type inference to maintain the task type.
|
||||
* @return The new {@link TaskSubscription} object.
|
||||
*/
|
||||
@NotNull
|
||||
@Contract(value = "_, _ -> new", pure = false)
|
||||
public static final <T extends Task> TaskSubscription<T> runSyncTask(@NotNull final JavaPlugin plugin,
|
||||
@NotNull final T task)
|
||||
{
|
||||
return new TaskSubscription<>(plugin, task, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link TaskSubscription} object that will run the given {@link Task} object asynchronously on the
|
||||
* main thread.
|
||||
*
|
||||
* @param plugin The plugin that owns the task.
|
||||
* @param task The task to run.
|
||||
* @param <T> Type inference to maintain the task type.
|
||||
* @return The new {@link TaskSubscription} object.
|
||||
*/
|
||||
@NotNull
|
||||
@Contract(value = "_, _ -> new", pure = false)
|
||||
public static final <T extends Task> TaskSubscription<T> runAsyncTask(@NotNull final JavaPlugin plugin,
|
||||
@NotNull final T task)
|
||||
{
|
||||
return new TaskSubscription<>(plugin, task, true);
|
||||
}
|
||||
}
|
153
Patchwork/src/main/java/fns/patchwork/service/Task.java
Normal file
153
Patchwork/src/main/java/fns/patchwork/service/Task.java
Normal file
@ -0,0 +1,153 @@
|
||||
package fns.patchwork.service;
|
||||
|
||||
import fns.patchwork.utils.DurationTools;
|
||||
import java.time.Duration;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
|
||||
/**
|
||||
* Represents a task that can be run asynchronously or synchronously.
|
||||
*/
|
||||
public abstract class Task extends BukkitRunnable
|
||||
{
|
||||
/**
|
||||
* The name of the task.
|
||||
*/
|
||||
private final String name;
|
||||
/**
|
||||
* The delay of the task.
|
||||
*/
|
||||
private final long delay;
|
||||
/**
|
||||
* The interval of the task.
|
||||
*/
|
||||
private final long interval;
|
||||
|
||||
/**
|
||||
* Creates a new task with the given name. This will initialize a task with no initail delay and no interval.
|
||||
*
|
||||
* @param name The name of the task.
|
||||
*/
|
||||
protected Task(final String name)
|
||||
{
|
||||
this(name, -1L, -1L);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new task with the given name, delay, and interval.
|
||||
* <br>
|
||||
* It's important to note that the delay and interval are in ticks. One tick is equal to 1/20th of a second, which
|
||||
* means there are 20 ticks are in one second. If your interval is intended to be anything less than 20 ticks, you
|
||||
* should use a {@link Service} instead.
|
||||
*
|
||||
* @param name The name of the task.
|
||||
* @param delay The delay of the task.
|
||||
* @param interval The interval of the task.
|
||||
*/
|
||||
protected Task(final String name, final long delay, final long interval)
|
||||
{
|
||||
this.name = name;
|
||||
this.delay = delay;
|
||||
this.interval = interval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new task with the given name and delay. This will intialize a single execute task with an initial delay
|
||||
* before execution.
|
||||
*
|
||||
* @param name The name of the task.
|
||||
* @param delay How long the task should wait before executing.
|
||||
*/
|
||||
protected Task(final String name, final long delay)
|
||||
{
|
||||
this(name, delay, -1L);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new task with the given name and delay. This is the same as longs, except that here, we naturally
|
||||
* support durations which are automatically converted to ticks for you. This means that using
|
||||
* {@link Duration#ofSeconds(long)} will work as expected.
|
||||
*
|
||||
* @param name The name of the task.
|
||||
* @param delay How long the task should wait before executing.
|
||||
*/
|
||||
protected Task(final String name, final Duration delay)
|
||||
{
|
||||
this(name, DurationTools.getTicks(delay), -1L);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new task with the given name, delay, and interval. This is the same as longs, except that here, we
|
||||
* naturally support durations which are automatically converted to ticks for you. This means that using
|
||||
* {@link Duration#ofSeconds(long)} will work as expected.
|
||||
*
|
||||
* @param name The name of the task.
|
||||
* @param delay How long the task should wait before executing.
|
||||
* @param interval How long the task should wait between executions.
|
||||
*/
|
||||
protected Task(final String name, final Duration delay, final Duration interval)
|
||||
{
|
||||
this(name, DurationTools.getTicks(delay), DurationTools.getTicks(interval));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new task with the given name, delay, and interval. This method is a convenience method to use a
|
||||
* {@link Duration} for the interval, while also being able to specify the delay as -1L so the task does not have an
|
||||
* initial delay before execution.
|
||||
*
|
||||
* @param name The name of the task.
|
||||
* @param delay The delay of the task.
|
||||
* @param interval The interval of the task.
|
||||
*/
|
||||
protected Task(final String name, final long delay, final Duration interval)
|
||||
{
|
||||
this(name, delay, DurationTools.getTicks(interval));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if the task is running, false otherwise.
|
||||
*/
|
||||
public boolean isRunning()
|
||||
{
|
||||
return !isCancelled();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The name of the task.
|
||||
*/
|
||||
public String getName()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if the task is repeating, false otherwise.
|
||||
*/
|
||||
public boolean isRepeating()
|
||||
{
|
||||
return this.interval > 0L;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if the task is delayed, false otherwise.
|
||||
*/
|
||||
public boolean isDelayed()
|
||||
{
|
||||
return this.delay > 0L;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The interval between each task execution.
|
||||
*/
|
||||
public long getInterval()
|
||||
{
|
||||
return interval;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The initial delay before the first execution of this task.
|
||||
*/
|
||||
public long getDelay()
|
||||
{
|
||||
return delay;
|
||||
}
|
||||
}
|
@ -0,0 +1,217 @@
|
||||
package fns.patchwork.service;
|
||||
|
||||
import fns.patchwork.utils.container.Pair;
|
||||
import java.util.concurrent.Executor;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.bukkit.scheduler.BukkitScheduler;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
/**
|
||||
* Represents a subscription to a task. Task subscriptions offer a nice wrapper for managing tasks, which are inevitably
|
||||
* just bukkit runnables with a bit more lenience in terms of instantiation modification and execution. It also offers a
|
||||
* more intuitive way to manage our tasks; rather than having to keep track of task ids for each {@link BukkitTask}
|
||||
* object that gets returned by the {@link BukkitScheduler}.
|
||||
*
|
||||
* @param <T> The type of task.
|
||||
*/
|
||||
public final class TaskSubscription<T extends Task>
|
||||
{
|
||||
/**
|
||||
* The task that is being subscribed to.
|
||||
*/
|
||||
private final T task;
|
||||
/**
|
||||
* The task id of the task.
|
||||
*/
|
||||
private final int taskId;
|
||||
/**
|
||||
* True if the task is async, false otherwise.
|
||||
*/
|
||||
private final boolean async;
|
||||
/**
|
||||
* The executor that will execute the task.
|
||||
*/
|
||||
private final Executor executor;
|
||||
|
||||
/**
|
||||
* True if the task is active, false otherwise. By default, this is set to false, and will be marked as true when
|
||||
* the task is started.
|
||||
*/
|
||||
private boolean isActive = false;
|
||||
|
||||
/**
|
||||
* Creates a new task subscription.
|
||||
*
|
||||
* @param plugin The plugin which owns the task.
|
||||
* @param task The task that is being subscribed to.
|
||||
* @param async True if the task is async, false otherwise.
|
||||
*/
|
||||
TaskSubscription(final JavaPlugin plugin, final T task, final boolean async)
|
||||
{
|
||||
this.task = task;
|
||||
this.async = async;
|
||||
|
||||
final long delay = (task.isDelayed()
|
||||
? task.getDelay()
|
||||
: 0);
|
||||
final long period = (task.isRepeating()
|
||||
? task.getInterval()
|
||||
: 0);
|
||||
|
||||
final Pair<Integer, Executor> integerExecutorPair = async
|
||||
? getAsync(plugin, delay, period)
|
||||
: getSync(plugin, delay, period);
|
||||
|
||||
this.executor = integerExecutorPair.value();
|
||||
this.taskId = integerExecutorPair.key();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the executor and task id for an async task, wrapped in a {@link Pair}<{@link Integer},
|
||||
* {@link Executor}>.
|
||||
* <br>
|
||||
* This will return a Pair where {@link Pair#value()} is an asynchronous executor.
|
||||
*
|
||||
* @param plugin The plugin which owns the task.
|
||||
* @param delay The delay of the task.
|
||||
* @param period The period of the task.
|
||||
* @return The executor and task id for an asynchronous task.
|
||||
*/
|
||||
private Pair<Integer, Executor> getAsync(final JavaPlugin plugin, final long delay, final long period)
|
||||
{
|
||||
final Executor executor1;
|
||||
final int[] tempId = new int[1];
|
||||
if (period != 0)
|
||||
{
|
||||
executor1 = r ->
|
||||
{
|
||||
final BukkitTask task1 = Bukkit.getScheduler()
|
||||
.runTaskTimerAsynchronously(plugin, r, delay, period);
|
||||
tempId[0] = task1.getTaskId();
|
||||
};
|
||||
} else if (delay != 0)
|
||||
{
|
||||
executor1 = r ->
|
||||
{
|
||||
final BukkitTask task1 = Bukkit.getScheduler()
|
||||
.runTaskLaterAsynchronously(plugin, r, delay);
|
||||
tempId[0] = task1.getTaskId();
|
||||
};
|
||||
} else
|
||||
{
|
||||
executor1 = r ->
|
||||
{
|
||||
final BukkitTask task1 = Bukkit.getScheduler()
|
||||
.runTaskAsynchronously(plugin, r);
|
||||
tempId[0] = task1.getTaskId();
|
||||
};
|
||||
}
|
||||
|
||||
return new Pair<>(tempId[0], executor1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the executor and task id for a sync task, wrapped in a {@link Pair}<{@link Integer},
|
||||
* {@link Executor}>.
|
||||
* <br>
|
||||
* This will return a Pair where {@link Pair#value()} is a synchronous executor.
|
||||
*
|
||||
* @param plugin The plugin which owns the task.
|
||||
* @param delay The delay of the task.
|
||||
* @param period The period of the task.
|
||||
* @return The executor and task id for a synchronous task.
|
||||
*/
|
||||
private Pair<Integer, Executor> getSync(final JavaPlugin plugin, final long delay, final long period)
|
||||
{
|
||||
final Executor executor1;
|
||||
final int[] tempId = new int[1];
|
||||
|
||||
if (period != 0)
|
||||
{
|
||||
executor1 = r ->
|
||||
{
|
||||
final BukkitTask task1 = Bukkit.getScheduler()
|
||||
.runTaskTimer(plugin, r, delay, period);
|
||||
tempId[0] = task1.getTaskId();
|
||||
};
|
||||
} else if (delay != 0)
|
||||
{
|
||||
executor1 = r ->
|
||||
{
|
||||
final BukkitTask task1 = Bukkit.getScheduler()
|
||||
.runTaskLater(plugin, r, delay);
|
||||
tempId[0] = task1.getTaskId();
|
||||
};
|
||||
} else
|
||||
{
|
||||
executor1 = r ->
|
||||
{
|
||||
final BukkitTask task1 = Bukkit.getScheduler()
|
||||
.runTask(plugin, r);
|
||||
tempId[0] = task1.getTaskId();
|
||||
};
|
||||
}
|
||||
|
||||
return new Pair<>(tempId[0], executor1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the task.
|
||||
*/
|
||||
public void start()
|
||||
{
|
||||
this.isActive = true;
|
||||
executor.execute(task);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the task.
|
||||
*/
|
||||
public void stop()
|
||||
{
|
||||
this.isActive = false;
|
||||
Bukkit.getScheduler()
|
||||
.cancelTask(this.getTaskId());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The task id of the task.
|
||||
*/
|
||||
public int getTaskId()
|
||||
{
|
||||
return taskId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The task that is being subscribed to.
|
||||
*/
|
||||
public T getTask()
|
||||
{
|
||||
return task;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if the task is async, false otherwise.
|
||||
*/
|
||||
public boolean isAsync()
|
||||
{
|
||||
return async;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The executor that will execute the task.
|
||||
*/
|
||||
public Executor getExecutor()
|
||||
{
|
||||
return executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if the task is active, false otherwise.
|
||||
*/
|
||||
public boolean isActive()
|
||||
{
|
||||
return isActive;
|
||||
}
|
||||
}
|
21
Patchwork/src/main/java/fns/patchwork/shop/Reactable.java
Normal file
21
Patchwork/src/main/java/fns/patchwork/shop/Reactable.java
Normal file
@ -0,0 +1,21 @@
|
||||
package fns.patchwork.shop;
|
||||
|
||||
import fns.patchwork.economy.EconomicEntity;
|
||||
import java.time.Duration;
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
public interface Reactable
|
||||
{
|
||||
Component getReactionMessage();
|
||||
|
||||
Duration getReactionDuration();
|
||||
|
||||
ReactionType getReactionType();
|
||||
|
||||
long getReward();
|
||||
|
||||
void display(final Audience audience);
|
||||
|
||||
void onReact(final EconomicEntity entity);
|
||||
}
|
60
Patchwork/src/main/java/fns/patchwork/shop/Reaction.java
Normal file
60
Patchwork/src/main/java/fns/patchwork/shop/Reaction.java
Normal file
@ -0,0 +1,60 @@
|
||||
package fns.patchwork.shop;
|
||||
|
||||
import java.time.Duration;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
/**
|
||||
* Represents a chat reaction that can be performed by a player.
|
||||
*/
|
||||
public abstract class Reaction implements Reactable
|
||||
{
|
||||
private final Duration reactionDuration;
|
||||
private final ReactionType reactionType;
|
||||
private final long reward;
|
||||
private Component reactionMessage = Component.empty();
|
||||
|
||||
protected Reaction(final ReactionType type)
|
||||
{
|
||||
this(50L, type);
|
||||
}
|
||||
|
||||
protected Reaction(final long reward, final ReactionType type)
|
||||
{
|
||||
this(30L, reward, type);
|
||||
}
|
||||
|
||||
protected Reaction(final long seconds, final long reward, final ReactionType reactionType)
|
||||
{
|
||||
this(Duration.ofSeconds(seconds), reward, reactionType);
|
||||
}
|
||||
|
||||
protected Reaction(final Duration duration, final long reward, final ReactionType reactionType)
|
||||
{
|
||||
this.reward = reward;
|
||||
this.reactionDuration = duration;
|
||||
this.reactionType = reactionType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getReactionMessage()
|
||||
{
|
||||
return reactionMessage;
|
||||
}
|
||||
|
||||
public void setReactionMessage(final Component message)
|
||||
{
|
||||
this.reactionMessage = message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Duration getReactionDuration()
|
||||
{
|
||||
return reactionDuration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReactionType getReactionType()
|
||||
{
|
||||
return reactionType;
|
||||
}
|
||||
}
|
56
Patchwork/src/main/java/fns/patchwork/shop/ReactionTask.java
Normal file
56
Patchwork/src/main/java/fns/patchwork/shop/ReactionTask.java
Normal file
@ -0,0 +1,56 @@
|
||||
package fns.patchwork.shop;
|
||||
|
||||
import fns.patchwork.base.Patchwork;
|
||||
import fns.patchwork.display.BossBarDisplay;
|
||||
import fns.patchwork.display.BossBarTimer;
|
||||
import fns.patchwork.economy.EconomicEntity;
|
||||
import fns.patchwork.service.Task;
|
||||
import io.papermc.paper.event.player.AsyncChatEvent;
|
||||
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(Patchwork.getInstance(), 0L, timer.getInterval());
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerChat(final AsyncChatEvent event)
|
||||
{
|
||||
if (event.message()
|
||||
.equals(reaction.getReactionMessage()))
|
||||
{
|
||||
final EconomicEntity entity = Patchwork.getInstance()
|
||||
.getRegistrations()
|
||||
.getUserRegistry()
|
||||
.getUser(event.getPlayer());
|
||||
|
||||
reaction.onReact(entity);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package fns.patchwork.shop;
|
||||
|
||||
public enum ReactionType
|
||||
{
|
||||
COPYCAT, UNSCRAMBLE, MATH;
|
||||
}
|
18
Patchwork/src/main/java/fns/patchwork/sql/SQL.java
Normal file
18
Patchwork/src/main/java/fns/patchwork/sql/SQL.java
Normal file
@ -0,0 +1,18 @@
|
||||
package fns.patchwork.sql;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public interface SQL
|
||||
{
|
||||
CompletableFuture<PreparedStatement> prepareStatement(final String query, final Object... args);
|
||||
|
||||
CompletableFuture<ResultSet> executeQuery(final String query, final Object... args);
|
||||
|
||||
CompletableFuture<Integer> executeUpdate(final String query, final Object... args);
|
||||
|
||||
CompletableFuture<Boolean> execute(final String query, final Object... args);
|
||||
|
||||
CompletableFuture<Boolean> createTable(final String table, final String... columns);
|
||||
}
|
53
Patchwork/src/main/java/fns/patchwork/sql/SQLProperties.java
Normal file
53
Patchwork/src/main/java/fns/patchwork/sql/SQLProperties.java
Normal file
@ -0,0 +1,53 @@
|
||||
package fns.patchwork.sql;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Properties;
|
||||
|
||||
public interface SQLProperties
|
||||
{
|
||||
Properties getProperties(File propertiesFile);
|
||||
|
||||
default Properties getDefaultProperties()
|
||||
{
|
||||
final Properties properties = new Properties();
|
||||
properties.setProperty("driver", "sqlite");
|
||||
properties.setProperty("host", "localhost");
|
||||
properties.setProperty("port", "3306");
|
||||
properties.setProperty("database", "database.db");
|
||||
properties.setProperty("username", "root");
|
||||
properties.setProperty("password", "password");
|
||||
return properties;
|
||||
}
|
||||
|
||||
default String toURLPlain()
|
||||
{
|
||||
return String.format("jdbc:%s://%s:%s/%s",
|
||||
this.getDriver(),
|
||||
this.getHost(),
|
||||
this.getPort(),
|
||||
this.getDatabase());
|
||||
}
|
||||
|
||||
String getDriver();
|
||||
|
||||
String getHost();
|
||||
|
||||
String getPort();
|
||||
|
||||
String getDatabase();
|
||||
|
||||
default String toURLWithLogin()
|
||||
{
|
||||
return String.format("jdbc:%s://%s:%s/%s?user=%s&password=%s",
|
||||
this.getDriver(),
|
||||
this.getHost(),
|
||||
this.getPort(),
|
||||
this.getDatabase(),
|
||||
this.getUsername(),
|
||||
this.getPassword());
|
||||
}
|
||||
|
||||
String getUsername();
|
||||
|
||||
String getPassword();
|
||||
}
|
28
Patchwork/src/main/java/fns/patchwork/user/User.java
Normal file
28
Patchwork/src/main/java/fns/patchwork/user/User.java
Normal file
@ -0,0 +1,28 @@
|
||||
package fns.patchwork.user;
|
||||
|
||||
import fns.patchwork.economy.EconomicEntity;
|
||||
import fns.patchwork.economy.EconomicEntityData;
|
||||
import fns.patchwork.security.PermissionHolder;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
public interface User extends PermissionHolder, EconomicEntity
|
||||
{
|
||||
@Override
|
||||
default EconomicEntityData getEconomicData()
|
||||
{
|
||||
return getUserData();
|
||||
}
|
||||
|
||||
// Implement a few EconomicEntity methods in the User interface
|
||||
@Override
|
||||
default String getName()
|
||||
{
|
||||
return getUserData().getUsername();
|
||||
}
|
||||
|
||||
UserData getUserData();
|
||||
|
||||
Component getDisplayName();
|
||||
|
||||
boolean isOnline();
|
||||
}
|
39
Patchwork/src/main/java/fns/patchwork/user/UserData.java
Normal file
39
Patchwork/src/main/java/fns/patchwork/user/UserData.java
Normal file
@ -0,0 +1,39 @@
|
||||
package fns.patchwork.user;
|
||||
|
||||
import fns.patchwork.display.adminchat.AdminChatFormat;
|
||||
import fns.patchwork.economy.EconomicEntityData;
|
||||
import fns.patchwork.security.Group;
|
||||
import java.util.UUID;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public interface UserData extends EconomicEntityData
|
||||
{
|
||||
@NotNull UUID getUniqueId();
|
||||
|
||||
String getUsername();
|
||||
|
||||
User getUser();
|
||||
|
||||
@Nullable Group getGroup();
|
||||
|
||||
void setGroup(@Nullable Group group);
|
||||
|
||||
long getPlaytime();
|
||||
|
||||
void setPlaytime(long playtime);
|
||||
|
||||
void addPlaytime(long playtime);
|
||||
|
||||
void resetPlaytime();
|
||||
|
||||
boolean canInteract();
|
||||
|
||||
void setInteractionState(boolean canInteract);
|
||||
|
||||
boolean hasCustomACFormat();
|
||||
|
||||
void setCustomACFormat(final String customACFormat);
|
||||
|
||||
AdminChatFormat getCustomACFormat();
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package fns.patchwork.utils;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
public final class DurationTools
|
||||
{
|
||||
// One tick is 1/20th of a second which is about 50ms.
|
||||
public static final Duration TICK = Duration.ofMillis(50L);
|
||||
// One second is 20 ticks.
|
||||
public static final Duration SECOND = TICK.multipliedBy(20L);
|
||||
// One minute is 60 seconds.
|
||||
public static final Duration MINUTE = SECOND.multipliedBy(60L);
|
||||
|
||||
private DurationTools()
|
||||
{
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
public static final long getTicks(final Duration duration)
|
||||
{
|
||||
return duration.toMillis() / 50L;
|
||||
}
|
||||
|
||||
public static final Duration getTickedSeconds(final long seconds)
|
||||
{
|
||||
return SECOND.multipliedBy(seconds);
|
||||
}
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
package fns.patchwork.utils;
|
||||
|
||||
import fns.patchwork.api.Interpolator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import org.bukkit.Color;
|
||||
|
||||
public final class InterpolationUtils
|
||||
{
|
||||
private InterpolationUtils()
|
||||
{
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
public static Set<Color> rainbow(final int length)
|
||||
{
|
||||
final LinkedHashSet<Color> base = new LinkedHashSet<>();
|
||||
final Set<Color> redToOrange = hsvGradient(length, Color.RED, Color.ORANGE, InterpolationUtils::linear);
|
||||
final Set<Color> orangeToYellow = hsvGradient(length, Color.ORANGE, Color.YELLOW, InterpolationUtils::linear);
|
||||
final Set<Color> yellowToGreen = hsvGradient(length, Color.YELLOW, Color.GREEN, InterpolationUtils::linear);
|
||||
final Set<Color> greenToBlue = hsvGradient(length, Color.GREEN, Color.BLUE, InterpolationUtils::linear);
|
||||
final Set<Color> blueToPurple = hsvGradient(length, Color.BLUE, Color.PURPLE, InterpolationUtils::linear);
|
||||
final Set<Color> purpleToRed = hsvGradient(length, Color.PURPLE, Color.RED, InterpolationUtils::linear);
|
||||
base.addAll(redToOrange);
|
||||
base.addAll(orangeToYellow);
|
||||
base.addAll(yellowToGreen);
|
||||
base.addAll(greenToBlue);
|
||||
base.addAll(blueToPurple);
|
||||
base.addAll(purpleToRed);
|
||||
return base;
|
||||
}
|
||||
|
||||
private static Set<Color> hsvGradient(final int length, final Color from, final Color to,
|
||||
final Interpolator interpolator)
|
||||
{
|
||||
// returns a float-array where hsv[0] = hue, hsv[1] = saturation, hsv[2] = value/brightness
|
||||
final float[] hsvFrom = java.awt.Color.RGBtoHSB(from.getRed(), from.getGreen(), from.getBlue(), null);
|
||||
final float[] hsvTo = java.awt.Color.RGBtoHSB(to.getRed(), to.getGreen(), to.getBlue(), null);
|
||||
|
||||
final double[] h = interpolator.interpolate(hsvFrom[0], hsvTo[0], length);
|
||||
final double[] s = interpolator.interpolate(hsvFrom[1], hsvTo[1], length);
|
||||
final double[] v = interpolator.interpolate(hsvFrom[2], hsvTo[2], length);
|
||||
|
||||
final LinkedHashSet<Color> gradient = new LinkedHashSet<>();
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
final int rgb = java.awt.Color.HSBtoRGB((float) h[i], (float) s[i], (float) v[i]);
|
||||
final Color color = Color.fromRGB(rgb);
|
||||
gradient.add(color);
|
||||
}
|
||||
return gradient;
|
||||
}
|
||||
|
||||
private static double[] linear(final double from, final double to, final int max)
|
||||
{
|
||||
final double[] res = new double[max];
|
||||
for (int i = 0; i < max; i++)
|
||||
{
|
||||
res[i] = from + i * ((to - from) / (max - 1));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
public static Set<TextColor> rainbowComponent(final int length)
|
||||
{
|
||||
final LinkedHashSet<TextColor> base = new LinkedHashSet<>();
|
||||
final Set<TextColor> redToOrange = componentRGBGradient(length, NamedTextColor.RED,
|
||||
NamedTextColor.GOLD, InterpolationUtils::linear);
|
||||
final Set<TextColor> orangeToYellow = componentRGBGradient(length, NamedTextColor.GOLD,
|
||||
NamedTextColor.YELLOW, InterpolationUtils::linear);
|
||||
final Set<TextColor> yellowToGreen = componentRGBGradient(length, NamedTextColor.YELLOW,
|
||||
NamedTextColor.GREEN, InterpolationUtils::linear);
|
||||
final Set<TextColor> greenToBlue = componentRGBGradient(length, NamedTextColor.GREEN,
|
||||
NamedTextColor.BLUE, InterpolationUtils::linear);
|
||||
final Set<TextColor> blueToPurple = componentRGBGradient(length, NamedTextColor.BLUE,
|
||||
NamedTextColor.LIGHT_PURPLE,
|
||||
InterpolationUtils::linear);
|
||||
final Set<TextColor> purpleToRed = componentRGBGradient(length, TextColor.color(75, 0, 130),
|
||||
TextColor.color(255, 0, 0), InterpolationUtils::linear);
|
||||
base.addAll(redToOrange);
|
||||
base.addAll(orangeToYellow);
|
||||
base.addAll(yellowToGreen);
|
||||
base.addAll(greenToBlue);
|
||||
base.addAll(blueToPurple);
|
||||
base.addAll(purpleToRed);
|
||||
return base;
|
||||
}
|
||||
|
||||
private static Set<TextColor> componentRGBGradient(final int length, final TextColor from, final TextColor to,
|
||||
final Interpolator interpolator)
|
||||
{
|
||||
final double[] r = interpolator.interpolate(from.red(), to.red(), length);
|
||||
final double[] g = interpolator.interpolate(from.green(), to.green(), length);
|
||||
final double[] b = interpolator.interpolate(from.blue(), to.blue(), length);
|
||||
|
||||
final LinkedHashSet<TextColor> gradient = new LinkedHashSet<>();
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
final TextColor color = TextColor.color((int) r[i], (int) g[i], (int) b[i]);
|
||||
gradient.add(color);
|
||||
}
|
||||
return gradient;
|
||||
}
|
||||
|
||||
public static Set<Color> standardGradient(final int length, final Color from, final Color to)
|
||||
{
|
||||
return rgbGradient(length, from, to, InterpolationUtils::linear);
|
||||
}
|
||||
|
||||
private static Set<Color> rgbGradient(final int length, final Color from, final Color to,
|
||||
final Interpolator interpolator)
|
||||
{
|
||||
final double[] r = interpolator.interpolate(from.getRed(), to.getRed(), length);
|
||||
final double[] g = interpolator.interpolate(from.getGreen(), to.getGreen(), length);
|
||||
final double[] b = interpolator.interpolate(from.getBlue(), to.getBlue(), length);
|
||||
|
||||
final LinkedHashSet<Color> gradient = new LinkedHashSet<>();
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
final Color color = Color.fromRGB((int) r[i], (int) g[i], (int) b[i]);
|
||||
gradient.add(color);
|
||||
}
|
||||
return gradient;
|
||||
}
|
||||
|
||||
public static Set<TextColor> standardComponentGradient(final int length, final TextColor from, final TextColor to)
|
||||
{
|
||||
return componentRGBGradient(length, from, to, InterpolationUtils::linear);
|
||||
}
|
||||
}
|
40
Patchwork/src/main/java/fns/patchwork/utils/ShapeUtils.java
Normal file
40
Patchwork/src/main/java/fns/patchwork/utils/ShapeUtils.java
Normal file
@ -0,0 +1,40 @@
|
||||
package fns.patchwork.utils;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.function.DoubleUnaryOperator;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
|
||||
public class ShapeUtils
|
||||
{
|
||||
private final double start;
|
||||
private final double end;
|
||||
private final World world;
|
||||
|
||||
public ShapeUtils(final World world, final double start, final double end)
|
||||
{
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.world = world;
|
||||
}
|
||||
|
||||
public List<Location> generate(final int count, final DoubleUnaryOperator x, final DoubleUnaryOperator y,
|
||||
final DoubleUnaryOperator z)
|
||||
{
|
||||
final double step = (start - end) / (count - 1);
|
||||
final LinkedList<Location> lset = new LinkedList<>();
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
final double t = start + i * step;
|
||||
final double xp = x.applyAsDouble(t);
|
||||
final double yp = y.applyAsDouble(t);
|
||||
final double zp = z.applyAsDouble(t);
|
||||
|
||||
lset.add(new Location(world, xp, yp, zp));
|
||||
}
|
||||
|
||||
return lset;
|
||||
}
|
||||
}
|
100
Patchwork/src/main/java/fns/patchwork/utils/Tagged.java
Normal file
100
Patchwork/src/main/java/fns/patchwork/utils/Tagged.java
Normal file
@ -0,0 +1,100 @@
|
||||
package fns.patchwork.utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public final class Tagged<T>
|
||||
{
|
||||
public static final Tagged<EntityType> NON_WIPEABLE = new Tagged<>(EntityType.ITEM_FRAME, EntityType.BLOCK_DISPLAY,
|
||||
EntityType.ITEM_DISPLAY, EntityType.LEASH_HITCH,
|
||||
EntityType.CHEST_BOAT, EntityType.BOAT,
|
||||
EntityType.TEXT_DISPLAY,
|
||||
EntityType.GLOW_ITEM_FRAME,
|
||||
EntityType.ARMOR_STAND, EntityType.PAINTING,
|
||||
EntityType.PLAYER, EntityType.DROPPED_ITEM,
|
||||
EntityType.MINECART, EntityType.MINECART_CHEST,
|
||||
EntityType.MINECART_COMMAND,
|
||||
EntityType.MINECART_FURNACE,
|
||||
EntityType.MINECART_HOPPER,
|
||||
EntityType.FISHING_HOOK, EntityType.DONKEY,
|
||||
EntityType.HORSE, EntityType.IRON_GOLEM,
|
||||
EntityType.RABBIT, EntityType.SNOWMAN,
|
||||
EntityType.VILLAGER, EntityType.WOLF);
|
||||
|
||||
public static final Tagged<EntityType> MINECARTS = new Tagged<>(EntityType.MINECART, EntityType.MINECART_CHEST,
|
||||
EntityType.MINECART_COMMAND,
|
||||
EntityType.MINECART_FURNACE,
|
||||
EntityType.MINECART_HOPPER,
|
||||
EntityType.MINECART_MOB_SPAWNER,
|
||||
EntityType.MINECART_TNT);
|
||||
|
||||
public static final Tagged<EntityType> BOATS = new Tagged<>(EntityType.BOAT, EntityType.CHEST_BOAT);
|
||||
|
||||
public static final Tagged<EntityType> HOSTILE = new Tagged<>(EntityType.BLAZE, EntityType.CAVE_SPIDER,
|
||||
EntityType.CREEPER, EntityType.DROWNED,
|
||||
EntityType.ELDER_GUARDIAN, EntityType.ENDER_CRYSTAL,
|
||||
EntityType.ENDER_DRAGON, EntityType.ENDERMAN,
|
||||
EntityType.ENDERMITE, EntityType.EVOKER,
|
||||
EntityType.EVOKER_FANGS, EntityType.GHAST,
|
||||
EntityType.GIANT, EntityType.GUARDIAN,
|
||||
EntityType.HOGLIN, EntityType.HUSK,
|
||||
EntityType.ILLUSIONER, EntityType.MAGMA_CUBE,
|
||||
EntityType.PHANTOM, EntityType.PIGLIN,
|
||||
EntityType.PIGLIN_BRUTE, EntityType.PILLAGER,
|
||||
EntityType.RAVAGER, EntityType.SHULKER,
|
||||
EntityType.SILVERFISH, EntityType.SKELETON,
|
||||
EntityType.SLIME, EntityType.SPIDER, EntityType.STRAY,
|
||||
EntityType.VEX, EntityType.VINDICATOR,
|
||||
EntityType.WARDEN, EntityType.WITCH,
|
||||
EntityType.WITHER, EntityType.WITHER_SKELETON,
|
||||
EntityType.ZOGLIN, EntityType.ZOMBIE,
|
||||
EntityType.ZOMBIE_VILLAGER,
|
||||
EntityType.ZOMBIFIED_PIGLIN);
|
||||
|
||||
public static final Tagged<EntityType> PASSIVE = new Tagged<>(EntityType.BAT, EntityType.BEE, EntityType.CAT,
|
||||
EntityType.CHICKEN, EntityType.COD, EntityType.COW,
|
||||
EntityType.DOLPHIN, EntityType.DONKEY, EntityType.FOX,
|
||||
EntityType.GOAT, EntityType.HORSE,
|
||||
EntityType.IRON_GOLEM, EntityType.LLAMA,
|
||||
EntityType.MULE, EntityType.MUSHROOM_COW,
|
||||
EntityType.OCELOT, EntityType.PANDA,
|
||||
EntityType.PARROT, EntityType.PIG,
|
||||
EntityType.POLAR_BEAR, EntityType.PUFFERFISH,
|
||||
EntityType.RABBIT, EntityType.SALMON,
|
||||
EntityType.SHEEP, EntityType.SKELETON_HORSE,
|
||||
EntityType.SNOWMAN, EntityType.SQUID,
|
||||
EntityType.STRIDER, EntityType.TRADER_LLAMA,
|
||||
EntityType.TROPICAL_FISH, EntityType.TURTLE,
|
||||
EntityType.VILLAGER, EntityType.WANDERING_TRADER,
|
||||
EntityType.WOLF);
|
||||
|
||||
public static final Tagged<EntityType> PLAYER_RELATED = new Tagged<>(EntityType.PLAYER, EntityType.ARMOR_STAND,
|
||||
EntityType.DROPPED_ITEM, EntityType.PAINTING,
|
||||
EntityType.ITEM_FRAME,
|
||||
EntityType.GLOW_ITEM_FRAME,
|
||||
EntityType.LEASH_HITCH,
|
||||
EntityType.FISHING_HOOK,
|
||||
EntityType.TEXT_DISPLAY,
|
||||
EntityType.BLOCK_DISPLAY,
|
||||
EntityType.ITEM_DISPLAY);
|
||||
|
||||
private final List<T> taggable;
|
||||
|
||||
private Tagged(final T... taggable)
|
||||
{
|
||||
this.taggable = new ArrayList<>();
|
||||
this.taggable.addAll(List.of(taggable));
|
||||
}
|
||||
|
||||
public boolean isTagged(final @NotNull T object)
|
||||
{
|
||||
for (final T t : this.taggable)
|
||||
{
|
||||
if (object.equals(t))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package fns.patchwork.utils.container;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class Identity
|
||||
{
|
||||
private final String key;
|
||||
|
||||
private final UUID id;
|
||||
|
||||
public Identity(final String key)
|
||||
{
|
||||
this.key = key;
|
||||
this.id = UUID.nameUUIDFromBytes(key.getBytes());
|
||||
}
|
||||
|
||||
public String getKey()
|
||||
{
|
||||
return key;
|
||||
}
|
||||
|
||||
public UUID getId()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package fns.patchwork.utils.container;
|
||||
|
||||
public record Pair<K, V>(K key, V value)
|
||||
{
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package fns.patchwork.utils.container;
|
||||
|
||||
public record Trio<A, B, C>(A primary, B secondary, C tertiary)
|
||||
{
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package fns.patchwork.utils.container;
|
||||
|
||||
public record UnaryPair<T>(T first, T second)
|
||||
{
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package fns.patchwork.utils.container;
|
||||
|
||||
public record UnaryTrio<T>(T primary, T secondary, T tertiary)
|
||||
{
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package fns.patchwork.utils.kyori;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||
|
||||
/**
|
||||
* This class contains the only reference to plain text component serializer, and allows access to it via wrapper
|
||||
* functions.
|
||||
*/
|
||||
public class FreedomAdventure
|
||||
{
|
||||
private static final PlainTextComponentSerializer PLAIN_TEXT_COMPONENT_SERIALIZER =
|
||||
PlainTextComponentSerializer.plainText();
|
||||
|
||||
private FreedomAdventure()
|
||||
{
|
||||
throw new UnsupportedOperationException("Instantiation of a static utility class is not supported.");
|
||||
}
|
||||
|
||||
public static String toPlainText(final Supplier<Component> supplier)
|
||||
{
|
||||
return toPlainText(supplier.get());
|
||||
}
|
||||
|
||||
public static String toPlainText(final Component component)
|
||||
{
|
||||
return PLAIN_TEXT_COMPONENT_SERIALIZER.serialize(component);
|
||||
}
|
||||
|
||||
public static Supplier<String> supplyPlainText(final Supplier<Component> supplier)
|
||||
{
|
||||
return new StringRepresentationSupplier(supplier.get());
|
||||
}
|
||||
|
||||
public static Supplier<String> supplyPlainText(final Component component)
|
||||
{
|
||||
return new StringRepresentationSupplier(component);
|
||||
}
|
||||
|
||||
private record StringRepresentationSupplier(Component component) implements Supplier<String>
|
||||
{
|
||||
@Override
|
||||
public String get()
|
||||
{
|
||||
return PLAIN_TEXT_COMPONENT_SERIALIZER.serialize(component);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright (c) 2023 TotalFreedom
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
* software and associated documentation files (the “Software”), to deal in the Software
|
||||
* without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to
|
||||
* whom the Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or
|
||||
* substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package fns.patchwork.utils.kyori;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.TextDecoration;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
|
||||
import net.kyori.adventure.text.minimessage.tag.standard.StandardTags;
|
||||
|
||||
/**
|
||||
* This class contains a wrapper for a MiniMessage serializer.
|
||||
*/
|
||||
public class FreedomMiniMessage
|
||||
{
|
||||
private static final MiniMessage unsafe = MiniMessage.miniMessage();
|
||||
private static final MiniMessage safe = MiniMessage.builder()
|
||||
.tags(TagResolver.resolver(
|
||||
StandardTags.color(),
|
||||
StandardTags.rainbow(),
|
||||
StandardTags.gradient(),
|
||||
StandardTags.newline(),
|
||||
StandardTags.decorations(TextDecoration.ITALIC),
|
||||
StandardTags.decorations(TextDecoration.BOLD),
|
||||
StandardTags.decorations(TextDecoration.STRIKETHROUGH),
|
||||
StandardTags.decorations(TextDecoration.UNDERLINED)
|
||||
))
|
||||
.build();
|
||||
|
||||
private FreedomMiniMessage()
|
||||
{
|
||||
throw new UnsupportedOperationException("Instantiation of a static utility class is not supported.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes an input string using an instance of MiniMessage that is either safe (resolves only a specific set
|
||||
* of tags) or unsafe (resolves all tags).
|
||||
*
|
||||
* @param safe Whether to use a safe instance of MiniMessage
|
||||
* @param input An input string formatted with MiniMessage's input
|
||||
* @param placeholders Custom placeholders to use when processing the input
|
||||
* @return A processed Component
|
||||
*/
|
||||
public static Component deserialize(final boolean safe, final String input, final TagResolver... placeholders)
|
||||
{
|
||||
return (safe
|
||||
? FreedomMiniMessage.safe
|
||||
: unsafe).deserialize(input, placeholders);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes an input component using an instance of MiniMessage that is either safe (resolves only a specific set
|
||||
* of tags) or unsafe (resolves all tags).
|
||||
*
|
||||
* @param safe Whether to use a safe instance of MiniMessage
|
||||
* @param input An already processed component
|
||||
* @return A processed Component
|
||||
*/
|
||||
public static String serialize(final boolean safe, final Component input)
|
||||
{
|
||||
return (safe
|
||||
? FreedomMiniMessage.safe
|
||||
: unsafe).serialize(input);
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
package fns.patchwork.utils.kyori;
|
||||
|
||||
import fns.patchwork.base.Patchwork;
|
||||
import net.kyori.adventure.chat.ChatType;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.bukkit.command.ConsoleCommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
/**
|
||||
* This class contains different methods to provide {@link ChatType.Bound} instances for sending messages to players in
|
||||
* game. This is now a requirement for all message requests to players due to the new chat signature system.
|
||||
* <br>
|
||||
* Even though Scissors has this feature disabled, upstream (Paper) and Kyori Adventure have made the appropriate API
|
||||
* changes to accomodate chat signatures.
|
||||
* <br>
|
||||
* As a result, we need to conform to those specifications even if we do not use this feature.
|
||||
*/
|
||||
public final class KyoriConstants
|
||||
{
|
||||
private static final ChatType type = ChatType.CHAT;
|
||||
|
||||
/**
|
||||
* A singleton {@link ChatType.Bound} for the Patchwork plugin.
|
||||
*/
|
||||
public static final ChatType.Bound PATCHWORK = fromPlugin(Patchwork.class);
|
||||
|
||||
private KyoriConstants()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a Chat Bound for a plugin.
|
||||
* <br>
|
||||
* This is a convenience method so you are not required to dependency inject your plugin instance any time that you
|
||||
* need a Bound.
|
||||
*
|
||||
* @param pluginClass The plugin class to get the plugin instance from.
|
||||
* @return A ChatType.Bound instance for the plugin.
|
||||
* @see #fromPlugin(JavaPlugin)
|
||||
*/
|
||||
public static ChatType.Bound fromPlugin(final Class<? extends JavaPlugin> pluginClass)
|
||||
{
|
||||
final JavaPlugin plugin = JavaPlugin.getPlugin(pluginClass);
|
||||
return fromPlugin(plugin);
|
||||
}
|
||||
|
||||
public static ChatType.Bound fromPlugin(final JavaPlugin plugin)
|
||||
{
|
||||
final String name = plugin.getName();
|
||||
final Component component = Component.text(name, NamedTextColor.GOLD);
|
||||
return type.bind(component);
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a Chat Bound for a player. Chat bounds are required for sending messages to players.
|
||||
* <br>
|
||||
* The chat bound is a representation of a validated chat signature, which is tied directly to the user's account
|
||||
* name. In our case, this is the player's name.
|
||||
*
|
||||
* @param player The player to get the bound for.
|
||||
* @return A ChatType.Bound instance for the player.
|
||||
*/
|
||||
public static ChatType.Bound fromPlayer(final Player player)
|
||||
{
|
||||
return type.bind(player.name());
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a Chat Bound for the console.
|
||||
* <br>
|
||||
* The chat bound is a representation of a validated chat signature, which is tied directly to the user's account
|
||||
* name. In our case, this is the player's name.
|
||||
*
|
||||
* @param sender The console to get the bound for.
|
||||
* @return A ChatType.Bound instance for the console.
|
||||
*/
|
||||
public static ChatType.Bound fromConsole(final ConsoleCommandSender sender)
|
||||
{
|
||||
return type.bind(sender.name());
|
||||
}
|
||||
}
|
@ -0,0 +1,268 @@
|
||||
package fns.patchwork.utils.logging;
|
||||
|
||||
import fns.patchwork.utils.kyori.FreedomAdventure;
|
||||
import java.util.function.Supplier;
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
import net.kyori.adventure.chat.ChatType;
|
||||
import net.kyori.adventure.chat.SignedMessage;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.ComponentLike;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class FreedomLogger implements Audience
|
||||
{
|
||||
private final Logger logger;
|
||||
private boolean debug = false;
|
||||
|
||||
private FreedomLogger(final String moduleName)
|
||||
{
|
||||
this.logger = LoggerFactory.getLogger("FreedomNetworkSuite::" + moduleName);
|
||||
}
|
||||
|
||||
public static FreedomLogger getLogger(final String moduleName)
|
||||
{
|
||||
return new FreedomLogger(moduleName);
|
||||
}
|
||||
|
||||
public void setDebugMode(final boolean debug)
|
||||
{
|
||||
this.debug = debug;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method allows you to log a message to the console, while also returning a Component that could be used to
|
||||
* message a player.
|
||||
*
|
||||
* @param message The message to send.
|
||||
* @return A component representation of the message.
|
||||
*/
|
||||
public Component info(final Supplier<String> message)
|
||||
{
|
||||
logger.info(message.get());
|
||||
return Component.text(message.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* This method allows you to log a component to the console, while also returning a String representation of the
|
||||
* component
|
||||
*
|
||||
* @param component The component to send.
|
||||
* @return A string representation of the message.
|
||||
*/
|
||||
public String infoComponent(final Supplier<Component> component)
|
||||
{
|
||||
return this.infoComponent(component.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* This method allows you to log a component to the console.
|
||||
*
|
||||
* @param component The component to send.
|
||||
* @return A plain text representation of the message
|
||||
*/
|
||||
public String infoComponent(final Component component)
|
||||
{
|
||||
final String plainText = FreedomAdventure.toPlainText(component);
|
||||
|
||||
logger.info(plainText);
|
||||
return plainText;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method allows you to log a message to the console.
|
||||
*
|
||||
* @param message The message to send.
|
||||
*/
|
||||
public void info(final String message)
|
||||
{
|
||||
logger.info(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method allows you to log a warning to the console.
|
||||
*
|
||||
* @param message The message to send.
|
||||
*/
|
||||
public void warn(final String message)
|
||||
{
|
||||
logger.warn(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method allows you to log a warning to the console.
|
||||
*
|
||||
* @param component The component to send.
|
||||
*/
|
||||
public void warnComponent(final Component component)
|
||||
{
|
||||
final String plainText = FreedomAdventure.toPlainText(component);
|
||||
|
||||
logger.warn(plainText);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method logs an error message to the console. It is highly recommended to deconstruct the stack trace and
|
||||
* pass it in a more readable format to this method.
|
||||
*
|
||||
* @param message The message to send.
|
||||
*/
|
||||
public void error(final String message)
|
||||
{
|
||||
logger.error(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method allows you to log an exception directly to the console.
|
||||
*
|
||||
* @param th The exception to log.
|
||||
*/
|
||||
public void error(final Throwable th)
|
||||
{
|
||||
logger.error("An error occurred:\n", th);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method allows you to log an error message to the console, while also returning a Component that could be
|
||||
* used to message a player. It is highly recommended that you deconstruct and limit the stack trace before passing
|
||||
* it to this method, if you are intending to use it for player communication.
|
||||
*
|
||||
* @param message The message to send.
|
||||
* @return A component representation of the message.
|
||||
*/
|
||||
public Component error(final Supplier<String> message)
|
||||
{
|
||||
logger.error(message.get());
|
||||
return Component.text(message.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* This method allows you to log an error component to the console, while also returning a String representation of
|
||||
* the error component.
|
||||
*
|
||||
* @param component The component to send.
|
||||
* @return A String representation of the component.
|
||||
*/
|
||||
public String errorComponent(final Supplier<Component> component)
|
||||
{
|
||||
return this.errorComponent(component.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* This method logs an error component to the console.
|
||||
*
|
||||
* @param component The message to send.
|
||||
*/
|
||||
public String errorComponent(final Component component)
|
||||
{
|
||||
final String plainText = FreedomAdventure.toPlainText(component);
|
||||
|
||||
logger.error(plainText);
|
||||
|
||||
return plainText;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method allows you to log a debug message to the console, while also returning a Component that could be used
|
||||
* to message a player. This method will only log if debug mode is enabled. If debug mode is not enabled, this
|
||||
* method will return an empty component.
|
||||
*
|
||||
* @param message The message to send.
|
||||
* @return A component representation of the message.
|
||||
*/
|
||||
public Component debug(final Supplier<String> message)
|
||||
{
|
||||
if (debug)
|
||||
{
|
||||
logger.debug(message.get());
|
||||
return Component.text(message.get());
|
||||
}
|
||||
return Component.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method allows you to log a debug component to the console, while also returning a String representation of
|
||||
* the debug component.
|
||||
*
|
||||
* @param component The component to send.
|
||||
* @return A String representation of the message.
|
||||
*/
|
||||
public String debugComponent(final Supplier<Component> component)
|
||||
{
|
||||
if (debug)
|
||||
{
|
||||
return this.debugComponent(component.get());
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* This method allows you to log a debug component to the console. This method will only log if debug mode is
|
||||
* enabled.
|
||||
*
|
||||
* @param component The component to send.
|
||||
*/
|
||||
public String debugComponent(final Component component)
|
||||
{
|
||||
final String plainText = FreedomAdventure.toPlainText(component);
|
||||
|
||||
this.debug(plainText);
|
||||
|
||||
return plainText;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method allows you to log a debug message to the console. This method will only log if debug mode is
|
||||
* enabled.
|
||||
*
|
||||
* @param message The message to send.
|
||||
*/
|
||||
public void debug(final String message)
|
||||
{
|
||||
if (debug)
|
||||
logger.debug(message);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void sendMessage(@NotNull final ComponentLike message)
|
||||
{
|
||||
final Component component = ComponentLike.unbox(message);
|
||||
|
||||
if (component == null)
|
||||
{
|
||||
this.info("**null component-like**");
|
||||
return;
|
||||
}
|
||||
|
||||
this.infoComponent(component);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(@NotNull final Component message)
|
||||
{
|
||||
this.infoComponent(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(@NotNull final Component message, final ChatType.@NotNull Bound boundChatType)
|
||||
{
|
||||
this.infoComponent(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(@NotNull final ComponentLike message, final ChatType.@NotNull Bound boundChatType)
|
||||
{
|
||||
this.sendMessage(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(@NotNull final SignedMessage signedMessage, final ChatType.@NotNull Bound boundChatType)
|
||||
{
|
||||
this.info(
|
||||
signedMessage.message()); // TODO: We might want to investigate whether this logs the ENTIRE message,
|
||||
// including unsigned & signed content, or only the signed part. This method was written in the assumption
|
||||
// that it provided all content.
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user