Skip to content

Commit

Permalink
feat: Add soulpoint subcommand and aliases for server commands, click…
Browse files Browse the repository at this point in the history
…-to-switch server texts, fix formatting issues, command no longer hands the game (#2415)
  • Loading branch information
kristofbolyai committed May 5, 2024
1 parent a8ef08a commit aa94e6e
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 55 deletions.
154 changes: 115 additions & 39 deletions common/src/main/java/com/wynntils/commands/ServersCommand.java
Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
/*
* Copyright © Wynntils 2022-2023.
* Copyright © Wynntils 2022-2024.
* This file is released under LGPLv3. See LICENSE for full license details.
*/
package com.wynntils.commands;

import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.tree.LiteralCommandNode;
import com.wynntils.core.components.Models;
import com.wynntils.core.consumers.commands.Command;
import com.wynntils.models.worlds.profile.ServerProfile;
import com.wynntils.utils.StringUtils;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
import net.minecraft.ChatFormatting;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.network.chat.ClickEvent;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.HoverEvent;
import net.minecraft.network.chat.MutableComponent;

public class ServersCommand extends Command {
Expand All @@ -28,29 +33,51 @@ public String getCommandName() {
return "servers";
}

@Override
protected List<String> getAliases() {
return List.of("s", "srv");
}

@Override
public LiteralArgumentBuilder<CommandSourceStack> getCommandBuilder(
LiteralArgumentBuilder<CommandSourceStack> base) {
return base.then(Commands.literal("list")
.then(Commands.literal("up").executes(this::serverUptimeList))
.executes(this::serverList)
.build())
.then(Commands.literal("info")
.then(Commands.argument("server", StringArgumentType.word())
.executes(this::serverInfo))
.build())
LiteralCommandNode<CommandSourceStack> listBuilder = Commands.literal("list")
.then(Commands.literal("up").executes(this::serverUptimeList))
.executes(this::serverList)
.build();

LiteralCommandNode<CommandSourceStack> infoBuilder = Commands.literal("info")
.then(Commands.argument("server", StringArgumentType.word()).executes(this::serverInfo))
.executes(this::serverInfo)
.build();

LiteralArgumentBuilder<CommandSourceStack> infoAliasBuilder = Commands.literal("i")
.then(Commands.argument("server", StringArgumentType.word()).executes(this::serverInfo))
.executes(this::serverInfo);

LiteralCommandNode<CommandSourceStack> soulpointsBuilder = Commands.literal("soulpoints")
.executes(this::serverSoulpointsList)
.build();

return base.then(listBuilder)
.then(infoBuilder)
.then(Commands.literal("l").executes(this::serverList))
.then(Commands.literal("ul").executes(this::serverUptimeList))
.then(Commands.literal("up").executes(this::serverUptimeList))
.then(Commands.literal("soul").executes(this::serverSoulpointsList))
.then(Commands.literal("s").executes(this::serverSoulpointsList))
.then(infoAliasBuilder)
.executes(this::syntaxError);
}

private int serverInfo(CommandContext<CommandSourceStack> context) {
if (!Models.ServerList.forceUpdate(UPDATE_TIME_OUT_MS)) {
context.getSource()
.sendFailure(Component.literal("Network problems; using cached data")
.withStyle(ChatFormatting.RED));
String server;
try {
server = context.getArgument("server", String.class);
} catch (Exception e) {
server = Models.WorldState.getCurrentWorldName();
}

String server = context.getArgument("server", String.class);

if (server.startsWith("wc")) server = server.toUpperCase(Locale.ROOT);

try {
Expand All @@ -67,12 +94,19 @@ private int serverInfo(CommandContext<CommandSourceStack> context) {
}

Set<String> players = serverProfile.getPlayers();
MutableComponent message = Component.literal(server + ":" + "\n")
.withStyle(ChatFormatting.GOLD)
.append(Component.literal("Uptime: " + serverProfile.getUptime() + "\n")
.withStyle(ChatFormatting.DARK_AQUA))
.append(Component.literal("Online players on " + server + ": " + players.size() + "\n")
.withStyle(ChatFormatting.DARK_AQUA));
MutableComponent message = Component.empty()
.append(getServerComponent(server).withStyle(ChatFormatting.GOLD))
.append(Component.literal(":" + "\n")
.withStyle(ChatFormatting.GOLD)
.append(Component.literal("Uptime: ")
.withStyle(ChatFormatting.DARK_AQUA)
.append(Component.literal(serverProfile.getUptime() + "\n")
.withStyle(ChatFormatting.AQUA)))
.append(Component.literal("Online players on ")
.withStyle(ChatFormatting.DARK_AQUA)
.append(Component.literal(server).withStyle(ChatFormatting.AQUA))
.append(Component.literal(": ").withStyle(ChatFormatting.DARK_AQUA))
.append(Component.literal(players.size() + "\n").withStyle(ChatFormatting.AQUA))));

if (players.isEmpty()) {
message.append(Component.literal("No players!").withStyle(ChatFormatting.AQUA));
Expand All @@ -86,12 +120,6 @@ private int serverInfo(CommandContext<CommandSourceStack> context) {
}

private int serverList(CommandContext<CommandSourceStack> context) {
if (!Models.ServerList.forceUpdate(3000)) {
context.getSource()
.sendFailure(Component.literal("Network problems; using cached data")
.withStyle(ChatFormatting.RED));
}

MutableComponent message = Component.literal("Server list:").withStyle(ChatFormatting.DARK_AQUA);

for (String serverType : Models.ServerList.getWynnServerTypes()) {
Expand All @@ -105,8 +133,14 @@ private int serverList(CommandContext<CommandSourceStack> context) {
StringUtils.capitalizeFirst(serverType) + " (" + currentTypeServers.size() + "):\n")
.withStyle(ChatFormatting.GOLD));

message.append(
Component.literal(String.join(", ", currentTypeServers)).withStyle(ChatFormatting.AQUA));
String lastServer = currentTypeServers.get(currentTypeServers.size() - 1);
for (String server : currentTypeServers) {
message.append(getServerComponent(server).withStyle(ChatFormatting.AQUA));

if (!server.equals(lastServer)) {
message.append(Component.literal(", ").withStyle(ChatFormatting.DARK_AQUA));
}
}
}

context.getSource().sendSuccess(() -> message, false);
Expand All @@ -115,20 +149,47 @@ private int serverList(CommandContext<CommandSourceStack> context) {
}

private int serverUptimeList(CommandContext<CommandSourceStack> context) {
if (!Models.ServerList.forceUpdate(3000)) {
context.getSource()
.sendFailure(Component.literal("Network problems; using cached data")
.withStyle(ChatFormatting.RED));
}

List<String> sortedServers = Models.ServerList.getServersSortedOnUptime();

MutableComponent message = Component.literal("Server list:").withStyle(ChatFormatting.DARK_AQUA);
MutableComponent message = Component.literal("Server list:").withStyle(ChatFormatting.GOLD);
for (String server : sortedServers) {
message.append("\n");
message.append(Component.literal(
server + ": " + Models.ServerList.getServer(server).getUptime())
.withStyle(ChatFormatting.AQUA));
message.append(getServerComponent(server)
.withStyle(ChatFormatting.DARK_AQUA)
.append(Component.literal(
": " + Models.ServerList.getServer(server).getUptime())
.withStyle(ChatFormatting.AQUA)));
}

context.getSource().sendSuccess(() -> message, false);

return 1;
}

private int serverSoulpointsList(CommandContext<CommandSourceStack> context) {
List<ServerProfile> soulPointServers = Models.ServerList.getServersSortedOnUptime().stream()
.map(Models.ServerList::getServer)
.filter(Objects::nonNull)
.filter(server -> server.getUptimeInMinutes() % 20 >= 10)
.sorted(Comparator.comparing(server -> 20 - (server.getUptimeInMinutes() % 20)))
.toList();

MutableComponent message = Component.literal("Soul point server list:").withStyle(ChatFormatting.GOLD);
for (ServerProfile server : soulPointServers) {
int minutesUntilSoulPoint = 20 - (server.getUptimeInMinutes() % 20);

// 1-2 minutes before - Green
// 3-4 minutes before - Yellow
// 5+ minutes before - Red
ChatFormatting timeColor = minutesUntilSoulPoint <= 2
? ChatFormatting.GREEN
: (minutesUntilSoulPoint <= 4 ? ChatFormatting.YELLOW : ChatFormatting.RED);

message.append("\n");
message.append(getServerComponent(server.getServerName())
.withStyle(ChatFormatting.DARK_AQUA)
.append(Component.literal(": In " + minutesUntilSoulPoint + "m")
.withStyle(timeColor)));
}

context.getSource().sendSuccess(() -> message, false);
Expand All @@ -140,4 +201,19 @@ private int syntaxError(CommandContext<CommandSourceStack> context) {
context.getSource().sendFailure(Component.literal("Missing argument").withStyle(ChatFormatting.RED));
return 0;
}

private MutableComponent getServerComponent(String server) {
return Component.literal(server)
.withStyle(style -> style.withHoverEvent(new HoverEvent(
HoverEvent.Action.SHOW_TEXT,
Component.literal("Click to switch to ")
.withStyle(ChatFormatting.GRAY)
.append(Component.literal(server).withStyle(ChatFormatting.WHITE))
.append(Component.literal("\n(Requires ")
.withStyle(ChatFormatting.DARK_PURPLE)
.append(Component.literal("HERO").withStyle(ChatFormatting.LIGHT_PURPLE))
.append(Component.literal(" rank)").withStyle(ChatFormatting.DARK_PURPLE))))))
.withStyle(style ->
style.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/switch " + server)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,9 @@
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import net.minecraftforge.eventbus.api.SubscribeEvent;

public final class ServerListModel extends Model {
Expand Down Expand Up @@ -78,17 +76,6 @@ public ServerProfile getServer(String worldId) {
return availableServers.get(worldId);
}

public boolean forceUpdate(int timeOutMs) {
CompletableFuture<Boolean> future = updateServerList();
try {
future.get(timeOutMs, TimeUnit.MILLISECONDS);
return true;
} catch (InterruptedException | ExecutionException | TimeoutException e) {
// if timeout is reached, return false
return false;
}
}

@SubscribeEvent
public void onWorldStateChange(WorldStateEvent event) {
if (event.getNewState() != WorldState.HUB && event.getNewState() != WorldState.CONNECTING) return;
Expand All @@ -110,6 +97,16 @@ private CompletableFuture<Boolean> updateServerList() {

long serverTime = dl.getResponseTimestamp();
for (Map.Entry<String, JsonElement> entry : servers.entrySet()) {
JsonElement serverElement = entry.getValue();

if (!serverElement.isJsonObject()) {
WynntilsMod.warn("Server element is not a JsonObject: " + serverElement);
continue;
}

// Inject the server name into the server profile
serverElement.getAsJsonObject().addProperty("serverName", entry.getKey());

ServerProfile profile = WynntilsMod.GSON.fromJson(entry.getValue(), ServerProfile.class);
profile.matchTime(serverTime);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright © Wynntils 2022-2023.
* Copyright © Wynntils 2022-2024.
* This file is released under LGPLv3. See LICENSE for full license details.
*/
package com.wynntils.models.worlds.profile;
Expand All @@ -8,14 +8,21 @@
import java.util.concurrent.TimeUnit;

public class ServerProfile {
private long firstSeen;
private final String serverName;
private final Set<String> players;

public ServerProfile(long firstSeem, Set<String> players) {
private long firstSeen;

public ServerProfile(String serverName, Set<String> players, long firstSeem) {
this.serverName = serverName;
this.firstSeen = firstSeem;
this.players = players;
}

public String getServerName() {
return serverName;
}

public Set<String> getPlayers() {
return players;
}
Expand All @@ -34,6 +41,10 @@ public String getUptime() {
- TimeUnit.HOURS.toMinutes(TimeUnit.MILLISECONDS.toHours(millis)));
}

public int getUptimeInMinutes() {
return (int) TimeUnit.MILLISECONDS.toMinutes(System.currentTimeMillis() - firstSeen);
}

/**
* This makes the firstSeen match the user computer time instead of the server time
* @param serverTime the input server time
Expand Down

0 comments on commit aa94e6e

Please sign in to comment.