Merge branch 'banning-update' of https://github.com/SimplexDevelopment/FreedomNetworkSuite into banning-update

This commit is contained in:
Paul Reilly 2023-05-21 21:47:54 -05:00
commit faca73f99c
22 changed files with 904 additions and 9 deletions

View File

@ -16,6 +16,7 @@ import org.jetbrains.annotations.Nullable;
import java.sql.SQLException;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicLong;
public class SimpleUserData implements UserData
{
@ -28,6 +29,8 @@ public class SimpleUserData implements UserData
private boolean frozen;
private boolean canInteract;
private boolean caged;
private AtomicLong balance;
private boolean transactionsFrozen;
public SimpleUserData(final Player player)
{
@ -46,7 +49,9 @@ public class SimpleUserData implements UserData
final long playtime,
final boolean frozen,
final boolean canInteract,
final boolean caged)
final boolean caged,
final long balance,
final boolean transactionsFrozen)
{
this.uuid = uuid;
this.username = username;
@ -56,6 +61,8 @@ public class SimpleUserData implements UserData
this.frozen = frozen;
this.canInteract = canInteract;
this.caged = caged;
this.balance = new AtomicLong(balance);
this.transactionsFrozen = transactionsFrozen;
}
public static SimpleUserData fromSQL(final SQL sql, final String uuid)
@ -82,11 +89,14 @@ public class SimpleUserData implements UserData
.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");
return new SimpleUserData(u, username, user, group, playtime, frozen, canInteract, caged);
long playtime = result.getLong("playtime");
boolean frozen = result.getBoolean("frozen");
boolean canInteract = result.getBoolean("canInteract");
boolean caged = result.getBoolean("caged");
long balance = result.getLong("balance");
boolean transactionsFrozen = result.getBoolean("transactionsFrozen");
return new SimpleUserData(u, username, user, group, playtime, frozen, canInteract, caged, balance, transactionsFrozen);
}
} catch (SQLException ex)
{
@ -207,4 +217,34 @@ public class SimpleUserData implements UserData
event.ping();
this.caged = caged;
}
@Override
public boolean areTransactionsFrozen()
{
return transactionsFrozen;
}
@Override
public long getBalance()
{
return balance.get();
}
@Override
public long addToBalance(long amount)
{
return balance.addAndGet(amount);
}
@Override
public long removeFromBalance(long amount)
{
return balance.addAndGet(-amount);
}
@Override
public void setBalance(long newBalance)
{
balance.set(newBalance);
}
}

View File

@ -0,0 +1,47 @@
package me.totalfreedom.fossil.economy;
import me.totalfreedom.economy.CompletedTransaction;
import me.totalfreedom.economy.EconomicEntity;
import me.totalfreedom.economy.Transaction;
import me.totalfreedom.economy.TransactionResult;
public class SimpleCompletedTransaction implements CompletedTransaction
{
private final TransactionResult transactionResult;
private final EconomicEntity source;
private final EconomicEntity destination;
private final long balance;
public SimpleCompletedTransaction(Transaction transaction, TransactionResult transactionResult)
{
this.source = transaction.getSource();
this.destination = transaction.getDestination();
this.balance = transaction.getBalance();
this.transactionResult = transactionResult;
}
@Override
public TransactionResult getResult()
{
return transactionResult;
}
@Override
public EconomicEntity getSource()
{
return source;
}
@Override
public EconomicEntity getDestination()
{
return destination;
}
@Override
public long getBalance()
{
return balance;
}
}

View File

@ -0,0 +1,37 @@
package me.totalfreedom.fossil.economy;
import me.totalfreedom.economy.CompletedTransaction;
import me.totalfreedom.economy.MutableTransaction;
import me.totalfreedom.economy.TransactionLogger;
import me.totalfreedom.economy.Transactor;
public class SimpleLoggedTransactor implements Transactor
{
private final Transactor transactor;
private final TransactionLogger transactionLogger;
public SimpleLoggedTransactor()
{
this(new SimpleTransactor(), new SimpleTransactionLogger());
}
public SimpleLoggedTransactor(Transactor transactor, TransactionLogger transactionLogger)
{
this.transactor = transactor;
this.transactionLogger = transactionLogger;
}
@Override
public CompletedTransaction handleTransaction(MutableTransaction transaction)
{
CompletedTransaction completedTransaction = transactor.handleTransaction(transaction);
transactionLogger.logTransaction(completedTransaction);
return completedTransaction;
}
public TransactionLogger getTransactionLogger()
{
return this.transactionLogger;
}
}

View File

@ -0,0 +1,30 @@
package me.totalfreedom.fossil.economy;
import me.totalfreedom.economy.EconomicEntity;
import me.totalfreedom.economy.MutableTransaction;
public class SimpleMutableTransaction extends SimpleTransaction implements MutableTransaction
{
public SimpleMutableTransaction(EconomicEntity source, EconomicEntity destination, long balance)
{
super(source, destination, balance);
}
@Override
public long addToBalance(long amount)
{
return balance.addAndGet(amount);
}
@Override
public long removeFromBalance(long amount)
{
return this.addToBalance(-amount);
}
@Override
public void setBalance(long newBalance)
{
balance.set(newBalance);
}
}

View File

@ -0,0 +1,38 @@
package me.totalfreedom.fossil.economy;
import me.totalfreedom.economy.EconomicEntity;
import me.totalfreedom.economy.Transaction;
import java.util.concurrent.atomic.AtomicLong;
public class SimpleTransaction implements Transaction
{
private final EconomicEntity source;
private final EconomicEntity destination;
protected final AtomicLong balance;
public SimpleTransaction(EconomicEntity source, EconomicEntity destination, long balance)
{
this.source = source;
this.destination = destination;
this.balance = new AtomicLong(balance);
}
@Override
public EconomicEntity getSource()
{
return source;
}
@Override
public EconomicEntity getDestination()
{
return destination;
}
@Override
public long getBalance()
{
return balance.get();
}
}

View File

@ -0,0 +1,45 @@
package me.totalfreedom.fossil.economy;
import me.totalfreedom.audience.MutableAudienceForwarder;
import me.totalfreedom.economy.*;
import me.totalfreedom.utils.FreedomLogger;
import net.kyori.adventure.text.Component;
public class SimpleTransactionLogger implements TransactionLogger
{
private final MutableAudienceForwarder audience = MutableAudienceForwarder.from(FreedomLogger.getLogger("Fossil"));
@Override
public void logTransaction(CompletedTransaction completedTransaction)
{
StringBuilder transactionLoggingStatementBuilder = new StringBuilder();
TransactionResult result = completedTransaction.getResult();
boolean resultSuccess = result.isSuccessful();
String resultMessage = result.getMessage();
EconomicEntity source = completedTransaction.getSource();
EconomicEntity destination = completedTransaction.getDestination();
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(".");
Component message = Component.text(transactionLoggingStatementBuilder.toString());
audience.sendMessage(message);
}
public MutableAudienceForwarder getAudienceForwarder()
{
return audience;
}
}

View File

@ -0,0 +1,46 @@
package me.totalfreedom.fossil.economy;
import me.totalfreedom.economy.TransactionResult;
import net.kyori.adventure.text.Component;
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);
private final String message;
private final Component component;
private final boolean successful;
public SimpleTransactionResult(String message, boolean successful)
{
this(message, Component.text(message, successful ? NamedTextColor.GREEN : NamedTextColor.RED), successful);
}
public SimpleTransactionResult(String message, Component component, boolean successful)
{
this.message = message;
this.component = component;
this.successful = successful;
}
@Override
public String getMessage()
{
return message;
}
@Override
public boolean isSuccessful()
{
return successful;
}
@Override
public Component getComponent()
{
return component;
}
}

View File

@ -0,0 +1,46 @@
package me.totalfreedom.fossil.economy;
import me.totalfreedom.economy.*;
public class SimpleTransactor implements Transactor
{
@Override
public CompletedTransaction handleTransaction(MutableTransaction transaction)
{
EconomicEntity source = transaction.getSource();
EconomicEntityData sourceData = source.getEconomicData();
if (sourceData.areTransactionsFrozen())
{
return new SimpleCompletedTransaction(transaction, SimpleTransactionResult.UNAUTHORIZED);
}
long transactionAmount = transaction.getBalance();
if (transactionAmount >= 0)
{
return new SimpleCompletedTransaction(transaction, SimpleTransactionResult.AMOUNT_TOO_SMALL);
}
long sourceBalance = sourceData.getBalance();
long diff = sourceBalance - transactionAmount;
if (diff > 0)
{
return new SimpleCompletedTransaction(transaction, SimpleTransactionResult.INSUFFICIENT_FUNDS);
}
EconomicEntity destination = transaction.getDestination();
EconomicEntityData destinationData = destination.getEconomicData();
if (destinationData.areTransactionsFrozen())
{
return new SimpleCompletedTransaction(transaction, SimpleTransactionResult.UNAUTHORIZED);
}
sourceData.removeFromBalance(transactionAmount);
destinationData.addToBalance(transactionAmount);
return new SimpleCompletedTransaction(transaction, SimpleTransactionResult.SUCCESSFUL);
}
}

View File

@ -0,0 +1,244 @@
package me.totalfreedom.audience;
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;
import java.util.HashSet;
import java.util.Set;
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.
* <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
{
private final Set<Audience> audiences = new HashSet<>();
public static MutableAudienceForwarder from(Audience... audiences)
{
MutableAudienceForwarder audienceForwarder = new MutableAudienceForwarder();
for (Audience audience : audiences)
{
audienceForwarder.addAudience(audience);
}
return audienceForwarder;
}
public void addAudience(Audience audience)
{
if (audiences.contains(audience) || audience == this /* Protect against honest self-referential calls */)
{
return;
}
audiences.add(audience);
}
public boolean removeAudience(Audience audience)
{
return audiences.remove(audience);
}
@Override
public @NotNull Audience filterAudience(@NotNull Predicate<? super Audience> filter)
{
return audiences.stream()
.filter(filter)
.findFirst()
.orElseThrow();
}
@Override
public void forEachAudience(@NotNull Consumer<? super Audience> action)
{
audiences.forEach(action);
}
@Override
public void sendMessage(@NotNull ComponentLike message)
{
audiences.forEach(a -> a.sendMessage(message));
}
@Override
public void sendMessage(@NotNull Component message)
{
audiences.forEach(a -> a.sendMessage(message));
}
@Override
public void sendMessage(@NotNull Component message, ChatType.@NotNull Bound boundChatType)
{
audiences.forEach(a -> a.sendMessage(message, boundChatType));
}
@Override
public void sendMessage(@NotNull ComponentLike message, ChatType.@NotNull Bound boundChatType)
{
audiences.forEach(a -> a.sendMessage(message, boundChatType));
}
@Override
public void sendMessage(@NotNull SignedMessage signedMessage, ChatType.@NotNull Bound boundChatType)
{
audiences.forEach(a -> a.sendMessage(signedMessage, boundChatType));
}
@Override
public void deleteMessage(@NotNull SignedMessage signedMessage)
{
audiences.forEach(a -> a.deleteMessage(signedMessage));
}
@Override
public void deleteMessage(SignedMessage.@NotNull Signature signature)
{
audiences.forEach(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 ComponentLike message)
{
audiences.forEach(a -> a.sendActionBar(message));
}
@Override
public void sendActionBar(@NotNull Component message)
{
audiences.forEach(a -> a.sendActionBar(message));
}
@Override
public void sendPlayerListHeader(@NotNull ComponentLike header)
{
audiences.forEach(a -> a.sendPlayerListHeader(header));
}
@Override
public void sendPlayerListHeader(@NotNull Component header)
{
audiences.forEach(a -> a.sendPlayerListHeader(header));
}
@Override
public void sendPlayerListFooter(@NotNull ComponentLike footer)
{
audiences.forEach(a -> a.sendPlayerListFooter(footer));
}
@Override
public void sendPlayerListFooter(@NotNull Component footer)
{
audiences.forEach(a -> a.sendPlayerListFooter(footer));
}
@Override
public void sendPlayerListHeaderAndFooter(@NotNull ComponentLike header, @NotNull ComponentLike footer)
{
audiences.forEach(a -> a.sendPlayerListHeaderAndFooter(header, footer));
}
@Override
public void sendPlayerListHeaderAndFooter(@NotNull Component header, @NotNull Component footer)
{
audiences.forEach(a -> a.sendPlayerListHeaderAndFooter(header, footer));
}
@Override
public void showTitle(@NotNull Title title)
{
audiences.forEach(a -> a.showTitle(title));
}
@Override
public <T> void sendTitlePart(@NotNull TitlePart<T> part, @NotNull T value)
{
audiences.forEach(a -> a.sendTitlePart(part, value));
}
@Override
public void clearTitle()
{
audiences.forEach(Audience::clearTitle);
}
@Override
public void resetTitle()
{
audiences.forEach(Audience::resetTitle);
}
@Override
public void showBossBar(@NotNull BossBar bar)
{
audiences.forEach(a -> a.showBossBar(bar));
}
@Override
public void hideBossBar(@NotNull BossBar bar)
{
audiences.forEach(a -> a.hideBossBar(bar));
}
@Override
public void playSound(@NotNull Sound sound)
{
audiences.forEach(a -> a.playSound(sound));
}
@Override
public void playSound(@NotNull Sound sound, double x, double y, double z)
{
audiences.forEach(a -> a.playSound(sound, x, y, z));
}
@Override
public void playSound(@NotNull Sound sound, Sound.@NotNull Emitter emitter)
{
audiences.forEach(a -> a.playSound(sound, emitter));
}
@Override
public void stopSound(@NotNull Sound sound)
{
audiences.forEach(a -> a.stopSound(sound));
}
@Override
public void stopSound(@NotNull SoundStop stop)
{
audiences.forEach(a -> a.stopSound(stop));
}
@Override
public void openBook(Book.@NotNull Builder book)
{
audiences.forEach(a -> a.openBook(book));
}
@Override
public void openBook(@NotNull Book book)
{
audiences.forEach(a -> a.openBook(book));
}
}

View File

@ -0,0 +1,7 @@
package me.totalfreedom.economy;
public interface CompletedTransaction extends Transaction
{
TransactionResult getResult();
}

View File

@ -0,0 +1,8 @@
package me.totalfreedom.economy;
public interface EconomicEntity
{
EconomicEntityData getEconomicData();
String getName();
}

View File

@ -0,0 +1,14 @@
package me.totalfreedom.economy;
public interface EconomicEntityData
{
boolean areTransactionsFrozen();
long getBalance();
long addToBalance(final long amount);
long removeFromBalance(final long amount);
void setBalance(final long newBalance);
}

View File

@ -0,0 +1,13 @@
package me.totalfreedom.economy;
/**
* Please ensure that all modifications of {@link MutableTransaction} happen BEFORE it is passed to a {@link Transactor} implementation
*/
public interface MutableTransaction extends Transaction
{
long addToBalance(final long amount);
long removeFromBalance(final long amount);
void setBalance(final long newBalance);
}

View File

@ -0,0 +1,11 @@
package me.totalfreedom.economy;
public interface Transaction
{
EconomicEntity getSource();
EconomicEntity getDestination();
long getBalance();
}

View File

@ -0,0 +1,6 @@
package me.totalfreedom.economy;
public interface TransactionLogger
{
void logTransaction(CompletedTransaction completedTransaction);
}

View File

@ -0,0 +1,12 @@
package me.totalfreedom.economy;
import net.kyori.adventure.text.Component;
public interface TransactionResult
{
String getMessage();
boolean isSuccessful();
Component getComponent();
}

View File

@ -0,0 +1,6 @@
package me.totalfreedom.economy;
public interface Transactor
{
CompletedTransaction handleTransaction(MutableTransaction transaction);
}

View File

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

View File

@ -1,12 +1,13 @@
package me.totalfreedom.user;
import me.totalfreedom.security.perm.Group;
import me.totalfreedom.economy.EconomicEntityData;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.UUID;
public interface UserData
public interface UserData extends EconomicEntityData
{
@NotNull UUID getUniqueId();

View File

@ -0,0 +1,48 @@
package me.totalfreedom.utils;
import net.kyori.adventure.text.Component;
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.
*/
public class FreedomAdventure
{
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(Component component)
{
return PLAIN_TEXT_COMPONENT_SERIALIZER.serialize(component);
}
public static String toPlainText(Supplier<Component> supplier)
{
return toPlainText(supplier.get());
}
public static Supplier<String> supplyPlainText(Supplier<Component> supplier)
{
return new StringRepresentationSupplier(supplier.get());
}
public static Supplier<String> supplyPlainText(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);
}
}
}

View File

@ -1,12 +1,17 @@
package me.totalfreedom.utils;
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;
import java.util.function.Supplier;
public class FreedomLogger
public class FreedomLogger implements Audience
{
private final Logger logger;
private boolean debug = false;
@ -36,6 +41,20 @@ public class FreedomLogger
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(Component component)
{
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
@ -50,6 +69,19 @@ public class FreedomLogger
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(Supplier<Component> component)
{
return this.infoComponent(component.get());
}
/**
* This method allows you to log a warning to the console.
*
@ -60,6 +92,18 @@ public class FreedomLogger
logger.warn(message);
}
/**
* This method allows you to log a warning to the console.
*
* @param component The component to send.
*/
public void warnComponent(Component component)
{
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
@ -72,6 +116,20 @@ public class FreedomLogger
logger.error(message);
}
/**
* This method logs an error component to the console.
*
* @param component The message to send.
*/
public String errorComponent(Component component)
{
String plainText = FreedomAdventure.toPlainText(component);
logger.error(plainText);
return plainText;
}
/**
* This method allows you to log an exception directly to the console.
*
@ -98,6 +156,19 @@ public class FreedomLogger
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(Supplier<Component> component)
{
return this.errorComponent(component.get());
}
/**
* This method allows you to log a debug message to the console.
* This method will only log if debug mode is enabled.
@ -110,6 +181,21 @@ public class FreedomLogger
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(Component component)
{
String plainText = FreedomAdventure.toPlainText(component);
this.debug(plainText);
return plainText;
}
/**
* This method allows you to log a debug message to the console,
* while also returning a Component that could be used to
@ -128,4 +214,59 @@ public class FreedomLogger
}
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(Supplier<Component> component)
{
if (debug)
{
return this.debugComponent(component.get());
}
return "";
}
@Override
public void sendMessage(@NotNull ComponentLike message)
{
Component component = ComponentLike.unbox(message);
if (component == null)
{
this.info("**null component-like**");
return;
}
this.infoComponent(component);
}
@Override
public void sendMessage(@NotNull Component message)
{
this.infoComponent(message);
}
@Override
public void sendMessage(@NotNull Component message, ChatType.@NotNull Bound boundChatType)
{
this.infoComponent(message);
}
@Override
public void sendMessage(@NotNull ComponentLike message, ChatType.@NotNull Bound boundChatType)
{
this.sendMessage(message);
}
@Override
public void sendMessage(@NotNull SignedMessage signedMessage, 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.
}
}

View File

@ -55,3 +55,43 @@ This proof-of-concept also uses the following libraries:
[<img src="https://img.shields.io/static/v1?label=Developer&message=Video&color=blueviolet&style=for-the-badge&logo=intellijidea">](https://github.com/VideoGameSmash12)
<br />
[<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
- [ ] Command API
- [ ] Particle API
- [x] User API
- [ ] Ban API
- [x] Service API
- [x] Task API
- [x] Permissions API
- [ ] Configuration API
- [ ] Event API
Datura:
- [ ] Permission Handling
- [ ] Permission Registration & Assignment
- [ ] SQL Data Handling
- [ ] Configuration Implementations
- [ ] User Data Implementations
- [ ] Banning Implementation
- [ ] Punishment Systems (e.x. Locker, Halter, Muter, Cager)
Fossil:
- [x] Economy Implementation
- [ ] Particle Implementation / Trails
- [ ] Command Implementations
- [ ] Implement a shop for the economy
- [ ] Chat reaction / game system
- [ ] Jumppads
Corvo:
- [ ] Service Implementation
- [ ] Service Handling
- [ ] Task Implementation
- [ ] Task Management
- [ ] Event (Project) Implementations
- [ ] Listener (Bukkit) Implementations