Made ShareCommand more better, added feedback for no-share-allowed

This commit is contained in:
Artemis-the-gr8 2022-07-13 02:20:59 +02:00
parent ce8b870560
commit 1c8d77e9cd
7 changed files with 108 additions and 29 deletions

View File

@ -47,7 +47,7 @@ public class Main extends JavaPlugin {
PluginCommand reloadcmd = this.getCommand("statisticreload");
if (reloadcmd != null) reloadcmd.setExecutor(new ReloadCommand(threadManager));
PluginCommand sharecmd = this.getCommand("statisticshare");
if (sharecmd != null) sharecmd.setExecutor(new ShareCommand(shareManager));
if (sharecmd != null) sharecmd.setExecutor(new ShareCommand(shareManager, messageWriter));
//register the listener
Bukkit.getPluginManager().registerEvents(new JoinListener(threadManager), this);

View File

@ -8,9 +8,11 @@ import net.kyori.adventure.text.TextComponent;
import javax.annotation.Nullable;
import java.time.Instant;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
@ -23,18 +25,20 @@ public final class ShareManager {
private static boolean isEnabled;
private static int waitingTime;
private volatile AtomicInteger resultID; //always starts with value 0
private ConcurrentHashMap<UUID, StatResult> statResults = null;
private ConcurrentHashMap<String, Instant> shareTimeStamp = null;
private volatile AtomicInteger resultID;
private ConcurrentHashMap<UUID, StatResult> statResultQueue;
private ConcurrentHashMap<String, Instant> shareTimeStamp;
private ArrayBlockingQueue<UUID> sharedResults;
private ShareManager(ConfigHandler config) {
isEnabled = config.enableStatSharing();
waitingTime = config.getStatShareWaitingTime();
if (isEnabled) {
resultID = new AtomicInteger();
statResults = new ConcurrentHashMap<>();
resultID = new AtomicInteger(); //always starts with value 0
statResultQueue = new ConcurrentHashMap<>();
shareTimeStamp = new ConcurrentHashMap<>();
sharedResults = new ArrayBlockingQueue<>(500);
}
}
@ -55,15 +59,19 @@ public final class ShareManager {
isEnabled = config.enableStatSharing();
waitingTime = config.getStatShareWaitingTime();
//if we went from disabled to enabled, initialize the HashMaps
if (isEnabled && statResults == null) {
statResults = new ConcurrentHashMap<>();
shareTimeStamp = new ConcurrentHashMap<>();
if (isEnabled) { //reset the sharedResultsQueue
sharedResults = new ArrayBlockingQueue<>(500);
if (statResultQueue == null) { //if we went from disabled to enabled, initialize the HashMaps
statResultQueue = new ConcurrentHashMap<>();
shareTimeStamp = new ConcurrentHashMap<>();
}
}
//if we went from enabled to disabled, purge the existing data
else if (!isEnabled && statResults != null) {
statResults = null;
else if (statResultQueue != null) {
statResultQueue = null;
shareTimeStamp = null;
sharedResults = null;
}
}
@ -77,22 +85,38 @@ public final class ShareManager {
int ID = getNextIDNumber();
UUID identifier = UUID.randomUUID();
statResults.put(identifier, new StatResult(playerName, statResult, ID, identifier));
statResultQueue.put(identifier, new StatResult(playerName, statResult, ID, identifier));
MyLogger.logMsg("Saving statResults with no. " + ID, DebugLevel.MEDIUM);
return identifier;
}
public @Nullable TextComponent getResultMessage(String playerName, UUID identifier) {
if (statResults.containsKey(identifier) && playerCanShare(playerName)) {
/** Takes a statResult from the internal ConcurrentHashmap,
puts the current time in the shareTimeStamp (ConcurrentHashMap),
puts the shareCode (UUID) in the sharedResults (ArrayBlockingQueue),
and returns the statResult. If no statResult was found, returns null.*/
public @Nullable TextComponent getStatResult(String playerName, UUID identifier) {
if (statResultQueue.containsKey(identifier)) {
shareTimeStamp.put(playerName, Instant.now());
return statResults.remove(identifier).statResult();
if (!sharedResults.offer(identifier)) { //create a new ArrayBlockingQueue if our queue is full
ArrayBlockingQueue<UUID> newQueue = new ArrayBlockingQueue<>(500);
synchronized (this) { //put the last 50 values in the new Queue
UUID[] lastValues = sharedResults.toArray(new UUID[0]);
Arrays.stream(Arrays.copyOfRange(lastValues, 450, 499))
.parallel().iterator()
.forEachRemaining(newQueue::offer);
sharedResults = newQueue;
}
sharedResults.offer(identifier);
}
return statResultQueue.remove(identifier).statResult();
} else {
//TODO send error-message if on time-out, and error-message if request is already shared
return null;
}
}
public boolean playerCanShare(String playerName) {
public boolean isOnCoolDown(String playerName) {
if (waitingTime == 0 || !shareTimeStamp.containsKey(playerName)) {
return true;
} else {
@ -101,10 +125,14 @@ public final class ShareManager {
}
}
public boolean requestAlreadyShared(UUID shareCode) {
return sharedResults.contains(shareCode);
}
/** If the given player already has more than x (in this case 25) StatResults saved,
remove the oldest one.*/
private void removeExcessResults(String playerName) {
List<StatResult> alreadySavedResults = statResults.values()
List<StatResult> alreadySavedResults = statResultQueue.values()
.parallelStream()
.filter(result -> result.playerName().equalsIgnoreCase(playerName))
.toList();
@ -114,8 +142,8 @@ public final class ShareManager {
.parallelStream()
.min(Comparator.comparing(StatResult::ID))
.orElseThrow().uuid();
MyLogger.logMsg("Removing old stat no. " + statResults.get(uuid).ID() + " for player " + playerName, DebugLevel.MEDIUM);
statResults.remove(uuid);
MyLogger.logMsg("Removing old stat no. " + statResultQueue.get(uuid).ID() + " for player " + playerName, DebugLevel.MEDIUM);
statResultQueue.remove(uuid);
}
}

View File

@ -2,7 +2,10 @@ package com.gmail.artemis.the.gr8.playerstats.commands;
import com.gmail.artemis.the.gr8.playerstats.Main;
import com.gmail.artemis.the.gr8.playerstats.ShareManager;
import com.gmail.artemis.the.gr8.playerstats.msg.MessageWriter;
import com.gmail.artemis.the.gr8.playerstats.utils.MyLogger;
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
import net.kyori.adventure.text.TextComponent;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
@ -14,18 +17,39 @@ public class ShareCommand implements CommandExecutor {
private static BukkitAudiences adventure;
private static ShareManager shareManager;
private final MessageWriter messageWriter;
public ShareCommand(ShareManager s) {
public ShareCommand(ShareManager s, MessageWriter m) {
adventure = Main.adventure();
shareManager = s;
messageWriter = m;
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, String label, String[] args) {
adventure.all().sendMessage(shareManager.getResultMessage(sender.getName(), UUID.fromString(args[0])));
//TODO send feedback if stat-result is null:
//can't share again yet (time-out)
//already shared this result (not in statResult list anymore)
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @NotNull String label, String[] args) {
if (args.length == 1 && shareManager.isEnabled()) {
UUID shareCode;
try {
shareCode = UUID.fromString(args[0]);
} catch (IllegalArgumentException e) {
MyLogger.logException(e, "ShareCommand", "/statshare is being called without a valid UUID argument");
return false;
}
if (shareManager.isOnCoolDown(sender.getName())) {
adventure.sender(sender).sendMessage(messageWriter.stillOnShareCoolDown());
}
else if (shareManager.requestAlreadyShared(shareCode)) {
adventure.sender(sender).sendMessage(messageWriter.resultsAlreadyShared());
}
else {
TextComponent result = shareManager.getStatResult(sender.getName(), shareCode);
if (result == null) { //at this point the only possible cause of statResult being null is the request being older than 25 player-requests ago
adventure.sender(sender).sendMessage(messageWriter.statResultsTooOld());
} else {
adventure.all().sendMessage(result);
}
}
}
return true;
}
}

View File

@ -62,6 +62,10 @@ public class ComponentFactory {
return text().color(PluginColor.MEDIUM_BLUE.getColor()).build();
}
public TextComponent messageAccentComponent() {
return text().color(PluginColor.LIGHT_GOLD.getColor()).build();
}
public TextComponent.Builder playerNameBuilder(String playerName, Target selection) {
return getComponentBuilder(playerName,
getColorFromString(config.getPlayerNameDecoration(selection, false)),

View File

@ -108,6 +108,29 @@ public class MessageWriter {
"Please wait for your previous lookup to finish!"));
}
public TextComponent stillOnShareCoolDown() {
return componentFactory.pluginPrefixComponent(false)
.append(space())
.append(componentFactory.messageComponent().content("You need to wait")
.append(space())
.append(componentFactory.messageAccentComponent().content(config.getStatShareWaitingTime() + ""))
.append(space()))
.append(text("minutes before you are able to share again!"));
}
public TextComponent resultsAlreadyShared() {
return componentFactory.pluginPrefixComponent(false)
.append(space())
.append(componentFactory.messageComponent().content("You already shared these results!"));
}
public TextComponent statResultsTooOld() {
return componentFactory.pluginPrefixComponent(false)
.append(space())
.append(componentFactory.messageComponent().content(
"It has been too long since you looked up this statistic, please repeat the original look-up if you want to share it!"));
}
public TextComponent unknownError(boolean isBukkitConsole) {
return componentFactory.pluginPrefixComponent(isBukkitConsole)
.append(space())

View File

@ -85,7 +85,7 @@ public class StatThread extends Thread {
case SERVER -> messageWriter.formatServerStat(getServerTotal(), request);
};
if (shareManager.isEnabled()) {
if (shareManager.isEnabled() && request.getCommandSender().hasPermission("playerstats.share")) {
UUID shareCode = shareManager.saveStatResult(request.getCommandSender().getName(), statResult);
statResult = messageWriter.addShareButton(statResult, shareCode);
}

View File

@ -31,7 +31,7 @@ permissions:
default: true
playerstats.share:
description: allows sharing stats in chat
default: true
default: op
playerstats.reload:
description: allows usage of /statreload
default: op