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

Compare commits

...

26 commits

Author SHA1 Message Date
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
a2be61eb95
refactor: code cleanup 2023-10-16 17:47:46 +02:00
4d192eefd5
build: bump commandapi 2023-10-12 22:20:59 +02:00
9454f2db90
docs: add SECURITY.md 2023-10-12 18:12:31 +02:00
ca5086621b
fix(bukkit): remove max value from commands 2023-10-07 13:34:03 +02:00
4c832f11f1
fix(bukkit): dont load commandapi twice 2023-10-07 12:56:28 +02:00
58cf77e2b9
fix(currency-money): format double 2023-10-05 22:09:48 +02:00
7ff6cbde47
fix(currency-money): load at STARTUP 2023-10-05 21:39:21 +02:00
1e86142a58
feat: handle maximum balance 2023-10-05 17:31:52 +02:00
f4d4bbe856
feat: currency identifier aliases 2023-09-29 15:05:03 +02:00
af3e64063f
build: make currency-money description different 2023-09-29 14:17:10 +02:00
2b9beb74da
ci: more things in pom, set project descriptions 2023-09-29 14:16:14 +02:00
1f0f663b06
ci: publish sources jar 2023-09-29 14:01:31 +02:00
1d808680d1
ci: add publishing to repo.jopga.me 2023-09-28 17:01:16 +02:00
13c8e75402
d 2023-09-28 16:23:38 +02:00
a0acd6e760
docs(README): fix typo 2023-09-28 16:23:10 +02:00
161 changed files with 3130 additions and 1249 deletions

View file

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

1
.gitignore vendored
View file

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

View file

@ -11,7 +11,7 @@
- **Multi-currency** - LightEco supports creating your own currencies and using them in your server - **Multi-currency** - LightEco supports creating your own currencies and using them in your server
- **Local and global currencies** - LightEco offers support for both local and global currencies, enabling you to create server-specific or proxy-wide currencies. - **Local and global currencies** - LightEco offers support for both local and global currencies, enabling you to create server-specific or proxy-wide currencies.
- **Storage** - LightEco offers support for multiple storage types. - **Storage** - LightEco offers support for multiple storage types.
- **Open-source** - LightEco is open-source, and is available on [GitHub](https://github.com/xHyroM/lightecp) - **Open-source** - LightEco is open-source, and is available on [GitHub](https://github.com/xHyroM/lighteco)
## Development ## Development

36
SECURITY.md Normal file
View file

@ -0,0 +1,36 @@
# Security Policy
## Supported Versions
LightEco is currently in alpha version, which means it's still not done. As such, it may contain bugs or other issues. We are working hard to improve the plugin and fix any problems that are found, but we encourage users to report any issues they encounter to us so that we can address them as quickly as possible.
| Version | Supported |
| ------- | ------------------ |
| 0.x.x | :white_check_mark: |
## Reporting a Vulnerability
If you find a security vulnerability, please report it to us as soon as possible. We take security very seriously and we appreciate your help in keeping our plugin safe.
**How to report a vulnerability**
You can report a vulnerability to us by:
* **Discord:** Contact me on Discord at [`xhyrom`](https://s.xhyrom.dev/discord-user).
* **Email:** Send an email to `github@xhyrom.dev`.
**What to expect when reporting a vulnerability**
We will investigate your report as soon as possible. We will typically provide you with an update within 24 hours.
If the vulnerability is accepted, we will work to fix it as quickly as possible. We will notify you when the fix is released.
If the vulnerability is declined, we will provide you with a reason for the decision.
**What to include in your report**
When reporting a vulnerability, please include the following information:
* **A detailed description of the vulnerability.** This should include how the vulnerability can be exploited and what impact it could have.
* **Steps to reproduce the vulnerability.** This will help us to quickly investigate the issue.
* **Any additional information that you think may be helpful.** This could include screenshots, code samples, or other relevant data.

View file

@ -3,6 +3,12 @@ plugins {
} }
dependencies { 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") compileOnly("org.projectlombok:lombok:1.18.28")
annotationProcessor("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.CommandManager;
import dev.xhyrom.lighteco.api.manager.CurrencyManager; import dev.xhyrom.lighteco.api.manager.CurrencyManager;
import dev.xhyrom.lighteco.api.manager.UserManager; 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.Platform;
import dev.xhyrom.lighteco.api.platform.PlayerAdapter; import dev.xhyrom.lighteco.api.platform.PlayerAdapter;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Optional;
public interface LightEco { public interface LightEco {
/** /**
* Gets the {@link Platform}, which represents the current platform the * Gets the {@link Platform}, which represents the current platform the
@ -37,6 +41,13 @@ public interface LightEco {
*/ */
@NonNull CommandManager getCommandManager(); @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. * Gets the {@link PlayerAdapter} for a player class.
* *

View file

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

View file

@ -0,0 +1,7 @@
package dev.xhyrom.lighteco.api.exception;
public class CannotBeGreaterThan extends IllegalArgumentException {
public CannotBeGreaterThan(String message) {
super(message);
}
}

View file

@ -0,0 +1,7 @@
package dev.xhyrom.lighteco.api.exception;
public class CannotBeNegative extends IllegalArgumentException {
public CannotBeNegative(String message) {
super(message);
}
}

View file

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

View file

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

View file

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

@ -13,6 +13,20 @@ public interface Currency {
*/ */
String getIdentifier(); String getIdentifier();
/**
* Returns the identifier aliases of the currency.
* <p>
* Useful if you want multiple commands for the same currency.
* For example, you can have a command `/hyrocoins` but also `/hc`.
* </p>
*
* @return the aliases
*/
default String[] getIdentifierAliases() {
return new String[0];
}
;
/** /**
* Returns the type of the currency, either {@link Type#LOCAL} or {@link Type#GLOBAL} * Returns the type of the currency, either {@link Type#LOCAL} or {@link Type#GLOBAL}
* *
@ -66,7 +80,8 @@ public interface Currency {
*/ */
default BigDecimal calculateTax(User user, BigDecimal amount) { default BigDecimal calculateTax(User user, BigDecimal amount) {
return BigDecimal.ZERO; return BigDecimal.ZERO;
}; }
;
/** /**
* Represents the type of currency * Represents the type of currency

View file

@ -1,6 +1,9 @@
package dev.xhyrom.lighteco.api.model.user; package dev.xhyrom.lighteco.api.model.user;
import dev.xhyrom.lighteco.api.model.currency.Currency; 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.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
@ -49,7 +52,8 @@ public interface User {
* @param amount the amount * @param amount the amount
* @throws IllegalArgumentException if the amount is negative * @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. * 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 * @param amount the amount
* @throws IllegalArgumentException if the amount is negative * @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(); @NonNull Type getType();
enum Type { enum Type {
BUKKIT("Bukkit"), PAPER("Paper"),
SPONGE("Sponge"), SPONGE("Sponge"),
VELOCITY("Velocity"), VELOCITY("Velocity"),
BUNGEECORD("BungeeCord"); BUNGEECORD("BungeeCord");
@ -27,7 +27,7 @@ public interface Platform {
} }
public boolean isLocal() { public boolean isLocal() {
return this == BUKKIT || this == SPONGE; return this == PAPER || this == SPONGE;
} }
public boolean isProxy() { public boolean isProxy() {

View file

@ -1,6 +1,7 @@
package dev.xhyrom.lighteco.api.platform; package dev.xhyrom.lighteco.api.platform;
import dev.xhyrom.lighteco.api.model.user.User; import dev.xhyrom.lighteco.api.model.user.User;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.concurrent.CompletableFuture; 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.currency.Currency;
import dev.xhyrom.lighteco.api.model.user.User; import dev.xhyrom.lighteco.api.model.user.User;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
@ -23,7 +24,10 @@ public interface StorageProvider {
void shutdown() throws Exception; void shutdown() throws Exception;
default void registerCurrency(@NonNull Currency currency) throws Exception {} default void registerCurrency(@NonNull Currency currency) throws Exception {}
@NonNull User loadUser(@NonNull UUID uniqueId, @Nullable String username) throws Exception; @NonNull User loadUser(@NonNull UUID uniqueId, @Nullable String username) throws Exception;
void saveUser(@NonNull User user) throws Exception; void saveUser(@NonNull User user) throws Exception;
void saveUsers(@NonNull User... users) throws Exception; void saveUsers(@NonNull User... users) throws Exception;
} }

View file

@ -3,6 +3,7 @@ import java.io.ByteArrayOutputStream
plugins { plugins {
id("java") id("java")
id("org.sonarqube") version "4.2.1.3168" id("org.sonarqube") version "4.2.1.3168"
id("com.diffplug.spotless") version "6.25.0"
} }
val majorVersion = 0 val majorVersion = 0
@ -10,12 +11,20 @@ val minorVersion = 1
val patchVersion = determinePatchVersion(project) val patchVersion = determinePatchVersion(project)
val commitHash = determineCommitHash(project) val commitHash = determineCommitHash(project)
defaultTasks("spotlessApply")
repositories {
mavenCentral()
}
allprojects { allprojects {
group = "dev.xhyrom" group = "dev.xhyrom"
version = "$majorVersion.$minorVersion.$patchVersion" version = "$majorVersion.$minorVersion.$patchVersion"
description = "Incredibly fast, lightweight, and modular plugin that excels across multiple platforms."
ext { ext {
set("version", "$majorVersion.$minorVersion.$patchVersion+$commitHash") set("version", "$majorVersion.$minorVersion.$patchVersion+$commitHash")
set("description", description)
} }
} }
@ -26,6 +35,19 @@ subprojects {
repositories { repositories {
mavenCentral() mavenCentral()
maven("https://storehouse.okaeri.eu/repository/maven-public/") 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/**")
} }
} }
@ -54,5 +76,5 @@ fun determineCommitHash(project: Project): String {
standardOutput = commitHashInfo standardOutput = commitHashInfo
} }
return commitHashInfo.toString() return commitHashInfo.toString().strip()
} }

View file

@ -4,7 +4,12 @@ plugins {
} }
java { java {
// toolchain.languageVersion.set(JavaLanguageVersion.of(17)) targetCompatibility = JavaVersion.VERSION_17
sourceCompatibility = JavaVersion.VERSION_17
toolchain {
languageVersion.set(JavaLanguageVersion.of(17))
}
withSourcesJar() withSourcesJar()
} }
@ -13,6 +18,7 @@ tasks {
filesMatching(listOf("plugin.yml")) { filesMatching(listOf("plugin.yml")) {
expand( expand(
"name" to project.name, "name" to project.name,
"coreName" to "LightEco",
"version" to project.version, "version" to project.version,
"description" to project.description, "description" to project.description,
"author" to "xHyroM" "author" to "xHyroM"
@ -24,3 +30,62 @@ tasks {
options.encoding = Charsets.UTF_8.name() options.encoding = Charsets.UTF_8.name()
} }
} }
publishing {
// Publishing to repo.jopga.me
publications.create<MavenPublication>("mavenJava") {
repositories.maven {
url = uri("https://repo.jopga.me/releases")
credentials(PasswordCredentials::class)
authentication {
create<BasicAuthentication>("basic")
}
}
groupId = rootProject.group as String
artifactId = project.name
version = rootProject.version as String
pom {
name.set("LightEco")
url.set("https://github.com/xHyroM/lighteco")
description.set(project.description)
organization {
name.set("xHyroM")
url.set("https://xhyrom.dev")
}
developers {
developer {
id.set("xHyroM")
name.set("xHyroM")
email.set("lol@xhyrom.dev")
timezone.set("Europe/Bratislava")
url.set("https://xhyrom.dev")
}
}
scm {
connection.set("scm:git:https://github.com/xHyroM/lighteco.git")
developerConnection.set("scm:git:git@github.com:xHyroM/lighteco.git")
url.set("https://github.com/xHyroM/lighteco")
}
licenses {
license {
name.set("Apache License 2.0")
url.set("https://github.com/xHyroM/lighteco/blob/main/LICENSE")
distribution.set("repo")
}
}
ciManagement {
system.set("GitHub Actions")
url.set("https://github.com/xHyroM/lighteco/actions")
}
}
artifact(tasks.named("jar")) {
classifier = ""
}
artifact(tasks.named("sourcesJar"))
}
}

View file

@ -1,35 +0,0 @@
package dev.xhyrom.lighteco.bukkit;
import dev.jorel.commandapi.CommandAPI;
import dev.jorel.commandapi.CommandAPIBukkitConfig;
import org.bukkit.plugin.java.JavaPlugin;
// Used inside plugin.yml
@SuppressWarnings("unused")
public class BukkitLightEcoLoader extends JavaPlugin {
private final BukkitLightEcoBootstrap bootstrap;
public BukkitLightEcoLoader() {
this.bootstrap = new BukkitLightEcoBootstrap(this);
}
@Override
public void onLoad() {
this.bootstrap.onLoad();
CommandAPI.onLoad(new CommandAPIBukkitConfig(this)
.verboseOutput(this.bootstrap.getPlugin().getConfig().debug)
);
}
@Override
public void onEnable() {
CommandAPI.onEnable();
this.bootstrap.onEnable();
}
@Override
public void onDisable() {
CommandAPI.onDisable();
this.bootstrap.onDisable();
}
}

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,74 +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) {
String permissionBase = "lighteco.currency." + currency.getIdentifier() + ".command.";
// Balance
for (CommandAPICommand cmd : new BalanceCommand(
this,
currency.getIdentifier(),
currency,
permissionBase
).multipleBuild()) {
cmd.register();
}
CommandAPICommand cmd = new CommandAPICommand(currency.getIdentifier())
.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();
}
@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();
}
}
}

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

@ -4,6 +4,7 @@ plugins {
dependencies { dependencies {
api(project(":lighteco-api")) api(project(":lighteco-api"))
api("org.checkerframework:checker-qual:3.8.0")
api("net.kyori:adventure-api:4.12.0") { api("net.kyori:adventure-api:4.12.0") {
exclude(module = "adventure-bom") exclude(module = "adventure-bom")
exclude(module = "checker-qual") exclude(module = "checker-qual")
@ -18,11 +19,13 @@ dependencies {
implementation("eu.okaeri:okaeri-configs-yaml-snakeyaml:5.0.0-beta.5") implementation("eu.okaeri:okaeri-configs-yaml-snakeyaml:5.0.0-beta.5")
implementation("eu.okaeri:okaeri-configs-validator-okaeri: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("com.zaxxer:HikariCP:5.0.1")
compileOnly("redis.clients:jedis:5.1.0")
compileOnly("org.projectlombok:lombok:1.18.28") compileOnly("org.projectlombok:lombok:1.18.28")
annotationProcessor("org.projectlombok:lombok:1.18.28") annotationProcessor("org.projectlombok:lombok:1.18.28")
compileOnly("org.checkerframework:checker-qual:3.8.0")
compileOnly("org.jetbrains:annotations:20.1.0") compileOnly("org.jetbrains:annotations:20.1.0")
} }

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

View file

@ -4,10 +4,10 @@ import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
public abstract class ApiAbstractManager<H> { public abstract class ApiAbstractManager<H> {
protected final LightEcoPlugin plugin; 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.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.manager.CommandManager;
import dev.xhyrom.lighteco.api.model.currency.Currency; import dev.xhyrom.lighteco.api.model.currency.Currency;
import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin; import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
public class ApiCommandManager extends ApiAbstractManager<dev.xhyrom.lighteco.common.manager.command.CommandManager> implements CommandManager { public class ApiCommandManager
public ApiCommandManager(LightEcoPlugin plugin, dev.xhyrom.lighteco.common.manager.command.CommandManager handler) { extends ApiAbstractManager<dev.xhyrom.lighteco.common.command.CommandManager>
super(plugin, handler); implements CommandManager {
public ApiCommandManager(
LightEcoPlugin plugin, dev.xhyrom.lighteco.common.command.CommandManager handle) {
super(plugin, handle);
} }
@Override @Override
public void registerCurrencyCommand(@NonNull Currency currency) { public void registerCurrencyCommand(@NonNull Currency currency) {
dev.xhyrom.lighteco.common.model.currency.Currency internal = this.plugin.getCurrencyManager() dev.xhyrom.lighteco.common.model.currency.Currency internal =
.getIfLoaded(currency.getIdentifier()); this.plugin.getCurrencyManager().getIfLoaded(currency.getIdentifier());
this.handler.registerCurrencyCommand(internal); this.handle.register(internal, false);
} }
@Override @Override
public void registerCurrencyCommand(@NonNull Currency currency, boolean main) { public void registerCurrencyCommand(@NonNull Currency currency, boolean main) {
dev.xhyrom.lighteco.common.model.currency.Currency internal = this.plugin.getCurrencyManager() dev.xhyrom.lighteco.common.model.currency.Currency internal =
.getIfLoaded(currency.getIdentifier()); 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.manager.CurrencyManager;
import dev.xhyrom.lighteco.api.model.currency.Currency; import dev.xhyrom.lighteco.api.model.currency.Currency;
import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin; import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Collection; import java.util.Collection;
public class ApiCurrencyManager extends ApiAbstractManager<dev.xhyrom.lighteco.common.manager.currency.CurrencyManager> implements CurrencyManager { public class ApiCurrencyManager
public ApiCurrencyManager(LightEcoPlugin plugin, dev.xhyrom.lighteco.common.manager.currency.CurrencyManager handler) { extends ApiAbstractManager<dev.xhyrom.lighteco.common.manager.currency.CurrencyManager>
super(plugin, handler); 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) { private Currency wrap(dev.xhyrom.lighteco.common.model.currency.Currency handle) {
return handler.getProxy(); return handle.getProxy();
} }
@Override @Override
public @NonNull Collection<Currency> getRegisteredCurrencies() { public @NonNull Collection<Currency> getRegisteredCurrencies() {
return this.handler.values() return this.handle.values().stream().map(this::wrap).toList();
.stream().map(this::wrap)
.toList();
} }
@Override @Override
public Currency getCurrency(@NonNull String identifier) { public Currency getCurrency(@NonNull String identifier) {
return wrap(this.handler.getIfLoaded(identifier)); return wrap(this.handle.getIfLoaded(identifier));
} }
@Override @Override
public void registerCurrency(@NonNull Currency currency) { public void registerCurrency(@NonNull Currency currency) {
dev.xhyrom.lighteco.common.model.currency.Currency internal = new dev.xhyrom.lighteco.common.model.currency.Currency(currency); dev.xhyrom.lighteco.common.model.currency.Currency internal =
this.handler.registerCurrency(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.api.platform.Platform;
import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin; import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
public class ApiPlatform implements Platform { public class ApiPlatform implements Platform {

View file

@ -1,9 +1,10 @@
package dev.xhyrom.lighteco.common.api.impl; package dev.xhyrom.lighteco.common.api.impl;
import dev.xhyrom.lighteco.api.manager.ContextManager; 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.manager.UserManager;
import dev.xhyrom.lighteco.api.model.user.User;
import dev.xhyrom.lighteco.api.platform.PlayerAdapter; import dev.xhyrom.lighteco.api.platform.PlayerAdapter;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.UUID; 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.currency.Currency;
import dev.xhyrom.lighteco.api.model.user.User; 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.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
@ -9,55 +12,65 @@ import java.math.BigDecimal;
import java.util.UUID; import java.util.UUID;
public class ApiUser implements User { 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) { throw new IllegalArgumentException(
this.handler = handler; "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 @Override
public @NonNull UUID getUniqueId() { public @NonNull UUID getUniqueId() {
return this.handler.getUniqueId(); return this.handle.getUniqueId();
} }
@Override @Override
public @Nullable String getUsername() { public @Nullable String getUsername() {
return this.handler.getUsername(); return this.handle.getUsername();
} }
@Override @Override
public @NonNull BigDecimal getBalance(@NonNull Currency currency) { public @NonNull BigDecimal getBalance(@NonNull Currency currency) {
dev.xhyrom.lighteco.common.model.currency.Currency internal = this.handler.getPlugin() dev.xhyrom.lighteco.common.model.currency.Currency internal =
.getCurrencyManager() this.handle.getPlugin().getCurrencyManager().getIfLoaded(currency.getIdentifier());
.getIfLoaded(currency.getIdentifier());
return this.handler.getBalance(internal); return this.handle.getBalance(internal);
} }
@Override @Override
public void setBalance(@NonNull Currency currency, @NonNull BigDecimal balance) { public void setBalance(@NonNull Currency currency, @NonNull BigDecimal balance) {
dev.xhyrom.lighteco.common.model.currency.Currency internal = this.handler.getPlugin() dev.xhyrom.lighteco.common.model.currency.Currency internal =
.getCurrencyManager() this.handle.getPlugin().getCurrencyManager().getIfLoaded(currency.getIdentifier());
.getIfLoaded(currency.getIdentifier());
this.handler.setBalance(internal, balance); this.handle.setBalance(internal, balance);
} }
@Override @Override
public void deposit(@NonNull Currency currency, @NonNull BigDecimal amount) { public void deposit(@NonNull Currency currency, @NonNull BigDecimal amount) {
dev.xhyrom.lighteco.common.model.currency.Currency internal = this.handler.getPlugin() dev.xhyrom.lighteco.common.model.currency.Currency internal =
.getCurrencyManager() this.handle.getPlugin().getCurrencyManager().getIfLoaded(currency.getIdentifier());
.getIfLoaded(currency.getIdentifier());
this.handler.deposit(internal, amount); this.handle.deposit(internal, amount);
} }
@Override @Override
public void withdraw(@NonNull Currency currency, @NonNull BigDecimal amount) { public void withdraw(@NonNull Currency currency, @NonNull BigDecimal amount) {
dev.xhyrom.lighteco.common.model.currency.Currency internal = this.handler.getPlugin() dev.xhyrom.lighteco.common.model.currency.Currency internal =
.getCurrencyManager() this.handle.getPlugin().getCurrencyManager().getIfLoaded(currency.getIdentifier());
.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,21 +3,24 @@ package dev.xhyrom.lighteco.common.api.impl;
import dev.xhyrom.lighteco.api.manager.UserManager; import dev.xhyrom.lighteco.api.manager.UserManager;
import dev.xhyrom.lighteco.api.model.user.User; import dev.xhyrom.lighteco.api.model.user.User;
import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin; import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.jetbrains.annotations.NotNull;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
public class ApiUserManager extends ApiAbstractManager<dev.xhyrom.lighteco.common.manager.user.UserManager> implements UserManager { public class ApiUserManager
public ApiUserManager(LightEcoPlugin plugin, dev.xhyrom.lighteco.common.manager.user.UserManager handler) { extends ApiAbstractManager<dev.xhyrom.lighteco.common.manager.user.UserManager>
super(plugin, handler); 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) { private User wrap(dev.xhyrom.lighteco.common.model.user.User handle) {
this.plugin.getUserManager().getHousekeeper().registerUsage(handler.getUniqueId()); this.plugin.getUserManager().getHousekeeper().registerUsage(handle.getUniqueId());
return handler.getProxy(); return handle.getProxy();
} }
@Override @Override
@ -27,8 +30,7 @@ public class ApiUserManager extends ApiAbstractManager<dev.xhyrom.lighteco.commo
@Override @Override
public @NonNull CompletableFuture<User> loadUser(@NonNull UUID uniqueId, String username) { public @NonNull CompletableFuture<User> loadUser(@NonNull UUID uniqueId, String username) {
return this.plugin.getStorage().loadUser(uniqueId, username) return this.plugin.getStorage().loadUser(uniqueId, username).thenApply(this::wrap);
.thenApply(this::wrap);
} }
@Override @Override
@ -37,17 +39,17 @@ public class ApiUserManager extends ApiAbstractManager<dev.xhyrom.lighteco.commo
} }
@Override @Override
public @NonNull CompletableFuture<Void> saveUsers(@NotNull @NonNull User... users) { public @NonNull CompletableFuture<Void> saveUsers(@NonNull User... users) {
return this.plugin.getStorage().saveUsers(users); return this.plugin.getStorage().saveUsers(users);
} }
@Override @Override
public @Nullable User getUser(@NonNull UUID uniqueId) { public @Nullable User getUser(@NonNull UUID uniqueId) {
return wrap(this.handler.getIfLoaded(uniqueId)); return wrap(this.handle.getIfLoaded(uniqueId));
} }
@Override @Override
public boolean isLoaded(@NonNull UUID uniqueId) { 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; private final long lifetime;
public ExpiringSet(long duration, TimeUnit unit) { public ExpiringSet(long duration, TimeUnit unit) {
this.cache = CacheBuilder.newBuilder() this.cache = CacheBuilder.newBuilder().expireAfterWrite(duration, unit).build();
.expireAfterWrite(duration, unit)
.build();
this.lifetime = unit.toMillis(duration); this.lifetime = unit.toMillis(duration);
} }

View file

@ -2,6 +2,4 @@ package dev.xhyrom.lighteco.common.cache;
import java.util.HashMap; 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,25 +2,38 @@ package dev.xhyrom.lighteco.common.config;
import dev.xhyrom.lighteco.common.config.housekeeper.HousekeeperConfig; import dev.xhyrom.lighteco.common.config.housekeeper.HousekeeperConfig;
import dev.xhyrom.lighteco.common.config.message.MessageConfig; 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 dev.xhyrom.lighteco.common.config.storage.StorageConfig;
import eu.okaeri.configs.OkaeriConfig; import eu.okaeri.configs.OkaeriConfig;
import eu.okaeri.configs.annotation.Comment; import eu.okaeri.configs.annotation.Comment;
import eu.okaeri.configs.annotation.Header; import eu.okaeri.configs.annotation.Header;
import java.math.BigDecimal;
@Header("LightEco configuration file.") @Header("LightEco configuration file.")
@Header("") @Header("")
public class Config extends OkaeriConfig { public class Config extends OkaeriConfig {
@Comment("This property must be unique for each server.") @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.") @Comment("Used for local currencies.")
public String server = "none"; public String server = "none";
@Comment("Storage settings.") @Comment("Storage settings.")
public StorageConfig storage = new StorageConfig(); public StorageConfig storage = new StorageConfig();
@Comment("Messaging settings.")
public MessagingConfig messaging = new MessagingConfig();
@Comment("Save interval to storage in seconds.") @Comment("Save interval to storage in seconds.")
public long saveInterval = 5L; 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.")
public BigDecimal maximumBalance = BigDecimal.valueOf(999999999999999.99);
@Comment("Messages") @Comment("Messages")
public MessageConfig messages = new MessageConfig(); public MessageConfig messages = new MessageConfig();

View file

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

View file

@ -4,18 +4,30 @@ import eu.okaeri.configs.OkaeriConfig;
import eu.okaeri.configs.annotation.Variable; import eu.okaeri.configs.annotation.Variable;
public class CurrencyMessageConfig extends OkaeriConfig { 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") @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 set =
public String give = "<currency> <dark_gray>| <gray>Gave <gold><target> <gold><amount> <dark_gray>| <gold><balance>"; "<currency> <dark_gray>| <gray>Set balance of <gold><target> <yellow>to <gold><amount>";
public String take = "<currency> <dark_gray>| <gray>Took <gold><amount> <yellow>from <gold><target>"; 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 pay =
public String payWithTax = "<currency> <dark_gray>| <gray>Paid <gold><amount> <yellow>to <gold><target> <dark_gray>(<gold><taxed_amount> <yellow>after tax<dark_gray>)"; "<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 notEnoughMoney = "<red>You don't have enough money!";
public String cannotPaySelf = "<red>You cannot pay yourself!"; 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; import java.util.Map;
public class MessageConfig extends OkaeriConfig { 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; package dev.xhyrom.lighteco.common.config.storage;
import dev.xhyrom.lighteco.common.storage.StorageType; import dev.xhyrom.lighteco.common.storage.StorageType;
import eu.okaeri.configs.OkaeriConfig; import eu.okaeri.configs.OkaeriConfig;
import eu.okaeri.configs.annotation.Comment; import eu.okaeri.configs.annotation.Comment;

View file

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

View file

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

View file

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

View file

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

View file

@ -2,13 +2,16 @@ package dev.xhyrom.lighteco.common.dependencies;
import com.google.common.collect.ImmutableSetMultimap; import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.SetMultimap; import com.google.common.collect.SetMultimap;
import dev.xhyrom.lighteco.common.messaging.MessagingType;
import dev.xhyrom.lighteco.common.storage.StorageType; import dev.xhyrom.lighteco.common.storage.StorageType;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.Set; import java.util.Set;
public class DependencyRegistry { public class DependencyRegistry {
private static final SetMultimap<StorageType, Dependency> STORAGE_DEPENDENCIES = ImmutableSetMultimap.<StorageType, Dependency>builder() private static final SetMultimap<StorageType, Dependency> STORAGE_DEPENDENCIES =
ImmutableSetMultimap.<StorageType, Dependency>builder()
.putAll(StorageType.SQLITE, Dependency.SQLITE_DRIVER) .putAll(StorageType.SQLITE, Dependency.SQLITE_DRIVER)
.putAll(StorageType.H2, Dependency.H2_DRIVER) .putAll(StorageType.H2, Dependency.H2_DRIVER)
.putAll(StorageType.MYSQL, Dependency.MYSQL_DRIVER, Dependency.HIKARI) .putAll(StorageType.MYSQL, Dependency.MYSQL_DRIVER, Dependency.HIKARI)
@ -16,6 +19,16 @@ public class DependencyRegistry {
.putAll(StorageType.POSTGRESQL, Dependency.POSTGRESQL_DRIVER, Dependency.HIKARI) .putAll(StorageType.POSTGRESQL, Dependency.POSTGRESQL_DRIVER, Dependency.HIKARI)
.build(); .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) { public Set<Dependency> resolveStorageDependencies(Set<StorageType> types) {
Set<Dependency> dependencies = new LinkedHashSet<>(); Set<Dependency> dependencies = new LinkedHashSet<>();
@ -26,6 +39,16 @@ public class DependencyRegistry {
return dependencies; 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) { public boolean shouldAutoLoad(Dependency dependency) {
return switch (dependency) { return switch (dependency) {
case H2_DRIVER, SQLITE_DRIVER -> false; case H2_DRIVER, SQLITE_DRIVER -> false;

View file

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

View file

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

View file

@ -12,7 +12,8 @@ import java.nio.file.Path;
import java.util.*; import java.util.*;
public class RelocationHandler { 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_CLASS = "me.lucko.jarrelocator.JarRelocator";
private static final String JAR_RELOCATOR_RUN_METHOD = "run"; private static final String JAR_RELOCATOR_RUN_METHOD = "run";
@ -31,10 +32,12 @@ public class RelocationHandler {
Class<?> jarRelocatorClass = classLoader.loadClass(JAR_RELOCATOR_CLASS); Class<?> jarRelocatorClass = classLoader.loadClass(JAR_RELOCATOR_CLASS);
// prepare the reflected constructor & method instances // 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.jarRelocatorConstructor.setAccessible(true);
this.jarRelocatorRunMethod = jarRelocatorClass.getDeclaredMethod(JAR_RELOCATOR_RUN_METHOD); this.jarRelocatorRunMethod =
jarRelocatorClass.getDeclaredMethod(JAR_RELOCATOR_RUN_METHOD);
this.jarRelocatorRunMethod.setAccessible(true); this.jarRelocatorRunMethod.setAccessible(true);
} catch (Exception e) { } catch (Exception e) {
try { try {
@ -56,8 +59,8 @@ public class RelocationHandler {
} }
// create and invoke a new relocator // 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); this.jarRelocatorRunMethod.invoke(relocator);
} }
} }

View file

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

View file

@ -1,211 +0,0 @@
package dev.xhyrom.lighteco.common.manager.command;
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);
target.setBalance(currency, amount);
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);
target.deposit(currency, amount);
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);
target.withdraw(currency, amount);
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);
target.deposit(currency, taxedAmount);
user.withdraw(currency, amount);
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.manager.Manager;
import dev.xhyrom.lighteco.common.model.currency.Currency; import dev.xhyrom.lighteco.common.model.currency.Currency;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Collection; 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.manager.SingleManager;
import dev.xhyrom.lighteco.common.model.currency.Currency; import dev.xhyrom.lighteco.common.model.currency.Currency;
import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin; import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Collection; 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; private final LightEcoPlugin plugin;
public StandardCurrencyManager(LightEcoPlugin plugin) { public StandardCurrencyManager(LightEcoPlugin plugin) {
@ -32,10 +34,14 @@ public class StandardCurrencyManager extends SingleManager<String, Currency> imp
@Override @Override
public void registerCurrency(@NonNull Currency currency) { public void registerCurrency(@NonNull Currency currency) {
if (this.isLoaded(currency.getIdentifier())) 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) 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.plugin.getStorage().registerCurrencySync(currency.getProxy());
this.map.put(currency.getIdentifier(), currency); 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.manager.ConcurrentManager;
import dev.xhyrom.lighteco.common.model.user.User; import dev.xhyrom.lighteco.common.model.user.User;
import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin; import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
import lombok.Getter; import lombok.Getter;
import java.util.Arrays; import java.util.Arrays;
@ -11,21 +12,26 @@ import java.util.concurrent.CompletableFuture;
public class StandardUserManager extends ConcurrentManager<UUID, User> implements UserManager { public class StandardUserManager extends ConcurrentManager<UUID, User> implements UserManager {
private final LightEcoPlugin plugin; private final LightEcoPlugin plugin;
@Getter @Getter
private final UserHousekeeper housekeeper; private final UserHousekeeper housekeeper;
public StandardUserManager(LightEcoPlugin plugin) { public StandardUserManager(LightEcoPlugin plugin) {
this.plugin = plugin; this.plugin = plugin;
this.housekeeper = new UserHousekeeper(plugin, this, UserHousekeeper.timeoutSettings( this.housekeeper = new UserHousekeeper(
plugin,
this,
UserHousekeeper.timeoutSettings(
this.plugin.getConfig().housekeeper.expireAfterWrite, this.plugin.getConfig().housekeeper.expireAfterWrite,
this.plugin.getConfig().housekeeper.expireAfterWriteUnit this.plugin.getConfig().housekeeper.expireAfterWriteUnit));
));
this.plugin.getBootstrap().getScheduler().asyncRepeating( this.plugin
.getBootstrap()
.getScheduler()
.asyncRepeating(
this.housekeeper, this.housekeeper,
this.plugin.getConfig().housekeeper.runInterval, this.plugin.getConfig().housekeeper.runInterval,
this.plugin.getConfig().housekeeper.runIntervalUnit this.plugin.getConfig().housekeeper.runIntervalUnit);
);
} }
@Override @Override
@ -54,10 +60,10 @@ public class StandardUserManager extends ConcurrentManager<UUID, User> implement
@Override @Override
public CompletableFuture<Void> saveUsers(User... users) { public CompletableFuture<Void> saveUsers(User... users) {
return this.plugin.getStorage().saveUsers( return this.plugin
Arrays.stream(users) .getStorage()
.saveUsers(Arrays.stream(users)
.map(User::getProxy) .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; 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.plugin = plugin;
this.userManager = userManager; this.userManager = userManager;
this.recentlyUsed = new ExpiringSet<>(timeoutSettings.duration, timeoutSettings.unit); 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 the user is dirty (has unsaved changes), don't unload
if (user.isDirty()) if (user.isDirty()) return;
return;
if (this.plugin.getConfig().debug) { if (this.plugin.getConfig().debug) {
this.plugin.getBootstrap().getLogger().info("Unloading data for " + uuid); this.plugin.getBootstrap().getLogger().info("Unloading data for " + uuid);

View file

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

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

View file

@ -16,6 +16,10 @@ public class Currency {
return proxy.getIdentifier(); return proxy.getIdentifier();
} }
public String[] getIdentifierAliases() {
return proxy.getIdentifierAliases();
}
public dev.xhyrom.lighteco.api.model.currency.Currency.Type getType() { public dev.xhyrom.lighteco.api.model.currency.Currency.Type getType() {
return proxy.getType(); return proxy.getType();
} }

View file

@ -1,16 +1,24 @@
package dev.xhyrom.lighteco.common.model.user; package dev.xhyrom.lighteco.common.model.user;
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.api.impl.ApiUser;
import dev.xhyrom.lighteco.common.cache.RedisBackedMap; 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.model.currency.Currency;
import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin; import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.util.HashMap; import java.util.HashMap;
import java.util.Optional;
import java.util.UUID; import java.util.UUID;
@Getter @Getter
@ -26,6 +34,7 @@ public class User {
@Getter @Getter
@Setter @Setter
private boolean dirty = false; private boolean dirty = false;
@Getter @Getter
@Setter @Setter
private String username; private String username;
@ -46,35 +55,65 @@ public class User {
return balances.getOrDefault(currency, currency.getDefaultBalance()); return balances.getOrDefault(currency, currency.getDefaultBalance());
} }
public void setBalance(@NonNull Currency currency, @NonNull BigDecimal balance) { public void setBalance(@NonNull Currency currency, @NonNull BigDecimal balance)
this.setBalance(currency, balance, false); throws CannotBeNegative, CannotBeGreaterThan {
this.setBalance(currency, balance, false, true);
} }
public void setBalance(@NonNull Currency currency, @NonNull BigDecimal balance, boolean force) { 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) { if (balance.compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalArgumentException("Balance cannot be negative"); 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);
} }
balance = balance.setScale(currency.fractionalDigits(), RoundingMode.DOWN); balance = balance.setScale(currency.fractionalDigits(), RoundingMode.DOWN);
balances.put(currency, balance); balances.put(currency, balance);
if (!force) if (!force) this.setDirty(true);
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 IllegalArgumentException { public void deposit(@NonNull Currency currency, @NonNull BigDecimal amount)
throws CannotBeNegative, CannotBeGreaterThan {
if (amount.compareTo(BigDecimal.ZERO) < 0) { if (amount.compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalArgumentException("Amount cannot be negative"); 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);
}
this.setBalance(currency, this.getBalance(currency).add(amount)); this.setBalance(currency, this.getBalance(currency).add(amount));
} }
public void withdraw(@NonNull Currency currency, @NonNull BigDecimal amount) throws IllegalArgumentException { public void withdraw(@NonNull Currency currency, @NonNull BigDecimal amount)
throws CannotBeNegative, CannotBeGreaterThan {
if (amount.compareTo(BigDecimal.ZERO) < 0) { if (amount.compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalArgumentException("Amount cannot be negative"); 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);
}
if (this.getBalance(currency).compareTo(amount) < 0) { if (this.getBalance(currency).compareTo(amount) < 0) {
// Withdraw all // Withdraw all
amount = this.getBalance(currency); amount = this.getBalance(currency);
@ -82,4 +121,8 @@ public class User {
this.setBalance(currency, this.getBalance(currency).subtract(amount)); this.setBalance(currency, this.getBalance(currency).subtract(amount));
} }
public void sendMessage(@NonNull Component message) {
this.plugin.getBootstrap().getPlayerAudience(this.getUniqueId()).sendMessage(message);
}
} }

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.config.Config;
import dev.xhyrom.lighteco.common.dependencies.DependencyManager; import dev.xhyrom.lighteco.common.dependencies.DependencyManager;
import dev.xhyrom.lighteco.common.dependencies.DependencyManagerImpl; 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.Storage;
import dev.xhyrom.lighteco.common.storage.StorageFactory; import dev.xhyrom.lighteco.common.storage.StorageFactory;
import dev.xhyrom.lighteco.common.task.UserSaveTask; import dev.xhyrom.lighteco.common.task.UserSaveTask;
import eu.okaeri.configs.ConfigManager; import eu.okaeri.configs.ConfigManager;
import eu.okaeri.configs.yaml.snakeyaml.YamlSnakeYamlConfigurer; import eu.okaeri.configs.yaml.snakeyaml.YamlSnakeYamlConfigurer;
import lombok.Getter; import lombok.Getter;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Optional;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@Getter @Getter
public abstract class AbstractLightEcoPlugin implements LightEcoPlugin { public abstract class AbstractLightEcoPlugin implements LightEcoPlugin {
private DependencyManager dependencyManager; private DependencyManager dependencyManager;
@Getter @Getter
private Config config; private Config config;
@Getter @Getter
private Storage storage; private Storage storage;
private InternalMessagingService messagingService;
private LightEcoApi api; private LightEcoApi api;
private UserSaveTask userSaveTask; private UserSaveTask userSaveTask;
@ -41,10 +51,14 @@ public abstract class AbstractLightEcoPlugin implements LightEcoPlugin {
public final void enable() { public final void enable() {
// setup storage // setup storage
StorageFactory factory = new StorageFactory(this); StorageFactory storageFactory = new StorageFactory(this);
this.dependencyManager.loadStorageDependencies(factory.getRequiredTypes()); 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 // register listeners
this.registerListeners(); this.registerListeners();
@ -52,6 +66,9 @@ public abstract class AbstractLightEcoPlugin implements LightEcoPlugin {
// setup managers // setup managers
this.setupManagers(); this.setupManagers();
// register built-in commands
this.getCommandManager().register();
// register platform hooks // register platform hooks
this.registerPlatformHooks(); this.registerPlatformHooks();
@ -61,7 +78,9 @@ public abstract class AbstractLightEcoPlugin implements LightEcoPlugin {
this.registerApiOnPlatform(this.api); this.registerApiOnPlatform(this.api);
this.userSaveTask = new UserSaveTask(this); 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() { public final void disable() {
@ -74,6 +93,8 @@ public abstract class AbstractLightEcoPlugin implements LightEcoPlugin {
// shutdown storage // shutdown storage
this.storage.shutdown(); this.storage.shutdown();
if (this.messagingService != null) this.messagingService.shutdown();
// close isolated class loaders // close isolated class loaders
this.dependencyManager.close(); this.dependencyManager.close();
} }
@ -81,8 +102,17 @@ public abstract class AbstractLightEcoPlugin implements LightEcoPlugin {
protected abstract void registerListeners(); protected abstract void registerListeners();
protected abstract void setupManagers(); protected abstract void setupManagers();
protected abstract MessagingFactory getMessagingFactory();
protected abstract void registerApiOnPlatform(LightEco api); protected abstract void registerApiOnPlatform(LightEco api);
protected abstract void registerPlatformHooks(); protected abstract void registerPlatformHooks();
protected abstract void removePlatformHooks(); 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.manager.ContextManager;
import dev.xhyrom.lighteco.api.platform.Platform; 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.config.Config;
import dev.xhyrom.lighteco.common.dependencies.DependencyManager; 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.currency.CurrencyManager;
import dev.xhyrom.lighteco.common.manager.user.UserManager; 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.plugin.bootstrap.LightEcoBootstrap;
import dev.xhyrom.lighteco.common.storage.Storage; import dev.xhyrom.lighteco.common.storage.Storage;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Optional;
public interface LightEcoPlugin { public interface LightEcoPlugin {
Platform.@NonNull Type getPlatformType(); Platform.@NonNull Type getPlatformType();
@NonNull LightEcoBootstrap getBootstrap(); @NonNull LightEcoBootstrap getBootstrap();
@NonNull Config getConfig(); @NonNull Config getConfig();
@NonNull UserManager getUserManager(); @NonNull UserManager getUserManager();
@NonNull CurrencyManager getCurrencyManager(); @NonNull CurrencyManager getCurrencyManager();
@NonNull CommandManager getCommandManager(); @NonNull CommandManager getCommandManager();
@NonNull ContextManager<?> getContextManager(); @NonNull ContextManager<?> getContextManager();
@NonNull DependencyManager getDependencyManager(); @NonNull DependencyManager getDependencyManager();
@NonNull Optional<InternalMessagingService> getMessagingService();
@NonNull Storage getStorage(); @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.logger.PluginLogger;
import dev.xhyrom.lighteco.common.plugin.scheduler.SchedulerAdapter; import dev.xhyrom.lighteco.common.plugin.scheduler.SchedulerAdapter;
import net.kyori.adventure.audience.Audience;
import java.io.InputStream; import java.io.InputStream;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.UUID; import java.util.UUID;
public interface LightEcoBootstrap { public interface LightEcoBootstrap {
Object getLoader(); Object getLoader();
PluginLogger getLogger(); PluginLogger getLogger();
SchedulerAdapter getScheduler(); SchedulerAdapter getScheduler();
Path getDataDirectory(); Path getDataDirectory();
String getVersion();
Optional<UUID> lookupUniqueId(String username);
boolean isPlayerOnline(UUID uniqueId); boolean isPlayerOnline(UUID uniqueId);
List<UUID> getOnlinePlayers(); List<UUID> getOnlinePlayers();
InputStream getResourceStream(String filename); InputStream getResourceStream(String filename);
Audience getPlayerAudience(UUID uniqueId);
} }

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