From 82252e7b1dfbb27aa6742693c328dba5aad2d492 Mon Sep 17 00:00:00 2001 From: Martin Sulikowski Date: Wed, 21 Jan 2026 21:05:47 +0100 Subject: [PATCH 1/6] Add baltop GUI, use snapshot leaderboard. --- buildSrc/src/main/kotlin/Versions.kt | 7 +- eternaleconomy-core/build.gradle.kts | 4 + .../economy/EconomyBukkitPlugin.java | 61 ++++-- .../economy/MiniMessageHolder.java | 15 ++ .../database/AccountRepositoryImpl.java | 58 ++++-- ...{AccountWrapper.java => AccountTable.java} | 20 +- .../impl/admin/AdminGenerateCommand.java | 157 ++++++++++++++ .../economy/config/ConfigService.java | 1 + .../config/implementation/PluginConfig.java | 154 +++++++------- .../eternalcode/economy/gui/GuiAction.java | 28 +++ .../economy/gui/GuiItemBuilder.java | 77 +++++++ .../economy/gui/GuiItemFactory.java | 119 +++++++++++ .../economy/gui/GuiItemRepresenter.java | 52 +++++ .../leaderboard/LeaderboardCommand.java | 19 +- .../leaderboard/LeaderboardConfigurer.java | 63 ++++++ .../leaderboard/LeaderboardRefreshTask.java | 30 +++ .../leaderboard/LeaderboardService.java | 1 + .../leaderboard/LeaderboardServiceImpl.java | 115 +++++------ .../leaderboard/LeaderboardSnapshot.java | 30 +++ .../leaderboard/menu/LeaderboardConfig.java | 152 ++++++++++++++ .../menu/LeaderboardItemRepresenter.java | 119 +++++++++++ .../leaderboard/menu/LeaderboardMenu.java | 194 ++++++++++++++++++ 22 files changed, 1291 insertions(+), 185 deletions(-) create mode 100644 eternaleconomy-core/src/main/java/com/eternalcode/economy/MiniMessageHolder.java rename eternaleconomy-core/src/main/java/com/eternalcode/economy/account/database/{AccountWrapper.java => AccountTable.java} (57%) create mode 100644 eternaleconomy-core/src/main/java/com/eternalcode/economy/command/impl/admin/AdminGenerateCommand.java create mode 100644 eternaleconomy-core/src/main/java/com/eternalcode/economy/gui/GuiAction.java create mode 100644 eternaleconomy-core/src/main/java/com/eternalcode/economy/gui/GuiItemBuilder.java create mode 100644 eternaleconomy-core/src/main/java/com/eternalcode/economy/gui/GuiItemFactory.java create mode 100644 eternaleconomy-core/src/main/java/com/eternalcode/economy/gui/GuiItemRepresenter.java create mode 100644 eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/LeaderboardConfigurer.java create mode 100644 eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/LeaderboardRefreshTask.java create mode 100644 eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/LeaderboardSnapshot.java create mode 100644 eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/menu/LeaderboardConfig.java create mode 100644 eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/menu/LeaderboardItemRepresenter.java create mode 100644 eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/menu/LeaderboardMenu.java diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 53d3c0af..54d4d7a2 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -2,6 +2,8 @@ object Versions { const val PAPER_API = "1.20.4-R0.1-SNAPSHOT" + const val TRIUMPH_GUI = "3.1.13" + const val OKAERI_CONFIGS = "5.0.13" const val LITE_COMMANDS = "3.10.9" @@ -10,13 +12,12 @@ object Versions { const val JETBRAINS_ANNOTATIONS = "26.0.2-1" - const val ADVENTURE_PLATFORM_BUKKIT = "4.4.1" - const val ADVENTURE_API = "4.24.0" - const val VAULT_API = "1.7.1" const val PLACEHOLDER_API = "2.11.7" + const val LITE_SKULL_API = "2.0.0" + const val MARIA_DB = "3.5.7" const val POSTGRESQL = "42.7.9" const val H2 = "2.4.240" diff --git a/eternaleconomy-core/build.gradle.kts b/eternaleconomy-core/build.gradle.kts index 60fa35b6..47fb89dc 100644 --- a/eternaleconomy-core/build.gradle.kts +++ b/eternaleconomy-core/build.gradle.kts @@ -60,6 +60,10 @@ dependencies { compileOnly("me.clip:placeholderapi:${Versions.PLACEHOLDER_API}") + // TriumpGUI for GUI + implementation("dev.triumphteam:triumph-gui:${Versions.TRIUMPH_GUI}") + implementation("dev.rollczi:liteskullapi:${Versions.LITE_SKULL_API}") + testImplementation(platform("org.junit:junit-bom:6.0.2")) testImplementation("org.junit.jupiter:junit-jupiter") testRuntimeOnly("org.junit.platform:junit-platform-launcher") 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 c69e4f9c..6dd6efe4 100644 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/EconomyBukkitPlugin.java +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/EconomyBukkitPlugin.java @@ -22,6 +22,7 @@ import com.eternalcode.economy.command.impl.WithdrawCommand; import com.eternalcode.economy.command.impl.admin.AdminAddCommand; import com.eternalcode.economy.command.impl.admin.AdminBalanceCommand; +import com.eternalcode.economy.command.impl.admin.AdminGenerateCommand; import com.eternalcode.economy.command.impl.admin.AdminRemoveCommand; import com.eternalcode.economy.command.impl.admin.AdminResetCommand; import com.eternalcode.economy.command.impl.admin.AdminSetCommand; @@ -35,7 +36,7 @@ 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.leaderboard.LeaderboardConfigurer; import com.eternalcode.economy.multification.NoticeBroadcastHandler; import com.eternalcode.economy.multification.NoticeHandler; import com.eternalcode.economy.multification.NoticeService; @@ -51,6 +52,8 @@ import dev.rollczi.litecommands.bukkit.LiteBukkitFactory; import dev.rollczi.litecommands.jakarta.LiteJakartaExtension; import dev.rollczi.litecommands.message.LiteMessages; +import dev.rollczi.liteskullapi.LiteSkullFactory; +import dev.rollczi.liteskullapi.SkullAPI; import jakarta.validation.constraints.Min; import java.io.File; import java.math.BigDecimal; @@ -68,7 +71,7 @@ public class EconomyBukkitPlugin extends JavaPlugin { private static final String PLUGIN_STARTED = "EternalEconomy has been enabled in %dms."; private DatabaseManager databaseManager; - + private SkullAPI skullAPI; private LiteCommands liteCommands; @Override @@ -97,6 +100,11 @@ public void onEnable() { NoticeService noticeService = new NoticeService(messageConfig, miniMessage); + this.skullAPI = LiteSkullFactory.builder() + .cacheExpireAfterWrite(Duration.ofMinutes(45L)) + .bukkitScheduler(this) + .build(); + Scheduler scheduler = EconomySchedulerAdapter.getAdaptiveScheduler(this); this.databaseManager = new DatabaseManager(this.getLogger(), dataFolder, pluginConfig.database); @@ -132,13 +140,10 @@ public void onEnable() { Economy.class, vaultEconomyProvider, this, ServicePriority.Highest); - this.liteCommands = LiteBukkitFactory.builder("eternaleconomy", this, server) + var liteCommandsBuilder = LiteBukkitFactory.builder("eternaleconomy", this, server) .extension( new LiteJakartaExtension<>(), settings -> settings - .violationMessage( - Min.class, BigDecimal.class, - new InvalidBigDecimalMessage<>( - noticeService))) + .violationMessage(Min.class, BigDecimal.class, new InvalidBigDecimalMessage<>(noticeService))) .annotations(extension -> extension.validator( Account.class, @@ -162,35 +167,49 @@ public void onEnable() { .commands( new AdminAddCommand( accountPaymentService, decimalFormatter, - noticeService), + noticeService + ), new AdminRemoveCommand( accountPaymentService, decimalFormatter, - noticeService), + noticeService + ), new AdminSetCommand( accountPaymentService, decimalFormatter, - noticeService), + noticeService + ), new AdminResetCommand(accountPaymentService, noticeService), new AdminBalanceCommand(noticeService, decimalFormatter), + new AdminGenerateCommand(accountManager, scheduler), new WithdrawCommand( withdrawService, cooldownDuration, - noticeService), + noticeService + ), new MoneyBalanceCommand(noticeService, decimalFormatter), new MoneyTransferCommand( accountPaymentService, decimalFormatter, - noticeService, pluginConfig), - new EconomyReloadCommand(configService, noticeService), - new LeaderboardCommand( - noticeService, decimalFormatter, - accountManager.getLeaderboardService(), - pluginConfig)) + noticeService, pluginConfig + ), + new EconomyReloadCommand(configService, noticeService)) .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()) + .result(NoticeBroadcast.class, new NoticeBroadcastHandler()); - .build(); + new LeaderboardConfigurer().configure( + this, + accountManager.getLeaderboardService(), + scheduler, + configService, + pluginConfig, + noticeService, + decimalFormatter, + this.skullAPI, + liteCommandsBuilder + ); + + this.liteCommands = liteCommandsBuilder.build(); server.getPluginManager().registerEvents(new AccountController(accountManager), this); @@ -216,6 +235,10 @@ public void onEnable() { @Override public void onDisable() { + if (this.skullAPI != null) { + this.skullAPI.shutdown(); + } + if (this.liteCommands != null) { this.liteCommands.unregister(); } diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/MiniMessageHolder.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/MiniMessageHolder.java new file mode 100644 index 00000000..09e417cb --- /dev/null +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/MiniMessageHolder.java @@ -0,0 +1,15 @@ +package com.eternalcode.economy; + +import com.eternalcode.commons.adventure.AdventureLegacyColorPostProcessor; +import com.eternalcode.commons.adventure.AdventureLegacyColorPreProcessor; +import com.eternalcode.commons.adventure.AdventureUrlPostProcessor; +import net.kyori.adventure.text.minimessage.MiniMessage; + +public interface MiniMessageHolder { + + MiniMessage MINI_MESSAGE = MiniMessage.builder() + .postProcessor(new AdventureUrlPostProcessor()) + .postProcessor(new AdventureLegacyColorPostProcessor()) + .preProcessor(new AdventureLegacyColorPreProcessor()) + .build(); +} diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/account/database/AccountRepositoryImpl.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/account/database/AccountRepositoryImpl.java index 267197c5..54f16c9d 100644 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/account/database/AccountRepositoryImpl.java +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/account/database/AccountRepositoryImpl.java @@ -5,6 +5,7 @@ import com.eternalcode.economy.database.AbstractRepositoryOrmLite; import com.eternalcode.economy.database.DatabaseException; import com.eternalcode.economy.database.DatabaseManager; +import com.j256.ormlite.dao.Dao; import com.j256.ormlite.stmt.QueryBuilder; import com.j256.ormlite.stmt.Where; import com.j256.ormlite.table.TableUtils; @@ -22,37 +23,38 @@ public AccountRepositoryImpl( super(databaseManager, scheduler); try { - TableUtils.createTableIfNotExists(databaseManager.connectionSource(), AccountWrapper.class); + TableUtils.createTableIfNotExists(databaseManager.connectionSource(), AccountTable.class); + this.createIndexes(); } catch (SQLException exception) { - throw new DatabaseException("Failed to create table for AccountWrapper.", exception); + throw new DatabaseException("Failed to create table for AccountTable.", exception); } } @Override public CompletableFuture save(Account account) { - return this.save(AccountWrapper.class, AccountWrapper.fromAccount(account)).thenApply(status -> null); + return this.save(AccountTable.class, AccountTable.fromAccount(account)).thenApply(status -> null); } @Override public CompletableFuture delete(Account account) { - return this.delete(AccountWrapper.class, AccountWrapper.fromAccount(account)).thenApply(status -> null); + return this.delete(AccountTable.class, AccountTable.fromAccount(account)).thenApply(status -> null); } @Override public CompletableFuture> getAllAccounts() { - return this.selectAll(AccountWrapper.class) + return this.selectAll(AccountTable.class) .thenApply(accountWrappers -> accountWrappers.stream() - .map(accountWrapper -> accountWrapper.toAccount()) + .map(AccountTable::toAccount) .toList()); } @Override public CompletableFuture> getTopAccounts(int limit, int offset) { - return this.>action( - AccountWrapper.class, dao -> { - QueryBuilder queryBuilder = dao.queryBuilder(); - queryBuilder.orderBy("balance", false); - queryBuilder.orderBy("uuid", true); + return this.>action( + AccountTable.class, dao -> { + QueryBuilder queryBuilder = dao.queryBuilder(); + queryBuilder.orderBy(AccountTable.BALANCE, false); + queryBuilder.orderBy(AccountTable.UUID, true); if (limit > 0) { queryBuilder.limit((long) limit); @@ -63,23 +65,23 @@ public CompletableFuture> getTopAccounts(int limit, int offset) { } return queryBuilder.query().stream() - .map(AccountWrapper::toAccount) + .map(AccountTable::toAccount) .toList(); }); } @Override public CompletableFuture getPosition(Account target) { - return this.action( - AccountWrapper.class, dao -> { - QueryBuilder qb = dao.queryBuilder(); - Where where = qb.where(); + return this.action( + AccountTable.class, dao -> { + QueryBuilder qb = dao.queryBuilder(); + Where where = qb.where(); - where.gt("balance", target.balance()); + where.gt(AccountTable.BALANCE, target.balance()); where.or(); - where.eq("balance", target.balance()); + where.eq(AccountTable.BALANCE, target.balance()); where.and(); - where.lt("uuid", target.uuid()); + where.lt(AccountTable.UUID, target.uuid()); return (int) qb.countOf() + 1; }); @@ -87,6 +89,22 @@ public CompletableFuture getPosition(Account target) { @Override public CompletableFuture countAccounts() { - return this.action(AccountWrapper.class, dao -> dao.countOf()); + return this.action(AccountTable.class, Dao::countOf); + } + + /* + * ORMLite annotations (@DatabaseField(index = true)) do not support creating + * composite indexes with specific sort directions (ASC/DESC). + * For the leaderboard, we need an index on (balance DESC, uuid ASC) to + * efficiently query the top accounts without sorting the entire table. + * This optimization significantly improves performance for large datasets. + * 20.01.2026 - vlucky + */ + private void createIndexes() { + this.action(AccountTable.class, dao -> { + dao.executeRaw( + "CREATE INDEX IF NOT EXISTS idx_leaderboard_composite ON eternaleconomy_accounts(balance DESC, uuid ASC)"); + return null; + }); } } diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/account/database/AccountWrapper.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/account/database/AccountTable.java similarity index 57% rename from eternaleconomy-core/src/main/java/com/eternalcode/economy/account/database/AccountWrapper.java rename to eternaleconomy-core/src/main/java/com/eternalcode/economy/account/database/AccountTable.java index 62d45457..807b93d7 100644 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/account/database/AccountWrapper.java +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/account/database/AccountTable.java @@ -8,28 +8,32 @@ import java.util.UUID; @DatabaseTable(tableName = "eternaleconomy_accounts") -class AccountWrapper { +class AccountTable { - @DatabaseField(id = true) + static final String UUID = "uuid"; + static final String NAME = "name"; + static final String BALANCE = "balance"; + + @DatabaseField(id = true, unique = true, columnName = UUID) private UUID uuid; - @DatabaseField(index = true, unique = true) + @DatabaseField(index = true, unique = true, columnName = NAME) private String name; - @DatabaseField(dataType = DataType.BIG_DECIMAL_NUMERIC, index = true) + @DatabaseField(dataType = DataType.BIG_DECIMAL_NUMERIC, columnName = BALANCE) private BigDecimal balance; - public AccountWrapper() { + public AccountTable() { } - public AccountWrapper(UUID uuid, String name, BigDecimal balance) { + public AccountTable(UUID uuid, String name, BigDecimal balance) { this.uuid = uuid; this.name = name; this.balance = balance; } - public static AccountWrapper fromAccount(Account account) { - return new AccountWrapper(account.uuid(), account.name(), account.balance()); + public static AccountTable fromAccount(Account account) { + return new AccountTable(account.uuid(), account.name(), account.balance()); } public Account toAccount() { diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/impl/admin/AdminGenerateCommand.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/impl/admin/AdminGenerateCommand.java new file mode 100644 index 00000000..94114129 --- /dev/null +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/impl/admin/AdminGenerateCommand.java @@ -0,0 +1,157 @@ +package com.eternalcode.economy.command.impl.admin; + +import com.eternalcode.commons.scheduler.Scheduler; +import com.eternalcode.economy.EconomyPermissionConstant; +import com.eternalcode.economy.account.Account; +import com.eternalcode.economy.account.AccountManager; +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 java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.atomic.AtomicInteger; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.command.CommandSender; + +@Command(name = "economy generate", aliases = "eco generate") +@Permission(EconomyPermissionConstant.ADMIN_SET_PERMISSION) +public class AdminGenerateCommand { + + private static final String[] FIRST_NAMES = { + "Alex", "Jordan", "Taylor", "Morgan", "Casey", "Riley", "Avery", "Quinn", + "Blake", "Cameron", "Dakota", "Drew", "Hayden", "Jesse", "Kendall", "Logan", + "Parker", "Reese", "Skyler", "Tyler", "Rowan", "Sage", "River", "Phoenix", + "Charlie", "Finley", "Emerson", "Harper", "Kai", "Lennon", "Marley", "Oakley", + "Peyton", "Rory", "Sawyer", "Spencer", "Tatum", "Winter", "Aspen", "Blair", + "Ellis", "Ezra", "Gray", "Haven", "Jude", "Kit", "Lane", "Nova", + "Onyx", "Quinn", "Rain", "Salem", "True", "Vale", "Wren", "Zion", + "Azure", "Bay", "Cedar", "Echo", "Frost", "Harbor", "Indigo", "Jade" + }; + + private static final String[] SUFFIXES = { + "MC", "YT", "TV", "TTV", "HD", "Pro", "Gaming", "Plays", + "Live", "Stream", "King", "Lord", "Master", "Shadow", "Dark", "Red", + "Blue", "Gold", "Silver", "Epic", "Mega", "Ultra", "Super", "Hyper", + "Alpha", "Beta", "Omega", "Prime", "Elite", "Ace", "Champion", "Legend", + "Hero", "Warrior", "Knight", "Dragon", "Wolf", "Tiger", "Lion", "Bear", + "Fox", "Hawk", "Eagle", "Raven", "Storm", "Blaze", "Flame", "Frost", + "Night", "Star", "Moon", "Sun", "Sky", "Ocean", "Fire", "Ice", + "Thunder", "Lightning", "Nova", "Void", "Nexus", "Apex" + }; + + private final AccountManager accountManager; + private final Scheduler scheduler; + + public AdminGenerateCommand(AccountManager accountManager, Scheduler scheduler) { + this.accountManager = accountManager; + this.scheduler = scheduler; + } + + @Execute + void execute(@Context CommandSender sender, @Arg @Min(1) int count) { + if (count > 100000) { + sender.sendMessage(Component.text("Maximum count is 100,000", NamedTextColor.RED)); + return; + } + + sender.sendMessage(Component.text("Generating " + count + " accounts...", NamedTextColor.YELLOW)); + + AtomicInteger generated = new AtomicInteger(0); + long startTime = System.currentTimeMillis(); + + scheduler.runAsync(() -> { + List batch = new ArrayList<>(Math.min(1000, count)); + Random random = ThreadLocalRandom.current(); + + for (int i = 0; i < count; i++) { + String name = generateRealisticName(random); + UUID uuid = UUID.randomUUID(); + BigDecimal balance = generateRandomBalance(random); + + Account account = new Account(uuid, name, balance); + batch.add(account); + + if (batch.size() >= 1000) { + saveBatch(batch, generated); + batch.clear(); + + try { + Thread.sleep(10); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } + } + + if (!batch.isEmpty()) { + saveBatch(batch, generated); + } + + long elapsed = System.currentTimeMillis() - startTime; + scheduler.run(() -> { + sender.sendMessage(Component.text( + "Generated " + generated.get() + " accounts in " + elapsed + "ms", + NamedTextColor.GREEN + )); + }); + }); + } + + private void saveBatch(List batch, AtomicInteger counter) { + for (Account account : batch) { + accountManager.save(account); + counter.incrementAndGet(); + } + } + + private String generateRealisticName(Random random) { + String firstName = FIRST_NAMES[random.nextInt(FIRST_NAMES.length)]; + + int style = random.nextInt(100); + + if (style < 30) { + return firstName + random.nextInt(10000); + } else if (style < 60) { + String suffix = SUFFIXES[random.nextInt(SUFFIXES.length)]; + return firstName + suffix; + } else if (style < 80) { + String suffix = SUFFIXES[random.nextInt(SUFFIXES.length)]; + return firstName + suffix + random.nextInt(1000); + } else if (style < 90) { + return firstName + "_" + SUFFIXES[random.nextInt(SUFFIXES.length)]; + } else { + String second = FIRST_NAMES[random.nextInt(FIRST_NAMES.length)]; + return firstName + second; + } + } + + private BigDecimal generateRandomBalance(Random random) { + int distribution = random.nextInt(100); + + double rawBalance; + + if (distribution < 40) { + rawBalance = 1 + random.nextDouble() * 100; + } else if (distribution < 70) { + rawBalance = 100 + random.nextDouble() * 1000; + } else if (distribution < 85) { + rawBalance = 1000 + random.nextDouble() * 10000; + } else if (distribution < 95) { + rawBalance = 10000 + random.nextDouble() * 100000; + } else { + rawBalance = 100000 + random.nextDouble() * 900000; + } + + return BigDecimal.valueOf(rawBalance).setScale(2, RoundingMode.HALF_UP); + } +} 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 3efd7f7e..fc9e2191 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 @@ -30,6 +30,7 @@ public T create(Class config, File file) { NoticeResolverRegistry noticeRegistry = NoticeResolverDefaults.createRegistry() .registerResolver(new SoundBukkitResolver()); + configFile .withConfigurer(yamlConfigurer, new SerdesCommons(), new SerdesBukkit()) .withConfigurer(yamlConfigurer, new MultificationSerdesPack(noticeRegistry)) 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 3ff6b3f6..e4096e62 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 @@ -14,81 +14,87 @@ @SuppressWarnings({ "FieldMayBeFinal", "FieldCanBeLocal" }) public class PluginConfig extends OkaeriConfig { - @Comment("Units settings") - public Units units = new Units(); - - @Comment("Database settings") - public DatabaseConfig database = new DatabaseConfig(); - - @Comment("Default balance for new accounts") - public BigDecimal defaultBalance = BigDecimal.valueOf(0.0); - - @Comment("Limit on the amount of money sent in one transaction") - public BigDecimal transactionLimit = BigDecimal.valueOf(1_000_000_000.0); - - @Comment("Limit of the entries per leaderboard page") - public int leaderboardPageSize = 10; - - @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 = 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}$") + @Comment("Units settings") + public Units units = new Units(); + + @Comment("Database settings") + public DatabaseConfig database = new DatabaseConfig(); + + @Comment("Default balance for new accounts") + public BigDecimal defaultBalance = BigDecimal.valueOf(0.0); + + @Comment("Limit on the amount of money sent in one transaction") + public BigDecimal transactionLimit = BigDecimal.valueOf(1_000_000_000.0); + + @Comment("Limit of the entries per leaderboard page") + public int leaderboardPageSize = 10; + + @Comment("Should leaderboard command show player's position in the leaderboard") + public boolean showLeaderboardPosition = true; + + @Comment("Should leaderboard command show GUI instead of chat message") + public boolean showLeaderboardGui = true; + + @Comment("Interval for refreshing the leaderboard cache") + public java.time.Duration leaderboardRefreshInterval = java.time.Duration.ofSeconds(30); + + @Comment("Currency item settings") + public WithdrawItem withdraw = new WithdrawItem(); + + public static class Units extends OkaeriConfig { + + 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(100) + .withTexture(0) .withGlow(true) - .build())); - } + .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/gui/GuiAction.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/gui/GuiAction.java new file mode 100644 index 00000000..d02e9a77 --- /dev/null +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/gui/GuiAction.java @@ -0,0 +1,28 @@ +package com.eternalcode.economy.gui; + +/** + * Actions available for GUI items. + */ +public enum GuiAction { + + /** No action */ + NONE, + + /** Close GUI */ + CLOSE, + + /** Execute command as player */ + COMMAND, + + /** Execute command as console */ + CONSOLE_COMMAND, + + /** Go to next page (pagination) */ + NEXT_PAGE, + + /** Go to previous page (pagination) */ + PREVIOUS_PAGE, + + /** Refresh current GUI */ + REFRESH +} diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/gui/GuiItemBuilder.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/gui/GuiItemBuilder.java new file mode 100644 index 00000000..17f625d2 --- /dev/null +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/gui/GuiItemBuilder.java @@ -0,0 +1,77 @@ +package com.eternalcode.economy.gui; + +import java.util.ArrayList; +import java.util.List; +import org.bukkit.Material; + +/** + * Fluent builder for GuiItemRepresenter. + * Used for creating default values in configs. + */ +public final class GuiItemBuilder { + + private final GuiItemRepresenter item = new GuiItemRepresenter(); + + private GuiItemBuilder(String name) { + this.item.name = name; + } + + public static GuiItemBuilder item(String name) { + return new GuiItemBuilder(name); + } + + public GuiItemBuilder material(Material material) { + this.item.material = material; + return this; + } + + public GuiItemBuilder lore(String... lines) { + this.item.lore = List.of(lines); + return this; + } + + public GuiItemBuilder lore(List lines) { + this.item.lore = new ArrayList<>(lines); + return this; + } + + public GuiItemBuilder action(GuiAction action) { + this.item.action = action; + return this; + } + + public GuiItemBuilder action(GuiAction action, String command) { + this.item.action = action; + this.item.command = command; + return this; + } + + public GuiItemBuilder customModelData(int cmd) { + this.item.customModelData = cmd; + return this; + } + + public GuiItemBuilder amount(int amount) { + this.item.amount = amount; + return this; + } + + public GuiItemBuilder texture(String texture) { + this.item.texture = texture; + return this; + } + + public GuiItemBuilder glow(boolean glow) { + this.item.glow = glow; + return this; + } + + public GuiItemBuilder hideFlags(boolean hideFlags) { + this.item.hideFlags = hideFlags; + return this; + } + + public GuiItemRepresenter build() { + return this.item; + } +} diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/gui/GuiItemFactory.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/gui/GuiItemFactory.java new file mode 100644 index 00000000..2143e1e2 --- /dev/null +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/gui/GuiItemFactory.java @@ -0,0 +1,119 @@ +package com.eternalcode.economy.gui; + +import com.destroystokyo.paper.profile.PlayerProfile; +import com.destroystokyo.paper.profile.ProfileProperty; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.function.UnaryOperator; +import java.util.stream.Collectors; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.Registry; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.ItemFlag; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.SkullMeta; + +/** + * Factory for creating ItemStack from GuiItemRepresenter configuration. + */ +public class GuiItemFactory { + + private final MiniMessage miniMessage; + + public GuiItemFactory(MiniMessage miniMessage) { + this.miniMessage = miniMessage; + } + + /** + * Creates ItemStack from configuration. + */ + public ItemStack createItem(GuiItemRepresenter config) { + return createItem(config, Map.of()); + } + + /** + * Creates ItemStack from configuration with placeholders. + */ + public ItemStack createItem(GuiItemRepresenter config, Map placeholders) { + UnaryOperator replacer = text -> { + String result = text; + for (Map.Entry entry : placeholders.entrySet()) { + result = result.replace(entry.getKey(), entry.getValue()); + } + return result; + }; + + return createItem(config, replacer); + } + + /** + * Creates ItemStack from configuration with placeholder replacement function. + */ + public ItemStack createItem(GuiItemRepresenter config, UnaryOperator placeholderReplacer) { + ItemStack item = new ItemStack(config.material, config.amount); + ItemMeta meta = item.getItemMeta(); + + if (meta == null) { + return item; + } + + // Name + if (config.name != null && !config.name.isEmpty()) { + String processedName = placeholderReplacer.apply(config.name); + meta.displayName(this.miniMessage.deserialize(processedName)); + } + + // Lore + if (config.lore != null && !config.lore.isEmpty()) { + List loreComponents = config.lore.stream() + .map(placeholderReplacer) + .map(this.miniMessage::deserialize) + .collect(Collectors.toList()); + meta.lore(loreComponents); + } + + // Custom Model Data + if (config.customModelData != null) { + meta.setCustomModelData(config.customModelData); + } + + // Glow effect + if (config.glow) { + Enchantment unbreaking = Registry.ENCHANTMENT.get(org.bukkit.NamespacedKey.minecraft("unbreaking")); + if (unbreaking != null) { + meta.addEnchant(unbreaking, 1, true); + } + meta.addItemFlags(ItemFlag.HIDE_ENCHANTS); + } + + // Hide flags + if (config.hideFlags) { + meta.addItemFlags(ItemFlag.values()); + } + + // Head texture + if (config.material == Material.PLAYER_HEAD && config.texture != null) { + applyHeadTexture((SkullMeta) meta, config.texture); + } + + item.setItemMeta(meta); + return item; + } + + private void applyHeadTexture(SkullMeta meta, String texture) { + // Base64 texture (longer than typical player name) + if (texture.length() > 32) { + PlayerProfile profile = Bukkit.createProfile(UUID.randomUUID()); + profile.setProperty(new ProfileProperty("textures", texture)); + meta.setPlayerProfile(profile); + } else { + // Player name + meta.setOwningPlayer(Bukkit.getOfflinePlayer(texture)); + } + } +} diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/gui/GuiItemRepresenter.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/gui/GuiItemRepresenter.java new file mode 100644 index 00000000..64a89b84 --- /dev/null +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/gui/GuiItemRepresenter.java @@ -0,0 +1,52 @@ +package com.eternalcode.economy.gui; + +import eu.okaeri.configs.OkaeriConfig; +import eu.okaeri.configs.annotation.Comment; +import java.util.ArrayList; +import java.util.List; +import org.bukkit.Material; + +/** + * Representation of a GUI item in configuration. + * Serializable by OkaeriConfig. + */ +public class GuiItemRepresenter extends OkaeriConfig { + + @Comment("Item name (supports MiniMessage)") + public String name = ""; + + @Comment("Item material") + public Material material = Material.STONE; + + @Comment("Item lore (list of lines, supports MiniMessage)") + public List lore = new ArrayList<>(); + + @Comment("Action on click") + public GuiAction action = GuiAction.NONE; + + @Comment("Command to execute (for COMMAND/CONSOLE_COMMAND actions)") + public String command = ""; + + @Comment("Custom Model Data (for resource packs)") + public Integer customModelData = null; + + @Comment("Amount of items in stack") + public int amount = 1; + + @Comment("Player head texture (Base64 or player name)") + public String texture = null; + + @Comment("Should item glow (enchantment effect)") + public boolean glow = false; + + @Comment("Hide item flags (enchantments, attributes, etc.)") + public boolean hideFlags = true; + + public GuiItemRepresenter() { + } + + public GuiItemRepresenter(String name, Material material) { + this.name = name; + this.material = material; + } +} 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 80cebefb..84fd7dbc 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 @@ -4,6 +4,7 @@ import com.eternalcode.economy.account.Account; import com.eternalcode.economy.config.implementation.PluginConfig; import com.eternalcode.economy.format.DecimalFormatter; +import com.eternalcode.economy.leaderboard.menu.LeaderboardMenu; import com.eternalcode.economy.multification.NoticeService; import dev.rollczi.litecommands.annotations.argument.Arg; import dev.rollczi.litecommands.annotations.command.Command; @@ -13,9 +14,11 @@ import dev.rollczi.litecommands.annotations.permission.Permission; import jakarta.validation.constraints.Min; import java.util.List; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; @SuppressWarnings("unused") -@Command(name = "balancetop", aliases = {"baltop"}) +@Command(name = "balancetop", aliases = {"baltop", "btgui", "topgui"}) @Permission(EconomyPermissionConstant.PLAYER_BALANCE_TOP_PERMISSION) public class LeaderboardCommand { @@ -23,17 +26,20 @@ public class LeaderboardCommand { private final DecimalFormatter decimalFormatter; private final LeaderboardService leaderboardService; private final PluginConfig pluginConfig; + private final LeaderboardMenu leaderboardMenu; public LeaderboardCommand( NoticeService noticeService, DecimalFormatter decimalFormatter, LeaderboardService leaderboardService, - PluginConfig pluginConfig + PluginConfig pluginConfig, + LeaderboardMenu leaderboardMenu ) { this.noticeService = noticeService; this.decimalFormatter = decimalFormatter; this.leaderboardService = leaderboardService; this.pluginConfig = pluginConfig; + this.leaderboardMenu = leaderboardMenu; } @ExecuteDefault @@ -43,6 +49,15 @@ void executeDefault(@Context Account account) { @Execute void execute(@Context Account account, @Min(1) @Arg("page") int page) { + if (this.pluginConfig.showLeaderboardGui) { + Player bukkitPlayer = Bukkit.getPlayer(account.uuid()); + + if (bukkitPlayer != null) { + this.leaderboardMenu.open(bukkitPlayer, page); + return; + } + } + this.leaderboardService.getLeaderboardPage(page - 1, this.pluginConfig.leaderboardPageSize) .thenAccept(leaderboardPage -> showPage(account, leaderboardPage)); } diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/LeaderboardConfigurer.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/LeaderboardConfigurer.java new file mode 100644 index 00000000..5abcdc96 --- /dev/null +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/LeaderboardConfigurer.java @@ -0,0 +1,63 @@ +package com.eternalcode.economy.leaderboard; + +import com.eternalcode.commons.bukkit.scheduler.BukkitSchedulerImpl; +import com.eternalcode.commons.scheduler.Scheduler; +import com.eternalcode.economy.config.ConfigService; +import com.eternalcode.economy.config.implementation.PluginConfig; +import com.eternalcode.economy.format.DecimalFormatter; +import com.eternalcode.economy.leaderboard.menu.LeaderboardConfig; +import com.eternalcode.economy.leaderboard.menu.LeaderboardMenu; +import com.eternalcode.economy.multification.NoticeService; +import dev.rollczi.litecommands.LiteCommandsBuilder; +import dev.rollczi.litecommands.bukkit.LiteBukkitSettings; +import dev.rollczi.liteskullapi.SkullAPI; +import java.io.File; +import org.bukkit.command.CommandSender; +import org.bukkit.plugin.Plugin; + +public class LeaderboardConfigurer { + + public void configure( + Plugin plugin, + LeaderboardService leaderboardService, + Scheduler scheduler, + ConfigService configService, + PluginConfig pluginConfig, + NoticeService noticeService, + DecimalFormatter decimalFormatter, + SkullAPI skullAPI, + LiteCommandsBuilder liteCommandsBuilder + ) { + + LeaderboardRefreshTask refreshTask = new LeaderboardRefreshTask( + leaderboardService, scheduler, + pluginConfig + ); + refreshTask.start(); + + LeaderboardConfig leaderboardConfig = configService.create( + LeaderboardConfig.class, + new File(plugin.getDataFolder(), "baltop-gui.yml") + ); + + Scheduler bukkitScheduler = new BukkitSchedulerImpl(plugin); + + LeaderboardMenu leaderboardMenu = new LeaderboardMenu( + leaderboardConfig, + bukkitScheduler, + leaderboardService, + decimalFormatter, + skullAPI + ); + + liteCommandsBuilder.commands( + new LeaderboardCommand( + noticeService, + decimalFormatter, + leaderboardService, + pluginConfig, + leaderboardMenu + ) + ); + } +} diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/LeaderboardRefreshTask.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/LeaderboardRefreshTask.java new file mode 100644 index 00000000..aaa1f17c --- /dev/null +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/LeaderboardRefreshTask.java @@ -0,0 +1,30 @@ +package com.eternalcode.economy.leaderboard; + +import com.eternalcode.commons.concurrent.FutureHandler; +import com.eternalcode.commons.scheduler.Scheduler; +import com.eternalcode.economy.config.implementation.PluginConfig; +import java.time.Duration; + +public final class LeaderboardRefreshTask { + + private final LeaderboardService leaderboardService; + private final Scheduler scheduler; + private final PluginConfig pluginConfig; + + public LeaderboardRefreshTask( + LeaderboardService leaderboardService, + Scheduler scheduler, + PluginConfig pluginConfig) { + this.leaderboardService = leaderboardService; + this.scheduler = scheduler; + this.pluginConfig = pluginConfig; + } + + public void start() { + this.scheduler.timerAsync( + () -> this.leaderboardService.refreshSnapshot() + .exceptionally(FutureHandler::handleException), + Duration.ZERO, + this.pluginConfig.leaderboardRefreshInterval); + } +} diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/LeaderboardService.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/LeaderboardService.java index fdc71227..1488ae42 100644 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/LeaderboardService.java +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/LeaderboardService.java @@ -9,4 +9,5 @@ public interface LeaderboardService { CompletableFuture getLeaderboardPage(int page, int pageSize); + CompletableFuture refreshSnapshot(); } diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/LeaderboardServiceImpl.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/LeaderboardServiceImpl.java index 196df3db..e91e6424 100644 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/LeaderboardServiceImpl.java +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/LeaderboardServiceImpl.java @@ -2,22 +2,17 @@ import com.eternalcode.economy.account.Account; import com.eternalcode.economy.account.database.AccountRepository; -import com.github.benmanes.caffeine.cache.Cache; -import com.github.benmanes.caffeine.cache.Caffeine; -import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicReference; public class LeaderboardServiceImpl implements LeaderboardService { - private static final String CACHE_KEY = "top_leaderboard"; - private static final int TOP_CACHE_SIZE = 100; + private static final int SNAPSHOT_SIZE = 10_000; private final AccountRepository repository; - private final Cache> topCache = Caffeine.newBuilder() - .expireAfterWrite(Duration.ofSeconds(30)) - .build(); + private final AtomicReference snapshotRef = new AtomicReference<>(); public LeaderboardServiceImpl(AccountRepository repository) { this.repository = repository; @@ -25,82 +20,84 @@ public LeaderboardServiceImpl(AccountRepository repository) { @Override public CompletableFuture getLeaderboardPage(int page, int pageSize) { - int startIndex = page * pageSize; + int startIndex = (page - 1) * pageSize; - if (startIndex < TOP_CACHE_SIZE) { - return getPageFromCache(page, pageSize); + if (startIndex < SNAPSHOT_SIZE) { + return getPageFromSnapshot(page, pageSize, startIndex); } - return getPageFromDatabase(page, pageSize); + return getPageFromDatabaseKeyset(page, pageSize, startIndex); } @Override public CompletableFuture getLeaderboardPosition(Account target) { - return getOrRefreshTopCache().thenCompose(topAccounts -> { - int position = topAccounts.indexOf(target); + LeaderboardSnapshot snapshot = snapshotRef.get(); - if (position != -1) { - return CompletableFuture.completedFuture(new LeaderboardEntry(target, position + 1)); + if (snapshot != null) { + Integer position = snapshot.getPosition(target.uuid()); + if (position != null) { + return CompletableFuture.completedFuture(new LeaderboardEntry(target, position)); } + } - return calculatePositionFromAll(target); - }); + return repository.getPosition(target).thenApply(position -> new LeaderboardEntry(target, position)); } public void invalidateCache() { - this.topCache.invalidate(CACHE_KEY); + this.snapshotRef.set(null); } - private CompletableFuture getPageFromCache(int page, int pageSize) { - return getOrRefreshTopCache().thenCombine(this.repository.countAccounts(), (topAccounts, totalEntries) -> { - int startIndex = page * pageSize; - int endIndex = Math.min(startIndex + pageSize, topAccounts.size()); - - List entries = new ArrayList<>(); - for (int i = startIndex; i < endIndex; i++) { - entries.add(new LeaderboardEntry(topAccounts.get(i), i + 1)); - } - - int maxPages = Math.max(1, (int) Math.ceil((double) totalEntries / pageSize)); - int nextPage = page + 1 < maxPages ? page + 1 : -1; - - return new LeaderboardPage(entries, page, maxPages, nextPage); - }); + public CompletableFuture refreshSnapshot() { + return repository.getTopAccounts(SNAPSHOT_SIZE, 0).thenCombine( + repository.countAccounts(), (accounts, totalCount) -> { + LeaderboardSnapshot newSnapshot = new LeaderboardSnapshot(accounts, totalCount); + snapshotRef.set(newSnapshot); + return null; + }); } - private CompletableFuture getPageFromDatabase(int page, int pageSize) { - return repository.getTopAccounts(pageSize, page * pageSize) - .thenCompose(accounts -> repository.countAccounts().thenApply(totalEntries -> { - List entries = new ArrayList<>(); - int startPosition = page * pageSize; + private CompletableFuture getOrRefreshSnapshot() { + LeaderboardSnapshot current = snapshotRef.get(); + if (current != null) { + return CompletableFuture.completedFuture(current); + } - for (int i = 0; i < accounts.size(); i++) { - entries.add(new LeaderboardEntry(accounts.get(i), startPosition + i + 1)); - } + return repository.getTopAccounts(SNAPSHOT_SIZE, 0).thenCombine( + repository.countAccounts(), (accounts, totalCount) -> { + LeaderboardSnapshot newSnapshot = new LeaderboardSnapshot(accounts, totalCount); + snapshotRef.set(newSnapshot); + return newSnapshot; + }); + } - int maxPages = Math.max(1, (int) Math.ceil((double) totalEntries / pageSize)); - int nextPage = page + 1 < maxPages ? page + 1 : -1; + private CompletableFuture getPageFromSnapshot(int page, int pageSize, int startIndex) { + return getOrRefreshSnapshot().thenApply(snapshot -> { + int endIndex = Math.min(startIndex + pageSize, snapshot.size()); + List accounts = snapshot.accounts().subList(startIndex, endIndex); - return new LeaderboardPage(entries, page, maxPages, nextPage); - })); + return createPage(accounts, page, pageSize, startIndex, snapshot.totalCount()); + }); } - private CompletableFuture> getOrRefreshTopCache() { - List cached = topCache.getIfPresent(CACHE_KEY); + private CompletableFuture getPageFromDatabaseKeyset(int page, int pageSize, int startIndex) { + return getOrRefreshSnapshot().thenCompose(snapshot -> { + int offset = startIndex - SNAPSHOT_SIZE; + return repository.getTopAccounts(pageSize, SNAPSHOT_SIZE + offset) + .thenApply(accounts -> createPage(accounts, page, pageSize, startIndex, snapshot.totalCount())); + }); + } - if (cached != null) { - return CompletableFuture.completedFuture(cached); + private LeaderboardPage createPage( + List accounts, int page, int pageSize, int startIndex, + long totalCount) { + List entries = new ArrayList<>(accounts.size()); + for (int i = 0; i < accounts.size(); i++) { + entries.add(new LeaderboardEntry(accounts.get(i), startIndex + i + 1)); } - return repository.getTopAccounts(TOP_CACHE_SIZE, 0) - .thenApply(topAccounts -> { - topCache.put(CACHE_KEY, topAccounts); - return topAccounts; - }); - } + int maxPages = Math.max(1, (int) Math.ceil((double) totalCount / pageSize)); + int nextPage = page + 1 < maxPages ? page + 1 : -1; - private CompletableFuture calculatePositionFromAll(Account target) { - return repository.getPosition(target) - .thenApply(position -> new LeaderboardEntry(target, position)); + return new LeaderboardPage(entries, page, maxPages, nextPage); } } diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/LeaderboardSnapshot.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/LeaderboardSnapshot.java new file mode 100644 index 00000000..3f06db7c --- /dev/null +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/LeaderboardSnapshot.java @@ -0,0 +1,30 @@ +package com.eternalcode.economy.leaderboard; + +import com.eternalcode.economy.account.Account; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +record LeaderboardSnapshot(List accounts, long totalCount, Map positionIndex) { + + LeaderboardSnapshot(List accounts, long totalCount) { + this(List.copyOf(accounts), totalCount, buildPositionIndex(accounts)); + } + + private static Map buildPositionIndex(List accounts) { + Map index = new ConcurrentHashMap<>(accounts.size()); + for (int i = 0; i < accounts.size(); i++) { + index.put(accounts.get(i).uuid(), i + 1); + } + return index; + } + + Integer getPosition(UUID uuid) { + return this.positionIndex.get(uuid); + } + + int size() { + return this.accounts.size(); + } +} diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/menu/LeaderboardConfig.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/menu/LeaderboardConfig.java new file mode 100644 index 00000000..60e86d46 --- /dev/null +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/menu/LeaderboardConfig.java @@ -0,0 +1,152 @@ +package com.eternalcode.economy.leaderboard.menu; + +import eu.okaeri.configs.OkaeriConfig; +import eu.okaeri.configs.annotation.Comment; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import org.bukkit.Material; + +public class LeaderboardConfig extends OkaeriConfig { + + @Comment("GUI title (supports MiniMessage)") + public String title = + "ʙᴀʟᴛᴏᴘ - Page {CURRENT_PAGE}/{TOTAL_PAGES}"; + + @Comment("Number of GUI rows (1-6)") + public int rows = 6; + + @Comment("Player items configuration") + public PlayerItems playerItems = new PlayerItems(); + + @Comment("Pagination items") + public PaginationItems paginationItems = new PaginationItems(); + + @Comment("Fill settings") + public FillSettings fillSettings = new FillSettings(); + + public static class PlayerItems extends OkaeriConfig { + + @Comment({ + "Player item template", + "Placeholders: {POSITION}, {PLAYER}, {BALANCE}, {FORMATTED_BALANCE}" + }) + public LeaderboardItemRepresenter template = LeaderboardItemRepresenter.of( + Material.PLAYER_HEAD, + "#{POSITION} - {PLAYER}", + List.of( + "", + " Balance: {FORMATTED_BALANCE}", + "" + ), + false, + "{PLAYER_HEAD}", + 0 + ); + + @Comment("Slots for player items (0-53)") + public List slots = List.of( + 10, 11, 12, 13, 14, 15, 16, + 19, 20, 21, 22, 23, 24, 25, + 28, 29, 30, 31, 32, 33, 34, + 37, 38, 39, 40, 41, 42, 43); + } + + public static class LeaderboardItem extends OkaeriConfig { + public Material material; + public boolean glow; + public String texture; + + public LeaderboardItem() { + } + + public LeaderboardItem(Material material, boolean glow, String texture) { + this.material = material; + this.glow = glow; + this.texture = texture; + } + } + + public static class PaginationItems extends OkaeriConfig { + + @Comment("Previous page button") + public LeaderboardItemRepresenter previousPage = LeaderboardItemRepresenter.of( + Material.ARROW, + "← Previous page", + List.of( + "", + " Current page: {CURRENT_PAGE}", + "", + " Click to go back"), + false, + "none", + 48); + + @Comment("Previous page button (disabled)") + public LeaderboardItemRepresenter previousPageDisabled = LeaderboardItemRepresenter.of( + Material.GRAY_DYE, + "← Previous page", + List.of( + "", + " You are on the first page"), + false, + "none", + 48); + + @Comment("Next page button") + public LeaderboardItemRepresenter nextPage = LeaderboardItemRepresenter.of( + Material.ARROW, + "Next page →", + List.of( + "", + " Current page: {CURRENT_PAGE}", + "", + " Click to go forward"), + false, + "none", + 50); + + @Comment("Next page button (disabled)") + public LeaderboardItemRepresenter nextPageDisabled = LeaderboardItemRepresenter.of( + Material.GRAY_DYE, + "Next page →", + List.of( + "", + " No more pages"), + false, + "none", + 50); + + @Comment("Page info item") + public LeaderboardItemRepresenter pageInfo = LeaderboardItemRepresenter.of( + Material.PAPER, + "Page {CURRENT_PAGE}/{TOTAL_PAGES}", + List.of( + "", + " Total players: {TOTAL_PLAYERS}"), + false, + "none", + 49); + + @Comment("Close button") + public LeaderboardItemRepresenter closeButton = LeaderboardItemRepresenter.of( + Material.BARRIER, + "Close", + List.of( + "", + " Click to close"), + false, + "none", + 45); + } + + public static class FillSettings extends OkaeriConfig { + @Comment("Enable fill items") + public boolean enableFillItems = true; + + @Comment("Materials for fill items") + public List fillItems = List.of( + Material.BLACK_STAINED_GLASS_PANE, + Material.GRAY_STAINED_GLASS_PANE); + } +} diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/menu/LeaderboardItemRepresenter.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/menu/LeaderboardItemRepresenter.java new file mode 100644 index 00000000..271b0f41 --- /dev/null +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/menu/LeaderboardItemRepresenter.java @@ -0,0 +1,119 @@ +package com.eternalcode.economy.leaderboard.menu; + +import com.eternalcode.economy.MiniMessageHolder; +import com.eternalcode.multification.shared.Formatter; +import dev.rollczi.liteskullapi.SkullAPI; +import dev.triumphteam.gui.builder.item.ItemBuilder; +import dev.triumphteam.gui.components.GuiAction; +import dev.triumphteam.gui.guis.GuiItem; +import eu.okaeri.configs.annotation.Exclude; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.TextDecoration; +import org.bukkit.Material; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.inventory.ItemStack; + +public class LeaderboardItemRepresenter implements Serializable, MiniMessageHolder { + + @Exclude + private static final Component RESET_ITEM = Component.text() + .decoration(TextDecoration.ITALIC, false) + .build(); + + public Material material; + public String name; + public List lore; + public boolean glow; + public String texture; + public int slot; + + private LeaderboardItemRepresenter( + Material material, + String name, + List lore, + boolean glow, + String texture, + int slot + ) { + this.material = material; + this.name = name; + this.lore = lore; + this.glow = glow; + this.texture = texture; + this.slot = slot; + } + + public LeaderboardItemRepresenter() { + } + + public static LeaderboardItemRepresenter of( + Material material, + String name, + List lore, + boolean glow, + String texture, + int slot + ) { + return new LeaderboardItemRepresenter(material, name, lore, glow, texture, slot); + } + + public GuiItem asGuiItem(SkullAPI skullAPI, Formatter formatter, GuiAction action) { + return createGuiItem(skullAPI, formatter, action, null); + } + + public GuiItem asGuiItem(SkullAPI skullAPI, GuiAction action) { + return this.asGuiItem(skullAPI, new Formatter(), action); + } + + public GuiItem asGuiItemPreloaded( + Formatter formatter, + GuiAction action, + ItemStack preloadedSkull + ) { + return createGuiItem(null, formatter, action, preloadedSkull); + } + + private GuiItem createGuiItem( + SkullAPI skullAPI, + Formatter formatter, + GuiAction action, + ItemStack preloadedSkull + ) { + int size = this.lore.size(); + List loreComponents = new ArrayList<>(size); + + for (int i = 0; i < size; i++) { + String formatted = formatter.format(this.lore.get(i)); + loreComponents.add(RESET_ITEM.append(MINI_MESSAGE.deserialize(formatted))); + } + + Component nameComponent = RESET_ITEM.append(MINI_MESSAGE.deserialize(formatter.format(this.name))); + + ItemBuilder builder; + + if (preloadedSkull != null) { + builder = ItemBuilder.from(preloadedSkull); + } + else if (this.texture != null && !this.texture.equals("none") && skullAPI != null) { + builder = ItemBuilder.from(Material.PLAYER_HEAD); + } + else { + builder = ItemBuilder.from(this.material); + } + + builder.name(nameComponent) + .lore(loreComponents) + .glow(this.glow); + + GuiItem guiItem = builder.asGuiItem(action); + + if (preloadedSkull == null && this.texture != null && !this.texture.equals("none") && skullAPI != null) { + skullAPI.getSkull(this.texture).thenAccept(guiItem::setItemStack); + } + + return guiItem; + } +} diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/menu/LeaderboardMenu.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/menu/LeaderboardMenu.java new file mode 100644 index 00000000..1d3ba3c2 --- /dev/null +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/menu/LeaderboardMenu.java @@ -0,0 +1,194 @@ +package com.eternalcode.economy.leaderboard.menu; + +import com.eternalcode.commons.concurrent.FutureHandler; +import com.eternalcode.commons.scheduler.Scheduler; +import com.eternalcode.economy.MiniMessageHolder; +import com.eternalcode.economy.format.DecimalFormatter; +import com.eternalcode.economy.leaderboard.LeaderboardEntry; +import com.eternalcode.economy.leaderboard.LeaderboardPage; +import com.eternalcode.economy.leaderboard.LeaderboardService; +import com.eternalcode.economy.leaderboard.menu.LeaderboardConfig.LeaderboardItem; +import com.eternalcode.multification.shared.Formatter; +import dev.rollczi.liteskullapi.SkullAPI; +import dev.triumphteam.gui.builder.item.ItemBuilder; +import dev.triumphteam.gui.guis.Gui; +import dev.triumphteam.gui.guis.GuiItem; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.TextDecoration; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +public class LeaderboardMenu implements MiniMessageHolder { + + private static final Component RESET = Component.text().decoration(TextDecoration.ITALIC, false).build(); + + private final LeaderboardConfig config; + private final Scheduler scheduler; + private final LeaderboardService leaderboardService; + private final DecimalFormatter decimalFormatter; + private final SkullAPI skullAPI; + + public LeaderboardMenu( + LeaderboardConfig config, + Scheduler scheduler, + LeaderboardService leaderboardService, + DecimalFormatter decimalFormatter, + SkullAPI skullAPI + ) { + this.config = config; + this.scheduler = scheduler; + this.leaderboardService = leaderboardService; + this.decimalFormatter = decimalFormatter; + this.skullAPI = skullAPI; + } + + public void open(Player player, int page) { + int pageSize = config.playerItems.slots.size(); + + this.scheduler.runAsync(() -> + this.leaderboardService.getLeaderboardPage(page, pageSize) + .thenCompose(result -> preloadSkulls(result) + .thenAccept(skullCache -> scheduler.run(() -> { + Gui gui = createGui(player, result, skullCache); + gui.open(player); + })) + ) + .exceptionally(FutureHandler::handleException) + ); + } + + private CompletableFuture> preloadSkulls(LeaderboardPage page) { + List entries = page.entries(); + List> futures = new ArrayList<>(entries.size()); + + for (LeaderboardEntry entry : entries) { + LeaderboardItemRepresenter item = this.config.playerItems.template; + + String textureToUse = item.texture; + + if (textureToUse != null && textureToUse.equals("{PLAYER_HEAD}")) { + futures.add(this.skullAPI.getSkull(entry.account().uuid())); + } + else if (textureToUse != null && !textureToUse.equals("none")) { + futures.add(this.skullAPI.getSkull(textureToUse)); + } + else { + futures.add(CompletableFuture.completedFuture(null)); + } + } + + return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) + .thenApply(v -> { + List skulls = new ArrayList<>(futures.size()); + for (CompletableFuture future : futures) { + skulls.add(future.join()); + } + return skulls; + }); + } + + private Gui createGui(Player player, LeaderboardPage page, List skullCache) { + int current = page.currentPage(); + int max = page.maxPages(); + int pageSize = this.config.playerItems.slots.size(); + int players = page.maxPages() <= 1 + ? page.entries().size() + : (page.maxPages() - 1) * pageSize + page.entries().size(); + + Formatter formatter = new Formatter() + .register("{CURRENT_PAGE}", current) + .register("{TOTAL_PAGES}", max) + .register("{TOTAL_PLAYERS}", players); + + Component title = RESET.append( + MINI_MESSAGE.deserialize(formatter.format(this.config.title)) + ); + + Gui gui = Gui.gui() + .title(title) + .rows(this.config.rows) + .disableAllInteractions() + .create(); + + if (this.config.fillSettings.enableFillItems) { + gui.getFiller().fill( + this.config.fillSettings.fillItems.stream() + .map(ItemBuilder::from) + .map(ItemBuilder::asGuiItem) + .toList() + ); + } + + renderEntries(gui, page, skullCache); + renderPagination(gui, player, current, max, players); + + return gui; + } + + private void renderEntries(Gui gui, LeaderboardPage page, List skullCache) { + List slots = this.config.playerItems.slots; + List entries = page.entries(); + + for (int i = 0; i < Math.min(slots.size(), entries.size()); i++) { + LeaderboardEntry entry = entries.get(i); + int position = entry.position(); + int slot = slots.get(i); + ItemStack preloadedSkull = skullCache.get(i); + + String formattedBalance = this.decimalFormatter.format(entry.account().balance()); + LeaderboardItemRepresenter item = this.config.playerItems.template; + + Formatter formatter = new Formatter() + .register("{POSITION}", position) + .register("{PLAYER}", entry.account().name()) + .register("{BALANCE}", entry.account().balance()::toPlainString) + .register("{FORMATTED_BALANCE}", formattedBalance); + + GuiItem guiItem = item.asGuiItemPreloaded(formatter, event -> {}, preloadedSkull); + gui.setItem(slot, guiItem); + } + } + + private void renderPagination(Gui gui, Player player, int current, int max, int players) { + Formatter formatter = new Formatter() + .register("{CURRENT_PAGE}", current) + .register("{TOTAL_PAGES}", max) + .register("{TOTAL_PLAYERS}", players); + + LeaderboardItemRepresenter item1 = + current > 1 ? this.config.paginationItems.previousPage : this.config.paginationItems.previousPageDisabled; + GuiItem guiItem1 = item1.asGuiItem( + this.skullAPI, formatter, e2 -> { + if (!(current > 1)) { + return; + } + + ((Runnable) () -> open(player, current - 1)).run(); + }); + + gui.setItem(item1.slot, guiItem1); + + LeaderboardItemRepresenter item = + current < max ? this.config.paginationItems.nextPage : this.config.paginationItems.nextPageDisabled; + GuiItem guiItem = item.asGuiItem( + this.skullAPI, formatter, e1 -> { + if (!(current < max)) { + return; + } + + ((Runnable) () -> open(player, current + 1)).run(); + }); + + gui.setItem(item.slot, guiItem); + + gui.setItem( + this.config.paginationItems.pageInfo.slot, + this.config.paginationItems.pageInfo.asGuiItem(this.skullAPI, formatter, e -> {})); + + LeaderboardItemRepresenter close = this.config.paginationItems.closeButton; + gui.setItem(close.slot, close.asGuiItem(this.skullAPI, e -> gui.close(player))); + } +} From ca7710fd56f494961b2cad61040e4e721c51f157 Mon Sep 17 00:00:00 2001 From: Martin Sulikowski Date: Wed, 21 Jan 2026 21:11:58 +0100 Subject: [PATCH 2/6] Remove unused GUI-related classes, builder, and factory for streamlining the codebase. --- .../economy/EconomyBukkitPlugin.java | 5 +- .../eternalcode/economy/account/Account.java | 7 +- .../economy/account/AccountManager.java | 27 ++- .../database/AccountRepositoryImpl.java | 76 ++++---- .../database/AccountRepositoryInMemory.java | 18 +- .../economy/bridge/BridgeManager.java | 23 +-- .../PlaceholderEconomyExpansion.java | 6 +- .../cooldown/CommandCooldownConfig.java | 1 - .../cooldown/CommandCooldownEditor.java | 4 +- .../cooldown/CommandCooldownMessage.java | 7 +- .../economy/command/impl/WithdrawCommand.java | 1 - .../impl/admin/AdminGenerateCommand.java | 27 ++- .../command/impl/admin/AdminSetCommand.java | 1 - .../message/InvalidBigDecimalMessage.java | 3 +- .../config/implementation/CommandsConfig.java | 2 - .../config/implementation/PluginConfig.java | 171 +++++++++--------- .../messages/MessageAdminSubSection.java | 18 +- .../messages/MessagesPlayerSubSection.java | 42 +++-- .../economy/config/item/ConfigItem.java | 11 +- .../config/item/WithdrawItemEntry.java | 4 +- .../DatabaseConnectionDriverConstant.java | 7 +- .../economy/database/DatabaseDriverType.java | 11 +- .../com/eternalcode/economy/delay/Delay.java | 13 +- .../economy/delay/InstantExpiry.java | 28 +-- .../economy/format/DecimalFormatterImpl.java | 8 +- .../eternalcode/economy/gui/GuiAction.java | 28 --- .../economy/gui/GuiItemBuilder.java | 77 -------- .../economy/gui/GuiItemFactory.java | 119 ------------ .../economy/gui/GuiItemRepresenter.java | 52 ------ .../leaderboard/menu/LeaderboardConfig.java | 2 - .../leaderboard/menu/LeaderboardMenu.java | 1 - .../withdraw/WithdrawItemServiceImpl.java | 3 +- 32 files changed, 274 insertions(+), 529 deletions(-) delete mode 100644 eternaleconomy-core/src/main/java/com/eternalcode/economy/gui/GuiAction.java delete mode 100644 eternaleconomy-core/src/main/java/com/eternalcode/economy/gui/GuiItemBuilder.java delete mode 100644 eternaleconomy-core/src/main/java/com/eternalcode/economy/gui/GuiItemFactory.java delete mode 100644 eternaleconomy-core/src/main/java/com/eternalcode/economy/gui/GuiItemRepresenter.java 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 6dd6efe4..a52a38a4 100644 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/EconomyBukkitPlugin.java +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/EconomyBukkitPlugin.java @@ -49,7 +49,9 @@ import com.eternalcode.multification.notice.NoticeBroadcast; import com.google.common.base.Stopwatch; import dev.rollczi.litecommands.LiteCommands; +import dev.rollczi.litecommands.LiteCommandsBuilder; import dev.rollczi.litecommands.bukkit.LiteBukkitFactory; +import dev.rollczi.litecommands.bukkit.LiteBukkitSettings; import dev.rollczi.litecommands.jakarta.LiteJakartaExtension; import dev.rollczi.litecommands.message.LiteMessages; import dev.rollczi.liteskullapi.LiteSkullFactory; @@ -140,7 +142,8 @@ public void onEnable() { Economy.class, vaultEconomyProvider, this, ServicePriority.Highest); - var liteCommandsBuilder = LiteBukkitFactory.builder("eternaleconomy", this, server) + LiteCommandsBuilder + liteCommandsBuilder = LiteBukkitFactory.builder("eternaleconomy", this, server) .extension( new LiteJakartaExtension<>(), settings -> settings .violationMessage(Min.class, BigDecimal.class, new InvalidBigDecimalMessage<>(noticeService))) diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/account/Account.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/account/Account.java index b4d0543f..d2d092d5 100644 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/account/Account.java +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/account/Account.java @@ -17,7 +17,9 @@ public Account withBalance(UnaryOperator operator) { @Override public boolean equals(Object o) { - if (o == null || getClass() != o.getClass()) return false; + if (o == null || getClass() != o.getClass()) { + return false; + } Account account = (Account) o; return Objects.equals(uuid, account.uuid) && Objects.equals(name, account.name); } @@ -26,5 +28,4 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(uuid, name); } - -} \ No newline at end of file +} diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/account/AccountManager.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/account/AccountManager.java index 166c1ee5..2ce8a259 100644 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/account/AccountManager.java +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/account/AccountManager.java @@ -2,8 +2,8 @@ import com.eternalcode.economy.account.database.AccountRepository; import com.eternalcode.economy.config.implementation.PluginConfig; -import com.eternalcode.economy.leaderboard.LeaderboardServiceImpl; import com.eternalcode.economy.leaderboard.LeaderboardService; +import com.eternalcode.economy.leaderboard.LeaderboardServiceImpl; import java.math.BigDecimal; import java.util.Collection; import java.util.Collections; @@ -18,7 +18,7 @@ public class AccountManager { private final Map accountByUniqueId = new ConcurrentHashMap<>(); private final NavigableMap accountByName = new ConcurrentSkipListMap<>( - String.CASE_INSENSITIVE_ORDER); + String.CASE_INSENSITIVE_ORDER); private final AccountRepository accountRepository; private final LeaderboardServiceImpl leaderboardService; @@ -34,16 +34,16 @@ public static AccountManager create(AccountRepository accountRepository, PluginC AccountManager accountManager = new AccountManager(accountRepository, config, logger); accountRepository.getAllAccounts() - .thenAccept(accounts -> { - for (Account account : accounts) { - accountManager.save(account); - } - }) - .exceptionally(throwable -> { - logger.severe("Failed to load accounts: " + throwable.getMessage()); - throwable.printStackTrace(); - return null; - }); + .thenAccept(accounts -> { + for (Account account : accounts) { + accountManager.save(account); + } + }) + .exceptionally(throwable -> { + logger.severe("Failed to load accounts: " + throwable.getMessage()); + throwable.printStackTrace(); + return null; + }); return accountManager; } @@ -97,7 +97,7 @@ public void save(Account account) { public Collection getAccountStartingWith(String prefix) { return Collections.unmodifiableCollection( - this.accountByName.subMap(prefix, true, prefix + Character.MAX_VALUE, true).values()); + this.accountByName.subMap(prefix, true, prefix + Character.MAX_VALUE, true).values()); } public Collection getAccounts() { @@ -107,5 +107,4 @@ public Collection getAccounts() { public LeaderboardService getLeaderboardService() { return this.leaderboardService; } - } diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/account/database/AccountRepositoryImpl.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/account/database/AccountRepositoryImpl.java index 54f16c9d..18f3ec79 100644 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/account/database/AccountRepositoryImpl.java +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/account/database/AccountRepositoryImpl.java @@ -17,15 +17,14 @@ public class AccountRepositoryImpl extends AbstractRepositoryOrmLite implements AccountRepository { - public AccountRepositoryImpl( - DatabaseManager databaseManager, - Scheduler scheduler) { + public AccountRepositoryImpl(DatabaseManager databaseManager, Scheduler scheduler) { super(databaseManager, scheduler); try { TableUtils.createTableIfNotExists(databaseManager.connectionSource(), AccountTable.class); this.createIndexes(); - } catch (SQLException exception) { + } + catch (SQLException exception) { throw new DatabaseException("Failed to create table for AccountTable.", exception); } } @@ -43,48 +42,44 @@ public CompletableFuture delete(Account account) { @Override public CompletableFuture> getAllAccounts() { return this.selectAll(AccountTable.class) - .thenApply(accountWrappers -> accountWrappers.stream() - .map(AccountTable::toAccount) - .toList()); + .thenApply(accountWrappers -> accountWrappers.stream().map(AccountTable::toAccount).toList()); } @Override public CompletableFuture> getTopAccounts(int limit, int offset) { return this.>action( - AccountTable.class, dao -> { - QueryBuilder queryBuilder = dao.queryBuilder(); - queryBuilder.orderBy(AccountTable.BALANCE, false); - queryBuilder.orderBy(AccountTable.UUID, true); - - if (limit > 0) { - queryBuilder.limit((long) limit); - } - - if (offset > 0) { - queryBuilder.offset((long) offset); - } - - return queryBuilder.query().stream() - .map(AccountTable::toAccount) - .toList(); - }); + AccountTable.class, dao -> { + QueryBuilder queryBuilder = dao.queryBuilder(); + queryBuilder.orderBy(AccountTable.BALANCE, false); + queryBuilder.orderBy(AccountTable.UUID, true); + + if (limit > 0) { + queryBuilder.limit((long) limit); + } + + if (offset > 0) { + queryBuilder.offset((long) offset); + } + + return queryBuilder.query().stream().map(AccountTable::toAccount).toList(); + }); } @Override public CompletableFuture getPosition(Account target) { return this.action( - AccountTable.class, dao -> { - QueryBuilder qb = dao.queryBuilder(); - Where where = qb.where(); - - where.gt(AccountTable.BALANCE, target.balance()); - where.or(); - where.eq(AccountTable.BALANCE, target.balance()); - where.and(); - where.lt(AccountTable.UUID, target.uuid()); - - return (int) qb.countOf() + 1; - }); + AccountTable.class, dao -> { + QueryBuilder qb = dao.queryBuilder(); + Where where = qb.where(); + + where.gt(AccountTable.BALANCE, target.balance()); + where.or(); + where.eq(AccountTable.BALANCE, target.balance()); + where.and(); + where.lt(AccountTable.UUID, target.uuid()); + + return (int) qb.countOf() + 1; + }); } @Override @@ -101,10 +96,11 @@ public CompletableFuture countAccounts() { * 20.01.2026 - vlucky */ private void createIndexes() { - this.action(AccountTable.class, dao -> { - dao.executeRaw( + this.action( + AccountTable.class, dao -> { + dao.executeRaw( "CREATE INDEX IF NOT EXISTS idx_leaderboard_composite ON eternaleconomy_accounts(balance DESC, uuid ASC)"); - return null; - }); + return null; + }); } } diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/account/database/AccountRepositoryInMemory.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/account/database/AccountRepositoryInMemory.java index bf1e42ad..35dfd12b 100644 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/account/database/AccountRepositoryInMemory.java +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/account/database/AccountRepositoryInMemory.java @@ -33,11 +33,11 @@ public CompletableFuture> getAllAccounts() { @Override public CompletableFuture> getTopAccounts(int limit, int offset) { List sorted = accounts.values().stream() - .sorted(Comparator.comparing(Account::balance).reversed() - .thenComparing(Account::uuid)) - .skip(offset) - .limit(limit) - .toList(); + .sorted(Comparator.comparing(Account::balance).reversed() + .thenComparing(Account::uuid)) + .skip(offset) + .limit(limit) + .toList(); return CompletableFuture.completedFuture(sorted); } @@ -45,10 +45,10 @@ public CompletableFuture> getTopAccounts(int limit, int offset) { @Override public CompletableFuture getPosition(Account target) { long position = accounts.values().stream() - .sorted(Comparator.comparing(Account::balance).reversed() - .thenComparing(Account::uuid)) - .takeWhile(account -> !account.uuid().equals(target.uuid())) - .count() + 1; + .sorted(Comparator.comparing(Account::balance).reversed() + .thenComparing(Account::uuid)) + .takeWhile(account -> !account.uuid().equals(target.uuid())) + .count() + 1; return CompletableFuture.completedFuture((int) position); } 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 9e1d5f54..73ca4b43 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 @@ -8,7 +8,6 @@ import org.bukkit.Bukkit; import org.bukkit.Server; import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.PluginDescriptionFile; import org.bukkit.plugin.PluginManager; public class BridgeManager { @@ -42,19 +41,21 @@ public void init() { // Using "load: STARTUP" in plugin.yml causes the plugin to load before PlaceholderAPI. // Therefore, we need to delay the bridge initialization until the server is fully started. // The scheduler runs the code after the "Done" message, ensuring the server is fully operational. - Bukkit.getScheduler().runTask(this.plugin, () -> { - this.setupBridge("PlaceholderAPI", () -> { - PlaceholderEconomyExpansion placeholderEconomyExpansion = new PlaceholderEconomyExpansion( - this.pluginMeta, - this.accountManager, - this.decimalFormatter - ); + Bukkit.getScheduler().runTask( + this.plugin, () -> { + this.setupBridge( + "PlaceholderAPI", () -> { + PlaceholderEconomyExpansion placeholderEconomyExpansion = new PlaceholderEconomyExpansion( + this.pluginMeta, + this.accountManager, + this.decimalFormatter + ); - placeholderEconomyExpansion.register(); + placeholderEconomyExpansion.register(); - System.out.println("PlaceholderAPI bridge initialized!"); + System.out.println("PlaceholderAPI bridge initialized!"); + }); }); - }); // other bridges (do not put bridges in the scheduler if not needed) } 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 72dfd8af..d05092b5 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 @@ -18,9 +18,9 @@ public class PlaceholderEconomyExpansion extends PlaceholderExpansion implements private final DecimalFormatter decimalFormatter; public PlaceholderEconomyExpansion( - PluginMeta pluginMeta, - AccountManager accountManager, - DecimalFormatter decimalFormatter) { + PluginMeta pluginMeta, + AccountManager accountManager, + DecimalFormatter decimalFormatter) { this.pluginMeta = pluginMeta; this.accountManager = accountManager; this.decimalFormatter = decimalFormatter; diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/cooldown/CommandCooldownConfig.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/cooldown/CommandCooldownConfig.java index bfedef9a..7198c34f 100644 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/cooldown/CommandCooldownConfig.java +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/cooldown/CommandCooldownConfig.java @@ -3,7 +3,6 @@ import com.eternalcode.multification.notice.Notice; import eu.okaeri.configs.OkaeriConfig; import eu.okaeri.configs.annotation.Comment; - import java.time.Duration; public class CommandCooldownConfig extends OkaeriConfig { diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/cooldown/CommandCooldownEditor.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/cooldown/CommandCooldownEditor.java index 884d47e6..48221c2e 100644 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/cooldown/CommandCooldownEditor.java +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/cooldown/CommandCooldownEditor.java @@ -5,9 +5,8 @@ import dev.rollczi.litecommands.cooldown.CooldownContext; import dev.rollczi.litecommands.editor.Editor; import dev.rollczi.litecommands.meta.Meta; -import org.bukkit.command.CommandSender; - import java.util.Map; +import org.bukkit.command.CommandSender; public class CommandCooldownEditor implements Editor { @@ -37,5 +36,4 @@ public CommandBuilder edit(CommandBuilder commandB return commandBuilder; } - } 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 6a55f010..1e3e5d8f 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 @@ -7,9 +7,8 @@ import dev.rollczi.litecommands.message.InvokedMessage; import dev.rollczi.litecommands.message.LiteMessages; import dev.rollczi.litecommands.time.DurationParser; -import org.bukkit.command.CommandSender; - import java.time.Duration; +import org.bukkit.command.CommandSender; public class CommandCooldownMessage implements InvokedMessage { @@ -29,12 +28,12 @@ public Object get(Invocation invocation, CooldownState cooldownSt return LiteMessages.COMMAND_COOLDOWN.getDefaultMessage(cooldownState); } - String formatted = DurationParser.TIME_UNITS.format(Duration.ofSeconds(cooldownState.getRemainingDuration().getSeconds())); + String formatted = + DurationParser.TIME_UNITS.format(Duration.ofSeconds(cooldownState.getRemainingDuration().getSeconds())); return noticeService.create() .notice(notice -> cooldown.message) .placeholder("{TIME}", formatted) .viewer(invocation.sender()); } - } 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 index aa4cd886..00ca1f3c 100644 --- 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 @@ -11,7 +11,6 @@ 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; diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/impl/admin/AdminGenerateCommand.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/impl/admin/AdminGenerateCommand.java index 94114129..ee04fb38 100644 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/impl/admin/AdminGenerateCommand.java +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/impl/admin/AdminGenerateCommand.java @@ -86,7 +86,8 @@ void execute(@Context CommandSender sender, @Arg @Min(1) int count) { try { Thread.sleep(10); - } catch (InterruptedException e) { + } + catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } @@ -121,15 +122,19 @@ private String generateRealisticName(Random random) { if (style < 30) { return firstName + random.nextInt(10000); - } else if (style < 60) { + } + else if (style < 60) { String suffix = SUFFIXES[random.nextInt(SUFFIXES.length)]; return firstName + suffix; - } else if (style < 80) { + } + else if (style < 80) { String suffix = SUFFIXES[random.nextInt(SUFFIXES.length)]; return firstName + suffix + random.nextInt(1000); - } else if (style < 90) { + } + else if (style < 90) { return firstName + "_" + SUFFIXES[random.nextInt(SUFFIXES.length)]; - } else { + } + else { String second = FIRST_NAMES[random.nextInt(FIRST_NAMES.length)]; return firstName + second; } @@ -142,13 +147,17 @@ private BigDecimal generateRandomBalance(Random random) { if (distribution < 40) { rawBalance = 1 + random.nextDouble() * 100; - } else if (distribution < 70) { + } + else if (distribution < 70) { rawBalance = 100 + random.nextDouble() * 1000; - } else if (distribution < 85) { + } + else if (distribution < 85) { rawBalance = 1000 + random.nextDouble() * 10000; - } else if (distribution < 95) { + } + else if (distribution < 95) { rawBalance = 10000 + random.nextDouble() * 100000; - } else { + } + else { rawBalance = 100000 + random.nextDouble() * 900000; } 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 1aac9574..f43e650d 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 @@ -58,5 +58,4 @@ private void setAccountBalance(CommandSender sender, Account receiver, BigDecima .player(receiver.uuid()) .send(); } - } diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/message/InvalidBigDecimalMessage.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/message/InvalidBigDecimalMessage.java index bf19d6f0..f101305d 100644 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/message/InvalidBigDecimalMessage.java +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/message/InvalidBigDecimalMessage.java @@ -8,7 +8,8 @@ import java.math.BigDecimal; import org.bukkit.command.CommandSender; -public class InvalidBigDecimalMessage implements InvokedMessage> { +public class InvalidBigDecimalMessage + implements InvokedMessage> { private final NoticeService noticeService; diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/config/implementation/CommandsConfig.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/config/implementation/CommandsConfig.java index a78c9db0..1d0aec8c 100644 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/config/implementation/CommandsConfig.java +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/config/implementation/CommandsConfig.java @@ -3,7 +3,6 @@ import com.eternalcode.economy.command.cooldown.CommandCooldownConfig; import eu.okaeri.configs.OkaeriConfig; import eu.okaeri.configs.annotation.Comment; - import java.util.Map; public class CommandsConfig extends OkaeriConfig { @@ -15,5 +14,4 @@ public class CommandsConfig extends OkaeriConfig { public Map cooldowns = Map.of( "pay", new CommandCooldownConfig() ); - } 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 e4096e62..d6b4071a 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 @@ -6,95 +6,94 @@ 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.List; +import org.bukkit.Material; -@SuppressWarnings({ "FieldMayBeFinal", "FieldCanBeLocal" }) +@SuppressWarnings({"FieldMayBeFinal", "FieldCanBeLocal"}) public class PluginConfig extends OkaeriConfig { - @Comment("Units settings") - public Units units = new Units(); - - @Comment("Database settings") - public DatabaseConfig database = new DatabaseConfig(); - - @Comment("Default balance for new accounts") - public BigDecimal defaultBalance = BigDecimal.valueOf(0.0); - - @Comment("Limit on the amount of money sent in one transaction") - public BigDecimal transactionLimit = BigDecimal.valueOf(1_000_000_000.0); - - @Comment("Limit of the entries per leaderboard page") - public int leaderboardPageSize = 10; - - @Comment("Should leaderboard command show player's position in the leaderboard") - public boolean showLeaderboardPosition = true; - - @Comment("Should leaderboard command show GUI instead of chat message") - public boolean showLeaderboardGui = true; - - @Comment("Interval for refreshing the leaderboard cache") - public java.time.Duration leaderboardRefreshInterval = java.time.Duration.ofSeconds(30); - - @Comment("Currency item settings") - public WithdrawItem withdraw = new WithdrawItem(); - - public static class Units extends OkaeriConfig { - - 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())); - } + @Comment("Units settings") + public Units units = new Units(); + + @Comment("Database settings") + public DatabaseConfig database = new DatabaseConfig(); + + @Comment("Default balance for new accounts") + public BigDecimal defaultBalance = BigDecimal.valueOf(0.0); + + @Comment("Limit on the amount of money sent in one transaction") + public BigDecimal transactionLimit = BigDecimal.valueOf(1_000_000_000.0); + + @Comment("Limit of the entries per leaderboard page") + public int leaderboardPageSize = 10; + + @Comment("Should leaderboard command show player's position in the leaderboard") + public boolean showLeaderboardPosition = true; + + @Comment("Should leaderboard command show GUI instead of chat message") + public boolean showLeaderboardGui = true; + + @Comment("Interval for refreshing the leaderboard cache") + public java.time.Duration leaderboardRefreshInterval = java.time.Duration.ofSeconds(30); + + @Comment("Currency item settings") + public WithdrawItem withdraw = new WithdrawItem(); + + public static class Units extends OkaeriConfig { + + 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 7805414d..4ce52cca 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,13 +8,19 @@ 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/MessagesPlayerSubSection.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/config/implementation/messages/MessagesPlayerSubSection.java index 9d3f293a..347cc7ec 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,23 +6,34 @@ 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", @@ -31,7 +42,8 @@ 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", @@ -46,9 +58,11 @@ public class MessagesPlayerSubSection extends OkaeriConfig { "Use {PAGE} placeholder to show the current page number" }) public Notice leaderboardFooter = Notice.builder() - .chat(" Click Go to page {NEXT_PAGE}'>/baltop {NEXT_PAGE} to go to the next page.") + .chat( + " Click Go to page {NEXT_PAGE}'>/baltop {NEXT_PAGE} to go to the next page.") .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 index 6ea1dff7..bf82ea47 100644 --- 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 @@ -1,17 +1,16 @@ 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; + private final String name; + private final List lore; + private final Material material; + private final Integer texture; + private final boolean glow; public ConfigItem(String name, List lore, Material material, Integer texture, boolean glow) { this.name = name; 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 index af85e9bc..50a73997 100644 --- 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 @@ -7,8 +7,8 @@ public class WithdrawItemEntry extends OkaeriConfig { - private BigDecimal minValue; - private ConfigItem item; + private final BigDecimal minValue; + private final ConfigItem item; public WithdrawItemEntry(BigDecimal minValue, ConfigItem item) { this.minValue = minValue; diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/database/DatabaseConnectionDriverConstant.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/database/DatabaseConnectionDriverConstant.java index 19cf0740..e4656d31 100644 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/database/DatabaseConnectionDriverConstant.java +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/database/DatabaseConnectionDriverConstant.java @@ -2,27 +2,22 @@ final class DatabaseConnectionDriverConstant { - private DatabaseConnectionDriverConstant() {} - // mysql static final String MYSQL_DRIVER = "com.mysql.cj.jdbc.Driver"; static final String MYSQL_JDBC_URL = "jdbc:mysql://%s:%s/%s?sslMode=%s"; - // mariadb — accepts "true"/"false" as valid sslMode values (aliases for "verify-full" and "disable") static final String MARIADB_DRIVER = "org.mariadb.jdbc.Driver"; static final String MARIADB_JDBC_URL = "jdbc:mariadb://%s:%s/%s?sslMode=%s"; - // h2 static final String H2_DRIVER = "org.h2.Driver"; static final String H2_JDBC_URL = "jdbc:h2:./%s/database"; - // sqlite static final String SQLITE_DRIVER = "org.sqlite.JDBC"; static final String SQLITE_JDBC_URL = "jdbc:sqlite:%s/database.db"; - // postgresql static final String POSTGRESQL_DRIVER = "org.postgresql.Driver"; static final String POSTGRESQL_JDBC_URL = "jdbc:postgresql://%s:%s/%s?sslmode=%s"; + private DatabaseConnectionDriverConstant() {} static String sslParamForMySQL(boolean enabled) { return enabled ? "REQUIRED" : "DISABLED"; diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/database/DatabaseDriverType.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/database/DatabaseDriverType.java index 8a795286..444841b6 100644 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/database/DatabaseDriverType.java +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/database/DatabaseDriverType.java @@ -1,6 +1,15 @@ package com.eternalcode.economy.database; -import static com.eternalcode.economy.database.DatabaseConnectionDriverConstant.*; +import static com.eternalcode.economy.database.DatabaseConnectionDriverConstant.H2_DRIVER; +import static com.eternalcode.economy.database.DatabaseConnectionDriverConstant.H2_JDBC_URL; +import static com.eternalcode.economy.database.DatabaseConnectionDriverConstant.MARIADB_DRIVER; +import static com.eternalcode.economy.database.DatabaseConnectionDriverConstant.MARIADB_JDBC_URL; +import static com.eternalcode.economy.database.DatabaseConnectionDriverConstant.MYSQL_DRIVER; +import static com.eternalcode.economy.database.DatabaseConnectionDriverConstant.MYSQL_JDBC_URL; +import static com.eternalcode.economy.database.DatabaseConnectionDriverConstant.POSTGRESQL_DRIVER; +import static com.eternalcode.economy.database.DatabaseConnectionDriverConstant.POSTGRESQL_JDBC_URL; +import static com.eternalcode.economy.database.DatabaseConnectionDriverConstant.SQLITE_DRIVER; +import static com.eternalcode.economy.database.DatabaseConnectionDriverConstant.SQLITE_JDBC_URL; public enum DatabaseDriverType { 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 index 6fa20dc0..47b51dcd 100644 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/delay/Delay.java +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/delay/Delay.java @@ -2,7 +2,6 @@ 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; @@ -19,8 +18,12 @@ private Delay(Supplier defaultDelay) { this.defaultDelay = defaultDelay; this.cache = Caffeine.newBuilder() - .expireAfter(new InstantExpiry()) - .build(); + .expireAfter(new InstantExpiry()) + .build(); + } + + public static Delay withDefault(Supplier defaultDelay) { + return new Delay<>(defaultDelay); } public void markDelay(T key, Duration delay) { @@ -51,8 +54,4 @@ public Duration getRemaining(T 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 index 7618fd02..0e6acde2 100644 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/delay/InstantExpiry.java +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/delay/InstantExpiry.java @@ -6,6 +6,20 @@ class InstantExpiry implements Expiry { + 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; + } + @Override public long expireAfterCreate(T key, Instant expireTime, long currentTime) { return timeToExpire(expireTime); @@ -20,18 +34,4 @@ public long expireAfterUpdate(T key, Instant newExpireTime, long currentTime, lo 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 fa77835d..57f87ae8 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 @@ -46,7 +46,7 @@ private static String getTruncatedAmount(double amount) { fractionalPart *= 100; fractionalPart = (fractionalPart < 99 && fractionalPart % 1 >= 0.5) ? ceil(fractionalPart) - : floor(fractionalPart); + : floor(fractionalPart); return (long) amount + TRUNCATED_AMOUNT_DELIMITER + (long) fractionalPart; } @@ -67,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/gui/GuiAction.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/gui/GuiAction.java deleted file mode 100644 index d02e9a77..00000000 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/gui/GuiAction.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.eternalcode.economy.gui; - -/** - * Actions available for GUI items. - */ -public enum GuiAction { - - /** No action */ - NONE, - - /** Close GUI */ - CLOSE, - - /** Execute command as player */ - COMMAND, - - /** Execute command as console */ - CONSOLE_COMMAND, - - /** Go to next page (pagination) */ - NEXT_PAGE, - - /** Go to previous page (pagination) */ - PREVIOUS_PAGE, - - /** Refresh current GUI */ - REFRESH -} diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/gui/GuiItemBuilder.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/gui/GuiItemBuilder.java deleted file mode 100644 index 17f625d2..00000000 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/gui/GuiItemBuilder.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.eternalcode.economy.gui; - -import java.util.ArrayList; -import java.util.List; -import org.bukkit.Material; - -/** - * Fluent builder for GuiItemRepresenter. - * Used for creating default values in configs. - */ -public final class GuiItemBuilder { - - private final GuiItemRepresenter item = new GuiItemRepresenter(); - - private GuiItemBuilder(String name) { - this.item.name = name; - } - - public static GuiItemBuilder item(String name) { - return new GuiItemBuilder(name); - } - - public GuiItemBuilder material(Material material) { - this.item.material = material; - return this; - } - - public GuiItemBuilder lore(String... lines) { - this.item.lore = List.of(lines); - return this; - } - - public GuiItemBuilder lore(List lines) { - this.item.lore = new ArrayList<>(lines); - return this; - } - - public GuiItemBuilder action(GuiAction action) { - this.item.action = action; - return this; - } - - public GuiItemBuilder action(GuiAction action, String command) { - this.item.action = action; - this.item.command = command; - return this; - } - - public GuiItemBuilder customModelData(int cmd) { - this.item.customModelData = cmd; - return this; - } - - public GuiItemBuilder amount(int amount) { - this.item.amount = amount; - return this; - } - - public GuiItemBuilder texture(String texture) { - this.item.texture = texture; - return this; - } - - public GuiItemBuilder glow(boolean glow) { - this.item.glow = glow; - return this; - } - - public GuiItemBuilder hideFlags(boolean hideFlags) { - this.item.hideFlags = hideFlags; - return this; - } - - public GuiItemRepresenter build() { - return this.item; - } -} diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/gui/GuiItemFactory.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/gui/GuiItemFactory.java deleted file mode 100644 index 2143e1e2..00000000 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/gui/GuiItemFactory.java +++ /dev/null @@ -1,119 +0,0 @@ -package com.eternalcode.economy.gui; - -import com.destroystokyo.paper.profile.PlayerProfile; -import com.destroystokyo.paper.profile.ProfileProperty; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.function.UnaryOperator; -import java.util.stream.Collectors; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.minimessage.MiniMessage; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.Registry; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.inventory.ItemFlag; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.inventory.meta.SkullMeta; - -/** - * Factory for creating ItemStack from GuiItemRepresenter configuration. - */ -public class GuiItemFactory { - - private final MiniMessage miniMessage; - - public GuiItemFactory(MiniMessage miniMessage) { - this.miniMessage = miniMessage; - } - - /** - * Creates ItemStack from configuration. - */ - public ItemStack createItem(GuiItemRepresenter config) { - return createItem(config, Map.of()); - } - - /** - * Creates ItemStack from configuration with placeholders. - */ - public ItemStack createItem(GuiItemRepresenter config, Map placeholders) { - UnaryOperator replacer = text -> { - String result = text; - for (Map.Entry entry : placeholders.entrySet()) { - result = result.replace(entry.getKey(), entry.getValue()); - } - return result; - }; - - return createItem(config, replacer); - } - - /** - * Creates ItemStack from configuration with placeholder replacement function. - */ - public ItemStack createItem(GuiItemRepresenter config, UnaryOperator placeholderReplacer) { - ItemStack item = new ItemStack(config.material, config.amount); - ItemMeta meta = item.getItemMeta(); - - if (meta == null) { - return item; - } - - // Name - if (config.name != null && !config.name.isEmpty()) { - String processedName = placeholderReplacer.apply(config.name); - meta.displayName(this.miniMessage.deserialize(processedName)); - } - - // Lore - if (config.lore != null && !config.lore.isEmpty()) { - List loreComponents = config.lore.stream() - .map(placeholderReplacer) - .map(this.miniMessage::deserialize) - .collect(Collectors.toList()); - meta.lore(loreComponents); - } - - // Custom Model Data - if (config.customModelData != null) { - meta.setCustomModelData(config.customModelData); - } - - // Glow effect - if (config.glow) { - Enchantment unbreaking = Registry.ENCHANTMENT.get(org.bukkit.NamespacedKey.minecraft("unbreaking")); - if (unbreaking != null) { - meta.addEnchant(unbreaking, 1, true); - } - meta.addItemFlags(ItemFlag.HIDE_ENCHANTS); - } - - // Hide flags - if (config.hideFlags) { - meta.addItemFlags(ItemFlag.values()); - } - - // Head texture - if (config.material == Material.PLAYER_HEAD && config.texture != null) { - applyHeadTexture((SkullMeta) meta, config.texture); - } - - item.setItemMeta(meta); - return item; - } - - private void applyHeadTexture(SkullMeta meta, String texture) { - // Base64 texture (longer than typical player name) - if (texture.length() > 32) { - PlayerProfile profile = Bukkit.createProfile(UUID.randomUUID()); - profile.setProperty(new ProfileProperty("textures", texture)); - meta.setPlayerProfile(profile); - } else { - // Player name - meta.setOwningPlayer(Bukkit.getOfflinePlayer(texture)); - } - } -} diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/gui/GuiItemRepresenter.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/gui/GuiItemRepresenter.java deleted file mode 100644 index 64a89b84..00000000 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/gui/GuiItemRepresenter.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.eternalcode.economy.gui; - -import eu.okaeri.configs.OkaeriConfig; -import eu.okaeri.configs.annotation.Comment; -import java.util.ArrayList; -import java.util.List; -import org.bukkit.Material; - -/** - * Representation of a GUI item in configuration. - * Serializable by OkaeriConfig. - */ -public class GuiItemRepresenter extends OkaeriConfig { - - @Comment("Item name (supports MiniMessage)") - public String name = ""; - - @Comment("Item material") - public Material material = Material.STONE; - - @Comment("Item lore (list of lines, supports MiniMessage)") - public List lore = new ArrayList<>(); - - @Comment("Action on click") - public GuiAction action = GuiAction.NONE; - - @Comment("Command to execute (for COMMAND/CONSOLE_COMMAND actions)") - public String command = ""; - - @Comment("Custom Model Data (for resource packs)") - public Integer customModelData = null; - - @Comment("Amount of items in stack") - public int amount = 1; - - @Comment("Player head texture (Base64 or player name)") - public String texture = null; - - @Comment("Should item glow (enchantment effect)") - public boolean glow = false; - - @Comment("Hide item flags (enchantments, attributes, etc.)") - public boolean hideFlags = true; - - public GuiItemRepresenter() { - } - - public GuiItemRepresenter(String name, Material material) { - this.name = name; - this.material = material; - } -} diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/menu/LeaderboardConfig.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/menu/LeaderboardConfig.java index 60e86d46..033b9168 100644 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/menu/LeaderboardConfig.java +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/menu/LeaderboardConfig.java @@ -2,9 +2,7 @@ import eu.okaeri.configs.OkaeriConfig; import eu.okaeri.configs.annotation.Comment; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; import org.bukkit.Material; public class LeaderboardConfig extends OkaeriConfig { diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/menu/LeaderboardMenu.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/menu/LeaderboardMenu.java index 1d3ba3c2..cb0f8b5c 100644 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/menu/LeaderboardMenu.java +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/menu/LeaderboardMenu.java @@ -7,7 +7,6 @@ import com.eternalcode.economy.leaderboard.LeaderboardEntry; import com.eternalcode.economy.leaderboard.LeaderboardPage; import com.eternalcode.economy.leaderboard.LeaderboardService; -import com.eternalcode.economy.leaderboard.menu.LeaderboardConfig.LeaderboardItem; import com.eternalcode.multification.shared.Formatter; import dev.rollczi.liteskullapi.SkullAPI; import dev.triumphteam.gui.builder.item.ItemBuilder; 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 index b8f28607..d3ff0839 100644 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/withdraw/WithdrawItemServiceImpl.java +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/withdraw/WithdrawItemServiceImpl.java @@ -173,7 +173,8 @@ public BigDecimal getValue(ItemStack itemStack) { try { return new BigDecimal(value); - } catch (NumberFormatException ignored) { + } + catch (NumberFormatException ignored) { return BigDecimal.ZERO; } } From 27551bd9b05f36b69bea557e22d419d4cb7670a4 Mon Sep 17 00:00:00 2001 From: Martin Sulikowski Date: Wed, 21 Jan 2026 21:13:09 +0100 Subject: [PATCH 3/6] Remove `AdminGenerateCommand` and its references, cleaning up unused functionality. --- .../economy/EconomyBukkitPlugin.java | 2 - .../impl/admin/AdminGenerateCommand.java | 166 ------------------ 2 files changed, 168 deletions(-) delete mode 100644 eternaleconomy-core/src/main/java/com/eternalcode/economy/command/impl/admin/AdminGenerateCommand.java 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 a52a38a4..e0ccd3cf 100644 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/EconomyBukkitPlugin.java +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/EconomyBukkitPlugin.java @@ -22,7 +22,6 @@ import com.eternalcode.economy.command.impl.WithdrawCommand; import com.eternalcode.economy.command.impl.admin.AdminAddCommand; import com.eternalcode.economy.command.impl.admin.AdminBalanceCommand; -import com.eternalcode.economy.command.impl.admin.AdminGenerateCommand; import com.eternalcode.economy.command.impl.admin.AdminRemoveCommand; import com.eternalcode.economy.command.impl.admin.AdminResetCommand; import com.eternalcode.economy.command.impl.admin.AdminSetCommand; @@ -182,7 +181,6 @@ public void onEnable() { ), new AdminResetCommand(accountPaymentService, noticeService), new AdminBalanceCommand(noticeService, decimalFormatter), - new AdminGenerateCommand(accountManager, scheduler), new WithdrawCommand( withdrawService, cooldownDuration, noticeService diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/impl/admin/AdminGenerateCommand.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/impl/admin/AdminGenerateCommand.java deleted file mode 100644 index ee04fb38..00000000 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/impl/admin/AdminGenerateCommand.java +++ /dev/null @@ -1,166 +0,0 @@ -package com.eternalcode.economy.command.impl.admin; - -import com.eternalcode.commons.scheduler.Scheduler; -import com.eternalcode.economy.EconomyPermissionConstant; -import com.eternalcode.economy.account.Account; -import com.eternalcode.economy.account.AccountManager; -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 java.math.BigDecimal; -import java.math.RoundingMode; -import java.util.ArrayList; -import java.util.List; -import java.util.Random; -import java.util.UUID; -import java.util.concurrent.ThreadLocalRandom; -import java.util.concurrent.atomic.AtomicInteger; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.NamedTextColor; -import org.bukkit.command.CommandSender; - -@Command(name = "economy generate", aliases = "eco generate") -@Permission(EconomyPermissionConstant.ADMIN_SET_PERMISSION) -public class AdminGenerateCommand { - - private static final String[] FIRST_NAMES = { - "Alex", "Jordan", "Taylor", "Morgan", "Casey", "Riley", "Avery", "Quinn", - "Blake", "Cameron", "Dakota", "Drew", "Hayden", "Jesse", "Kendall", "Logan", - "Parker", "Reese", "Skyler", "Tyler", "Rowan", "Sage", "River", "Phoenix", - "Charlie", "Finley", "Emerson", "Harper", "Kai", "Lennon", "Marley", "Oakley", - "Peyton", "Rory", "Sawyer", "Spencer", "Tatum", "Winter", "Aspen", "Blair", - "Ellis", "Ezra", "Gray", "Haven", "Jude", "Kit", "Lane", "Nova", - "Onyx", "Quinn", "Rain", "Salem", "True", "Vale", "Wren", "Zion", - "Azure", "Bay", "Cedar", "Echo", "Frost", "Harbor", "Indigo", "Jade" - }; - - private static final String[] SUFFIXES = { - "MC", "YT", "TV", "TTV", "HD", "Pro", "Gaming", "Plays", - "Live", "Stream", "King", "Lord", "Master", "Shadow", "Dark", "Red", - "Blue", "Gold", "Silver", "Epic", "Mega", "Ultra", "Super", "Hyper", - "Alpha", "Beta", "Omega", "Prime", "Elite", "Ace", "Champion", "Legend", - "Hero", "Warrior", "Knight", "Dragon", "Wolf", "Tiger", "Lion", "Bear", - "Fox", "Hawk", "Eagle", "Raven", "Storm", "Blaze", "Flame", "Frost", - "Night", "Star", "Moon", "Sun", "Sky", "Ocean", "Fire", "Ice", - "Thunder", "Lightning", "Nova", "Void", "Nexus", "Apex" - }; - - private final AccountManager accountManager; - private final Scheduler scheduler; - - public AdminGenerateCommand(AccountManager accountManager, Scheduler scheduler) { - this.accountManager = accountManager; - this.scheduler = scheduler; - } - - @Execute - void execute(@Context CommandSender sender, @Arg @Min(1) int count) { - if (count > 100000) { - sender.sendMessage(Component.text("Maximum count is 100,000", NamedTextColor.RED)); - return; - } - - sender.sendMessage(Component.text("Generating " + count + " accounts...", NamedTextColor.YELLOW)); - - AtomicInteger generated = new AtomicInteger(0); - long startTime = System.currentTimeMillis(); - - scheduler.runAsync(() -> { - List batch = new ArrayList<>(Math.min(1000, count)); - Random random = ThreadLocalRandom.current(); - - for (int i = 0; i < count; i++) { - String name = generateRealisticName(random); - UUID uuid = UUID.randomUUID(); - BigDecimal balance = generateRandomBalance(random); - - Account account = new Account(uuid, name, balance); - batch.add(account); - - if (batch.size() >= 1000) { - saveBatch(batch, generated); - batch.clear(); - - try { - Thread.sleep(10); - } - catch (InterruptedException e) { - Thread.currentThread().interrupt(); - break; - } - } - } - - if (!batch.isEmpty()) { - saveBatch(batch, generated); - } - - long elapsed = System.currentTimeMillis() - startTime; - scheduler.run(() -> { - sender.sendMessage(Component.text( - "Generated " + generated.get() + " accounts in " + elapsed + "ms", - NamedTextColor.GREEN - )); - }); - }); - } - - private void saveBatch(List batch, AtomicInteger counter) { - for (Account account : batch) { - accountManager.save(account); - counter.incrementAndGet(); - } - } - - private String generateRealisticName(Random random) { - String firstName = FIRST_NAMES[random.nextInt(FIRST_NAMES.length)]; - - int style = random.nextInt(100); - - if (style < 30) { - return firstName + random.nextInt(10000); - } - else if (style < 60) { - String suffix = SUFFIXES[random.nextInt(SUFFIXES.length)]; - return firstName + suffix; - } - else if (style < 80) { - String suffix = SUFFIXES[random.nextInt(SUFFIXES.length)]; - return firstName + suffix + random.nextInt(1000); - } - else if (style < 90) { - return firstName + "_" + SUFFIXES[random.nextInt(SUFFIXES.length)]; - } - else { - String second = FIRST_NAMES[random.nextInt(FIRST_NAMES.length)]; - return firstName + second; - } - } - - private BigDecimal generateRandomBalance(Random random) { - int distribution = random.nextInt(100); - - double rawBalance; - - if (distribution < 40) { - rawBalance = 1 + random.nextDouble() * 100; - } - else if (distribution < 70) { - rawBalance = 100 + random.nextDouble() * 1000; - } - else if (distribution < 85) { - rawBalance = 1000 + random.nextDouble() * 10000; - } - else if (distribution < 95) { - rawBalance = 10000 + random.nextDouble() * 100000; - } - else { - rawBalance = 100000 + random.nextDouble() * 900000; - } - - return BigDecimal.valueOf(rawBalance).setScale(2, RoundingMode.HALF_UP); - } -} From 7b908b5bb396ff8d72469c66a888e49d0afd0865 Mon Sep 17 00:00:00 2001 From: Martin Sulikowski Date: Tue, 10 Feb 2026 01:28:51 +0100 Subject: [PATCH 4/6] Fix folia support. --- .../economy/EconomyBukkitPlugin.java | 30 ++++++++----------- .../economy/bridge/BridgeManager.java | 29 +++++++++--------- 2 files changed, 27 insertions(+), 32 deletions(-) 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 e0ccd3cf..09ea860d 100644 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/EconomyBukkitPlugin.java +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/EconomyBukkitPlugin.java @@ -141,11 +141,13 @@ public void onEnable() { Economy.class, vaultEconomyProvider, this, ServicePriority.Highest); - LiteCommandsBuilder - liteCommandsBuilder = LiteBukkitFactory.builder("eternaleconomy", this, server) + LiteCommandsBuilder liteCommandsBuilder = LiteBukkitFactory + .builder("eternaleconomy", this, server) .extension( new LiteJakartaExtension<>(), settings -> settings - .violationMessage(Min.class, BigDecimal.class, new InvalidBigDecimalMessage<>(noticeService))) + .violationMessage( + Min.class, BigDecimal.class, + new InvalidBigDecimalMessage<>(noticeService))) .annotations(extension -> extension.validator( Account.class, @@ -169,27 +171,22 @@ public void onEnable() { .commands( new AdminAddCommand( accountPaymentService, decimalFormatter, - noticeService - ), + noticeService), new AdminRemoveCommand( accountPaymentService, decimalFormatter, - noticeService - ), + noticeService), new AdminSetCommand( accountPaymentService, decimalFormatter, - noticeService - ), + noticeService), new AdminResetCommand(accountPaymentService, noticeService), new AdminBalanceCommand(noticeService, decimalFormatter), new WithdrawCommand( withdrawService, cooldownDuration, - noticeService - ), + noticeService), new MoneyBalanceCommand(noticeService, decimalFormatter), new MoneyTransferCommand( accountPaymentService, decimalFormatter, - noticeService, pluginConfig - ), + noticeService, pluginConfig), new EconomyReloadCommand(configService, noticeService)) .context(Account.class, new AccountContext(accountManager, messageConfig)) @@ -207,8 +204,7 @@ public void onEnable() { noticeService, decimalFormatter, this.skullAPI, - liteCommandsBuilder - ); + liteCommandsBuilder); this.liteCommands = liteCommandsBuilder.build(); @@ -226,8 +222,8 @@ public void onEnable() { accountManager, decimalFormatter, server, - this, - this.getLogger()); + this.getLogger(), + scheduler); bridgeManager.init(); Duration elapsed = started.elapsed(); 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 73ca4b43..1331db5a 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 @@ -1,13 +1,12 @@ package com.eternalcode.economy.bridge; +import com.eternalcode.commons.scheduler.Scheduler; 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; -import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginManager; public class BridgeManager { @@ -18,42 +17,42 @@ public class BridgeManager { private final DecimalFormatter decimalFormatter; private final Server server; - private final Plugin plugin; private final Logger logger; + private final Scheduler scheduler; public BridgeManager( PluginMeta pluginMeta, AccountManager accountManager, DecimalFormatter decimalFormatter, Server server, - Plugin plugin, - Logger logger + Logger logger, + Scheduler scheduler ) { this.pluginMeta = pluginMeta; this.accountManager = accountManager; this.decimalFormatter = decimalFormatter; this.server = server; - this.plugin = plugin; this.logger = logger; + this.scheduler = scheduler; } public void init() { - // Using "load: STARTUP" in plugin.yml causes the plugin to load before PlaceholderAPI. - // Therefore, we need to delay the bridge initialization until the server is fully started. - // The scheduler runs the code after the "Done" message, ensuring the server is fully operational. - Bukkit.getScheduler().runTask( - this.plugin, () -> { + // Using "load: STARTUP" in plugin.yml causes the plugin to load before + // PlaceholderAPI. + // Therefore, we need to delay the bridge initialization until the server is + // fully started. + // The scheduler runs the code after the "Done" message, ensuring the server is + // fully operational. + this.scheduler.run( + () -> { this.setupBridge( "PlaceholderAPI", () -> { PlaceholderEconomyExpansion placeholderEconomyExpansion = new PlaceholderEconomyExpansion( this.pluginMeta, this.accountManager, - this.decimalFormatter - ); + this.decimalFormatter); placeholderEconomyExpansion.register(); - - System.out.println("PlaceholderAPI bridge initialized!"); }); }); From 7c30d2eb84ebd7e5cb540d66bc208d3ee2b8b423 Mon Sep 17 00:00:00 2001 From: Jakubk15 <77227023+Jakubk15@users.noreply.github.com> Date: Fri, 20 Feb 2026 16:47:41 +0100 Subject: [PATCH 5/6] Mark LiteSkullAPI as Paper Library to decrease JAR file size --- eternaleconomy-core/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eternaleconomy-core/build.gradle.kts b/eternaleconomy-core/build.gradle.kts index 4d3218cc..57842c91 100644 --- a/eternaleconomy-core/build.gradle.kts +++ b/eternaleconomy-core/build.gradle.kts @@ -62,7 +62,7 @@ dependencies { // TriumphGUI for GUI implementation("dev.triumphteam:triumph-gui:${Versions.TRIUMPH_GUI}") - implementation("dev.rollczi:liteskullapi:${Versions.LITE_SKULL_API}") + paperLibrary("dev.rollczi:liteskullapi:${Versions.LITE_SKULL_API}") testImplementation(platform("org.junit:junit-bom:6.0.3")) testImplementation("org.junit.jupiter:junit-jupiter") From cc6b752d7ced9aab367640e79f38bbdcf13dd794 Mon Sep 17 00:00:00 2001 From: Jakubk15 <77227023+Jakubk15@users.noreply.github.com> Date: Fri, 20 Feb 2026 17:37:01 +0100 Subject: [PATCH 6/6] Fix incorrect page numeration --- .../leaderboard/LeaderboardCommand.java | 14 ++++---- .../leaderboard/LeaderboardServiceImpl.java | 35 ++++++++++--------- 2 files changed, 26 insertions(+), 23 deletions(-) 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 84fd7dbc..31e94ebe 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 @@ -1,5 +1,6 @@ package com.eternalcode.economy.leaderboard; +import com.eternalcode.commons.concurrent.FutureHandler; import com.eternalcode.economy.EconomyPermissionConstant; import com.eternalcode.economy.account.Account; import com.eternalcode.economy.config.implementation.PluginConfig; @@ -13,10 +14,11 @@ import dev.rollczi.litecommands.annotations.execute.ExecuteDefault; import dev.rollczi.litecommands.annotations.permission.Permission; import jakarta.validation.constraints.Min; -import java.util.List; import org.bukkit.Bukkit; import org.bukkit.entity.Player; +import java.util.List; + @SuppressWarnings("unused") @Command(name = "balancetop", aliases = {"baltop", "btgui", "topgui"}) @Permission(EconomyPermissionConstant.PLAYER_BALANCE_TOP_PERMISSION) @@ -58,12 +60,12 @@ void execute(@Context Account account, @Min(1) @Arg("page") int page) { } } - this.leaderboardService.getLeaderboardPage(page - 1, this.pluginConfig.leaderboardPageSize) - .thenAccept(leaderboardPage -> showPage(account, leaderboardPage)); + this.leaderboardService.getLeaderboardPage(page, this.pluginConfig.leaderboardPageSize) + .thenAccept(leaderboardPage -> this.showPage(account, leaderboardPage)); } private void showPage(Account account, LeaderboardPage page) { - int currentPage = page.currentPage() + 1; + int currentPage = page.currentPage(); List entries = page.entries(); if (entries.isEmpty()) { @@ -100,13 +102,13 @@ private void showPage(Account account, LeaderboardPage page) { .placeholder("{POSITION}", String.valueOf(entry.position())) .player(account.uuid()) .send(); - }); + }).exceptionally(FutureHandler::handleException); } if (page.nextPage() != -1) { this.noticeService.create() .notice(messages -> messages.player.leaderboardFooter) - .placeholder("{NEXT_PAGE}", String.valueOf(page.nextPage() + 1)) + .placeholder("{NEXT_PAGE}", String.valueOf(page.nextPage())) .placeholder("{TOTAL_PAGES}", String.valueOf(page.maxPages())) .placeholder("{PAGE}", String.valueOf(currentPage)) .player(account.uuid()) diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/LeaderboardServiceImpl.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/LeaderboardServiceImpl.java index e91e6424..a7cde65d 100644 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/LeaderboardServiceImpl.java +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/LeaderboardServiceImpl.java @@ -2,6 +2,7 @@ import com.eternalcode.economy.account.Account; import com.eternalcode.economy.account.database.AccountRepository; + import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; @@ -23,15 +24,15 @@ public CompletableFuture getLeaderboardPage(int page, int pageS int startIndex = (page - 1) * pageSize; if (startIndex < SNAPSHOT_SIZE) { - return getPageFromSnapshot(page, pageSize, startIndex); + return this.getPageFromSnapshot(page, pageSize, startIndex); } - return getPageFromDatabaseKeyset(page, pageSize, startIndex); + return this.getPageFromDatabaseKeyset(page, pageSize, startIndex); } @Override public CompletableFuture getLeaderboardPosition(Account target) { - LeaderboardSnapshot snapshot = snapshotRef.get(); + LeaderboardSnapshot snapshot = this.snapshotRef.get(); if (snapshot != null) { Integer position = snapshot.getPosition(target.uuid()); @@ -40,7 +41,7 @@ public CompletableFuture getLeaderboardPosition(Account target } } - return repository.getPosition(target).thenApply(position -> new LeaderboardEntry(target, position)); + return this.repository.getPosition(target).thenApply(position -> new LeaderboardEntry(target, position)); } public void invalidateCache() { @@ -48,42 +49,42 @@ public void invalidateCache() { } public CompletableFuture refreshSnapshot() { - return repository.getTopAccounts(SNAPSHOT_SIZE, 0).thenCombine( - repository.countAccounts(), (accounts, totalCount) -> { + return this.repository.getTopAccounts(SNAPSHOT_SIZE, 0).thenCombine( + this.repository.countAccounts(), (accounts, totalCount) -> { LeaderboardSnapshot newSnapshot = new LeaderboardSnapshot(accounts, totalCount); - snapshotRef.set(newSnapshot); + this.snapshotRef.set(newSnapshot); return null; }); } private CompletableFuture getOrRefreshSnapshot() { - LeaderboardSnapshot current = snapshotRef.get(); + LeaderboardSnapshot current = this.snapshotRef.get(); if (current != null) { return CompletableFuture.completedFuture(current); } - return repository.getTopAccounts(SNAPSHOT_SIZE, 0).thenCombine( - repository.countAccounts(), (accounts, totalCount) -> { + return this.repository.getTopAccounts(SNAPSHOT_SIZE, 0).thenCombine( + this.repository.countAccounts(), (accounts, totalCount) -> { LeaderboardSnapshot newSnapshot = new LeaderboardSnapshot(accounts, totalCount); - snapshotRef.set(newSnapshot); + this.snapshotRef.set(newSnapshot); return newSnapshot; }); } private CompletableFuture getPageFromSnapshot(int page, int pageSize, int startIndex) { - return getOrRefreshSnapshot().thenApply(snapshot -> { + return this.getOrRefreshSnapshot().thenApply(snapshot -> { int endIndex = Math.min(startIndex + pageSize, snapshot.size()); List accounts = snapshot.accounts().subList(startIndex, endIndex); - return createPage(accounts, page, pageSize, startIndex, snapshot.totalCount()); + return this.createPage(accounts, page, pageSize, startIndex, snapshot.totalCount()); }); } private CompletableFuture getPageFromDatabaseKeyset(int page, int pageSize, int startIndex) { - return getOrRefreshSnapshot().thenCompose(snapshot -> { + return this.getOrRefreshSnapshot().thenCompose(snapshot -> { int offset = startIndex - SNAPSHOT_SIZE; - return repository.getTopAccounts(pageSize, SNAPSHOT_SIZE + offset) - .thenApply(accounts -> createPage(accounts, page, pageSize, startIndex, snapshot.totalCount())); + return this.repository.getTopAccounts(pageSize, SNAPSHOT_SIZE + offset) + .thenApply(accounts -> this.createPage(accounts, page, pageSize, startIndex, snapshot.totalCount())); }); } @@ -96,7 +97,7 @@ private LeaderboardPage createPage( } int maxPages = Math.max(1, (int) Math.ceil((double) totalCount / pageSize)); - int nextPage = page + 1 < maxPages ? page + 1 : -1; + int nextPage = page < maxPages ? page + 1 : -1; return new LeaderboardPage(entries, page, maxPages, nextPage); }