package com.mojang.realmsclient.dto; import com.google.common.collect.ComparisonChain; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.gson.annotations.JsonAdapter; import com.google.gson.annotations.SerializedName; import com.mojang.logging.LogUtils; import com.mojang.util.UUIDTypeAdapter; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.UUID; import java.util.Map.Entry; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.Util; import net.minecraft.client.Minecraft; import net.minecraft.client.multiplayer.ServerData; import net.minecraft.network.chat.Component; import org.apache.commons.lang3.builder.EqualsBuilder; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; @Environment(EnvType.CLIENT) public class RealmsServer extends ValueObject implements ReflectionBasedSerialization { private static final Logger LOGGER = LogUtils.getLogger(); private static final int NO_VALUE = -1; public static final Component WORLD_CLOSED_COMPONENT = Component.translatable("mco.play.button.realm.closed"); @SerializedName("id") public long id = -1L; @Nullable @SerializedName("remoteSubscriptionId") public String remoteSubscriptionId; @Nullable @SerializedName("name") public String name; @SerializedName("motd") public String motd = ""; @SerializedName("state") public RealmsServer.State state = RealmsServer.State.CLOSED; @Nullable @SerializedName("owner") public String owner; @SerializedName("ownerUUID") @JsonAdapter(UUIDTypeAdapter.class) public UUID ownerUUID = Util.NIL_UUID; @SerializedName("players") public List players = Lists.newArrayList(); @SerializedName("slots") private List slotList = createEmptySlots(); @Exclude public Map slots = new HashMap(); @SerializedName("expired") public boolean expired; @SerializedName("expiredTrial") public boolean expiredTrial = false; @SerializedName("daysLeft") public int daysLeft; @SerializedName("worldType") public RealmsServer.WorldType worldType = RealmsServer.WorldType.NORMAL; @SerializedName("isHardcore") public boolean isHardcore = false; @SerializedName("gameMode") public int gameMode = -1; @SerializedName("activeSlot") public int activeSlot = -1; @Nullable @SerializedName("minigameName") public String minigameName; @SerializedName("minigameId") public int minigameId = -1; @Nullable @SerializedName("minigameImage") public String minigameImage; @SerializedName("parentWorldId") public long parentRealmId = -1L; @Nullable @SerializedName("parentWorldName") public String parentWorldName; @SerializedName("activeVersion") public String activeVersion = ""; @SerializedName("compatibility") public RealmsServer.Compatibility compatibility = RealmsServer.Compatibility.UNVERIFIABLE; @Nullable @SerializedName("regionSelectionPreference") public RegionSelectionPreferenceDto regionSelectionPreference; public String getDescription() { return this.motd; } @Nullable public String getName() { return this.name; } @Nullable public String getMinigameName() { return this.minigameName; } public void setName(String name) { this.name = name; } public void setDescription(String motd) { this.motd = motd; } public static RealmsServer parse(GuardedSerializer serializer, String json) { try { RealmsServer realmsServer = serializer.fromJson(json, RealmsServer.class); if (realmsServer == null) { LOGGER.error("Could not parse McoServer: {}", json); return new RealmsServer(); } else { finalize(realmsServer); return realmsServer; } } catch (Exception var3) { LOGGER.error("Could not parse McoServer: {}", var3.getMessage()); return new RealmsServer(); } } public static void finalize(RealmsServer server) { if (server.players == null) { server.players = Lists.newArrayList(); } if (server.slotList == null) { server.slotList = createEmptySlots(); } if (server.slots == null) { server.slots = new HashMap(); } if (server.worldType == null) { server.worldType = RealmsServer.WorldType.NORMAL; } if (server.activeVersion == null) { server.activeVersion = ""; } if (server.compatibility == null) { server.compatibility = RealmsServer.Compatibility.UNVERIFIABLE; } if (server.regionSelectionPreference == null) { server.regionSelectionPreference = RegionSelectionPreferenceDto.DEFAULT; } sortInvited(server); finalizeSlots(server); } private static void sortInvited(RealmsServer server) { server.players .sort( (playerInfo, playerInfo2) -> ComparisonChain.start() .compareFalseFirst(playerInfo2.getAccepted(), playerInfo.getAccepted()) .compare(playerInfo.getName().toLowerCase(Locale.ROOT), playerInfo2.getName().toLowerCase(Locale.ROOT)) .result() ); } private static void finalizeSlots(RealmsServer server) { server.slotList.forEach(realmsSlot -> server.slots.put(realmsSlot.slotId, realmsSlot)); for (int i = 1; i <= 3; i++) { if (!server.slots.containsKey(i)) { server.slots.put(i, RealmsSlot.defaults(i)); } } } private static List createEmptySlots() { List list = new ArrayList(); list.add(RealmsSlot.defaults(1)); list.add(RealmsSlot.defaults(2)); list.add(RealmsSlot.defaults(3)); return list; } public boolean isCompatible() { return this.compatibility.isCompatible(); } public boolean needsUpgrade() { return this.compatibility.needsUpgrade(); } public boolean needsDowngrade() { return this.compatibility.needsDowngrade(); } public boolean shouldPlayButtonBeActive() { boolean bl = !this.expired && this.state == RealmsServer.State.OPEN; return bl && (this.isCompatible() || this.needsUpgrade() || this.isSelfOwnedServer()); } private boolean isSelfOwnedServer() { return Minecraft.getInstance().isLocalPlayer(this.ownerUUID); } public int hashCode() { return Objects.hash(new Object[]{this.id, this.name, this.motd, this.state, this.owner, this.expired}); } public boolean equals(Object object) { if (object == null) { return false; } else if (object == this) { return true; } else if (object.getClass() != this.getClass()) { return false; } else { RealmsServer realmsServer = (RealmsServer)object; return new EqualsBuilder() .append(this.id, realmsServer.id) .append(this.name, realmsServer.name) .append(this.motd, realmsServer.motd) .append(this.state, realmsServer.state) .append(this.owner, realmsServer.owner) .append(this.expired, realmsServer.expired) .append(this.worldType, this.worldType) .isEquals(); } } public RealmsServer clone() { RealmsServer realmsServer = new RealmsServer(); realmsServer.id = this.id; realmsServer.remoteSubscriptionId = this.remoteSubscriptionId; realmsServer.name = this.name; realmsServer.motd = this.motd; realmsServer.state = this.state; realmsServer.owner = this.owner; realmsServer.players = this.players; realmsServer.slotList = this.slotList.stream().map(RealmsSlot::clone).toList(); realmsServer.slots = this.cloneSlots(this.slots); realmsServer.expired = this.expired; realmsServer.expiredTrial = this.expiredTrial; realmsServer.daysLeft = this.daysLeft; realmsServer.worldType = this.worldType; realmsServer.isHardcore = this.isHardcore; realmsServer.gameMode = this.gameMode; realmsServer.ownerUUID = this.ownerUUID; realmsServer.minigameName = this.minigameName; realmsServer.activeSlot = this.activeSlot; realmsServer.minigameId = this.minigameId; realmsServer.minigameImage = this.minigameImage; realmsServer.parentWorldName = this.parentWorldName; realmsServer.parentRealmId = this.parentRealmId; realmsServer.activeVersion = this.activeVersion; realmsServer.compatibility = this.compatibility; realmsServer.regionSelectionPreference = this.regionSelectionPreference != null ? this.regionSelectionPreference.clone() : null; return realmsServer; } public Map cloneSlots(Map slots) { Map map = Maps.newHashMap(); for (Entry entry : slots.entrySet()) { map.put( (Integer)entry.getKey(), new RealmsSlot((Integer)entry.getKey(), ((RealmsSlot)entry.getValue()).options.clone(), ((RealmsSlot)entry.getValue()).settings) ); } return map; } public boolean isSnapshotRealm() { return this.parentRealmId != -1L; } public boolean isMinigameActive() { return this.worldType == RealmsServer.WorldType.MINIGAME; } public String getWorldName(int slot) { return this.name == null ? ((RealmsSlot)this.slots.get(slot)).options.getSlotName(slot) : this.name + " (" + ((RealmsSlot)this.slots.get(slot)).options.getSlotName(slot) + ")"; } public ServerData toServerData(String ip) { return new ServerData((String)Objects.requireNonNullElse(this.name, "unknown server"), ip, ServerData.Type.REALM); } @Environment(EnvType.CLIENT) public static enum Compatibility { UNVERIFIABLE, INCOMPATIBLE, RELEASE_TYPE_INCOMPATIBLE, NEEDS_DOWNGRADE, NEEDS_UPGRADE, COMPATIBLE; public boolean isCompatible() { return this == COMPATIBLE; } public boolean needsUpgrade() { return this == NEEDS_UPGRADE; } public boolean needsDowngrade() { return this == NEEDS_DOWNGRADE; } } @Environment(EnvType.CLIENT) public static class McoServerComparator implements Comparator { private final String refOwner; public McoServerComparator(String refOwner) { this.refOwner = refOwner; } public int compare(RealmsServer first, RealmsServer second) { return ComparisonChain.start() .compareTrueFirst(first.isSnapshotRealm(), second.isSnapshotRealm()) .compareTrueFirst(first.state == RealmsServer.State.UNINITIALIZED, second.state == RealmsServer.State.UNINITIALIZED) .compareTrueFirst(first.expiredTrial, second.expiredTrial) .compareTrueFirst(Objects.equals(first.owner, this.refOwner), Objects.equals(second.owner, this.refOwner)) .compareFalseFirst(first.expired, second.expired) .compareTrueFirst(first.state == RealmsServer.State.OPEN, second.state == RealmsServer.State.OPEN) .compare(first.id, second.id) .result(); } } @Environment(EnvType.CLIENT) public static enum State { CLOSED, OPEN, UNINITIALIZED; } @Environment(EnvType.CLIENT) public static enum WorldType { NORMAL, MINIGAME, ADVENTUREMAP, EXPERIENCE, INSPIRATION; } }