diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/CombatPlugin.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/CombatPlugin.java
index 4b492fc1..912fea9e 100644
--- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/CombatPlugin.java
+++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/CombatPlugin.java
@@ -1,55 +1,45 @@
package com.eternalcode.combat;
-import com.eternalcode.combat.border.BorderTriggerController;
import com.eternalcode.combat.border.BorderService;
import com.eternalcode.combat.border.BorderServiceImpl;
+import com.eternalcode.combat.border.BorderTriggerController;
import com.eternalcode.combat.border.animation.block.BorderBlockController;
import com.eternalcode.combat.border.animation.particle.ParticleController;
import com.eternalcode.combat.bridge.BridgeService;
-import com.eternalcode.combat.crystalpvp.RespawnAnchorListener;
-import com.eternalcode.combat.crystalpvp.EndCrystalListener;
-import com.eternalcode.combat.fight.controller.FightBypassAdminController;
-import com.eternalcode.combat.fight.controller.FightBypassCreativeController;
-import com.eternalcode.combat.fight.controller.FightBypassPermissionController;
-import com.eternalcode.combat.fight.controller.FightInventoryController;
-import com.eternalcode.combat.fight.death.DeathEffectController;
-import com.eternalcode.combat.fight.drop.DropKeepInventoryService;
-import com.eternalcode.combat.fight.FightManager;
-import com.eternalcode.combat.fight.drop.DropService;
-import com.eternalcode.combat.fight.effect.FightEffectService;
-import com.eternalcode.combat.fight.firework.FireworkController;
-import com.eternalcode.combat.fight.knockback.KnockbackService;
-import com.eternalcode.combat.fight.tagout.FightTagOutService;
-import com.eternalcode.combat.fight.pearl.FightPearlService;
-import com.eternalcode.combat.handler.InvalidUsageHandlerImpl;
-import com.eternalcode.combat.handler.MissingPermissionHandlerImpl;
import com.eternalcode.combat.config.ConfigService;
import com.eternalcode.combat.config.implementation.PluginConfig;
-import com.eternalcode.combat.fight.drop.DropController;
-import com.eternalcode.combat.fight.drop.DropKeepInventoryServiceImpl;
-import com.eternalcode.combat.fight.drop.DropServiceImpl;
-import com.eternalcode.combat.fight.drop.impl.PercentDropModifier;
-import com.eternalcode.combat.fight.drop.impl.PlayersHealthDropModifier;
-import com.eternalcode.combat.fight.FightTagCommand;
-import com.eternalcode.combat.fight.controller.FightActionBlockerController;
-import com.eternalcode.combat.fight.controller.FightMessageController;
-import com.eternalcode.combat.fight.controller.FightTagController;
-import com.eternalcode.combat.fight.controller.FightUnTagController;
-import com.eternalcode.combat.fight.effect.FightEffectController;
+import com.eternalcode.combat.crystalpvp.EndCrystalListener;
+import com.eternalcode.combat.crystalpvp.RespawnAnchorListener;
import com.eternalcode.combat.event.EventManager;
+import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.fight.FightManagerImpl;
+import com.eternalcode.combat.fight.FightTagCommand;
import com.eternalcode.combat.fight.FightTask;
+import com.eternalcode.combat.fight.controller.*;
+import com.eternalcode.combat.fight.death.DeathEffectController;
+import com.eternalcode.combat.fight.drop.*;
+import com.eternalcode.combat.fight.drop.impl.PercentDropModifier;
+import com.eternalcode.combat.fight.drop.impl.PlayersHealthDropModifier;
+import com.eternalcode.combat.fight.effect.FightEffectController;
+import com.eternalcode.combat.fight.effect.FightEffectService;
import com.eternalcode.combat.fight.effect.FightEffectServiceImpl;
+import com.eternalcode.combat.fight.firework.FireworkController;
+import com.eternalcode.combat.fight.knockback.KnockbackRegionController;
+import com.eternalcode.combat.fight.knockback.KnockbackService;
import com.eternalcode.combat.fight.logout.LogoutController;
import com.eternalcode.combat.fight.logout.LogoutService;
import com.eternalcode.combat.fight.pearl.FightPearlController;
+import com.eternalcode.combat.fight.pearl.FightPearlService;
import com.eternalcode.combat.fight.pearl.FightPearlServiceImpl;
+import com.eternalcode.combat.fight.tagout.FightTagOutCommand;
import com.eternalcode.combat.fight.tagout.FightTagOutController;
+import com.eternalcode.combat.fight.tagout.FightTagOutService;
import com.eternalcode.combat.fight.tagout.FightTagOutServiceImpl;
-import com.eternalcode.combat.fight.tagout.FightTagOutCommand;
+import com.eternalcode.combat.handler.InvalidUsageHandlerImpl;
+import com.eternalcode.combat.handler.MissingPermissionHandlerImpl;
import com.eternalcode.combat.notification.NoticeService;
-import com.eternalcode.combat.fight.knockback.KnockbackRegionController;
import com.eternalcode.combat.region.RegionProvider;
+import com.eternalcode.combat.time.DurationService;
import com.eternalcode.combat.updater.UpdaterNotificationController;
import com.eternalcode.combat.updater.UpdaterService;
import com.eternalcode.commons.adventure.AdventureLegacyColorPostProcessor;
@@ -61,7 +51,6 @@
import dev.rollczi.litecommands.bukkit.LiteBukkitFactory;
import dev.rollczi.litecommands.bukkit.LiteBukkitMessages;
import dev.rollczi.litecommands.folia.FoliaExtension;
-import java.time.Duration;
import net.kyori.adventure.platform.AudienceProvider;
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
import net.kyori.adventure.text.minimessage.MiniMessage;
@@ -73,6 +62,7 @@
import org.bukkit.plugin.java.JavaPlugin;
import java.io.File;
+import java.time.Duration;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
@@ -109,6 +99,8 @@ public void onEnable() {
MinecraftScheduler scheduler = CombatSchedulerAdapter.getAdaptiveScheduler(this);
+ DurationService durationService = DurationService.ofConfig(pluginConfig.durationFormat);
+
this.fightManager = new FightManagerImpl(eventManager);
this.fightPearlService = new FightPearlServiceImpl(pluginConfig.pearl);
this.fightTagOutService = new FightTagOutServiceImpl();
@@ -134,6 +126,7 @@ public void onEnable() {
server.getPluginManager(),
this.getLogger(),
this,
+ durationService,
this.fightManager
);
bridgeService.init(server);
@@ -151,7 +144,7 @@ public void onEnable() {
.commands(
new FightTagCommand(this.fightManager, noticeService, pluginConfig),
- new FightTagOutCommand(this.fightTagOutService, noticeService, pluginConfig),
+ new FightTagOutCommand(this.fightTagOutService, noticeService, durationService, pluginConfig),
new EternalCombatReloadCommand(configService, noticeService)
)
@@ -164,7 +157,7 @@ public void onEnable() {
.build();
- FightTask fightTask = new FightTask(server, pluginConfig, this.fightManager, noticeService);
+ FightTask fightTask = new FightTask(server, pluginConfig, durationService, this.fightManager, noticeService);
scheduler.timer(fightTask, Duration.ofSeconds(1), Duration.ofSeconds(1));
new Metrics(this, BSTATS_METRICS_ID);
@@ -181,7 +174,7 @@ public void onEnable() {
new FightBypassPermissionController(server, pluginConfig),
new FightBypassCreativeController(server, pluginConfig),
new FightActionBlockerController(this.fightManager, noticeService, pluginConfig, server),
- new FightPearlController(pluginConfig.pearl, noticeService, this.fightManager, this.fightPearlService),
+ new FightPearlController(pluginConfig.pearl, noticeService, durationService, this.fightManager, this.fightPearlService),
new DeathEffectController(pluginConfig),
new UpdaterNotificationController(updaterService, pluginConfig, this.audienceProvider, miniMessage),
new KnockbackRegionController(noticeService, this.regionProvider, this.fightManager, knockbackService, server),
diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/bridge/BridgeService.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/bridge/BridgeService.java
index 1eb42a23..1bf26372 100644
--- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/bridge/BridgeService.java
+++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/bridge/BridgeService.java
@@ -1,20 +1,22 @@
package com.eternalcode.combat.bridge;
-import com.eternalcode.combat.region.lands.LandsRegionProvider;
import com.eternalcode.combat.bridge.placeholder.FightTagPlaceholder;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.region.CompositeRegionProvider;
-import com.eternalcode.combat.region.bukkit.DefaultRegionProvider;
import com.eternalcode.combat.region.RegionProvider;
+import com.eternalcode.combat.region.bukkit.DefaultRegionProvider;
+import com.eternalcode.combat.region.lands.LandsRegionProvider;
import com.eternalcode.combat.region.worldguard.WorldGuardRegionProvider;
+import com.eternalcode.combat.time.DurationService;
+import org.bukkit.Server;
+import org.bukkit.plugin.Plugin;
+import org.bukkit.plugin.PluginManager;
+
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import java.util.stream.Collectors;
-import org.bukkit.Server;
-import org.bukkit.plugin.Plugin;
-import org.bukkit.plugin.PluginManager;
public class BridgeService {
@@ -22,6 +24,7 @@ public class BridgeService {
private final PluginManager pluginManager;
private final Logger logger;
private final Plugin plugin;
+ private final DurationService durationService;
private final FightManager fightManager;
private RegionProvider regionProvider;
@@ -31,12 +34,14 @@ public BridgeService(
PluginManager pluginManager,
Logger logger,
Plugin plugin,
+ DurationService durationService,
FightManager fightManager
) {
this.config = config;
this.pluginManager = pluginManager;
this.logger = logger;
this.plugin = plugin;
+ this.durationService = durationService;
this.fightManager = fightManager;
}
@@ -69,7 +74,7 @@ public void init(Server server) {
initialize(
"PlaceholderAPI",
- () -> new FightTagPlaceholder(this.config, this.fightManager, server, this.plugin).register(),
+ () -> new FightTagPlaceholder(this.config, durationService, this.fightManager, server, this.plugin).register(),
() -> this.logger.warning("PlaceholderAPI not found; skipping placeholders.")
);
}
diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/bridge/placeholder/FightTagPlaceholder.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/bridge/placeholder/FightTagPlaceholder.java
index ad5e0c6a..41843aea 100644
--- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/bridge/placeholder/FightTagPlaceholder.java
+++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/bridge/placeholder/FightTagPlaceholder.java
@@ -4,9 +4,8 @@
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.fight.FightTag;
-import com.eternalcode.combat.util.DurationUtil;
+import com.eternalcode.combat.time.DurationService;
import com.eternalcode.commons.time.DurationParser;
-import java.util.Optional;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import org.bukkit.OfflinePlayer;
import org.bukkit.Server;
@@ -14,17 +13,21 @@
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
+import java.util.Optional;
+
public class FightTagPlaceholder extends PlaceholderExpansion {
private static final String IDENTIFIER = "eternalcombat";
private final PlaceholderSettings placeholderSettings;
+ private final DurationService durationService;
private final FightManager fightManager;
private final Server server;
private final Plugin plugin;
- public FightTagPlaceholder(PluginConfig pluginConfig, FightManager fightManager, Server server, Plugin plugin) {
+ public FightTagPlaceholder(PluginConfig pluginConfig, DurationService durationService, FightManager fightManager, Server server, Plugin plugin) {
this.placeholderSettings = pluginConfig.placeholders;
+ this.durationService = durationService;
this.fightManager = fightManager;
this.server = server;
this.plugin = plugin;
@@ -51,7 +54,7 @@ private String handleRemainingMillis(OfflinePlayer player) {
private String handleRemainingSeconds(OfflinePlayer player) {
return this.getFightTag(player)
- .map(tag -> DurationUtil.format(tag.getRemainingDuration()))
+ .map(tag -> durationService.format(tag.getRemainingDuration()))
.orElse("");
}
@@ -110,7 +113,7 @@ public boolean canRegister() {
@Override
public @NotNull String getAuthor() {
- return this.plugin.getDescription().getAuthors().get(0);
+ return this.plugin.getDescription().getAuthors().getFirst();
}
@Override
diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/CrystalPvpSettings.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/CrystalPvpSettings.java
index 8e20a4b1..ab5caeba 100644
--- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/CrystalPvpSettings.java
+++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/CrystalPvpSettings.java
@@ -8,7 +8,6 @@ public class CrystalPvpSettings extends OkaeriConfig {
@Comment({"# Should player be tagged when damaged from crystal explosion set by other player"})
public boolean tagFromCrystals = true;
-
- @Comment({"#Should player be tagged when damaged from respawn anchor explosion set by other player"})
+ @Comment({"# Should player be tagged when damaged from respawn anchor explosion set by other player"})
public boolean tagFromRespawnAnchor = true;
}
diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/DurationFormatSettings.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/DurationFormatSettings.java
new file mode 100644
index 00000000..18e1cf68
--- /dev/null
+++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/DurationFormatSettings.java
@@ -0,0 +1,36 @@
+package com.eternalcode.combat.config.implementation;
+
+import eu.okaeri.configs.OkaeriConfig;
+import eu.okaeri.configs.annotation.Comment;
+
+public class DurationFormatSettings extends OkaeriConfig {
+
+ @Comment({
+ "# Pattern used to format durations.",
+ "# Placeholders:",
+ "# %d{singular|plural} - days",
+ "# %h{singular|plural} - hours",
+ "# %m{singular|plural} - minutes",
+ "# %s{singular|plural} - seconds",
+ "# Example:",
+ "# %d{day|days} %h{hour|hours} %m{minute|minutes} %s{second|seconds}"
+ })
+ public String pattern = "%d{day|days} %h{hour|hours} %m{minute|minutes} %s{second|seconds}";
+
+ @Comment({
+ "# Separator used between duration parts.",
+ "# Example result:",
+ "# 1 hour, 2 minutes, 5 seconds"
+ })
+ public String separator = ", ";
+
+ @Comment({
+ "# Separator used before the last duration part.",
+ "# Example result:",
+ "# 1 hour, 2 minutes and 5 seconds"
+ })
+ public String lastSeparator = " and ";
+
+ @Comment({"# Text displayed when duration is zero or negative."})
+ public String zero = "<1 second";
+}
diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/MessagesSettings.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/MessagesSettings.java
index d5f69e21..0860e7e1 100644
--- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/MessagesSettings.java
+++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/MessagesSettings.java
@@ -23,11 +23,6 @@ public class MessagesSettings extends OkaeriConfig {
.actionBar("Combat ends in: {TIME}")
.build();
- @Comment({
- "# Would you like to display milliseconds instead of seconds in combat notification "
- })
- public boolean withoutMillis = true;
-
@Comment({
"# Message displayed when a player lacks permission to execute a command.",
"# The {PERMISSION} placeholder is replaced with the required permission."
diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/PlaceholderSettings.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/PlaceholderSettings.java
index 4465587a..e7609dd7 100644
--- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/PlaceholderSettings.java
+++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/PlaceholderSettings.java
@@ -5,10 +5,10 @@
public class PlaceholderSettings extends OkaeriConfig {
- @Comment("Text returned by %eternalcombat_isInCombat_formatted% placeholder when the player is in combat")
+ @Comment("# Text returned by %eternalcombat_isInCombat_formatted% placeholder when the player is in combat")
public String isInCombatFormattedTrue = "In Combat";
- @Comment("Text returned by %eternalcombat_isInCombat_formatted% placeholder when the player is out of combat")
+ @Comment("# Text returned by %eternalcombat_isInCombat_formatted% placeholder when the player is out of combat")
public String isInCombatFormattedFalse = "Not In Combat";
}
diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/PluginConfig.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/PluginConfig.java
index bc29665f..1252a7ed 100644
--- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/PluginConfig.java
+++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/PluginConfig.java
@@ -8,6 +8,7 @@
import com.eternalcode.combat.fight.pearl.FightPearlSettings;
import eu.okaeri.configs.OkaeriConfig;
import eu.okaeri.configs.annotation.Comment;
+
import java.time.Duration;
import java.util.List;
@@ -29,6 +30,13 @@ public class PluginConfig extends OkaeriConfig {
})
public Settings settings = new Settings();
+ @Comment({
+ " ",
+ "# Duration formatting settings.",
+ "# Controls how time values (e.g. cooldowns, timers) are displayed."
+ })
+ public DurationFormatSettings durationFormat = new DurationFormatSettings();
+
@Comment({
" ",
"# Settings related to Ender Pearls.",
diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/FightTask.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/FightTask.java
index 76435166..c6757b56 100644
--- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/FightTask.java
+++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/FightTask.java
@@ -3,7 +3,7 @@
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.event.CauseOfUnTag;
import com.eternalcode.combat.notification.NoticeService;
-import com.eternalcode.combat.util.DurationUtil;
+import com.eternalcode.combat.time.DurationService;
import org.bukkit.Server;
import org.bukkit.entity.Player;
@@ -14,12 +14,14 @@ public class FightTask implements Runnable {
private final Server server;
private final PluginConfig config;
+ private final DurationService durationService;
private final FightManager fightManager;
private final NoticeService noticeService;
- public FightTask(Server server, PluginConfig config, FightManager fightManager, NoticeService noticeService) {
+ public FightTask(Server server, PluginConfig config, DurationService durationService, FightManager fightManager, NoticeService noticeService) {
this.server = server;
this.config = config;
+ this.durationService = durationService;
this.fightManager = fightManager;
this.noticeService = noticeService;
}
@@ -45,7 +47,7 @@ public void run() {
this.noticeService.create()
.player(player.getUniqueId())
.notice(this.config.messagesSettings.combatNotification)
- .placeholder("{TIME}", DurationUtil.format(remaining, this.config.messagesSettings.withoutMillis))
+ .placeholder("{TIME}", durationService.format(remaining))
.send();
}
diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/pearl/FightPearlController.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/pearl/FightPearlController.java
index bfd35700..230db5da 100644
--- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/pearl/FightPearlController.java
+++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/pearl/FightPearlController.java
@@ -2,9 +2,7 @@
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.notification.NoticeService;
-import com.eternalcode.combat.util.DurationUtil;
-import java.time.Duration;
-import java.util.UUID;
+import com.eternalcode.combat.time.DurationService;
import org.bukkit.Material;
import org.bukkit.entity.EnderPearl;
import org.bukkit.entity.Player;
@@ -14,23 +12,27 @@
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.ProjectileLaunchEvent;
-import org.bukkit.inventory.ItemStack;
+
+import java.time.Duration;
+import java.util.UUID;
public class FightPearlController implements Listener {
private final FightPearlSettings settings;
private final NoticeService noticeService;
+ private final DurationService durationService;
private final FightManager fightManager;
private final FightPearlService fightPearlService;
public FightPearlController(
FightPearlSettings settings,
- NoticeService noticeService,
+ NoticeService noticeService, DurationService durationService,
FightManager fightManager,
FightPearlService fightPearlService
) {
this.settings = settings;
this.noticeService = noticeService;
+ this.durationService = durationService;
this.fightManager = fightManager;
this.fightPearlService = fightPearlService;
}
@@ -92,7 +94,7 @@ private void handlePearlCooldown(ProjectileLaunchEvent event, Player player, UUI
this.noticeService.create()
.player(playerId)
.notice(this.settings.pearlThrowBlockedDelayDuringCombat)
- .placeholder("{TIME}", DurationUtil.format(remainingDelay))
+ .placeholder("{TIME}", durationService.format(remainingDelay))
.send();
return;
}
diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/tagout/FightTagOutCommand.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/tagout/FightTagOutCommand.java
index 56ef3898..592a4ef1 100644
--- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/tagout/FightTagOutCommand.java
+++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/tagout/FightTagOutCommand.java
@@ -2,15 +2,16 @@
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.notification.NoticeService;
-import com.eternalcode.combat.util.DurationUtil;
+import com.eternalcode.combat.time.DurationService;
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 org.bukkit.entity.Player;
+
import java.time.Duration;
import java.util.UUID;
-import org.bukkit.entity.Player;
@Permission("eternalcombat.tagout")
@Command(name = "tagout", aliases = "tagimmunity")
@@ -18,60 +19,57 @@ public class FightTagOutCommand {
private final FightTagOutService fightTagOutService;
private final NoticeService noticeService;
+ private final DurationService durationService;
private final PluginConfig config;
public FightTagOutCommand(
FightTagOutService fightTagOutService,
- NoticeService noticeService,
+ NoticeService noticeService, DurationService durationService,
PluginConfig config
) {
this.fightTagOutService = fightTagOutService;
this.noticeService = noticeService;
+ this.durationService = durationService;
this.config = config;
}
@Execute
- void tagout(@Context Player sender, @Arg Duration time) {
- UUID targetUniqueId = sender.getUniqueId();
-
- this.fightTagOutService.tagOut(targetUniqueId, time);
+ void tagOut(@Context Player sender, @Arg Duration time) {
+ this.fightTagOutService.tagOut(sender.getUniqueId(), time);
this.noticeService.create()
.notice(this.config.messagesSettings.admin.adminTagOutSelf)
- .placeholder("{TIME}", DurationUtil.format(time))
+ .placeholder("{TIME}", durationService.format(time))
.viewer(sender)
.send();
}
@Execute
- void tagout(@Context Player sender, @Arg Player target, @Arg Duration time) {
- UUID targetUniqueId = target.getUniqueId();
-
- this.fightTagOutService.tagOut(targetUniqueId, time);
+ void tagOut(@Context Player sender, @Arg Player target, @Arg Duration time) {
+ this.fightTagOutService.tagOut(target.getUniqueId(), time);
this.noticeService.create()
.notice(this.config.messagesSettings.admin.adminTagOut)
.placeholder("{PLAYER}", target.getName())
- .placeholder("{TIME}", DurationUtil.format(time))
+ .placeholder("{TIME}", durationService.format(time))
.viewer(sender)
.send();
this.noticeService.create()
.notice(this.config.messagesSettings.admin.playerTagOut)
- .placeholder("{TIME}", DurationUtil.format(time))
+ .placeholder("{TIME}", durationService.format(time))
.player(target.getUniqueId())
.send();
}
@Execute(name = "remove")
- void untagout(@Context Player sender, @Arg Player target) {
+ void unTagOut(@Context Player sender, @Arg Player target) {
UUID targetUniqueId = target.getUniqueId();
this.fightTagOutService.unTagOut(targetUniqueId);
-
if (!targetUniqueId.equals(sender.getUniqueId())) {
this.noticeService.create()
.notice(this.config.messagesSettings.admin.adminTagOutOff)
@@ -87,10 +85,8 @@ void untagout(@Context Player sender, @Arg Player target) {
}
@Execute(name = "remove")
- void untagout(@Context Player sender) {
- UUID senderUniqueId = sender.getUniqueId();
-
- this.fightTagOutService.unTagOut(senderUniqueId);
+ void unTagout(@Context Player sender) {
+ this.fightTagOutService.unTagOut(sender.getUniqueId());
this.noticeService.create()
.notice(this.config.messagesSettings.admin.playerTagOutOff)
diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/time/DurationFormatter.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/time/DurationFormatter.java
new file mode 100644
index 00000000..86348cd4
--- /dev/null
+++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/time/DurationFormatter.java
@@ -0,0 +1,124 @@
+package com.eternalcode.combat.time;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
+
+final class DurationFormatter {
+
+ private final Token[] tokens;
+ private final String separator;
+ private final String lastSeparator;
+ private final String zero;
+
+ DurationFormatter(
+ @NotNull String pattern,
+ @NotNull String separator,
+ @NotNull String lastSeparator,
+ @NotNull String zero
+ ) {
+ this.separator = separator;
+ this.lastSeparator = lastSeparator;
+ this.zero = zero;
+
+ this.tokens = parsePattern(pattern);
+ }
+
+ String format(Duration duration) {
+ if (duration.isZero() || duration.isNegative()) {
+ return zero;
+ }
+
+ int count = 0;
+
+ for (Token token : tokens) {
+ if (token.unit.extract(duration) > 0) {
+ count++;
+ }
+ }
+
+ if (count == 0) {
+ return zero;
+ }
+
+ StringBuilder result = new StringBuilder(tokens.length * 16);
+
+ int index = 0;
+
+ for (Token token : tokens) {
+ long value = token.unit.extract(duration);
+ if (value <= 0) {
+ continue;
+ }
+
+ if (index > 0) {
+ result.append(index == count - 1 ? lastSeparator : separator);
+ }
+
+ String word = value == 1 ? token.singular : token.plural;
+
+ if (token.spaceBetween) {
+ result.append(value).append(' ').append(word);
+ }
+ else {
+ result.append(value).append(word);
+ }
+
+ index++;
+ }
+
+ return result.toString();
+ }
+
+ private static Token[] parsePattern(String pattern) {
+ List tokens = new ArrayList<>(4);
+
+ for (int i = 0; i < pattern.length(); i++) {
+
+ char c = pattern.charAt(i);
+ if (c != '%') {
+ continue;
+ }
+
+ if (++i >= pattern.length()) {
+ throw new IllegalArgumentException("Incomplete placeholder in pattern");
+ }
+
+
+ char symbol = pattern.charAt(i);
+ DurationUnit unit = DurationUnit.fromSymbol(symbol);
+
+ int braceIndex = pattern.indexOf('{', i + 1);
+ if (braceIndex == -1) {
+ throw new IllegalArgumentException("Missing plural definition after %" + symbol);
+ }
+
+ boolean spaceBetween = braceIndex > i + 1;
+
+ int start = braceIndex + 1;
+ int end = pattern.indexOf('}', start);
+ if (end == -1) {
+ throw new IllegalArgumentException("Unclosed plural definition in pattern");
+ }
+
+ String block = pattern.substring(start, end);
+
+ int split = block.indexOf('|');
+ if (split == -1) {
+ throw new IllegalArgumentException("Plural must be singular|plural");
+ }
+
+ String singular = block.substring(0, split);
+ String plural = block.substring(split + 1);
+
+ tokens.add(new Token(unit, singular, plural, spaceBetween));
+ i = end;
+ }
+
+ return tokens.toArray(new Token[0]);
+ }
+
+ private record Token(DurationUnit unit, String singular, String plural, boolean spaceBetween) { }
+}
diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/time/DurationService.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/time/DurationService.java
new file mode 100644
index 00000000..7d9c4737
--- /dev/null
+++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/time/DurationService.java
@@ -0,0 +1,28 @@
+package com.eternalcode.combat.time;
+
+import com.eternalcode.combat.config.implementation.DurationFormatSettings;
+import org.jetbrains.annotations.NotNull;
+
+import java.time.Duration;
+
+public final class DurationService {
+
+ private final DurationFormatter formatter;
+
+ private DurationService(DurationFormatter formatter) {
+ this.formatter = formatter;
+ }
+
+ public static DurationService ofConfig(@NotNull DurationFormatSettings config) {
+ return new DurationService(new DurationFormatter(
+ config.pattern,
+ config.separator,
+ config.lastSeparator,
+ config.zero
+ ));
+ }
+
+ public String format(@NotNull Duration duration) {
+ return this.formatter.format(duration);
+ }
+}
diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/time/DurationUnit.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/time/DurationUnit.java
new file mode 100644
index 00000000..af97fe11
--- /dev/null
+++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/time/DurationUnit.java
@@ -0,0 +1,54 @@
+package com.eternalcode.combat.time;
+
+import java.time.Duration;
+
+enum DurationUnit {
+
+ DAYS('d') {
+ @Override
+ long extract(Duration duration) {
+ return duration.toDaysPart();
+ }
+ },
+
+ HOURS('h') {
+ @Override
+ long extract(Duration duration) {
+ return duration.toHoursPart();
+ }
+ },
+
+ MINUTES('m') {
+ @Override
+ long extract(Duration duration) {
+ return duration.toMinutesPart();
+ }
+ },
+
+ SECONDS('s') {
+ @Override
+ long extract(Duration duration) {
+ return duration.toSecondsPart();
+ }
+ };
+
+ private static final DurationUnit[] VALUES = values();
+
+ private final char symbol;
+
+ DurationUnit(char symbol) {
+ this.symbol = symbol;
+ }
+
+ abstract long extract(Duration duration);
+
+ static DurationUnit fromSymbol(char symbol) {
+ for (DurationUnit unit : VALUES) {
+ if (unit.symbol == symbol) {
+ return unit;
+ }
+ }
+
+ throw new IllegalArgumentException("Unknown duration unit: %" + symbol);
+ }
+}
diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/util/DurationUtil.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/util/DurationUtil.java
deleted file mode 100644
index 3333dac9..00000000
--- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/util/DurationUtil.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package com.eternalcode.combat.util;
-
-import com.eternalcode.commons.time.DurationParser;
-import com.eternalcode.commons.time.TemporalAmountParser;
-import java.math.RoundingMode;
-import java.time.Duration;
-import java.time.temporal.ChronoUnit;
-
-public class DurationUtil {
-
- private static final TemporalAmountParser WITHOUT_MILLIS_FORMAT = new DurationParser()
- .withUnit("s", ChronoUnit.SECONDS)
- .withUnit("m", ChronoUnit.MINUTES)
- .withUnit("h", ChronoUnit.HOURS)
- .withUnit("d", ChronoUnit.DAYS)
- .withRounded(ChronoUnit.MILLIS, RoundingMode.UP);
-
- private static final TemporalAmountParser STANDARD_FORMAT = new DurationParser()
- .withUnit("d", ChronoUnit.DAYS)
- .withUnit("h", ChronoUnit.HOURS)
- .withUnit("m", ChronoUnit.MINUTES)
- .withUnit("s", ChronoUnit.SECONDS)
- .withUnit("ms", ChronoUnit.MILLIS);
-
- public static final Duration ONE_SECOND = Duration.ofSeconds(1);
-
- public DurationUtil() {
- throw new UnsupportedOperationException("This class cannot be instantiated");
- }
-
- public static String format(Duration duration, boolean removeMillis) {
- if (removeMillis) {
- if (duration.toMillis() < ONE_SECOND.toMillis()) {
- return "0s";
- }
-
- return WITHOUT_MILLIS_FORMAT.format(duration);
- }
-
- if (duration.toMillis() > ONE_SECOND.toMillis()) {
- return WITHOUT_MILLIS_FORMAT.format(duration);
- }
-
- return STANDARD_FORMAT.format(duration);
- }
-
- public static String format(Duration duration) {
- return format(duration, true);
- }
-}