Add start of VoteToMute functionality in chat system
Implemented VoteToMute system enabling initiated voting for muting a player in chat. This includes creating new classes "ActiveVoteToMute", "VoteToMute", and "VoteToMuteStarter". The "VoteToMute" class handles the voting command logic, it allows players to vote on whether to mute other players. The code also adds a call to register this new command in the main VelocityChat class.
This commit is contained in:
parent
44d6e994cc
commit
ed2ba74772
|
|
@ -113,6 +113,7 @@ public class VelocityChat {
|
|||
new Reload(server);
|
||||
new MailCommand(server);
|
||||
new Report(server);
|
||||
new VoteToMute(server);
|
||||
server.getCommandManager().register("party", new PartyCommand());
|
||||
// all (proxy)commands go here
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,160 @@
|
|||
package com.alttd.velocitychat.commands;
|
||||
|
||||
import com.alttd.chat.config.Config;
|
||||
import com.alttd.chat.util.Utility;
|
||||
import com.alttd.velocitychat.commands.vote_to_mute.ActiveVoteToMute;
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
|
||||
import com.mojang.brigadier.suggestion.Suggestions;
|
||||
import com.mojang.brigadier.tree.LiteralCommandNode;
|
||||
import com.velocitypowered.api.command.BrigadierCommand;
|
||||
import com.velocitypowered.api.command.CommandMeta;
|
||||
import com.velocitypowered.api.command.CommandSource;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import com.velocitypowered.api.proxy.ServerConnection;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class VoteToMute {
|
||||
|
||||
public VoteToMute(ProxyServer proxyServer) {
|
||||
RequiredArgumentBuilder<CommandSource, String> playerNode = RequiredArgumentBuilder
|
||||
.<CommandSource, String>argument("player", StringArgumentType.string())
|
||||
.suggests((context, builder) -> {
|
||||
List<Player> possiblePlayers;
|
||||
if (context.getSource() instanceof Player player) {
|
||||
Optional<ServerConnection> currentServer = player.getCurrentServer();
|
||||
if (currentServer.isPresent()) {
|
||||
possiblePlayers = getEligiblePlayers(currentServer.get().getServer());
|
||||
} else {
|
||||
possiblePlayers = getEligiblePlayers(proxyServer);
|
||||
}
|
||||
} else {
|
||||
possiblePlayers = getEligiblePlayers(proxyServer);
|
||||
}
|
||||
Collection<String> possibleValues = possiblePlayers.stream()
|
||||
.map(Player::getUsername)
|
||||
.toList();
|
||||
|
||||
if (possibleValues.isEmpty())
|
||||
return Suggestions.empty();
|
||||
|
||||
String remaining = builder.getRemaining().toLowerCase();
|
||||
possibleValues.stream()
|
||||
.filter(str -> str.toLowerCase().startsWith(remaining))
|
||||
.map(StringArgumentType::escapeIfRequired)
|
||||
.forEach(builder::suggest);
|
||||
return builder.buildFuture();
|
||||
})
|
||||
.executes(context -> {
|
||||
sendHelpMessage(context.getSource());
|
||||
return 1;
|
||||
});
|
||||
|
||||
RequiredArgumentBuilder<CommandSource, String> yesNoNode = RequiredArgumentBuilder.
|
||||
<CommandSource, String>argument("vote", StringArgumentType.string())
|
||||
.suggests(((commandContext, suggestionsBuilder) -> {
|
||||
List<String> yesNoValues = Arrays.asList("yes", "no");
|
||||
String remaining = suggestionsBuilder.getRemaining().toLowerCase();
|
||||
yesNoValues.stream()
|
||||
.filter((String str) -> str.toLowerCase().startsWith(remaining))
|
||||
.map(StringArgumentType::escapeIfRequired)
|
||||
.forEach(suggestionsBuilder::suggest);
|
||||
return suggestionsBuilder.buildFuture();
|
||||
}));
|
||||
|
||||
LiteralArgumentBuilder<CommandSource> voteNode = LiteralArgumentBuilder
|
||||
.<CommandSource>literal("vote")
|
||||
.then(playerNode
|
||||
.then(yesNoNode
|
||||
.executes(commandContext -> {
|
||||
if (!(commandContext.getSource() instanceof Player)) {
|
||||
commandContext.getSource().sendMessage(Utility.parseMiniMessage(
|
||||
"<red>Only players are allowed to vote</red>"));
|
||||
}
|
||||
Player source = (Player) commandContext.getSource();
|
||||
String playerName = commandContext.getArgument("player", String.class);
|
||||
Optional<ActiveVoteToMute> optionalActiveVoteToMute = ActiveVoteToMute.getInstance(playerName);
|
||||
if (optionalActiveVoteToMute.isEmpty()) {
|
||||
commandContext.getSource().sendMessage(Utility.parseMiniMessage(
|
||||
"<red>This player does not have an active vote to mute them</red>"));
|
||||
return 1;
|
||||
}
|
||||
ActiveVoteToMute activeVoteToMute = optionalActiveVoteToMute.get();
|
||||
String vote = commandContext.getArgument("vote", String.class);
|
||||
switch (vote.toLowerCase()) {
|
||||
case "yes" -> activeVoteToMute.vote(source.getUniqueId(), true);
|
||||
case "no" -> activeVoteToMute.vote(source.getUniqueId(), false);
|
||||
default -> commandContext.getSource().sendMessage(Utility.parseMiniMessage(
|
||||
"<red><vote> is not a valid vote option</red>", Placeholder.parsed("vote", vote)));
|
||||
}
|
||||
return 1;
|
||||
})).executes(context -> {
|
||||
sendHelpMessage(context.getSource());
|
||||
return 1;
|
||||
})).executes(context -> {
|
||||
sendHelpMessage(context.getSource());
|
||||
return 1;
|
||||
});
|
||||
|
||||
LiteralCommandNode<CommandSource> command = LiteralArgumentBuilder
|
||||
.<CommandSource>literal("votetomute")
|
||||
.requires(commandSource -> commandSource.hasPermission("chat.vote-to-mute"))
|
||||
.requires(commandSource -> commandSource instanceof Player)
|
||||
.then(playerNode
|
||||
.executes(commandContext -> {
|
||||
String playerName = commandContext.getArgument("player", String.class);
|
||||
Optional<Player> optionalPlayer = proxyServer.getPlayer(playerName);
|
||||
if (optionalPlayer.isEmpty()) {
|
||||
commandContext.getSource().sendMessage(Utility.parseMiniMessage(
|
||||
"<red>Player <player> is not online</red>",
|
||||
Placeholder.parsed("player", playerName)));
|
||||
return 1;
|
||||
}
|
||||
return 1;
|
||||
}))
|
||||
.then(voteNode)
|
||||
.executes(context -> {
|
||||
sendHelpMessage(context.getSource());
|
||||
return 1;
|
||||
})
|
||||
.build();
|
||||
//TODO test command
|
||||
//TODO add command to pick out the messages
|
||||
//TODO add command to go to the next page
|
||||
|
||||
BrigadierCommand brigadierCommand = new BrigadierCommand(command);
|
||||
|
||||
CommandMeta.Builder metaBuilder = proxyServer.getCommandManager().metaBuilder(brigadierCommand);
|
||||
|
||||
CommandMeta meta = metaBuilder.build();
|
||||
|
||||
proxyServer.getCommandManager().register(meta, brigadierCommand);
|
||||
}
|
||||
|
||||
private void sendHelpMessage(CommandSource commandSource) {
|
||||
commandSource.sendMessage(Utility.parseMiniMessage("<red>Use: <gold>/votetomute <player></gold>.</red>"));
|
||||
}
|
||||
|
||||
private List<Player> getEligiblePlayers(ProxyServer proxyServer) {
|
||||
return proxyServer.getAllPlayers().stream()
|
||||
.filter(player -> player.hasPermission("chat.affected-by-vote-to-mute"))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private List<Player> getEligiblePlayers(RegisteredServer registeredServer) {
|
||||
return registeredServer.getPlayersConnected().stream()
|
||||
.filter(player -> player.hasPermission("chat.affected-by-vote-to-mute"))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
package com.alttd.velocitychat.commands.vote_to_mute;
|
||||
|
||||
import com.alttd.chat.util.Utility;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ActiveVoteToMute {
|
||||
|
||||
private static final HashMap<String, ActiveVoteToMute> instances = new HashMap<>();
|
||||
private static final Component prefix = Utility.parseMiniMessage("<gold>[VoteMute]</gold>");
|
||||
|
||||
private final Instant start;
|
||||
private final Player votedPlayer;
|
||||
private final Duration duration;
|
||||
private HashSet<UUID> votedFor = new HashSet<>();
|
||||
private HashSet<UUID> votedAgainst = new HashSet<>();
|
||||
private int totalEligibleVoters;
|
||||
|
||||
public static Optional<ActiveVoteToMute> getInstance(String username) {
|
||||
if (!instances.containsKey(username))
|
||||
return Optional.empty();
|
||||
return Optional.of(instances.get(username));
|
||||
}
|
||||
|
||||
public ActiveVoteToMute(@NotNull Player votedPlayer, Duration duration, int totalEligibleVoters) {
|
||||
this.start = Instant.now();
|
||||
this.votedPlayer = votedPlayer;
|
||||
this.duration = duration;
|
||||
this.totalEligibleVoters = totalEligibleVoters;
|
||||
instances.put(votedPlayer.getUsername(), this);
|
||||
}
|
||||
|
||||
public void start(@NotNull RegisteredServer registeredServer, Component chatLogs) {
|
||||
Component message = Utility.parseMiniMessage(
|
||||
String.format("""
|
||||
<prefix> <gold>[VoteMute]</gold> <green>A vote to mute <player> for one hour has been started, please read the logs below before voting.</green>
|
||||
<logs>
|
||||
<prefix> Click: <click:run_command:'/votetomute vote %s yes'><red>Mute</red></click> --- <click:run_command:'/votetomute vote %s no'><yellow>Don't mute</yellow></click>""",
|
||||
votedPlayer.getUsername(), votedPlayer.getUsername()),
|
||||
Placeholder.component("prefix", prefix),
|
||||
Placeholder.parsed("player", votedPlayer.getUsername()),
|
||||
Placeholder.component("logs", chatLogs));
|
||||
registeredServer.getPlayersConnected().stream()
|
||||
.filter(player -> player.hasPermission("chat.vote-to-mute"))
|
||||
.forEach(player -> player.sendMessage(message));
|
||||
}
|
||||
|
||||
public void vote(UUID uuid, boolean votedToMute) {
|
||||
if (votedToMute) {
|
||||
votedFor.add(uuid);
|
||||
votedAgainst.remove(uuid);
|
||||
} else {
|
||||
votedAgainst.add(uuid);
|
||||
votedFor.remove(uuid);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean votePassed() {
|
||||
double totalVotes = (votedFor.size() + votedAgainst.size());
|
||||
if (totalVotes / totalEligibleVoters < 0.6) {
|
||||
return false;
|
||||
}
|
||||
return votedFor.size() / totalVotes > 0.6;
|
||||
}
|
||||
|
||||
public boolean voteEnded() {
|
||||
if (votedFor.size() + votedAgainst.size() == totalEligibleVoters)
|
||||
return true;
|
||||
return duration.minus(Duration.between(start, Instant.now())).isNegative();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
package com.alttd.velocitychat.commands.vote_to_mute;
|
||||
|
||||
import com.alttd.chat.objects.chat_log.ChatLog;
|
||||
import com.alttd.chat.objects.chat_log.ChatLogHandler;
|
||||
import com.alttd.chat.util.Utility;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.JoinConfiguration;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
public class VoteToMuteStarter {
|
||||
|
||||
private static final HashMap<UUID, VoteToMuteStarter> instanceMap = new HashMap<>();
|
||||
|
||||
private static final Component prefix = Utility.parseMiniMessage("<gold>[VoteMute]</gold>");
|
||||
private final ChatLogHandler chatLogHandler;
|
||||
private final Player votedPlayer;
|
||||
private final Player commandSource;
|
||||
private final String serverName;
|
||||
private List<Component> parsedChatLogs;
|
||||
|
||||
public static Optional<VoteToMuteStarter> getInstance(UUID uuid) {
|
||||
if (!instanceMap.containsKey(uuid))
|
||||
return Optional.empty();
|
||||
return Optional.of(instanceMap.get(uuid));
|
||||
}
|
||||
|
||||
public VoteToMuteStarter(ChatLogHandler chatLogHandler, Player votedPlayer, Player commandSource, String serverName) {
|
||||
this.chatLogHandler = chatLogHandler;
|
||||
this.votedPlayer = votedPlayer;
|
||||
this.commandSource = commandSource;
|
||||
this.serverName = serverName;
|
||||
instanceMap.put(commandSource.getUniqueId(), this);
|
||||
}
|
||||
|
||||
public void start() {
|
||||
chatLogHandler.retrieveChatLogs(votedPlayer.getUniqueId(), Duration.ofMinutes(5), serverName).whenCompleteAsync((chatLogs, throwable) -> {
|
||||
if (throwable != null) {
|
||||
commandSource.sendMessage(Utility.parseMiniMessage("<prefix> <red>Unable to retrieve messages</red> for player <player>",
|
||||
Placeholder.component("prefix", prefix),
|
||||
Placeholder.parsed("player", votedPlayer.getUsername())));
|
||||
return;
|
||||
}
|
||||
parseChatLogs(chatLogs);
|
||||
commandSource.sendMessage(Utility.parseMiniMessage(
|
||||
"<prefix> <green>Please select up to 10 messages other players should see to decide their vote, seperated by comma's. " +
|
||||
"Example: <gold>/votetomute messages 1, 2, 5, 8</gold></green>"));
|
||||
showPage(0);
|
||||
});
|
||||
}
|
||||
|
||||
private void parseChatLogs(List<ChatLog> chatLogs) {
|
||||
TagResolver.Single playerTag = Placeholder.parsed("player", votedPlayer.getUsername());
|
||||
chatLogs.sort(Comparator.comparing(ChatLog::getTimestamp));
|
||||
parsedChatLogs = IntStream.range(0, chatLogs.size())
|
||||
.mapToObj(i -> Utility.parseMiniMessage(
|
||||
"<number>. [ChatLog] <player>: <message>",
|
||||
TagResolver.resolver(
|
||||
Placeholder.unparsed("message", chatLogs.get(i).getMessage()),
|
||||
Placeholder.parsed("number", String.valueOf(i + 1)),
|
||||
playerTag
|
||||
))
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
||||
public void showPage(int page) {
|
||||
List<Component> collect = parsedChatLogs.stream().skip(page * 10L).limit(page).toList();
|
||||
Component chatLogsComponent = Component.join(JoinConfiguration.newlines(), collect);
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
stringBuilder.append("<prefix> ChatLogs for <player>\n<logs>\n");
|
||||
if (page != 0) {
|
||||
stringBuilder.append("<click:run_command:/votetomute page ")
|
||||
.append(page - 1)
|
||||
.append("><hover:show_text:'<gold>Click to go to previous page'><gold><previous page></gold></hover></click> ");
|
||||
}
|
||||
if (parsedChatLogs.size() > page * 10) {
|
||||
stringBuilder.append("<click:run_command:/votetomute page ")
|
||||
.append(page + 1)
|
||||
.append("><hover:show_text:'<gold>Click to go to next page'><gold><previous page></gold></hover></click> ");
|
||||
}
|
||||
commandSource.sendMessage(Utility.parseMiniMessage(stringBuilder.toString(),
|
||||
Placeholder.parsed("player", votedPlayer.getUsername()),
|
||||
Placeholder.component("prefix", prefix),
|
||||
Placeholder.component("logs", chatLogsComponent)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the chat logs for the given list of IDs. It removes 1 from the IDs before using them
|
||||
* It removes the instance from the hashmap after this function call
|
||||
*
|
||||
* @param ids A list of integers representing the IDs of the chat logs to retrieve.
|
||||
* @return A Component object containing the selected chat logs joined by newlines.
|
||||
*/
|
||||
public Component getChatLogsAndClose(List<Integer> ids) {
|
||||
List<Component> selectedChatLogs = ids.stream()
|
||||
.filter(id -> id >= 1 && id <= parsedChatLogs.size())
|
||||
.map(id -> parsedChatLogs.get(id - 1))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
instanceMap.remove(commandSource.getUniqueId());
|
||||
return Component.join(JoinConfiguration.newlines(), selectedChatLogs);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user