feat(config): configure aura progression
This commit is contained in:
@@ -19,3 +19,4 @@ description: Chronological record of material changes to the Spigot Creeper Fear
|
|||||||
- Extended US-001 so a self-destructing creeper awards deduplicated progress and normal feedback to every player its explosion hits.
|
- Extended US-001 so a self-destructing creeper awards deduplicated progress and normal feedback to every player its explosion hits.
|
||||||
- Completed US-004 with an asynchronous self-service progress command, ordinary-player permission, completion output, and command metadata.
|
- Completed US-004 with an asynchronous self-service progress command, ordinary-player permission, completion output, and command metadata.
|
||||||
- Completed US-005 with UUID-backed offline inspection, atomic progress and rank administration, separate permissions, online-state refresh, and audit logging.
|
- Completed US-005 with UUID-backed offline inspection, atomic progress and rank administration, separate permissions, online-state refresh, and audit logging.
|
||||||
|
- Completed US-006 with validated per-rank requirements and multipliers, configurable feedback, persistent threshold administration, and safe reload behavior.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
type: User Story
|
type: User Story
|
||||||
title: "US-006: Configure aura progression"
|
title: "US-006: Configure aura progression"
|
||||||
description: Let administrators safely configure and persist per-rank requirements, multipliers, and feedback.
|
description: Let administrators safely configure and persist per-rank requirements, multipliers, and feedback.
|
||||||
status: backlog
|
status: done
|
||||||
---
|
---
|
||||||
|
|
||||||
# US-006: Configure aura progression
|
# US-006: Configure aura progression
|
||||||
@@ -11,18 +11,18 @@ As a **server administrator**, I want to configure progression and aura behavior
|
|||||||
|
|
||||||
## Acceptance criteria
|
## Acceptance criteria
|
||||||
|
|
||||||
- [ ] Configuration provides documented defaults for the six per-rank kill requirements and six unlocked-rank damage multipliers.
|
- [x] Configuration provides documented defaults for the six per-rank kill requirements and six unlocked-rank damage multipliers.
|
||||||
- [ ] Rank requirements are positive integers.
|
- [x] Rank requirements are positive integers.
|
||||||
- [ ] Damage multipliers are finite and non-negative.
|
- [x] Damage multipliers are finite and non-negative.
|
||||||
- [ ] Progress boss-bar duration and player-facing rank messages are configurable.
|
- [x] Progress boss-bar duration and player-facing rank messages are configurable.
|
||||||
- [ ] `/creeperaura threshold <rank> <points>` validates, applies, and persists the points required to unlock that rank.
|
- [x] `/creeperaura threshold <rank> <points>` validates, applies, and persists the points required to unlock that rank.
|
||||||
- [ ] `/creeperaura reload` safely loads externally edited configuration without requiring a server restart.
|
- [x] `/creeperaura reload` safely loads externally edited configuration without requiring a server restart.
|
||||||
- [ ] Threshold-management and reload commands require documented administrative permissions.
|
- [x] Threshold-management and reload commands require documented administrative permissions.
|
||||||
- [ ] Invalid configuration is rejected with actionable diagnostics while the last valid configuration remains active.
|
- [x] Invalid configuration is rejected with actionable diagnostics while the last valid configuration remains active.
|
||||||
- [ ] Changing requirements never automatically removes an unlocked rank, grants a new rank, or discards current-tier progress.
|
- [x] Changing requirements never automatically removes an unlocked rank, grants a new rank, or discards current-tier progress.
|
||||||
- [ ] After a requirement change, the player's next qualifying kill can grant at most the next rank when its requirement is satisfied.
|
- [x] After a requirement change, the player's next qualifying kill can grant at most the next rank when its requirement is satisfied.
|
||||||
- [ ] Rank advancement resets current-tier progress to zero rather than carrying excess progress forward.
|
- [x] Rank advancement resets current-tier progress to zero rather than carrying excess progress forward.
|
||||||
- [ ] Configuration-command changes survive plugin and server restarts.
|
- [x] Configuration-command changes survive plugin and server restarts.
|
||||||
|
|
||||||
## Related
|
## Related
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import games.dmg.creeperfear.aura.CreeperAuraListener;
|
|||||||
import games.dmg.creeperfear.command.CreeperAuraCommand;
|
import games.dmg.creeperfear.command.CreeperAuraCommand;
|
||||||
import games.dmg.creeperfear.command.ProgressMessages;
|
import games.dmg.creeperfear.command.ProgressMessages;
|
||||||
import games.dmg.creeperfear.command.ProgressMutations;
|
import games.dmg.creeperfear.command.ProgressMutations;
|
||||||
|
import games.dmg.creeperfear.config.AuraConfigLoader;
|
||||||
|
import games.dmg.creeperfear.config.AuraConfigurationManager;
|
||||||
import games.dmg.creeperfear.feedback.ProgressDisplay;
|
import games.dmg.creeperfear.feedback.ProgressDisplay;
|
||||||
import games.dmg.creeperfear.feedback.ProgressFeedback;
|
import games.dmg.creeperfear.feedback.ProgressFeedback;
|
||||||
import games.dmg.creeperfear.listener.CreeperDeathListener;
|
import games.dmg.creeperfear.listener.CreeperDeathListener;
|
||||||
@@ -28,8 +30,10 @@ public final class CreeperFearPlugin extends JavaPlugin {
|
|||||||
Path databasePath = getDataFolder().toPath().resolve("player-progress.sqlite3");
|
Path databasePath = getDataFolder().toPath().resolve("player-progress.sqlite3");
|
||||||
AuraRules auraRules = AuraRules.defaults();
|
AuraRules auraRules = AuraRules.defaults();
|
||||||
progressService = new ProgressService(new SqliteProgressRepository(databasePath), auraRules);
|
progressService = new ProgressService(new SqliteProgressRepository(databasePath), auraRules);
|
||||||
long displayTicks = Math.max(1L, getConfig().getLong("feedback.progress-bar-seconds", 5L)) * 20L;
|
progressFeedback = new ProgressFeedback(this, new ProgressDisplay(auraRules), 100L);
|
||||||
progressFeedback = new ProgressFeedback(this, new ProgressDisplay(auraRules), displayTicks);
|
AuraConfigurationManager configurationManager = new AuraConfigurationManager(
|
||||||
|
this, new AuraConfigLoader(), auraRules, progressFeedback);
|
||||||
|
configurationManager.applyCurrent();
|
||||||
getServer().getPluginManager().registerEvents(
|
getServer().getPluginManager().registerEvents(
|
||||||
new CreeperDeathListener(progressService, progressFeedback, getLogger()), this);
|
new CreeperDeathListener(progressService, progressFeedback, getLogger()), this);
|
||||||
getServer().getPluginManager().registerEvents(
|
getServer().getPluginManager().registerEvents(
|
||||||
@@ -44,7 +48,8 @@ public final class CreeperFearPlugin extends JavaPlugin {
|
|||||||
progressService,
|
progressService,
|
||||||
new ProgressMessages(auraRules),
|
new ProgressMessages(auraRules),
|
||||||
new ProgressMutations(),
|
new ProgressMutations(),
|
||||||
progressFeedback));
|
progressFeedback,
|
||||||
|
configurationManager));
|
||||||
getServer().getOnlinePlayers().forEach(player -> progressService.loadOnline(player.getUniqueId())
|
getServer().getOnlinePlayers().forEach(player -> progressService.loadOnline(player.getUniqueId())
|
||||||
.exceptionally(failure -> {
|
.exceptionally(failure -> {
|
||||||
getLogger().log(Level.SEVERE,
|
getLogger().log(Level.SEVERE,
|
||||||
|
|||||||
@@ -2,24 +2,22 @@ package games.dmg.creeperfear.aura;
|
|||||||
|
|
||||||
import games.dmg.creeperfear.progress.AuraRank;
|
import games.dmg.creeperfear.progress.AuraRank;
|
||||||
import games.dmg.creeperfear.progress.PlayerProgress;
|
import games.dmg.creeperfear.progress.PlayerProgress;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.EnumMap;
|
import java.util.EnumMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public final class AuraRules {
|
public final class AuraRules {
|
||||||
private final Map<AuraRank, Integer> requirements;
|
private volatile Map<AuraRank, Integer> requirements;
|
||||||
private final Map<AuraRank, Double> damageMultipliers;
|
private volatile Map<AuraRank, Double> damageMultipliers;
|
||||||
|
|
||||||
public AuraRules(Map<AuraRank, Integer> requirements, Map<AuraRank, Double> damageMultipliers) {
|
public AuraRules(Map<AuraRank, Integer> requirements, Map<AuraRank, Double> damageMultipliers) {
|
||||||
this.requirements = new EnumMap<>(requirements);
|
replace(requirements, damageMultipliers);
|
||||||
this.damageMultipliers = new EnumMap<>(damageMultipliers);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static AuraRules defaults() {
|
public static AuraRules defaults() {
|
||||||
Map<AuraRank, Integer> requirements = new EnumMap<>(AuraRank.class);
|
Map<AuraRank, Integer> requirements = new EnumMap<>(AuraRank.class);
|
||||||
for (AuraRank rank : AuraRank.values()) {
|
for (AuraRank rank : AuraRank.values()) {
|
||||||
if (!rank.isMaximum()) {
|
if (!rank.isMaximum()) requirements.put(rank, 100);
|
||||||
requirements.put(rank, 100);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Map<AuraRank, Double> multipliers = new EnumMap<>(AuraRank.class);
|
Map<AuraRank, Double> multipliers = new EnumMap<>(AuraRank.class);
|
||||||
multipliers.put(AuraRank.LOCKED, 1.0);
|
multipliers.put(AuraRank.LOCKED, 1.0);
|
||||||
@@ -32,16 +30,36 @@ public final class AuraRules {
|
|||||||
return new AuraRules(requirements, multipliers);
|
return new AuraRules(requirements, multipliers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public synchronized void replaceWith(AuraRules replacement) {
|
||||||
|
replace(replacement.requirements, replacement.damageMultipliers);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void replace(Map<AuraRank, Integer> newRequirements, Map<AuraRank, Double> newMultipliers) {
|
||||||
|
EnumMap<AuraRank, Integer> checkedRequirements = new EnumMap<>(AuraRank.class);
|
||||||
|
for (AuraRank rank : AuraRank.values()) {
|
||||||
|
if (rank.isMaximum()) continue;
|
||||||
|
Integer value = newRequirements.get(rank);
|
||||||
|
if (value == null || value <= 0) {
|
||||||
|
throw new IllegalArgumentException("Requirement for " + rank.next() + " must be a positive integer.");
|
||||||
|
}
|
||||||
|
checkedRequirements.put(rank, value);
|
||||||
|
}
|
||||||
|
EnumMap<AuraRank, Double> checkedMultipliers = new EnumMap<>(AuraRank.class);
|
||||||
|
for (AuraRank rank : AuraRank.values()) {
|
||||||
|
Double value = newMultipliers.get(rank);
|
||||||
|
if (value == null || !Double.isFinite(value) || value < 0) {
|
||||||
|
throw new IllegalArgumentException("Damage multiplier for " + rank + " must be finite and non-negative.");
|
||||||
|
}
|
||||||
|
checkedMultipliers.put(rank, value);
|
||||||
|
}
|
||||||
|
requirements = Collections.unmodifiableMap(checkedRequirements);
|
||||||
|
damageMultipliers = Collections.unmodifiableMap(checkedMultipliers);
|
||||||
|
}
|
||||||
|
|
||||||
public PlayerProgress advanceIfEarned(PlayerProgress progress) {
|
public PlayerProgress advanceIfEarned(PlayerProgress progress) {
|
||||||
if (progress.rank().isMaximum()) {
|
if (progress.rank().isMaximum()) return progress;
|
||||||
return progress;
|
if (progress.tierKills() < requirementForCurrentRank(progress.rank())) return progress;
|
||||||
}
|
return new PlayerProgress(progress.playerId(), progress.lastKnownName(), progress.rank().next(), 0);
|
||||||
int required = requirements.get(progress.rank());
|
|
||||||
if (progress.tierKills() < required) {
|
|
||||||
return progress;
|
|
||||||
}
|
|
||||||
return new PlayerProgress(
|
|
||||||
progress.playerId(), progress.lastKnownName(), progress.rank().next(), 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public double damageMultiplier(AuraRank rank) {
|
public double damageMultiplier(AuraRank rank) {
|
||||||
@@ -49,9 +67,7 @@ public final class AuraRules {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int requirementForCurrentRank(AuraRank rank) {
|
public int requirementForCurrentRank(AuraRank rank) {
|
||||||
if (rank.isMaximum()) {
|
if (rank.isMaximum()) throw new IllegalArgumentException("Rank VI has no next requirement");
|
||||||
throw new IllegalArgumentException("Rank VI has no next requirement");
|
|
||||||
}
|
|
||||||
return requirements.get(rank);
|
return requirements.get(rank);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package games.dmg.creeperfear.command;
|
package games.dmg.creeperfear.command;
|
||||||
|
|
||||||
|
import games.dmg.creeperfear.config.AuraConfigurationManager;
|
||||||
import games.dmg.creeperfear.feedback.ProgressFeedback;
|
import games.dmg.creeperfear.feedback.ProgressFeedback;
|
||||||
import games.dmg.creeperfear.progress.AuraRank;
|
import games.dmg.creeperfear.progress.AuraRank;
|
||||||
import games.dmg.creeperfear.progress.PlayerProgress;
|
import games.dmg.creeperfear.progress.PlayerProgress;
|
||||||
@@ -22,22 +23,47 @@ public final class CreeperAuraCommand implements CommandExecutor {
|
|||||||
private final ProgressMessages messages;
|
private final ProgressMessages messages;
|
||||||
private final ProgressMutations mutations;
|
private final ProgressMutations mutations;
|
||||||
private final ProgressFeedback feedback;
|
private final ProgressFeedback feedback;
|
||||||
|
private final AuraConfigurationManager configurationManager;
|
||||||
|
|
||||||
public CreeperAuraCommand(
|
public CreeperAuraCommand(
|
||||||
JavaPlugin plugin,
|
JavaPlugin plugin,
|
||||||
ProgressService progressService,
|
ProgressService progressService,
|
||||||
ProgressMessages messages,
|
ProgressMessages messages,
|
||||||
ProgressMutations mutations,
|
ProgressMutations mutations,
|
||||||
ProgressFeedback feedback) {
|
ProgressFeedback feedback,
|
||||||
|
AuraConfigurationManager configurationManager) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.progressService = progressService;
|
this.progressService = progressService;
|
||||||
this.messages = messages;
|
this.messages = messages;
|
||||||
this.mutations = mutations;
|
this.mutations = mutations;
|
||||||
this.feedback = feedback;
|
this.feedback = feedback;
|
||||||
|
this.configurationManager = configurationManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||||
|
if (args.length == 1 && args[0].equalsIgnoreCase("reload")) {
|
||||||
|
if (!require(sender, "creeperfear.admin.configure")) return true;
|
||||||
|
try {
|
||||||
|
configurationManager.reload();
|
||||||
|
sender.sendMessage(ChatColor.GREEN + "Creeper Aura configuration reloaded.");
|
||||||
|
} catch (RuntimeException exception) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "Configuration rejected: " + exception.getMessage());
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (args.length == 3 && args[0].equalsIgnoreCase("threshold")) {
|
||||||
|
if (!require(sender, "creeperfear.admin.configure")) return true;
|
||||||
|
try {
|
||||||
|
AuraRank rank = AuraRank.valueOf(args[1].toUpperCase());
|
||||||
|
int points = Integer.parseInt(args[2]);
|
||||||
|
configurationManager.setThreshold(rank, points);
|
||||||
|
sender.sendMessage(ChatColor.GREEN + "Set the " + rank + " requirement to " + points + ".");
|
||||||
|
} catch (IllegalArgumentException exception) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "Threshold rejected: " + exception.getMessage());
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (args.length == 1 && args[0].equalsIgnoreCase("progress")) {
|
if (args.length == 1 && args[0].equalsIgnoreCase("progress")) {
|
||||||
return showSelf(sender);
|
return showSelf(sender);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package games.dmg.creeperfear.config;
|
||||||
|
|
||||||
|
import games.dmg.creeperfear.aura.AuraRules;
|
||||||
|
import games.dmg.creeperfear.progress.AuraRank;
|
||||||
|
import java.util.EnumMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.bukkit.configuration.ConfigurationSection;
|
||||||
|
|
||||||
|
public final class AuraConfigLoader {
|
||||||
|
public AuraConfiguration load(ConfigurationSection config) {
|
||||||
|
Map<AuraRank, Integer> requirements = new EnumMap<>(AuraRank.class);
|
||||||
|
for (AuraRank current : AuraRank.values()) {
|
||||||
|
if (current.isMaximum()) continue;
|
||||||
|
AuraRank target = current.next();
|
||||||
|
String path = "progression.requirements." + target.name();
|
||||||
|
if (!config.isInt(path)) throw new IllegalArgumentException(path + " must be an integer.");
|
||||||
|
requirements.put(current, config.getInt(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<AuraRank, Double> multipliers = new EnumMap<>(AuraRank.class);
|
||||||
|
multipliers.put(AuraRank.LOCKED, 1.0);
|
||||||
|
for (AuraRank rank : AuraRank.values()) {
|
||||||
|
if (!rank.isUnlocked()) continue;
|
||||||
|
String path = "aura.damage-multipliers." + rank.name();
|
||||||
|
if (!config.isDouble(path) && !config.isInt(path)) {
|
||||||
|
throw new IllegalArgumentException(path + " must be a number.");
|
||||||
|
}
|
||||||
|
multipliers.put(rank, config.getDouble(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
int seconds = config.getInt("feedback.progress-bar-seconds");
|
||||||
|
if (!config.isInt("feedback.progress-bar-seconds") || seconds <= 0) {
|
||||||
|
throw new IllegalArgumentException("feedback.progress-bar-seconds must be a positive integer.");
|
||||||
|
}
|
||||||
|
String title = requiredString(config, "feedback.rank-up-title");
|
||||||
|
String subtitle = requiredString(config, "feedback.rank-up-subtitle");
|
||||||
|
return new AuraConfiguration(new AuraRules(requirements, multipliers), seconds * 20L, title, subtitle);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String requiredString(ConfigurationSection config, String path) {
|
||||||
|
String value = config.getString(path);
|
||||||
|
if (value == null || value.isBlank()) throw new IllegalArgumentException(path + " must not be blank.");
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package games.dmg.creeperfear.config;
|
||||||
|
|
||||||
|
import games.dmg.creeperfear.aura.AuraRules;
|
||||||
|
|
||||||
|
public record AuraConfiguration(
|
||||||
|
AuraRules rules,
|
||||||
|
long progressBarTicks,
|
||||||
|
String rankUpTitle,
|
||||||
|
String rankUpSubtitle) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package games.dmg.creeperfear.config;
|
||||||
|
|
||||||
|
import games.dmg.creeperfear.aura.AuraRules;
|
||||||
|
import games.dmg.creeperfear.feedback.ProgressFeedback;
|
||||||
|
import games.dmg.creeperfear.progress.AuraRank;
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
|
public final class AuraConfigurationManager {
|
||||||
|
private final JavaPlugin plugin;
|
||||||
|
private final AuraConfigLoader loader;
|
||||||
|
private final AuraRules activeRules;
|
||||||
|
private final ProgressFeedback feedback;
|
||||||
|
|
||||||
|
public AuraConfigurationManager(
|
||||||
|
JavaPlugin plugin, AuraConfigLoader loader, AuraRules activeRules, ProgressFeedback feedback) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.loader = loader;
|
||||||
|
this.activeRules = activeRules;
|
||||||
|
this.feedback = feedback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void applyCurrent() {
|
||||||
|
apply(loader.load(plugin.getConfig()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reload() {
|
||||||
|
plugin.reloadConfig();
|
||||||
|
apply(loader.load(plugin.getConfig()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setThreshold(AuraRank targetRank, int points) {
|
||||||
|
if (!targetRank.isUnlocked()) throw new IllegalArgumentException("The target rank must be I through VI.");
|
||||||
|
if (points <= 0) throw new IllegalArgumentException("Threshold points must be positive.");
|
||||||
|
String path = "progression.requirements." + targetRank.name();
|
||||||
|
Object prior = plugin.getConfig().get(path);
|
||||||
|
plugin.getConfig().set(path, points);
|
||||||
|
try {
|
||||||
|
AuraConfiguration candidate = loader.load(plugin.getConfig());
|
||||||
|
plugin.saveConfig();
|
||||||
|
apply(candidate);
|
||||||
|
} catch (RuntimeException exception) {
|
||||||
|
plugin.getConfig().set(path, prior);
|
||||||
|
throw exception;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void apply(AuraConfiguration configuration) {
|
||||||
|
activeRules.replaceWith(configuration.rules());
|
||||||
|
feedback.reconfigure(
|
||||||
|
configuration.progressBarTicks(),
|
||||||
|
configuration.rankUpTitle(),
|
||||||
|
configuration.rankUpSubtitle());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,7 +16,9 @@ import org.bukkit.scheduler.BukkitTask;
|
|||||||
public final class ProgressFeedback implements KillFeedback, AutoCloseable {
|
public final class ProgressFeedback implements KillFeedback, AutoCloseable {
|
||||||
private final JavaPlugin plugin;
|
private final JavaPlugin plugin;
|
||||||
private final ProgressDisplay display;
|
private final ProgressDisplay display;
|
||||||
private final long displayTicks;
|
private volatile long displayTicks;
|
||||||
|
private volatile String rankUpTitle = "Creeper Aura %rank%";
|
||||||
|
private volatile String rankUpSubtitle = "Unlocked!";
|
||||||
private final Map<UUID, BossBar> bars = new HashMap<>();
|
private final Map<UUID, BossBar> bars = new HashMap<>();
|
||||||
private final Map<UUID, BukkitTask> hideTasks = new HashMap<>();
|
private final Map<UUID, BukkitTask> hideTasks = new HashMap<>();
|
||||||
|
|
||||||
@@ -32,8 +34,12 @@ public final class ProgressFeedback implements KillFeedback, AutoCloseable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void showOnServerThread(Player player, AuraRank previousRank, PlayerProgress progress) {
|
private void showOnServerThread(Player player, AuraRank previousRank, PlayerProgress progress) {
|
||||||
display.rankUpTitle(previousRank, progress.rank()).ifPresent(title ->
|
display.rankUpTitle(previousRank, progress.rank()).ifPresent(ignored -> player.sendTitle(
|
||||||
player.sendTitle(title, "Unlocked!", 10, 70, 20));
|
rankUpTitle.replace("%rank%", progress.rank().name()),
|
||||||
|
rankUpSubtitle.replace("%rank%", progress.rank().name()),
|
||||||
|
10,
|
||||||
|
70,
|
||||||
|
20));
|
||||||
|
|
||||||
ProgressDisplay.State state = display.forProgress(progress);
|
ProgressDisplay.State state = display.forProgress(progress);
|
||||||
if (!state.visible() || !player.isOnline()) {
|
if (!state.visible() || !player.isOnline()) {
|
||||||
@@ -59,6 +65,13 @@ public final class ProgressFeedback implements KillFeedback, AutoCloseable {
|
|||||||
plugin, () -> hide(playerId), displayTicks));
|
plugin, () -> hide(playerId), displayTicks));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void reconfigure(long newDisplayTicks, String newRankUpTitle, String newRankUpSubtitle) {
|
||||||
|
if (newDisplayTicks <= 0) throw new IllegalArgumentException("Display duration must be positive.");
|
||||||
|
displayTicks = newDisplayTicks;
|
||||||
|
rankUpTitle = newRankUpTitle;
|
||||||
|
rankUpSubtitle = newRankUpSubtitle;
|
||||||
|
}
|
||||||
|
|
||||||
public void refreshIfVisible(Player player, PlayerProgress progress) {
|
public void refreshIfVisible(Player player, PlayerProgress progress) {
|
||||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||||
BossBar bar = bars.get(player.getUniqueId());
|
BossBar bar = bars.get(player.getUniqueId());
|
||||||
|
|||||||
@@ -1,2 +1,22 @@
|
|||||||
|
progression:
|
||||||
|
requirements:
|
||||||
|
I: 100
|
||||||
|
II: 100
|
||||||
|
III: 100
|
||||||
|
IV: 100
|
||||||
|
V: 100
|
||||||
|
VI: 100
|
||||||
|
|
||||||
|
aura:
|
||||||
|
damage-multipliers:
|
||||||
|
I: 3.0
|
||||||
|
II: 2.0
|
||||||
|
III: 1.5
|
||||||
|
IV: 1.0
|
||||||
|
V: 0.5
|
||||||
|
VI: 0.0
|
||||||
|
|
||||||
feedback:
|
feedback:
|
||||||
progress-bar-seconds: 5
|
progress-bar-seconds: 5
|
||||||
|
rank-up-title: "Creeper Aura %rank%"
|
||||||
|
rank-up-subtitle: "Unlocked!"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ description: Unlock Creeper Aura ranks by defeating creepers.
|
|||||||
commands:
|
commands:
|
||||||
creeperaura:
|
creeperaura:
|
||||||
description: Check and administer Creeper Aura progression.
|
description: Check and administer Creeper Aura progression.
|
||||||
usage: /<command> <progress [player]|set <player> <progress>|add <player> <progress>|rank <player> <rank>>
|
usage: /<command> <progress [player]|set <player> <progress>|add <player> <progress>|rank <player> <rank>|threshold <rank> <points>|reload>
|
||||||
permissions:
|
permissions:
|
||||||
creeperfear.progress:
|
creeperfear.progress:
|
||||||
description: Check personal Creeper Aura progression.
|
description: Check personal Creeper Aura progression.
|
||||||
@@ -18,3 +18,6 @@ permissions:
|
|||||||
creeperfear.admin.modify:
|
creeperfear.admin.modify:
|
||||||
description: Modify another player's Creeper Aura progression.
|
description: Modify another player's Creeper Aura progression.
|
||||||
default: op
|
default: op
|
||||||
|
creeperfear.admin.configure:
|
||||||
|
description: Change and reload Creeper Aura configuration.
|
||||||
|
default: op
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package games.dmg.creeperfear.config;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
|
import games.dmg.creeperfear.progress.AuraRank;
|
||||||
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class AuraConfigLoaderTest {
|
||||||
|
@Test
|
||||||
|
void loadsPerRankRequirementsMultipliersAndFeedback() throws Exception {
|
||||||
|
AuraConfiguration configuration = new AuraConfigLoader().load(validConfiguration());
|
||||||
|
|
||||||
|
assertEquals(125, configuration.rules().requirementForCurrentRank(AuraRank.LOCKED));
|
||||||
|
assertEquals(2.5, configuration.rules().damageMultiplier(AuraRank.I));
|
||||||
|
assertEquals(140L, configuration.progressBarTicks());
|
||||||
|
assertEquals("Aura %rank%", configuration.rankUpTitle());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void rejectsUnsafeValues() throws Exception {
|
||||||
|
YamlConfiguration config = validConfiguration();
|
||||||
|
config.set("aura.damage-multipliers.III", -1.0);
|
||||||
|
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> new AuraConfigLoader().load(config));
|
||||||
|
}
|
||||||
|
|
||||||
|
private YamlConfiguration validConfiguration() throws Exception {
|
||||||
|
YamlConfiguration config = new YamlConfiguration();
|
||||||
|
config.loadFromString("""
|
||||||
|
progression:
|
||||||
|
requirements: {I: 125, II: 100, III: 100, IV: 100, V: 100, VI: 100}
|
||||||
|
aura:
|
||||||
|
damage-multipliers: {I: 2.5, II: 2.0, III: 1.5, IV: 1.0, V: 0.5, VI: 0.0}
|
||||||
|
feedback:
|
||||||
|
progress-bar-seconds: 7
|
||||||
|
rank-up-title: "Aura %rank%"
|
||||||
|
rank-up-subtitle: "Unlocked"
|
||||||
|
""");
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user