diff --git a/Datura/src/main/java/me/totalfreedom/datura/user/SimpleUserData.java b/Datura/src/main/java/me/totalfreedom/datura/user/SimpleUserData.java
index cb6db2a..82d086d 100644
--- a/Datura/src/main/java/me/totalfreedom/datura/user/SimpleUserData.java
+++ b/Datura/src/main/java/me/totalfreedom/datura/user/SimpleUserData.java
@@ -15,6 +15,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
{
@@ -26,6 +27,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)
{
@@ -42,7 +45,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;
@@ -52,6 +57,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(SQL sql, String uuid)
@@ -82,7 +89,9 @@ public class SimpleUserData implements UserData
boolean frozen = result.getBoolean("frozen");
boolean canInteract = result.getBoolean("canInteract");
boolean caged = result.getBoolean("caged");
- return new SimpleUserData(u, username, user, group, playtime, frozen, canInteract, 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)
{
@@ -197,4 +206,34 @@ public class SimpleUserData implements UserData
{
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);
+ }
}
diff --git a/Fossil/src/main/java/me/totalfreedom/fossil/economy/SimpleCompletedTransaction.java b/Fossil/src/main/java/me/totalfreedom/fossil/economy/SimpleCompletedTransaction.java
new file mode 100644
index 0000000..b7ba257
--- /dev/null
+++ b/Fossil/src/main/java/me/totalfreedom/fossil/economy/SimpleCompletedTransaction.java
@@ -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;
+ }
+}
diff --git a/Fossil/src/main/java/me/totalfreedom/fossil/economy/SimpleLoggedTransactor.java b/Fossil/src/main/java/me/totalfreedom/fossil/economy/SimpleLoggedTransactor.java
new file mode 100644
index 0000000..4bf35ca
--- /dev/null
+++ b/Fossil/src/main/java/me/totalfreedom/fossil/economy/SimpleLoggedTransactor.java
@@ -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;
+ }
+}
diff --git a/Fossil/src/main/java/me/totalfreedom/fossil/economy/SimpleMutableTransaction.java b/Fossil/src/main/java/me/totalfreedom/fossil/economy/SimpleMutableTransaction.java
new file mode 100644
index 0000000..c99cb65
--- /dev/null
+++ b/Fossil/src/main/java/me/totalfreedom/fossil/economy/SimpleMutableTransaction.java
@@ -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);
+ }
+}
diff --git a/Fossil/src/main/java/me/totalfreedom/fossil/economy/SimpleTransaction.java b/Fossil/src/main/java/me/totalfreedom/fossil/economy/SimpleTransaction.java
new file mode 100644
index 0000000..4e3e008
--- /dev/null
+++ b/Fossil/src/main/java/me/totalfreedom/fossil/economy/SimpleTransaction.java
@@ -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();
+ }
+}
diff --git a/Fossil/src/main/java/me/totalfreedom/fossil/economy/SimpleTransactionLogger.java b/Fossil/src/main/java/me/totalfreedom/fossil/economy/SimpleTransactionLogger.java
new file mode 100644
index 0000000..e53551b
--- /dev/null
+++ b/Fossil/src/main/java/me/totalfreedom/fossil/economy/SimpleTransactionLogger.java
@@ -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;
+ }
+}
diff --git a/Fossil/src/main/java/me/totalfreedom/fossil/economy/SimpleTransactionResult.java b/Fossil/src/main/java/me/totalfreedom/fossil/economy/SimpleTransactionResult.java
new file mode 100644
index 0000000..258ae84
--- /dev/null
+++ b/Fossil/src/main/java/me/totalfreedom/fossil/economy/SimpleTransactionResult.java
@@ -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;
+ }
+}
diff --git a/Fossil/src/main/java/me/totalfreedom/fossil/economy/SimpleTransactor.java b/Fossil/src/main/java/me/totalfreedom/fossil/economy/SimpleTransactor.java
new file mode 100644
index 0000000..735571e
--- /dev/null
+++ b/Fossil/src/main/java/me/totalfreedom/fossil/economy/SimpleTransactor.java
@@ -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);
+ }
+}
diff --git a/Patchwork/src/main/java/me/totalfreedom/audience/MutableAudienceForwarder.java b/Patchwork/src/main/java/me/totalfreedom/audience/MutableAudienceForwarder.java
new file mode 100644
index 0000000..60fc88b
--- /dev/null
+++ b/Patchwork/src/main/java/me/totalfreedom/audience/MutableAudienceForwarder.java
@@ -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.
+ *
+ * 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 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 void sendTitlePart(@NotNull TitlePart 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));
+ }
+}
diff --git a/Patchwork/src/main/java/me/totalfreedom/economy/CompletedTransaction.java b/Patchwork/src/main/java/me/totalfreedom/economy/CompletedTransaction.java
new file mode 100644
index 0000000..7b58dfd
--- /dev/null
+++ b/Patchwork/src/main/java/me/totalfreedom/economy/CompletedTransaction.java
@@ -0,0 +1,7 @@
+package me.totalfreedom.economy;
+
+public interface CompletedTransaction extends Transaction
+{
+
+ TransactionResult getResult();
+}
diff --git a/Patchwork/src/main/java/me/totalfreedom/economy/EconomicEntity.java b/Patchwork/src/main/java/me/totalfreedom/economy/EconomicEntity.java
new file mode 100644
index 0000000..1855d1e
--- /dev/null
+++ b/Patchwork/src/main/java/me/totalfreedom/economy/EconomicEntity.java
@@ -0,0 +1,8 @@
+package me.totalfreedom.economy;
+
+public interface EconomicEntity
+{
+ EconomicEntityData getEconomicData();
+
+ String getName();
+}
diff --git a/Patchwork/src/main/java/me/totalfreedom/economy/EconomicEntityData.java b/Patchwork/src/main/java/me/totalfreedom/economy/EconomicEntityData.java
new file mode 100644
index 0000000..308a6c0
--- /dev/null
+++ b/Patchwork/src/main/java/me/totalfreedom/economy/EconomicEntityData.java
@@ -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);
+}
diff --git a/Patchwork/src/main/java/me/totalfreedom/economy/MutableTransaction.java b/Patchwork/src/main/java/me/totalfreedom/economy/MutableTransaction.java
new file mode 100644
index 0000000..8548fa7
--- /dev/null
+++ b/Patchwork/src/main/java/me/totalfreedom/economy/MutableTransaction.java
@@ -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);
+}
diff --git a/Patchwork/src/main/java/me/totalfreedom/economy/Transaction.java b/Patchwork/src/main/java/me/totalfreedom/economy/Transaction.java
new file mode 100644
index 0000000..eb0e310
--- /dev/null
+++ b/Patchwork/src/main/java/me/totalfreedom/economy/Transaction.java
@@ -0,0 +1,11 @@
+package me.totalfreedom.economy;
+
+public interface Transaction
+{
+ EconomicEntity getSource();
+
+ EconomicEntity getDestination();
+
+ long getBalance();
+
+}
diff --git a/Patchwork/src/main/java/me/totalfreedom/economy/TransactionLogger.java b/Patchwork/src/main/java/me/totalfreedom/economy/TransactionLogger.java
new file mode 100644
index 0000000..f8dce2d
--- /dev/null
+++ b/Patchwork/src/main/java/me/totalfreedom/economy/TransactionLogger.java
@@ -0,0 +1,6 @@
+package me.totalfreedom.economy;
+
+public interface TransactionLogger
+{
+ void logTransaction(CompletedTransaction completedTransaction);
+}
diff --git a/Patchwork/src/main/java/me/totalfreedom/economy/TransactionResult.java b/Patchwork/src/main/java/me/totalfreedom/economy/TransactionResult.java
new file mode 100644
index 0000000..fd4a13d
--- /dev/null
+++ b/Patchwork/src/main/java/me/totalfreedom/economy/TransactionResult.java
@@ -0,0 +1,12 @@
+package me.totalfreedom.economy;
+
+import net.kyori.adventure.text.Component;
+
+public interface TransactionResult
+{
+ String getMessage();
+
+ boolean isSuccessful();
+
+ Component getComponent();
+}
diff --git a/Patchwork/src/main/java/me/totalfreedom/economy/Transactor.java b/Patchwork/src/main/java/me/totalfreedom/economy/Transactor.java
new file mode 100644
index 0000000..8fd9c47
--- /dev/null
+++ b/Patchwork/src/main/java/me/totalfreedom/economy/Transactor.java
@@ -0,0 +1,6 @@
+package me.totalfreedom.economy;
+
+public interface Transactor
+{
+ CompletedTransaction handleTransaction(MutableTransaction transaction);
+}
diff --git a/Patchwork/src/main/java/me/totalfreedom/user/User.java b/Patchwork/src/main/java/me/totalfreedom/user/User.java
index 222e2a9..61e8ca3 100644
--- a/Patchwork/src/main/java/me/totalfreedom/user/User.java
+++ b/Patchwork/src/main/java/me/totalfreedom/user/User.java
@@ -1,10 +1,25 @@
package me.totalfreedom.user;
+import me.totalfreedom.economy.EconomicEntity;
+import me.totalfreedom.economy.EconomicEntityData;
import me.totalfreedom.security.PermissionHolder;
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();
diff --git a/Patchwork/src/main/java/me/totalfreedom/user/UserData.java b/Patchwork/src/main/java/me/totalfreedom/user/UserData.java
index ac2e277..f7ac1d1 100644
--- a/Patchwork/src/main/java/me/totalfreedom/user/UserData.java
+++ b/Patchwork/src/main/java/me/totalfreedom/user/UserData.java
@@ -1,12 +1,13 @@
package me.totalfreedom.user;
+import me.totalfreedom.economy.EconomicEntityData;
import me.totalfreedom.security.Group;
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();
diff --git a/Patchwork/src/main/java/me/totalfreedom/utils/FreedomAdventure.java b/Patchwork/src/main/java/me/totalfreedom/utils/FreedomAdventure.java
new file mode 100644
index 0000000..cd0fa3d
--- /dev/null
+++ b/Patchwork/src/main/java/me/totalfreedom/utils/FreedomAdventure.java
@@ -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 supplier)
+ {
+ return toPlainText(supplier.get());
+ }
+
+ public static Supplier supplyPlainText(Supplier supplier)
+ {
+ return new StringRepresentationSupplier(supplier.get());
+ }
+
+ public static Supplier supplyPlainText(Component component)
+ {
+ return new StringRepresentationSupplier(component);
+ }
+
+ private record StringRepresentationSupplier(Component component) implements Supplier
+ {
+ @Override
+ public String get()
+ {
+ return PLAIN_TEXT_COMPONENT_SERIALIZER.serialize(component);
+ }
+ }
+}
diff --git a/Patchwork/src/main/java/me/totalfreedom/utils/FreedomLogger.java b/Patchwork/src/main/java/me/totalfreedom/utils/FreedomLogger.java
index c0deaa1..6172443 100644
--- a/Patchwork/src/main/java/me/totalfreedom/utils/FreedomLogger.java
+++ b/Patchwork/src/main/java/me/totalfreedom/utils/FreedomLogger.java
@@ -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)
+ {
+ 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)
+ {
+ 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)
+ {
+ 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.
+ }
}