diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index a3fd9288..b5c2f85c 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -1,6 +1,6 @@ object Versions { - const val SPIGOT_API = "1.19.4-R0.1-SNAPSHOT" + const val PAPER_API = "1.20.4-R0.1-SNAPSHOT" const val OKAERI_CONFIGS = "5.0.5" const val LITE_COMMANDS = "3.10.5" diff --git a/buildSrc/src/main/kotlin/economy-repositories.gradle.kts b/buildSrc/src/main/kotlin/economy-repositories.gradle.kts index 5e501f5f..6ae90508 100644 --- a/buildSrc/src/main/kotlin/economy-repositories.gradle.kts +++ b/buildSrc/src/main/kotlin/economy-repositories.gradle.kts @@ -3,7 +3,7 @@ plugins { } repositories { - mavenCentral() + maven("https://maven-central.storage-download.googleapis.com/maven2") maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") maven("https://repo.papermc.io/repository/maven-public/") maven("https://repo.panda-lang.org/releases/") @@ -13,4 +13,4 @@ repositories { maven("https://jitpack.io") maven("https://repo.extendedclip.com/content/repositories/placeholderapi/") maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") -} \ No newline at end of file +} diff --git a/eternaleconomy-api/build.gradle.kts b/eternaleconomy-api/build.gradle.kts index 73f32897..fe91090c 100644 --- a/eternaleconomy-api/build.gradle.kts +++ b/eternaleconomy-api/build.gradle.kts @@ -1,12 +1,11 @@ plugins { `economy-java` `economy-repositories` - // `economy-checkstyle` `economy-publish` } dependencies { - compileOnly("org.spigotmc:spigot-api:${Versions.SPIGOT_API}") + compileOnly("io.papermc.paper:paper-api:${Versions.PAPER_API}") api("org.jetbrains:annotations:${Versions.JETBRAINS_ANNOTATIONS}") } diff --git a/eternaleconomy-core/build.gradle.kts b/eternaleconomy-core/build.gradle.kts index 09f83224..a417f8ae 100644 --- a/eternaleconomy-core/build.gradle.kts +++ b/eternaleconomy-core/build.gradle.kts @@ -1,54 +1,61 @@ import net.minecrell.pluginyml.bukkit.BukkitPluginDescription +import net.minecrell.pluginyml.paper.PaperPluginDescription plugins { `economy-java` `economy-repositories` -// `economy-checkstyle` - id("net.minecrell.plugin-yml.bukkit") + id("net.minecrell.plugin-yml.paper") id("com.gradleup.shadow") id("xyz.jpenilla.run-paper") id("me.champeau.jmh") } + +repositories { + maven { + name = "papermc" + url = uri("https://repo.papermc.io/repository/maven-public/") + } +} + dependencies { // api module implementation(project(":eternaleconomy-api")) - // spigot-api - compileOnly("org.spigotmc:spigot-api:${Versions.SPIGOT_API}") + // paper-api + compileOnly("io.papermc.paper:paper-api:${Versions.PAPER_API}") // eternalcode commons - implementation("com.eternalcode:eternalcode-commons-adventure:${Versions.ETERNALCODE_COMMONS}") - implementation("com.eternalcode:eternalcode-commons-bukkit:${Versions.ETERNALCODE_COMMONS}") - implementation("com.eternalcode:eternalcode-commons-shared:${Versions.ETERNALCODE_COMMONS}") - implementation("com.eternalcode:eternalcode-commons-folia:${Versions.ETERNALCODE_COMMONS}") - - bukkitLibrary("org.mariadb.jdbc:mariadb-java-client:${Versions.MARIA_DB}") - bukkitLibrary("org.postgresql:postgresql:${Versions.POSTGRESQL}") - bukkitLibrary("com.h2database:h2:${Versions.H2}") - bukkitLibrary("com.j256.ormlite:ormlite-core:${Versions.ORMLITE}") - bukkitLibrary("com.j256.ormlite:ormlite-jdbc:${Versions.ORMLITE}") - bukkitLibrary("com.zaxxer:HikariCP:${Versions.HIKARI_CP}") - - implementation("dev.rollczi:litecommands-bukkit:${Versions.LITE_COMMANDS}") - implementation("dev.rollczi:litecommands-adventure:${Versions.LITE_COMMANDS}") - implementation("dev.rollczi:litecommands-jakarta:${Versions.LITE_COMMANDS}") + paperLibrary("com.eternalcode:eternalcode-commons-adventure:${Versions.ETERNALCODE_COMMONS}") + paperLibrary("com.eternalcode:eternalcode-commons-bukkit:${Versions.ETERNALCODE_COMMONS}") + paperLibrary("com.eternalcode:eternalcode-commons-shared:${Versions.ETERNALCODE_COMMONS}") + paperLibrary("com.eternalcode:eternalcode-commons-folia:${Versions.ETERNALCODE_COMMONS}") + + paperLibrary("org.mariadb.jdbc:mariadb-java-client:${Versions.MARIA_DB}") + paperLibrary("org.postgresql:postgresql:${Versions.POSTGRESQL}") + paperLibrary("com.h2database:h2:${Versions.H2}") + paperLibrary("com.j256.ormlite:ormlite-core:${Versions.ORMLITE}") + paperLibrary("com.j256.ormlite:ormlite-jdbc:${Versions.ORMLITE}") + paperLibrary("com.zaxxer:HikariCP:${Versions.HIKARI_CP}") + + paperLibrary("dev.rollczi:litecommands-bukkit:${Versions.LITE_COMMANDS}") + paperLibrary("dev.rollczi:litecommands-adventure:${Versions.LITE_COMMANDS}") + paperLibrary("dev.rollczi:litecommands-jakarta:${Versions.LITE_COMMANDS}") // multification - implementation("com.eternalcode:multification-bukkit:${Versions.MULTIFICATION}") - implementation("com.eternalcode:multification-okaeri:${Versions.MULTIFICATION}") - - // kyori - implementation("net.kyori:adventure-platform-bukkit:${Versions.ADVENTURE_PLATFORM_BUKKIT}") - implementation("net.kyori:adventure-text-minimessage:${Versions.ADVENTURE_API}") + paperLibrary("com.eternalcode:multification-bukkit:${Versions.MULTIFICATION}") + paperLibrary("com.eternalcode:multification-okaeri:${Versions.MULTIFICATION}") // vault compileOnly("com.github.MilkBowl:VaultAPI:${Versions.VAULT_API}") // okaeri configs - implementation("eu.okaeri:okaeri-configs-yaml-snakeyaml:${Versions.OKAERI_CONFIGS}") - implementation("eu.okaeri:okaeri-configs-serdes-commons:${Versions.OKAERI_CONFIGS}") + paperLibrary("eu.okaeri:okaeri-configs-yaml-snakeyaml:${Versions.OKAERI_CONFIGS}") + paperLibrary("eu.okaeri:okaeri-configs-serdes-commons:${Versions.OKAERI_CONFIGS}") + paperLibrary("eu.okaeri:okaeri-configs-serdes-bukkit:${Versions.OKAERI_CONFIGS}") + + paperLibrary("com.github.cryptomorin:XSeries:13.5.1") compileOnly("me.clip:placeholderapi:${Versions.PLACEHOLDER_API}") @@ -66,9 +73,10 @@ tasks.test { useJUnitPlatform() } -bukkit { +paper { main = "com.eternalcode.economy.EconomyBukkitPlugin" - apiVersion = "1.13" + loader = "com.eternalcode.economy.EconomyBukkitLoader" + apiVersion = "1.19" prefix = "EternalEconomy" author = "EternalCodeTeam" name = "EternalEconomy" @@ -79,14 +87,26 @@ bukkit { load = BukkitPluginDescription.PluginLoadOrder.STARTUP version = "${project.version}" - depend = listOf("Vault") - softDepend = listOf("PlaceholderAPI") + serverDependencies { + register("Vault") { + required = true + load = PaperPluginDescription.RelativeLoadOrder.BEFORE + } + register("PlaceholderAPI") { + required = false + load = PaperPluginDescription.RelativeLoadOrder.BEFORE + } + } foliaSupported = true + generateLibrariesJson = true } tasks.runServer { - minecraftVersion("1.21.8") + minecraftVersion("1.21.11") + downloadPlugins { + url("https://github.com/MilkBowl/Vault/releases/download/1.7.3/Vault.jar") + } } tasks.shadowJar { @@ -97,14 +117,8 @@ tasks.shadowJar { "org/jetbrains/annotations/**" ) - val prefix = "com.eternalcode.economy.libs" - listOf( - "dev.rollczi", - "eu.okaeri", - "panda", - "org.yaml", - "net.kyori", - "com.eternalcode.commons", - "net.jodah", - ).forEach { relocate(it, prefix) } +// val prefix = "com.eternalcode.economy.libs" +// listOf( +// +// ).forEach { relocate(it, prefix) } } diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/EconomyBukkitLoader.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/EconomyBukkitLoader.java new file mode 100644 index 00000000..1249bd22 --- /dev/null +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/EconomyBukkitLoader.java @@ -0,0 +1,55 @@ +package com.eternalcode.economy; + +import com.google.gson.Gson; +import io.papermc.paper.plugin.loader.PluginClasspathBuilder; +import io.papermc.paper.plugin.loader.PluginLoader; +import io.papermc.paper.plugin.loader.library.impl.MavenLibraryResolver; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; +import org.eclipse.aether.artifact.DefaultArtifact; +import org.eclipse.aether.graph.Dependency; +import org.eclipse.aether.repository.RemoteRepository; +import org.jetbrains.annotations.NotNull; + +public class EconomyBukkitLoader implements PluginLoader { + + public static final Gson GSON = new Gson(); + public static final String LIBRARIES_JSON_PATH = "/paper-libraries.json"; + + @Override + public void classloader(@NotNull PluginClasspathBuilder classpathBuilder) { + MavenLibraryResolver resolver = new MavenLibraryResolver(); + + PluginLibraries pluginLibraries = this.load(); + + pluginLibraries.asDependencies().forEach(resolver::addDependency); + pluginLibraries.asRepositories().forEach(resolver::addRepository); + + classpathBuilder.addLibrary(resolver); + } + + public PluginLibraries load() { + try (InputStream in = getClass().getResourceAsStream(LIBRARIES_JSON_PATH)) { + return GSON.fromJson(new InputStreamReader(in, StandardCharsets.UTF_8), PluginLibraries.class); + } + catch (IOException exception) { + throw new RuntimeException(exception); + } + } + + private record PluginLibraries(Map repositories, List dependencies) { + public Stream asDependencies() { + return this.dependencies.stream().map(d -> new Dependency(new DefaultArtifact(d), null)); + } + + public Stream asRepositories() { + return this.repositories.entrySet().stream() + .map(e -> new RemoteRepository.Builder(e.getKey(), "default", e.getValue()).build()); + } + } +} diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/EconomyBukkitPlugin.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/EconomyBukkitPlugin.java index 705ee90e..cbdb1eda 100644 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/EconomyBukkitPlugin.java +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/EconomyBukkitPlugin.java @@ -11,34 +11,39 @@ import com.eternalcode.economy.account.database.AccountRepository; import com.eternalcode.economy.account.database.AccountRepositoryImpl; import com.eternalcode.economy.bridge.BridgeManager; -import com.eternalcode.economy.command.impl.admin.AdminAddCommand; -import com.eternalcode.economy.command.impl.admin.AdminBalanceCommand; -import com.eternalcode.economy.command.impl.admin.AdminRemoveCommand; -import com.eternalcode.economy.command.impl.admin.AdminResetCommand; -import com.eternalcode.economy.command.impl.admin.AdminSetCommand; import com.eternalcode.economy.command.argument.AccountArgument; import com.eternalcode.economy.command.context.AccountContext; import com.eternalcode.economy.command.cooldown.CommandCooldownEditor; import com.eternalcode.economy.command.cooldown.CommandCooldownMessage; import com.eternalcode.economy.command.handler.InvalidUsageHandlerImpl; import com.eternalcode.economy.command.handler.MissingPermissionHandlerImpl; -import com.eternalcode.economy.command.message.InvalidBigDecimalMessage; import com.eternalcode.economy.command.impl.MoneyBalanceCommand; import com.eternalcode.economy.command.impl.MoneyTransferCommand; -import com.eternalcode.economy.database.DatabaseManager; -import com.eternalcode.economy.leaderboard.LeaderboardCommand; +import com.eternalcode.economy.command.impl.admin.AdminAddCommand; +import com.eternalcode.economy.command.impl.admin.AdminBalanceCommand; +import com.eternalcode.economy.command.impl.admin.AdminRemoveCommand; +import com.eternalcode.economy.command.impl.admin.AdminResetCommand; +import com.eternalcode.economy.command.impl.admin.AdminSetCommand; +import com.eternalcode.economy.command.message.InvalidBigDecimalMessage; import com.eternalcode.economy.command.validator.notsender.NotSender; import com.eternalcode.economy.command.validator.notsender.NotSenderValidator; import com.eternalcode.economy.config.ConfigService; import com.eternalcode.economy.config.implementation.CommandsConfig; import com.eternalcode.economy.config.implementation.PluginConfig; import com.eternalcode.economy.config.implementation.messages.MessageConfig; +import com.eternalcode.economy.database.DatabaseManager; import com.eternalcode.economy.format.DecimalFormatter; import com.eternalcode.economy.format.DecimalFormatterImpl; +import com.eternalcode.economy.leaderboard.LeaderboardCommand; import com.eternalcode.economy.multification.NoticeBroadcastHandler; import com.eternalcode.economy.multification.NoticeHandler; import com.eternalcode.economy.multification.NoticeService; import com.eternalcode.economy.vault.VaultEconomyProvider; +import com.eternalcode.economy.command.impl.WithdrawCommand; +import com.eternalcode.economy.withdraw.WithdrawItemServiceImpl; +import com.eternalcode.economy.withdraw.WithdrawService; +import com.eternalcode.economy.withdraw.controller.WithdrawAnvilController; +import com.eternalcode.economy.withdraw.controller.WithdrawController; import com.eternalcode.multification.notice.Notice; import com.eternalcode.multification.notice.NoticeBroadcast; import com.google.common.base.Stopwatch; @@ -46,12 +51,11 @@ import dev.rollczi.litecommands.bukkit.LiteBukkitFactory; import dev.rollczi.litecommands.jakarta.LiteJakartaExtension; import dev.rollczi.litecommands.message.LiteMessages; +import jakarta.validation.constraints.Min; import jakarta.validation.constraints.Positive; import java.io.File; import java.math.BigDecimal; import java.time.Duration; -import net.kyori.adventure.platform.AudienceProvider; -import net.kyori.adventure.platform.bukkit.BukkitAudiences; import net.kyori.adventure.text.minimessage.MiniMessage; import net.milkbowl.vault.economy.Economy; import org.bukkit.Server; @@ -64,7 +68,6 @@ public class EconomyBukkitPlugin extends JavaPlugin { private static final String PLUGIN_STARTED = "EternalEconomy has been enabled in %dms."; - private AudienceProvider audienceProvider; private DatabaseManager databaseManager; private LiteCommands liteCommands; @@ -74,21 +77,26 @@ public void onEnable() { Stopwatch started = Stopwatch.createStarted(); Server server = this.getServer(); - this.audienceProvider = BukkitAudiences.create(this); MiniMessage miniMessage = MiniMessage.builder() - .postProcessor(new AdventureUrlPostProcessor()) - .postProcessor(new AdventureLegacyColorPostProcessor()) - .preProcessor(new AdventureLegacyColorPreProcessor()) - .build(); + .postProcessor(new AdventureUrlPostProcessor()) + .postProcessor(new AdventureLegacyColorPostProcessor()) + .preProcessor(new AdventureLegacyColorPreProcessor()) + .build(); File dataFolder = this.getDataFolder(); ConfigService configService = new ConfigService(); - MessageConfig messageConfig = configService.create(MessageConfig.class, new File(dataFolder, "messages.yml")); - PluginConfig pluginConfig = configService.create(PluginConfig.class, new File(dataFolder, "config.yml")); - CommandsConfig commandsConfig = configService.create(CommandsConfig.class, new File(dataFolder, "commands.yml")); - - NoticeService noticeService = new NoticeService(messageConfig, this.audienceProvider, miniMessage); + MessageConfig messageConfig = configService.create( + MessageConfig.class, + new File(dataFolder, "messages.yml")); + PluginConfig pluginConfig = configService.create( + PluginConfig.class, + new File(dataFolder, "config.yml")); + CommandsConfig commandsConfig = configService.create( + CommandsConfig.class, + new File(dataFolder, "commands.yml")); + + NoticeService noticeService = new NoticeService(messageConfig, miniMessage); Scheduler scheduler = EconomySchedulerAdapter.getAdaptiveScheduler(this); @@ -101,60 +109,103 @@ public void onEnable() { DecimalFormatter decimalFormatter = new DecimalFormatterImpl(pluginConfig); AccountPaymentService accountPaymentService = new AccountPaymentService(accountManager, pluginConfig); - VaultEconomyProvider vaultEconomyProvider = - new VaultEconomyProvider(this, decimalFormatter, accountPaymentService, accountManager); - server.getServicesManager().register(Economy.class, vaultEconomyProvider, this, ServicePriority.Highest); + WithdrawItemServiceImpl withdrawItemServiceImpl = new WithdrawItemServiceImpl( + this, pluginConfig, + decimalFormatter, + miniMessage); + WithdrawService withdrawService = new WithdrawService( + server, + noticeService, + decimalFormatter, + withdrawItemServiceImpl, + accountPaymentService, + accountManager); + + Duration cooldownDuration = Duration.ofSeconds(pluginConfig.withdraw.cooldownSeconds); + + VaultEconomyProvider vaultEconomyProvider = new VaultEconomyProvider( + this, decimalFormatter, + accountPaymentService, accountManager); + + server.getServicesManager().register( + Economy.class, vaultEconomyProvider, this, + ServicePriority.Highest); this.liteCommands = LiteBukkitFactory.builder("eternaleconomy", this, server) - .extension(new LiteJakartaExtension<>(), settings -> settings - .violationMessage(Positive.class, BigDecimal.class, new InvalidBigDecimalMessage<>(noticeService)) - ) - - .annotations(extension -> extension.validator( - Account.class, - NotSender.class, - new NotSenderValidator(messageConfig))) - - .missingPermission(new MissingPermissionHandlerImpl(noticeService)) - .invalidUsage(new InvalidUsageHandlerImpl(noticeService)) - - .message(LiteMessages.COMMAND_COOLDOWN, new CommandCooldownMessage(noticeService, commandsConfig)) - .message(LiteMessages.INVALID_NUMBER, (invocation, amount) -> noticeService.create() - .notice(messageConfig.positiveNumberRequired) - .placeholder("{AMOUNT}", amount) - .viewer(invocation.sender())) - .editorGlobal(new CommandCooldownEditor(commandsConfig)) - - .commands( - new AdminAddCommand(accountPaymentService, decimalFormatter, noticeService), - new AdminRemoveCommand(accountPaymentService, decimalFormatter, noticeService), - new AdminSetCommand(accountPaymentService, decimalFormatter, noticeService), - new AdminResetCommand(accountPaymentService, noticeService), - new AdminBalanceCommand(noticeService, decimalFormatter), - new MoneyBalanceCommand(noticeService, decimalFormatter), - new MoneyTransferCommand(accountPaymentService, decimalFormatter, noticeService, pluginConfig), - new EconomyReloadCommand(configService, noticeService), - new LeaderboardCommand(noticeService, decimalFormatter, accountManager, pluginConfig) - ) - - .context(Account.class, new AccountContext(accountManager, messageConfig)) - .argument(Account.class, new AccountArgument(accountManager, noticeService, server)) - - .result(Notice.class, new NoticeHandler(noticeService)) - .result(NoticeBroadcast.class, new NoticeBroadcastHandler()) - - .build(); + .extension( + new LiteJakartaExtension<>(), settings -> settings + .violationMessage( + Min.class, BigDecimal.class, + new InvalidBigDecimalMessage<>( + noticeService))) + + .annotations(extension -> extension.validator( + Account.class, + NotSender.class, + new NotSenderValidator(messageConfig))) + + .missingPermission(new MissingPermissionHandlerImpl(noticeService)) + .invalidUsage(new InvalidUsageHandlerImpl(noticeService)) + + .message( + LiteMessages.COMMAND_COOLDOWN, + new CommandCooldownMessage(noticeService, commandsConfig)) + .message( + LiteMessages.INVALID_NUMBER, + (invocation, amount) -> noticeService.create() + .notice(messageConfig.positiveNumberRequired) + .placeholder("{AMOUNT}", amount) + .viewer(invocation.sender())) + .editorGlobal(new CommandCooldownEditor(commandsConfig)) + + .commands( + new AdminAddCommand( + accountPaymentService, decimalFormatter, + noticeService), + new AdminRemoveCommand( + accountPaymentService, decimalFormatter, + noticeService), + new AdminSetCommand( + accountPaymentService, decimalFormatter, + noticeService), + new AdminResetCommand(accountPaymentService, noticeService), + new AdminBalanceCommand(noticeService, decimalFormatter), + new WithdrawCommand( + withdrawService, cooldownDuration, + noticeService), + new MoneyBalanceCommand(noticeService, decimalFormatter), + new MoneyTransferCommand( + accountPaymentService, decimalFormatter, + noticeService, pluginConfig), + new EconomyReloadCommand(configService, noticeService), + new LeaderboardCommand( + noticeService, decimalFormatter, accountManager, + pluginConfig)) + + .context(Account.class, new AccountContext(accountManager, messageConfig)) + .argument(Account.class, new AccountArgument(accountManager, noticeService, server)) + + .result(Notice.class, new NoticeHandler(noticeService)) + .result(NoticeBroadcast.class, new NoticeBroadcastHandler()) + + .build(); server.getPluginManager().registerEvents(new AccountController(accountManager), this); + server.getPluginManager().registerEvents( + new WithdrawController(withdrawService, withdrawItemServiceImpl), + this); + server.getPluginManager().registerEvents( + new WithdrawAnvilController(withdrawItemServiceImpl, noticeService), + this); + BridgeManager bridgeManager = new BridgeManager( - this.getDescription(), - accountManager, - decimalFormatter, - server, - this, - this.getLogger() - ); + this.getPluginMeta(), + accountManager, + decimalFormatter, + server, + this, + this.getLogger()); bridgeManager.init(); Duration elapsed = started.elapsed(); @@ -163,10 +214,6 @@ public void onEnable() { @Override public void onDisable() { - if (this.audienceProvider != null) { - this.audienceProvider.close(); - } - if (this.liteCommands != null) { this.liteCommands.unregister(); } diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/EconomyPermissionConstant.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/EconomyPermissionConstant.java index f8e3b747..cae969b3 100644 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/EconomyPermissionConstant.java +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/EconomyPermissionConstant.java @@ -13,4 +13,5 @@ public class EconomyPermissionConstant { public static final String PLAYER_BALANCE_OTHER_PERMISSION = "eternaleconomy.player.balance.other"; public static final String PLAYER_PAY_PERMISSION = "eternaleconomy.player.pay"; public static final String PLAYER_BALANCE_TOP_PERMISSION = "eternaleconomy.player.balance.top"; + public static final String PLAYER_WITHDRAW_PERMISSION = "eternaleconomy.player.withdraw"; } diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/bridge/BridgeManager.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/bridge/BridgeManager.java index b424583f..9e1d5f54 100644 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/bridge/BridgeManager.java +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/bridge/BridgeManager.java @@ -3,6 +3,7 @@ import com.eternalcode.economy.account.AccountManager; import com.eternalcode.economy.bridge.placeholderapi.PlaceholderEconomyExpansion; import com.eternalcode.economy.format.DecimalFormatter; +import io.papermc.paper.plugin.configuration.PluginMeta; import java.util.logging.Logger; import org.bukkit.Bukkit; import org.bukkit.Server; @@ -12,7 +13,7 @@ public class BridgeManager { - private final PluginDescriptionFile pluginDescriptionFile; + private final PluginMeta pluginMeta; private final AccountManager accountManager; private final DecimalFormatter decimalFormatter; @@ -22,14 +23,14 @@ public class BridgeManager { private final Logger logger; public BridgeManager( - PluginDescriptionFile pluginDescriptionFile, + PluginMeta pluginMeta, AccountManager accountManager, DecimalFormatter decimalFormatter, Server server, Plugin plugin, Logger logger ) { - this.pluginDescriptionFile = pluginDescriptionFile; + this.pluginMeta = pluginMeta; this.accountManager = accountManager; this.decimalFormatter = decimalFormatter; this.server = server; @@ -44,7 +45,7 @@ public void init() { Bukkit.getScheduler().runTask(this.plugin, () -> { this.setupBridge("PlaceholderAPI", () -> { PlaceholderEconomyExpansion placeholderEconomyExpansion = new PlaceholderEconomyExpansion( - this.pluginDescriptionFile, + this.pluginMeta, this.accountManager, this.decimalFormatter ); diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/bridge/placeholderapi/PlaceholderEconomyExpansion.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/bridge/placeholderapi/PlaceholderEconomyExpansion.java index 31eac73e..72dfd8af 100644 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/bridge/placeholderapi/PlaceholderEconomyExpansion.java +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/bridge/placeholderapi/PlaceholderEconomyExpansion.java @@ -4,42 +4,41 @@ import com.eternalcode.economy.account.AccountManager; import com.eternalcode.economy.bridge.BridgeInitializer; import com.eternalcode.economy.format.DecimalFormatter; +import io.papermc.paper.plugin.configuration.PluginMeta; import java.util.UUID; import me.clip.placeholderapi.expansion.PlaceholderExpansion; import org.bukkit.OfflinePlayer; -import org.bukkit.plugin.PluginDescriptionFile; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public class PlaceholderEconomyExpansion extends PlaceholderExpansion implements BridgeInitializer { - private final PluginDescriptionFile pluginDescriptionFile; + private final PluginMeta pluginMeta; private final AccountManager accountManager; private final DecimalFormatter decimalFormatter; public PlaceholderEconomyExpansion( - PluginDescriptionFile pluginDescriptionFile, - AccountManager accountManager, - DecimalFormatter decimalFormatter - ) { - this.pluginDescriptionFile = pluginDescriptionFile; + PluginMeta pluginMeta, + AccountManager accountManager, + DecimalFormatter decimalFormatter) { + this.pluginMeta = pluginMeta; this.accountManager = accountManager; this.decimalFormatter = decimalFormatter; } @Override public @NotNull String getIdentifier() { - return this.pluginDescriptionFile.getName().toLowerCase(); + return this.pluginMeta.getName().toLowerCase(); } @Override public @NotNull String getAuthor() { - return this.pluginDescriptionFile.getAuthors().get(0); + return this.pluginMeta.getAuthors().get(0); } @Override public @NotNull String getVersion() { - return this.pluginDescriptionFile.getVersion(); + return this.pluginMeta.getVersion(); } @Override @@ -54,7 +53,7 @@ public PlaceholderEconomyExpansion( switch (params) { case "balance" -> { - return String.format("%.2f", account.balance()); + return this.decimalFormatter.format(account.balance()); } case "balance_formatted" -> { return this.decimalFormatter.format(account.balance()); diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/cooldown/CommandCooldownMessage.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/cooldown/CommandCooldownMessage.java index 9dcd914c..6a55f010 100644 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/cooldown/CommandCooldownMessage.java +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/cooldown/CommandCooldownMessage.java @@ -23,7 +23,7 @@ public CommandCooldownMessage(NoticeService noticeService, CommandsConfig comman @Override public Object get(Invocation invocation, CooldownState cooldownState) { - CommandCooldownConfig cooldown = commandsConfig.cooldowns.get(cooldownState.getCooldownContext().getKey()); + CommandCooldownConfig cooldown = commandsConfig.cooldowns.get(cooldownState.getKey()); if (cooldown == null) { return LiteMessages.COMMAND_COOLDOWN.getDefaultMessage(cooldownState); diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/impl/MoneyTransferCommand.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/impl/MoneyTransferCommand.java index a1175b83..360fed8f 100644 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/impl/MoneyTransferCommand.java +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/impl/MoneyTransferCommand.java @@ -12,7 +12,7 @@ import dev.rollczi.litecommands.annotations.context.Context; import dev.rollczi.litecommands.annotations.execute.Execute; import dev.rollczi.litecommands.annotations.permission.Permission; -import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.Min; import java.math.BigDecimal; @Command(name = "pay", aliases = "transfer") @@ -22,7 +22,7 @@ public class MoneyTransferCommand { private final AccountPaymentService accountPaymentService; private final DecimalFormatter decimalFormatter; private final NoticeService noticeService; - private PluginConfig pluginConfig; + private final PluginConfig pluginConfig; public MoneyTransferCommand( AccountPaymentService accountPaymentService, @@ -37,18 +37,7 @@ public MoneyTransferCommand( } @Execute - void execute(@Context Account payer, @Arg @NotSender Account receiver, @Arg @Positive BigDecimal amount) { - if (payer.balance().compareTo(amount) < 1) { - BigDecimal subtract = amount.subtract(payer.balance()); - this.noticeService.create() - .notice(notice -> notice.player.insufficientBalance) - .placeholder("{MISSING_BALANCE}", this.decimalFormatter.format(subtract)) - .player(payer.uuid()) - .send(); - - return; - } - + void execute(@Context Account payer, @Arg @NotSender Account receiver, @Arg @Min(1) BigDecimal amount) { if (amount.compareTo(this.pluginConfig.transactionLimit) > 0) { this.noticeService.create() .notice(notice -> notice.player.transferLimit) diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/impl/WithdrawCommand.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/impl/WithdrawCommand.java new file mode 100644 index 00000000..aa4cd886 --- /dev/null +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/impl/WithdrawCommand.java @@ -0,0 +1,56 @@ +package com.eternalcode.economy.command.impl; + +import com.eternalcode.economy.EconomyPermissionConstant; +import com.eternalcode.economy.account.Account; +import com.eternalcode.economy.delay.Delay; +import com.eternalcode.economy.multification.NoticeService; +import com.eternalcode.economy.withdraw.WithdrawService; +import dev.rollczi.litecommands.annotations.argument.Arg; +import dev.rollczi.litecommands.annotations.command.Command; +import dev.rollczi.litecommands.annotations.context.Context; +import dev.rollczi.litecommands.annotations.execute.Execute; +import dev.rollczi.litecommands.annotations.permission.Permission; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.Positive; +import java.math.BigDecimal; +import java.time.Duration; +import java.util.UUID; + +@Command(name = "withdraw", aliases = {"paycheck", "check"}) +@Permission(EconomyPermissionConstant.PLAYER_WITHDRAW_PERMISSION) +public class WithdrawCommand { + + public static final int MIN_WITHDRAWAL_AMOUNT = 1; + + private final WithdrawService withdrawService; + private final Delay cooldown; + private final NoticeService noticeService; + + public WithdrawCommand( + WithdrawService withdrawService, + Duration cooldownDuration, + NoticeService noticeService + ) { + this.withdrawService = withdrawService; + this.cooldown = Delay.withDefault(() -> cooldownDuration); + this.noticeService = noticeService; + } + + @Execute + void execute(@Context Account account, @Arg("amount") @Min(MIN_WITHDRAWAL_AMOUNT) BigDecimal value) { + UUID playerId = account.uuid(); + + if (this.cooldown.hasDelay(playerId)) { + long remainingSeconds = Math.max(0, this.cooldown.getRemaining(playerId).toSeconds()); + this.noticeService.create() + .notice(messageConfig -> messageConfig.withdraw.withdrawCooldown) + .placeholder("{TIME}", String.valueOf(remainingSeconds)) + .player(playerId) + .send(); + return; + } + + this.withdrawService.addBanknote(playerId, value); + this.cooldown.markDelay(playerId); + } +} diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/impl/admin/AdminAddCommand.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/impl/admin/AdminAddCommand.java index 0cd2b242..79e458bd 100644 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/impl/admin/AdminAddCommand.java +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/impl/admin/AdminAddCommand.java @@ -10,7 +10,7 @@ import dev.rollczi.litecommands.annotations.context.Context; import dev.rollczi.litecommands.annotations.execute.Execute; import dev.rollczi.litecommands.annotations.permission.Permission; -import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.Min; import java.math.BigDecimal; import org.bukkit.command.CommandSender; @@ -33,7 +33,7 @@ public AdminAddCommand( } @Execute - void execute(@Context CommandSender sender, @Arg Account receiver, @Arg @Positive BigDecimal amount) { + void execute(@Context CommandSender sender, @Arg Account receiver, @Arg @Min(1) BigDecimal amount) { this.accountPaymentService.addBalance(receiver, amount); this.noticeService.create() diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/impl/admin/AdminRemoveCommand.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/impl/admin/AdminRemoveCommand.java index bfb314c1..009a0a38 100644 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/impl/admin/AdminRemoveCommand.java +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/impl/admin/AdminRemoveCommand.java @@ -10,7 +10,7 @@ import dev.rollczi.litecommands.annotations.context.Context; import dev.rollczi.litecommands.annotations.execute.Execute; import dev.rollczi.litecommands.annotations.permission.Permission; -import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.Min; import java.math.BigDecimal; import org.bukkit.command.CommandSender; @@ -33,7 +33,7 @@ public AdminRemoveCommand( } @Execute - void execute(@Context CommandSender sender, @Arg Account receiver, @Arg @Positive BigDecimal amount) { + void execute(@Context CommandSender sender, @Arg Account receiver, @Arg @Min(1) BigDecimal amount) { if (receiver.balance().compareTo(amount) < 0) { BigDecimal subtract = amount.subtract(receiver.balance()); this.noticeService.create() diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/impl/admin/AdminSetCommand.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/impl/admin/AdminSetCommand.java index bd85ab91..1aac9574 100644 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/impl/admin/AdminSetCommand.java +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/impl/admin/AdminSetCommand.java @@ -10,7 +10,7 @@ import dev.rollczi.litecommands.annotations.context.Context; import dev.rollczi.litecommands.annotations.execute.Execute; import dev.rollczi.litecommands.annotations.permission.Permission; -import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.Min; import java.math.BigDecimal; import org.bukkit.command.CommandSender; @@ -25,15 +25,23 @@ public class AdminSetCommand { public AdminSetCommand( AccountPaymentService accountPaymentService, DecimalFormatter decimalFormatter, - NoticeService noticeService - ) { + NoticeService noticeService) { this.accountPaymentService = accountPaymentService; this.decimalFormatter = decimalFormatter; this.noticeService = noticeService; } @Execute - void execute(@Context CommandSender sender, @Arg Account receiver, @Arg @Positive BigDecimal amount) { + void execute(@Context CommandSender sender, @Arg Account receiver, @Arg @Min(1) BigDecimal amount) { + this.setAccountBalance(sender, receiver, amount); + } + + @Execute(name = "0") + void execute(@Context CommandSender sender, @Arg Account receiver) { + this.setAccountBalance(sender, receiver, BigDecimal.ZERO); + } + + private void setAccountBalance(CommandSender sender, Account receiver, BigDecimal amount) { this.accountPaymentService.setBalance(receiver, amount); this.noticeService.create() @@ -50,4 +58,5 @@ void execute(@Context CommandSender sender, @Arg Account receiver, @Arg @Positiv .player(receiver.uuid()) .send(); } + } diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/config/ConfigService.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/config/ConfigService.java index 640df63c..3efd7f7e 100644 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/config/ConfigService.java +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/config/ConfigService.java @@ -7,6 +7,7 @@ import eu.okaeri.configs.ConfigManager; import eu.okaeri.configs.OkaeriConfig; import eu.okaeri.configs.serdes.commons.SerdesCommons; +import eu.okaeri.configs.yaml.bukkit.serdes.SerdesBukkit; import eu.okaeri.configs.yaml.snakeyaml.YamlSnakeYamlConfigurer; import java.io.File; import java.util.HashSet; @@ -30,7 +31,7 @@ public T create(Class config, File file) { NoticeResolverRegistry noticeRegistry = NoticeResolverDefaults.createRegistry() .registerResolver(new SoundBukkitResolver()); configFile - .withConfigurer(yamlConfigurer, new SerdesCommons()) + .withConfigurer(yamlConfigurer, new SerdesCommons(), new SerdesBukkit()) .withConfigurer(yamlConfigurer, new MultificationSerdesPack(noticeRegistry)) .withBindFile(file) .withRemoveOrphans(true) diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/config/implementation/PluginConfig.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/config/implementation/PluginConfig.java index 18afcf4c..3ff6b3f6 100644 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/config/implementation/PluginConfig.java +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/config/implementation/PluginConfig.java @@ -1,14 +1,17 @@ package com.eternalcode.economy.config.implementation; +import com.eternalcode.economy.config.item.ConfigItem; +import com.eternalcode.economy.config.item.WithdrawItemEntry; import com.eternalcode.economy.database.DatabaseConfig; import com.eternalcode.economy.format.DecimalUnit; import eu.okaeri.configs.OkaeriConfig; import eu.okaeri.configs.annotation.Comment; +import org.bukkit.Material; + import java.math.BigDecimal; -import java.util.Arrays; import java.util.List; -@SuppressWarnings({"FieldMayBeFinal", "FieldCanBeLocal"}) +@SuppressWarnings({ "FieldMayBeFinal", "FieldCanBeLocal" }) public class PluginConfig extends OkaeriConfig { @Comment("Units settings") @@ -29,15 +32,63 @@ public class PluginConfig extends OkaeriConfig { @Comment("Should leaderboard command show player's position in the leaderboard") public boolean showLeaderboardPosition = true; + @Comment("Currency item settings") + public WithdrawItem withdraw = new WithdrawItem(); + public static class Units extends OkaeriConfig { - public List format = Arrays.asList( - new DecimalUnit(1_000L, 'k'), - new DecimalUnit(1_000_000L, 'm'), - new DecimalUnit(1_000_000_000L, 'b'), - new DecimalUnit(1_000_000_000_000L, 't'), - new DecimalUnit(1_000_000_000_000_000L, 'p'), - new DecimalUnit(1_000_000_000_000_000_000L, 'e') - ); + public List format = List.of( + new DecimalUnit(1_000L, 'k'), + new DecimalUnit(1_000_000L, 'm'), + new DecimalUnit(1_000_000_000L, 'b'), + new DecimalUnit(1_000_000_000_000L, 't'), + new DecimalUnit(1_000_000_000_000_000L, 'p'), + new DecimalUnit(1_000_000_000_000_000_000L, 'e')); + } + + public static class WithdrawItem extends OkaeriConfig { + + @Comment("Cooldown in seconds between withdraw commands (0 = disabled)") + public int cooldownSeconds = 5; + + @Comment("Default item used when multi-item system is disabled or as fallback") + public ConfigItem item = ConfigItem.builder() + .withName("Check worth {VALUE}$") + .withLore(List.of("Right click to redeem")) + .withMaterial(Material.PAPER) + .withTexture(0) + .withGlow(true) + .build(); + + @Comment({ + "Enable multi-item system: different items for different banknote values", + "When disabled, always uses the default 'item' above" + }) + public boolean multiItemEnabled = false; + + @Comment({ + "Item configurations for specific value thresholds", + "System selects the entry with highest minValue <= banknote value", + "Example: minValue 1.0 = coins, minValue 100.0 = banknotes" + }) + public List multiItemEntries = List.of( + new WithdrawItemEntry( + BigDecimal.ONE, + ConfigItem.builder() + .withName("Coin worth {VALUE}$") + .withLore(List.of("Right click to redeem")) + .withMaterial(Material.GOLD_NUGGET) + .withTexture(1) + .withGlow(false) + .build()), + new WithdrawItemEntry( + BigDecimal.valueOf(100), + ConfigItem.builder() + .withName("Banknote worth {VALUE}$") + .withLore(List.of("Right click to redeem")) + .withMaterial(Material.PAPER) + .withTexture(100) + .withGlow(true) + .build())); } } diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/config/implementation/messages/MessageAdminSubSection.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/config/implementation/messages/MessageAdminSubSection.java index f7c581dd..7805414d 100644 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/config/implementation/messages/MessageAdminSubSection.java +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/config/implementation/messages/MessageAdminSubSection.java @@ -8,23 +8,13 @@ public class MessageAdminSubSection extends OkaeriConfig { public Notice insufficientFunds = - Notice.chat("ECONOMY " - + "Player {PLAYER} has insufficient funds, they are missing {MISSING_BALANCE}."); + Notice.chat("ECONOMY Player {PLAYER} has insufficient funds, they are missing {MISSING_BALANCE}."); - public Notice added = Notice.chat("ECONOMY " - + "Added {AMOUNT} to " - + "{PLAYER}."); - public Notice removed = Notice.chat("ECONOMY " - + " Removed {AMOUNT} from " - + "{PLAYER}."); - public Notice set = Notice.chat("ECONOMY " - + "Set {PLAYER}'s balance to " - + "{AMOUNT}."); - public Notice reset = Notice.chat("ECONOMY " - + "Reset {PLAYER}'s balance."); - public Notice balance = Notice.chat("ECONOMY " - + " {PLAYER}'s balance is " - + "{BALANCE}."); + public Notice added = Notice.chat("ECONOMY Added {AMOUNT} to {PLAYER}."); + public Notice removed = Notice.chat("ECONOMY Removed {AMOUNT} from {PLAYER}."); + public Notice set = Notice.chat("ECONOMY Set {PLAYER}'s balance to {AMOUNT}."); + public Notice reset = Notice.chat("ECONOMY Reset {PLAYER}'s balance."); + public Notice balance = Notice.chat("ECONOMY {PLAYER}'s balance is {BALANCE}."); } diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/config/implementation/messages/MessageConfig.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/config/implementation/messages/MessageConfig.java index b44fdd85..c6819043 100644 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/config/implementation/messages/MessageConfig.java +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/config/implementation/messages/MessageConfig.java @@ -1,5 +1,6 @@ package com.eternalcode.economy.config.implementation.messages; +import com.eternalcode.economy.withdraw.WithdrawMessageConfig; import com.eternalcode.multification.notice.Notice; import eu.okaeri.configs.OkaeriConfig; @@ -23,4 +24,5 @@ public class MessageConfig extends OkaeriConfig { public MessageAdminSubSection admin = new MessageAdminSubSection(); public MessagesPlayerSubSection player = new MessagesPlayerSubSection(); + public WithdrawMessageConfig withdraw = new WithdrawMessageConfig(); } diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/config/implementation/messages/MessagesPlayerSubSection.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/config/implementation/messages/MessagesPlayerSubSection.java index 42654ec9..9d3f293a 100644 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/config/implementation/messages/MessagesPlayerSubSection.java +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/config/implementation/messages/MessagesPlayerSubSection.java @@ -6,37 +6,23 @@ public class MessagesPlayerSubSection extends OkaeriConfig { - public Notice added = Notice.chat("ECONOMY " - + "Added {AMOUNT} to your account."); - public Notice removed = Notice.chat("ECONOMY " - + " Removed {AMOUNT} from your account."); - public Notice set = Notice.chat("ECONOMY " - + "Set your balance to {AMOUNT}."); - public Notice reset = Notice.chat("ECONOMY " - + "Resetted your balance."); - public Notice balance = Notice.chat("ECONOMY " - + " Your balance is {BALANCE}."); + public Notice added = Notice.chat("ECONOMY Added {AMOUNT} to your account."); + public Notice removed = Notice.chat("ECONOMY Removed {AMOUNT} from your account."); + public Notice set = Notice.chat("ECONOMY Set your balance to {AMOUNT}."); + public Notice reset = Notice.chat("ECONOMY Resetted your balance."); + public Notice balance = Notice.chat("ECONOMY Your balance is {BALANCE}."); public Notice balanceOther = - Notice.chat("ECONOMY " - + " {PLAYER}'s balance is {BALANCE}."); - public Notice insufficientBalance = Notice.chat("ECONOMY " - + " Insufficient funds," - + " you are missing {MISSING_BALANCE}."); - public Notice transferSuccess = Notice.chat("ECONOMY Successfully transferred {AMOUNT} to " - + "{PLAYER}."); - public Notice transferReceived = Notice.chat("ECONOMY " - + " Received {AMOUNT} from " - + "{PLAYER}."); - public Notice transferLimit = Notice.chat("ECONOMY " - + " Transaction limit is {LIMIT}."); + Notice.chat("ECONOMY {PLAYER}'s balance is {BALANCE}."); + public Notice insufficientBalance = Notice.chat("ECONOMY Insufficient funds, you are missing {MISSING_BALANCE}."); + public Notice transferSuccess = Notice.chat("ECONOMY Successfully transferred {AMOUNT} to {PLAYER}."); + public Notice transferReceived = Notice.chat("ECONOMY Received {AMOUNT} from {PLAYER}."); + public Notice transferLimit = Notice.chat("ECONOMY Transaction limit is {LIMIT}."); @Comment({ "Use {PAGE} placeholder to show the current page number", "Use {TOTAL_PAGES} placeholder to show the total number of pages" }) - public Notice leaderboardHeader = Notice.chat(" Balance leaderboard (Page " - + "{PAGE}/{TOTAL_PAGES}): "); + public Notice leaderboardHeader = Notice.chat(" Balance leaderboard (Page {PAGE}/{TOTAL_PAGES}): "); @Comment({ "Leaderboard entry notice, only displayed if showLeaderboardPosition is set to true in the config.yml", @@ -45,8 +31,7 @@ public class MessagesPlayerSubSection extends OkaeriConfig { "Use {BALANCE} placeholder to show the player's formatted balance", "Use {BALANCE_RAW} placeholder to show the player's raw balance" }) - public Notice leaderboardEntry = Notice.chat(" #{POSITION} {PLAYER} -" - + " {BALANCE}"); + public Notice leaderboardEntry = Notice.chat(" #{POSITION} {PLAYER} - {BALANCE}"); @Comment({ "Leaderboard position notice, only displayed if showLeaderboardPosition is set to true in the config.yml", @@ -65,6 +50,5 @@ public class MessagesPlayerSubSection extends OkaeriConfig { .build(); @Comment("Leaderboard is empty notice") - public Notice leaderboardEmpty = Notice.chat("ECONOMY " - + " Leaderboard is empty :("); + public Notice leaderboardEmpty = Notice.chat("ECONOMY Leaderboard is empty :("); } diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/config/item/ConfigItem.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/config/item/ConfigItem.java new file mode 100644 index 00000000..6ea1dff7 --- /dev/null +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/config/item/ConfigItem.java @@ -0,0 +1,92 @@ +package com.eternalcode.economy.config.item; + +import eu.okaeri.configs.OkaeriConfig; +import java.util.Collections; +import java.util.List; +import org.bukkit.Material; + +public class ConfigItem extends OkaeriConfig { + + private String name; + private List lore; + private Material material; + private Integer texture; + private boolean glow; + + public ConfigItem(String name, List lore, Material material, Integer texture, boolean glow) { + this.name = name; + this.lore = lore != null ? lore : List.of(); + this.material = material; + this.texture = texture; + this.glow = glow; + } + + public ConfigItem() { + this.name = "§6Item"; + this.lore = List.of("§7This is an item"); + this.material = Material.PLAYER_HEAD; + this.texture = null; + this.glow = false; + } + + public static Builder builder() { + return new Builder(); + } + + public String name() { + return this.name; + } + + public List lore() { + return this.lore; + } + + public Material material() { + return this.material; + } + + public Integer texture() { + return this.texture; + } + + public boolean glow() { + return this.glow; + } + + public static class Builder { + private String name = "§6Item"; + private List lore = List.of("§7This is an item"); + private Material material = Material.PLAYER_HEAD; + private Integer texture = null; + private boolean glow = false; + + public Builder withName(String name) { + this.name = name; + return this; + } + + public Builder withLore(List lore) { + this.lore = lore != null ? lore : List.of(); + return this; + } + + public Builder withMaterial(Material material) { + this.material = material; + return this; + } + + public Builder withTexture(Integer texture) { + this.texture = texture; + return this; + } + + public Builder withGlow(boolean glow) { + this.glow = glow; + return this; + } + + public ConfigItem build() { + return new ConfigItem(this.name, this.lore, this.material, this.texture, this.glow); + } + } +} diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/config/item/WithdrawItemEntry.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/config/item/WithdrawItemEntry.java new file mode 100644 index 00000000..af85e9bc --- /dev/null +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/config/item/WithdrawItemEntry.java @@ -0,0 +1,36 @@ +package com.eternalcode.economy.config.item; + +import eu.okaeri.configs.OkaeriConfig; +import java.math.BigDecimal; +import java.util.List; +import org.bukkit.Material; + +public class WithdrawItemEntry extends OkaeriConfig { + + private BigDecimal minValue; + private ConfigItem item; + + public WithdrawItemEntry(BigDecimal minValue, ConfigItem item) { + this.minValue = minValue; + this.item = item; + } + + public WithdrawItemEntry() { + this.minValue = BigDecimal.ONE; + this.item = ConfigItem.builder() + .withName("Coin worth {VALUE}$") + .withLore(List.of("Right click to redeem")) + .withMaterial(Material.GOLD_NUGGET) + .withTexture(1) + .withGlow(false) + .build(); + } + + public BigDecimal minValue() { + return this.minValue; + } + + public ConfigItem item() { + return this.item; + } +} diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/delay/Delay.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/delay/Delay.java new file mode 100644 index 00000000..6fa20dc0 --- /dev/null +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/delay/Delay.java @@ -0,0 +1,58 @@ +package com.eternalcode.economy.delay; + +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; + +import java.time.Duration; +import java.time.Instant; +import java.util.function.Supplier; + +public class Delay { + + private final Cache cache; + private final Supplier defaultDelay; + + private Delay(Supplier defaultDelay) { + if (defaultDelay == null) { + throw new IllegalArgumentException("defaultDelay cannot be null"); + } + + this.defaultDelay = defaultDelay; + this.cache = Caffeine.newBuilder() + .expireAfter(new InstantExpiry()) + .build(); + } + + public void markDelay(T key, Duration delay) { + if (delay.isZero() || delay.isNegative()) { + this.cache.invalidate(key); + } + + this.cache.put(key, Instant.now().plus(delay)); + } + + public void markDelay(T key) { + this.markDelay(key, this.defaultDelay.get()); + } + + public void unmarkDelay(T key) { + this.cache.invalidate(key); + } + + public boolean hasDelay(T key) { + Instant delayExpireMoment = this.getExpireAt(key); + return Instant.now().isBefore(delayExpireMoment); + } + + public Duration getRemaining(T key) { + return Duration.between(Instant.now(), this.getExpireAt(key)); + } + + private Instant getExpireAt(T key) { + return this.cache.asMap().getOrDefault(key, Instant.MIN); + } + + public static Delay withDefault(Supplier defaultDelay) { + return new Delay<>(defaultDelay); + } +} diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/delay/InstantExpiry.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/delay/InstantExpiry.java new file mode 100644 index 00000000..7618fd02 --- /dev/null +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/delay/InstantExpiry.java @@ -0,0 +1,37 @@ +package com.eternalcode.economy.delay; + +import com.github.benmanes.caffeine.cache.Expiry; +import java.time.Duration; +import java.time.Instant; + +class InstantExpiry implements Expiry { + + @Override + public long expireAfterCreate(T key, Instant expireTime, long currentTime) { + return timeToExpire(expireTime); + } + + @Override + public long expireAfterUpdate(T key, Instant newExpireTime, long currentTime, long currentDuration) { + return timeToExpire(newExpireTime); + } + + @Override + public long expireAfterRead(T key, Instant value, long currentTime, long currentDuration) { + return currentDuration; + } + + private static long timeToExpire(Instant expireTime) { + Duration toExpire = Duration.between(Instant.now(), expireTime); + if (toExpire.isNegative()) { + return 0; + } + + long nanos = toExpire.toNanos(); + if (nanos == 0) { + return 1; + } + + return nanos; + } +} diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/format/DecimalFormatterImpl.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/format/DecimalFormatterImpl.java index d6d1aaec..fa77835d 100644 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/format/DecimalFormatterImpl.java +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/format/DecimalFormatterImpl.java @@ -8,6 +8,7 @@ import com.eternalcode.economy.config.implementation.PluginConfig; import java.math.BigDecimal; +import java.math.RoundingMode; import java.util.List; /** @@ -33,14 +34,19 @@ private static String getFormattedAmountWithSuffix(double amount, double divisor // that use of this method was approximately 15x faster than use // of built-in DecimalFormat. private static String getTruncatedAmount(double amount) { + if (amount < 0.01 && amount > 0) { + BigDecimal bd = BigDecimal.valueOf(amount); + return bd.setScale(2, RoundingMode.HALF_UP).toPlainString(); + } + double fractionalPart = getFractionalPart(amount); if (fractionalPart < 0.01) { return Long.toString((long) amount); } fractionalPart *= 100; - fractionalPart = - (fractionalPart < 99 && fractionalPart % 1 >= 0.5) ? ceil(fractionalPart) : floor(fractionalPart); + fractionalPart = (fractionalPart < 99 && fractionalPart % 1 >= 0.5) ? ceil(fractionalPart) + : floor(fractionalPart); return (long) amount + TRUNCATED_AMOUNT_DELIMITER + (long) fractionalPart; } @@ -61,9 +67,9 @@ public String getFormattedDecimal(double amount) { DecimalUnit decimalUnit = units.get(nearestScaleDivider); return getFormattedAmountWithSuffix( - amount, - decimalUnit.getFactor(), - decimalUnit.getSuffix()); + amount, + decimalUnit.getFactor(), + decimalUnit.getSuffix()); } @Override diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/LeaderboardCommand.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/LeaderboardCommand.java index 82a3c33a..a79f8c7d 100644 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/LeaderboardCommand.java +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/LeaderboardCommand.java @@ -11,7 +11,7 @@ import dev.rollczi.litecommands.annotations.execute.Execute; import dev.rollczi.litecommands.annotations.execute.ExecuteDefault; import dev.rollczi.litecommands.annotations.permission.Permission; -import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.Min; import java.util.List; @SuppressWarnings("unused") @@ -42,7 +42,7 @@ void executeDefault(@Context Account account) { } @Execute - void execute(@Context Account account, @Positive @Arg("page") int page) { + void execute(@Context Account account, @Min(1) @Arg("page") int page) { LeaderboardPage leaderboardPage = this.leaderboardService.getLeaderboardPage(page - 1, this.pluginConfig.leaderboardPageSize); showPage(account, leaderboardPage); } @@ -96,5 +96,4 @@ private void showPage(Account account, LeaderboardPage page) { .send(); } } - } diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/multification/NoticeService.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/multification/NoticeService.java index 8d62a952..b9a855d5 100644 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/multification/NoticeService.java +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/multification/NoticeService.java @@ -4,23 +4,20 @@ import com.eternalcode.multification.adventure.AudienceConverter; import com.eternalcode.multification.bukkit.BukkitMultification; import com.eternalcode.multification.translation.TranslationProvider; -import net.kyori.adventure.platform.AudienceProvider; +import net.kyori.adventure.audience.Audience; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.serializer.ComponentSerializer; import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; public class NoticeService extends BukkitMultification { private final MessageConfig messageConfig; - private final AudienceProvider audienceProvider; private final MiniMessage miniMessage; - public NoticeService(MessageConfig messageConfig, AudienceProvider audienceProvider, MiniMessage miniMessage) { + public NoticeService(MessageConfig messageConfig, MiniMessage miniMessage) { this.messageConfig = messageConfig; - this.audienceProvider = audienceProvider; this.miniMessage = miniMessage; } @@ -36,12 +33,6 @@ public NoticeService(MessageConfig messageConfig, AudienceProvider audienceProvi @Override protected @NotNull AudienceConverter audienceConverter() { - return commandSender -> { - if (commandSender instanceof Player player) { - return this.audienceProvider.player(player.getUniqueId()); - } - - return this.audienceProvider.console(); - }; + return Audience::audience; } -} \ No newline at end of file +} diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/withdraw/WithdrawItemService.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/withdraw/WithdrawItemService.java new file mode 100644 index 00000000..bfd49e1c --- /dev/null +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/withdraw/WithdrawItemService.java @@ -0,0 +1,13 @@ +package com.eternalcode.economy.withdraw; + +import java.math.BigDecimal; +import org.bukkit.inventory.ItemStack; + +public interface WithdrawItemService { + + BigDecimal getValue(ItemStack itemStack); + + boolean isBanknote(ItemStack itemStack); + + ItemStack createBanknote(BigDecimal value); +} diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/withdraw/WithdrawItemServiceImpl.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/withdraw/WithdrawItemServiceImpl.java new file mode 100644 index 00000000..b8f28607 --- /dev/null +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/withdraw/WithdrawItemServiceImpl.java @@ -0,0 +1,180 @@ +package com.eternalcode.economy.withdraw; + +import com.cryptomorin.xseries.XEnchantment; +import com.eternalcode.economy.config.implementation.PluginConfig; +import com.eternalcode.economy.config.item.ConfigItem; +import com.eternalcode.economy.config.item.WithdrawItemEntry; +import com.eternalcode.economy.format.DecimalFormatter; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.NavigableMap; +import java.util.TreeMap; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.TextDecoration; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; +import org.bukkit.NamespacedKey; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.ItemFlag; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.persistence.PersistentDataType; +import org.bukkit.plugin.Plugin; + +public class WithdrawItemServiceImpl implements WithdrawItemService { + + private static final String VALUE_PLACEHOLDER = "{VALUE}"; + private static final String WITHDRAW_VALUE_KEY = "withdraw_value"; + private static final TagResolver EMPTY_RESOLVER = TagResolver.empty(); + + private final PluginConfig pluginConfig; + private final DecimalFormatter moneyFormatter; + private final MiniMessage miniMessage; + private final NamespacedKey banknoteValueKey; + private final Enchantment glowEnchantment; + + private volatile NavigableMap cachedThresholdMap; + private volatile List cachedEntries; + + public WithdrawItemServiceImpl( + Plugin plugin, + PluginConfig pluginConfig, + DecimalFormatter moneyFormatter, + MiniMessage miniMessage + ) { + this.pluginConfig = pluginConfig; + this.moneyFormatter = moneyFormatter; + this.miniMessage = miniMessage; + this.banknoteValueKey = new NamespacedKey(plugin, WITHDRAW_VALUE_KEY); + this.glowEnchantment = XEnchantment.UNBREAKING.get(); + } + + @Override + public ItemStack createBanknote(BigDecimal value) { + if (value.compareTo(BigDecimal.ZERO) <= 0) { + throw new IllegalArgumentException("Banknote value must be positive, got: " + value); + } + + ConfigItem configItem = this.selectConfigItem(value); + String formattedValue = this.moneyFormatter.format(value); + + ItemStack itemStack = new ItemStack(configItem.material()); + + itemStack.editMeta(meta -> { + if (configItem.texture() != null) { + meta.setCustomModelData(configItem.texture()); + } + + Component name = this.miniMessage.deserialize( + configItem.name().replace(VALUE_PLACEHOLDER, formattedValue), + EMPTY_RESOLVER + ).decoration(TextDecoration.ITALIC, false); + + meta.displayName(name); + + if (!configItem.lore().isEmpty()) { + List lore = new ArrayList<>(configItem.lore().size()); + for (String line : configItem.lore()) { + lore.add( + this.miniMessage.deserialize( + line.replace(VALUE_PLACEHOLDER, formattedValue), + EMPTY_RESOLVER + ).decoration(TextDecoration.ITALIC, false) + ); + } + meta.lore(lore); + } + + if (configItem.glow()) { + meta.addEnchant(this.glowEnchantment, 1, true); + meta.addItemFlags(ItemFlag.HIDE_ENCHANTS); + } + + meta.getPersistentDataContainer().set( + this.banknoteValueKey, + PersistentDataType.STRING, + value.toPlainString() + ); + }); + + return itemStack; + } + + private ConfigItem selectConfigItem(BigDecimal value) { + if (!this.pluginConfig.withdraw.multiItemEnabled) { + return this.pluginConfig.withdraw.item; + } + + NavigableMap thresholdMap = this.getOrRebuildThresholdMap(); + if (thresholdMap.isEmpty()) { + return this.pluginConfig.withdraw.item; + } + + Map.Entry entry = thresholdMap.floorEntry(value); + if (entry == null) { + return this.pluginConfig.withdraw.item; + } + + return entry.getValue(); + } + + private NavigableMap getOrRebuildThresholdMap() { + List entries = this.pluginConfig.withdraw.multiItemEntries; + + if (this.cachedThresholdMap == null || this.cachedEntries != entries) { + this.cachedThresholdMap = this.buildThresholdMap(entries); + this.cachedEntries = entries; + } + + return this.cachedThresholdMap; + } + + private NavigableMap buildThresholdMap(List entries) { + NavigableMap map = new TreeMap<>(); + + if (entries == null || entries.isEmpty()) { + return map; + } + + for (WithdrawItemEntry entry : entries) { + if (entry.minValue() != null && entry.item() != null) { + map.put(entry.minValue(), entry.item()); + } + } + + return map; + } + + @Override + public boolean isBanknote(ItemStack itemStack) { + if (itemStack == null || !itemStack.hasItemMeta()) { + return false; + } + + ItemMeta meta = itemStack.getItemMeta(); + return meta.getPersistentDataContainer().has(this.banknoteValueKey, PersistentDataType.STRING); + } + + @Override + public BigDecimal getValue(ItemStack itemStack) { + if (itemStack == null || !itemStack.hasItemMeta()) { + return BigDecimal.ZERO; + } + + ItemMeta meta = itemStack.getItemMeta(); + String value = meta.getPersistentDataContainer() + .get(this.banknoteValueKey, PersistentDataType.STRING); + + if (value == null) { + return BigDecimal.ZERO; + } + + try { + return new BigDecimal(value); + } catch (NumberFormatException ignored) { + return BigDecimal.ZERO; + } + } +} diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/withdraw/WithdrawMessageConfig.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/withdraw/WithdrawMessageConfig.java new file mode 100644 index 00000000..96cbc749 --- /dev/null +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/withdraw/WithdrawMessageConfig.java @@ -0,0 +1,24 @@ +package com.eternalcode.economy.withdraw; + +import com.eternalcode.multification.notice.Notice; +import eu.okaeri.configs.OkaeriConfig; + +public class WithdrawMessageConfig extends OkaeriConfig { + public Notice noBanknoteInHand = Notice.chat( + "ECONOMY You must hold a banknote in your hand to redeem it!"); + + public Notice banknoteWithdrawn = Notice.chat( + "ECONOMY You have withdrawn a banknote worth {VALUE}!"); + + public Notice banknoteRedeemed = Notice.chat( + "ECONOMY You have redeemed your banknote worth {VALUE}!"); + + public Notice invalidInteraction = Notice.chat( + "ECONOMY You cannot use the banknote item in an anvil or crafting table!"); + + public Notice noInventorySpace = Notice.chat( + "ECONOMY You do not have enough space in your inventory to withdraw a banknote!"); + + public Notice withdrawCooldown = Notice.chat( + "ECONOMY You must wait {TIME} seconds before withdrawing again!"); +} diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/withdraw/WithdrawService.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/withdraw/WithdrawService.java new file mode 100644 index 00000000..e71ef05f --- /dev/null +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/withdraw/WithdrawService.java @@ -0,0 +1,109 @@ +package com.eternalcode.economy.withdraw; + +import com.eternalcode.economy.account.Account; +import com.eternalcode.economy.account.AccountManager; +import com.eternalcode.economy.account.AccountPaymentService; +import com.eternalcode.economy.format.DecimalFormatter; +import com.eternalcode.economy.multification.NoticeService; +import java.math.BigDecimal; +import java.util.UUID; +import org.bukkit.Material; +import org.bukkit.Server; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +public class WithdrawService { + + private static final int MINIMUM_AMOUNT = 1; + + private final Server server; + private final NoticeService noticeService; + private final WithdrawItemService withdrawItemService; + private final DecimalFormatter decimalFormatter; + private final AccountPaymentService accountPaymentService; + private final AccountManager accountManager; + + public WithdrawService( + Server server, + NoticeService noticeService, + DecimalFormatter decimalFormatter, + WithdrawItemService withdrawItemService, + AccountPaymentService accountPaymentService, + AccountManager accountManager + ) { + this.server = server; + this.noticeService = noticeService; + this.decimalFormatter = decimalFormatter; + this.withdrawItemService = withdrawItemService; + this.accountPaymentService = accountPaymentService; + this.accountManager = accountManager; + } + + public void addBanknote(UUID uuid, BigDecimal value) { + if (value.compareTo(BigDecimal.ZERO) <= 0) { + throw new IllegalArgumentException("Banknote value must be positive, got: " + value); + } + + Player player = this.server.getPlayer(uuid); + if (player == null) { + return; + } + + if (player.getInventory().firstEmpty() == -1) { + this.noticeService.create() + .notice(messageConfig -> messageConfig.withdraw.noInventorySpace) + .player(player.getUniqueId()) + .send(); + return; + } + + ItemStack banknote = this.withdrawItemService.createBanknote(value); + player.getInventory().addItem(banknote); + + Account account = this.accountManager.getAccount(player.getUniqueId()); + this.accountPaymentService.removeBalance(account, value); + + this.noticeService.create() + .notice(messageConfig -> messageConfig.withdraw.banknoteWithdrawn) + .placeholder("{VALUE}", this.decimalFormatter.format(value)) + .player(player.getUniqueId()) + .send(); + } + + public void redeem(Player player, ItemStack item, BigDecimal value, int amount) { + if (amount < MINIMUM_AMOUNT) { + throw new IllegalArgumentException("Amount must be at least " + MINIMUM_AMOUNT + ", got: " + amount); + } + + if (value.compareTo(BigDecimal.ZERO) <= 0) { + throw new IllegalArgumentException("Banknote value must be positive, got: " + value); + } + + if (item.getType() == Material.AIR) { + this.noticeService.create() + .notice(messageConfig -> messageConfig.withdraw.noBanknoteInHand) + .player(player.getUniqueId()) + .send(); + return; + } + + if (item.getAmount() < amount) { + throw new IllegalArgumentException( + "Cannot redeem " + amount + " items when only " + item.getAmount() + " are available" + ); + } + + item.setAmount(item.getAmount() - amount); + + BigDecimal totalValue = value.multiply(BigDecimal.valueOf(amount)); + + Account account = this.accountManager.getAccount(player.getUniqueId()); + this.accountPaymentService.addBalance(account, totalValue); + + this.noticeService.create() + .notice(messageConfig -> messageConfig.withdraw.banknoteRedeemed) + .placeholder("{VALUE}", this.decimalFormatter.format(totalValue)) + .player(player.getUniqueId()) + .send(); + } +} diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/withdraw/controller/WithdrawAnvilController.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/withdraw/controller/WithdrawAnvilController.java new file mode 100644 index 00000000..19125bbb --- /dev/null +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/withdraw/controller/WithdrawAnvilController.java @@ -0,0 +1,98 @@ +package com.eternalcode.economy.withdraw.controller; + +import com.eternalcode.economy.multification.NoticeService; +import com.eternalcode.economy.withdraw.WithdrawItemService; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.InventoryAction; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryDragEvent; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +public class WithdrawAnvilController implements Listener { + + private final WithdrawItemService withdrawItemService; + private final NoticeService noticeService; + + public WithdrawAnvilController(WithdrawItemService withdrawItemService, NoticeService noticeService) { + this.withdrawItemService = withdrawItemService; + this.noticeService = noticeService; + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onAnvilClick(InventoryClickEvent event) { + Inventory topInventory = event.getInventory(); + + if (this.isRestrictedInventory(topInventory)) { + return; + } + + ItemStack clickedItem = event.getCurrentItem(); + ItemStack cursorItem = event.getCursor(); + + if (this.isBanknoteInteraction(clickedItem, cursorItem, event.getAction())) { + event.setCancelled(true); + event.getView().close(); + + this.noticeService.create() + .notice(messageConfig -> messageConfig.withdraw.invalidInteraction) + .viewer(event.getWhoClicked()) + .send(); + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onAnvilDrag(InventoryDragEvent event) { + Inventory inventory = event.getInventory(); + + if (this.isRestrictedInventory(inventory)) { + return; + } + + ItemStack draggedItem = event.getOldCursor(); + if (draggedItem == null || !this.withdrawItemService.isBanknote(draggedItem)) { + return; + } + + int inventorySize = inventory.getSize(); + boolean isDraggingToTopInventory = false; + + for (Integer slot : event.getRawSlots()) { + if (slot < inventorySize) { + isDraggingToTopInventory = true; + break; + } + } + + if (isDraggingToTopInventory) { + event.setCancelled(true); + event.getView().close(); + + this.noticeService.create() + .notice(messageConfig -> messageConfig.withdraw.invalidInteraction) + .viewer(event.getWhoClicked()) + .send(); + } + } + + private boolean isRestrictedInventory(Inventory inventory) { + InventoryType type = inventory.getType(); + return type != InventoryType.ANVIL && type != InventoryType.CRAFTING && type != InventoryType.WORKBENCH; + } + + private boolean isBanknoteInteraction(ItemStack clickedItem, ItemStack cursorItem, InventoryAction action) { + if (action == InventoryAction.MOVE_TO_OTHER_INVENTORY && this.withdrawItemService.isBanknote(clickedItem)) { + return true; + } + + if (action == InventoryAction.PLACE_ALL || action == InventoryAction.PLACE_ONE + || action == InventoryAction.PLACE_SOME) { + return this.withdrawItemService.isBanknote(cursorItem); + } + + return false; + } +} diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/withdraw/controller/WithdrawController.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/withdraw/controller/WithdrawController.java new file mode 100644 index 00000000..ee8a54e7 --- /dev/null +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/withdraw/controller/WithdrawController.java @@ -0,0 +1,57 @@ +package com.eternalcode.economy.withdraw.controller; + +import com.eternalcode.economy.withdraw.WithdrawItemService; +import com.eternalcode.economy.withdraw.WithdrawService; +import java.math.BigDecimal; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; + +public class WithdrawController implements Listener { + + private final WithdrawService withdrawService; + private final WithdrawItemService withdrawItemService; + + public WithdrawController(WithdrawService withdrawService, WithdrawItemService withdrawItemService) { + this.withdrawService = withdrawService; + this.withdrawItemService = withdrawItemService; + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = false) + public void onItemUse(PlayerInteractEvent event) { + Action action = event.getAction(); + if (action != Action.RIGHT_CLICK_AIR && action != Action.RIGHT_CLICK_BLOCK) { + return; + } + + Player player = event.getPlayer(); + EquipmentSlot hand = event.getHand(); + if (hand == null) { + return; + } + + ItemStack item = hand == EquipmentSlot.HAND + ? player.getInventory().getItemInMainHand() + : player.getInventory().getItemInOffHand(); + + if (item == null || item.getType() == Material.AIR) { + return; + } + + BigDecimal value = this.withdrawItemService.getValue(item); + if (value == null || value.compareTo(BigDecimal.ZERO) <= 0) { + return; + } + + event.setCancelled(true); + + int itemAmount = player.isSneaking() ? item.getAmount() : 1; + this.withdrawService.redeem(player, item, value, itemAmount); + } +}