Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions buildSrc/src/main/kotlin/Versions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -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"
Expand Down
4 changes: 4 additions & 0 deletions eternaleconomy-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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<CommandSender> liteCommands;

@Override
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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,
Expand All @@ -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);

Expand All @@ -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();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<Void> 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<Void> 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<Collection<Account>> 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<List<Account>> getTopAccounts(int limit, int offset) {
return this.<AccountWrapper, UUID, List<Account>>action(
AccountWrapper.class, dao -> {
QueryBuilder<AccountWrapper, UUID> queryBuilder = dao.queryBuilder();
queryBuilder.orderBy("balance", false);
queryBuilder.orderBy("uuid", true);
return this.<AccountTable, UUID, List<Account>>action(
AccountTable.class, dao -> {
QueryBuilder<AccountTable, UUID> queryBuilder = dao.queryBuilder();
queryBuilder.orderBy(AccountTable.BALANCE, false);
queryBuilder.orderBy(AccountTable.UUID, true);

if (limit > 0) {
queryBuilder.limit((long) limit);
Expand All @@ -63,30 +65,46 @@ public CompletableFuture<List<Account>> getTopAccounts(int limit, int offset) {
}

return queryBuilder.query().stream()
.map(AccountWrapper::toAccount)
.map(AccountTable::toAccount)
.toList();
});
}

@Override
public CompletableFuture<Integer> getPosition(Account target) {
return this.<AccountWrapper, UUID, Integer>action(
AccountWrapper.class, dao -> {
QueryBuilder<AccountWrapper, UUID> qb = dao.queryBuilder();
Where<AccountWrapper, UUID> where = qb.where();
return this.<AccountTable, UUID, Integer>action(
AccountTable.class, dao -> {
QueryBuilder<AccountTable, UUID> qb = dao.queryBuilder();
Where<AccountTable, UUID> 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;
});
}

@Override
public CompletableFuture<Long> countAccounts() {
return this.<AccountWrapper, UUID, Long>action(AccountWrapper.class, dao -> dao.countOf());
return this.<AccountTable, UUID, Long>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;
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
Loading