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); - } -}