Add persistent score archives
This commit is contained in:
@@ -8,7 +8,9 @@
|
|||||||
* `/score-add [playerName] <amount>` - Add amount points to the player's score.
|
* `/score-add [playerName] <amount>` - Add amount points to the player's score.
|
||||||
* `/score-subtract [playerName] <amount>` - Subtracts amount points from the player's score.
|
* `/score-subtract [playerName] <amount>` - Subtracts amount points from the player's score.
|
||||||
* `/score-reset [playerName]` - Resets the player's score to 0.
|
* `/score-reset [playerName]` - Resets the player's score to 0.
|
||||||
* `/score-archive [playerName]` - Saves the player's score to a "High Scores" table and resets their current score to 0.
|
* `/score-archive [playerName|all]` - Archives the caller/selected player or every bucket (operators); archived scores reset to their bucket's initial value.
|
||||||
|
* `/score-archive bucket <bucketId>` - Archives and resets all scores in one bucket (operators).
|
||||||
|
* `/score-archive-list [archiveId]` - Lists saved archive IDs or displays the scores in one archive.
|
||||||
* `/score-bucket [bucketId]` - Switches your active bucket.
|
* `/score-bucket [bucketId]` - Switches your active bucket.
|
||||||
* `/score-bucket create <id> <singular> <plural> <initialValue> [admin|global|player|none]` - Creates a bucket (operators).
|
* `/score-bucket create <id> <singular> <plural> <initialValue> [admin|global|player|none]` - Creates a bucket (operators).
|
||||||
* `/score-bucket <playerName|all> <bucketId>` - Switches a player or the server default (operators).
|
* `/score-bucket <playerName|all> <bucketId>` - Switches a player or the server default (operators).
|
||||||
@@ -24,7 +26,7 @@ NOTES:
|
|||||||
* Permissions support is coming AFTER I get archive to work.
|
* Permissions support is coming AFTER I get archive to work.
|
||||||
* The commands aside from get are intended for admins and whatnot as some other plugin should be using the methods instead of letting player's execute commands.
|
* The commands aside from get are intended for admins and whatnot as some other plugin should be using the methods instead of letting player's execute commands.
|
||||||
|
|
||||||
Bucket reporting defaults to `none`; `admin` notifies operators and score admins, `global` notifies all online players, and `player` notifies only the scored player. Other plugins can create buckets with `createBucket(id, singular, plural, initialValue, reporting)` and use the bucket-aware score methods. Existing score methods operate on the player's active bucket. Bucket definitions, reporting policies, assignments, and scores are saved in `plugins/ScoreKeeper/scores.yml`.
|
Bucket reporting defaults to `none`; `admin` notifies operators and score admins, `global` notifies all online players, and `player` notifies only the scored player. Other plugins can create buckets with `createBucket(id, singular, plural, initialValue, reporting)` and use the bucket-aware score methods. Existing score methods operate on the player's active bucket. Bucket definitions, reporting policies, assignments, and scores are saved in `plugins/ScoreKeeper/scores.yml`; archives are saved separately in `plugins/ScoreKeeper/archives.yml`.
|
||||||
|
|
||||||
Other plugins can start runs with `startTargetScoreRun`, `startTimedScoreRun`, or `startUntilDisabledScoreRun`, and end one with `stopHighScoreRun`. During a run, players receive their own score changes, while changes by current top-`n` players are reported globally. Finished runs publish a deterministic score-ordered table and each online player's place; reset runs restore the pre-run bucket scores.
|
Other plugins can start runs with `startTargetScoreRun`, `startTimedScoreRun`, or `startUntilDisabledScoreRun`, and end one with `stopHighScoreRun`. During a run, players receive their own score changes, while changes by current top-`n` players are reported globally. Finished runs publish a deterministic score-ordered table and each online player's place; reset runs restore the pre-run bucket scores.
|
||||||
|
|
||||||
|
|||||||
2
TODO.md
2
TODO.md
@@ -25,4 +25,4 @@
|
|||||||
| SK-03 | Done | Add optional score reporting to buckets with these four values. { id: "Bread", singular: "loaf", plural: "loaves", initialValue: 0, reporting:"admin|global|player|none" } admin means ops/admin only, global means everyone on the server, player means only tell the player, and none means don't report changes.
|
| SK-03 | Done | Add optional score reporting to buckets with these four values. { id: "Bread", singular: "loaf", plural: "loaves", initialValue: 0, reporting:"admin|global|player|none" } admin means ops/admin only, global means everyone on the server, player means only tell the player, and none means don't report changes.
|
||||||
| SK-04 | Done | Tell a player when their bucket changes. Something like "You are now tracking ${id}. You have ${score} ${plural or singular depending in score}." |
|
| SK-04 | Done | Tell a player when their bucket changes. Something like "You are now tracking ${id}. You have ${score} ${plural or singular depending in score}." |
|
||||||
| SK-05 | Done | Enable high score runs for a bucket. Admins or pugins should be able to start a run on a certain kind of score. The top n (configurable) players will have their scores reported globally for this bucket and the individual player will have their score reported to them when it changes. Scores will be optionally reset to their initialValue with the old score for this bucket saved. High score runs will have a lifetime of a target high score, a time like 4 hours or 1 week, or until-disabled. When the high score run is finished all players will be messaged a table with the top n places and their scores along with the individual user's place and score. The scores will be reset to their previous values if they were reset to their initialValue at the beginning otherwise they will be left alone. |
|
| SK-05 | Done | Enable high score runs for a bucket. Admins or pugins should be able to start a run on a certain kind of score. The top n (configurable) players will have their scores reported globally for this bucket and the individual player will have their score reported to them when it changes. Scores will be optionally reset to their initialValue with the old score for this bucket saved. High score runs will have a lifetime of a target high score, a time like 4 hours or 1 week, or until-disabled. When the high score run is finished all players will be messaged a table with the top n places and their scores along with the individual user's place and score. The scores will be reset to their previous values if they were reset to their initialValue at the beginning otherwise they will be left alone. |
|
||||||
| SK-06 | Ready | Score archiving. Scores either all or within a given bucket should be able to be archived and reset to their initial value. The archived scores should be able to be listed via a command. |
|
| SK-06 | Done | Score archiving. Scores either all or within a given bucket should be able to be archived and reset to their initial value. The archived scores should be able to be listed via a command. |
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import java.io.File;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -31,6 +32,7 @@ import java.util.logging.Level;
|
|||||||
|
|
||||||
import com.majinnaibu.minecraft.plugins.scorekeeper.commands.ScoreAddCommand;
|
import com.majinnaibu.minecraft.plugins.scorekeeper.commands.ScoreAddCommand;
|
||||||
import com.majinnaibu.minecraft.plugins.scorekeeper.commands.ScoreArchiveCommand;
|
import com.majinnaibu.minecraft.plugins.scorekeeper.commands.ScoreArchiveCommand;
|
||||||
|
import com.majinnaibu.minecraft.plugins.scorekeeper.commands.ScoreArchiveListCommand;
|
||||||
import com.majinnaibu.minecraft.plugins.scorekeeper.commands.ScoreBucketCommand;
|
import com.majinnaibu.minecraft.plugins.scorekeeper.commands.ScoreBucketCommand;
|
||||||
import com.majinnaibu.minecraft.plugins.scorekeeper.commands.ScoreGetCommand;
|
import com.majinnaibu.minecraft.plugins.scorekeeper.commands.ScoreGetCommand;
|
||||||
import com.majinnaibu.minecraft.plugins.scorekeeper.commands.ScoreResetCommand;
|
import com.majinnaibu.minecraft.plugins.scorekeeper.commands.ScoreResetCommand;
|
||||||
@@ -51,6 +53,7 @@ public class ScoreKeeperPlugin extends JavaPlugin {
|
|||||||
private final Map<String, Map<UUID, Integer>> _bucketScores = new LinkedHashMap<>();
|
private final Map<String, Map<UUID, Integer>> _bucketScores = new LinkedHashMap<>();
|
||||||
private final Map<UUID, String> _playerBuckets = new LinkedHashMap<>();
|
private final Map<UUID, String> _playerBuckets = new LinkedHashMap<>();
|
||||||
private final Map<String, HighScoreRun> _highScoreRuns = new LinkedHashMap<>();
|
private final Map<String, HighScoreRun> _highScoreRuns = new LinkedHashMap<>();
|
||||||
|
private final Map<String, ScoreArchive> _scoreArchives = new LinkedHashMap<>();
|
||||||
private String _defaultBucketId = "points";
|
private String _defaultBucketId = "points";
|
||||||
public final String _logPrefix = "[ScoreKeeper] ";
|
public final String _logPrefix = "[ScoreKeeper] ";
|
||||||
public final Component _messagePrefix =
|
public final Component _messagePrefix =
|
||||||
@@ -92,6 +95,7 @@ public class ScoreKeeperPlugin extends JavaPlugin {
|
|||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
logError(ex);
|
logError(ex);
|
||||||
}
|
}
|
||||||
|
saveArchives();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -101,12 +105,14 @@ public class ScoreKeeperPlugin extends JavaPlugin {
|
|||||||
getCommand("score-subtract").setExecutor(new ScoreSubtractCommand(this));
|
getCommand("score-subtract").setExecutor(new ScoreSubtractCommand(this));
|
||||||
getCommand("score-reset").setExecutor(new ScoreResetCommand(this));
|
getCommand("score-reset").setExecutor(new ScoreResetCommand(this));
|
||||||
getCommand("score-archive").setExecutor(new ScoreArchiveCommand(this));
|
getCommand("score-archive").setExecutor(new ScoreArchiveCommand(this));
|
||||||
|
getCommand("score-archive-list").setExecutor(new ScoreArchiveListCommand(this));
|
||||||
getCommand("score-bucket").setExecutor(new ScoreBucketCommand(this));
|
getCommand("score-bucket").setExecutor(new ScoreBucketCommand(this));
|
||||||
getCommand("score-run").setExecutor(new ScoreRunCommand(this));
|
getCommand("score-run").setExecutor(new ScoreRunCommand(this));
|
||||||
|
|
||||||
_buckets.put("points", new ScoreBucket("points", "point", "points", 0));
|
_buckets.put("points", new ScoreBucket("points", "point", "points", 0));
|
||||||
_bucketScores.put("points", new LinkedHashMap<>());
|
_bucketScores.put("points", new LinkedHashMap<>());
|
||||||
loadScores();
|
loadScores();
|
||||||
|
loadArchives();
|
||||||
checkHighScoreRuns();
|
checkHighScoreRuns();
|
||||||
getServer().getScheduler().runTaskTimer(this, this::checkHighScoreRuns, 20L, 20L);
|
getServer().getScheduler().runTaskTimer(this, this::checkHighScoreRuns, 20L, 20L);
|
||||||
|
|
||||||
@@ -124,7 +130,30 @@ public class ScoreKeeperPlugin extends JavaPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void archiveScore(Player player) {
|
public void archiveScore(Player player) {
|
||||||
logWarning("Unable to archive score for " + player.getName() + ".");
|
archivePlayerScore(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String archivePlayerScore(Player player) {
|
||||||
|
String bucketId = getPlayerBucket(player);
|
||||||
|
return createScoreArchive(
|
||||||
|
"player " + player.getName() + " in " + bucketId, List.of(bucketId), player);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String archiveBucketScores(String bucketId) {
|
||||||
|
requireBucket(bucketId);
|
||||||
|
return createScoreArchive("bucket " + bucketId, List.of(bucketId), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String archiveAllScores() {
|
||||||
|
return createScoreArchive("all buckets", new ArrayList<>(_buckets.keySet()), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ScoreArchive> getScoreArchives() {
|
||||||
|
return List.copyOf(_scoreArchives.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ScoreArchive getScoreArchive(String archiveId) {
|
||||||
|
return _scoreArchives.get(archiveId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getScore(Player player) {
|
public int getScore(Player player) {
|
||||||
@@ -298,6 +327,35 @@ public class ScoreKeeperPlugin extends JavaPlugin {
|
|||||||
"You are now tracking " + bucket.getId() + ". You have " + score + " " + unit + "."));
|
"You are now tracking " + bucket.getId() + ". You have " + score + " " + unit + "."));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String createScoreArchive(String scope, List<String> bucketIds, Player player) {
|
||||||
|
for (String bucketId : bucketIds) {
|
||||||
|
if (_highScoreRuns.containsKey(bucketId)) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Stop the active high-score run for " + bucketId + " before archiving it");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Map<UUID, Integer>> archivedScores = new LinkedHashMap<>();
|
||||||
|
UUID playerId = player == null ? null : player.getUniqueId();
|
||||||
|
for (String bucketId : bucketIds) {
|
||||||
|
ScoreBucket bucket = requireBucket(bucketId);
|
||||||
|
Map<UUID, Integer> bucketScores = _bucketScores.get(bucketId);
|
||||||
|
if (playerId == null) {
|
||||||
|
archivedScores.put(bucketId, new LinkedHashMap<>(bucketScores));
|
||||||
|
bucketScores.replaceAll((uuid, score) -> bucket.getInitialValue());
|
||||||
|
} else {
|
||||||
|
int score = getScore(player, bucketId);
|
||||||
|
archivedScores.put(bucketId, Map.of(playerId, score));
|
||||||
|
bucketScores.put(playerId, bucket.getInitialValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String archiveId = UUID.randomUUID().toString();
|
||||||
|
_scoreArchives.put(
|
||||||
|
archiveId, new ScoreArchive(archiveId, scope, System.currentTimeMillis(), archivedScores));
|
||||||
|
return archiveId;
|
||||||
|
}
|
||||||
|
|
||||||
private void startHighScoreRun(
|
private void startHighScoreRun(
|
||||||
String bucketId,
|
String bucketId,
|
||||||
int topN,
|
int topN,
|
||||||
@@ -514,6 +572,62 @@ public class ScoreKeeperPlugin extends JavaPlugin {
|
|||||||
loadHighScoreRuns(scores);
|
loadHighScoreRuns(scores);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void loadArchives() {
|
||||||
|
File archiveFile = new File(getDataFolder(), "archives.yml");
|
||||||
|
if (!archiveFile.isFile()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
YamlConfiguration archives = YamlConfiguration.loadConfiguration(archiveFile);
|
||||||
|
ConfigurationSection archiveSection = archives.getConfigurationSection("archives");
|
||||||
|
if (archiveSection == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (String archiveId : archiveSection.getKeys(false)) {
|
||||||
|
String path = "archives." + archiveId;
|
||||||
|
Map<String, Map<UUID, Integer>> bucketScores = new LinkedHashMap<>();
|
||||||
|
ConfigurationSection buckets = archives.getConfigurationSection(path + ".buckets");
|
||||||
|
if (buckets != null) {
|
||||||
|
for (String bucketId : buckets.getKeys(false)) {
|
||||||
|
Map<UUID, Integer> playerScores = new LinkedHashMap<>();
|
||||||
|
ConfigurationSection savedScores =
|
||||||
|
archives.getConfigurationSection(path + ".buckets." + bucketId + ".scores");
|
||||||
|
if (savedScores != null) {
|
||||||
|
loadPlayerScores(savedScores, playerScores);
|
||||||
|
}
|
||||||
|
bucketScores.put(bucketId, playerScores);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_scoreArchives.put(
|
||||||
|
archiveId,
|
||||||
|
new ScoreArchive(
|
||||||
|
archiveId,
|
||||||
|
archives.getString(path + ".scope", "unknown"),
|
||||||
|
archives.getLong(path + ".created-at"),
|
||||||
|
bucketScores));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveArchives() {
|
||||||
|
YamlConfiguration archives = new YamlConfiguration();
|
||||||
|
for (ScoreArchive archive : _scoreArchives.values()) {
|
||||||
|
String path = "archives." + archive.getId();
|
||||||
|
archives.set(path + ".scope", archive.getScope());
|
||||||
|
archives.set(path + ".created-at", archive.getCreatedAtMillis());
|
||||||
|
for (var bucketEntry : archive.getBucketScores().entrySet()) {
|
||||||
|
for (var scoreEntry : bucketEntry.getValue().entrySet()) {
|
||||||
|
archives.set(
|
||||||
|
path + ".buckets." + bucketEntry.getKey() + ".scores." + scoreEntry.getKey(),
|
||||||
|
scoreEntry.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
archives.save(new File(getDataFolder(), "archives.yml"));
|
||||||
|
} catch (IOException ex) {
|
||||||
|
logError(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void loadLegacyScores(YamlConfiguration scores) {
|
private void loadLegacyScores(YamlConfiguration scores) {
|
||||||
loadPlayerScores(scores, _bucketScores.get("points"));
|
loadPlayerScores(scores, _bucketScores.get("points"));
|
||||||
}
|
}
|
||||||
@@ -638,6 +752,44 @@ public class ScoreKeeperPlugin extends JavaPlugin {
|
|||||||
|
|
||||||
private record ScoreEntry(UUID playerId, String playerName, int score) {}
|
private record ScoreEntry(UUID playerId, String playerName, int score) {}
|
||||||
|
|
||||||
|
public static final class ScoreArchive {
|
||||||
|
private final String _id;
|
||||||
|
private final String _scope;
|
||||||
|
private final long _createdAtMillis;
|
||||||
|
private final Map<String, Map<UUID, Integer>> _bucketScores;
|
||||||
|
|
||||||
|
private ScoreArchive(
|
||||||
|
String id,
|
||||||
|
String scope,
|
||||||
|
long createdAtMillis,
|
||||||
|
Map<String, Map<UUID, Integer>> bucketScores) {
|
||||||
|
_id = id;
|
||||||
|
_scope = scope;
|
||||||
|
_createdAtMillis = createdAtMillis;
|
||||||
|
Map<String, Map<UUID, Integer>> copiedScores = new LinkedHashMap<>();
|
||||||
|
for (var entry : bucketScores.entrySet()) {
|
||||||
|
copiedScores.put(entry.getKey(), Map.copyOf(entry.getValue()));
|
||||||
|
}
|
||||||
|
_bucketScores = Collections.unmodifiableMap(copiedScores);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return _id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getScope() {
|
||||||
|
return _scope;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getCreatedAtMillis() {
|
||||||
|
return _createdAtMillis;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Map<UUID, Integer>> getBucketScores() {
|
||||||
|
return _bucketScores;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region Logging
|
// region Logging
|
||||||
|
|||||||
@@ -20,10 +20,12 @@ package com.majinnaibu.minecraft.plugins.scorekeeper.commands;
|
|||||||
import com.majinnaibu.minecraft.plugins.scorekeeper.ScoreKeeperPlugin;
|
import com.majinnaibu.minecraft.plugins.scorekeeper.ScoreKeeperPlugin;
|
||||||
|
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
|
|
||||||
import org.bukkit.command.Command;
|
import org.bukkit.command.Command;
|
||||||
import org.bukkit.command.CommandExecutor;
|
import org.bukkit.command.CommandExecutor;
|
||||||
import org.bukkit.command.CommandSender;
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
public class ScoreArchiveCommand implements CommandExecutor {
|
public class ScoreArchiveCommand implements CommandExecutor {
|
||||||
private final ScoreKeeperPlugin _plugin;
|
private final ScoreKeeperPlugin _plugin;
|
||||||
@@ -34,7 +36,45 @@ public class ScoreArchiveCommand implements CommandExecutor {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] split) {
|
public boolean onCommand(CommandSender sender, Command command, String label, String[] split) {
|
||||||
_plugin.sendMessage(sender, Component.text("archive command unimplemented"));
|
if (!sender.hasPermission("scorekeeper.admin")) {
|
||||||
|
sendError(sender, "You do not have permission to archive scores");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
String archiveId;
|
||||||
|
if (split.length == 1 && split[0].equalsIgnoreCase("all")) {
|
||||||
|
archiveId = _plugin.archiveAllScores();
|
||||||
|
} else if (split.length == 2 && split[0].equalsIgnoreCase("bucket")) {
|
||||||
|
archiveId = _plugin.archiveBucketScores(split[1]);
|
||||||
|
} else if (split.length == 0 && sender instanceof Player player) {
|
||||||
|
archiveId = _plugin.archivePlayerScore(player);
|
||||||
|
} else if (split.length == 1) {
|
||||||
|
Player target = _plugin.getServer().getPlayerExact(split[0]);
|
||||||
|
if (target == null) {
|
||||||
|
sendError(sender, "Can't find a player with that name");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
archiveId = _plugin.archivePlayerScore(target);
|
||||||
|
} else {
|
||||||
|
sendUsage(sender);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
_plugin.sendMessage(sender, Component.text("Saved score archive " + archiveId + "."));
|
||||||
|
} catch (IllegalArgumentException ex) {
|
||||||
|
sendError(sender, ex.getMessage());
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void sendError(CommandSender sender, String message) {
|
||||||
|
_plugin.sendMessage(sender, Component.text(message).color(NamedTextColor.RED));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendUsage(CommandSender sender) {
|
||||||
|
_plugin.sendMessage(
|
||||||
|
sender,
|
||||||
|
Component.text("Usage: /score-archive [playerName|all] | /score-archive bucket <bucketId>")
|
||||||
|
.color(NamedTextColor.YELLOW));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
/*
|
||||||
|
This file is part of ScoreKeeper.
|
||||||
|
|
||||||
|
ScoreKeeper is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
ScoreKeeper is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with ScoreKeeper. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.majinnaibu.minecraft.plugins.scorekeeper.commands;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import com.majinnaibu.minecraft.plugins.scorekeeper.ScoreBucket;
|
||||||
|
import com.majinnaibu.minecraft.plugins.scorekeeper.ScoreKeeperPlugin;
|
||||||
|
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
|
|
||||||
|
import org.bukkit.OfflinePlayer;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
|
||||||
|
public class ScoreArchiveListCommand implements CommandExecutor {
|
||||||
|
private final ScoreKeeperPlugin _plugin;
|
||||||
|
|
||||||
|
public ScoreArchiveListCommand(ScoreKeeperPlugin plugin) {
|
||||||
|
_plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||||
|
if (args.length == 0) {
|
||||||
|
var archives = _plugin.getScoreArchives();
|
||||||
|
if (archives.isEmpty()) {
|
||||||
|
_plugin.sendMessage(sender, Component.text("No score archives have been saved."));
|
||||||
|
} else {
|
||||||
|
archives.stream()
|
||||||
|
.sorted(
|
||||||
|
Comparator.comparingLong(
|
||||||
|
(ScoreKeeperPlugin.ScoreArchive archive) -> archive.getCreatedAtMillis())
|
||||||
|
.reversed())
|
||||||
|
.forEach(
|
||||||
|
archive ->
|
||||||
|
_plugin.sendMessage(
|
||||||
|
sender,
|
||||||
|
Component.text(
|
||||||
|
archive.getId()
|
||||||
|
+ " | "
|
||||||
|
+ archive.getScope()
|
||||||
|
+ " | "
|
||||||
|
+ Instant.ofEpochMilli(archive.getCreatedAtMillis()))));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.length != 1) {
|
||||||
|
sendUsage(sender);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
ScoreKeeperPlugin.ScoreArchive archive = _plugin.getScoreArchive(args[0]);
|
||||||
|
if (archive == null) {
|
||||||
|
_plugin.sendMessage(
|
||||||
|
sender,
|
||||||
|
Component.text("No score archive exists with ID " + args[0] + ".")
|
||||||
|
.color(NamedTextColor.RED));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_plugin.sendMessage(
|
||||||
|
sender,
|
||||||
|
Component.text(
|
||||||
|
"Archive "
|
||||||
|
+ archive.getId()
|
||||||
|
+ " ("
|
||||||
|
+ archive.getScope()
|
||||||
|
+ ", "
|
||||||
|
+ Instant.ofEpochMilli(archive.getCreatedAtMillis())
|
||||||
|
+ ")"));
|
||||||
|
for (var bucketEntry : archive.getBucketScores().entrySet()) {
|
||||||
|
ScoreBucket bucket = _plugin.getBucket(bucketEntry.getKey());
|
||||||
|
_plugin.sendMessage(sender, Component.text("Bucket " + bucketEntry.getKey() + ":"));
|
||||||
|
bucketEntry.getValue().entrySet().stream()
|
||||||
|
.sorted(
|
||||||
|
Map.Entry.<UUID, Integer>comparingByValue()
|
||||||
|
.reversed()
|
||||||
|
.thenComparing(entry -> entry.getKey().toString()))
|
||||||
|
.forEach(
|
||||||
|
entry ->
|
||||||
|
_plugin.sendMessage(
|
||||||
|
sender,
|
||||||
|
Component.text(
|
||||||
|
displayName(entry.getKey())
|
||||||
|
+ " - "
|
||||||
|
+ entry.getValue()
|
||||||
|
+ " "
|
||||||
|
+ scoreUnit(bucket, entry.getValue()))));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String displayName(UUID playerId) {
|
||||||
|
OfflinePlayer player = _plugin.getServer().getOfflinePlayer(playerId);
|
||||||
|
return player.getName() == null ? playerId.toString() : player.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String scoreUnit(ScoreBucket bucket, int score) {
|
||||||
|
if (bucket == null) {
|
||||||
|
return "scores";
|
||||||
|
}
|
||||||
|
return score == 1 ? bucket.getSingular() : bucket.getPlural();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendUsage(CommandSender sender) {
|
||||||
|
_plugin.sendMessage(
|
||||||
|
sender,
|
||||||
|
Component.text("Usage: /score-archive-list [archiveId]").color(NamedTextColor.YELLOW));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,9 @@ commands:
|
|||||||
score-archive:
|
score-archive:
|
||||||
description: Archives and resets the player's score
|
description: Archives and resets the player's score
|
||||||
usage: /score-archive [playername]
|
usage: /score-archive [playername]
|
||||||
|
score-archive-list:
|
||||||
|
description: Lists saved score archives or displays one archive.
|
||||||
|
usage: /score-archive-list [archiveId]
|
||||||
score-bucket:
|
score-bucket:
|
||||||
description: Selects or manages score buckets.
|
description: Selects or manages score buckets.
|
||||||
usage: /score-bucket [bucketId] | create <id> <singular> <plural> <initialValue> [admin|global|player|none] | <player|all> <bucketId>
|
usage: /score-bucket [bucketId] | create <id> <singular> <plural> <initialValue> [admin|global|player|none] | <player|all> <bucketId>
|
||||||
|
|||||||
Reference in New Issue
Block a user