updates 🎱

This commit is contained in:
Paul Reilly 2023-05-30 17:39:54 -05:00
parent b95a06fa7c
commit def84bd747
63 changed files with 1469 additions and 1195 deletions

View File

@ -22,13 +22,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Build with Gradle
uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
with:
arguments: build
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Build with Gradle
uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
with:
arguments: build

View File

@ -6,18 +6,20 @@ import org.bukkit.plugin.java.JavaPlugin;
public class Corvo extends JavaPlugin
{
@Override
public void onEnable() {
public void onDisable()
{
CommonsBase.getInstance()
.getRegistrations()
.getModuleRegistry()
.addModule(this);
.getRegistrations()
.getModuleRegistry()
.removeModule(this);
}
@Override
public void onDisable() {
public void onEnable()
{
CommonsBase.getInstance()
.getRegistrations()
.getModuleRegistry()
.removeModule(this);
.getRegistrations()
.getModuleRegistry()
.addModule(this);
}
}

View File

@ -20,18 +20,40 @@ public class Datura extends JavaPlugin
public void onEnable()
{
CommonsBase.getInstance()
.getRegistrations()
.getModuleRegistry()
.addModule(this);
.getRegistrations()
.getModuleRegistry()
.addModule(this);
CommonsBase.getInstance().getRegistrations().getServiceRegistry().register(this, locker);
CommonsBase.getInstance().getRegistrations().getServiceRegistry().register(this, cager);
CommonsBase.getInstance()
.getRegistrations()
.getServiceRegistry()
.register(this, locker);
CommonsBase.getInstance()
.getRegistrations()
.getServiceRegistry()
.register(this, cager);
Bukkit.getPluginManager().registerEvents(halter, this);
Bukkit.getPluginManager()
.registerEvents(halter, this);
}
public MySQL getSQL()
{
return sql;
}
public Halter getHalter()
{
return halter;
}
public Locker getLocker()
{
return locker;
}
public Cager getCager()
{
return cager;
}
}

View File

@ -1,66 +0,0 @@
package me.totalfreedom.datura.banning;
import me.totalfreedom.security.ban.BanID;
import java.time.Instant;
import java.time.temporal.ChronoField;
public final class BanUID implements BanID
{
private final char prefix;
private final int numericalTag;
private BanUID(final boolean permanent)
{
if (permanent)
{
prefix = 'P';
} else
{
prefix = 'T';
}
final Instant instant = Instant.now();
final String stringBuilder = String.valueOf(instant.get(ChronoField.DAY_OF_YEAR)) + // The first three numbers between 001 -> 365
instant.get(ChronoField.HOUR_OF_DAY) + // next two numbers between 00 -> 23
instant.get(ChronoField.MINUTE_OF_HOUR) + // next two numbers between 00 -> 59
instant.get(ChronoField.MILLI_OF_SECOND); // last three numbers between 000 -> 999
numericalTag = Integer.parseInt(stringBuilder);
}
public static BanUID createTempID()
{
return new BanUID(false);
}
public static BanUID createPermID()
{
return new BanUID(true);
}
@Override
public String getID()
{
return getIDPrefix() + "-" + getNumericalTag();
}
@Override
public char getIDPrefix()
{
return prefix;
}
@Override
public int getNumericalTag()
{
return numericalTag;
}
@Override
public boolean isPermanent()
{
return getIDPrefix() == 'P';
}
}

View File

@ -1,82 +0,0 @@
package me.totalfreedom.datura.banning;
import me.totalfreedom.security.ban.Ban;
import me.totalfreedom.security.ban.BanID;
import org.jetbrains.annotations.Nullable;
import java.time.Instant;
import java.util.UUID;
public final class SimpleBan implements Ban
{
private final BanID id;
private final UUID offenderID;
private final String reason;
private final String issuer;
private final Instant creationTime;
private final Instant expiry;
public SimpleBan(
final UUID offenderID,
final String reason,
final String issuer,
final Instant creationTime,
final Instant expiry)
{
if (expiry == null)
{
this.id = BanUID.createPermID();
} else
{
this.id = BanUID.createTempID();
}
this.offenderID = offenderID;
this.reason = reason;
this.issuer = issuer;
this.creationTime = creationTime;
this.expiry = expiry;
}
@Override
public BanID getBanID()
{
return id;
}
@Override
public UUID getOffenderID()
{
return offenderID;
}
@Override
public String getReason()
{
return reason;
}
@Override
public String getBanIssuer()
{
return issuer;
}
@Override
public Instant getCreationTime()
{
return creationTime;
}
@Override
public @Nullable Instant getExpiry()
{
return expiry;
}
@Override
public boolean isExpired()
{
return Instant.now().compareTo(expiry) >= 0;
}
}

View File

@ -1,26 +0,0 @@
package me.totalfreedom.datura.cmd;
import me.totalfreedom.command.CommandBase;
import me.totalfreedom.command.annotation.Completion;
import me.totalfreedom.command.annotation.Info;
import me.totalfreedom.command.annotation.Permissive;
import me.totalfreedom.command.annotation.Subcommand;
import me.totalfreedom.datura.Datura;
import org.bukkit.entity.Player;
@Completion(args = {"%player%"}, index = 0)
@Info(name = "kick", description = "Kick a player from the server.", usage = "/kick <player>")
@Permissive(perm = "datura.kick")
public class KickCommand extends CommandBase
{
protected KickCommand(final Datura plugin)
{
super(plugin);
}
@Subcommand(permission = "datura.kick", args = {Player.class})
public void kick(final Player player)
{
player.kickPlayer("You have been kicked from the server.");
}
}

View File

@ -0,0 +1,41 @@
package me.totalfreedom.datura.cmd;
import me.totalfreedom.command.Commander;
import me.totalfreedom.command.annotation.Completion;
import me.totalfreedom.command.annotation.Info;
import me.totalfreedom.command.annotation.Permissive;
import me.totalfreedom.command.annotation.Subcommand;
import me.totalfreedom.datura.Datura;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
@Info(name = "locker", description = "Lock a player, preventing them from interacting with their game client.",
usage = "/locker <player> <on|off>", aliases = {"lock", "lockup"})
@Permissive(perm = "datura.locker")
public final class LockerCommand extends Commander
{
public LockerCommand(final @NotNull Datura plugin)
{
super(plugin);
}
@Completion(args = {"%player%"}, index = 0)
@Completion(args = {"on", "off"}, index = 1)
@Subcommand(permission = "datura.locker", args = {Player.class, String.class})
public void lockPlayer(final CommandSender sender, final Player player, final String string)
{
if (string.equalsIgnoreCase("on"))
{
((Datura) getPlugin()).getLocker()
.lock(player);
sender.sendPlainMessage("Locked " + player.getName() + ".");
} else if (string.equalsIgnoreCase("off"))
{
((Datura) getPlugin()).getLocker()
.unlock(player);
sender.sendPlainMessage("Unlocked " + player.getName() + ".");
}
}
}

View File

@ -5,39 +5,37 @@ import me.totalfreedom.security.perm.NodeType;
public class DefaultNodes
{
private DefaultNodes() {
public static final Node OP = new PermissionNodeBuilder()
.key("freedom.master_key")
.value(true)
.type(NodeType.PERMISSION)
.negated(false)
.wildcard(true)
.build();
public static final Node NON_OP = new PermissionNodeBuilder()
.key("freedom.default")
.value(true)
.type(NodeType.PERMISSION)
.negated(false)
.wildcard(false)
.build();
public static final Node ALL = new PermissionNodeBuilder()
.key("*")
.value(true)
.type(NodeType.PERMISSION)
.negated(false)
.wildcard(true)
.build();
public static final Node NONE = new PermissionNodeBuilder()
.key("freedom.none")
.value(true)
.type(NodeType.PERMISSION)
.negated(false)
.wildcard(false)
.build();
private DefaultNodes()
{
throw new AssertionError();
}
public static final Node OP = new PermissionNodeBuilder()
.key("freedom.master_key")
.value(true)
.type(NodeType.PERMISSION)
.negated(false)
.wildcard(true)
.build();
public static final Node NON_OP = new PermissionNodeBuilder()
.key("freedom.default")
.value(true)
.type(NodeType.PERMISSION)
.negated(false)
.wildcard(false)
.build();
public static final Node ALL = new PermissionNodeBuilder()
.key("*")
.value(true)
.type(NodeType.PERMISSION)
.negated(false)
.wildcard(true)
.build();
public static final Node NONE = new PermissionNodeBuilder()
.key("freedom.none")
.value(true)
.type(NodeType.PERMISSION)
.negated(false)
.wildcard(false)
.build();
}

View File

@ -28,11 +28,11 @@ public class FreedomGroup implements Group
private final PermissionAttachment attachment;
public FreedomGroup(final Component name,
final Component prefix,
final Component abbreviation,
final int weight,
final boolean isDefault,
final boolean isHidden)
final Component prefix,
final Component abbreviation,
final int weight,
final boolean isDefault,
final boolean isHidden)
{
this.name = name;
this.prefix = prefix;
@ -44,6 +44,12 @@ public class FreedomGroup implements Group
this.attachment = new PermissionAttachment(CommonsBase.getInstance(), this);
}
@Override
public UUID getUniqueId()
{
return UUID.nameUUIDFromBytes(getName().toString()
.getBytes());
}
@Override
public Component getName()
@ -81,12 +87,6 @@ public class FreedomGroup implements Group
return isHidden;
}
@Override
public UUID getUniqueId()
{
return UUID.nameUUIDFromBytes(getName().toString().getBytes());
}
@Override
public Set<Node> permissions()
{
@ -109,9 +109,10 @@ public class FreedomGroup implements Group
public boolean isPermissionSet(@NotNull final String name)
{
final Node node = permissions().stream()
.filter(n -> n.key().equalsIgnoreCase(name))
.findFirst()
.orElse(null);
.filter(n -> n.key()
.equalsIgnoreCase(name))
.findFirst()
.orElse(null);
return node != null && node.value();
}
@ -120,10 +121,11 @@ public class FreedomGroup implements Group
public boolean isPermissionSet(@NotNull final Permission perm)
{
final Node node = permissions()
.stream()
.filter(n -> n.bukkit().equals(perm))
.findFirst()
.orElse(null);
.stream()
.filter(n -> n.bukkit()
.equals(perm))
.findFirst()
.orElse(null);
return node != null && node.value();
}
@ -132,9 +134,10 @@ public class FreedomGroup implements Group
public boolean hasPermission(@NotNull final String name)
{
final Node node = permissions().stream()
.filter(n -> n.key().equalsIgnoreCase(name))
.findFirst()
.orElse(null);
.filter(n -> n.key()
.equalsIgnoreCase(name))
.findFirst()
.orElse(null);
return node != null && node.value();
}
@ -143,10 +146,11 @@ public class FreedomGroup implements Group
public boolean hasPermission(@NotNull final Permission perm)
{
final Node node = permissions()
.stream()
.filter(n -> n.bukkit().equals(perm))
.findFirst()
.orElse(null);
.stream()
.filter(n -> n.bukkit()
.equals(perm))
.findFirst()
.orElse(null);
return node != null && node.value();
}
@ -164,7 +168,8 @@ public class FreedomGroup implements Group
* @return This group's PermissionAttachment.
*/
@Override
public @NotNull PermissionAttachment addAttachment(@NotNull final Plugin plugin, @NotNull final String name, final boolean value)
public @NotNull PermissionAttachment addAttachment(@NotNull final Plugin plugin, @NotNull final String name,
final boolean value)
{
attachment.setPermission(name, value);
return attachment;
@ -177,7 +182,8 @@ public class FreedomGroup implements Group
}
@Override
public @Nullable PermissionAttachment addAttachment(@NotNull final Plugin plugin, @NotNull final String name, final boolean value, final int ticks)
public @Nullable PermissionAttachment addAttachment(@NotNull final Plugin plugin, @NotNull final String name,
final boolean value, final int ticks)
{
attachment.setPermission(name, value);
return attachment;
@ -205,23 +211,23 @@ public class FreedomGroup implements Group
public @NotNull Set<PermissionAttachmentInfo> getEffectivePermissions()
{
return permissions()
.stream()
.map(n -> new PermissionAttachmentInfo(
this,
n.key(),
attachment,
n.value()))
.collect(Collectors.toSet());
.stream()
.map(n -> new PermissionAttachmentInfo(
this,
n.key(),
attachment,
n.value()))
.collect(Collectors.toSet());
}
@Override
public boolean isOp()
{
final Node node = permissions()
.stream()
.filter(n -> n.equals(DefaultNodes.OP))
.findFirst()
.orElse(null);
.stream()
.filter(n -> n.equals(DefaultNodes.OP))
.findFirst()
.orElse(null);
return node != null && node.value();
}

View File

@ -43,10 +43,10 @@ public class FreedomUser implements User
this.displayName = player.displayName();
final Datura datura = CommonsBase.getInstance()
.getRegistrations()
.getModuleRegistry()
.getModule(Datura.class)
.getModule();
.getRegistrations()
.getModuleRegistry()
.getModule(Datura.class)
.getModule();
UserData data = SimpleUserData.fromSQL(datura.getSQL(), uuid.toString());
@ -58,16 +58,29 @@ public class FreedomUser implements User
this.userData = data;
CommonsBase.getInstance()
.getRegistrations()
.getUserRegistry()
.registerUserData(this, userData);
.getRegistrations()
.getUserRegistry()
.registerUserData(this, userData);
}
@Override
public UserData getUserData() {
public UserData getUserData()
{
return userData;
}
@Override
public Component getDisplayName()
{
return displayName;
}
@Override
public boolean isOnline()
{
return Bukkit.getPlayer(uuid) != null;
}
@Override
public UUID getUniqueId()
{
@ -96,18 +109,6 @@ public class FreedomUser implements User
return permissions.remove(node);
}
@Override
public Component getDisplayName()
{
return displayName;
}
@Override
public boolean isOnline()
{
return Bukkit.getPlayer(uuid) != null;
}
@Override
public boolean isPermissionSet(@NotNull final String name)
{
@ -137,7 +138,8 @@ public class FreedomUser implements User
}
@Override
public @NotNull PermissionAttachment addAttachment(@NotNull final Plugin plugin, @NotNull final String name, final boolean value)
public @NotNull PermissionAttachment addAttachment(@NotNull final Plugin plugin, @NotNull final String name,
final boolean value)
{
final Player player = Bukkit.getPlayer(uuid);
if (player != null)
@ -161,7 +163,8 @@ public class FreedomUser implements User
}
@Override
public @Nullable PermissionAttachment addAttachment(@NotNull final Plugin plugin, @NotNull final String name, final boolean value, final int ticks)
public @Nullable PermissionAttachment addAttachment(@NotNull final Plugin plugin, @NotNull final String name,
final boolean value, final int ticks)
{
final Player player = Bukkit.getPlayer(uuid);
if (player != null)

View File

@ -9,29 +9,31 @@ record PermissionNode(String key,
boolean value,
long expiry,
NodeType type,
boolean wildcard,
boolean negated) implements Node
boolean wildcard) implements Node
{
@Override
public Permission bukkit()
{
return new Permission(key(),
value() ? PermissionDefault.TRUE : PermissionDefault.FALSE);
value()
? PermissionDefault.TRUE
: PermissionDefault.FALSE);
}
@Override
public boolean compare(final Node node)
{
return node.key().equalsIgnoreCase(key())
&& node.value() == value()
&& node.type() == type();
return node.key()
.equalsIgnoreCase(key())
&& node.value() == value()
&& node.type() == type();
}
@Override
public boolean isExpired()
{
if (isPermanent())
if (!isTemporary())
{
return false;
}
@ -39,15 +41,9 @@ record PermissionNode(String key,
return System.currentTimeMillis() > expiry();
}
@Override
public boolean isPermanent()
{
return expiry() == -1;
}
@Override
public boolean isTemporary()
{
return !isPermanent();
return expiry() > -1;
}
}

View File

@ -14,8 +14,8 @@ import org.bukkit.event.player.PlayerQuitEvent;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
@ -31,7 +31,8 @@ public class Cager extends Service
super("cage_service");
this.cagedPlayers = new HashSet<>();
this.cageLocations = new HashMap<>();
Bukkit.getPluginManager().registerEvents(new CageListener(), CommonsBase.getInstance());
Bukkit.getPluginManager()
.registerEvents(new CageListener(), CommonsBase.getInstance());
}
/**
@ -49,6 +50,39 @@ public class Cager extends Service
cageLocations.put(uuid, createCage(player.getLocation(), Material.GLASS));
}
/**
* This method generates a cube centered around the passed location,
* made of the provided material. This method returns the passed location object.
* We use the {@link Shaper} class to generate the cube, which allows us to define
* custom shapes using {@link DoubleUnaryOperator}s.
*
* @param location The location to center the cube around.
* @param material The material to use for the cube.
* @return The center location of the cube (the passed location).
* @see Shaper
* @see DoubleUnaryOperator
*/
public Location createCage(final Location location, final Material material)
{
final Shaper shaper = new Shaper(location.getWorld(), 0.0, 4.0);
final List<Location> cubed = new LinkedList<>();
cubed.addAll(shaper.generate(5, t -> t, t -> 4.0, t -> t));
cubed.addAll(shaper.generate(5, t -> t, t -> 0.0, t -> t));
cubed.addAll(shaper.generate(5, t -> 0.0, t -> t, t -> t));
cubed.addAll(shaper.generate(5, t -> 4.0, t -> t, t -> t));
cubed.addAll(shaper.generate(5, t -> t, t -> t, t -> 0.0));
cubed.addAll(shaper.generate(5, t -> t, t -> t, t -> 4.0));
for (final Location l : cubed)
{
location.getWorld()
.getBlockAt(l)
.setType(material);
}
return location.clone(); // Return the passed location as that is the center of the cube.
}
public void cagePlayer(final UUID uuid, final Material material)
{
final Player player = Bukkit.getPlayer(uuid);
@ -92,12 +126,14 @@ public class Cager extends Service
final Location cageLocation = getCageLocation(player);
final boolean inside;
if (!player.getWorld().equals(cageLocation.getWorld()))
if (!player.getWorld()
.equals(cageLocation.getWorld()))
{
inside = false;
} else
{
inside = player.getLocation().distanceSquared(cageLocation) > (Math.pow(2.5, 2.0));
inside = player.getLocation()
.distanceSquared(cageLocation) > (Math.pow(2.5, 2.0));
}
if (!inside)
@ -119,43 +155,13 @@ public class Cager extends Service
return cageLocations.get(player.getUniqueId());
}
/**
* This method generates a cube centered around the passed location,
* made of the provided material. This method returns the passed location object.
* We use the {@link Shaper} class to generate the cube, which allows us to define
* custom shapes using {@link DoubleUnaryOperator}s.
*
* @param location The location to center the cube around.
* @param material The material to use for the cube.
* @return The center location of the cube (the passed location).
* @see Shaper
* @see DoubleUnaryOperator
*/
public Location createCage(final Location location, final Material material)
{
final Shaper shaper = new Shaper(location.getWorld(), 0.0, 4.0);
final List<Location> cubed = new LinkedList<>();
cubed.addAll(shaper.generate(5, t -> t, t -> 4.0, t -> t));
cubed.addAll(shaper.generate(5, t -> t, t -> 0.0, t -> t));
cubed.addAll(shaper.generate(5, t -> 0.0, t -> t, t -> t));
cubed.addAll(shaper.generate(5, t -> 4.0, t -> t, t -> t));
cubed.addAll(shaper.generate(5, t -> t, t -> t, t -> 0.0));
cubed.addAll(shaper.generate(5, t -> t, t -> t, t -> 4.0));
for (final Location l : cubed)
{
location.getWorld().getBlockAt(l).setType(material);
}
return location.clone(); // Return the passed location as that is the center of the cube.
}
private final class CageListener implements Listener
{
@EventHandler
public void blockBreakEvent(final BlockBreakEvent event)
{
if (cagedPlayers.contains(event.getPlayer().getUniqueId()))
if (cagedPlayers.contains(event.getPlayer()
.getUniqueId()))
{
event.setCancelled(true);
}
@ -164,9 +170,11 @@ public class Cager extends Service
@EventHandler
public void playerLeaveEvent(final PlayerQuitEvent event)
{
if (cagedPlayers.contains(event.getPlayer().getUniqueId()))
if (cagedPlayers.contains(event.getPlayer()
.getUniqueId()))
{
uncagePlayer(event.getPlayer().getUniqueId());
uncagePlayer(event.getPlayer()
.getUniqueId());
}
}
}

View File

@ -25,7 +25,8 @@ public class Halter implements Listener
@EventHandler
public void playerMove(final PlayerMoveEvent event)
{
if (haltedPlayers.contains(event.getPlayer().getUniqueId()))
if (haltedPlayers.contains(event.getPlayer()
.getUniqueId()))
{
event.setCancelled(true);
}

View File

@ -22,15 +22,23 @@ public class Locker extends Service
super("locker-service");
}
public void lock(final UUID uuid)
public void lock(final Player player)
{
lockedPlayers.add(uuid);
lockedPlayers.add(player.getUniqueId());
}
public void unlock(final Player player)
{
lockedPlayers.remove(player.getUniqueId());
}
@Override
public void tick()
{
lockedPlayers.removeIf(uuid -> !CommonsBase.getInstance().getServer().getOfflinePlayer(uuid).isOnline());
lockedPlayers.removeIf(uuid -> !CommonsBase.getInstance()
.getServer()
.getOfflinePlayer(uuid)
.isOnline());
for (final UUID uuid : lockedPlayers)
{
@ -43,8 +51,10 @@ public class Locker extends Service
private void lockingMethod(@NotNull final Player player)
{
final double x = player.getLocation().getX();
final double z = player.getLocation().getZ();
final double x = player.getLocation()
.getX();
final double z = player.getLocation()
.getZ();
if ((x / z % 0.001) < 1)
{
@ -65,13 +75,15 @@ public class Locker extends Service
player.openInventory(Bukkit.createInventory(null, 54));
player.closeInventory(InventoryCloseEvent.Reason.UNKNOWN);
player.teleport(player.getLocation().clone());
player.teleport(player.getLocation()
.clone());
final SplittableRandom random = new SplittableRandom();
player.getEyeLocation().add(new Vector(
random.nextDouble(-1.0, 1.0),
random.nextDouble(-1.0, 1.0),
random.nextDouble(-1.0, 1.0)
));
player.getEyeLocation()
.add(new Vector(
random.nextDouble(-1.0, 1.0),
random.nextDouble(-1.0, 1.0),
random.nextDouble(-1.0, 1.0)
));
}
}

View File

@ -1,89 +0,0 @@
package me.totalfreedom.datura.sql;
import me.totalfreedom.base.CommonsBase;
import me.totalfreedom.datura.banning.SimpleBan;
import me.totalfreedom.security.ban.Ban;
import me.totalfreedom.security.ban.BanID;
import me.totalfreedom.sql.SQL;
import me.totalfreedom.utils.FreedomLogger;
import java.sql.SQLException;
import java.time.Instant;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
public class DBBan
{
private final SQL sql;
public DBBan(final SQL sql)
{
this.sql = sql;
}
public CompletableFuture<Ban> fromSQL(final BanID id)
{
return sql.executeQuery("SELECT * FROM bans WHERE id = ?", id.getID())
.thenApplyAsync(result ->
{
try
{
if (result.next())
{
final UUID uuid = UUID.fromString(result.getString("uuid"));
final Instant timestamp = Instant.parse(result.getString("timestamp"));
final Instant expiry;
final String ex = result.getString("expiry");
if (ex.equals("-1"))
{
expiry = null;
} else
{
expiry = Instant.parse(ex);
}
return new SimpleBan(uuid,
result.getString("reason"),
result.getString("issuer"),
timestamp,
expiry);
}
} catch (SQLException e)
{
FreedomLogger.getLogger("Datura")
.error(e.getMessage());
}
return null;
}, CommonsBase.getInstance().getExecutor().getAsync());
}
public void addBan(final Ban ban)
{
sql.executeUpdate("INSERT INTO bans (id, uuid, reason, issuer, timestamp, expiry) VALUES (?, ?, ?, ?, ?, ?)",
ban.getBanID().getID(),
ban.getOffenderID().toString(),
ban.getReason(),
ban.getBanIssuer(),
ban.getCreationTime().toString(),
(ban.getExpiry() != null ? ban.getExpiry().toString() : "-1")
);
}
public boolean hasEntry(final UUID uuid) {
return sql.executeQuery("SELECT * FROM bans WHERE uuid = ?", uuid.toString())
.thenApplyAsync(result ->
{
try
{
return result.next();
} catch (SQLException e)
{
FreedomLogger.getLogger("Datura")
.error(e.getMessage());
}
return false;
}, CommonsBase.getInstance().getExecutor().getAsync())
.join();
}
}

View File

@ -2,6 +2,7 @@ package me.totalfreedom.datura.sql;
import me.totalfreedom.base.CommonsBase;
import me.totalfreedom.sql.SQL;
import me.totalfreedom.utils.Identity;
import java.sql.Connection;
import java.sql.DriverManager;
@ -13,10 +14,18 @@ import java.util.concurrent.CompletionException;
public class MySQL implements SQL
{
private String url = "jdbc:mysql://";
/**
* Using StringBuilder for finality.
*/
private final StringBuilder url = new StringBuilder("jdbc:mysql://");
public MySQL(final String host, final int port, final String database) {
url += host + ":" + port + "/" + database;
public MySQL(final String host, final int port, final String database)
{
url.append(host)
.append(':')
.append(port)
.append('/')
.append(database);
}
/**
@ -26,85 +35,130 @@ public class MySQL implements SQL
* @param username The username to add
* @param password The password to add
*/
public void addCredentials(final String username, final String password) {
if (url.contains("?user=")) {
url = url.split("\\x3f")[0];
public void addCredentials(final String username, final String password)
{
if (url.toString()
.contains("?user="))
{
final String split = url.toString()
.split("\\x3f")[0];
url.setLength(0);
url.append(split);
}
url += "?user=" + username + "&password=" + password;
url.append("?user=")
.append(username)
.append("&password=")
.append(password);
}
@Override
public CompletableFuture<Connection> getConnection(final String url)
public CompletableFuture<ResultSet> getRow(final String table, final String column, final Identity identity)
{
return CompletableFuture.supplyAsync(() -> {
try {
return DriverManager.getConnection(url);
} catch (SQLException ex) {
throw new CompletionException("Failed to connect to the database: "
+ url + "\n", ex);
}
}, CommonsBase.getInstance().getExecutor().getAsync());
return executeQuery("SELECT * FROM ? WHERE ? = ?", table, column, identity.getId());
}
@Override
public CompletableFuture<PreparedStatement> prepareStatement(final String query, final Object... args)
{
return getConnection(url)
.thenApplyAsync(connection -> {
try {
final PreparedStatement statement = connection.prepareStatement(query);
for (int i = 0; i < args.length; i++) {
statement.setObject(i + 1, args[i]);
}
return statement;
} catch (SQLException ex) {
throw new CompletionException("Failed to prepare statement: "
+ query + "\n", ex);
}
}, CommonsBase.getInstance().getExecutor().getAsync());
return getConnection()
.thenApplyAsync(connection ->
{
try
{
final PreparedStatement statement = connection.prepareStatement(query);
for (int i = 0; i < args.length; i++)
{
statement.setObject(i + 1, args[i]);
}
return statement;
}
catch (SQLException ex)
{
throw new CompletionException("Failed to prepare statement: "
+ query + "\n", ex);
}
}, CommonsBase.getInstance()
.getExecutor()
.getAsync());
}
private CompletableFuture<Connection> getConnection()
{
return CompletableFuture.supplyAsync(() ->
{
try
{
return DriverManager.getConnection(url.toString());
}
catch (SQLException ex)
{
throw new CompletionException("Failed to connect to the database: "
+ url.toString() + "\n", ex);
}
}, CommonsBase.getInstance()
.getExecutor()
.getAsync());
}
@Override
public CompletableFuture<ResultSet> executeQuery(final String query, final Object... args)
{
return prepareStatement(query, args)
.thenApplyAsync(statement -> {
try {
return statement.executeQuery();
} catch (SQLException ex) {
throw new CompletionException("Failed to retrieve a result set from query: "
+ query + "\n", ex);
}
}, CommonsBase.getInstance().getExecutor().getAsync());
.thenApplyAsync(statement ->
{
try
{
return statement.executeQuery();
}
catch (SQLException ex)
{
throw new CompletionException(
"Failed to retrieve a result set from query: "
+ query + "\n", ex);
}
}, CommonsBase.getInstance()
.getExecutor()
.getAsync());
}
@Override
public CompletableFuture<Integer> executeUpdate(final String query, final Object... args)
{
return prepareStatement(query, args)
.thenApplyAsync(statement -> {
try {
return statement.executeUpdate();
} catch (SQLException ex) {
throw new CompletionException("Failed to execute update: "
+ query + "\n", ex);
}
}, CommonsBase.getInstance().getExecutor().getAsync());
.thenApplyAsync(statement ->
{
try
{
return statement.executeUpdate();
}
catch (SQLException ex)
{
throw new CompletionException("Failed to execute update: "
+ query + "\n", ex);
}
}, CommonsBase.getInstance()
.getExecutor()
.getAsync());
}
@Override
public CompletableFuture<Boolean> execute(final String query, final Object... args)
{
return prepareStatement(query, args)
.thenApplyAsync(statement -> {
try {
return statement.execute();
} catch (SQLException ex) {
throw new CompletionException("Failed to execute statement: "
+ query + "\n", ex);
}
}, CommonsBase.getInstance().getExecutor().getAsync());
.thenApplyAsync(statement ->
{
try
{
return statement.execute();
}
catch (SQLException ex)
{
throw new CompletionException("Failed to execute statement: "
+ query + "\n", ex);
}
}, CommonsBase.getInstance()
.getExecutor()
.getAsync());
}
@Override
@ -113,9 +167,11 @@ public class MySQL implements SQL
final StringBuilder query = new StringBuilder();
query.append("CREATE TABLE IF NOT EXISTS ? (");
for (int i = 0; i < columns.length; i++) {
for (int i = 0; i < columns.length; i++)
{
query.append("?");
if (i != columns.length - 1) {
if (i != columns.length - 1)
{
query.append(", ");
}
}
@ -123,4 +179,90 @@ public class MySQL implements SQL
return execute(query.toString(), table, columns);
}
public <T> CompletableFuture<T> getColumn(final String table, final String column, final String key,
final Identity identity, final Class<T> type)
{
return executeQuery("SELECT ? FROM ? WHERE ? = ?", column, table, key, identity.getId())
.thenApplyAsync(resultSet ->
{
try
{
if (resultSet.next())
{
return resultSet.getObject(column, type);
}
}
catch (SQLException ex)
{
throw new CompletionException(
"Failed to retrieve column: " + column + " from table: " + table + " " +
"where primary key: " + key + " is equal to: " + identity.getId() + "\n",
ex);
}
return null;
}, CommonsBase.getInstance()
.getExecutor()
.getAsync());
}
public CompletableFuture<Boolean> updateColumn(final String table, final String column, final Object value,
final String key, final Identity identity)
{
return executeUpdate("UPDATE ? SET ? = ? WHERE ? = ?", table, column, value, key, identity.getId())
.thenApplyAsync(result -> result > 0, CommonsBase.getInstance()
.getExecutor()
.getAsync());
}
public CompletableFuture<Boolean> deleteRow(final String table, final String key, final Identity identity)
{
return executeUpdate("DELETE FROM ? WHERE ? = ?", table, key, identity.getId())
.thenApplyAsync(result -> result > 0, CommonsBase.getInstance()
.getExecutor()
.getAsync());
}
public CompletableFuture<Boolean> insertRow(final String table, final Object... values)
{
final StringBuilder query = new StringBuilder();
query.append("INSERT INTO ? VALUES (");
for (int i = 0; i < values.length; i++)
{
query.append("?");
if (i != values.length - 1)
{
query.append(", ");
}
}
query.append(")");
return execute(query.toString(), table, values);
}
public CompletableFuture<Boolean> insertRow(final String table, final String[] columns, final Object... values)
{
final StringBuilder query = new StringBuilder();
query.append("INSERT INTO ? (");
for (int i = 0; i < columns.length; i++)
{
query.append("?");
if (i != columns.length - 1)
{
query.append(", ");
}
}
query.append(") VALUES (");
for (int i = 0; i < values.length; i++)
{
query.append("?");
if (i != values.length - 1)
{
query.append(", ");
}
}
query.append(")");
return execute(query.toString(), table, columns, values);
}
}

View File

@ -0,0 +1,14 @@
package me.totalfreedom.datura.sql;
import com.google.errorprone.annotations.Immutable;
/**
* Represents a single result from a result set.
*/
@Immutable
public record Result(String name, Object value)
{
}

View File

@ -38,20 +38,22 @@ public class SimpleUserData implements UserData
this.username = player.getName();
this.user = new FreedomUser(player);
CommonsBase.getInstance().getEventBus().addEvent(event);
CommonsBase.getInstance()
.getEventBus()
.addEvent(event);
}
private SimpleUserData(
final UUID uuid,
final String username,
final User user,
final Group group,
final long playtime,
final boolean frozen,
final boolean canInteract,
final boolean caged,
final long balance,
final boolean transactionsFrozen)
final UUID uuid,
final String username,
final User user,
final Group group,
final long playtime,
final boolean frozen,
final boolean canInteract,
final boolean caged,
final long balance,
final boolean transactionsFrozen)
{
this.uuid = uuid;
this.username = username;
@ -68,57 +70,64 @@ public class SimpleUserData implements UserData
public static SimpleUserData fromSQL(final SQL sql, final String uuid)
{
return sql.executeQuery("SELECT * FROM users WHERE UUID = ?", uuid)
.thenApplyAsync(result ->
{
try
{
if (result.next())
{
final String g = result.getString("group");
.thenApplyAsync(result ->
{
try
{
if (result.next())
{
final String g = result.getString("group");
final UUID u = UUID.fromString(uuid);
final String username = result.getString("username");
final UUID u = UUID.fromString(uuid);
final String username = result.getString("username");
final Player player = Bukkit.getPlayer(u);
final Player player = Bukkit.getPlayer(u);
if (player == null)
throw new IllegalStateException("Player should be online but they are not!");
if (player == null)
throw new IllegalStateException(
"Player should be online but they are not!");
final User user = new FreedomUser(player);
final Group group = CommonsBase.getInstance()
.getRegistrations()
.getGroupRegistry()
.getGroup(g);
final User user = new FreedomUser(player);
final Group group = CommonsBase.getInstance()
.getRegistrations()
.getGroupRegistry()
.getGroup(g);
final long playtime = result.getLong("playtime");
final boolean frozen = result.getBoolean("frozen");
final boolean canInteract = result.getBoolean("canInteract");
final boolean caged = result.getBoolean("caged");
final long balance = result.getLong("balance");
final boolean transactionsFrozen = result.getBoolean("transactionsFrozen");
return new SimpleUserData(u, username, user, group, playtime, frozen, canInteract, caged, balance, transactionsFrozen);
}
} catch (SQLException ex)
{
final String sb = "An error occurred while trying to retrieve user data for UUID " +
uuid +
" from the database." +
"\nCaused by: " +
ExceptionUtils.getRootCauseMessage(ex) +
"\nStack trace: " +
ExceptionUtils.getStackTrace(ex);
final long playtime = result.getLong("playtime");
final boolean frozen = result.getBoolean("frozen");
final boolean canInteract = result.getBoolean("canInteract");
final boolean caged = result.getBoolean("caged");
final long balance = result.getLong("balance");
final boolean transactionsFrozen = result.getBoolean(
"transactionsFrozen");
return new SimpleUserData(u, username, user, group, playtime, frozen,
canInteract, caged, balance,
transactionsFrozen);
}
}
catch (SQLException ex)
{
final String sb = "An error occurred while trying to retrieve user data for" +
" UUID " +
uuid +
" from the database." +
"\nCaused by: " +
ExceptionUtils.getRootCauseMessage(ex) +
"\nStack trace: " +
ExceptionUtils.getStackTrace(ex);
FreedomLogger.getLogger("Datura")
.error(sb);
}
FreedomLogger.getLogger("Datura")
.error(sb);
}
final Player player = Bukkit.getPlayer(UUID.fromString(uuid));
if (player == null) throw new IllegalStateException("Player should be online but they are not!");
return new SimpleUserData(player);
}, CommonsBase.getInstance()
.getExecutor()
.getAsync())
.join();
final Player player = Bukkit.getPlayer(UUID.fromString(uuid));
if (player == null) throw new IllegalStateException(
"Player should be online but they are not!");
return new SimpleUserData(player);
}, CommonsBase.getInstance()
.getExecutor()
.getAsync())
.join();
}
@Override
@ -230,6 +239,12 @@ public class SimpleUserData implements UserData
return balance.get();
}
@Override
public void setBalance(final long newBalance)
{
balance.set(newBalance);
}
@Override
public long addToBalance(final long amount)
{
@ -241,10 +256,4 @@ public class SimpleUserData implements UserData
{
return balance.addAndGet(-amount);
}
@Override
public void setBalance(final long newBalance)
{
balance.set(newBalance);
}
}

View File

@ -9,8 +9,8 @@ public class Fossil extends JavaPlugin
public void onEnable()
{
CommonsBase.getInstance()
.getRegistrations()
.getModuleRegistry()
.addModule(this);
.getRegistrations()
.getModuleRegistry()
.addModule(this);
}
}

View File

@ -7,9 +7,9 @@ import java.util.concurrent.atomic.AtomicLong;
public class SimpleTransaction implements Transaction
{
protected final AtomicLong balance;
private final EconomicEntity source;
private final EconomicEntity destination;
protected final AtomicLong balance;
public SimpleTransaction(final EconomicEntity source, final EconomicEntity destination, final long balance)
{

View File

@ -1,10 +1,10 @@
package me.totalfreedom.fossil.economy;
import me.totalfreedom.audience.MutableAudienceForwarder;
import me.totalfreedom.economy.TransactionResult;
import me.totalfreedom.economy.CompletedTransaction;
import me.totalfreedom.economy.TransactionLogger;
import me.totalfreedom.economy.EconomicEntity;
import me.totalfreedom.economy.TransactionLogger;
import me.totalfreedom.economy.TransactionResult;
import me.totalfreedom.utils.FreedomLogger;
import net.kyori.adventure.text.Component;
@ -24,17 +24,19 @@ public class SimpleTransactionLogger implements TransactionLogger
final EconomicEntity destination = completedTransaction.getDestination();
final long transactionAmount = completedTransaction.getBalance();
transactionLoggingStatementBuilder.append(resultSuccess ? "Successful" : "Unsuccessful")
.append(" (")
.append(resultMessage)
.append(") ")
.append(" transaction between ")
.append(source.getName())
.append(" ")
.append(destination.getName())
.append(" where the volume of currency transferred was $")
.append(transactionAmount)
.append(".");
transactionLoggingStatementBuilder.append(resultSuccess
? "Successful"
: "Unsuccessful")
.append(" (")
.append(resultMessage)
.append(") ")
.append(" transaction between ")
.append(source.getName())
.append(" ")
.append(destination.getName())
.append(" where the volume of currency transferred was $")
.append(transactionAmount)
.append(".");
final Component message = Component.text(transactionLoggingStatementBuilder.toString());

View File

@ -7,16 +7,21 @@ import net.kyori.adventure.text.format.NamedTextColor;
public class SimpleTransactionResult implements TransactionResult
{
public static final TransactionResult SUCCESSFUL = new SimpleTransactionResult("Successful transaction.", true);
public static final TransactionResult UNAUTHORIZED = new SimpleTransactionResult("Unauthorized transaction.", false);
public static final TransactionResult AMOUNT_TOO_SMALL = new SimpleTransactionResult("Transaction balance too small.", false);
public static final TransactionResult INSUFFICIENT_FUNDS = new SimpleTransactionResult("The source has an insufficient balance to carry out this transaction.", false);
public static final TransactionResult UNAUTHORIZED = new SimpleTransactionResult("Unauthorized transaction.",
false);
public static final TransactionResult AMOUNT_TOO_SMALL = new SimpleTransactionResult(
"Transaction balance too small.", false);
public static final TransactionResult INSUFFICIENT_FUNDS = new SimpleTransactionResult(
"The source has an insufficient balance to carry out this transaction.", false);
private final String message;
private final Component component;
private final boolean successful;
public SimpleTransactionResult(final String message, final boolean successful)
{
this(message, Component.text(message, successful ? NamedTextColor.GREEN : NamedTextColor.RED), successful);
this(message, Component.text(message, successful
? NamedTextColor.GREEN
: NamedTextColor.RED), successful);
}
public SimpleTransactionResult(final String message, final Component component, final boolean successful)

View File

@ -0,0 +1,5 @@
package me.totalfreedom.fossil.shop;
public class Shoppe
{
}

View File

@ -0,0 +1,11 @@
package me.totalfreedom.fossil.shop.menus;
import me.totalfreedom.display.AbstractMenu;
public final class MainMenu extends AbstractMenu
{
protected MainMenu(final int size)
{
super(size);
}
}

View File

@ -16,13 +16,13 @@ import java.util.function.Function;
@FunctionalInterface
public interface Context<T>
{
T get();
default <S> Context<S> map(@NotNull final Function<T, S> mapper)
{
return () -> mapper.apply(get());
}
T get();
default @Nullable String asString()
{
if (get() instanceof String string)
@ -56,26 +56,35 @@ public interface Context<T>
}
}
default @Nullable Integer asInt() {
if (get() instanceof Integer integer) {
default @Nullable Integer asInt()
{
if (get() instanceof Integer integer)
{
return integer;
} else {
} else
{
return null;
}
}
default @Nullable Long asLong() {
if (get() instanceof Long longg) {
default @Nullable Long asLong()
{
if (get() instanceof Long longg)
{
return longg;
} else {
} else
{
return null;
}
}
default @Nullable Float asFloat() {
if (get() instanceof Float floatt) {
default @Nullable Float asFloat()
{
if (get() instanceof Float floatt)
{
return floatt;
} else {
} else
{
return null;
}
}

View File

@ -19,7 +19,8 @@ import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* A replacement for {@link net.kyori.adventure.audience.ForwardingAudience} that allows for audiences to be removed & added at will. Not thread safe.
* A replacement for {@link net.kyori.adventure.audience.ForwardingAudience} that allows for audiences to be removed
* & added at will. Not thread safe.
* <p>
* This is intended for use in toggleable logging systems, for example, potion spy.
*/
@ -60,9 +61,9 @@ public class MutableAudienceForwarder implements Audience
public @NotNull Audience filterAudience(@NotNull final Predicate<? super Audience> filter)
{
return audiences.stream()
.filter(filter)
.findFirst()
.orElseThrow();
.filter(filter)
.findFirst()
.orElseThrow();
}
@Override

View File

@ -16,25 +16,23 @@ public class CommonsBase extends JavaPlugin
}
@Override
public void onEnable()
public void onDisable()
{
getRegistrations().getServiceRegistry().register(this, eventBus);
getExecutor().getSync()
.execute(() -> getRegistrations()
.getServiceRegistry()
.startAll());
getRegistrations().getServiceRegistry()
.stopAll();
getRegistrations().getServiceRegistry()
.unregister(EventBus.class, eventBus);
}
@Override
public void onDisable()
public void onEnable()
{
getRegistrations().getServiceRegistry().stopAll();
getRegistrations().getServiceRegistry().unregister(EventBus.class, eventBus);
}
public Registration getRegistrations()
{
return registration;
getRegistrations().getServiceRegistry()
.register(this, eventBus);
getExecutor().getSync()
.execute(() -> getRegistrations()
.getServiceRegistry()
.startAll());
}
public FreedomExecutor getExecutor()
@ -42,6 +40,11 @@ public class CommonsBase extends JavaPlugin
return executor;
}
public Registration getRegistrations()
{
return registration;
}
public EventBus getEventBus()
{
return eventBus;

View File

@ -1,12 +1,11 @@
package me.totalfreedom.base;
import me.totalfreedom.data.GroupRegistry;
import me.totalfreedom.data.BanRegistry;
import me.totalfreedom.data.ConfigRegistry;
import me.totalfreedom.data.EventRegistry;
import me.totalfreedom.data.GroupRegistry;
import me.totalfreedom.data.ModuleRegistry;
import me.totalfreedom.data.ServiceRegistry;
import me.totalfreedom.data.UserRegistry;
import me.totalfreedom.data.EventRegistry;
public class Registration
{
@ -15,7 +14,6 @@ public class Registration
private final ServiceRegistry serviceRegistry;
private final ModuleRegistry moduleRegistry;
private final GroupRegistry groupRegistry;
private final BanRegistry banRegistry;
private final ConfigRegistry configRegistry;
public Registration()
@ -25,8 +23,7 @@ public class Registration
this.serviceRegistry = new ServiceRegistry();
this.moduleRegistry = new ModuleRegistry();
this.groupRegistry = new GroupRegistry();
this.banRegistry = new BanRegistry();
this.configRegistry = new ConfigRegistry();
this.configRegistry = new ConfigRegistry();
}
public ModuleRegistry getModuleRegistry()
@ -54,11 +51,6 @@ public class Registration
return groupRegistry;
}
public BanRegistry getBanRegistry()
{
return banRegistry;
}
public ConfigRegistry getConfigRegistry()
{
return configRegistry;

View File

@ -0,0 +1,188 @@
package me.totalfreedom.command;
import me.totalfreedom.api.Context;
import me.totalfreedom.command.annotation.Completion;
import me.totalfreedom.command.annotation.Subcommand;
import me.totalfreedom.provider.ContextProvider;
import me.totalfreedom.utils.FreedomLogger;
import net.kyori.adventure.text.Component;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.command.PluginIdentifiableCommand;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
public class BukkitDelegate extends Command implements PluginIdentifiableCommand
{
private final JavaPlugin plugin;
private final Commander command;
private final boolean noConsole;
BukkitDelegate(final Commander command)
{
super(command.getInfo()
.name());
this.command = command;
this.plugin = command.getPlugin();
this.setDescription(command.getInfo()
.description());
this.setUsage(command.getInfo()
.usage());
this.setPermission(command.getPerms()
.perm());
this.setAliases(Arrays.asList(command.getInfo()
.aliases()));
this.permissionMessage(Component.text(command.getPerms()
.noPerms()));
this.noConsole = command.getPerms()
.onlyPlayers();
}
@Override
public boolean execute(@NotNull final CommandSender sender,
@NotNull final String commandLabel,
@NotNull final String[] args)
{
if (sender instanceof ConsoleCommandSender && noConsole)
{
sender.sendMessage(Component.text("This command can only be run by players."));
return true;
}
if (getPermission() != null && !sender.hasPermission(getPermission()))
{
Component permissionMessage = permissionMessage();
if (permissionMessage == null)
permissionMessage = Component.text("You do not have permission to use this command.");
sender.sendMessage(permissionMessage);
return true;
}
if (args.length > 0)
{
final ContextProvider provider = new ContextProvider();
final Set<Subcommand> nodes = command.getSubcommands()
.keySet();
for (final Subcommand node : nodes)
{
processSubCommands(args, sender, provider, node);
}
return true;
}
if (command.getBaseMethod() != null)
{
try
{
command.getBaseMethod()
.invoke(command, sender);
}
catch (Exception ex)
{
FreedomLogger.getLogger("Patchwork")
.error(ex);
}
return true;
}
return false;
}
private void processSubCommands(final @NotNull String @NotNull [] args,
final CommandSender sender, final ContextProvider provider,
final Subcommand node)
{
final Class<?>[] argTypes = node.args();
if (argTypes.length != args.length)
return;
final Object[] objects = new Object[argTypes.length + 1];
for (int i = 0; i < argTypes.length; i++)
{
final Class<?> argType = argTypes[i];
final String arg = args[i];
if (argType == String.class)
continue;
final Context<?> context = () -> provider.fromString(arg, argType);
objects[i] = context.get();
}
try
{
command.getSubcommands()
.get(node)
.invoke(command, sender, objects);
}
catch (Exception ex)
{
FreedomLogger.getLogger("Patchwork")
.error(ex);
}
}
@Override
public List<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;
}
}

View File

@ -1,171 +0,0 @@
package me.totalfreedom.command;
import me.totalfreedom.api.Context;
import me.totalfreedom.command.annotation.Completion;
import me.totalfreedom.command.annotation.Subcommand;
import me.totalfreedom.provider.ContextProvider;
import me.totalfreedom.utils.FreedomLogger;
import net.kyori.adventure.text.Component;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.command.PluginIdentifiableCommand;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
public class BukkitDelegator extends Command implements PluginIdentifiableCommand
{
private final JavaPlugin plugin;
private final CommandBase command;
private final boolean noConsole;
BukkitDelegator(final JavaPlugin plugin, final CommandBase command)
{
super(command.getInfo().name());
this.command = command;
this.plugin = command.getPlugin();
this.setDescription(command.getInfo().description());
this.setUsage(command.getInfo().usage());
this.setPermission(command.getPerms().perm());
this.setAliases(Arrays.asList(command.getInfo().aliases()));
this.permissionMessage(Component.text(command.getPerms().noPerms()));
this.noConsole = command.getPerms().onlyPlayers();
}
@Override
public boolean execute(@NotNull final CommandSender sender,
@NotNull final String commandLabel,
@NotNull final String[] args)
{
if (commandLabel.isEmpty() || !commandLabel.equalsIgnoreCase(getName()))
return false;
if (sender instanceof ConsoleCommandSender && noConsole)
{
sender.sendMessage(Component.text("This command can only be run by players."));
return true;
}
if (getPermission() != null && !sender.hasPermission(getPermission()))
{
Component permissionMessage = permissionMessage();
if (permissionMessage == null)
permissionMessage = Component.text("You do not have permission to use this command.");
sender.sendMessage(permissionMessage);
return true;
}
if (args.length > 0)
{
final ContextProvider provider = new ContextProvider();
final Set<Subcommand> nodes = command.getSubcommands().keySet();
for (final Subcommand node : nodes)
{
final Class<?>[] argTypes = node.args();
if (argTypes.length != args.length)
continue;
Object[] objects = new Object[0];
for (int i = 0; i < argTypes.length; i++)
{
final Class<?> argType = argTypes[i];
final String arg = args[i];
if (argType == String.class)
continue;
final Context<?> context = () -> provider.fromString(arg);
if (!argType.isInstance(context.get()))
{
throw new IllegalStateException(); // TODO: Change this.
}
objects = Arrays.copyOf(objects, objects.length + 1);
objects[objects.length - 1] = context.get();
}
try
{
command.getSubcommands().get(node).invoke(command, objects);
} catch (Exception ex)
{
FreedomLogger.getLogger("Patchwork")
.error(ex);
}
}
return false;
}
if (command.getBaseMethodPair() != null)
{
try
{
command.getBaseMethodPair().value().invoke(command, sender);
} catch (Exception ex)
{
FreedomLogger.getLogger("Patchwork")
.error(ex);
}
}
return true;
}
@Override
public List<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;
}
}

View File

@ -1,89 +0,0 @@
package me.totalfreedom.command;
import me.totalfreedom.command.annotation.*;
import me.totalfreedom.utils.Pair;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.CommandSender;
import org.bukkit.plugin.java.JavaPlugin;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Stream;
public abstract class CommandBase
{
private final JavaPlugin plugin;
private final Info info;
private final Permissive perms;
private final Map<Subcommand, Method> subcommands;
private final Set<Completion> completions;
private final Pair<Base, Method> baseMethodPair;
protected CommandBase(final JavaPlugin plugin)
{
this.info = this.getClass().getDeclaredAnnotation(Info.class);
this.perms = this.getClass().getDeclaredAnnotation(Permissive.class);
this.plugin = plugin;
this.subcommands = new HashMap<>();
this.completions = new HashSet<>();
if (this.getClass().isAnnotationPresent(Base.class))
{
final Method method = Stream.of(this.getClass().getDeclaredMethods())
.filter(m -> m.isAnnotationPresent(Base.class))
.findFirst()
.orElseThrow(() -> new RuntimeException("Base annotation present but no method found."));
this.baseMethodPair = new Pair<>(method.getDeclaredAnnotation(Base.class), method);
} else
{
this.baseMethodPair = null;
}
registerAnnotations();
}
private void registerAnnotations()
{
Stream.of(this.getClass().getDeclaredMethods())
.filter(method -> method.isAnnotationPresent(Subcommand.class))
.forEach(method -> this.subcommands.put(
method.getDeclaredAnnotation(Subcommand.class),
method));
List.of(this.getClass().getDeclaredAnnotationsByType(Completion.class))
.stream()
.forEach(completions::add);
}
public Pair<Base, Method> getBaseMethodPair()
{
return baseMethodPair;
}
Info getInfo()
{
return this.info;
}
Permissive getPerms()
{
return this.perms;
}
public JavaPlugin getPlugin()
{
return this.plugin;
}
Map<Subcommand, Method> getSubcommands()
{
return this.subcommands;
}
Set<Completion> getCompletions()
{
return this.completions;
}
}

View File

@ -12,14 +12,11 @@ public class CommandHandler
this.plugin = plugin;
}
// TODO: Figure out how to use CommandExecutor and TabCompleter.
// We need to find a way to resolve PluginCommands so we can
// set the executor and tab completer as necessary.
// OR we need to find an alternative way to process tab completions.
public <T extends CommandBase> void registerCommand(final T command)
public <T extends Commander> void registerCommand(final T command)
{
final BukkitDelegator delegate = new BukkitDelegator(plugin, command);
final BukkitDelegate delegate = new BukkitDelegate(command);
Bukkit.getCommandMap().register(plugin.getName(), delegate);
Bukkit.getCommandMap()
.register(plugin.getName(), delegate);
}
}

View File

@ -0,0 +1,108 @@
package me.totalfreedom.command;
import me.totalfreedom.command.annotation.Base;
import me.totalfreedom.command.annotation.Completion;
import me.totalfreedom.command.annotation.Info;
import me.totalfreedom.command.annotation.Permissive;
import me.totalfreedom.command.annotation.Subcommand;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
public abstract class Commander
{
private final JavaPlugin plugin;
private final Info info;
private final Permissive perms;
private final Map<Subcommand, Method> subcommands;
private final Set<Completion> completions;
private final Method baseMethod;
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();
}
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);
}
@Nullable
public Method getBaseMethod()
{
return baseMethod;
}
@NotNull
Info getInfo()
{
return this.info;
}
@NotNull
Permissive getPerms()
{
return this.perms;
}
@NotNull
public JavaPlugin getPlugin()
{
return this.plugin;
}
@NotNull
Map<Subcommand, Method> getSubcommands()
{
return this.subcommands;
}
@Nullable
Set<Completion> getCompletions()
{
return this.completions;
}
}

View File

@ -1,11 +1,13 @@
package me.totalfreedom.command.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Target(ElementType.METHOD)
@Repeatable(Completions.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface Completion
{

View File

@ -0,0 +1,13 @@
package me.totalfreedom.command.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Completions
{
Completion[] value();
}

View File

@ -1,8 +1,11 @@
package me.totalfreedom.command.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Subcommand
{

View File

@ -1,34 +0,0 @@
package me.totalfreedom.data;
import me.totalfreedom.security.ban.Ban;
import me.totalfreedom.security.ban.BanID;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
public class BanRegistry
{
private final List<Ban> bansList = new ArrayList<>();
public boolean addBan(final Ban ban) {
return bansList.add(ban);
}
public boolean removeBan(final Ban ban) {
return bansList.remove(ban);
}
@Nullable
public Ban getBan(final BanID banID)
{
for (final Ban ban : bansList)
{
if (ban.getBanID().matches(banID))
{
return ban;
}
}
return null;
}
}

View File

@ -15,26 +15,32 @@ public class GroupRegistry
this.groups = new ArrayList<>();
}
public boolean registerGroup(final Group group) {
public boolean registerGroup(final Group group)
{
return groups.add(group);
}
public boolean unregisterGroup(final Group group) {
public boolean unregisterGroup(final Group group)
{
return groups.remove(group);
}
public Group getGroup(final String name) {
public Group getGroup(final String name)
{
final PlainTextComponentSerializer s = PlainTextComponentSerializer.plainText();
for (final Group group : groups) {
for (final Group group : groups)
{
final String n = s.serialize(group.getName());
if (n.equalsIgnoreCase(name)) {
if (n.equalsIgnoreCase(name))
{
return group;
}
}
return null;
}
public List<Group> getGroups() {
public List<Group> getGroups()
{
return groups;
}
}

View File

@ -24,7 +24,8 @@ public class ModuleRegistry
this.plugins.add(plugin);
}
public void removeModule(final JavaPlugin plugin) {
public void removeModule(final JavaPlugin plugin)
{
this.plugins.remove(plugin);
}

View File

@ -42,29 +42,34 @@ public class ServiceRegistry
public <T extends Service> void register(final Plugin plugin, final T service)
{
this.services.add(service);
if (!service.getClass().isInstance(service))
if (!service.getClass()
.isInstance(service))
{
throw new UnknownError("""
A critical issue has been encountered:
The service %s is not an instance of itself.
This is a critical issue and should be reported immediately.
""".formatted(service.getClass().getName()));
A critical issue has been encountered:
The service %s is not an instance of itself.
This is a critical issue and should be reported immediately.
""".formatted(service.getClass()
.getName()));
}
Bukkit.getServicesManager().register(
(Class<T>) service.getClass(),
service,
plugin,
ServicePriority.Normal);
Bukkit.getServicesManager()
.register(
(Class<T>) service.getClass(),
service,
plugin,
ServicePriority.Normal);
}
public <T extends Service> RegisteredServiceProvider<T> getService(final Class<T> clazz)
{
return Bukkit.getServicesManager().getRegistration(clazz);
return Bukkit.getServicesManager()
.getRegistration(clazz);
}
public void unregister(final Class<? extends Service> clazz, final Service service)
{
this.services.remove(service);
Bukkit.getServicesManager().unregister(clazz, service);
Bukkit.getServicesManager()
.unregister(clazz, service);
}
}

View File

@ -0,0 +1,121 @@
package me.totalfreedom.display;
import net.kyori.adventure.text.Component;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
public abstract class AbstractMenu
{
private static final Map<UUID, AbstractMenu> invByUUID = new HashMap<>();
private static final Map<UUID, UUID> openInvs = new HashMap<>();
private final Map<Integer, ClickAction> actionMap;
private final Displayable displayable;
private final UUID displayableUUID;
protected AbstractMenu(final int size)
{
actionMap = new HashMap<>();
this.displayable = new Displayable(size);
this.displayableUUID = UUID.randomUUID();
invByUUID.put(getDisplayableUUID(), this);
}
public UUID getDisplayableUUID()
{
return displayableUUID;
}
public static Map<UUID, AbstractMenu> getInvByUUID()
{
return invByUUID;
}
public static Map<UUID, UUID> getOpenInvs()
{
return openInvs;
}
public void setItem(final int slot, final @NotNull ItemStack stack)
{
setItem(slot, stack, null);
}
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);
}
}
public Displayable getDisplayable()
{
return displayable;
}
public void open(final @NotNull Player player)
{
player.openInventory(getDisplayable());
openInvs.put(player.getUniqueId(), getDisplayableUUID());
}
public void delete()
{
Bukkit.getOnlinePlayers()
.forEach(player ->
{
if (openInvs.get(player.getUniqueId())
.equals(getDisplayableUUID()))
{
close(player);
}
});
invByUUID.remove(getDisplayableUUID());
}
public void close(final @NotNull Player player)
{
player.closeInventory();
openInvs.remove(player.getUniqueId());
}
public Map<Integer, ClickAction> getActions()
{
return actionMap;
}
public ItemStack newItem(final @NotNull Material material, final @NotNull Component name)
{
return this.newItem(material, name, new Component[0]);
}
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;
}
}

View File

@ -0,0 +1,9 @@
package me.totalfreedom.display;
import org.bukkit.entity.Player;
@FunctionalInterface
public interface ClickAction
{
void onClick(final Player player);
}

View File

@ -1,5 +1,6 @@
package me.totalfreedom.display;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.entity.HumanEntity;
import org.bukkit.event.inventory.InventoryType;
@ -14,11 +15,11 @@ import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
public abstract class Displayable implements Inventory, InventoryHolder
public final class Displayable implements Inventory, InventoryHolder
{
private final int size;
private final ItemStack[] contents;
private ItemStack[] contents;
protected Displayable(final int size)
{
@ -27,9 +28,9 @@ public abstract class Displayable implements Inventory, InventoryHolder
throw new IllegalArgumentException("Invalid size for Displayable inventory");
}
this.size = (size % 9 == 0)
? size
: size + (9 - size % 9);
// 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];
}
@ -78,7 +79,8 @@ public abstract class Displayable implements Inventory, InventoryHolder
}
@Override
public @NotNull HashMap<Integer, ItemStack> addItem(final @NotNull ItemStack... items) throws IllegalArgumentException
public @NotNull HashMap<Integer, ItemStack> addItem(final @NotNull ItemStack... items)
throws IllegalArgumentException
{
final HashMap<Integer, ItemStack> remainingItems = new HashMap<>();
@ -116,7 +118,8 @@ public abstract class Displayable implements Inventory, InventoryHolder
}
@Override
public @NotNull HashMap<Integer, ItemStack> removeItem(final @NotNull ItemStack... items) throws IllegalArgumentException
public @NotNull HashMap<Integer, ItemStack> removeItem(final @NotNull ItemStack... items)
throws IllegalArgumentException
{
final HashMap<Integer, ItemStack> removedItems = new HashMap<>();
@ -148,13 +151,21 @@ public abstract class Displayable implements Inventory, InventoryHolder
if (remainingAmount < item.getAmount())
{
removedItems.put(removedItems.size(), new ItemStack(item.getType(), item.getAmount() - remainingAmount));
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()
{
@ -176,39 +187,15 @@ public abstract class Displayable implements Inventory, InventoryHolder
}
@Override
public @NotNull ListIterator<ItemStack> iterator()
public @Nullable ItemStack @NotNull [] getStorageContents()
{
return iterator(0);
return contents;
}
@Override
public @NotNull ListIterator<ItemStack> iterator(final int index)
public void setStorageContents(final @Nullable ItemStack @NotNull [] items) throws IllegalArgumentException
{
return List.of(contents).listIterator(index);
}
@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 List<HumanEntity> getViewers()
{
return new ArrayList<>();
this.contents = items;
}
@Override
@ -272,7 +259,7 @@ public abstract class Displayable implements Inventory, InventoryHolder
if (content != null && content.isSimilar(item))
{
totalAmount += content.getAmount();
if (totalAmount >= amount)
if (totalAmount == amount)
{
return true;
}
@ -304,7 +291,8 @@ public abstract class Displayable implements Inventory, InventoryHolder
}
@Override
public @NotNull HashMap<Integer, ? extends ItemStack> all(final @NotNull Material material) throws IllegalArgumentException
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++)
@ -439,4 +427,53 @@ public abstract class Displayable implements Inventory, InventoryHolder
{
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;
}
}

View File

@ -16,7 +16,8 @@ public class DisplayableView extends InventoryView
private final InventoryType type;
private String title;
public DisplayableView(final Player player, final Displayable top, final Displayable bottom) {
public DisplayableView(final Player player, final Displayable top, final Displayable bottom)
{
this.player = player;
this.top = top;
this.bottom = bottom;

View File

@ -6,9 +6,9 @@ public interface EconomicEntityData
long getBalance();
void setBalance(final long newBalance);
long addToBalance(final long amount);
long removeFromBalance(final long amount);
void setBalance(final long newBalance);
}

View File

@ -1,7 +1,8 @@
package me.totalfreedom.economy;
/**
* Please ensure that all modifications of {@link MutableTransaction} happen BEFORE it is passed to a {@link Transactor} implementation
* Please ensure that all modifications of {@link MutableTransaction} happen BEFORE it is passed to a
* {@link Transactor} implementation
*/
public interface MutableTransaction extends Transaction
{

View File

@ -27,9 +27,10 @@ public class EventBus extends Service
public <T extends FEvent> T getEvent(final Class<T> eventClass)
{
final FEvent e = eventSet.stream()
.filter(event -> event.getEventClass().equals(eventClass))
.findFirst()
.orElse(null);
.filter(event -> event.getEventClass()
.equals(eventClass))
.findFirst()
.orElse(null);
return eventClass.cast(e);
}
@ -37,10 +38,11 @@ public class EventBus extends Service
public <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);
.filter(event -> event.getEventClass()
.equals(eventClass))
.findFirst()
.map(eventClass::cast)
.orElse(null);
if (eventContext.get() == null)
{

View File

@ -7,24 +7,32 @@ class SubscriptionBox<T extends FEvent>
{
private final List<EventSubscription<T>> subscriptions;
public SubscriptionBox() {
public SubscriptionBox()
{
this.subscriptions = new ArrayList<>();
}
public void addSubscription(final EventSubscription<T> subscription) {
public void addSubscription(final EventSubscription<T> subscription)
{
subscriptions.add(subscription);
}
public void removeSubscription(final EventSubscription<?> subscription) {
public void removeSubscription(final EventSubscription<?> subscription)
{
subscriptions.remove(subscription);
}
public void tick() {
subscriptions.forEach(s -> {
if (!s.event().shouldCall()) return;
public void tick()
{
subscriptions.forEach(s ->
{
if (!s.event()
.shouldCall()) return;
s.callback().call(s.event());
s.event().reset();
});
s.callback()
.call(s.event());
s.event()
.reset();
});
}
}

View File

@ -1,11 +1,31 @@
package me.totalfreedom.particle;
import org.bukkit.Color;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.UUID;
public interface Trail
{
@NotNull
UUID getAssocPlayerUUID();
// Nullable because the player may not be online and trail selections should be persistent whether they are
// active or not.
@Nullable
Player getAssocPlayer();
@NotNull
TrailType getTrailType();
@Nullable Color getColor();
@NotNull
Color getColor();
void setColor(@NotNull Color color);
boolean isActive();
void setActive(boolean active);
}

View File

@ -14,21 +14,22 @@ import java.util.stream.Stream;
public class ContextProvider
{
public Object fromString(final String string)
public <T> T fromString(final String string, final Class<T> clazz)
{
return Stream.of(toBoolean(string),
toDouble(string),
toInt(string),
toLong(string),
toFloat(string),
toPlayer(string),
toWorld(string),
toLocation(string),
toCommandSender(string),
toComponent(string))
.filter(Objects::nonNull)
.findFirst()
.orElse(string);
toDouble(string),
toInt(string),
toLong(string),
toFloat(string),
toPlayer(string),
toWorld(string),
toLocation(string),
toCommandSender(string),
toComponent(string))
.filter(Objects::nonNull)
.findFirst()
.map(clazz::cast)
.orElse(null);
}
private @Nullable Boolean toBoolean(final String string)
@ -36,7 +37,8 @@ public class ContextProvider
try
{
return Boolean.parseBoolean(string);
} catch (Exception e)
}
catch (Exception ignored)
{
return null;
}
@ -47,7 +49,8 @@ public class ContextProvider
try
{
return Double.parseDouble(string);
} catch (Exception e)
}
catch (Exception ignored)
{
return null;
}
@ -58,7 +61,8 @@ public class ContextProvider
try
{
return Integer.parseInt(string);
} catch (Exception e)
}
catch (Exception ignored)
{
return null;
}
@ -69,7 +73,8 @@ public class ContextProvider
try
{
return Long.parseLong(string);
} catch (Exception e)
}
catch (Exception ignored)
{
return null;
}
@ -80,7 +85,8 @@ public class ContextProvider
try
{
return Float.parseFloat(string);
} catch (Exception e)
}
catch (Exception ignored)
{
return null;
}
@ -91,20 +97,11 @@ public class ContextProvider
return Bukkit.getPlayer(string);
}
private @Nullable CommandSender toCommandSender(final String string)
{
if (toPlayer(string) == null) return null;
return toPlayer(string);
}
private @Nullable World toWorld(final String string)
{
return Bukkit.getWorld(string);
}
// If we decide to, we can "modify" this to use spaces
// and adjust our inputs accordingly.
/**
* When using this method, the input string must be formatted as
* <br>
@ -127,6 +124,13 @@ public class ContextProvider
return new Location(toWorld(split[0]), x, y, z);
}
private @Nullable CommandSender toCommandSender(final String string)
{
if (toPlayer(string) == null) return null;
return toPlayer(string);
}
private @NotNull Component toComponent(final String string)
{
return Component.text(string);

View File

@ -3,10 +3,10 @@ package me.totalfreedom.security.ban;
/**
* Represents an ID for a ban. These are formatted either as:
* <p>
* P-00129381
* <br>
* T-00128381
* <br>
* P-00129381
* <br>
* T-00128381
* <br>
* </p>
* Where P marks a ban as permanent, and T marks a ban as temporary.
*/
@ -19,6 +19,23 @@ public interface BanID
*/
String getID();
/**
* Checks the prefix of the Ban ID to see whether if it is permanent.
*
* @return true if the Ban ID is prefixed with a P, false otherwise.
*/
boolean isPermanent();
default boolean matches(BanID other)
{
if (other == null)
{
return false;
}
return (getIDPrefix() == other.getIDPrefix())
&& (getNumericalTag() == other.getNumericalTag());
}
/**
* This method returns the ban type denominator character for the Ban ID.
* This would either be T or P, where T = temporary and P = permanent.
@ -34,19 +51,4 @@ public interface BanID
* @return The numerical tag of this ban ID.
*/
int getNumericalTag();
/**
* Checks the prefix of the Ban ID to see whether if it is permanent.
*
* @return true if the Ban ID is prefixed with a P, false otherwise.
*/
boolean isPermanent();
default boolean matches(BanID other) {
if (other == null) {
return false;
}
return (getIDPrefix() == other.getIDPrefix())
&& (getNumericalTag() == other.getNumericalTag());
}
}

View File

@ -21,11 +21,7 @@ public interface Node
boolean isExpired();
boolean isPermanent();
boolean isTemporary();
boolean wildcard();
boolean negated();
}

View File

@ -13,38 +13,30 @@ public class FreedomExecutor
public FreedomExecutor()
{
syncExecutor = r -> Bukkit.getScheduler().runTask(CommonsBase.getInstance(), r);
asyncExecutor = r -> Bukkit.getScheduler().runTaskAsynchronously(CommonsBase.getInstance(), r);
}
public Executor getSync()
{
return syncExecutor;
}
public Executor getAsync()
{
return asyncExecutor;
syncExecutor = r -> Bukkit.getScheduler()
.runTask(CommonsBase.getInstance(), r);
asyncExecutor = r -> Bukkit.getScheduler()
.runTaskAsynchronously(CommonsBase.getInstance(), r);
}
public Executor scheduled(final long delay, final long period)
{
return r -> Bukkit.getScheduler()
.runTaskTimer(
CommonsBase.getInstance(),
r,
delay,
period);
.runTaskTimer(
CommonsBase.getInstance(),
r,
delay,
period);
}
public Executor scheduledAsync(final long delay, final long period)
{
return r -> Bukkit.getScheduler()
.runTaskTimerAsynchronously(
CommonsBase.getInstance(),
r,
delay,
period);
.runTaskTimerAsynchronously(
CommonsBase.getInstance(),
r,
delay,
period);
}
public void runSync(@NotNull final Task task)
@ -52,8 +44,18 @@ public class FreedomExecutor
getSync().execute(task);
}
public Executor getSync()
{
return syncExecutor;
}
public void runAsync(@NotNull final Task task)
{
getAsync().execute(task);
}
public Executor getAsync()
{
return asyncExecutor;
}
}

View File

@ -17,24 +17,24 @@ public abstract class Service
{
isActive = true;
CommonsBase.getInstance()
.getExecutor()
.getSync()
.execute(() ->
{
while (isActive)
{
tick();
}
});
.getExecutor()
.getSync()
.execute(() ->
{
while (isActive)
{
tick();
}
});
}
public abstract void tick();
public void stop()
{
isActive = false;
}
public abstract void tick();
public String getName()
{
return name;

View File

@ -1,14 +1,11 @@
package me.totalfreedom.sql;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.concurrent.CompletableFuture;
public interface SQL
{
CompletableFuture<Connection> getConnection(final String url);
CompletableFuture<PreparedStatement> prepareStatement(final String query, final Object... args);
CompletableFuture<ResultSet> executeQuery(final String query, final Object... args);

View File

@ -19,6 +19,15 @@ public interface SQLProperties
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();
@ -27,27 +36,18 @@ public interface SQLProperties
String getDatabase();
String getUsername();
String getPassword();
default String toURLPlain()
{
return String.format("jdbc:%s://%s:%s/%s",
this.getDriver(),
this.getHost(),
this.getPort(),
this.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());
this.getDriver(),
this.getHost(),
this.getPort(),
this.getDatabase(),
this.getUsername(),
this.getPassword());
}
String getUsername();
String getPassword();
}

View File

@ -1,12 +1,18 @@
package me.totalfreedom.user;
import me.totalfreedom.security.perm.PermissionHolder;
import me.totalfreedom.economy.EconomicEntity;
import me.totalfreedom.economy.EconomicEntityData;
import me.totalfreedom.security.perm.PermissionHolder;
import net.kyori.adventure.text.Component;
public interface User extends PermissionHolder, EconomicEntity
{
@Override
default EconomicEntityData getEconomicData()
{
return getUserData();
}
// Implement a few EconomicEntity methods in the User interface
@Override
default String getName()
@ -14,12 +20,6 @@ public interface User extends PermissionHolder, EconomicEntity
return getUserData().getUsername();
}
@Override
default EconomicEntityData getEconomicData()
{
return getUserData();
}
UserData getUserData();
Component getDisplayName();

View File

@ -1,7 +1,7 @@
package me.totalfreedom.user;
import me.totalfreedom.security.perm.Group;
import me.totalfreedom.economy.EconomicEntityData;
import me.totalfreedom.security.perm.Group;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

View File

@ -6,27 +6,29 @@ import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import java.util.function.Supplier;
/**
* This class contains the only reference to plain text component serializer, and allows access to it via wrapper functions.
* This class contains the only reference to plain text component serializer, and allows access to it via wrapper
* functions.
*/
public class FreedomAdventure
{
private static final PlainTextComponentSerializer PLAIN_TEXT_COMPONENT_SERIALIZER =
PlainTextComponentSerializer.plainText();
private FreedomAdventure()
{
throw new UnsupportedOperationException("Instantiation of a static utility class is not supported.");
}
private static final PlainTextComponentSerializer PLAIN_TEXT_COMPONENT_SERIALIZER = PlainTextComponentSerializer.plainText();
public static String toPlainText(final Supplier<Component> supplier)
{
return toPlainText(supplier.get());
}
public static String toPlainText(final Component component)
{
return PLAIN_TEXT_COMPONENT_SERIALIZER.serialize(component);
}
public static String toPlainText(final Supplier<Component> supplier)
{
return toPlainText(supplier.get());
}
public static Supplier<String> supplyPlainText(final Supplier<Component> supplier)
{
return new StringRepresentationSupplier(supplier.get());

View File

@ -31,30 +31,6 @@ public class FreedomLogger implements Audience
this.debug = debug;
}
/**
* 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 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,
* while also returning a Component that could be used to
@ -80,6 +56,28 @@ public class FreedomLogger implements Audience
public String infoComponent(final Supplier<Component> component)
{
return this.infoComponent(component.get());
} /**
* 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 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;
}
/**
@ -116,20 +114,6 @@ public class FreedomLogger implements Audience
logger.error(message);
}
/**
* 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 an exception directly to the console.
*
@ -170,28 +154,15 @@ public class FreedomLogger implements Audience
}
/**
* This method allows you to log a debug message to the console.
* This method will only log if debug mode is enabled.
* This method logs an error component to the console.
*
* @param message The message to send.
* @param component The message to send.
*/
public void debug(final String message)
{
if (debug)
logger.debug(message);
}
/**
* This method allows you to log a debug component to the console.
* This method will only log if debug mode is enabled.
*
* @param component The component to send.
*/
public String debugComponent(final Component component)
public String errorComponent(final Component component)
{
final String plainText = FreedomAdventure.toPlainText(component);
this.debug(plainText);
logger.error(plainText);
return plainText;
}
@ -231,6 +202,35 @@ public class FreedomLogger implements Audience
return "";
}
/**
* This method allows you to log a debug component to the console.
* This method will only log if debug mode is enabled.
*
* @param component The component to send.
*/
public String debugComponent(final Component component)
{
final String plainText = FreedomAdventure.toPlainText(component);
this.debug(plainText);
return plainText;
}
/**
* This method allows you to log a debug message to the console.
* This method will only log if debug mode is enabled.
*
* @param message The message to send.
*/
public void debug(final String message)
{
if (debug)
logger.debug(message);
}
@Override
public void sendMessage(@NotNull final ComponentLike message)
@ -267,6 +267,9 @@ public class FreedomLogger implements Audience
@Override
public void sendMessage(@NotNull final SignedMessage signedMessage, final ChatType.@NotNull Bound boundChatType)
{
this.info(signedMessage.message()); // TODO: We might want to investigate whether this logs the ENTIRE message, including unsigned & signed content, or only the signed part. This method was written in the assumption that it provided all content.
this.info(
signedMessage.message()); // TODO: We might want to investigate whether this logs the ENTIRE message,
// including unsigned & signed content, or only the signed part. This method was written in the assumption
// that it provided all content.
}
}

View File

@ -9,7 +9,8 @@ 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 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
@ -30,13 +31,6 @@ public final class KyoriConstants
{
}
public static ChatType.Bound fromPlugin(final JavaPlugin plugin)
{
final String name = plugin.getName();
final Component component = Component.text(name, NamedTextColor.GOLD);
return type.bind(component);
}
/**
* Represents a Chat Bound for a plugin.
* <br>
@ -53,6 +47,13 @@ public final class KyoriConstants
return fromPlugin(plugin);
}
public static ChatType.Bound fromPlugin(final JavaPlugin plugin)
{
final String name = plugin.getName();
final Component component = Component.text(name, NamedTextColor.GOLD);
return type.bind(component);
}
/**
* Represents a Chat Bound for a player.
* Chat bounds are required for sending messages to players.
@ -73,6 +74,7 @@ public final class KyoriConstants
* <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.
*/

View File

@ -20,7 +20,8 @@ public class Shaper
this.world = world;
}
public List<Location> generate(final int count, final DoubleUnaryOperator x, final DoubleUnaryOperator y, final DoubleUnaryOperator z)
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<>();

View File

@ -1,17 +1,27 @@
[Google GSON]: https://github.com/google/gson "Google GSON"
[Jetbrains Annotations]: https://github.com/JetBrains/JetBrains.Annotations "JetBrains Annotations"
[Lombok]: https://github.com/projectlombok/lombok "Lombok"
[Apache Commons]: https://github.com/apache/commons-lang "Apache Commons"
[SLF4J]: https://github.com/qos-ch/slf4j "SLF4J"
[Paper]: https://github.com/PaperMC/Paper "Paper"
[Kyori Adventure]: https://github.com/KyoriPowered/adventure "Kyori Adventure"
[Reflections API]: https://github.com/ronmamo/reflections "Reflections API"
[TotalFreedomMod]: https://github.com/AtlasMediaGroup/TotalFreedomMod "TotalFreedomMod"
#####
![Header Image](https://media.discordapp.net/attachments/436759124953399296/1107175759941996544/20230514_002037_0000.png)
###
[<img src="https://img.shields.io/static/v1?label=%20&message=Help%20Wanted&color=red&style=for-the-badge">](https://discord.gg/4PdtmrVNRx)
![GitHub contributors](https://img.shields.io/github/contributors/SimplexDevelopment/FreedomNetworkSuite?style=for-the-badge)
![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/SimplexDevelopment/FreedomNetworkSuite?style=for-the-badge)
@ -21,6 +31,7 @@
![Codacy grade](https://img.shields.io/codacy/grade/7a0fa4694878444dabc6cc2804fbf125?style=for-the-badge)
###
[<img src="https://img.shields.io/static/v1?label=Roadmap&message=Google%20Docs&color=4285F4&style=for-the-badge&logo=googledrive">](https://docs.google.com/document/d/197fwNo076RsCiPW6e6QWaGEzTGnDcRuf5FBA6lNeiPE)
[<img src="https://img.shields.io/github/license/SimplexDevelopment/FreedomNetworkSuite?style=for-the-badge">](https://github.com/SimplexDevelopment/FreedomNetworkSuite/blob/kitchen-sink/LICENSE.md)
![GitHub top language](https://img.shields.io/github/languages/top/SimplexDevelopment/FreedomNetworkSuite?style=for-the-badge&logo=github)
@ -40,16 +51,18 @@ Honorable mention:
[<img src="https://img.shields.io/static/v1?label=Plex&message=A%20New%20Freedom%20Plugin&color=4285F4&style=flat-square&logo=plex)">](https://github.com/plexusorg/Plex)
This proof-of-concept also uses the following libraries:
- [Google GSON] for Json interpretation
- [Jetbrains Annotations] for additional compiler annotations
- [Lombok] for boilerplate generation
- [Apache Commons] for various utilities
- [SLF4J] for logging
- [Paper] for the server implementation
- [Kyori Adventure] for chat formatting
- [Reflections API] for reflections
- [Google GSON] for Json interpretation
- [Jetbrains Annotations] for additional compiler annotations
- [Lombok] for boilerplate generation
- [Apache Commons] for various utilities
- [SLF4J] for logging
- [Paper] for the server implementation
- [Kyori Adventure] for chat formatting
- [Reflections API] for reflections
# Developers
[<img src="https://img.shields.io/static/v1?label=Developer&message=Patches&color=blueviolet&style=for-the-badge&logo=intellijidea">](https://github.com/Paldiu)
<br />
[<img src="https://img.shields.io/static/v1?label=Developer&message=Video&color=blueviolet&style=for-the-badge&logo=intellijidea">](https://github.com/VideoGameSmash12)
@ -57,7 +70,9 @@ This proof-of-concept also uses the following libraries:
[<img src="https://img.shields.io/static/v1?label=Developer&message=Allink&color=blueviolet&style=for-the-badge&logo=intellijidea">](https://github.com/allinkdev)
# To Do List
Patchwork:
- [x] Logging System
- [x] SQL API
- [x] Economy API
@ -72,6 +87,7 @@ Patchwork:
- [ ] Event API
Datura:
- [ ] Permission Handling
- [ ] Permission Registration & Assignment
- [ ] SQL Data Handling
@ -81,6 +97,7 @@ Datura:
- [ ] Punishment Systems (e.x. Locker, Halter, Muter, Cager)
Fossil:
- [x] Economy Implementation
- [ ] Particle Implementation / Trails
- [ ] Command Implementations
@ -89,6 +106,7 @@ Fossil:
- [ ] Jumppads
Corvo:
- [ ] Service Implementation
- [ ] Service Handling
- [ ] Task Implementation