1
0
Fork 0
mirror of https://github.com/xHyroM/lighteco.git synced 2024-11-23 15:21:06 +01:00

Compare commits

..

12 commits

Author SHA1 Message Date
cd5310e718
Merge 5b8045236d into 6cd7ad0901 2024-08-27 14:22:53 +00:00
5b8045236d
feat: more sponge things 2024-08-27 16:22:50 +02:00
240b6a3f3f
rebase 2024-08-27 16:03:25 +02:00
6cd7ad0901
build: add spotless formatter (#9) 2024-07-16 18:22:32 +02:00
23e73cb5da
feat!: better command abstraction (#4)
* feat: better command abstraction

* feat: OfflineUserArgument type

* feat: balance, balance other cmds in common & double, integer arg

* feat: command manager, better arguments abstraction

* feat: map args

* feat: some things

* feat(better command abstraction)!: switch to brigadier (#8)

* feat: switch to brigadier

* some updates

* feat: make suggestion provider work

* supress warnings

* feat: pay & set command

* feat: give & take command, better suggestions, permissions

* refactor: cleanup

* fix: dont register pay if currency is not payable

* fix: send commands only if you have perms

* make second test currency not payable

* feat: use command's description & name

* refactor: cleanup, rename bukkit to paper

* refactor: cleanup, rename bukkit to paper

* feat: add built-in info command

* ci: build paper, not bukkit

* refactor: better test organization

* refactor: better test organization

* feat: locks

* feat: finish locking

* feat: handle missing user

* refactor: move methods to CommandHelper

* refactor: change minimum api version

* feat: console support
2024-07-16 18:04:37 +02:00
75a1e8274a
build: support 1.21 2024-06-14 22:18:08 +02:00
08050e15e5
build: update grale 2024-05-08 18:03:28 +02:00
b7c90ae5c8
fix: don't register balance commands as main currency command 2024-02-25 18:00:28 +01:00
9c0968ded2
build: bump deps 2024-01-09 21:28:03 +01:00
35f94eac3b
feat: messaging (#7)
* feat: messaging

* fix: some things

* feat: make it actually working

* feat: disable messaging by default

* fix: avoid infinity loop

* feat: multiple channels

* remove todo message

* fix: global channel should be lighteco:messages

* fix: check if CHANNELS not null

* refactor: set channels in constructor

* fix: push user updates on quit when user is dirty
2023-12-24 15:46:13 +01:00
b1d5ae7c09
feat(pay): send message to target (#6)
* feat(pay): send message for target

* fix: set audience on enable
2023-10-30 16:08:31 +01:00
572738dab5
build: strip commit hash 2023-10-30 12:15:15 +01:00
153 changed files with 2935 additions and 1258 deletions

View file

@ -12,8 +12,7 @@ jobs:
distribution: 'adopt'
- name: Build with Gradle
run: |
./gradlew shadowJar -p bukkittest
./gradlew shadowJar -p bukkit
./gradlew shadowJar -p paper
./gradlew shadowJar -p currency-money
- uses: actions/upload-artifact@v3
with:

1
.gitignore vendored
View file

@ -7,6 +7,7 @@
### ForgeGradle ###
# Minecraft client/server files
run/
runServer/
### Intellij ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider

View file

@ -3,6 +3,12 @@ plugins {
}
dependencies {
api("net.kyori:adventure-api:4.12.0") {
exclude(module = "adventure-bom")
exclude(module = "checker-qual")
exclude(module = "annotations")
}
compileOnly("org.projectlombok:lombok:1.18.28")
annotationProcessor("org.projectlombok:lombok:1.18.28")

View file

@ -3,10 +3,14 @@ package dev.xhyrom.lighteco.api;
import dev.xhyrom.lighteco.api.manager.CommandManager;
import dev.xhyrom.lighteco.api.manager.CurrencyManager;
import dev.xhyrom.lighteco.api.manager.UserManager;
import dev.xhyrom.lighteco.api.messaging.MessagingService;
import dev.xhyrom.lighteco.api.platform.Platform;
import dev.xhyrom.lighteco.api.platform.PlayerAdapter;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Optional;
public interface LightEco {
/**
* Gets the {@link Platform}, which represents the current platform the
@ -37,6 +41,13 @@ public interface LightEco {
*/
@NonNull CommandManager getCommandManager();
/**
* Gets the {@link MessagingService}, which manages the messaging.
*
* @return the messaging service
*/
@NonNull Optional<MessagingService> getMessagingService();
/**
* Gets the {@link PlayerAdapter} for a player class.
*

View file

@ -1,10 +1,11 @@
package dev.xhyrom.lighteco.api;
import lombok.experimental.UtilityClass;
import org.checkerframework.checker.nullness.qual.NonNull;
import static org.jetbrains.annotations.ApiStatus.Internal;
import lombok.experimental.UtilityClass;
import org.checkerframework.checker.nullness.qual.NonNull;
@UtilityClass
public final class LightEcoProvider {
private static LightEco instance;

View file

@ -1,6 +1,7 @@
package dev.xhyrom.lighteco.api.manager;
import dev.xhyrom.lighteco.api.model.currency.Currency;
import org.checkerframework.checker.nullness.qual.NonNull;
public interface CommandManager {

View file

@ -1,6 +1,7 @@
package dev.xhyrom.lighteco.api.manager;
import dev.xhyrom.lighteco.api.model.currency.Currency;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;

View file

@ -1,6 +1,7 @@
package dev.xhyrom.lighteco.api.manager;
import dev.xhyrom.lighteco.api.model.user.User;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
@ -9,6 +10,7 @@ import java.util.concurrent.CompletableFuture;
public interface UserManager {
@NonNull CompletableFuture<User> loadUser(@NonNull UUID uniqueId);
@NonNull CompletableFuture<User> loadUser(@NonNull UUID uniqueId, String username);
@NonNull CompletableFuture<Void> saveUser(@NonNull User user);

View file

@ -0,0 +1,8 @@
package dev.xhyrom.lighteco.api.messaging;
import dev.xhyrom.lighteco.api.model.currency.Currency;
import dev.xhyrom.lighteco.api.model.user.User;
public interface MessagingService {
void pushUserUpdate(User user, Currency currency);
}

View file

@ -0,0 +1,11 @@
package dev.xhyrom.lighteco.api.messenger;
import dev.xhyrom.lighteco.api.messenger.message.Message;
import org.checkerframework.checker.nullness.qual.NonNull;
public interface IncomingMessageConsumer {
void consumeIncomingMessage(@NonNull Message message);
void consumeRawIncomingMessage(@NonNull String message);
}

View file

@ -0,0 +1,12 @@
package dev.xhyrom.lighteco.api.messenger;
import dev.xhyrom.lighteco.api.messenger.message.OutgoingMessage;
import org.checkerframework.checker.nullness.qual.NonNull;
public interface Messenger extends AutoCloseable {
void sendOutgoingMessage(@NonNull OutgoingMessage message, boolean global);
@Override
default void close() {}
}

View file

@ -0,0 +1,7 @@
package dev.xhyrom.lighteco.api.messenger;
import org.checkerframework.checker.nullness.qual.NonNull;
public interface MessengerProvider {
@NonNull Messenger obtain(@NonNull IncomingMessageConsumer consumer);
}

View file

@ -0,0 +1,12 @@
package dev.xhyrom.lighteco.api.messenger.message;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.UUID;
/**
* Represents a message that can be received by a {@link dev.xhyrom.lighteco.api.messenger.Messenger}.
*/
public interface Message {
@NonNull UUID getId();
}

View file

@ -0,0 +1,15 @@
package dev.xhyrom.lighteco.api.messenger.message;
import org.checkerframework.checker.nullness.qual.NonNull;
/**
* Represents a message that can be sent via a {@link dev.xhyrom.lighteco.api.messenger.Messenger}.
*/
public interface OutgoingMessage extends Message {
/**
* Serializes message into a string.
*
* @return serialized message
*/
@NonNull String serialize();
}

View file

@ -0,0 +1,24 @@
package dev.xhyrom.lighteco.api.messenger.message.type;
import dev.xhyrom.lighteco.api.messenger.message.Message;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.math.BigDecimal;
import java.util.UUID;
/**
* Represents a message that is sent when a user updates their profile.
*/
public interface UserUpdateMessage extends Message {
/**
* Gets the unique id of the user that updated their profile.
*
* @return the user's unique id
*/
@NonNull UUID getUserUniqueId();
@NonNull String getCurrencyIdentifier();
@NonNull BigDecimal getNewBalance();
}

View file

@ -24,7 +24,8 @@ public interface Currency {
*/
default String[] getIdentifierAliases() {
return new String[0];
};
}
;
/**
* Returns the type of the currency, either {@link Type#LOCAL} or {@link Type#GLOBAL}
@ -79,7 +80,8 @@ public interface Currency {
*/
default BigDecimal calculateTax(User user, BigDecimal amount) {
return BigDecimal.ZERO;
};
}
;
/**
* Represents the type of currency

View file

@ -1,6 +1,9 @@
package dev.xhyrom.lighteco.api.model.user;
import dev.xhyrom.lighteco.api.model.currency.Currency;
import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
@ -49,7 +52,8 @@ public interface User {
* @param amount the amount
* @throws IllegalArgumentException if the amount is negative
*/
void deposit(@NonNull Currency currency, @NonNull BigDecimal amount) throws IllegalArgumentException;
void deposit(@NonNull Currency currency, @NonNull BigDecimal amount)
throws IllegalArgumentException;
/**
* Subtract the specified amount from the balance of this user for the specified currency.
@ -58,5 +62,14 @@ public interface User {
* @param amount the amount
* @throws IllegalArgumentException if the amount is negative
*/
void withdraw(@NonNull Currency currency, @NonNull BigDecimal amount) throws IllegalArgumentException;
void withdraw(@NonNull Currency currency, @NonNull BigDecimal amount)
throws IllegalArgumentException;
/**
* Send a message to this user.
* Message will be silently dropped if the user is offline.
*
* @param message the message
*/
void sendMessage(Component message);
}

View file

@ -11,7 +11,7 @@ public interface Platform {
@NonNull Type getType();
enum Type {
BUKKIT("Bukkit"),
PAPER("Paper"),
SPONGE("Sponge"),
VELOCITY("Velocity"),
BUNGEECORD("BungeeCord");
@ -27,7 +27,7 @@ public interface Platform {
}
public boolean isLocal() {
return this == BUKKIT || this == SPONGE;
return this == PAPER || this == SPONGE;
}
public boolean isProxy() {

View file

@ -1,6 +1,7 @@
package dev.xhyrom.lighteco.api.platform;
import dev.xhyrom.lighteco.api.model.user.User;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.concurrent.CompletableFuture;

View file

@ -2,6 +2,7 @@ package dev.xhyrom.lighteco.api.storage;
import dev.xhyrom.lighteco.api.model.currency.Currency;
import dev.xhyrom.lighteco.api.model.user.User;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
@ -23,7 +24,10 @@ public interface StorageProvider {
void shutdown() throws Exception;
default void registerCurrency(@NonNull Currency currency) throws Exception {}
@NonNull User loadUser(@NonNull UUID uniqueId, @Nullable String username) throws Exception;
void saveUser(@NonNull User user) throws Exception;
void saveUsers(@NonNull User... users) throws Exception;
}

View file

@ -3,6 +3,7 @@ import java.io.ByteArrayOutputStream
plugins {
id("java")
id("org.sonarqube") version "4.2.1.3168"
id("com.diffplug.spotless") version "6.25.0"
}
val majorVersion = 0
@ -10,6 +11,12 @@ val minorVersion = 1
val patchVersion = determinePatchVersion(project)
val commitHash = determineCommitHash(project)
defaultTasks("spotlessApply")
repositories {
mavenCentral()
}
allprojects {
group = "dev.xhyrom"
version = "$majorVersion.$minorVersion.$patchVersion"
@ -28,6 +35,19 @@ subprojects {
repositories {
mavenCentral()
maven("https://storehouse.okaeri.eu/repository/maven-public/")
maven("https://libraries.minecraft.net")
}
}
spotless {
java {
importOrder()
removeUnusedImports()
palantirJavaFormat().style("AOSP")
formatAnnotations()
target("api/src/main/java/**", "common/src/main/java/**", "currency-money/src/main/java/**", "paper/src/main/java/**", "sponge-8/src/main/java/**", "test/**/src/main/java/**")
}
}
@ -56,5 +76,5 @@ fun determineCommitHash(project: Project): String {
standardOutput = commitHashInfo
}
return commitHashInfo.toString()
return commitHashInfo.toString().strip()
}

View file

@ -4,7 +4,12 @@ plugins {
}
java {
// toolchain.languageVersion.set(JavaLanguageVersion.of(17))
targetCompatibility = JavaVersion.VERSION_17
sourceCompatibility = JavaVersion.VERSION_17
toolchain {
languageVersion.set(JavaLanguageVersion.of(17))
}
withSourcesJar()
}
@ -13,6 +18,7 @@ tasks {
filesMatching(listOf("plugin.yml")) {
expand(
"name" to project.name,
"coreName" to "LightEco",
"version" to project.version,
"description" to project.description,
"author" to "xHyroM"

View file

@ -1,67 +0,0 @@
package dev.xhyrom.lighteco.bukkit;
import dev.xhyrom.lighteco.api.LightEco;
import dev.xhyrom.lighteco.api.manager.ContextManager;
import dev.xhyrom.lighteco.api.platform.Platform;
import dev.xhyrom.lighteco.bukkit.hooks.Hooks;
import dev.xhyrom.lighteco.bukkit.listeners.BukkitConnectionListener;
import dev.xhyrom.lighteco.bukkit.manager.BukkitCommandManager;
import dev.xhyrom.lighteco.bukkit.manager.BukkitContextManager;
import dev.xhyrom.lighteco.common.manager.currency.StandardCurrencyManager;
import dev.xhyrom.lighteco.common.plugin.AbstractLightEcoPlugin;
import dev.xhyrom.lighteco.common.manager.user.StandardUserManager;
import lombok.Getter;
import org.bukkit.entity.Player;
import org.bukkit.plugin.ServicePriority;
import org.checkerframework.checker.nullness.qual.NonNull;
@Getter
public class BukkitLightEcoPlugin extends AbstractLightEcoPlugin {
private final BukkitLightEcoBootstrap bootstrap;
@Getter
private StandardUserManager userManager;
@Getter
private StandardCurrencyManager currencyManager;
@Getter
private BukkitCommandManager commandManager;
@Getter
private ContextManager<Player> contextManager;
public BukkitLightEcoPlugin(BukkitLightEcoBootstrap bootstrap) {
this.bootstrap = bootstrap;
}
@Override
protected void registerListeners() {
this.bootstrap.getLoader().getServer().getPluginManager().registerEvents(new BukkitConnectionListener(this), this.bootstrap.getLoader());
}
@Override
public void setupManagers() {
this.userManager = new StandardUserManager(this);
this.currencyManager = new StandardCurrencyManager(this);
this.commandManager = new BukkitCommandManager(this);
this.contextManager = new BukkitContextManager();
}
@Override
protected void registerApiOnPlatform(LightEco api) {
this.getBootstrap().getLoader().getServer().getServicesManager().register(LightEco.class, api, this.getBootstrap().getLoader(), ServicePriority.Normal);
}
@Override
protected void registerPlatformHooks() {
Hooks.register(this);
}
@Override
protected void removePlatformHooks() {
Hooks.unregister();
}
@Override
public Platform.@NonNull Type getPlatformType() {
return Platform.Type.BUKKIT;
}
}

View file

@ -1,59 +0,0 @@
package dev.xhyrom.lighteco.bukkit.commands;
import dev.jorel.commandapi.CommandAPICommand;
import dev.jorel.commandapi.arguments.OfflinePlayerArgument;
import dev.jorel.commandapi.executors.CommandArguments;
import dev.xhyrom.lighteco.bukkit.chat.BukkitCommandSender;
import dev.xhyrom.lighteco.bukkit.manager.BukkitCommandManager;
import dev.xhyrom.lighteco.common.model.currency.Currency;
import lombok.RequiredArgsConstructor;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.CommandSender;
@RequiredArgsConstructor
public class BalanceCommand implements Command {
private final BukkitCommandManager manager;
private final String name;
private final Currency currency;
private final String permissionBase;
@Override
public CommandAPICommand[] multipleBuild() {
return new CommandAPICommand[]{
new CommandAPICommand(name)
.withPermission(permissionBase + "balance.others")
.withArguments(new OfflinePlayerArgument("target"))
.executes((sender, args) -> {
this.handleBalance(sender, args, currency);
}),
new CommandAPICommand(name)
.withPermission(permissionBase + "balance")
.executesPlayer((sender, args) -> {
this.handleBalance(sender, args, currency);
})
};
}
private void handleBalance(CommandSender originalSender, CommandArguments args, Currency currency) {
BukkitCommandSender sender = new BukkitCommandSender(originalSender, this.manager.audienceFactory);
OfflinePlayer target = (OfflinePlayer) args.get("target");
if (target == null) {
this.manager.onBalance(sender, currency);
return;
}
this.manager.plugin.getUserManager().loadUser(target.getUniqueId())
.thenAccept(result -> {
String username = result.getUsername() == null ?
target.getName() != null
? target.getName()
: args.getRaw("target")
: result.getUsername();
result.setUsername(username);
this.manager.onBalance(sender, currency, result);
});
}
}

View file

@ -1,13 +0,0 @@
package dev.xhyrom.lighteco.bukkit.commands;
import dev.jorel.commandapi.CommandAPICommand;
public interface Command {
default CommandAPICommand build() {
return null;
}
default CommandAPICommand[] multipleBuild() {
return new CommandAPICommand[0];
}
}

View file

@ -1,54 +0,0 @@
package dev.xhyrom.lighteco.bukkit.commands;
import dev.jorel.commandapi.CommandAPICommand;
import dev.jorel.commandapi.arguments.DoubleArgument;
import dev.jorel.commandapi.arguments.IntegerArgument;
import dev.jorel.commandapi.arguments.OfflinePlayerArgument;
import dev.jorel.commandapi.executors.CommandArguments;
import dev.xhyrom.lighteco.bukkit.chat.BukkitCommandSender;
import dev.xhyrom.lighteco.bukkit.manager.BukkitCommandManager;
import dev.xhyrom.lighteco.common.model.currency.Currency;
import lombok.RequiredArgsConstructor;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.CommandSender;
import java.math.BigDecimal;
@RequiredArgsConstructor
public class GiveCommand implements Command {
private final BukkitCommandManager manager;
private final Currency currency;
private final String permissionBase;
@Override
public CommandAPICommand build() {
return new CommandAPICommand("give")
.withPermission(permissionBase + "give")
.withArguments(
new OfflinePlayerArgument("target"),
currency.getProxy().fractionalDigits() > 0
? new DoubleArgument("amount", 1)
: new IntegerArgument("amount", 1)
)
.executes((sender, args) -> {
this.handleGive(sender, args, currency);
});
}
private void handleGive(CommandSender originalSender, CommandArguments args, Currency currency) {
BukkitCommandSender sender = new BukkitCommandSender(originalSender, this.manager.audienceFactory);
OfflinePlayer target = (OfflinePlayer) args.get("target");
BigDecimal amount = BigDecimal.valueOf(Double.parseDouble(args.getRaw("amount")));
if (!this.manager.canUse(sender, currency)) return;
this.manager.plugin.getUserManager().loadUser(target.getUniqueId())
.thenAccept(result -> {
String name = target.getName() != null ? target.getName() : args.getRaw("target");
result.setUsername(name);
this.manager.onGive(sender, currency, result, amount);
});
}
}

View file

@ -1,57 +0,0 @@
package dev.xhyrom.lighteco.bukkit.commands;
import dev.jorel.commandapi.CommandAPICommand;
import dev.jorel.commandapi.arguments.DoubleArgument;
import dev.jorel.commandapi.arguments.IntegerArgument;
import dev.jorel.commandapi.arguments.OfflinePlayerArgument;
import dev.jorel.commandapi.executors.CommandArguments;
import dev.xhyrom.lighteco.bukkit.chat.BukkitCommandSender;
import dev.xhyrom.lighteco.bukkit.manager.BukkitCommandManager;
import dev.xhyrom.lighteco.common.model.currency.Currency;
import lombok.RequiredArgsConstructor;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.CommandSender;
import java.math.BigDecimal;
@RequiredArgsConstructor
public class PayCommand implements Command {
private final BukkitCommandManager manager;
private final Currency currency;
private final String permissionBase;
@Override
public CommandAPICommand build() {
return new CommandAPICommand("pay")
.withPermission(permissionBase + "pay")
.withArguments(
new OfflinePlayerArgument("target"),
currency.getProxy().fractionalDigits() > 0
? new DoubleArgument("amount", 1)
: new IntegerArgument("amount", 1)
)
.executesPlayer((sender, args) -> {
this.handlePay(sender, args, currency);
});
}
private void handlePay(CommandSender originalSender, CommandArguments args, Currency currency) {
BukkitCommandSender sender = new BukkitCommandSender(originalSender, this.manager.audienceFactory);
OfflinePlayer target = (OfflinePlayer) args.get("target");
BigDecimal amount = BigDecimal.valueOf(Double.parseDouble(args.getRaw("amount")));
if (!this.manager.canUse(sender, currency)) return;
this.manager.plugin.getUserManager().loadUser(target.getUniqueId())
.thenAccept(result -> {
String username = result.getUsername() == null ?
target.getName() != null
? target.getName()
: args.getRaw("target")
: result.getUsername();
result.setUsername(username);
this.manager.onPay(sender, currency, result, amount);
});
}
}

View file

@ -1,54 +0,0 @@
package dev.xhyrom.lighteco.bukkit.commands;
import dev.jorel.commandapi.CommandAPICommand;
import dev.jorel.commandapi.arguments.DoubleArgument;
import dev.jorel.commandapi.arguments.IntegerArgument;
import dev.jorel.commandapi.arguments.OfflinePlayerArgument;
import dev.jorel.commandapi.executors.CommandArguments;
import dev.xhyrom.lighteco.bukkit.chat.BukkitCommandSender;
import dev.xhyrom.lighteco.bukkit.manager.BukkitCommandManager;
import dev.xhyrom.lighteco.common.model.currency.Currency;
import lombok.RequiredArgsConstructor;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.CommandSender;
import java.math.BigDecimal;
@RequiredArgsConstructor
public class SetCommand implements Command {
private final BukkitCommandManager manager;
private final Currency currency;
private final String permissionBase;
@Override
public CommandAPICommand build() {
return new CommandAPICommand("set")
.withPermission(permissionBase + "set")
.withArguments(
new OfflinePlayerArgument("target"),
currency.getProxy().fractionalDigits() > 0
? new DoubleArgument("amount", 0)
: new IntegerArgument("amount", 0)
)
.executes((sender, args) -> {
this.handleSet(sender, args, currency);
});
}
private void handleSet(CommandSender originalSender, CommandArguments args, Currency currency) {
BukkitCommandSender sender = new BukkitCommandSender(originalSender, this.manager.audienceFactory);
OfflinePlayer target = (OfflinePlayer) args.get("target");
BigDecimal amount = BigDecimal.valueOf(Double.parseDouble(args.getRaw("amount")));
if (!this.manager.canUse(sender, currency)) return;
this.manager.plugin.getUserManager().loadUser(target.getUniqueId())
.thenAccept(result -> {
String name = target.getName() != null ? target.getName() : args.getRaw("target");
result.setUsername(name);
this.manager.onSet(sender, currency, result, amount);
});
}
}

View file

@ -1,54 +0,0 @@
package dev.xhyrom.lighteco.bukkit.commands;
import dev.jorel.commandapi.CommandAPICommand;
import dev.jorel.commandapi.arguments.DoubleArgument;
import dev.jorel.commandapi.arguments.IntegerArgument;
import dev.jorel.commandapi.arguments.OfflinePlayerArgument;
import dev.jorel.commandapi.executors.CommandArguments;
import dev.xhyrom.lighteco.bukkit.chat.BukkitCommandSender;
import dev.xhyrom.lighteco.bukkit.manager.BukkitCommandManager;
import dev.xhyrom.lighteco.common.model.currency.Currency;
import lombok.RequiredArgsConstructor;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.CommandSender;
import java.math.BigDecimal;
@RequiredArgsConstructor
public class TakeCommand implements Command {
private final BukkitCommandManager manager;
private final Currency currency;
private final String permissionBase;
@Override
public CommandAPICommand build() {
return new CommandAPICommand("take")
.withPermission(permissionBase + "take")
.withArguments(
new OfflinePlayerArgument("target"),
currency.getProxy().fractionalDigits() > 0
? new DoubleArgument("amount", 1)
: new IntegerArgument("amount", 1)
)
.executes((sender, args) -> {
this.handleTake(sender, args, currency);
});
}
private void handleTake(CommandSender originalSender, CommandArguments args, Currency currency) {
BukkitCommandSender sender = new BukkitCommandSender(originalSender, this.manager.audienceFactory);
OfflinePlayer target = (OfflinePlayer) args.get("target");
BigDecimal amount = BigDecimal.valueOf(Double.parseDouble(args.getRaw("amount")));
if (!this.manager.canUse(sender, currency)) return;
this.manager.plugin.getUserManager().loadUser(target.getUniqueId())
.thenAccept(result -> {
String name = target.getName() != null ? target.getName() : args.getRaw("target");
result.setUsername(name);
this.manager.onTake(sender, currency, result, amount);
});
}
}

View file

@ -1,81 +0,0 @@
package dev.xhyrom.lighteco.bukkit.manager;
import dev.jorel.commandapi.CommandAPICommand;
import dev.xhyrom.lighteco.bukkit.commands.*;
import dev.xhyrom.lighteco.common.manager.command.AbstractCommandManager;
import dev.xhyrom.lighteco.common.model.currency.Currency;
import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
import org.bukkit.plugin.java.JavaPlugin;
import org.checkerframework.checker.nullness.qual.NonNull;
public class BukkitCommandManager extends AbstractCommandManager {
public final BukkitAudiences audienceFactory;
public BukkitCommandManager(LightEcoPlugin plugin) {
super(plugin);
this.audienceFactory = BukkitAudiences.create((JavaPlugin) this.plugin.getBootstrap().getLoader());
}
@Override
public void registerCurrencyCommand(@NonNull Currency currency) {
registerCommands(currency.getIdentifier(), currency);
for (String alias : currency.getIdentifierAliases()) {
registerCommands(alias, currency);
}
}
@Override
public void registerCurrencyCommand(@NonNull Currency currency, boolean main) {
if (!main) {
registerCurrencyCommand(currency);
return;
}
String permissionBase = "lighteco.currency." + currency.getIdentifier() + ".command.";
// Register main command
registerCurrencyCommand(currency);
// Expose pay as main command
if (currency.isPayable())
new PayCommand(this, currency, permissionBase).build().register();
// Expose balance as main command
for (CommandAPICommand cmd : new BalanceCommand(
this,
"balance",
currency,
permissionBase
).multipleBuild()) {
cmd.register();
}
}
private void registerCommands(@NonNull String name, @NonNull Currency currency) {
String permissionBase = "lighteco.currency." + currency.getIdentifier() + ".command.";
// Balance
for (CommandAPICommand cmd : new BalanceCommand(
this,
name,
currency,
permissionBase
).multipleBuild()) {
cmd.register();
}
CommandAPICommand cmd = new CommandAPICommand(name)
.withSubcommand(new SetCommand(this, currency, permissionBase).build())
.withSubcommand(new GiveCommand(this, currency, permissionBase).build())
.withSubcommand(new TakeCommand(this, currency, permissionBase).build())
.withSubcommands(new BalanceCommand(this, "balance", currency, permissionBase).multipleBuild());
if (currency.isPayable())
cmd = cmd.withSubcommand(new PayCommand(this, currency, permissionBase).build());
cmd.register();
}
}

44
bukkittest/.gitignore vendored
View file

@ -1,44 +0,0 @@
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store
runServer/

View file

@ -19,7 +19,10 @@ dependencies {
implementation("eu.okaeri:okaeri-configs-yaml-snakeyaml:5.0.0-beta.5")
implementation("eu.okaeri:okaeri-configs-validator-okaeri:5.0.0-beta.5")
compileOnly("com.mojang:brigadier:1.0.18")
compileOnly("com.zaxxer:HikariCP:5.0.1")
compileOnly("redis.clients:jedis:5.1.0")
compileOnly("org.projectlombok:lombok:1.18.28")
annotationProcessor("org.projectlombok:lombok:1.18.28")

View file

@ -4,12 +4,16 @@ import dev.xhyrom.lighteco.api.LightEco;
import dev.xhyrom.lighteco.api.manager.CommandManager;
import dev.xhyrom.lighteco.api.manager.CurrencyManager;
import dev.xhyrom.lighteco.api.manager.UserManager;
import dev.xhyrom.lighteco.api.messaging.MessagingService;
import dev.xhyrom.lighteco.api.platform.Platform;
import dev.xhyrom.lighteco.api.platform.PlayerAdapter;
import dev.xhyrom.lighteco.common.api.impl.*;
import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Optional;
public class LightEcoApi implements LightEco {
private final LightEcoPlugin plugin;
@ -49,12 +53,18 @@ public class LightEcoApi implements LightEco {
return this.commandManager;
}
@Override
public @NonNull Optional<MessagingService> getMessagingService() {
return this.plugin.getMessagingService().map(ApiMessagingService::new);
}
@Override
public @NonNull <T> PlayerAdapter<T> getPlayerAdapter(@NonNull Class<T> playerClass) {
Class<?> expected = this.plugin.getContextManager().getPlayerClass();
if (!expected.equals(playerClass)) {
throw new IllegalArgumentException("Expected player class " + expected.getName() + ", got " + playerClass.getName());
throw new IllegalArgumentException("Expected player class " + expected.getName()
+ ", got " + playerClass.getName());
}
return (PlayerAdapter<T>) this.playerAdapter;

View file

@ -4,10 +4,10 @@ import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
public abstract class ApiAbstractManager<H> {
protected final LightEcoPlugin plugin;
protected final H handler;
protected final H handle;
protected ApiAbstractManager(LightEcoPlugin plugin, H handler) {
protected ApiAbstractManager(LightEcoPlugin plugin, H handle) {
this.plugin = plugin;
this.handler = handler;
this.handle = handle;
}
}

View file

@ -3,26 +3,30 @@ package dev.xhyrom.lighteco.common.api.impl;
import dev.xhyrom.lighteco.api.manager.CommandManager;
import dev.xhyrom.lighteco.api.model.currency.Currency;
import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
import org.checkerframework.checker.nullness.qual.NonNull;
public class ApiCommandManager extends ApiAbstractManager<dev.xhyrom.lighteco.common.manager.command.CommandManager> implements CommandManager {
public ApiCommandManager(LightEcoPlugin plugin, dev.xhyrom.lighteco.common.manager.command.CommandManager handler) {
super(plugin, handler);
public class ApiCommandManager
extends ApiAbstractManager<dev.xhyrom.lighteco.common.command.CommandManager>
implements CommandManager {
public ApiCommandManager(
LightEcoPlugin plugin, dev.xhyrom.lighteco.common.command.CommandManager handle) {
super(plugin, handle);
}
@Override
public void registerCurrencyCommand(@NonNull Currency currency) {
dev.xhyrom.lighteco.common.model.currency.Currency internal = this.plugin.getCurrencyManager()
.getIfLoaded(currency.getIdentifier());
dev.xhyrom.lighteco.common.model.currency.Currency internal =
this.plugin.getCurrencyManager().getIfLoaded(currency.getIdentifier());
this.handler.registerCurrencyCommand(internal);
this.handle.register(internal, false);
}
@Override
public void registerCurrencyCommand(@NonNull Currency currency, boolean main) {
dev.xhyrom.lighteco.common.model.currency.Currency internal = this.plugin.getCurrencyManager()
.getIfLoaded(currency.getIdentifier());
dev.xhyrom.lighteco.common.model.currency.Currency internal =
this.plugin.getCurrencyManager().getIfLoaded(currency.getIdentifier());
this.handler.registerCurrencyCommand(internal, main);
this.handle.register(internal, main);
}
}
}

View file

@ -3,34 +3,38 @@ package dev.xhyrom.lighteco.common.api.impl;
import dev.xhyrom.lighteco.api.manager.CurrencyManager;
import dev.xhyrom.lighteco.api.model.currency.Currency;
import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Collection;
public class ApiCurrencyManager extends ApiAbstractManager<dev.xhyrom.lighteco.common.manager.currency.CurrencyManager> implements CurrencyManager {
public ApiCurrencyManager(LightEcoPlugin plugin, dev.xhyrom.lighteco.common.manager.currency.CurrencyManager handler) {
super(plugin, handler);
public class ApiCurrencyManager
extends ApiAbstractManager<dev.xhyrom.lighteco.common.manager.currency.CurrencyManager>
implements CurrencyManager {
public ApiCurrencyManager(
LightEcoPlugin plugin,
dev.xhyrom.lighteco.common.manager.currency.CurrencyManager handle) {
super(plugin, handle);
}
private Currency wrap(dev.xhyrom.lighteco.common.model.currency.Currency handler) {
return handler.getProxy();
private Currency wrap(dev.xhyrom.lighteco.common.model.currency.Currency handle) {
return handle.getProxy();
}
@Override
public @NonNull Collection<Currency> getRegisteredCurrencies() {
return this.handler.values()
.stream().map(this::wrap)
.toList();
return this.handle.values().stream().map(this::wrap).toList();
}
@Override
public Currency getCurrency(@NonNull String identifier) {
return wrap(this.handler.getIfLoaded(identifier));
return wrap(this.handle.getIfLoaded(identifier));
}
@Override
public void registerCurrency(@NonNull Currency currency) {
dev.xhyrom.lighteco.common.model.currency.Currency internal = new dev.xhyrom.lighteco.common.model.currency.Currency(currency);
this.handler.registerCurrency(internal);
dev.xhyrom.lighteco.common.model.currency.Currency internal =
new dev.xhyrom.lighteco.common.model.currency.Currency(currency);
this.handle.registerCurrency(internal);
}
}

View file

@ -0,0 +1,22 @@
package dev.xhyrom.lighteco.common.api.impl;
import dev.xhyrom.lighteco.api.messaging.MessagingService;
import dev.xhyrom.lighteco.api.model.currency.Currency;
import dev.xhyrom.lighteco.api.model.user.User;
import dev.xhyrom.lighteco.common.messaging.InternalMessagingService;
public class ApiMessagingService implements MessagingService {
private final InternalMessagingService handle;
public ApiMessagingService(InternalMessagingService handle) {
this.handle = handle;
}
@Override
public void pushUserUpdate(User user, Currency currency) {
dev.xhyrom.lighteco.common.model.currency.Currency internalCurrency =
this.handle.getPlugin().getCurrencyManager().getIfLoaded(currency.getIdentifier());
this.handle.pushUserUpdate(ApiUser.cast(user), internalCurrency);
}
}

View file

@ -2,6 +2,7 @@ package dev.xhyrom.lighteco.common.api.impl;
import dev.xhyrom.lighteco.api.platform.Platform;
import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
import org.checkerframework.checker.nullness.qual.NonNull;
public class ApiPlatform implements Platform {

View file

@ -1,9 +1,10 @@
package dev.xhyrom.lighteco.common.api.impl;
import dev.xhyrom.lighteco.api.manager.ContextManager;
import dev.xhyrom.lighteco.api.model.user.User;
import dev.xhyrom.lighteco.api.manager.UserManager;
import dev.xhyrom.lighteco.api.model.user.User;
import dev.xhyrom.lighteco.api.platform.PlayerAdapter;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.UUID;

View file

@ -2,6 +2,9 @@ package dev.xhyrom.lighteco.common.api.impl;
import dev.xhyrom.lighteco.api.model.currency.Currency;
import dev.xhyrom.lighteco.api.model.user.User;
import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
@ -9,55 +12,65 @@ import java.math.BigDecimal;
import java.util.UUID;
public class ApiUser implements User {
private final dev.xhyrom.lighteco.common.model.user.User handler;
public static dev.xhyrom.lighteco.common.model.user.User cast(User u) {
if (u instanceof ApiUser) {
return ((ApiUser) u).handle;
}
public ApiUser(dev.xhyrom.lighteco.common.model.user.User handler) {
this.handler = handler;
throw new IllegalArgumentException(
"Cannot cast " + u.getClass().getName() + " to " + ApiUser.class.getName());
}
private final dev.xhyrom.lighteco.common.model.user.User handle;
public ApiUser(dev.xhyrom.lighteco.common.model.user.User handle) {
this.handle = handle;
}
@Override
public @NonNull UUID getUniqueId() {
return this.handler.getUniqueId();
return this.handle.getUniqueId();
}
@Override
public @Nullable String getUsername() {
return this.handler.getUsername();
return this.handle.getUsername();
}
@Override
public @NonNull BigDecimal getBalance(@NonNull Currency currency) {
dev.xhyrom.lighteco.common.model.currency.Currency internal = this.handler.getPlugin()
.getCurrencyManager()
.getIfLoaded(currency.getIdentifier());
dev.xhyrom.lighteco.common.model.currency.Currency internal =
this.handle.getPlugin().getCurrencyManager().getIfLoaded(currency.getIdentifier());
return this.handler.getBalance(internal);
return this.handle.getBalance(internal);
}
@Override
public void setBalance(@NonNull Currency currency, @NonNull BigDecimal balance) {
dev.xhyrom.lighteco.common.model.currency.Currency internal = this.handler.getPlugin()
.getCurrencyManager()
.getIfLoaded(currency.getIdentifier());
dev.xhyrom.lighteco.common.model.currency.Currency internal =
this.handle.getPlugin().getCurrencyManager().getIfLoaded(currency.getIdentifier());
this.handler.setBalance(internal, balance);
this.handle.setBalance(internal, balance);
}
@Override
public void deposit(@NonNull Currency currency, @NonNull BigDecimal amount) {
dev.xhyrom.lighteco.common.model.currency.Currency internal = this.handler.getPlugin()
.getCurrencyManager()
.getIfLoaded(currency.getIdentifier());
dev.xhyrom.lighteco.common.model.currency.Currency internal =
this.handle.getPlugin().getCurrencyManager().getIfLoaded(currency.getIdentifier());
this.handler.deposit(internal, amount);
this.handle.deposit(internal, amount);
}
@Override
public void withdraw(@NonNull Currency currency, @NonNull BigDecimal amount) {
dev.xhyrom.lighteco.common.model.currency.Currency internal = this.handler.getPlugin()
.getCurrencyManager()
.getIfLoaded(currency.getIdentifier());
dev.xhyrom.lighteco.common.model.currency.Currency internal =
this.handle.getPlugin().getCurrencyManager().getIfLoaded(currency.getIdentifier());
this.handler.withdraw(internal, amount);
this.handle.withdraw(internal, amount);
}
@Override
public void sendMessage(Component message) {
this.handle.sendMessage(message);
}
}

View file

@ -3,20 +3,24 @@ package dev.xhyrom.lighteco.common.api.impl;
import dev.xhyrom.lighteco.api.manager.UserManager;
import dev.xhyrom.lighteco.api.model.user.User;
import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
public class ApiUserManager extends ApiAbstractManager<dev.xhyrom.lighteco.common.manager.user.UserManager> implements UserManager {
public ApiUserManager(LightEcoPlugin plugin, dev.xhyrom.lighteco.common.manager.user.UserManager handler) {
super(plugin, handler);
public class ApiUserManager
extends ApiAbstractManager<dev.xhyrom.lighteco.common.manager.user.UserManager>
implements UserManager {
public ApiUserManager(
LightEcoPlugin plugin, dev.xhyrom.lighteco.common.manager.user.UserManager handle) {
super(plugin, handle);
}
private User wrap(dev.xhyrom.lighteco.common.model.user.User handler) {
this.plugin.getUserManager().getHousekeeper().registerUsage(handler.getUniqueId());
return handler.getProxy();
private User wrap(dev.xhyrom.lighteco.common.model.user.User handle) {
this.plugin.getUserManager().getHousekeeper().registerUsage(handle.getUniqueId());
return handle.getProxy();
}
@Override
@ -26,8 +30,7 @@ public class ApiUserManager extends ApiAbstractManager<dev.xhyrom.lighteco.commo
@Override
public @NonNull CompletableFuture<User> loadUser(@NonNull UUID uniqueId, String username) {
return this.plugin.getStorage().loadUser(uniqueId, username)
.thenApply(this::wrap);
return this.plugin.getStorage().loadUser(uniqueId, username).thenApply(this::wrap);
}
@Override
@ -42,11 +45,11 @@ public class ApiUserManager extends ApiAbstractManager<dev.xhyrom.lighteco.commo
@Override
public @Nullable User getUser(@NonNull UUID uniqueId) {
return wrap(this.handler.getIfLoaded(uniqueId));
return wrap(this.handle.getIfLoaded(uniqueId));
}
@Override
public boolean isLoaded(@NonNull UUID uniqueId) {
return this.handler.isLoaded(uniqueId);
return this.handle.isLoaded(uniqueId);
}
}

View file

@ -10,9 +10,7 @@ public class ExpiringSet<T> {
private final long lifetime;
public ExpiringSet(long duration, TimeUnit unit) {
this.cache = CacheBuilder.newBuilder()
.expireAfterWrite(duration, unit)
.build();
this.cache = CacheBuilder.newBuilder().expireAfterWrite(duration, unit).build();
this.lifetime = unit.toMillis(duration);
}

View file

@ -2,6 +2,4 @@ package dev.xhyrom.lighteco.common.cache;
import java.util.HashMap;
public class RedisBackedMap<T, U> extends HashMap<T, U> {
}
public class RedisBackedMap<T, U> extends HashMap<T, U> {}

View file

@ -0,0 +1,58 @@
package dev.xhyrom.lighteco.common.command;
import com.mojang.brigadier.context.CommandContext;
import dev.xhyrom.lighteco.common.command.argument.type.OfflineUserArgument;
import dev.xhyrom.lighteco.common.command.exception.LockedUserException;
import dev.xhyrom.lighteco.common.config.message.CurrencyMessageConfig;
import dev.xhyrom.lighteco.common.model.chat.CommandSender;
import dev.xhyrom.lighteco.common.model.currency.Currency;
import dev.xhyrom.lighteco.common.model.user.User;
import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
import lombok.experimental.UtilityClass;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import java.util.Map;
@UtilityClass
public class CommandHelper {
public static User getUser(CommandContext<CommandSource> context) {
LightEcoPlugin plugin = context.getSource().plugin();
CommandSender sender = context.getSource().sender();
User target = null;
try {
target = OfflineUserArgument.getOfflineUser(context, "target");
} catch (LockedUserException e) {
sender.sendMessage(
MiniMessage.miniMessage().deserialize(plugin.getConfig().messages.wait));
}
if (target == null || target.getUsername() == null) {
String userName = context.getArgument("target", String.class);
sender.sendMessage(MiniMessage.miniMessage()
.deserialize(
plugin.getConfig().messages.userNotFound,
Placeholder.parsed("username", userName)));
return null;
}
return target;
}
public static CurrencyMessageConfig getCurrencyMessageConfig(
LightEcoPlugin plugin, Currency currency) {
Map<String, CurrencyMessageConfig> config = plugin.getConfig().messages.currency;
CurrencyMessageConfig currencyMessageConfig = config.get(currency.getIdentifier());
if (currencyMessageConfig == null) {
return config.get("default");
}
return currencyMessageConfig;
}
}

View file

@ -0,0 +1,146 @@
package dev.xhyrom.lighteco.common.command;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.ParseResults;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import dev.xhyrom.lighteco.common.command.abstraction.Command;
import dev.xhyrom.lighteco.common.commands.BalanceCommand;
import dev.xhyrom.lighteco.common.commands.CurrencyParentCommand;
import dev.xhyrom.lighteco.common.commands.InfoCommand;
import dev.xhyrom.lighteco.common.commands.PayCommand;
import dev.xhyrom.lighteco.common.model.chat.CommandSender;
import dev.xhyrom.lighteco.common.model.currency.Currency;
import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
import lombok.Getter;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration;
import net.kyori.adventure.text.minimessage.MiniMessage;
import java.util.*;
import java.util.concurrent.*;
public class CommandManager {
protected final LightEcoPlugin plugin;
private final ExecutorService executor =
Executors.newSingleThreadExecutor(new ThreadFactoryBuilder()
.setDaemon(true)
.setNameFormat("lighteco-command-executor")
.build());
@Getter
private final CommandDispatcher<CommandSource> dispatcher = new CommandDispatcher<>();
@Getter
private final Set<UUID> locks = ConcurrentHashMap.newKeySet();
private final Map<UUID, UUID> locksMappings = new ConcurrentHashMap<>();
public CommandManager(LightEcoPlugin plugin) {
this.plugin = plugin;
}
// Register built-in commands
public void register() {
register(new InfoCommand());
}
public void register(Currency currency, boolean main) {
register(new CurrencyParentCommand(currency));
if (main) {
register(BalanceCommand.create(currency));
register(PayCommand.create(currency));
}
}
protected void register(Command command) {
dispatcher.getRoot().addChild(command.build());
}
public void execute(CommandSender sender, String name, String[] args) {
if (!sender.isConsole() && locks.contains(sender.getUniqueId())) {
sender.sendMessage(
MiniMessage.miniMessage().deserialize(this.plugin.getConfig().messages.wait));
return;
}
final CommandSource source = new CommandSource(this.plugin, sender);
final ParseResults<CommandSource> parseResults = dispatcher.parse(
name + (args.length > 0 ? " " + String.join(" ", args) : ""), source);
if (!sender.isConsole()) locks.add(sender.getUniqueId());
CompletableFuture.runAsync(
() -> {
try {
dispatcher.execute(parseResults);
} catch (CommandSyntaxException e) {
this.sendError(sender, name, e);
} finally {
if (!source.sender().isConsole()) {
this.plugin
.getBootstrap()
.getLogger()
.debug("Removing lock for " + sender.getUsername());
UUID target = locksMappings.get(sender.getUniqueId());
if (target != null) {
locks.remove(target);
locksMappings.remove(sender.getUniqueId());
this.plugin
.getBootstrap()
.getLogger()
.debug("Removing lock caused by " + sender.getUsername()
+ " for " + target);
}
locks.remove(sender.getUniqueId());
}
}
},
executor);
}
public void lockBySender(CommandSender sender, UUID target) {
locks.add(target);
locksMappings.put(sender.getUniqueId(), target);
}
private void sendError(CommandSender sender, String name, CommandSyntaxException e) {
sender.sendMessage(Component.text(e.getRawMessage().getString(), NamedTextColor.RED));
if (e.getInput() != null && e.getCursor() >= 0) {
int j = Math.min(e.getInput().length(), e.getCursor());
Component msg = Component.empty()
.color(NamedTextColor.GRAY)
.clickEvent(
ClickEvent.clickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/" + name));
if (j > 10) {
msg = msg.append(Component.text("..."));
}
msg = msg.append(Component.text(e.getInput().substring(Math.max(0, j - 10), j)));
if (j < e.getInput().length()) {
Component component = Component.text(e.getInput().substring(j))
.color(NamedTextColor.RED)
.decorate(TextDecoration.UNDERLINED);
msg = msg.append(component);
}
msg = msg.append(Component.translatable("command.context.here")
.color(NamedTextColor.RED)
.decorate(TextDecoration.ITALIC));
sender.sendMessage(msg);
}
}
}

View file

@ -0,0 +1,8 @@
package dev.xhyrom.lighteco.common.command;
import dev.xhyrom.lighteco.common.model.chat.CommandSender;
import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
import org.checkerframework.checker.nullness.qual.NonNull;
public record CommandSource(@NonNull LightEcoPlugin plugin, @NonNull CommandSender sender) {}

View file

@ -0,0 +1,36 @@
package dev.xhyrom.lighteco.common.command.abstraction;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.tree.CommandNode;
import dev.xhyrom.lighteco.common.command.CommandSource;
import lombok.Getter;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@Getter
public abstract class Command {
@NonNull protected final String name;
@NonNull private final String description;
@NonNull private final List<String> aliases = new ArrayList<>();
public Command(@NonNull String name, @NonNull String description, String... aliases) {
this.name = name;
this.description = description;
Collections.addAll(this.aliases, aliases);
}
public abstract CommandNode<CommandSource> build();
protected LiteralArgumentBuilder<CommandSource> builder() {
return LiteralArgumentBuilder.literal(name);
}
}

View file

@ -0,0 +1,46 @@
package dev.xhyrom.lighteco.common.command.argument.type;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import dev.xhyrom.lighteco.common.command.CommandSource;
import dev.xhyrom.lighteco.common.command.exception.LockedUserException;
import dev.xhyrom.lighteco.common.model.chat.CommandSender;
import dev.xhyrom.lighteco.common.model.user.User;
import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
import java.util.UUID;
public class OfflineUserArgument implements ArgumentType<String> {
private OfflineUserArgument() {}
public static User getOfflineUser(CommandContext<CommandSource> context, String name)
throws LockedUserException {
String userName = context.getArgument(name, String.class);
LightEcoPlugin plugin = context.getSource().plugin();
CommandSender sender = context.getSource().sender();
UUID uniqueId = plugin.getBootstrap().lookupUniqueId(userName).orElse(null);
if (uniqueId == null) {
return null;
}
if (sender.getUniqueId() != uniqueId
&& plugin.getCommandManager().getLocks().contains(uniqueId)) {
throw new LockedUserException(uniqueId);
}
// Lock the user to prevent race conditions
if (!sender.isConsole())
plugin.getCommandManager().lockBySender(context.getSource().sender(), uniqueId);
return plugin.getUserManager().loadUser(uniqueId).join();
}
@Override
public String parse(StringReader reader) throws CommandSyntaxException {
return reader.readString();
}
}

View file

@ -0,0 +1,9 @@
package dev.xhyrom.lighteco.common.command.exception;
import java.util.UUID;
public class LockedUserException extends Exception {
public LockedUserException(UUID uniqueId) {
super("User with uuid " + uniqueId + " is currently locked thus cannot be modified");
}
}

View file

@ -0,0 +1,36 @@
package dev.xhyrom.lighteco.common.command.suggestion.type;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.suggestion.SuggestionProvider;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import dev.xhyrom.lighteco.common.command.CommandSource;
import dev.xhyrom.lighteco.common.model.user.User;
import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
public class OfflineUserSuggestionProvider implements SuggestionProvider<CommandSource> {
public static OfflineUserSuggestionProvider create() {
return new OfflineUserSuggestionProvider();
}
@Override
public CompletableFuture<Suggestions> getSuggestions(
CommandContext<CommandSource> context, SuggestionsBuilder builder) {
LightEcoPlugin plugin = context.getSource().plugin();
String remaining = builder.getRemaining();
for (UUID uniqueId : plugin.getBootstrap().getOnlinePlayers()) {
User user = plugin.getUserManager().getIfLoaded(uniqueId);
if (user == null) continue;
builder.suggest(user.getUsername());
}
return builder.buildFuture();
}
}

View file

@ -0,0 +1,89 @@
package dev.xhyrom.lighteco.common.commands;
import static com.mojang.brigadier.Command.SINGLE_SUCCESS;
import static dev.xhyrom.lighteco.common.command.CommandHelper.*;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.tree.CommandNode;
import dev.xhyrom.lighteco.common.command.CommandSource;
import dev.xhyrom.lighteco.common.command.abstraction.Command;
import dev.xhyrom.lighteco.common.command.suggestion.type.OfflineUserSuggestionProvider;
import dev.xhyrom.lighteco.common.model.chat.CommandSender;
import dev.xhyrom.lighteco.common.model.currency.Currency;
import dev.xhyrom.lighteco.common.model.user.User;
import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.math.BigDecimal;
public class BalanceCommand extends Command {
private final Currency currency;
public static BalanceCommand create(@NonNull Currency currency) {
return new BalanceCommand(currency);
}
public BalanceCommand(@NonNull Currency currency) {
super("balance", "Check your balance");
this.currency = currency;
}
@Override
public CommandNode<CommandSource> build() {
return builder()
.then(RequiredArgumentBuilder.<CommandSource, String>argument(
"target", StringArgumentType.word())
.suggests(OfflineUserSuggestionProvider.create())
.requires((source) -> source.sender()
.eligible("lighteco.currency." + currency.getIdentifier()
+ ".command.balance.others"))
.executes(context -> {
LightEcoPlugin plugin = context.getSource().plugin();
CommandSender sender = context.getSource().sender();
final User target = getUser(context);
if (target == null) return SINGLE_SUCCESS;
BigDecimal balance = target.getBalance(currency);
sender.sendMessage(MiniMessage.miniMessage()
.deserialize(
getCurrencyMessageConfig(plugin, currency)
.balanceOthers,
Placeholder.parsed(
"currency", currency.getIdentifier()),
Placeholder.parsed("target", target.getUsername()),
Placeholder.parsed(
"balance", balance.toPlainString())));
return SINGLE_SUCCESS;
}))
.requires((source) -> source.sender()
.eligible("lighteco.currency." + currency.getIdentifier()
+ ".command.balance"))
.executes(context -> {
LightEcoPlugin plugin = context.getSource().plugin();
CommandSender sender = context.getSource().sender();
User user = plugin.getUserManager().getIfLoaded(sender.getUniqueId());
BigDecimal balance = user.getBalance(currency);
sender.sendMessage(MiniMessage.miniMessage()
.deserialize(
getCurrencyMessageConfig(plugin, currency).balance,
Placeholder.parsed("currency", currency.getIdentifier()),
Placeholder.parsed("balance", balance.toPlainString())));
return SINGLE_SUCCESS;
})
.build();
}
}

View file

@ -0,0 +1,61 @@
package dev.xhyrom.lighteco.common.commands;
import static com.mojang.brigadier.Command.SINGLE_SUCCESS;
import static dev.xhyrom.lighteco.common.command.CommandHelper.getCurrencyMessageConfig;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.tree.CommandNode;
import dev.xhyrom.lighteco.common.command.CommandSource;
import dev.xhyrom.lighteco.common.command.abstraction.Command;
import dev.xhyrom.lighteco.common.model.chat.CommandSender;
import dev.xhyrom.lighteco.common.model.currency.Currency;
import dev.xhyrom.lighteco.common.model.user.User;
import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.math.BigDecimal;
public class CurrencyParentCommand extends Command {
private final Currency currency;
public CurrencyParentCommand(@NonNull Currency currency) {
super(currency.getIdentifier(), currency.getIdentifier());
this.currency = currency;
}
@Override
public CommandNode<CommandSource> build() {
LiteralArgumentBuilder<CommandSource> builder = builder()
.then(BalanceCommand.create(currency).build())
.then(SetCommand.create(currency).build())
.then(GiveCommand.create(currency).build())
.then(TakeCommand.create(currency).build())
.executes(context -> {
LightEcoPlugin plugin = context.getSource().plugin();
CommandSender sender = context.getSource().sender();
User user = plugin.getUserManager().getIfLoaded(sender.getUniqueId());
BigDecimal balance = user.getBalance(currency);
sender.sendMessage(MiniMessage.miniMessage()
.deserialize(
getCurrencyMessageConfig(plugin, currency).balance,
Placeholder.parsed("currency", currency.getIdentifier()),
Placeholder.parsed("balance", balance.toPlainString())));
return SINGLE_SUCCESS;
});
if (currency.isPayable())
builder = builder.then(PayCommand.create(currency).build());
return builder.build();
}
}

View file

@ -0,0 +1,116 @@
package dev.xhyrom.lighteco.common.commands;
import static com.mojang.brigadier.Command.SINGLE_SUCCESS;
import static dev.xhyrom.lighteco.common.command.CommandHelper.*;
import com.mojang.brigadier.arguments.DoubleArgumentType;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.tree.CommandNode;
import dev.xhyrom.lighteco.api.exception.CannotBeGreaterThan;
import dev.xhyrom.lighteco.common.command.CommandSource;
import dev.xhyrom.lighteco.common.command.abstraction.Command;
import dev.xhyrom.lighteco.common.command.suggestion.type.OfflineUserSuggestionProvider;
import dev.xhyrom.lighteco.common.model.chat.CommandSender;
import dev.xhyrom.lighteco.common.model.currency.Currency;
import dev.xhyrom.lighteco.common.model.user.User;
import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.math.BigDecimal;
import java.math.RoundingMode;
public class GiveCommand extends Command {
private static final MiniMessage miniMessage = MiniMessage.miniMessage();
private final Currency currency;
public static GiveCommand create(@NonNull Currency currency) {
return new GiveCommand(currency);
}
public GiveCommand(@NonNull Currency currency) {
super("give", "Give a player some money");
this.currency = currency;
}
private void execute(CommandContext<CommandSource> context) {
final LightEcoPlugin plugin = context.getSource().plugin();
final CommandSender sender = context.getSource().sender();
final User target = getUser(context);
if (target == null) return;
BigDecimal amount = BigDecimal.valueOf(
currency.fractionalDigits() > 0
? context.getArgument("amount", Double.class)
: context.getArgument("amount", Integer.class));
amount = amount.setScale(currency.fractionalDigits(), RoundingMode.DOWN);
try {
target.deposit(currency, amount);
} catch (CannotBeGreaterThan e) {
sender.sendMessage(miniMessage.deserialize(
getCurrencyMessageConfig(plugin, this.currency).cannotBeGreaterThan,
Placeholder.parsed("max", plugin.getConfig().maximumBalance.toPlainString())));
return;
}
sender.sendMessage(miniMessage.deserialize(
getCurrencyMessageConfig(plugin, this.currency).set,
Placeholder.parsed("currency", currency.getIdentifier()),
Placeholder.parsed("target", target.getUsername()),
Placeholder.parsed("amount", amount.toPlainString())));
}
@Override
public CommandNode<CommandSource> build() {
if (currency.fractionalDigits() > 0) {
return builder()
.requires((source) -> source.sender()
.eligible("lighteco.currency." + currency.getIdentifier()
+ ".command.give"))
.then(RequiredArgumentBuilder.<CommandSource, String>argument(
"target", StringArgumentType.word())
.suggests(OfflineUserSuggestionProvider.create())
.then(RequiredArgumentBuilder.<CommandSource, Double>argument(
"amount", DoubleArgumentType.doubleArg(1))
.requires((source) -> source.sender()
.eligible("lighteco.currency."
+ currency.getIdentifier() + ".command.give"))
.executes(c -> {
execute(c);
return SINGLE_SUCCESS;
})))
.build();
}
return builder()
.requires((source) -> source.sender()
.eligible(
"lighteco.currency." + currency.getIdentifier() + ".command.give"))
.then(RequiredArgumentBuilder.<CommandSource, String>argument(
"target", StringArgumentType.word())
.suggests(OfflineUserSuggestionProvider.create())
.then(RequiredArgumentBuilder.<CommandSource, Integer>argument(
"amount", IntegerArgumentType.integer(1))
.requires((source) -> source.sender()
.eligible("lighteco.currency." + currency.getIdentifier()
+ ".command.give"))
.executes(c -> {
execute(c);
return SINGLE_SUCCESS;
})))
.build();
}
}

View file

@ -0,0 +1,40 @@
package dev.xhyrom.lighteco.common.commands;
import static com.mojang.brigadier.Command.SINGLE_SUCCESS;
import com.mojang.brigadier.tree.CommandNode;
import dev.xhyrom.lighteco.common.command.CommandSource;
import dev.xhyrom.lighteco.common.command.abstraction.Command;
import dev.xhyrom.lighteco.common.model.chat.CommandSender;
import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
public class InfoCommand extends Command {
public InfoCommand() {
super("lighteco", "LightEco information");
}
@Override
public CommandNode<CommandSource> build() {
return builder()
.requires(source -> source.sender().eligible("lighteco.command.info"))
.executes(context -> {
LightEcoPlugin plugin = context.getSource().plugin();
CommandSender sender = context.getSource().sender();
sender.sendMessage(MiniMessage.miniMessage()
.deserialize(
"<#fa5246><bold>LightEco</bold></#fa5246> <dark_gray>(<#d6766f>v<version><dark_gray>) <white>on <#d6766f><platform>",
Placeholder.parsed(
"version", plugin.getBootstrap().getVersion()),
Placeholder.parsed(
"platform", plugin.getPlatformType().getName())));
return SINGLE_SUCCESS;
})
.build();
}
}

View file

@ -0,0 +1,160 @@
package dev.xhyrom.lighteco.common.commands;
import static com.mojang.brigadier.Command.SINGLE_SUCCESS;
import static dev.xhyrom.lighteco.common.command.CommandHelper.*;
import com.mojang.brigadier.arguments.DoubleArgumentType;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.tree.CommandNode;
import dev.xhyrom.lighteco.api.exception.CannotBeGreaterThan;
import dev.xhyrom.lighteco.common.command.CommandSource;
import dev.xhyrom.lighteco.common.command.abstraction.Command;
import dev.xhyrom.lighteco.common.command.suggestion.type.OfflineUserSuggestionProvider;
import dev.xhyrom.lighteco.common.model.chat.CommandSender;
import dev.xhyrom.lighteco.common.model.currency.Currency;
import dev.xhyrom.lighteco.common.model.user.User;
import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.math.BigDecimal;
import java.math.RoundingMode;
public class PayCommand extends Command {
private static final MiniMessage miniMessage = MiniMessage.miniMessage();
private final Currency currency;
public static PayCommand create(@NonNull Currency currency) {
return new PayCommand(currency);
}
public PayCommand(@NonNull Currency currency) {
super("pay", "Pay a player");
this.currency = currency;
}
private void execute(CommandContext<CommandSource> context) {
final LightEcoPlugin plugin = context.getSource().plugin();
final CommandSender sender = context.getSource().sender();
final User target = getUser(context);
if (target == null) return;
BigDecimal amount = BigDecimal.valueOf(
currency.fractionalDigits() > 0
? context.getArgument("amount", Double.class)
: context.getArgument("amount", Integer.class));
amount = amount.setScale(currency.fractionalDigits(), RoundingMode.DOWN);
final User user = plugin.getUserManager().getIfLoaded(sender.getUniqueId());
if (user == null) {
return;
}
if (user.getBalance(this.currency).compareTo(amount) < 0) {
sender.sendMessage(miniMessage.deserialize(
getCurrencyMessageConfig(plugin, this.currency).notEnoughMoney));
return;
}
// calculate tax using Currency#calculateTax
BigDecimal tax = currency.getProxy().calculateTax(user.getProxy(), amount);
tax = tax.setScale(currency.getProxy().fractionalDigits(), RoundingMode.DOWN);
// subtract tax from amount
BigDecimal taxedAmount = amount.subtract(tax);
try {
target.deposit(currency, taxedAmount);
user.withdraw(currency, amount);
} catch (CannotBeGreaterThan e) {
sender.sendMessage(miniMessage.deserialize(
getCurrencyMessageConfig(plugin, this.currency).cannotBeGreaterThan,
Placeholder.parsed("max", plugin.getConfig().maximumBalance.toPlainString())));
return;
}
String template = tax.compareTo(BigDecimal.ZERO) > 0
? getCurrencyMessageConfig(plugin, this.currency).payWithTax
: getCurrencyMessageConfig(plugin, this.currency).pay;
String templateReceived = tax.compareTo(BigDecimal.ZERO) > 0
? getCurrencyMessageConfig(plugin, this.currency).payReceivedWithTax
: getCurrencyMessageConfig(plugin, this.currency).payReceived;
sender.sendMessage(miniMessage.deserialize(
template,
Placeholder.parsed("currency", currency.getIdentifier()),
Placeholder.parsed("target", target.getUsername()),
Placeholder.parsed("amount", amount.toPlainString()),
Placeholder.parsed("taxed_amount", taxedAmount.toPlainString()),
Placeholder.parsed("sender_balance", user.getBalance(currency).toPlainString()),
Placeholder.parsed(
"receiver_balance", target.getBalance(currency).toPlainString())));
target.sendMessage(miniMessage.deserialize(
templateReceived,
Placeholder.parsed("currency", currency.getIdentifier()),
Placeholder.parsed("sender", user.getUsername()),
Placeholder.parsed("amount", amount.toPlainString()),
Placeholder.parsed("taxed_amount", taxedAmount.toPlainString()),
Placeholder.parsed("sender_balance", user.getBalance(currency).toPlainString()),
Placeholder.parsed(
"receiver_balance", target.getBalance(currency).toPlainString())));
}
@Override
public CommandNode<CommandSource> build() {
if (currency.fractionalDigits() > 0) {
return builder()
.requires((source) -> !source.sender().isConsole()
&& source.sender()
.eligible("lighteco.currency." + currency.getIdentifier()
+ ".command.pay"))
.then(RequiredArgumentBuilder.<CommandSource, String>argument(
"target", StringArgumentType.word())
.suggests(OfflineUserSuggestionProvider.create())
.then(RequiredArgumentBuilder.<CommandSource, Double>argument(
"amount", DoubleArgumentType.doubleArg(1))
.requires((source) -> source.sender()
.eligible("lighteco.currency."
+ currency.getIdentifier() + ".command.pay"))
.executes(c -> {
execute(c);
return SINGLE_SUCCESS;
})))
.build();
}
return builder()
.requires((source) -> !source.sender().isConsole()
&& source.sender()
.eligible("lighteco.currency." + currency.getIdentifier()
+ ".command.pay"))
.then(RequiredArgumentBuilder.<CommandSource, String>argument(
"target", StringArgumentType.word())
.suggests(OfflineUserSuggestionProvider.create())
.then(RequiredArgumentBuilder.<CommandSource, Integer>argument(
"amount", IntegerArgumentType.integer(1))
.requires((source) -> source.sender()
.eligible("lighteco.currency." + currency.getIdentifier()
+ ".command.pay"))
.executes(c -> {
execute(c);
return SINGLE_SUCCESS;
})))
.build();
}
}

View file

@ -0,0 +1,115 @@
package dev.xhyrom.lighteco.common.commands;
import static com.mojang.brigadier.Command.SINGLE_SUCCESS;
import static dev.xhyrom.lighteco.common.command.CommandHelper.*;
import com.mojang.brigadier.arguments.DoubleArgumentType;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.tree.CommandNode;
import dev.xhyrom.lighteco.api.exception.CannotBeGreaterThan;
import dev.xhyrom.lighteco.common.command.CommandSource;
import dev.xhyrom.lighteco.common.command.abstraction.Command;
import dev.xhyrom.lighteco.common.command.suggestion.type.OfflineUserSuggestionProvider;
import dev.xhyrom.lighteco.common.model.chat.CommandSender;
import dev.xhyrom.lighteco.common.model.currency.Currency;
import dev.xhyrom.lighteco.common.model.user.User;
import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.math.BigDecimal;
import java.math.RoundingMode;
public class SetCommand extends Command {
private static final MiniMessage miniMessage = MiniMessage.miniMessage();
private final Currency currency;
public static SetCommand create(@NonNull Currency currency) {
return new SetCommand(currency);
}
public SetCommand(@NonNull Currency currency) {
super("set", "Set a player's balance");
this.currency = currency;
}
private void execute(CommandContext<CommandSource> context) {
final LightEcoPlugin plugin = context.getSource().plugin();
final CommandSender sender = context.getSource().sender();
final User target = getUser(context);
if (target == null) return;
BigDecimal amount = BigDecimal.valueOf(
currency.fractionalDigits() > 0
? context.getArgument("amount", Double.class)
: context.getArgument("amount", Integer.class));
amount = amount.setScale(currency.fractionalDigits(), RoundingMode.DOWN);
try {
target.setBalance(currency, amount);
} catch (CannotBeGreaterThan e) {
sender.sendMessage(miniMessage.deserialize(
getCurrencyMessageConfig(plugin, this.currency).cannotBeGreaterThan,
Placeholder.parsed("max", plugin.getConfig().maximumBalance.toPlainString())));
return;
}
sender.sendMessage(miniMessage.deserialize(
getCurrencyMessageConfig(plugin, this.currency).set,
Placeholder.parsed("currency", currency.getIdentifier()),
Placeholder.parsed("target", target.getUsername()),
Placeholder.parsed("amount", amount.toPlainString())));
}
@Override
public CommandNode<CommandSource> build() {
if (currency.fractionalDigits() > 0) {
return builder()
.requires((source) -> source.sender()
.eligible("lighteco.currency." + currency.getIdentifier()
+ ".command.set"))
.then(RequiredArgumentBuilder.<CommandSource, String>argument(
"target", StringArgumentType.word())
.suggests(OfflineUserSuggestionProvider.create())
.then(RequiredArgumentBuilder.<CommandSource, Double>argument(
"amount", DoubleArgumentType.doubleArg(1))
.requires((source) -> source.sender()
.eligible("lighteco.currency."
+ currency.getIdentifier() + ".command.set"))
.executes(c -> {
execute(c);
return SINGLE_SUCCESS;
})))
.build();
}
return builder()
.requires((source) -> source.sender()
.eligible("lighteco.currency." + currency.getIdentifier() + ".command.set"))
.then(RequiredArgumentBuilder.<CommandSource, String>argument(
"target", StringArgumentType.word())
.suggests(OfflineUserSuggestionProvider.create())
.then(RequiredArgumentBuilder.<CommandSource, Integer>argument(
"amount", IntegerArgumentType.integer(1))
.requires((source) -> source.sender()
.eligible("lighteco.currency." + currency.getIdentifier()
+ ".command.set"))
.executes(c -> {
execute(c);
return SINGLE_SUCCESS;
})))
.build();
}
}

View file

@ -0,0 +1,116 @@
package dev.xhyrom.lighteco.common.commands;
import static com.mojang.brigadier.Command.SINGLE_SUCCESS;
import static dev.xhyrom.lighteco.common.command.CommandHelper.*;
import com.mojang.brigadier.arguments.DoubleArgumentType;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.tree.CommandNode;
import dev.xhyrom.lighteco.api.exception.CannotBeGreaterThan;
import dev.xhyrom.lighteco.common.command.CommandSource;
import dev.xhyrom.lighteco.common.command.abstraction.Command;
import dev.xhyrom.lighteco.common.command.suggestion.type.OfflineUserSuggestionProvider;
import dev.xhyrom.lighteco.common.model.chat.CommandSender;
import dev.xhyrom.lighteco.common.model.currency.Currency;
import dev.xhyrom.lighteco.common.model.user.User;
import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.math.BigDecimal;
import java.math.RoundingMode;
public class TakeCommand extends Command {
private static final MiniMessage miniMessage = MiniMessage.miniMessage();
private final Currency currency;
public static TakeCommand create(@NonNull Currency currency) {
return new TakeCommand(currency);
}
public TakeCommand(@NonNull Currency currency) {
super("take", "Take money from a player");
this.currency = currency;
}
private void execute(CommandContext<CommandSource> context) {
final LightEcoPlugin plugin = context.getSource().plugin();
final CommandSender sender = context.getSource().sender();
final User target = getUser(context);
if (target == null) return;
BigDecimal amount = BigDecimal.valueOf(
currency.fractionalDigits() > 0
? context.getArgument("amount", Double.class)
: context.getArgument("amount", Integer.class));
amount = amount.setScale(currency.fractionalDigits(), RoundingMode.DOWN);
try {
target.withdraw(currency, amount);
} catch (CannotBeGreaterThan e) {
sender.sendMessage(miniMessage.deserialize(
getCurrencyMessageConfig(plugin, this.currency).cannotBeGreaterThan,
Placeholder.parsed("max", plugin.getConfig().maximumBalance.toPlainString())));
return;
}
sender.sendMessage(miniMessage.deserialize(
getCurrencyMessageConfig(plugin, this.currency).set,
Placeholder.parsed("currency", currency.getIdentifier()),
Placeholder.parsed("target", target.getUsername()),
Placeholder.parsed("amount", amount.toPlainString())));
}
@Override
public CommandNode<CommandSource> build() {
if (currency.fractionalDigits() > 0) {
return builder()
.requires((source) -> source.sender()
.eligible("lighteco.currency." + currency.getIdentifier()
+ ".command.take"))
.then(RequiredArgumentBuilder.<CommandSource, String>argument(
"target", StringArgumentType.word())
.suggests(OfflineUserSuggestionProvider.create())
.then(RequiredArgumentBuilder.<CommandSource, Double>argument(
"amount", DoubleArgumentType.doubleArg(1))
.requires((source) -> source.sender()
.eligible("lighteco.currency."
+ currency.getIdentifier() + ".command.take"))
.executes(c -> {
execute(c);
return SINGLE_SUCCESS;
})))
.build();
}
return builder()
.requires((source) -> source.sender()
.eligible(
"lighteco.currency." + currency.getIdentifier() + ".command.take"))
.then(RequiredArgumentBuilder.<CommandSource, String>argument(
"target", StringArgumentType.word())
.suggests(OfflineUserSuggestionProvider.create())
.then(RequiredArgumentBuilder.<CommandSource, Integer>argument(
"amount", IntegerArgumentType.integer(1))
.requires((source) -> source.sender()
.eligible("lighteco.currency." + currency.getIdentifier()
+ ".command.take"))
.executes(c -> {
execute(c);
return SINGLE_SUCCESS;
})))
.build();
}
}

View file

@ -2,7 +2,9 @@ package dev.xhyrom.lighteco.common.config;
import dev.xhyrom.lighteco.common.config.housekeeper.HousekeeperConfig;
import dev.xhyrom.lighteco.common.config.message.MessageConfig;
import dev.xhyrom.lighteco.common.config.messaging.MessagingConfig;
import dev.xhyrom.lighteco.common.config.storage.StorageConfig;
import eu.okaeri.configs.OkaeriConfig;
import eu.okaeri.configs.annotation.Comment;
import eu.okaeri.configs.annotation.Header;
@ -13,18 +15,23 @@ import java.math.BigDecimal;
@Header("")
public class Config extends OkaeriConfig {
@Comment("This property must be unique for each server.")
@Comment("If you have multiple servers, you must set this property to a different value for each server.")
@Comment(
"If you have multiple servers, you must set this property to a different value for each server.")
@Comment("Used for local currencies.")
public String server = "none";
@Comment("Storage settings.")
public StorageConfig storage = new StorageConfig();
@Comment("Messaging settings.")
public MessagingConfig messaging = new MessagingConfig();
@Comment("Save interval to storage in seconds.")
public long saveInterval = 5L;
@Comment("Maximum allowed balance.")
@Comment("If you want to change this value, you must also change the data type in the database.")
@Comment(
"If you want to change this value, you must also change the data type in the database.")
public BigDecimal maximumBalance = BigDecimal.valueOf(999999999999999.99);
@Comment("Messages")

View file

@ -8,9 +8,11 @@ import java.util.concurrent.TimeUnit;
public class HousekeeperConfig extends OkaeriConfig {
@Comment("How long should the cache be kept after the last write")
public int expireAfterWrite = 300;
public TimeUnit expireAfterWriteUnit = TimeUnit.SECONDS;
@Comment("How often should housekeeper run")
public int runInterval = 60;
public TimeUnit runIntervalUnit = TimeUnit.SECONDS;
}

View file

@ -4,18 +4,29 @@ import eu.okaeri.configs.OkaeriConfig;
import eu.okaeri.configs.annotation.Variable;
public class CurrencyMessageConfig extends OkaeriConfig {
public String balance = "<currency> <dark_gray>| <gray>Your balance: <yellow><balance> </yellow></gray>";
public String balance =
"<currency> <dark_gray>| <gray>Your balance: <yellow><balance> </yellow></gray>";
@Variable("balance-others")
public String balanceOthers = "<currency> <dark_gray>| <gray>Balance of <yellow><target> <dark_gray>| <gray><yellow><balance> </yellow></gray>";
public String balanceOthers =
"<currency> <dark_gray>| <gray>Balance of <yellow><target> <dark_gray>| <gray><yellow><balance> </yellow></gray>";
public String set = "<currency> <dark_gray>| <gray>Set balance of <gold><target> <yellow>to <gold><amount>";
public String give = "<currency> <dark_gray>| <gray>Gave <gold><target> <gold><amount> <dark_gray>| <gold><balance>";
public String take = "<currency> <dark_gray>| <gray>Took <gold><amount> <yellow>from <gold><target>";
public String set =
"<currency> <dark_gray>| <gray>Set balance of <gold><target> <yellow>to <gold><amount>";
public String give =
"<currency> <dark_gray>| <gray>Gave <gold><target> <gold><amount> <dark_gray>| <gold><balance>";
public String take =
"<currency> <dark_gray>| <gray>Took <gold><amount> <yellow>from <gold><target>";
public String pay = "<currency> <dark_gray>| <gray>Paid <gold><amount> <yellow>to <gold><target>";
public String payWithTax = "<currency> <dark_gray>| <gray>Paid <gold><amount> <yellow>to <gold><target> <dark_gray>(<gold><taxed_amount> <yellow>after tax<dark_gray>)";
public String pay =
"<currency> <dark_gray>| <gray>Paid <gold><amount> <yellow>to <gold><target>";
public String payWithTax =
"<currency> <dark_gray>| <gray>Paid <gold><amount> <yellow>to <gold><target> <dark_gray>(<gold><taxed_amount> <yellow>after tax<dark_gray>)";
public String payReceived =
"<currency> <dark_gray>| <gray>Received <gold><amount> <yellow>from <gold><sender>";
public String payReceivedWithTax =
"<currency> <dark_gray>| <gray>Received <gold><amount> <yellow>from <gold><sender> <dark_gray>(<gold><taxed_amount> <yellow>after tax<dark_gray>)";
public String wait = "<red>Please wait a moment before using this command again.";
public String notEnoughMoney = "<red>You don't have enough money!";
public String cannotPaySelf = "<red>You cannot pay yourself!";
public String cannotBeGreaterThan = "<red>Amount cannot be greater than <gold><max>";

View file

@ -6,5 +6,9 @@ import java.util.Collections;
import java.util.Map;
public class MessageConfig extends OkaeriConfig {
public Map<String, CurrencyMessageConfig> currency = Collections.singletonMap("default", new CurrencyMessageConfig());
public Map<String, CurrencyMessageConfig> currency =
Collections.singletonMap("default", new CurrencyMessageConfig());
public String wait = "<red>Please wait a moment before using this command again.";
public String userNotFound = "<red>User <username> not found.";
}

View file

@ -0,0 +1,16 @@
package dev.xhyrom.lighteco.common.config.messaging;
import dev.xhyrom.lighteco.common.messaging.MessagingType;
import eu.okaeri.configs.OkaeriConfig;
import eu.okaeri.configs.annotation.Comment;
public class MessagingConfig extends OkaeriConfig {
@Comment("Messaging provider.")
@Comment("Available providers: redis")
public MessagingType provider = MessagingType.NONE;
@Comment("Data storage settings.")
@Comment("You don't need to worry about this if you're using plugin message.")
public MessagingDataConfig data = new MessagingDataConfig();
}

View file

@ -0,0 +1,17 @@
package dev.xhyrom.lighteco.common.config.messaging;
import eu.okaeri.configs.OkaeriConfig;
import eu.okaeri.configs.annotation.Comment;
public class MessagingDataConfig extends OkaeriConfig {
@Comment("Define the address and port of the messaging service.")
public String address = "localhost";
@Comment("Credentials for connecting to the messaging service.")
public String username = "root";
public String password = "password";
@Comment("Whether to use SSL to connect to the messaging service.")
public boolean ssl = false;
}

View file

@ -1,6 +1,7 @@
package dev.xhyrom.lighteco.common.config.storage;
import dev.xhyrom.lighteco.common.storage.StorageType;
import eu.okaeri.configs.OkaeriConfig;
import eu.okaeri.configs.annotation.Comment;

View file

@ -12,6 +12,7 @@ public class StorageDataConfig extends OkaeriConfig {
@Comment("Credentials for connecting to the database.")
public String username = "root";
public String password = "password";
@Comment("Maximum number of connections in the pool.")

View file

@ -1,7 +1,9 @@
package dev.xhyrom.lighteco.common.dependencies;
import com.google.common.collect.ImmutableList;
import dev.xhyrom.lighteco.common.dependencies.relocation.Relocation;
import lombok.Getter;
import java.util.List;
@ -12,59 +14,40 @@ public enum Dependency {
* Somewhere we use brackets instad of dots, so we need to rewrite them
* This is because gradle's shadow plugin relocates using replacing full paths (dots)
*/
ASM(
"org.ow2.asm",
"asm",
"9.1"
),
ASM_COMMONS(
"org.ow2.asm",
"asm-commons",
"9.1"
),
JAR_RELOCATOR(
"me.lucko",
"jar-relocator",
"1.7"
),
HIKARI(
"com{}zaxxer",
"HikariCP",
"5.0.1",
Relocation.of("hikari", "com{}zaxxer{}hikari")
),
H2_DRIVER(
"com.h2database",
"h2",
"2.1.214"
),
SQLITE_DRIVER(
"org.xerial",
"sqlite-jdbc",
"3.28.0"
),
ASM("org.ow2.asm", "asm", "9.1"),
ASM_COMMONS("org.ow2.asm", "asm-commons", "9.1"),
JAR_RELOCATOR("me.lucko", "jar-relocator", "1.7"),
HIKARI("com{}zaxxer", "HikariCP", "5.0.1", Relocation.of("hikari", "com{}zaxxer{}hikari")),
H2_DRIVER("com.h2database", "h2", "2.1.214"),
SQLITE_DRIVER("org.xerial", "sqlite-jdbc", "3.28.0"),
MARIADB_DRIVER(
"org{}mariadb{}jdbc",
"mariadb-java-client",
"3.1.3",
Relocation.of("mariadb", "org{}mariadb{}jdbc")
),
MYSQL_DRIVER(
"mysql",
"mysql-connector-java",
"8.0.23",
Relocation.of("mysql", "com{}mysql")
),
Relocation.of("mariadb", "org{}mariadb{}jdbc")),
MYSQL_DRIVER("mysql", "mysql-connector-java", "8.0.23", Relocation.of("mysql", "com{}mysql")),
POSTGRESQL_DRIVER(
"org{}postgresql",
"postgresql",
"42.6.0",
Relocation.of("postgresql", "org{}postgresql")
);
Relocation.of("postgresql", "org{}postgresql")),
JEDIS(
"redis.clients",
"jedis",
"5.1.0",
Relocation.of("jedis", "redis{}clients{}jedis"),
Relocation.of("commonspool2", "org{}apache{}commons{}pool2")),
SLF4J_SIMPLE("org.slf4j", "slf4j-simple", "1.7.30"),
SLF4J_API("org.slf4j", "slf4j-api", "1.7.30"),
COMMONS_POOL_2(
"org.apache.commons",
"commons-pool2",
"2.9.0",
Relocation.of("commonspool2", "org{}apache{}commons{}pool2"));
private final String fullPath;
private final String version;
@Getter
private final List<Relocation> relocations;
@ -75,13 +58,13 @@ public enum Dependency {
}
Dependency(String groupId, String artifactId, String version, Relocation... relocations) {
this.fullPath = String.format(MAVEN_FORMAT,
this.fullPath = String.format(
MAVEN_FORMAT,
rewriteEscape(groupId).replace('.', '/'),
rewriteEscape(artifactId),
version,
rewriteEscape(artifactId),
version
);
version);
this.version = version;
this.relocations = ImmutableList.copyOf(relocations);

View file

@ -1,5 +1,6 @@
package dev.xhyrom.lighteco.common.dependencies;
import dev.xhyrom.lighteco.common.messaging.MessagingType;
import dev.xhyrom.lighteco.common.storage.StorageType;
import java.util.Set;
@ -9,6 +10,8 @@ public interface DependencyManager extends AutoCloseable {
void loadStorageDependencies(Set<StorageType> types);
void loadMessagingDependencies(Set<MessagingType> types);
ClassLoader obtainClassLoaderWith(Set<Dependency> dependencies);
@Override

View file

@ -2,13 +2,16 @@ package dev.xhyrom.lighteco.common.dependencies;
import com.google.common.collect.ImmutableSet;
import com.google.common.io.MoreFiles;
import dev.xhyrom.lighteco.common.config.Config;
import dev.xhyrom.lighteco.common.dependencies.relocation.Relocation;
import dev.xhyrom.lighteco.common.dependencies.relocation.RelocationHandler;
import dev.xhyrom.lighteco.common.messaging.MessagingType;
import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
import dev.xhyrom.lighteco.common.plugin.logger.PluginLogger;
import dev.xhyrom.lighteco.common.util.URLClassLoaderAccess;
import dev.xhyrom.lighteco.common.storage.StorageType;
import dev.xhyrom.lighteco.common.util.URLClassLoaderAccess;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import java.io.IOException;
@ -17,8 +20,6 @@ import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
public class DependencyManagerImpl implements DependencyManager {
private final EnumMap<Dependency, Path> loaded = new EnumMap<>(Dependency.class);
@ -36,7 +37,8 @@ public class DependencyManagerImpl implements DependencyManager {
this.logger = plugin.getBootstrap().getLogger();
this.registry = new DependencyRegistry();
this.cacheDirectory = setupCacheDirectory(plugin);
this.classLoader = URLClassLoaderAccess.create((URLClassLoader) plugin.getBootstrap().getClass().getClassLoader());
this.classLoader = URLClassLoaderAccess.create(
(URLClassLoader) plugin.getBootstrap().getClass().getClassLoader());
}
private synchronized RelocationHandler getRelocationHandler() {
@ -49,42 +51,25 @@ public class DependencyManagerImpl implements DependencyManager {
@Override
public void loadDependencies(Set<Dependency> dependencies) {
CountDownLatch latch = new CountDownLatch(dependencies.size());
if (this.config.debug)
this.logger.info("Loading dependencies: " + dependencies);
if (this.config.debug) this.logger.info("Loading dependencies: " + dependencies);
for (Dependency dependency : dependencies) {
if (this.loaded.containsKey(dependency)) {
latch.countDown();
continue;
}
CompletableFuture.runAsync(() -> {
if (this.config.debug)
this.logger.info("Loading dependency " + dependency);
if (this.config.debug) this.logger.info("Loading dependency " + dependency);
try {
loadDependency(dependency);
} catch (Exception e) {
throw new RuntimeException("Failed to load dependency " + dependency, e);
} finally {
latch.countDown();
if (this.config.debug)
this.logger.info("Loaded dependency " + dependency);
}
});
try {
loadDependency(dependency);
} catch (Exception e) {
throw new RuntimeException("Failed to load dependency " + dependency, e);
} finally {
if (this.config.debug) this.logger.info("Loaded dependency " + dependency);
}
}
try {
latch.await();
if (this.config.debug)
this.logger.info("Loaded dependencies: " + dependencies);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (this.config.debug) this.logger.info("Loaded dependencies: " + dependencies);
}
private void loadDependency(Dependency dependency) throws Exception {
@ -142,6 +127,11 @@ public class DependencyManagerImpl implements DependencyManager {
loadDependencies(this.registry.resolveStorageDependencies(types));
}
@Override
public void loadMessagingDependencies(Set<MessagingType> types) {
loadDependencies(this.registry.resolveMessagingDependencies(types));
}
@Override
public ClassLoader obtainClassLoaderWith(Set<Dependency> dependencies) {
ImmutableSet<Dependency> set = ImmutableSet.copyOf(dependencies);

View file

@ -2,19 +2,32 @@ package dev.xhyrom.lighteco.common.dependencies;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.SetMultimap;
import dev.xhyrom.lighteco.common.messaging.MessagingType;
import dev.xhyrom.lighteco.common.storage.StorageType;
import java.util.LinkedHashSet;
import java.util.Set;
public class DependencyRegistry {
private static final SetMultimap<StorageType, Dependency> STORAGE_DEPENDENCIES = ImmutableSetMultimap.<StorageType, Dependency>builder()
.putAll(StorageType.SQLITE, Dependency.SQLITE_DRIVER)
.putAll(StorageType.H2, Dependency.H2_DRIVER)
.putAll(StorageType.MYSQL, Dependency.MYSQL_DRIVER, Dependency.HIKARI)
.putAll(StorageType.MARIADB, Dependency.MARIADB_DRIVER, Dependency.HIKARI)
.putAll(StorageType.POSTGRESQL, Dependency.POSTGRESQL_DRIVER, Dependency.HIKARI)
.build();
private static final SetMultimap<StorageType, Dependency> STORAGE_DEPENDENCIES =
ImmutableSetMultimap.<StorageType, Dependency>builder()
.putAll(StorageType.SQLITE, Dependency.SQLITE_DRIVER)
.putAll(StorageType.H2, Dependency.H2_DRIVER)
.putAll(StorageType.MYSQL, Dependency.MYSQL_DRIVER, Dependency.HIKARI)
.putAll(StorageType.MARIADB, Dependency.MARIADB_DRIVER, Dependency.HIKARI)
.putAll(StorageType.POSTGRESQL, Dependency.POSTGRESQL_DRIVER, Dependency.HIKARI)
.build();
private static final SetMultimap<MessagingType, Dependency> MESSAGING_DEPENDENCIES =
ImmutableSetMultimap.<MessagingType, Dependency>builder()
.putAll(
MessagingType.REDIS,
Dependency.COMMONS_POOL_2,
Dependency.JEDIS,
Dependency.SLF4J_API,
Dependency.SLF4J_SIMPLE)
.build();
public Set<Dependency> resolveStorageDependencies(Set<StorageType> types) {
Set<Dependency> dependencies = new LinkedHashSet<>();
@ -26,6 +39,16 @@ public class DependencyRegistry {
return dependencies;
}
public Set<Dependency> resolveMessagingDependencies(Set<MessagingType> types) {
Set<Dependency> dependencies = new LinkedHashSet<>();
for (MessagingType type : types) {
dependencies.addAll(MESSAGING_DEPENDENCIES.get(type));
}
return dependencies;
}
public boolean shouldAutoLoad(Dependency dependency) {
return switch (dependency) {
case H2_DRIVER, SQLITE_DRIVER -> false;

View file

@ -11,5 +11,4 @@ public class IsolatedClassLoader extends URLClassLoader {
public IsolatedClassLoader(URL[] urls) {
super(urls, ClassLoader.getSystemClassLoader().getParent());
}
}
}

View file

@ -4,4 +4,4 @@
// Copyright (c) contributors
// Under MIT License
package dev.xhyrom.lighteco.common.dependencies;
package dev.xhyrom.lighteco.common.dependencies;

View file

@ -17,5 +17,4 @@ public class Relocation {
this.pattern = pattern;
this.relocatedPattern = relocatedPattern;
}
}

View file

@ -12,7 +12,8 @@ import java.nio.file.Path;
import java.util.*;
public class RelocationHandler {
public static final Set<Dependency> DEPENDENCIES = EnumSet.of(Dependency.ASM, Dependency.ASM_COMMONS, Dependency.JAR_RELOCATOR);
public static final Set<Dependency> DEPENDENCIES =
EnumSet.of(Dependency.ASM, Dependency.ASM_COMMONS, Dependency.JAR_RELOCATOR);
private static final String JAR_RELOCATOR_CLASS = "me.lucko.jarrelocator.JarRelocator";
private static final String JAR_RELOCATOR_RUN_METHOD = "run";
@ -31,10 +32,12 @@ public class RelocationHandler {
Class<?> jarRelocatorClass = classLoader.loadClass(JAR_RELOCATOR_CLASS);
// prepare the reflected constructor & method instances
this.jarRelocatorConstructor = jarRelocatorClass.getDeclaredConstructor(File.class, File.class, Map.class);
this.jarRelocatorConstructor =
jarRelocatorClass.getDeclaredConstructor(File.class, File.class, Map.class);
this.jarRelocatorConstructor.setAccessible(true);
this.jarRelocatorRunMethod = jarRelocatorClass.getDeclaredMethod(JAR_RELOCATOR_RUN_METHOD);
this.jarRelocatorRunMethod =
jarRelocatorClass.getDeclaredMethod(JAR_RELOCATOR_RUN_METHOD);
this.jarRelocatorRunMethod.setAccessible(true);
} catch (Exception e) {
try {
@ -56,8 +59,8 @@ public class RelocationHandler {
}
// create and invoke a new relocator
Object relocator = this.jarRelocatorConstructor.newInstance(input.toFile(), output.toFile(), mappings);
Object relocator =
this.jarRelocatorConstructor.newInstance(input.toFile(), output.toFile(), mappings);
this.jarRelocatorRunMethod.invoke(relocator);
}
}

View file

@ -6,6 +6,7 @@ public interface Manager<I, T> {
T apply(I identifier);
Collection<I> keys();
Collection<T> values();
T getOrMake(I identifier);

View file

@ -1,260 +0,0 @@
package dev.xhyrom.lighteco.common.manager.command;
import dev.xhyrom.lighteco.api.exception.CannotBeGreaterThan;
import dev.xhyrom.lighteco.common.config.message.CurrencyMessageConfig;
import dev.xhyrom.lighteco.common.model.chat.CommandSender;
import dev.xhyrom.lighteco.common.model.currency.Currency;
import dev.xhyrom.lighteco.common.model.user.User;
import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Map;
import java.util.UUID;
public abstract class AbstractCommandManager implements CommandManager {
public final LightEcoPlugin plugin;
private final MiniMessage miniMessage = MiniMessage.miniMessage();
private final Map<String, CurrencyMessageConfig> config;
private final ArrayList<UUID> mustWait = new ArrayList<>();
protected AbstractCommandManager(LightEcoPlugin plugin) {
this.plugin = plugin;
this.config = this.plugin.getConfig().messages.currency;
}
@Override
public boolean canUse(CommandSender sender, Currency currency) {
// Console doesn't need to wait
if (sender.getUniqueId() == null) return true;
if (mustWait.contains(sender.getUniqueId())) {
sender.sendMessage(
miniMessage.deserialize(
this.getConfig(currency).wait
)
);
return false;
}
return true;
}
private void addToMustWait(UUID ...uuids) {
for (UUID uuid : uuids) {
if (uuid != null)
mustWait.add(uuid);
}
}
private void removeFromMustWait(UUID ...uuids) {
for (UUID uuid : uuids) {
if (uuid != null)
mustWait.remove(uuid);
}
}
private CurrencyMessageConfig getConfig(Currency currency) {
CurrencyMessageConfig currencyMessageConfig = this.config.get(currency.getIdentifier());
if (currencyMessageConfig == null) {
return this.config.get("default");
}
return currencyMessageConfig;
}
@Override
public void onBalance(CommandSender sender, Currency currency) {
User user = this.plugin.getUserManager().getIfLoaded(sender.getUniqueId());
BigDecimal balance = user.getBalance(currency);
sender.sendMessage(
miniMessage.deserialize(
getConfig(currency).balance,
Placeholder.parsed("currency", currency.getIdentifier()),
Placeholder.parsed("balance", balance.toPlainString())
)
);
}
@Override
public void onBalance(CommandSender sender, Currency currency, User target) {
BigDecimal balance = target.getBalance(currency);
sender.sendMessage(
miniMessage.deserialize(
getConfig(currency).balanceOthers,
Placeholder.parsed("currency", currency.getIdentifier()),
Placeholder.parsed("target", target.getUsername()),
Placeholder.parsed("balance", balance.toPlainString())
)
);
}
@Override
public void onSet(CommandSender sender, Currency currency, User target, BigDecimal amount) {
addToMustWait(sender.getUniqueId(), target.getUniqueId());
amount = amount.setScale(currency.getProxy().fractionalDigits(), RoundingMode.DOWN);
try {
target.setBalance(currency, amount);
} catch (CannotBeGreaterThan e) {
removeFromMustWait(target.getUniqueId(), sender.getUniqueId());
sender.sendMessage(
miniMessage.deserialize(
this.getConfig(currency).cannotBeGreaterThan,
Placeholder.parsed("max", this.plugin.getConfig().maximumBalance.toPlainString())
)
);
return;
}
sender.sendMessage(
miniMessage.deserialize(
this.getConfig(currency).set,
Placeholder.parsed("currency", currency.getIdentifier()),
Placeholder.parsed("target", target.getUsername()),
Placeholder.parsed("amount", amount.toPlainString())
)
);
removeFromMustWait(target.getUniqueId(), sender.getUniqueId());
}
@Override
public void onGive(CommandSender sender, Currency currency, User target, BigDecimal amount) {
addToMustWait(sender.getUniqueId(), target.getUniqueId());
amount = amount.setScale(currency.getProxy().fractionalDigits(), RoundingMode.DOWN);
try {
target.deposit(currency, amount);
} catch (CannotBeGreaterThan e) {
removeFromMustWait(target.getUniqueId(), sender.getUniqueId());
sender.sendMessage(
miniMessage.deserialize(
this.getConfig(currency).cannotBeGreaterThan,
Placeholder.parsed("max", this.plugin.getConfig().maximumBalance.toPlainString())
)
);
return;
}
sender.sendMessage(
miniMessage.deserialize(
this.getConfig(currency).give,
Placeholder.parsed("currency", currency.getIdentifier()),
Placeholder.parsed("target", target.getUsername()),
Placeholder.parsed("amount", amount.toPlainString()),
Placeholder.parsed("balance", target.getBalance(currency).toPlainString())
)
);
removeFromMustWait(target.getUniqueId(), sender.getUniqueId());
}
@Override
public void onTake(CommandSender sender, Currency currency, User target, BigDecimal amount) {
addToMustWait(sender.getUniqueId(), target.getUniqueId());
amount = amount.setScale(currency.getProxy().fractionalDigits(), RoundingMode.DOWN);
try {
target.withdraw(currency, amount);
} catch (CannotBeGreaterThan e) {
removeFromMustWait(target.getUniqueId(), sender.getUniqueId());
sender.sendMessage(
miniMessage.deserialize(
this.getConfig(currency).cannotBeGreaterThan,
Placeholder.parsed("max", this.plugin.getConfig().maximumBalance.toPlainString())
)
);
return;
}
sender.sendMessage(
miniMessage.deserialize(
this.getConfig(currency).take,
Placeholder.parsed("currency", currency.getIdentifier()),
Placeholder.parsed("target", target.getUsername()),
Placeholder.parsed("amount", amount.toPlainString()),
Placeholder.parsed("balance", target.getBalance(currency).toPlainString())
)
);
removeFromMustWait(target.getUniqueId(), sender.getUniqueId());
}
@Override
public void onPay(CommandSender sender, Currency currency, User target, BigDecimal amount) {
if (sender.getUniqueId() != null && (sender.getUniqueId() == target.getUniqueId())) {
sender.sendMessage(
miniMessage.deserialize(this.getConfig(currency).cannotPaySelf)
);
return;
}
amount = amount.setScale(currency.getProxy().fractionalDigits(), RoundingMode.DOWN);
User user = this.plugin.getUserManager().getIfLoaded(sender.getUniqueId());
if (user.getBalance(currency).compareTo(amount) < 0) {
sender.sendMessage(
miniMessage.deserialize(this.getConfig(currency).notEnoughMoney)
);
return;
}
addToMustWait(sender.getUniqueId(), target.getUniqueId());
// calculate tax using Currency#calculateTax
BigDecimal tax = currency.getProxy().calculateTax(user.getProxy(), amount);
tax = tax.setScale(currency.getProxy().fractionalDigits(), RoundingMode.DOWN);
// subtract tax from amount
BigDecimal taxedAmount = amount.subtract(tax);
try {
target.deposit(currency, taxedAmount);
user.withdraw(currency, amount);
} catch (CannotBeGreaterThan e) {
removeFromMustWait(target.getUniqueId(), sender.getUniqueId());
sender.sendMessage(
miniMessage.deserialize(
this.getConfig(currency).cannotBeGreaterThan,
Placeholder.parsed("max", this.plugin.getConfig().maximumBalance.toPlainString())
)
);
return;
}
String template = tax.compareTo(BigDecimal.ZERO) > 0
? this.getConfig(currency).payWithTax
: this.getConfig(currency).pay;
sender.sendMessage(
miniMessage.deserialize(
template,
Placeholder.parsed("currency", currency.getIdentifier()),
Placeholder.parsed("target", target.getUsername()),
Placeholder.parsed("amount", amount.toPlainString()),
Placeholder.parsed("taxed_amount", taxedAmount.toPlainString()),
Placeholder.parsed("sender_balance", user.getBalance(currency).toPlainString()),
Placeholder.parsed("receiver_balance", target.getBalance(currency).toPlainString())
)
);
removeFromMustWait(target.getUniqueId(), sender.getUniqueId());
}
}

View file

@ -1,23 +0,0 @@
package dev.xhyrom.lighteco.common.manager.command;
import dev.xhyrom.lighteco.common.model.chat.CommandSender;
import dev.xhyrom.lighteco.common.model.currency.Currency;
import dev.xhyrom.lighteco.common.model.user.User;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.math.BigDecimal;
public interface CommandManager {
void registerCurrencyCommand(@NonNull Currency currency);
void registerCurrencyCommand(@NonNull Currency currency, boolean main);
boolean canUse(CommandSender sender, Currency currency);
void onBalance(CommandSender sender, Currency currency);
void onBalance(CommandSender sender, Currency currency, User target);
void onSet(CommandSender sender, Currency currency, User target, BigDecimal amount);
void onGive(CommandSender sender, Currency currency, User target, BigDecimal amount);
void onTake(CommandSender sender, Currency currency, User target, BigDecimal amount);
void onPay(CommandSender sender, Currency currency, User target, BigDecimal amount);
}

View file

@ -2,6 +2,7 @@ package dev.xhyrom.lighteco.common.manager.currency;
import dev.xhyrom.lighteco.common.manager.Manager;
import dev.xhyrom.lighteco.common.model.currency.Currency;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Collection;

View file

@ -3,11 +3,13 @@ package dev.xhyrom.lighteco.common.manager.currency;
import dev.xhyrom.lighteco.common.manager.SingleManager;
import dev.xhyrom.lighteco.common.model.currency.Currency;
import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Collection;
public class StandardCurrencyManager extends SingleManager<String, Currency> implements CurrencyManager {
public class StandardCurrencyManager extends SingleManager<String, Currency>
implements CurrencyManager {
private final LightEcoPlugin plugin;
public StandardCurrencyManager(LightEcoPlugin plugin) {
@ -32,10 +34,14 @@ public class StandardCurrencyManager extends SingleManager<String, Currency> imp
@Override
public void registerCurrency(@NonNull Currency currency) {
if (this.isLoaded(currency.getIdentifier()))
throw new IllegalArgumentException("Currency with identifier " + currency.getIdentifier() + " already registered");
throw new IllegalArgumentException(
"Currency with identifier " + currency.getIdentifier() + " already registered");
if (this.plugin.getConfig().debug)
this.plugin.getBootstrap().getLogger().info("Registering currency " + currency.getIdentifier());
this.plugin
.getBootstrap()
.getLogger()
.info("Registering currency " + currency.getIdentifier());
this.plugin.getStorage().registerCurrencySync(currency.getProxy());
this.map.put(currency.getIdentifier(), currency);

View file

@ -3,6 +3,7 @@ package dev.xhyrom.lighteco.common.manager.user;
import dev.xhyrom.lighteco.common.manager.ConcurrentManager;
import dev.xhyrom.lighteco.common.model.user.User;
import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
import lombok.Getter;
import java.util.Arrays;
@ -11,21 +12,26 @@ import java.util.concurrent.CompletableFuture;
public class StandardUserManager extends ConcurrentManager<UUID, User> implements UserManager {
private final LightEcoPlugin plugin;
@Getter
private final UserHousekeeper housekeeper;
public StandardUserManager(LightEcoPlugin plugin) {
this.plugin = plugin;
this.housekeeper = new UserHousekeeper(plugin, this, UserHousekeeper.timeoutSettings(
this.plugin.getConfig().housekeeper.expireAfterWrite,
this.plugin.getConfig().housekeeper.expireAfterWriteUnit
));
this.housekeeper = new UserHousekeeper(
plugin,
this,
UserHousekeeper.timeoutSettings(
this.plugin.getConfig().housekeeper.expireAfterWrite,
this.plugin.getConfig().housekeeper.expireAfterWriteUnit));
this.plugin.getBootstrap().getScheduler().asyncRepeating(
this.housekeeper,
this.plugin.getConfig().housekeeper.runInterval,
this.plugin.getConfig().housekeeper.runIntervalUnit
);
this.plugin
.getBootstrap()
.getScheduler()
.asyncRepeating(
this.housekeeper,
this.plugin.getConfig().housekeeper.runInterval,
this.plugin.getConfig().housekeeper.runIntervalUnit);
}
@Override
@ -49,15 +55,15 @@ public class StandardUserManager extends ConcurrentManager<UUID, User> implement
@Override
public CompletableFuture<Void> saveUser(User user) {
return this.plugin.getStorage().saveUser(user.getProxy());
return this.plugin.getStorage().saveUser(user.getProxy());
}
@Override
public CompletableFuture<Void> saveUsers(User... users) {
return this.plugin.getStorage().saveUsers(
Arrays.stream(users)
return this.plugin
.getStorage()
.saveUsers(Arrays.stream(users)
.map(User::getProxy)
.toArray(dev.xhyrom.lighteco.api.model.user.User[]::new)
);
.toArray(dev.xhyrom.lighteco.api.model.user.User[]::new));
}
}
}

View file

@ -13,7 +13,8 @@ public class UserHousekeeper implements Runnable {
private final ExpiringSet<UUID> recentlyUsed;
public UserHousekeeper(LightEcoPlugin plugin, UserManager userManager, TimeoutSettings timeoutSettings) {
public UserHousekeeper(
LightEcoPlugin plugin, UserManager userManager, TimeoutSettings timeoutSettings) {
this.plugin = plugin;
this.userManager = userManager;
this.recentlyUsed = new ExpiringSet<>(timeoutSettings.duration, timeoutSettings.unit);
@ -41,8 +42,7 @@ public class UserHousekeeper implements Runnable {
}
// If the user is dirty (has unsaved changes), don't unload
if (user.isDirty())
return;
if (user.isDirty()) return;
if (this.plugin.getConfig().debug) {
this.plugin.getBootstrap().getLogger().info("Unloading data for " + uuid);
@ -64,4 +64,4 @@ public class UserHousekeeper implements Runnable {
this.unit = unit;
}
}
}
}

View file

@ -10,8 +10,10 @@ public interface UserManager extends Manager<UUID, User> {
UserHousekeeper getHousekeeper();
CompletableFuture<Void> saveUser(User user);
CompletableFuture<Void> saveUsers(User... users);
CompletableFuture<User> loadUser(UUID uniqueId);
CompletableFuture<User> loadUser(UUID uniqueId, String username);
}

View file

@ -0,0 +1,13 @@
package dev.xhyrom.lighteco.common.messaging;
import dev.xhyrom.lighteco.common.model.currency.Currency;
import dev.xhyrom.lighteco.common.model.user.User;
import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
public interface InternalMessagingService {
LightEcoPlugin getPlugin();
void pushUserUpdate(User user, Currency currency);
void shutdown();
}

View file

@ -0,0 +1,163 @@
package dev.xhyrom.lighteco.common.messaging;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import dev.xhyrom.lighteco.api.messenger.IncomingMessageConsumer;
import dev.xhyrom.lighteco.api.messenger.Messenger;
import dev.xhyrom.lighteco.api.messenger.MessengerProvider;
import dev.xhyrom.lighteco.api.messenger.message.Message;
import dev.xhyrom.lighteco.api.messenger.message.type.UserUpdateMessage;
import dev.xhyrom.lighteco.common.cache.ExpiringSet;
import dev.xhyrom.lighteco.common.messaging.message.MessageType;
import dev.xhyrom.lighteco.common.messaging.message.UserUpdateMessageImpl;
import dev.xhyrom.lighteco.common.model.currency.Currency;
import dev.xhyrom.lighteco.common.model.user.User;
import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
import dev.xhyrom.lighteco.common.util.gson.GsonProvider;
import lombok.Getter;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
public class LightEcoMessagingService implements InternalMessagingService, IncomingMessageConsumer {
@Getter
private final LightEcoPlugin plugin;
private final ExpiringSet<UUID> receivedMessages;
private final Messenger messenger;
private final MessengerProvider provider;
public LightEcoMessagingService(LightEcoPlugin plugin, MessengerProvider provider) {
this.plugin = plugin;
this.provider = provider;
this.messenger = provider.obtain(this);
this.receivedMessages = new ExpiringSet<>(30, TimeUnit.MINUTES);
}
private UUID generateMessageId() {
UUID id = UUID.randomUUID();
this.receivedMessages.add(id);
return id;
}
@Override
public void pushUserUpdate(User user, Currency currency) {
this.plugin
.getBootstrap()
.getScheduler()
.async()
.execute(() -> this.messenger.sendOutgoingMessage(
new UserUpdateMessageImpl(
generateMessageId(),
user.getUniqueId(),
currency.getIdentifier(),
user.getBalance(currency)),
currency.getType()
== dev.xhyrom.lighteco.api.model.currency.Currency.Type.GLOBAL));
}
public static @NonNull String serialize(MessageType type, UUID id, JsonElement content) {
JsonObject data = new JsonObject();
data.add("i", new JsonPrimitive(id.toString()));
data.add("t", new JsonPrimitive(type.name()));
data.add("c", content);
return GsonProvider.get().toJson(data);
}
@Override
public void consumeIncomingMessage(@NonNull Message message) {
if (!this.receivedMessages.add(message.getId())) {
return;
}
this.processIncomingMessage(message);
}
@Override
public void consumeRawIncomingMessage(@NonNull String message) {
try {
deserializeAndConsumeRawIncomingMessage(message);
} catch (Exception e) {
this.plugin
.getBootstrap()
.getLogger()
.warn("Failed to deserialize incoming message: " + message, e);
}
}
private void deserializeAndConsumeRawIncomingMessage(@NonNull String message) {
JsonObject parsed = GsonProvider.get().fromJson(message, JsonObject.class);
JsonElement idElement = parsed.get("i");
if (idElement == null) {
throw new IllegalStateException("Missing message id: " + message);
}
UUID id = UUID.fromString(idElement.getAsString());
if (!this.receivedMessages.add(id)) {
return;
}
JsonElement typeElement = parsed.get("t");
if (typeElement == null) {
throw new IllegalStateException("Missing message type: " + message);
}
MessageType type = MessageType.valueOf(typeElement.getAsString());
@Nullable JsonElement contentElement = parsed.get("c");
Message deserialized;
switch (type) {
case USER_UPDATE:
deserialized = UserUpdateMessageImpl.deserialize(id, contentElement);
break;
default:
return;
}
this.processIncomingMessage(deserialized);
}
private void processIncomingMessage(Message message) {
if (message instanceof UserUpdateMessage userUpdateMessage) {
this.plugin.getBootstrap().getScheduler().async().execute(() -> {
User user = this.plugin
.getUserManager()
.getIfLoaded(userUpdateMessage.getUserUniqueId());
if (user == null) {
return;
}
Currency currency = this.plugin
.getCurrencyManager()
.getIfLoaded(userUpdateMessage.getCurrencyIdentifier());
if (currency == null) {
return;
}
user.setBalance(currency, userUpdateMessage.getNewBalance(), false, false);
});
} else {
throw new IllegalStateException(
"Unknown message type: " + message.getClass().getName());
}
}
@Override
public void shutdown() {
this.messenger.close();
}
}

View file

@ -0,0 +1,38 @@
package dev.xhyrom.lighteco.common.messaging;
import dev.xhyrom.lighteco.api.messenger.MessengerProvider;
import dev.xhyrom.lighteco.common.messaging.type.redis.RedisMessengerProvider;
import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
import java.util.Set;
public class MessagingFactory {
private final LightEcoPlugin plugin;
public MessagingFactory(LightEcoPlugin plugin) {
this.plugin = plugin;
}
protected LightEcoPlugin getPlugin() {
return this.plugin;
}
public Set<MessagingType> getRequiredTypes() {
return Set.of(this.plugin.getConfig().messaging.provider);
}
public InternalMessagingService get() {
MessagingType type = this.plugin.getConfig().messaging.provider;
MessengerProvider provider = this.createProvider(type);
if (provider == null) return null;
return new LightEcoMessagingService(this.plugin, provider);
}
private MessengerProvider createProvider(MessagingType type) {
return switch (type) {
case REDIS -> new RedisMessengerProvider(this.plugin);
default -> null;
};
}
}

View file

@ -0,0 +1,6 @@
package dev.xhyrom.lighteco.common.messaging;
public enum MessagingType {
NONE,
REDIS
}

View file

@ -0,0 +1,21 @@
package dev.xhyrom.lighteco.common.messaging.message;
import dev.xhyrom.lighteco.api.messenger.message.Message;
import dev.xhyrom.lighteco.api.messenger.message.OutgoingMessage;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.UUID;
public abstract class AbstractMessage implements Message, OutgoingMessage {
private final UUID id;
protected AbstractMessage(UUID id) {
this.id = id;
}
@Override
public @NonNull UUID getId() {
return this.id;
}
}

View file

@ -0,0 +1,5 @@
package dev.xhyrom.lighteco.common.messaging.message;
public enum MessageType {
USER_UPDATE;
}

View file

@ -0,0 +1,55 @@
package dev.xhyrom.lighteco.common.messaging.message;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import dev.xhyrom.lighteco.api.messenger.message.type.UserUpdateMessage;
import dev.xhyrom.lighteco.common.messaging.LightEcoMessagingService;
import lombok.Getter;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.math.BigDecimal;
import java.util.UUID;
public class UserUpdateMessageImpl extends AbstractMessage implements UserUpdateMessage {
private static final MessageType TYPE = MessageType.USER_UPDATE;
@Getter
private final UUID userUniqueId;
@Getter
private final String currencyIdentifier;
@Getter
private final BigDecimal newBalance;
public static UserUpdateMessage deserialize(UUID id, @NonNull JsonElement data) {
JsonObject obj = data.getAsJsonObject();
UUID userUniqueId = UUID.fromString(obj.get("u").getAsString());
String currencyIdentifier = obj.get("c").getAsString();
BigDecimal newBalance = obj.get("b").getAsBigDecimal();
return new UserUpdateMessageImpl(id, userUniqueId, currencyIdentifier, newBalance);
}
public UserUpdateMessageImpl(
UUID id, UUID userUniqueId, String currencyIdentifier, BigDecimal newBalance) {
super(id);
this.userUniqueId = userUniqueId;
this.currencyIdentifier = currencyIdentifier;
this.newBalance = newBalance;
}
@Override
public @NonNull String serialize() {
JsonObject data = new JsonObject();
data.add("u", new JsonPrimitive(this.userUniqueId.toString()));
data.add("c", new JsonPrimitive(this.currencyIdentifier));
data.add("b", new JsonPrimitive(this.newBalance));
return LightEcoMessagingService.serialize(TYPE, getId(), data);
}
}

View file

@ -0,0 +1,97 @@
package dev.xhyrom.lighteco.common.messaging.type.redis;
import dev.xhyrom.lighteco.api.messenger.IncomingMessageConsumer;
import dev.xhyrom.lighteco.api.messenger.Messenger;
import dev.xhyrom.lighteco.api.messenger.message.OutgoingMessage;
import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
import lombok.Getter;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import redis.clients.jedis.*;
public class RedisMessenger implements Messenger {
private static final String CHANNEL = "lighteco:{}:messages";
@Getter
private final String[] channels;
private final LightEcoPlugin plugin;
private final IncomingMessageConsumer consumer;
private UnifiedJedis jedis;
private Subscription sub;
public RedisMessenger(LightEcoPlugin plugin, IncomingMessageConsumer consumer) {
this.plugin = plugin;
this.consumer = consumer;
this.channels = new String[] {
CHANNEL.replace("{}:", ""), CHANNEL.replace("{}", this.plugin.getConfig().server)
};
}
public void init(
@Nullable String address, @Nullable String username, String password, boolean ssl) {
this.init(new JedisPooled(parseAddress(address), jedisConfig(username, password, ssl)));
}
private void init(UnifiedJedis jedis) {
this.jedis = jedis;
this.sub = new Subscription(this);
this.plugin.getBootstrap().getScheduler().async().execute(() -> {
this.jedis.subscribe(this.sub, this.getChannels());
});
}
private static JedisClientConfig jedisConfig(
@Nullable String username, @Nullable String password, boolean ssl) {
return DefaultJedisClientConfig.builder()
.user(username)
.password(password)
.ssl(ssl)
.timeoutMillis(Protocol.DEFAULT_TIMEOUT)
.build();
}
private static HostAndPort parseAddress(String address) {
String[] addressSplit = address.split(":");
String host = addressSplit[0];
int port =
addressSplit.length > 1 ? Integer.parseInt(addressSplit[1]) : Protocol.DEFAULT_PORT;
return new HostAndPort(host, port);
}
@Override
public void sendOutgoingMessage(@NonNull OutgoingMessage message, boolean global) {
this.jedis.publish(global ? getChannels()[0] : getChannels()[1], message.serialize());
}
@Override
public void close() {
this.sub.unsubscribe();
this.jedis.close();
}
private static class Subscription extends JedisPubSub {
private final RedisMessenger messenger;
public Subscription(RedisMessenger messenger) {
this.messenger = messenger;
}
@Override
public void onMessage(String channel, String message) {
String[] channels = this.messenger.getChannels();
if (!channel.equals(channels[0]) && !channel.equals(channels[1])) {
return;
}
this.messenger.consumer.consumeRawIncomingMessage(message);
}
}
}

View file

@ -0,0 +1,34 @@
package dev.xhyrom.lighteco.common.messaging.type.redis;
import dev.xhyrom.lighteco.api.messenger.IncomingMessageConsumer;
import dev.xhyrom.lighteco.api.messenger.Messenger;
import dev.xhyrom.lighteco.api.messenger.MessengerProvider;
import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
import org.checkerframework.checker.nullness.qual.NonNull;
public class RedisMessengerProvider implements MessengerProvider {
private final LightEcoPlugin plugin;
public RedisMessengerProvider(LightEcoPlugin plugin) {
this.plugin = plugin;
}
@Override
public @NonNull Messenger obtain(@NonNull IncomingMessageConsumer consumer) {
RedisMessenger messenger = new RedisMessenger(this.plugin, consumer);
String address = this.plugin.getConfig().messaging.data.address;
String username = this.plugin.getConfig().messaging.data.username;
String password = this.plugin.getConfig().messaging.data.password;
if (password.isEmpty()) password = null;
if (username.isEmpty()) username = null;
boolean ssl = this.plugin.getConfig().messaging.data.ssl;
messenger.init(address, username, password, ssl);
return messenger;
}
}

View file

@ -1,9 +1,9 @@
package dev.xhyrom.lighteco.common.model.chat;
public abstract class AbstractCommandSender<T> implements CommandSender {
protected final T delegate;
protected final T delegate;
protected AbstractCommandSender(T delegate) {
this.delegate = delegate;
}
protected AbstractCommandSender(T delegate) {
this.delegate = delegate;
}
}

View file

@ -2,11 +2,20 @@ package dev.xhyrom.lighteco.common.model.chat;
import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.UUID;
public interface CommandSender {
String getUsername();
UUID getUniqueId();
@Nullable UUID getUniqueId();
boolean eligible(String permission);
void sendMessage(Component message);
default boolean isConsole() {
return getUniqueId() == null;
}
}

View file

@ -4,15 +4,21 @@ import dev.xhyrom.lighteco.api.exception.CannotBeGreaterThan;
import dev.xhyrom.lighteco.api.exception.CannotBeNegative;
import dev.xhyrom.lighteco.common.api.impl.ApiUser;
import dev.xhyrom.lighteco.common.cache.RedisBackedMap;
import dev.xhyrom.lighteco.common.messaging.InternalMessagingService;
import dev.xhyrom.lighteco.common.model.currency.Currency;
import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
import lombok.Getter;
import lombok.Setter;
import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.HashMap;
import java.util.Optional;
import java.util.UUID;
@Getter
@ -28,6 +34,7 @@ public class User {
@Getter
@Setter
private boolean dirty = false;
@Getter
@Setter
private String username;
@ -48,45 +55,63 @@ public class User {
return balances.getOrDefault(currency, currency.getDefaultBalance());
}
public void setBalance(@NonNull Currency currency, @NonNull BigDecimal balance) throws CannotBeNegative, CannotBeGreaterThan {
this.setBalance(currency, balance, false);
public void setBalance(@NonNull Currency currency, @NonNull BigDecimal balance)
throws CannotBeNegative, CannotBeGreaterThan {
this.setBalance(currency, balance, false, true);
}
public void setBalance(@NonNull Currency currency, @NonNull BigDecimal balance, boolean force) throws CannotBeNegative, CannotBeGreaterThan {
public void setBalance(@NonNull Currency currency, @NonNull BigDecimal balance, boolean force)
throws CannotBeNegative, CannotBeGreaterThan {
this.setBalance(currency, balance, force, true);
}
public void setBalance(
@NonNull Currency currency, @NonNull BigDecimal balance, boolean force, boolean publish)
throws CannotBeNegative, CannotBeGreaterThan {
if (balance.compareTo(BigDecimal.ZERO) < 0) {
throw new CannotBeNegative("Balance cannot be negative");
}
if (balance.compareTo(this.plugin.getConfig().maximumBalance) > 0) {
throw new CannotBeGreaterThan("Balance cannot be greater than " + this.plugin.getConfig().maximumBalance);
throw new CannotBeGreaterThan(
"Balance cannot be greater than " + this.plugin.getConfig().maximumBalance);
}
balance = balance.setScale(currency.fractionalDigits(), RoundingMode.DOWN);
balances.put(currency, balance);
if (!force)
this.setDirty(true);
if (!force) this.setDirty(true);
if (publish) {
@NonNull Optional<InternalMessagingService> messagingService = this.plugin.getMessagingService();
messagingService.ifPresent(internalMessagingService ->
internalMessagingService.pushUserUpdate(this, currency));
}
}
public void deposit(@NonNull Currency currency, @NonNull BigDecimal amount) throws CannotBeNegative, CannotBeGreaterThan {
public void deposit(@NonNull Currency currency, @NonNull BigDecimal amount)
throws CannotBeNegative, CannotBeGreaterThan {
if (amount.compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalArgumentException("Amount cannot be negative");
}
if (amount.compareTo(this.plugin.getConfig().maximumBalance) > 0) {
throw new CannotBeGreaterThan("Amount cannot be greater than " + this.plugin.getConfig().maximumBalance);
throw new CannotBeGreaterThan(
"Amount cannot be greater than " + this.plugin.getConfig().maximumBalance);
}
this.setBalance(currency, this.getBalance(currency).add(amount));
}
public void withdraw(@NonNull Currency currency, @NonNull BigDecimal amount) throws CannotBeNegative, CannotBeGreaterThan {
public void withdraw(@NonNull Currency currency, @NonNull BigDecimal amount)
throws CannotBeNegative, CannotBeGreaterThan {
if (amount.compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalArgumentException("Amount cannot be negative");
}
if (amount.compareTo(this.plugin.getConfig().maximumBalance) > 0) {
throw new CannotBeGreaterThan("Amount cannot be greater than " + this.plugin.getConfig().maximumBalance);
throw new CannotBeGreaterThan(
"Amount cannot be greater than " + this.plugin.getConfig().maximumBalance);
}
if (this.getBalance(currency).compareTo(amount) < 0) {
@ -96,4 +121,8 @@ public class User {
this.setBalance(currency, this.getBalance(currency).subtract(amount));
}
public void sendMessage(@NonNull Component message) {
this.plugin.getBootstrap().getPlayerAudience(this.getUniqueId()).sendMessage(message);
}
}

View file

@ -1,4 +1,4 @@
/**
* Common classes used by all platform-specific implementations.
*/
package dev.xhyrom.lighteco.common;
package dev.xhyrom.lighteco.common;

View file

@ -6,23 +6,33 @@ import dev.xhyrom.lighteco.common.api.LightEcoApi;
import dev.xhyrom.lighteco.common.config.Config;
import dev.xhyrom.lighteco.common.dependencies.DependencyManager;
import dev.xhyrom.lighteco.common.dependencies.DependencyManagerImpl;
import dev.xhyrom.lighteco.common.messaging.InternalMessagingService;
import dev.xhyrom.lighteco.common.messaging.MessagingFactory;
import dev.xhyrom.lighteco.common.storage.Storage;
import dev.xhyrom.lighteco.common.storage.StorageFactory;
import dev.xhyrom.lighteco.common.task.UserSaveTask;
import eu.okaeri.configs.ConfigManager;
import eu.okaeri.configs.yaml.snakeyaml.YamlSnakeYamlConfigurer;
import lombok.Getter;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
@Getter
public abstract class AbstractLightEcoPlugin implements LightEcoPlugin {
private DependencyManager dependencyManager;
@Getter
private Config config;
@Getter
private Storage storage;
private InternalMessagingService messagingService;
private LightEcoApi api;
private UserSaveTask userSaveTask;
@ -41,10 +51,14 @@ public abstract class AbstractLightEcoPlugin implements LightEcoPlugin {
public final void enable() {
// setup storage
StorageFactory factory = new StorageFactory(this);
this.dependencyManager.loadStorageDependencies(factory.getRequiredTypes());
StorageFactory storageFactory = new StorageFactory(this);
this.dependencyManager.loadStorageDependencies(storageFactory.getRequiredTypes());
this.storage = factory.get();
MessagingFactory messagingFactory = this.getMessagingFactory();
this.dependencyManager.loadMessagingDependencies(messagingFactory.getRequiredTypes());
this.storage = storageFactory.get();
this.messagingService = messagingFactory.get();
// register listeners
this.registerListeners();
@ -52,6 +66,9 @@ public abstract class AbstractLightEcoPlugin implements LightEcoPlugin {
// setup managers
this.setupManagers();
// register built-in commands
this.getCommandManager().register();
// register platform hooks
this.registerPlatformHooks();
@ -61,7 +78,9 @@ public abstract class AbstractLightEcoPlugin implements LightEcoPlugin {
this.registerApiOnPlatform(this.api);
this.userSaveTask = new UserSaveTask(this);
this.getBootstrap().getScheduler().asyncRepeating(userSaveTask, this.config.saveInterval, TimeUnit.SECONDS);
this.getBootstrap()
.getScheduler()
.asyncRepeating(userSaveTask, this.config.saveInterval, TimeUnit.SECONDS);
}
public final void disable() {
@ -74,6 +93,8 @@ public abstract class AbstractLightEcoPlugin implements LightEcoPlugin {
// shutdown storage
this.storage.shutdown();
if (this.messagingService != null) this.messagingService.shutdown();
// close isolated class loaders
this.dependencyManager.close();
}
@ -81,8 +102,17 @@ public abstract class AbstractLightEcoPlugin implements LightEcoPlugin {
protected abstract void registerListeners();
protected abstract void setupManagers();
protected abstract MessagingFactory getMessagingFactory();
protected abstract void registerApiOnPlatform(LightEco api);
protected abstract void registerPlatformHooks();
protected abstract void removePlatformHooks();
@Override
public @NonNull Optional<InternalMessagingService> getMessagingService() {
return Optional.ofNullable(this.messagingService);
}
}

View file

@ -2,27 +2,37 @@ package dev.xhyrom.lighteco.common.plugin;
import dev.xhyrom.lighteco.api.manager.ContextManager;
import dev.xhyrom.lighteco.api.platform.Platform;
import dev.xhyrom.lighteco.common.command.CommandManager;
import dev.xhyrom.lighteco.common.config.Config;
import dev.xhyrom.lighteco.common.dependencies.DependencyManager;
import dev.xhyrom.lighteco.common.manager.command.CommandManager;
import dev.xhyrom.lighteco.common.manager.currency.CurrencyManager;
import dev.xhyrom.lighteco.common.manager.user.UserManager;
import dev.xhyrom.lighteco.common.messaging.InternalMessagingService;
import dev.xhyrom.lighteco.common.plugin.bootstrap.LightEcoBootstrap;
import dev.xhyrom.lighteco.common.storage.Storage;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Optional;
public interface LightEcoPlugin {
Platform.@NonNull Type getPlatformType();
@NonNull LightEcoBootstrap getBootstrap();
@NonNull Config getConfig();
@NonNull UserManager getUserManager();
@NonNull CurrencyManager getCurrencyManager();
@NonNull CommandManager getCommandManager();
@NonNull ContextManager<?> getContextManager();
@NonNull DependencyManager getDependencyManager();
@NonNull Optional<InternalMessagingService> getMessagingService();
@NonNull Storage getStorage();
}

View file

@ -3,17 +3,32 @@ package dev.xhyrom.lighteco.common.plugin.bootstrap;
import dev.xhyrom.lighteco.common.plugin.logger.PluginLogger;
import dev.xhyrom.lighteco.common.plugin.scheduler.SchedulerAdapter;
import net.kyori.adventure.audience.Audience;
import java.io.InputStream;
import java.nio.file.Path;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public interface LightEcoBootstrap {
Object getLoader();
PluginLogger getLogger();
SchedulerAdapter getScheduler();
Path getDataDirectory();
String getVersion();
Optional<UUID> lookupUniqueId(String username);
boolean isPlayerOnline(UUID uniqueId);
List<UUID> getOnlinePlayers();
InputStream getResourceStream(String filename);
Audience getPlayerAudience(UUID uniqueId);
}

View file

@ -2,6 +2,8 @@ package dev.xhyrom.lighteco.common.plugin.bootstrap;
public interface LoaderBootstrap {
void onLoad();
void onEnable();
void onDisable();
}

View file

@ -2,17 +2,26 @@ package dev.xhyrom.lighteco.common.plugin.logger;
public interface PluginLogger {
void info(String message);
void info(String message, Object ...args);
void info(String message, Object... args);
void debug(String message);
void debug(String message, Object... args);
void warn(String message);
void warn(String message, Object ...args);
void warn(String message, Object... args);
void warn(String message, Throwable throwable);
void warn(String message, Throwable throwable, Object ...args);
void warn(String message, Throwable throwable, Object... args);
void error(String message);
void error(String message, Object ...args);
void error(String message, Object... args);
void error(String message, Throwable throwable);
void error(String message, Throwable throwable, Object ...args);
void error(String message, Throwable throwable, Object... args);
}

View file

@ -75,7 +75,8 @@ public class Storage {
}
return future(() -> this.provider.loadUser(uniqueId, username))
.thenApply(apiUser -> this.plugin.getUserManager().getIfLoaded(apiUser.getUniqueId()));
.thenApply(
apiUser -> this.plugin.getUserManager().getIfLoaded(apiUser.getUniqueId()));
}
public CompletableFuture<Void> saveUser(dev.xhyrom.lighteco.api.model.user.User user) {

Some files were not shown because too many files have changed in this diff Show more