Skip to content

Commit c81f73a

Browse files
committed
Introduce configurable duration formatter with plural forms and separators
1 parent 96deb78 commit c81f73a

15 files changed

Lines changed: 329 additions & 132 deletions

eternalcombat-plugin/src/main/java/com/eternalcode/combat/CombatPlugin.java

Lines changed: 28 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,45 @@
11
package com.eternalcode.combat;
22

3-
import com.eternalcode.combat.border.BorderTriggerController;
43
import com.eternalcode.combat.border.BorderService;
54
import com.eternalcode.combat.border.BorderServiceImpl;
5+
import com.eternalcode.combat.border.BorderTriggerController;
66
import com.eternalcode.combat.border.animation.block.BorderBlockController;
77
import com.eternalcode.combat.border.animation.particle.ParticleController;
88
import com.eternalcode.combat.bridge.BridgeService;
9-
import com.eternalcode.combat.crystalpvp.RespawnAnchorListener;
10-
import com.eternalcode.combat.crystalpvp.EndCrystalListener;
11-
import com.eternalcode.combat.fight.controller.FightBypassAdminController;
12-
import com.eternalcode.combat.fight.controller.FightBypassCreativeController;
13-
import com.eternalcode.combat.fight.controller.FightBypassPermissionController;
14-
import com.eternalcode.combat.fight.controller.FightInventoryController;
15-
import com.eternalcode.combat.fight.death.DeathEffectController;
16-
import com.eternalcode.combat.fight.drop.DropKeepInventoryService;
17-
import com.eternalcode.combat.fight.FightManager;
18-
import com.eternalcode.combat.fight.drop.DropService;
19-
import com.eternalcode.combat.fight.effect.FightEffectService;
20-
import com.eternalcode.combat.fight.firework.FireworkController;
21-
import com.eternalcode.combat.fight.knockback.KnockbackService;
22-
import com.eternalcode.combat.fight.tagout.FightTagOutService;
23-
import com.eternalcode.combat.fight.pearl.FightPearlService;
24-
import com.eternalcode.combat.handler.InvalidUsageHandlerImpl;
25-
import com.eternalcode.combat.handler.MissingPermissionHandlerImpl;
269
import com.eternalcode.combat.config.ConfigService;
2710
import com.eternalcode.combat.config.implementation.PluginConfig;
28-
import com.eternalcode.combat.fight.drop.DropController;
29-
import com.eternalcode.combat.fight.drop.DropKeepInventoryServiceImpl;
30-
import com.eternalcode.combat.fight.drop.DropServiceImpl;
31-
import com.eternalcode.combat.fight.drop.impl.PercentDropModifier;
32-
import com.eternalcode.combat.fight.drop.impl.PlayersHealthDropModifier;
33-
import com.eternalcode.combat.fight.FightTagCommand;
34-
import com.eternalcode.combat.fight.controller.FightActionBlockerController;
35-
import com.eternalcode.combat.fight.controller.FightMessageController;
36-
import com.eternalcode.combat.fight.controller.FightTagController;
37-
import com.eternalcode.combat.fight.controller.FightUnTagController;
38-
import com.eternalcode.combat.fight.effect.FightEffectController;
11+
import com.eternalcode.combat.crystalpvp.EndCrystalListener;
12+
import com.eternalcode.combat.crystalpvp.RespawnAnchorListener;
3913
import com.eternalcode.combat.event.EventManager;
14+
import com.eternalcode.combat.fight.FightManager;
4015
import com.eternalcode.combat.fight.FightManagerImpl;
16+
import com.eternalcode.combat.fight.FightTagCommand;
4117
import com.eternalcode.combat.fight.FightTask;
18+
import com.eternalcode.combat.fight.controller.*;
19+
import com.eternalcode.combat.fight.death.DeathEffectController;
20+
import com.eternalcode.combat.fight.drop.*;
21+
import com.eternalcode.combat.fight.drop.impl.PercentDropModifier;
22+
import com.eternalcode.combat.fight.drop.impl.PlayersHealthDropModifier;
23+
import com.eternalcode.combat.fight.effect.FightEffectController;
24+
import com.eternalcode.combat.fight.effect.FightEffectService;
4225
import com.eternalcode.combat.fight.effect.FightEffectServiceImpl;
26+
import com.eternalcode.combat.fight.firework.FireworkController;
27+
import com.eternalcode.combat.fight.knockback.KnockbackRegionController;
28+
import com.eternalcode.combat.fight.knockback.KnockbackService;
4329
import com.eternalcode.combat.fight.logout.LogoutController;
4430
import com.eternalcode.combat.fight.logout.LogoutService;
4531
import com.eternalcode.combat.fight.pearl.FightPearlController;
32+
import com.eternalcode.combat.fight.pearl.FightPearlService;
4633
import com.eternalcode.combat.fight.pearl.FightPearlServiceImpl;
34+
import com.eternalcode.combat.fight.tagout.FightTagOutCommand;
4735
import com.eternalcode.combat.fight.tagout.FightTagOutController;
36+
import com.eternalcode.combat.fight.tagout.FightTagOutService;
4837
import com.eternalcode.combat.fight.tagout.FightTagOutServiceImpl;
49-
import com.eternalcode.combat.fight.tagout.FightTagOutCommand;
38+
import com.eternalcode.combat.handler.InvalidUsageHandlerImpl;
39+
import com.eternalcode.combat.handler.MissingPermissionHandlerImpl;
5040
import com.eternalcode.combat.notification.NoticeService;
51-
import com.eternalcode.combat.fight.knockback.KnockbackRegionController;
5241
import com.eternalcode.combat.region.RegionProvider;
42+
import com.eternalcode.combat.time.DurationService;
5343
import com.eternalcode.combat.updater.UpdaterNotificationController;
5444
import com.eternalcode.combat.updater.UpdaterService;
5545
import com.eternalcode.commons.adventure.AdventureLegacyColorPostProcessor;
@@ -61,7 +51,6 @@
6151
import dev.rollczi.litecommands.bukkit.LiteBukkitFactory;
6252
import dev.rollczi.litecommands.bukkit.LiteBukkitMessages;
6353
import dev.rollczi.litecommands.folia.FoliaExtension;
64-
import java.time.Duration;
6554
import net.kyori.adventure.platform.AudienceProvider;
6655
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
6756
import net.kyori.adventure.text.minimessage.MiniMessage;
@@ -73,6 +62,7 @@
7362
import org.bukkit.plugin.java.JavaPlugin;
7463

7564
import java.io.File;
65+
import java.time.Duration;
7666
import java.util.concurrent.TimeUnit;
7767
import java.util.stream.Stream;
7868

@@ -109,6 +99,8 @@ public void onEnable() {
10999

110100
MinecraftScheduler scheduler = CombatSchedulerAdapter.getAdaptiveScheduler(this);
111101

102+
DurationService durationService = DurationService.ofConfig(pluginConfig.durationFormat);
103+
112104
this.fightManager = new FightManagerImpl(eventManager);
113105
this.fightPearlService = new FightPearlServiceImpl(pluginConfig.pearl);
114106
this.fightTagOutService = new FightTagOutServiceImpl();
@@ -134,6 +126,7 @@ public void onEnable() {
134126
server.getPluginManager(),
135127
this.getLogger(),
136128
this,
129+
durationService,
137130
this.fightManager
138131
);
139132
bridgeService.init(server);
@@ -151,7 +144,7 @@ public void onEnable() {
151144

152145
.commands(
153146
new FightTagCommand(this.fightManager, noticeService, pluginConfig),
154-
new FightTagOutCommand(this.fightTagOutService, noticeService, pluginConfig),
147+
new FightTagOutCommand(this.fightTagOutService, noticeService, durationService, pluginConfig),
155148
new EternalCombatReloadCommand(configService, noticeService)
156149
)
157150

@@ -164,7 +157,7 @@ public void onEnable() {
164157

165158
.build();
166159

167-
FightTask fightTask = new FightTask(server, pluginConfig, this.fightManager, noticeService);
160+
FightTask fightTask = new FightTask(server, pluginConfig, durationService, this.fightManager, noticeService);
168161
scheduler.timer(fightTask, Duration.ofSeconds(1), Duration.ofSeconds(1));
169162

170163
new Metrics(this, BSTATS_METRICS_ID);
@@ -181,7 +174,7 @@ public void onEnable() {
181174
new FightBypassPermissionController(server, pluginConfig),
182175
new FightBypassCreativeController(server, pluginConfig),
183176
new FightActionBlockerController(this.fightManager, noticeService, pluginConfig, server),
184-
new FightPearlController(pluginConfig.pearl, noticeService, this.fightManager, this.fightPearlService),
177+
new FightPearlController(pluginConfig.pearl, noticeService, durationService, this.fightManager, this.fightPearlService),
185178
new DeathEffectController(pluginConfig),
186179
new UpdaterNotificationController(updaterService, pluginConfig, this.audienceProvider, miniMessage),
187180
new KnockbackRegionController(noticeService, this.regionProvider, this.fightManager, knockbackService, server),

eternalcombat-plugin/src/main/java/com/eternalcode/combat/bridge/BridgeService.java

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,30 @@
11
package com.eternalcode.combat.bridge;
22

3-
import com.eternalcode.combat.region.lands.LandsRegionProvider;
43
import com.eternalcode.combat.bridge.placeholder.FightTagPlaceholder;
54
import com.eternalcode.combat.config.implementation.PluginConfig;
65
import com.eternalcode.combat.fight.FightManager;
76
import com.eternalcode.combat.region.CompositeRegionProvider;
8-
import com.eternalcode.combat.region.bukkit.DefaultRegionProvider;
97
import com.eternalcode.combat.region.RegionProvider;
8+
import com.eternalcode.combat.region.bukkit.DefaultRegionProvider;
9+
import com.eternalcode.combat.region.lands.LandsRegionProvider;
1010
import com.eternalcode.combat.region.worldguard.WorldGuardRegionProvider;
11+
import com.eternalcode.combat.time.DurationService;
12+
import org.bukkit.Server;
13+
import org.bukkit.plugin.Plugin;
14+
import org.bukkit.plugin.PluginManager;
15+
1116
import java.util.ArrayList;
1217
import java.util.List;
1318
import java.util.logging.Logger;
1419
import java.util.stream.Collectors;
15-
import org.bukkit.Server;
16-
import org.bukkit.plugin.Plugin;
17-
import org.bukkit.plugin.PluginManager;
1820

1921
public class BridgeService {
2022

2123
private final PluginConfig config;
2224
private final PluginManager pluginManager;
2325
private final Logger logger;
2426
private final Plugin plugin;
27+
private final DurationService durationService;
2528
private final FightManager fightManager;
2629

2730
private RegionProvider regionProvider;
@@ -31,12 +34,14 @@ public BridgeService(
3134
PluginManager pluginManager,
3235
Logger logger,
3336
Plugin plugin,
37+
DurationService durationService,
3438
FightManager fightManager
3539
) {
3640
this.config = config;
3741
this.pluginManager = pluginManager;
3842
this.logger = logger;
3943
this.plugin = plugin;
44+
this.durationService = durationService;
4045
this.fightManager = fightManager;
4146
}
4247

@@ -69,7 +74,7 @@ public void init(Server server) {
6974

7075
initialize(
7176
"PlaceholderAPI",
72-
() -> new FightTagPlaceholder(this.config, this.fightManager, server, this.plugin).register(),
77+
() -> new FightTagPlaceholder(this.config, durationService, this.fightManager, server, this.plugin).register(),
7378
() -> this.logger.warning("PlaceholderAPI not found; skipping placeholders.")
7479
);
7580
}

eternalcombat-plugin/src/main/java/com/eternalcode/combat/bridge/placeholder/FightTagPlaceholder.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,30 @@
44
import com.eternalcode.combat.config.implementation.PluginConfig;
55
import com.eternalcode.combat.fight.FightManager;
66
import com.eternalcode.combat.fight.FightTag;
7-
import com.eternalcode.combat.util.DurationUtil;
7+
import com.eternalcode.combat.time.DurationService;
88
import com.eternalcode.commons.time.DurationParser;
9-
import java.util.Optional;
109
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
1110
import org.bukkit.OfflinePlayer;
1211
import org.bukkit.Server;
1312
import org.bukkit.entity.Player;
1413
import org.bukkit.plugin.Plugin;
1514
import org.jetbrains.annotations.NotNull;
1615

16+
import java.util.Optional;
17+
1718
public class FightTagPlaceholder extends PlaceholderExpansion {
1819

1920
private static final String IDENTIFIER = "eternalcombat";
2021

2122
private final PlaceholderSettings placeholderSettings;
23+
private final DurationService durationService;
2224
private final FightManager fightManager;
2325
private final Server server;
2426
private final Plugin plugin;
2527

26-
public FightTagPlaceholder(PluginConfig pluginConfig, FightManager fightManager, Server server, Plugin plugin) {
28+
public FightTagPlaceholder(PluginConfig pluginConfig, DurationService durationService, FightManager fightManager, Server server, Plugin plugin) {
2729
this.placeholderSettings = pluginConfig.placeholders;
30+
this.durationService = durationService;
2831
this.fightManager = fightManager;
2932
this.server = server;
3033
this.plugin = plugin;
@@ -51,7 +54,7 @@ private String handleRemainingMillis(OfflinePlayer player) {
5154

5255
private String handleRemainingSeconds(OfflinePlayer player) {
5356
return this.getFightTag(player)
54-
.map(tag -> DurationUtil.format(tag.getRemainingDuration()))
57+
.map(tag -> durationService.format(tag.getRemainingDuration()))
5558
.orElse("");
5659
}
5760

@@ -110,7 +113,7 @@ public boolean canRegister() {
110113

111114
@Override
112115
public @NotNull String getAuthor() {
113-
return this.plugin.getDescription().getAuthors().get(0);
116+
return this.plugin.getDescription().getAuthors().getFirst();
114117
}
115118

116119
@Override

eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/CrystalPvpSettings.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ public class CrystalPvpSettings extends OkaeriConfig {
88
@Comment({"# Should player be tagged when damaged from crystal explosion set by other player"})
99
public boolean tagFromCrystals = true;
1010

11-
12-
@Comment({"#Should player be tagged when damaged from respawn anchor explosion set by other player"})
11+
@Comment({"# Should player be tagged when damaged from respawn anchor explosion set by other player"})
1312
public boolean tagFromRespawnAnchor = true;
1413
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.eternalcode.combat.config.implementation;
2+
3+
import eu.okaeri.configs.OkaeriConfig;
4+
import eu.okaeri.configs.annotation.Comment;
5+
6+
public class DurationFormatSettings extends OkaeriConfig {
7+
8+
@Comment({
9+
"# Pattern used to format durations.",
10+
"# Placeholders:",
11+
"# %d{singular|plural} - days",
12+
"# %h{singular|plural} - hours",
13+
"# %m{singular|plural} - minutes",
14+
"# %s{singular|plural} - seconds",
15+
"# Example:",
16+
"# %d{day|days} %h{hour|hours} %m{minute|minutes} %s{second|seconds}"
17+
})
18+
public String pattern = "%d{day|days} %h{hour|hours} %m{minute|minutes} %s{second|seconds}";
19+
20+
@Comment({
21+
"# Separator used between duration parts.",
22+
"# Example result:",
23+
"# 1 hour, 2 minutes, 5 seconds"
24+
})
25+
public String separator = ", ";
26+
27+
@Comment({
28+
"# Separator used before the last duration part.",
29+
"# Example result:",
30+
"# 1 hour, 2 minutes and 5 seconds"
31+
})
32+
public String lastSeparator = " and ";
33+
34+
@Comment({"# Text displayed when duration is zero or negative."})
35+
public String zero = "<1 second";
36+
}

eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/MessagesSettings.java

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,6 @@ public class MessagesSettings extends OkaeriConfig {
2323
.actionBar("Combat ends in: <red>{TIME}</red>")
2424
.build();
2525

26-
@Comment({
27-
"# Would you like to display milliseconds instead of seconds in combat notification "
28-
})
29-
public boolean withoutMillis = true;
30-
3126
@Comment({
3227
"# Message displayed when a player lacks permission to execute a command.",
3328
"# The {PERMISSION} placeholder is replaced with the required permission."

eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/PlaceholderSettings.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55

66
public class PlaceholderSettings extends OkaeriConfig {
77

8-
@Comment("Text returned by %eternalcombat_isInCombat_formatted% placeholder when the player is in combat")
8+
@Comment("# Text returned by %eternalcombat_isInCombat_formatted% placeholder when the player is in combat")
99
public String isInCombatFormattedTrue = "In Combat";
1010

11-
@Comment("Text returned by %eternalcombat_isInCombat_formatted% placeholder when the player is out of combat")
11+
@Comment("# Text returned by %eternalcombat_isInCombat_formatted% placeholder when the player is out of combat")
1212
public String isInCombatFormattedFalse = "Not In Combat";
1313

1414
}

eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/PluginConfig.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import com.eternalcode.combat.fight.pearl.FightPearlSettings;
99
import eu.okaeri.configs.OkaeriConfig;
1010
import eu.okaeri.configs.annotation.Comment;
11+
1112
import java.time.Duration;
1213
import java.util.List;
1314

@@ -29,6 +30,13 @@ public class PluginConfig extends OkaeriConfig {
2930
})
3031
public Settings settings = new Settings();
3132

33+
@Comment({
34+
" ",
35+
"# Duration formatting settings.",
36+
"# Controls how time values (e.g. cooldowns, timers) are displayed."
37+
})
38+
public DurationFormatSettings durationFormat = new DurationFormatSettings();
39+
3240
@Comment({
3341
" ",
3442
"# Settings related to Ender Pearls.",

eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/FightTask.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import com.eternalcode.combat.config.implementation.PluginConfig;
44
import com.eternalcode.combat.fight.event.CauseOfUnTag;
55
import com.eternalcode.combat.notification.NoticeService;
6-
import com.eternalcode.combat.util.DurationUtil;
6+
import com.eternalcode.combat.time.DurationService;
77
import org.bukkit.Server;
88
import org.bukkit.entity.Player;
99

@@ -14,12 +14,14 @@ public class FightTask implements Runnable {
1414

1515
private final Server server;
1616
private final PluginConfig config;
17+
private final DurationService durationService;
1718
private final FightManager fightManager;
1819
private final NoticeService noticeService;
1920

20-
public FightTask(Server server, PluginConfig config, FightManager fightManager, NoticeService noticeService) {
21+
public FightTask(Server server, PluginConfig config, DurationService durationService, FightManager fightManager, NoticeService noticeService) {
2122
this.server = server;
2223
this.config = config;
24+
this.durationService = durationService;
2325
this.fightManager = fightManager;
2426
this.noticeService = noticeService;
2527
}
@@ -45,7 +47,7 @@ public void run() {
4547
this.noticeService.create()
4648
.player(player.getUniqueId())
4749
.notice(this.config.messagesSettings.combatNotification)
48-
.placeholder("{TIME}", DurationUtil.format(remaining, this.config.messagesSettings.withoutMillis))
50+
.placeholder("{TIME}", durationService.format(remaining))
4951
.send();
5052

5153
}

0 commit comments

Comments
 (0)