diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..9722139 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,27 @@ +name: Build + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up JDK + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: 17 + cache: 'gradle' + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Build + run: ./gradlew eternaleconomy-core:shadowJar \ No newline at end of file diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index c2d8cb6..5215cac 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -8,7 +8,7 @@ repositories { } dependencies { - implementation("com.gradleup.shadow:shadow-gradle-plugin:8.3.0") + implementation("com.gradleup.shadow:shadow-gradle-plugin:8.3.3") implementation("net.minecrell:plugin-yml:0.6.0") implementation("xyz.jpenilla:run-task:2.3.1") implementation("me.champeau.jmh:jmh-gradle-plugin:0.7.2") diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index f3e4394..7172869 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -5,7 +5,7 @@ object Versions { const val OKAERI_CONFIGS = "5.0.3" const val LITE_COMMANDS = "3.6.0-SNAPSHOT" - const val ETERNALCODE_COMMONS = "1.1.4-SNAPSHOT" + const val ETERNALCODE_COMMONS = "1.1.5" const val MULTIFICATION = "1.1.3" const val JETBRAINS_ANNOTATIONS = "24.1.0" diff --git a/buildSrc/src/main/kotlin/economy-checkstyle.gradle.kts b/buildSrc/src/main/kotlin/economy-checkstyle.gradle.kts index 92e39b2..ba1c29b 100644 --- a/buildSrc/src/main/kotlin/economy-checkstyle.gradle.kts +++ b/buildSrc/src/main/kotlin/economy-checkstyle.gradle.kts @@ -3,7 +3,7 @@ plugins { } checkstyle { - toolVersion = "10.17.0" + toolVersion = "10.18.2" configFile = file("${rootDir}/config/checkstyle/checkstyle.xml") configProperties["checkstyle.suppressions.file"] = "${rootDir}/config/checkstyle/suppressions.xml" @@ -17,7 +17,7 @@ configurations.named("checkstyle") { resolutionStrategy { capabilitiesResolution { withCapability("com.google.collections:google-collections") { - select("com.google.guava:guava:33.2.1-jre") + select("com.google.guava:guava:33.3.1-jre") } } } diff --git a/buildSrc/src/main/kotlin/economy-repositories.gradle.kts b/buildSrc/src/main/kotlin/economy-repositories.gradle.kts index c2f34b8..5e501f5 100644 --- a/buildSrc/src/main/kotlin/economy-repositories.gradle.kts +++ b/buildSrc/src/main/kotlin/economy-repositories.gradle.kts @@ -12,4 +12,5 @@ repositories { maven("https://storehouse.okaeri.eu/repository/maven-public/") maven("https://jitpack.io") maven("https://repo.extendedclip.com/content/repositories/placeholderapi/") + maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") } \ No newline at end of file diff --git a/eternaleconomy-core/build.gradle.kts b/eternaleconomy-core/build.gradle.kts index 17b0955..d968146 100644 --- a/eternaleconomy-core/build.gradle.kts +++ b/eternaleconomy-core/build.gradle.kts @@ -1,3 +1,5 @@ +import net.minecrell.pluginyml.bukkit.BukkitPluginDescription + plugins { `economy-java` `economy-repositories` @@ -50,7 +52,7 @@ dependencies { compileOnly("me.clip:placeholderapi:${Versions.PLACEHOLDER_API}") - testImplementation(platform("org.junit:junit-bom:5.10.2")) + testImplementation(platform("org.junit:junit-bom:5.11.2")) testImplementation("org.junit.jupiter:junit-jupiter") jmh("org.openjdk.jmh:jmh-core:1.37") jmh("org.openjdk.jmh:jmh-generator-annprocess:1.37") @@ -68,6 +70,10 @@ bukkit { author = "EternalCodeTeam" name = "EternalEconomy" website = "www.eternalcode.pl" + // Enabling this option previously caused issues where the plugin was loaded before Vault, + // preventing the Vault Economy Provider from registering and causing dependent plugins to malfunction. + // Setting the load order to startup ensures the economy plugin is one of the first to load, avoiding these issues. + load = BukkitPluginDescription.PluginLoadOrder.STARTUP version = "${project.version}" depend = listOf("Vault") 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 e9bc8ca..555c05e 100644 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/EconomyBukkitPlugin.java +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/EconomyBukkitPlugin.java @@ -18,6 +18,8 @@ import com.eternalcode.economy.command.admin.AdminSetCommand; import com.eternalcode.economy.command.argument.AccountArgument; import com.eternalcode.economy.command.context.AccountContext; +import com.eternalcode.economy.command.cooldown.CommandCooldownEditor; +import com.eternalcode.economy.command.cooldown.CommandCooldownMessage; import com.eternalcode.economy.command.handler.InvalidUsageHandlerImpl; import com.eternalcode.economy.command.handler.MissingPermissionHandlerImpl; import com.eternalcode.economy.command.message.InvalidBigDecimalMessage; @@ -26,6 +28,7 @@ import com.eternalcode.economy.command.validator.notsender.NotSender; import com.eternalcode.economy.command.validator.notsender.NotSenderValidator; import com.eternalcode.economy.config.ConfigService; +import com.eternalcode.economy.config.implementation.CommandsConfig; import com.eternalcode.economy.config.implementation.PluginConfig; import com.eternalcode.economy.config.implementation.messages.MessageConfig; import com.eternalcode.economy.database.DatabaseManager; @@ -41,6 +44,7 @@ import dev.rollczi.litecommands.LiteCommands; import dev.rollczi.litecommands.bukkit.LiteBukkitFactory; import dev.rollczi.litecommands.jakarta.LiteJakartaExtension; +import dev.rollczi.litecommands.message.LiteMessages; import jakarta.validation.constraints.Positive; import java.io.File; import java.math.BigDecimal; @@ -81,6 +85,7 @@ public void onEnable() { ConfigService configService = new ConfigService(); MessageConfig messageConfig = configService.create(MessageConfig.class, new File(dataFolder, "messages.yml")); PluginConfig pluginConfig = configService.create(PluginConfig.class, new File(dataFolder, "config.yml")); + CommandsConfig commandsConfig = configService.create(CommandsConfig.class, new File(dataFolder, "commands.yml")); NoticeService noticeService = new NoticeService(messageConfig, this.audienceProvider, miniMessage); @@ -112,6 +117,9 @@ public void onEnable() { .missingPermission(new MissingPermissionHandlerImpl(noticeService)) .invalidUsage(new InvalidUsageHandlerImpl(noticeService)) + .message(LiteMessages.COMMAND_COOLDOWN, new CommandCooldownMessage(noticeService, commandsConfig)) + .editorGlobal(new CommandCooldownEditor(commandsConfig)) + .commands( new AdminAddCommand(accountPaymentService, decimalFormatter, noticeService), new AdminRemoveCommand(accountPaymentService, decimalFormatter, noticeService), @@ -119,7 +127,7 @@ public void onEnable() { new AdminResetCommand(accountPaymentService, noticeService), new AdminBalanceCommand(noticeService, decimalFormatter), new MoneyBalanceCommand(noticeService, decimalFormatter), - new MoneyTransferCommand(accountPaymentService, decimalFormatter, noticeService), + new MoneyTransferCommand(accountPaymentService, decimalFormatter, noticeService, pluginConfig), new EconomyReloadCommand(configService, noticeService) ) @@ -138,6 +146,7 @@ public void onEnable() { accountManager, decimalFormatter, server, + this, this.getLogger() ); bridgeManager.init(); 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 9ee4d1e..b424583 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 @@ -4,7 +4,9 @@ import com.eternalcode.economy.bridge.placeholderapi.PlaceholderEconomyExpansion; import com.eternalcode.economy.format.DecimalFormatter; import java.util.logging.Logger; +import org.bukkit.Bukkit; import org.bukkit.Server; +import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginDescriptionFile; import org.bukkit.plugin.PluginManager; @@ -16,6 +18,7 @@ public class BridgeManager { private final DecimalFormatter decimalFormatter; private final Server server; + private final Plugin plugin; private final Logger logger; public BridgeManager( @@ -23,23 +26,36 @@ public BridgeManager( AccountManager accountManager, DecimalFormatter decimalFormatter, Server server, + Plugin plugin, Logger logger ) { this.pluginDescriptionFile = pluginDescriptionFile; this.accountManager = accountManager; this.decimalFormatter = decimalFormatter; this.server = server; + this.plugin = plugin; this.logger = logger; } public void init() { - this.setupBridge("PlaceholderAPI", () -> { - new PlaceholderEconomyExpansion( - this.pluginDescriptionFile, - this.accountManager, - this.decimalFormatter - ).initialize(); + // 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.pluginDescriptionFile, + this.accountManager, + this.decimalFormatter + ); + + placeholderEconomyExpansion.register(); + + System.out.println("PlaceholderAPI bridge initialized!"); + }); }); + + // other bridges (do not put bridges in the scheduler if not needed) } private void setupBridge(String pluginName, BridgeInitializer bridge) { 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 new file mode 100644 index 0000000..bfedef9 --- /dev/null +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/cooldown/CommandCooldownConfig.java @@ -0,0 +1,19 @@ +package com.eternalcode.economy.command.cooldown; + +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 { + @Comment("Duration of the cooldown (e.g. 5s, 10m, 1h)") + public Duration duration = Duration.ofSeconds(5); + @Comment("Permission for admins to bypass the cooldown") + public String bypassPermission = "eternaleconomy.player.pay.bypass"; + public Notice message = Notice.builder() + .chat("ECONOMY " + + "You must wait {TIME} before using /pay again.") + .actionBar("Wait {TIME}!") + .build(); +} 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 new file mode 100644 index 0000000..884d47e --- /dev/null +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/cooldown/CommandCooldownEditor.java @@ -0,0 +1,41 @@ +package com.eternalcode.economy.command.cooldown; + +import com.eternalcode.economy.config.implementation.CommandsConfig; +import dev.rollczi.litecommands.command.builder.CommandBuilder; +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; + +public class CommandCooldownEditor implements Editor { + + private final CommandsConfig commandsConfig; + + public CommandCooldownEditor(CommandsConfig commandsConfig) { + this.commandsConfig = commandsConfig; + } + + @Override + public CommandBuilder edit(CommandBuilder commandBuilder) { + Meta meta = commandBuilder.meta(); + + for (Map.Entry entry : commandsConfig.cooldowns.entrySet()) { + String commandName = entry.getKey(); + boolean isCurrent = commandBuilder.isNameOrAlias(commandName); + + if (!isCurrent) { + continue; + } + + CommandCooldownConfig cooldown = entry.getValue(); + + meta.put(Meta.COOLDOWN, new CooldownContext(commandName, cooldown.duration, cooldown.bypassPermission)); + break; + } + + 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 new file mode 100644 index 0000000..9dcd914 --- /dev/null +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/cooldown/CommandCooldownMessage.java @@ -0,0 +1,40 @@ +package com.eternalcode.economy.command.cooldown; + +import com.eternalcode.economy.config.implementation.CommandsConfig; +import com.eternalcode.economy.multification.NoticeService; +import dev.rollczi.litecommands.cooldown.CooldownState; +import dev.rollczi.litecommands.invocation.Invocation; +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; + +public class CommandCooldownMessage implements InvokedMessage { + + private final NoticeService noticeService; + private final CommandsConfig commandsConfig; + + public CommandCooldownMessage(NoticeService noticeService, CommandsConfig commandsConfig) { + this.noticeService = noticeService; + this.commandsConfig = commandsConfig; + } + + @Override + public Object get(Invocation invocation, CooldownState cooldownState) { + CommandCooldownConfig cooldown = commandsConfig.cooldowns.get(cooldownState.getCooldownContext().getKey()); + + if (cooldown == null) { + return LiteMessages.COMMAND_COOLDOWN.getDefaultMessage(cooldownState); + } + + 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/player/MoneyTransferCommand.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/player/MoneyTransferCommand.java index 96a55e2..77b87d5 100644 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/player/MoneyTransferCommand.java +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/command/player/MoneyTransferCommand.java @@ -4,6 +4,7 @@ import com.eternalcode.economy.account.Account; import com.eternalcode.economy.account.AccountPaymentService; import com.eternalcode.economy.command.validator.notsender.NotSender; +import com.eternalcode.economy.config.implementation.PluginConfig; import com.eternalcode.economy.format.DecimalFormatter; import com.eternalcode.economy.multification.NoticeService; import dev.rollczi.litecommands.annotations.argument.Arg; @@ -21,15 +22,18 @@ public class MoneyTransferCommand { private final AccountPaymentService accountPaymentService; private final DecimalFormatter decimalFormatter; private final NoticeService noticeService; + private PluginConfig pluginConfig; public MoneyTransferCommand( AccountPaymentService accountPaymentService, DecimalFormatter decimalFormatter, - NoticeService noticeService + NoticeService noticeService, + PluginConfig pluginConfig ) { this.accountPaymentService = accountPaymentService; this.decimalFormatter = decimalFormatter; this.noticeService = noticeService; + this.pluginConfig = pluginConfig; } @Execute @@ -45,6 +49,16 @@ void execute(@Context Account payer, @Arg @NotSender Account receiver, @Arg @Pos return; } + if (amount.compareTo(this.pluginConfig.transactionLimit) > 0) { + this.noticeService.create() + .notice(notice -> notice.player.transferLimit) + .placeholder("{LIMIT}", this.decimalFormatter.format(this.pluginConfig.transactionLimit)) + .player(payer.uuid()) + .send(); + + return; + } + this.accountPaymentService.payment(payer, receiver, amount); this.noticeService.create() 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 new file mode 100644 index 0000000..a78c9db --- /dev/null +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/config/implementation/CommandsConfig.java @@ -0,0 +1,19 @@ +package com.eternalcode.economy.config.implementation; + +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 { + + @Comment({ + "Cooldowns for commands", + "You can set a cooldown for each command, e.g. 'pay' command:", + }) + 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 928db4f..bde1823 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 @@ -20,6 +20,9 @@ public class PluginConfig extends OkaeriConfig { @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); + public static class Units extends OkaeriConfig { public List format = Arrays.asList( 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 a84abe6..dd0d7a5 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 @@ -27,4 +27,6 @@ public class MessagesPlayerSubSection extends OkaeriConfig { public Notice transferReceived = Notice.chat("ECONOMY " + " Received {AMOUNT} from " + "{PLAYER}."); + public Notice transferLimit = Notice.chat("ECONOMY " + + " Transaction limit is {LIMIT}."); } diff --git a/eternaleconomy-core/src/main/java/com/eternalcode/economy/database/AbstractRepositoryOrmLite.java b/eternaleconomy-core/src/main/java/com/eternalcode/economy/database/AbstractRepositoryOrmLite.java index 292f7ae..43084be 100644 --- a/eternaleconomy-core/src/main/java/com/eternalcode/economy/database/AbstractRepositoryOrmLite.java +++ b/eternaleconomy-core/src/main/java/com/eternalcode/economy/database/AbstractRepositoryOrmLite.java @@ -55,7 +55,7 @@ protected CompletableFuture action( ThrowingFunction, R, SQLException> action) { CompletableFuture completableFuture = new CompletableFuture<>(); - this.scheduler.async(() -> { + this.scheduler.runAsync(() -> { Dao dao = this.databaseManager.getDao(type); try { diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e644113..a4b76b9 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index d96302a..df97d72 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists \ No newline at end of file +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index b740cf1..f5feea6 100644 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -84,7 +86,8 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/gradlew.bat b/gradlew.bat index 25da30d..9d21a21 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## diff --git a/settings.gradle.kts b/settings.gradle.kts index 72bf8ac..a747b1a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,11 +1,4 @@ -/* - * This file was generated by the Gradle 'init' task. - * - * The settings file is used to specify which projects to include in your build. - * For more detailed information on multi-project builds, please refer to https://docs.gradle.org/8.8/userguide/multi_project_builds.html in the Gradle documentation. - * This project uses @Incubating APIs which are subject to change. - */ - rootProject.name = "EternalEconomy" + include("eternaleconomy-core") include("eternaleconomy-api")