1206 lines
61 KiB
Java
1206 lines
61 KiB
Java
/*
|
|
* Decompiled with CFR 0.152.
|
|
*
|
|
* Could not load the following classes:
|
|
* com.google.common.collect.Lists
|
|
* com.google.common.util.concurrent.RateLimiter
|
|
* com.mojang.logging.LogUtils
|
|
* org.apache.commons.lang3.StringUtils
|
|
* org.jspecify.annotations.Nullable
|
|
* org.slf4j.Logger
|
|
*/
|
|
package com.mojang.realmsclient;
|
|
|
|
import com.google.common.collect.Lists;
|
|
import com.google.common.util.concurrent.RateLimiter;
|
|
import com.mojang.logging.LogUtils;
|
|
import com.mojang.realmsclient.RealmsAvailability;
|
|
import com.mojang.realmsclient.client.Ping;
|
|
import com.mojang.realmsclient.client.RealmsClient;
|
|
import com.mojang.realmsclient.dto.PingResult;
|
|
import com.mojang.realmsclient.dto.RealmsNews;
|
|
import com.mojang.realmsclient.dto.RealmsNotification;
|
|
import com.mojang.realmsclient.dto.RealmsServer;
|
|
import com.mojang.realmsclient.dto.RealmsServerPlayerLists;
|
|
import com.mojang.realmsclient.dto.RegionPingResult;
|
|
import com.mojang.realmsclient.exception.RealmsServiceException;
|
|
import com.mojang.realmsclient.gui.RealmsDataFetcher;
|
|
import com.mojang.realmsclient.gui.RealmsServerList;
|
|
import com.mojang.realmsclient.gui.screens.AddRealmPopupScreen;
|
|
import com.mojang.realmsclient.gui.screens.RealmsCreateRealmScreen;
|
|
import com.mojang.realmsclient.gui.screens.RealmsGenericErrorScreen;
|
|
import com.mojang.realmsclient.gui.screens.RealmsLongRunningMcoTaskScreen;
|
|
import com.mojang.realmsclient.gui.screens.RealmsPendingInvitesScreen;
|
|
import com.mojang.realmsclient.gui.screens.RealmsPopups;
|
|
import com.mojang.realmsclient.gui.screens.configuration.RealmsConfigureWorldScreen;
|
|
import com.mojang.realmsclient.gui.task.DataFetcher;
|
|
import com.mojang.realmsclient.util.RealmsPersistence;
|
|
import com.mojang.realmsclient.util.RealmsUtil;
|
|
import com.mojang.realmsclient.util.task.GetServerDetailsTask;
|
|
import java.util.ArrayList;
|
|
import java.util.Collection;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Objects;
|
|
import java.util.Optional;
|
|
import java.util.Set;
|
|
import java.util.UUID;
|
|
import java.util.concurrent.CompletableFuture;
|
|
import java.util.concurrent.Executor;
|
|
import java.util.function.Consumer;
|
|
import java.util.function.Supplier;
|
|
import net.minecraft.ChatFormatting;
|
|
import net.minecraft.SharedConstants;
|
|
import net.minecraft.client.Minecraft;
|
|
import net.minecraft.client.gui.GuiGraphics;
|
|
import net.minecraft.client.gui.components.AbstractWidget;
|
|
import net.minecraft.client.gui.components.Button;
|
|
import net.minecraft.client.gui.components.CycleButton;
|
|
import net.minecraft.client.gui.components.FocusableTextWidget;
|
|
import net.minecraft.client.gui.components.ImageButton;
|
|
import net.minecraft.client.gui.components.ImageWidget;
|
|
import net.minecraft.client.gui.components.LoadingDotsWidget;
|
|
import net.minecraft.client.gui.components.MultiLineTextWidget;
|
|
import net.minecraft.client.gui.components.ObjectSelectionList;
|
|
import net.minecraft.client.gui.components.PlayerFaceRenderer;
|
|
import net.minecraft.client.gui.components.PopupScreen;
|
|
import net.minecraft.client.gui.components.SpriteIconButton;
|
|
import net.minecraft.client.gui.components.Tooltip;
|
|
import net.minecraft.client.gui.components.WidgetSprites;
|
|
import net.minecraft.client.gui.components.WidgetTooltipHolder;
|
|
import net.minecraft.client.gui.components.events.GuiEventListener;
|
|
import net.minecraft.client.gui.layouts.FrameLayout;
|
|
import net.minecraft.client.gui.layouts.GridLayout;
|
|
import net.minecraft.client.gui.layouts.HeaderAndFooterLayout;
|
|
import net.minecraft.client.gui.layouts.Layout;
|
|
import net.minecraft.client.gui.layouts.LayoutSettings;
|
|
import net.minecraft.client.gui.layouts.LinearLayout;
|
|
import net.minecraft.client.gui.layouts.SpacerElement;
|
|
import net.minecraft.client.gui.navigation.ScreenRectangle;
|
|
import net.minecraft.client.gui.screens.ConfirmLinkScreen;
|
|
import net.minecraft.client.gui.screens.Screen;
|
|
import net.minecraft.client.gui.screens.inventory.tooltip.ClientActivePlayersTooltip;
|
|
import net.minecraft.client.input.KeyEvent;
|
|
import net.minecraft.client.input.MouseButtonEvent;
|
|
import net.minecraft.client.renderer.PlayerSkinRenderCache;
|
|
import net.minecraft.client.renderer.RenderPipelines;
|
|
import net.minecraft.client.resources.sounds.SimpleSoundInstance;
|
|
import net.minecraft.network.chat.CommonComponents;
|
|
import net.minecraft.network.chat.Component;
|
|
import net.minecraft.network.chat.MutableComponent;
|
|
import net.minecraft.realms.RealmsScreen;
|
|
import net.minecraft.resources.Identifier;
|
|
import net.minecraft.sounds.SoundEvents;
|
|
import net.minecraft.util.CommonLinks;
|
|
import net.minecraft.util.Util;
|
|
import net.minecraft.world.item.component.ResolvableProfile;
|
|
import net.minecraft.world.level.GameType;
|
|
import org.apache.commons.lang3.StringUtils;
|
|
import org.jspecify.annotations.Nullable;
|
|
import org.slf4j.Logger;
|
|
|
|
public class RealmsMainScreen
|
|
extends RealmsScreen {
|
|
private static final Identifier INFO_SPRITE = Identifier.withDefaultNamespace("icon/info");
|
|
private static final Identifier NEW_REALM_SPRITE = Identifier.withDefaultNamespace("icon/new_realm");
|
|
private static final Identifier EXPIRED_SPRITE = Identifier.withDefaultNamespace("realm_status/expired");
|
|
private static final Identifier EXPIRES_SOON_SPRITE = Identifier.withDefaultNamespace("realm_status/expires_soon");
|
|
private static final Identifier OPEN_SPRITE = Identifier.withDefaultNamespace("realm_status/open");
|
|
private static final Identifier CLOSED_SPRITE = Identifier.withDefaultNamespace("realm_status/closed");
|
|
private static final Identifier INVITE_SPRITE = Identifier.withDefaultNamespace("icon/invite");
|
|
private static final Identifier NEWS_SPRITE = Identifier.withDefaultNamespace("icon/news");
|
|
public static final Identifier HARDCORE_MODE_SPRITE = Identifier.withDefaultNamespace("hud/heart/hardcore_full");
|
|
private static final Logger LOGGER = LogUtils.getLogger();
|
|
private static final Identifier NO_REALMS_LOCATION = Identifier.withDefaultNamespace("textures/gui/realms/no_realms.png");
|
|
private static final Component TITLE = Component.translatable("menu.online");
|
|
private static final Component LOADING_TEXT = Component.translatable("mco.selectServer.loading");
|
|
private static final Component SERVER_UNITIALIZED_TEXT = Component.translatable("mco.selectServer.uninitialized");
|
|
private static final Component SUBSCRIPTION_EXPIRED_TEXT = Component.translatable("mco.selectServer.expiredList");
|
|
private static final Component SUBSCRIPTION_RENEW_TEXT = Component.translatable("mco.selectServer.expiredRenew");
|
|
private static final Component TRIAL_EXPIRED_TEXT = Component.translatable("mco.selectServer.expiredTrial");
|
|
private static final Component PLAY_TEXT = Component.translatable("mco.selectServer.play");
|
|
private static final Component LEAVE_SERVER_TEXT = Component.translatable("mco.selectServer.leave");
|
|
private static final Component CONFIGURE_SERVER_TEXT = Component.translatable("mco.selectServer.configure");
|
|
private static final Component SERVER_EXPIRED_TOOLTIP = Component.translatable("mco.selectServer.expired");
|
|
private static final Component SERVER_EXPIRES_SOON_TOOLTIP = Component.translatable("mco.selectServer.expires.soon");
|
|
private static final Component SERVER_EXPIRES_IN_DAY_TOOLTIP = Component.translatable("mco.selectServer.expires.day");
|
|
private static final Component SERVER_OPEN_TOOLTIP = Component.translatable("mco.selectServer.open");
|
|
private static final Component SERVER_CLOSED_TOOLTIP = Component.translatable("mco.selectServer.closed");
|
|
private static final Component UNITIALIZED_WORLD_NARRATION = Component.translatable("gui.narrate.button", SERVER_UNITIALIZED_TEXT);
|
|
private static final Component NO_REALMS_TEXT = Component.translatable("mco.selectServer.noRealms");
|
|
private static final Component NO_PENDING_INVITES = Component.translatable("mco.invites.nopending");
|
|
private static final Component PENDING_INVITES = Component.translatable("mco.invites.pending");
|
|
private static final Component INCOMPATIBLE_POPUP_TITLE = Component.translatable("mco.compatibility.incompatible.popup.title");
|
|
private static final Component INCOMPATIBLE_RELEASE_TYPE_POPUP_MESSAGE = Component.translatable("mco.compatibility.incompatible.releaseType.popup.message");
|
|
private static final int BUTTON_WIDTH = 100;
|
|
private static final int BUTTON_COLUMNS = 3;
|
|
private static final int BUTTON_SPACING = 4;
|
|
private static final int CONTENT_WIDTH = 308;
|
|
private static final int LOGO_PADDING = 5;
|
|
private static final int HEADER_HEIGHT = 44;
|
|
private static final int FOOTER_PADDING = 11;
|
|
private static final int NEW_REALM_SPRITE_WIDTH = 40;
|
|
private static final int NEW_REALM_SPRITE_HEIGHT = 20;
|
|
private static final boolean SNAPSHOT;
|
|
private static boolean snapshotToggle;
|
|
private final CompletableFuture<RealmsAvailability.Result> availability = RealmsAvailability.get();
|
|
private @Nullable DataFetcher.Subscription dataSubscription;
|
|
private final Set<UUID> handledSeenNotifications = new HashSet<UUID>();
|
|
private static boolean regionsPinged;
|
|
private final RateLimiter inviteNarrationLimiter;
|
|
private final Screen lastScreen;
|
|
private Button playButton;
|
|
private Button backButton;
|
|
private Button renewButton;
|
|
private Button configureButton;
|
|
private Button leaveButton;
|
|
private RealmSelectionList realmSelectionList;
|
|
private RealmsServerList serverList;
|
|
private List<RealmsServer> availableSnapshotServers = List.of();
|
|
private RealmsServerPlayerLists onlinePlayersPerRealm = new RealmsServerPlayerLists(Map.of());
|
|
private volatile boolean trialsAvailable;
|
|
private volatile @Nullable String newsLink;
|
|
private final List<RealmsNotification> notifications = new ArrayList<RealmsNotification>();
|
|
private Button addRealmButton;
|
|
private NotificationButton pendingInvitesButton;
|
|
private NotificationButton newsButton;
|
|
private LayoutState activeLayoutState;
|
|
private @Nullable HeaderAndFooterLayout layout;
|
|
|
|
public RealmsMainScreen(Screen lastScreen) {
|
|
super(TITLE);
|
|
this.lastScreen = lastScreen;
|
|
this.inviteNarrationLimiter = RateLimiter.create((double)0.01666666753590107);
|
|
}
|
|
|
|
@Override
|
|
public void init() {
|
|
this.serverList = new RealmsServerList(this.minecraft);
|
|
this.realmSelectionList = new RealmSelectionList();
|
|
MutableComponent invitesTitle = Component.translatable("mco.invites.title");
|
|
this.pendingInvitesButton = new NotificationButton(invitesTitle, INVITE_SPRITE, b -> this.minecraft.setScreen(new RealmsPendingInvitesScreen(this, invitesTitle)), null);
|
|
MutableComponent newsTitle = Component.translatable("mco.news");
|
|
this.newsButton = new NotificationButton(newsTitle, NEWS_SPRITE, b -> {
|
|
String newsLink = this.newsLink;
|
|
if (newsLink == null) {
|
|
return;
|
|
}
|
|
ConfirmLinkScreen.confirmLinkNow((Screen)this, newsLink);
|
|
if (this.newsButton.notificationCount() != 0) {
|
|
RealmsPersistence.RealmsPersistenceData data = RealmsPersistence.readFile();
|
|
data.hasUnreadNews = false;
|
|
RealmsPersistence.writeFile(data);
|
|
this.newsButton.setNotificationCount(0);
|
|
}
|
|
}, newsTitle);
|
|
this.playButton = Button.builder(PLAY_TEXT, button -> RealmsMainScreen.play(this.getSelectedServer(), this)).width(100).build();
|
|
this.configureButton = Button.builder(CONFIGURE_SERVER_TEXT, button -> this.configureClicked(this.getSelectedServer())).width(100).build();
|
|
this.renewButton = Button.builder(SUBSCRIPTION_RENEW_TEXT, button -> this.onRenew(this.getSelectedServer())).width(100).build();
|
|
this.leaveButton = Button.builder(LEAVE_SERVER_TEXT, button -> this.leaveClicked(this.getSelectedServer())).width(100).build();
|
|
this.addRealmButton = Button.builder(Component.translatable("mco.selectServer.purchase"), button -> this.openTrialAvailablePopup()).size(100, 20).build();
|
|
this.backButton = Button.builder(CommonComponents.GUI_BACK, button -> this.onClose()).width(100).build();
|
|
if (RealmsClient.ENVIRONMENT == RealmsClient.Environment.STAGE) {
|
|
this.addRenderableWidget(CycleButton.booleanBuilder(Component.literal("Snapshot"), Component.literal("Release"), snapshotToggle).create(5, 5, 100, 20, Component.literal("Realm"), (button, value) -> {
|
|
snapshotToggle = value;
|
|
this.availableSnapshotServers = List.of();
|
|
this.debugRefreshDataFetchers();
|
|
}));
|
|
}
|
|
this.updateLayout(LayoutState.LOADING);
|
|
this.updateButtonStates();
|
|
this.availability.thenAcceptAsync(result -> {
|
|
Screen errorScreen = result.createErrorScreen(this.lastScreen);
|
|
if (errorScreen == null) {
|
|
this.dataSubscription = this.initDataFetcher(this.minecraft.realmsDataFetcher());
|
|
} else {
|
|
this.minecraft.setScreen(errorScreen);
|
|
}
|
|
}, this.screenExecutor);
|
|
}
|
|
|
|
public static boolean isSnapshot() {
|
|
return SNAPSHOT && snapshotToggle;
|
|
}
|
|
|
|
@Override
|
|
protected void repositionElements() {
|
|
if (this.layout != null) {
|
|
this.realmSelectionList.updateSize(this.width, this.layout);
|
|
this.layout.arrangeElements();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onClose() {
|
|
this.minecraft.setScreen(this.lastScreen);
|
|
}
|
|
|
|
private void updateLayout() {
|
|
if (this.serverList.isEmpty() && this.availableSnapshotServers.isEmpty() && this.notifications.isEmpty()) {
|
|
this.updateLayout(LayoutState.NO_REALMS);
|
|
} else {
|
|
this.updateLayout(LayoutState.LIST);
|
|
}
|
|
}
|
|
|
|
private void updateLayout(LayoutState state) {
|
|
if (this.activeLayoutState == state) {
|
|
return;
|
|
}
|
|
if (this.layout != null) {
|
|
this.layout.visitWidgets(x$0 -> this.removeWidget((GuiEventListener)x$0));
|
|
}
|
|
this.layout = this.createLayout(state);
|
|
this.activeLayoutState = state;
|
|
this.layout.visitWidgets(x$0 -> {
|
|
AbstractWidget cfr_ignored_0 = (AbstractWidget)this.addRenderableWidget(x$0);
|
|
});
|
|
this.repositionElements();
|
|
}
|
|
|
|
private HeaderAndFooterLayout createLayout(LayoutState state) {
|
|
HeaderAndFooterLayout layout = new HeaderAndFooterLayout(this);
|
|
layout.setHeaderHeight(44);
|
|
layout.addToHeader(this.createHeader());
|
|
Layout footer = this.createFooter(state);
|
|
footer.arrangeElements();
|
|
layout.setFooterHeight(footer.getHeight() + 22);
|
|
layout.addToFooter(footer);
|
|
switch (state.ordinal()) {
|
|
case 0: {
|
|
layout.addToContents(new LoadingDotsWidget(this.font, LOADING_TEXT));
|
|
break;
|
|
}
|
|
case 1: {
|
|
layout.addToContents(this.createNoRealmsContent());
|
|
break;
|
|
}
|
|
case 2: {
|
|
layout.addToContents(this.realmSelectionList);
|
|
}
|
|
}
|
|
return layout;
|
|
}
|
|
|
|
private Layout createHeader() {
|
|
int sideCellWidth = 90;
|
|
LinearLayout buttons = LinearLayout.horizontal().spacing(4);
|
|
buttons.defaultCellSetting().alignVerticallyMiddle();
|
|
buttons.addChild(this.pendingInvitesButton);
|
|
buttons.addChild(this.newsButton);
|
|
LinearLayout header = LinearLayout.horizontal();
|
|
header.defaultCellSetting().alignVerticallyMiddle();
|
|
header.addChild(SpacerElement.width(90));
|
|
header.addChild(RealmsMainScreen.realmsLogo(), LayoutSettings::alignHorizontallyCenter);
|
|
header.addChild(new FrameLayout(90, 44)).addChild(buttons, LayoutSettings::alignHorizontallyRight);
|
|
return header;
|
|
}
|
|
|
|
private Layout createFooter(LayoutState state) {
|
|
GridLayout footer = new GridLayout().spacing(4);
|
|
GridLayout.RowHelper helper = footer.createRowHelper(3);
|
|
if (state == LayoutState.LIST) {
|
|
helper.addChild(this.playButton);
|
|
helper.addChild(this.configureButton);
|
|
helper.addChild(this.renewButton);
|
|
helper.addChild(this.leaveButton);
|
|
}
|
|
helper.addChild(this.addRealmButton);
|
|
helper.addChild(this.backButton);
|
|
return footer;
|
|
}
|
|
|
|
private LinearLayout createNoRealmsContent() {
|
|
LinearLayout content = LinearLayout.vertical().spacing(8);
|
|
content.defaultCellSetting().alignHorizontallyCenter();
|
|
content.addChild(ImageWidget.texture(130, 64, NO_REALMS_LOCATION, 130, 64));
|
|
content.addChild(FocusableTextWidget.builder(NO_REALMS_TEXT, this.font).maxWidth(308).alwaysShowBorder(false).backgroundFill(FocusableTextWidget.BackgroundFill.ON_FOCUS).build());
|
|
return content;
|
|
}
|
|
|
|
private void updateButtonStates() {
|
|
RealmsServer server = this.getSelectedServer();
|
|
boolean serverSelected = server != null;
|
|
this.addRealmButton.active = this.activeLayoutState != LayoutState.LOADING;
|
|
boolean bl = this.playButton.active = serverSelected && server.shouldPlayButtonBeActive();
|
|
if (!this.playButton.active && serverSelected && server.state == RealmsServer.State.CLOSED) {
|
|
this.playButton.setTooltip(Tooltip.create(RealmsServer.WORLD_CLOSED_COMPONENT));
|
|
}
|
|
this.renewButton.active = serverSelected && this.shouldRenewButtonBeActive(server);
|
|
this.leaveButton.active = serverSelected && this.shouldLeaveButtonBeActive(server);
|
|
this.configureButton.active = serverSelected && this.shouldConfigureButtonBeActive(server);
|
|
}
|
|
|
|
private boolean shouldRenewButtonBeActive(RealmsServer server) {
|
|
return server.expired && RealmsMainScreen.isSelfOwnedServer(server);
|
|
}
|
|
|
|
private boolean shouldConfigureButtonBeActive(RealmsServer server) {
|
|
return RealmsMainScreen.isSelfOwnedServer(server) && server.state != RealmsServer.State.UNINITIALIZED;
|
|
}
|
|
|
|
private boolean shouldLeaveButtonBeActive(RealmsServer server) {
|
|
return !RealmsMainScreen.isSelfOwnedServer(server);
|
|
}
|
|
|
|
@Override
|
|
public void tick() {
|
|
super.tick();
|
|
if (this.dataSubscription != null) {
|
|
this.dataSubscription.tick();
|
|
}
|
|
}
|
|
|
|
public static void refreshPendingInvites() {
|
|
Minecraft.getInstance().realmsDataFetcher().pendingInvitesTask.reset();
|
|
}
|
|
|
|
public static void refreshServerList() {
|
|
Minecraft.getInstance().realmsDataFetcher().serverListUpdateTask.reset();
|
|
}
|
|
|
|
private void debugRefreshDataFetchers() {
|
|
for (DataFetcher.Task<?> task : this.minecraft.realmsDataFetcher().getTasks()) {
|
|
task.reset();
|
|
}
|
|
}
|
|
|
|
private DataFetcher.Subscription initDataFetcher(RealmsDataFetcher dataSource) {
|
|
DataFetcher.Subscription result = dataSource.dataFetcher.createSubscription();
|
|
result.subscribe(dataSource.serverListUpdateTask, updatedServers -> {
|
|
this.serverList.updateServersList(updatedServers.serverList());
|
|
this.availableSnapshotServers = updatedServers.availableSnapshotServers();
|
|
this.refreshListAndLayout();
|
|
boolean ownsNonExpiredRealmServer = false;
|
|
for (RealmsServer retrievedServer : this.serverList) {
|
|
if (!this.isSelfOwnedNonExpiredServer(retrievedServer)) continue;
|
|
ownsNonExpiredRealmServer = true;
|
|
}
|
|
if (!regionsPinged && ownsNonExpiredRealmServer) {
|
|
regionsPinged = true;
|
|
this.pingRegions();
|
|
}
|
|
});
|
|
RealmsMainScreen.callRealmsClient(RealmsClient::getNotifications, retrievedNotifications -> {
|
|
this.notifications.clear();
|
|
this.notifications.addAll((Collection<RealmsNotification>)retrievedNotifications);
|
|
for (RealmsNotification notification : retrievedNotifications) {
|
|
RealmsNotification.InfoPopup popup;
|
|
PopupScreen popupScreen;
|
|
if (!(notification instanceof RealmsNotification.InfoPopup) || (popupScreen = (popup = (RealmsNotification.InfoPopup)notification).buildScreen(this, this::dismissNotification)) == null) continue;
|
|
this.minecraft.setScreen(popupScreen);
|
|
this.markNotificationsAsSeen(List.of(notification));
|
|
break;
|
|
}
|
|
if (!this.notifications.isEmpty() && this.activeLayoutState != LayoutState.LOADING) {
|
|
this.refreshListAndLayout();
|
|
}
|
|
});
|
|
result.subscribe(dataSource.pendingInvitesTask, numberOfPendingInvites -> {
|
|
this.pendingInvitesButton.setNotificationCount((int)numberOfPendingInvites);
|
|
this.pendingInvitesButton.setTooltip(numberOfPendingInvites == 0 ? Tooltip.create(NO_PENDING_INVITES) : Tooltip.create(PENDING_INVITES));
|
|
if (numberOfPendingInvites > 0 && this.inviteNarrationLimiter.tryAcquire(1)) {
|
|
this.minecraft.getNarrator().saySystemNow(Component.translatable("mco.configure.world.invite.narration", numberOfPendingInvites));
|
|
}
|
|
});
|
|
result.subscribe(dataSource.trialAvailabilityTask, newStatus -> {
|
|
this.trialsAvailable = newStatus;
|
|
});
|
|
result.subscribe(dataSource.onlinePlayersTask, playerList -> {
|
|
this.onlinePlayersPerRealm = playerList;
|
|
});
|
|
result.subscribe(dataSource.newsTask, news -> {
|
|
dataSource.newsManager.updateUnreadNews((RealmsNews)news);
|
|
this.newsLink = dataSource.newsManager.newsLink();
|
|
this.newsButton.setNotificationCount(dataSource.newsManager.hasUnreadNews() ? Integer.MAX_VALUE : 0);
|
|
});
|
|
return result;
|
|
}
|
|
|
|
private void markNotificationsAsSeen(Collection<RealmsNotification> notifications) {
|
|
ArrayList<UUID> seenNotifications = new ArrayList<UUID>(notifications.size());
|
|
for (RealmsNotification notification : notifications) {
|
|
if (notification.seen() || this.handledSeenNotifications.contains(notification.uuid())) continue;
|
|
seenNotifications.add(notification.uuid());
|
|
}
|
|
if (!seenNotifications.isEmpty()) {
|
|
RealmsMainScreen.callRealmsClient(realmsClient -> {
|
|
realmsClient.notificationsSeen(seenNotifications);
|
|
return null;
|
|
}, ignored -> this.handledSeenNotifications.addAll(seenNotifications));
|
|
}
|
|
}
|
|
|
|
private static <T> void callRealmsClient(RealmsCall<T> supplier, Consumer<T> callback) {
|
|
Minecraft minecraft = Minecraft.getInstance();
|
|
((CompletableFuture)CompletableFuture.supplyAsync(() -> {
|
|
try {
|
|
return supplier.request(RealmsClient.getOrCreate(minecraft));
|
|
}
|
|
catch (RealmsServiceException e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
}).thenAcceptAsync(callback, (Executor)minecraft)).exceptionally(e -> {
|
|
LOGGER.error("Failed to execute call to Realms Service", e);
|
|
return null;
|
|
});
|
|
}
|
|
|
|
private void refreshListAndLayout() {
|
|
this.realmSelectionList.refreshEntries(this);
|
|
this.updateLayout();
|
|
this.updateButtonStates();
|
|
}
|
|
|
|
private void pingRegions() {
|
|
new Thread(() -> {
|
|
List<RegionPingResult> regionPingResultList = Ping.pingAllRegions();
|
|
RealmsClient client = RealmsClient.getOrCreate();
|
|
PingResult pingResult = new PingResult(regionPingResultList, this.getOwnedNonExpiredRealmIds());
|
|
try {
|
|
client.sendPingResults(pingResult);
|
|
}
|
|
catch (Throwable t) {
|
|
LOGGER.warn("Could not send ping result to Realms: ", t);
|
|
}
|
|
}).start();
|
|
}
|
|
|
|
private List<Long> getOwnedNonExpiredRealmIds() {
|
|
ArrayList ids = Lists.newArrayList();
|
|
for (RealmsServer server : this.serverList) {
|
|
if (!this.isSelfOwnedNonExpiredServer(server)) continue;
|
|
ids.add(server.id);
|
|
}
|
|
return ids;
|
|
}
|
|
|
|
private void onRenew(@Nullable RealmsServer server) {
|
|
if (server != null) {
|
|
String extensionUrl = CommonLinks.extendRealms(server.remoteSubscriptionId, this.minecraft.getUser().getProfileId(), server.expiredTrial);
|
|
this.minecraft.setScreen(new ConfirmLinkScreen(result -> {
|
|
if (result) {
|
|
Util.getPlatform().openUri(extensionUrl);
|
|
} else {
|
|
this.minecraft.setScreen(this);
|
|
}
|
|
}, extensionUrl, true));
|
|
}
|
|
}
|
|
|
|
private void configureClicked(@Nullable RealmsServer selectedServer) {
|
|
if (selectedServer != null && this.minecraft.isLocalPlayer(selectedServer.ownerUUID)) {
|
|
this.minecraft.setScreen(new RealmsConfigureWorldScreen(this, selectedServer.id));
|
|
}
|
|
}
|
|
|
|
private void leaveClicked(@Nullable RealmsServer selectedServer) {
|
|
if (selectedServer != null && !this.minecraft.isLocalPlayer(selectedServer.ownerUUID)) {
|
|
MutableComponent popupMessage = Component.translatable("mco.configure.world.leave.question.line1");
|
|
this.minecraft.setScreen(RealmsPopups.infoPopupScreen(this, popupMessage, popup -> this.leaveServer(selectedServer)));
|
|
}
|
|
}
|
|
|
|
private @Nullable RealmsServer getSelectedServer() {
|
|
Object e = this.realmSelectionList.getSelected();
|
|
if (e instanceof ServerEntry) {
|
|
ServerEntry entry = (ServerEntry)e;
|
|
return entry.getServer();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private void leaveServer(final RealmsServer server) {
|
|
new Thread("Realms-leave-server"){
|
|
|
|
@Override
|
|
public void run() {
|
|
try {
|
|
RealmsClient client = RealmsClient.getOrCreate();
|
|
client.uninviteMyselfFrom(server.id);
|
|
RealmsMainScreen.this.minecraft.execute(RealmsMainScreen::refreshServerList);
|
|
}
|
|
catch (RealmsServiceException e) {
|
|
LOGGER.error("Couldn't configure world", (Throwable)e);
|
|
RealmsMainScreen.this.minecraft.execute(() -> RealmsMainScreen.this.minecraft.setScreen(new RealmsGenericErrorScreen(e, (Screen)RealmsMainScreen.this)));
|
|
}
|
|
}
|
|
}.start();
|
|
this.minecraft.setScreen(this);
|
|
}
|
|
|
|
private void dismissNotification(UUID uuid) {
|
|
RealmsMainScreen.callRealmsClient(realmsClient -> {
|
|
realmsClient.notificationsDismiss(List.of(uuid));
|
|
return null;
|
|
}, ignored -> {
|
|
this.notifications.removeIf(notification -> notification.dismissable() && uuid.equals(notification.uuid()));
|
|
this.refreshListAndLayout();
|
|
});
|
|
}
|
|
|
|
public void resetScreen() {
|
|
this.realmSelectionList.setSelected((Entry)null);
|
|
RealmsMainScreen.refreshServerList();
|
|
}
|
|
|
|
@Override
|
|
public Component getNarrationMessage() {
|
|
return switch (this.activeLayoutState.ordinal()) {
|
|
default -> throw new MatchException(null, null);
|
|
case 0 -> CommonComponents.joinForNarration(super.getNarrationMessage(), LOADING_TEXT);
|
|
case 1 -> CommonComponents.joinForNarration(super.getNarrationMessage(), NO_REALMS_TEXT);
|
|
case 2 -> super.getNarrationMessage();
|
|
};
|
|
}
|
|
|
|
@Override
|
|
public void render(GuiGraphics graphics, int xm, int ym, float a) {
|
|
super.render(graphics, xm, ym, a);
|
|
if (RealmsMainScreen.isSnapshot()) {
|
|
graphics.drawString(this.font, "Minecraft " + SharedConstants.getCurrentVersion().name(), 2, this.height - 10, -1);
|
|
}
|
|
if (this.trialsAvailable && this.addRealmButton.active) {
|
|
AddRealmPopupScreen.renderDiamond(graphics, this.addRealmButton);
|
|
}
|
|
switch (RealmsClient.ENVIRONMENT) {
|
|
case STAGE: {
|
|
this.renderEnvironment(graphics, "STAGE!", -256);
|
|
break;
|
|
}
|
|
case LOCAL: {
|
|
this.renderEnvironment(graphics, "LOCAL!", -8388737);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void openTrialAvailablePopup() {
|
|
this.minecraft.setScreen(new AddRealmPopupScreen(this, this.trialsAvailable));
|
|
}
|
|
|
|
public static void play(@Nullable RealmsServer server, Screen cancelScreen) {
|
|
RealmsMainScreen.play(server, cancelScreen, false);
|
|
}
|
|
|
|
public static void play(@Nullable RealmsServer server, Screen cancelScreen, boolean skipCompatibility) {
|
|
if (server != null) {
|
|
if (!RealmsMainScreen.isSnapshot() || skipCompatibility || server.isMinigameActive()) {
|
|
Minecraft.getInstance().setScreen(new RealmsLongRunningMcoTaskScreen(cancelScreen, new GetServerDetailsTask(cancelScreen, server)));
|
|
return;
|
|
}
|
|
switch (server.compatibility) {
|
|
case COMPATIBLE: {
|
|
Minecraft.getInstance().setScreen(new RealmsLongRunningMcoTaskScreen(cancelScreen, new GetServerDetailsTask(cancelScreen, server)));
|
|
break;
|
|
}
|
|
case UNVERIFIABLE: {
|
|
RealmsMainScreen.confirmToPlay(server, cancelScreen, Component.translatable("mco.compatibility.unverifiable.title").withColor(-171), Component.translatable("mco.compatibility.unverifiable.message"), CommonComponents.GUI_CONTINUE);
|
|
break;
|
|
}
|
|
case NEEDS_DOWNGRADE: {
|
|
RealmsMainScreen.confirmToPlay(server, cancelScreen, Component.translatable("selectWorld.backupQuestion.downgrade").withColor(-2142128), Component.translatable("mco.compatibility.downgrade.description", Component.literal(server.activeVersion).withColor(-171), Component.literal(SharedConstants.getCurrentVersion().name()).withColor(-171)), Component.translatable("mco.compatibility.downgrade"));
|
|
break;
|
|
}
|
|
case NEEDS_UPGRADE: {
|
|
RealmsMainScreen.upgradeRealmAndPlay(server, cancelScreen);
|
|
break;
|
|
}
|
|
case INCOMPATIBLE: {
|
|
Minecraft.getInstance().setScreen(new PopupScreen.Builder(cancelScreen, INCOMPATIBLE_POPUP_TITLE).setMessage(Component.translatable("mco.compatibility.incompatible.series.popup.message", Component.literal(server.activeVersion).withColor(-171), Component.literal(SharedConstants.getCurrentVersion().name()).withColor(-171))).addButton(CommonComponents.GUI_BACK, PopupScreen::onClose).build());
|
|
break;
|
|
}
|
|
case RELEASE_TYPE_INCOMPATIBLE: {
|
|
Minecraft.getInstance().setScreen(new PopupScreen.Builder(cancelScreen, INCOMPATIBLE_POPUP_TITLE).setMessage(INCOMPATIBLE_RELEASE_TYPE_POPUP_MESSAGE).addButton(CommonComponents.GUI_BACK, PopupScreen::onClose).build());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void confirmToPlay(RealmsServer server, Screen lastScreen, Component title, Component message, Component confirmButton) {
|
|
Minecraft.getInstance().setScreen(new PopupScreen.Builder(lastScreen, title).setMessage(message).addButton(confirmButton, popupScreen -> {
|
|
Minecraft.getInstance().setScreen(new RealmsLongRunningMcoTaskScreen(lastScreen, new GetServerDetailsTask(lastScreen, server)));
|
|
RealmsMainScreen.refreshServerList();
|
|
}).addButton(CommonComponents.GUI_CANCEL, PopupScreen::onClose).build());
|
|
}
|
|
|
|
private static void upgradeRealmAndPlay(RealmsServer server, Screen cancelScreen) {
|
|
MutableComponent title = Component.translatable("mco.compatibility.upgrade.title").withColor(-171);
|
|
MutableComponent confirmButton = Component.translatable("mco.compatibility.upgrade");
|
|
MutableComponent serverVersion = Component.literal(server.activeVersion).withColor(-171);
|
|
MutableComponent clientVersion = Component.literal(SharedConstants.getCurrentVersion().name()).withColor(-171);
|
|
MutableComponent message = RealmsMainScreen.isSelfOwnedServer(server) ? Component.translatable("mco.compatibility.upgrade.description", serverVersion, clientVersion) : Component.translatable("mco.compatibility.upgrade.friend.description", serverVersion, clientVersion);
|
|
RealmsMainScreen.confirmToPlay(server, cancelScreen, title, message, confirmButton);
|
|
}
|
|
|
|
public static Component getVersionComponent(String version, boolean isCompatible) {
|
|
return RealmsMainScreen.getVersionComponent(version, isCompatible ? -8355712 : -2142128);
|
|
}
|
|
|
|
public static Component getVersionComponent(String version, int color) {
|
|
if (StringUtils.isBlank((CharSequence)version)) {
|
|
return CommonComponents.EMPTY;
|
|
}
|
|
return Component.literal(version).withColor(color);
|
|
}
|
|
|
|
public static Component getGameModeComponent(int gameMode, boolean hardcore) {
|
|
if (hardcore) {
|
|
return Component.translatable("gameMode.hardcore").withColor(-65536);
|
|
}
|
|
return GameType.byId(gameMode).getLongDisplayName();
|
|
}
|
|
|
|
private static boolean isSelfOwnedServer(RealmsServer serverData) {
|
|
return Minecraft.getInstance().isLocalPlayer(serverData.ownerUUID);
|
|
}
|
|
|
|
private boolean isSelfOwnedNonExpiredServer(RealmsServer serverData) {
|
|
return RealmsMainScreen.isSelfOwnedServer(serverData) && !serverData.expired;
|
|
}
|
|
|
|
private void renderEnvironment(GuiGraphics graphics, String text, int color) {
|
|
graphics.pose().pushMatrix();
|
|
graphics.pose().translate((float)(this.width / 2 - 25), 20.0f);
|
|
graphics.pose().rotate(-0.34906584f);
|
|
graphics.pose().scale(1.5f, 1.5f);
|
|
graphics.drawString(this.font, text, 0, 0, color);
|
|
graphics.pose().popMatrix();
|
|
}
|
|
|
|
static {
|
|
snapshotToggle = SNAPSHOT = !SharedConstants.getCurrentVersion().stable();
|
|
}
|
|
|
|
private class RealmSelectionList
|
|
extends ObjectSelectionList<Entry> {
|
|
public RealmSelectionList() {
|
|
super(Minecraft.getInstance(), RealmsMainScreen.this.width, RealmsMainScreen.this.height, 0, 36);
|
|
}
|
|
|
|
@Override
|
|
public void setSelected(@Nullable Entry selected) {
|
|
super.setSelected(selected);
|
|
RealmsMainScreen.this.updateButtonStates();
|
|
}
|
|
|
|
@Override
|
|
public int getRowWidth() {
|
|
return 300;
|
|
}
|
|
|
|
private void refreshEntries(RealmsMainScreen realmsMainScreen) {
|
|
Entry previouslySelected = (Entry)this.getSelected();
|
|
this.clearEntries();
|
|
for (RealmsNotification notification : RealmsMainScreen.this.notifications) {
|
|
if (!(notification instanceof RealmsNotification.VisitUrl)) continue;
|
|
RealmsNotification.VisitUrl visitUrl = (RealmsNotification.VisitUrl)notification;
|
|
this.addEntriesForNotification(visitUrl, realmsMainScreen, previouslySelected);
|
|
RealmsMainScreen.this.markNotificationsAsSeen(List.of(notification));
|
|
break;
|
|
}
|
|
this.refreshServerEntries(previouslySelected);
|
|
}
|
|
|
|
private void addEntriesForNotification(RealmsNotification.VisitUrl visitUrl, RealmsMainScreen realmsMainScreen, @Nullable Entry previouslySelected) {
|
|
NotificationMessageEntry notificationMessageEntry;
|
|
Component message = visitUrl.getMessage();
|
|
int messageHeight = RealmsMainScreen.this.font.wordWrapHeight(message, NotificationMessageEntry.textWidth(this.getRowWidth()));
|
|
NotificationMessageEntry entry = new NotificationMessageEntry(realmsMainScreen, messageHeight, message, visitUrl);
|
|
this.addEntry(entry, 38 + messageHeight);
|
|
if (previouslySelected instanceof NotificationMessageEntry && (notificationMessageEntry = (NotificationMessageEntry)previouslySelected).getText().equals(message)) {
|
|
this.setSelected(entry);
|
|
}
|
|
}
|
|
|
|
private void refreshServerEntries(@Nullable Entry previouslySelected) {
|
|
for (RealmsServer eligibleForSnapshotServer : RealmsMainScreen.this.availableSnapshotServers) {
|
|
this.addEntry(new AvailableSnapshotEntry(eligibleForSnapshotServer));
|
|
}
|
|
for (RealmsServer server : RealmsMainScreen.this.serverList) {
|
|
Entry entry;
|
|
if (RealmsMainScreen.isSnapshot() && !server.isSnapshotRealm()) {
|
|
if (server.state == RealmsServer.State.UNINITIALIZED) continue;
|
|
entry = new ParentEntry(RealmsMainScreen.this, server);
|
|
} else {
|
|
entry = new ServerEntry(server);
|
|
}
|
|
this.addEntry(entry);
|
|
if (!(previouslySelected instanceof ServerEntry)) continue;
|
|
ServerEntry serverEntry = (ServerEntry)previouslySelected;
|
|
if (serverEntry.serverData.id != server.id) continue;
|
|
this.setSelected(entry);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static class NotificationButton
|
|
extends SpriteIconButton.CenteredIcon {
|
|
private static final Identifier[] NOTIFICATION_ICONS = new Identifier[]{Identifier.withDefaultNamespace("notification/1"), Identifier.withDefaultNamespace("notification/2"), Identifier.withDefaultNamespace("notification/3"), Identifier.withDefaultNamespace("notification/4"), Identifier.withDefaultNamespace("notification/5"), Identifier.withDefaultNamespace("notification/more")};
|
|
private static final int UNKNOWN_COUNT = Integer.MAX_VALUE;
|
|
private static final int SIZE = 20;
|
|
private static final int SPRITE_SIZE = 14;
|
|
private int notificationCount;
|
|
|
|
public NotificationButton(Component title, Identifier texture, Button.OnPress onPress, @Nullable Component tooltip) {
|
|
super(20, 20, title, 14, 14, new WidgetSprites(texture), onPress, tooltip, null);
|
|
}
|
|
|
|
private int notificationCount() {
|
|
return this.notificationCount;
|
|
}
|
|
|
|
public void setNotificationCount(int notificationCount) {
|
|
this.notificationCount = notificationCount;
|
|
}
|
|
|
|
@Override
|
|
public void renderContents(GuiGraphics graphics, int mouseX, int mouseY, float a) {
|
|
super.renderContents(graphics, mouseX, mouseY, a);
|
|
if (this.active && this.notificationCount != 0) {
|
|
this.drawNotificationCounter(graphics);
|
|
}
|
|
}
|
|
|
|
private void drawNotificationCounter(GuiGraphics graphics) {
|
|
graphics.blitSprite(RenderPipelines.GUI_TEXTURED, NOTIFICATION_ICONS[Math.min(this.notificationCount, 6) - 1], this.getX() + this.getWidth() - 5, this.getY() - 3, 8, 8);
|
|
}
|
|
}
|
|
|
|
private static enum LayoutState {
|
|
LOADING,
|
|
NO_REALMS,
|
|
LIST;
|
|
|
|
}
|
|
|
|
private static interface RealmsCall<T> {
|
|
public T request(RealmsClient var1) throws RealmsServiceException;
|
|
}
|
|
|
|
private class ServerEntry
|
|
extends Entry {
|
|
private static final Component ONLINE_PLAYERS_TOOLTIP_HEADER = Component.translatable("mco.onlinePlayers");
|
|
private static final int PLAYERS_ONLINE_SPRITE_SIZE = 9;
|
|
private static final int PLAYERS_ONLINE_SPRITE_SEPARATION = 3;
|
|
private static final int SKIN_HEAD_LARGE_WIDTH = 36;
|
|
private final RealmsServer serverData;
|
|
private final WidgetTooltipHolder tooltip;
|
|
|
|
public ServerEntry(RealmsServer serverData) {
|
|
this.tooltip = new WidgetTooltipHolder();
|
|
this.serverData = serverData;
|
|
boolean selfOwnedServer = RealmsMainScreen.isSelfOwnedServer(serverData);
|
|
if (RealmsMainScreen.isSnapshot() && selfOwnedServer && serverData.isSnapshotRealm()) {
|
|
this.tooltip.set(Tooltip.create(Component.translatable("mco.snapshot.paired", serverData.parentWorldName)));
|
|
} else if (!selfOwnedServer && serverData.needsDowngrade()) {
|
|
this.tooltip.set(Tooltip.create(Component.translatable("mco.snapshot.friendsRealm.downgrade", serverData.activeVersion)));
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void renderContent(GuiGraphics graphics, int mouseX, int mouseY, boolean hovered, float a) {
|
|
if (this.serverData.state == RealmsServer.State.UNINITIALIZED) {
|
|
graphics.blitSprite(RenderPipelines.GUI_TEXTURED, NEW_REALM_SPRITE, this.getContentX() - 5, this.getContentYMiddle() - 10, 40, 20);
|
|
int textYPos = this.getContentYMiddle() - ((RealmsMainScreen)RealmsMainScreen.this).font.lineHeight / 2;
|
|
graphics.drawString(RealmsMainScreen.this.font, SERVER_UNITIALIZED_TEXT, this.getContentX() + 40 - 2, textYPos, -8388737);
|
|
return;
|
|
}
|
|
RealmsUtil.renderPlayerFace(graphics, this.getContentX(), this.getContentY(), 32, this.serverData.ownerUUID);
|
|
this.renderFirstLine(graphics, this.getContentY(), this.getContentX(), this.getContentWidth(), -1, this.serverData);
|
|
this.renderSecondLine(graphics, this.getContentY(), this.getContentX(), this.getContentWidth(), this.serverData);
|
|
this.renderThirdLine(graphics, this.getContentY(), this.getContentX(), this.serverData);
|
|
this.renderStatusLights(this.serverData, graphics, this.getContentRight(), this.getContentY(), mouseX, mouseY);
|
|
boolean hasRenderedTooltip = this.renderOnlinePlayers(graphics, this.getContentY(), this.getContentX(), this.getContentWidth(), this.getContentHeight(), mouseX, mouseY, a);
|
|
if (!hasRenderedTooltip) {
|
|
this.tooltip.refreshTooltipForNextRenderPass(graphics, mouseX, mouseY, hovered, this.isFocused(), new ScreenRectangle(this.getContentX(), this.getContentY(), this.getContentWidth(), this.getContentHeight()));
|
|
}
|
|
}
|
|
|
|
private boolean renderOnlinePlayers(GuiGraphics graphics, int rowTop, int rowLeft, int rowWidth, int rowHeight, int mouseX, int mouseY, float a) {
|
|
List<ResolvableProfile> profileResults = RealmsMainScreen.this.onlinePlayersPerRealm.getProfileResultsFor(this.serverData.id);
|
|
int playerCount = profileResults.size();
|
|
if (playerCount > 0) {
|
|
int playersOnlineXEnd = rowLeft + rowWidth - 21;
|
|
int playersOnlineY = rowTop + rowHeight - 9 - 2;
|
|
int playerOnlineWidth = 9 * playerCount + 3 * (playerCount - 1);
|
|
int playersOnlineXStart = playersOnlineXEnd - playerOnlineWidth;
|
|
ArrayList<PlayerSkinRenderCache.RenderInfo> tooltipEntries = mouseX >= playersOnlineXStart && mouseX <= playersOnlineXEnd && mouseY >= playersOnlineY && mouseY <= playersOnlineY + 9 ? new ArrayList<PlayerSkinRenderCache.RenderInfo>(playerCount) : null;
|
|
PlayerSkinRenderCache skinCache = RealmsMainScreen.this.minecraft.playerSkinRenderCache();
|
|
for (int i = 0; i < profileResults.size(); ++i) {
|
|
ResolvableProfile profile = profileResults.get(i);
|
|
PlayerSkinRenderCache.RenderInfo profileRenderInfo = skinCache.getOrDefault(profile);
|
|
int xPos = playersOnlineXStart + 12 * i;
|
|
PlayerFaceRenderer.draw(graphics, profileRenderInfo.playerSkin(), xPos, playersOnlineY, 9);
|
|
if (tooltipEntries == null) continue;
|
|
tooltipEntries.add(profileRenderInfo);
|
|
}
|
|
if (tooltipEntries != null) {
|
|
graphics.setTooltipForNextFrame(RealmsMainScreen.this.font, List.of(ONLINE_PLAYERS_TOOLTIP_HEADER), Optional.of(new ClientActivePlayersTooltip.ActivePlayersTooltip(tooltipEntries)), mouseX, mouseY);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private void playRealm() {
|
|
RealmsMainScreen.this.minecraft.getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1.0f));
|
|
RealmsMainScreen.play(this.serverData, RealmsMainScreen.this);
|
|
}
|
|
|
|
private void createUnitializedRealm() {
|
|
RealmsMainScreen.this.minecraft.getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1.0f));
|
|
RealmsCreateRealmScreen createScreen = new RealmsCreateRealmScreen(RealmsMainScreen.this, this.serverData, this.serverData.isSnapshotRealm());
|
|
RealmsMainScreen.this.minecraft.setScreen(createScreen);
|
|
}
|
|
|
|
@Override
|
|
public boolean mouseClicked(MouseButtonEvent event, boolean doubleClick) {
|
|
if (this.serverData.state == RealmsServer.State.UNINITIALIZED) {
|
|
this.createUnitializedRealm();
|
|
} else if (this.serverData.shouldPlayButtonBeActive() && doubleClick && this.isFocused()) {
|
|
this.playRealm();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean keyPressed(KeyEvent event) {
|
|
if (event.isSelection()) {
|
|
if (this.serverData.state == RealmsServer.State.UNINITIALIZED) {
|
|
this.createUnitializedRealm();
|
|
return true;
|
|
}
|
|
if (this.serverData.shouldPlayButtonBeActive()) {
|
|
this.playRealm();
|
|
return true;
|
|
}
|
|
}
|
|
return super.keyPressed(event);
|
|
}
|
|
|
|
@Override
|
|
public Component getNarration() {
|
|
if (this.serverData.state == RealmsServer.State.UNINITIALIZED) {
|
|
return UNITIALIZED_WORLD_NARRATION;
|
|
}
|
|
return Component.translatable("narrator.select", Objects.requireNonNullElse(this.serverData.name, "unknown server"));
|
|
}
|
|
|
|
public RealmsServer getServer() {
|
|
return this.serverData;
|
|
}
|
|
}
|
|
|
|
private abstract class Entry
|
|
extends ObjectSelectionList.Entry<Entry> {
|
|
protected static final int STATUS_LIGHT_WIDTH = 10;
|
|
private static final int STATUS_LIGHT_HEIGHT = 28;
|
|
protected static final int PADDING_X = 7;
|
|
protected static final int PADDING_Y = 2;
|
|
|
|
private Entry() {
|
|
}
|
|
|
|
protected void renderStatusLights(RealmsServer serverData, GuiGraphics graphics, int rowRight, int rowTop, int mouseX, int mouseY) {
|
|
int x = rowRight - 10 - 7;
|
|
int y = rowTop + 2;
|
|
if (serverData.expired) {
|
|
this.drawRealmStatus(graphics, x, y, mouseX, mouseY, EXPIRED_SPRITE, () -> SERVER_EXPIRED_TOOLTIP);
|
|
} else if (serverData.state == RealmsServer.State.CLOSED) {
|
|
this.drawRealmStatus(graphics, x, y, mouseX, mouseY, CLOSED_SPRITE, () -> SERVER_CLOSED_TOOLTIP);
|
|
} else if (RealmsMainScreen.isSelfOwnedServer(serverData) && serverData.daysLeft < 7) {
|
|
this.drawRealmStatus(graphics, x, y, mouseX, mouseY, EXPIRES_SOON_SPRITE, () -> {
|
|
if (serverData.daysLeft <= 0) {
|
|
return SERVER_EXPIRES_SOON_TOOLTIP;
|
|
}
|
|
if (serverData.daysLeft == 1) {
|
|
return SERVER_EXPIRES_IN_DAY_TOOLTIP;
|
|
}
|
|
return Component.translatable("mco.selectServer.expires.days", serverData.daysLeft);
|
|
});
|
|
} else if (serverData.state == RealmsServer.State.OPEN) {
|
|
this.drawRealmStatus(graphics, x, y, mouseX, mouseY, OPEN_SPRITE, () -> SERVER_OPEN_TOOLTIP);
|
|
}
|
|
}
|
|
|
|
private void drawRealmStatus(GuiGraphics graphics, int x, int y, int xm, int ym, Identifier sprite, Supplier<Component> tooltip) {
|
|
graphics.blitSprite(RenderPipelines.GUI_TEXTURED, sprite, x, y, 10, 28);
|
|
if (RealmsMainScreen.this.realmSelectionList.isMouseOver(xm, ym) && xm >= x && xm <= x + 10 && ym >= y && ym <= y + 28) {
|
|
graphics.setTooltipForNextFrame(tooltip.get(), xm, ym);
|
|
}
|
|
}
|
|
|
|
protected void renderFirstLine(GuiGraphics graphics, int rowTop, int rowLeft, int rowWidth, int serverNameColor, RealmsServer serverData) {
|
|
int textX = this.textX(rowLeft);
|
|
int firstLineY = this.firstLineY(rowTop);
|
|
Component versionComponent = RealmsMainScreen.getVersionComponent(serverData.activeVersion, serverData.isCompatible());
|
|
int versionTextX = this.versionTextX(rowLeft, rowWidth, versionComponent);
|
|
this.renderClampedString(graphics, serverData.getName(), textX, firstLineY, versionTextX, serverNameColor);
|
|
if (versionComponent != CommonComponents.EMPTY && !serverData.isMinigameActive()) {
|
|
graphics.drawString(RealmsMainScreen.this.font, versionComponent, versionTextX, firstLineY, -8355712);
|
|
}
|
|
}
|
|
|
|
protected void renderSecondLine(GuiGraphics graphics, int rowTop, int rowLeft, int rowWidth, RealmsServer serverData) {
|
|
int textX = this.textX(rowLeft);
|
|
int firstLineY = this.firstLineY(rowTop);
|
|
int secondLineY = this.secondLineY(firstLineY);
|
|
String minigameName = serverData.getMinigameName();
|
|
boolean minigameActive = serverData.isMinigameActive();
|
|
if (minigameActive && minigameName != null) {
|
|
MutableComponent minigameNameComponent = Component.literal(minigameName).withStyle(ChatFormatting.GRAY);
|
|
graphics.drawString(RealmsMainScreen.this.font, Component.translatable("mco.selectServer.minigameName", minigameNameComponent).withColor(-171), textX, secondLineY, -1);
|
|
} else {
|
|
int maxX = this.renderGameMode(serverData, graphics, rowLeft, rowWidth, firstLineY);
|
|
this.renderClampedString(graphics, serverData.getDescription(), textX, this.secondLineY(firstLineY), maxX, -8355712);
|
|
}
|
|
}
|
|
|
|
protected void renderThirdLine(GuiGraphics graphics, int rowTop, int rowLeft, RealmsServer server) {
|
|
int textX = this.textX(rowLeft);
|
|
int firstLineY = this.firstLineY(rowTop);
|
|
int thirdLineY = this.thirdLineY(firstLineY);
|
|
if (!RealmsMainScreen.isSelfOwnedServer(server)) {
|
|
graphics.drawString(RealmsMainScreen.this.font, server.owner, textX, this.thirdLineY(firstLineY), -8355712);
|
|
} else if (server.expired) {
|
|
Component expirationText = server.expiredTrial ? TRIAL_EXPIRED_TEXT : SUBSCRIPTION_EXPIRED_TEXT;
|
|
graphics.drawString(RealmsMainScreen.this.font, expirationText, textX, thirdLineY, -2142128);
|
|
}
|
|
}
|
|
|
|
protected void renderClampedString(GuiGraphics graphics, @Nullable String string, int x, int y, int maxX, int color) {
|
|
if (string == null) {
|
|
return;
|
|
}
|
|
int availableSpace = maxX - x;
|
|
if (RealmsMainScreen.this.font.width(string) > availableSpace) {
|
|
String clampedName = RealmsMainScreen.this.font.plainSubstrByWidth(string, availableSpace - RealmsMainScreen.this.font.width("... "));
|
|
graphics.drawString(RealmsMainScreen.this.font, clampedName + "...", x, y, color);
|
|
} else {
|
|
graphics.drawString(RealmsMainScreen.this.font, string, x, y, color);
|
|
}
|
|
}
|
|
|
|
protected int versionTextX(int rowLeft, int rowWidth, Component versionComponent) {
|
|
return rowLeft + rowWidth - RealmsMainScreen.this.font.width(versionComponent) - 20;
|
|
}
|
|
|
|
protected int gameModeTextX(int rowLeft, int rowWidth, Component versionComponent) {
|
|
return rowLeft + rowWidth - RealmsMainScreen.this.font.width(versionComponent) - 20;
|
|
}
|
|
|
|
protected int renderGameMode(RealmsServer server, GuiGraphics graphics, int rowLeft, int rowWidth, int firstLineY) {
|
|
boolean hardcore = server.isHardcore;
|
|
int gameMode = server.gameMode;
|
|
int x = rowLeft;
|
|
if (GameType.isValidId(gameMode)) {
|
|
Component gameModeComponent = RealmsMainScreen.getGameModeComponent(gameMode, hardcore);
|
|
x = this.gameModeTextX(rowLeft, rowWidth, gameModeComponent);
|
|
graphics.drawString(RealmsMainScreen.this.font, gameModeComponent, x, this.secondLineY(firstLineY), -8355712);
|
|
}
|
|
if (hardcore) {
|
|
graphics.blitSprite(RenderPipelines.GUI_TEXTURED, HARDCORE_MODE_SPRITE, x -= 10, this.secondLineY(firstLineY), 8, 8);
|
|
}
|
|
return x;
|
|
}
|
|
|
|
protected int firstLineY(int rowTop) {
|
|
return rowTop + 1;
|
|
}
|
|
|
|
protected int lineHeight() {
|
|
return 2 + ((RealmsMainScreen)RealmsMainScreen.this).font.lineHeight;
|
|
}
|
|
|
|
protected int textX(int rowLeft) {
|
|
return rowLeft + 36 + 2;
|
|
}
|
|
|
|
protected int secondLineY(int firstLineY) {
|
|
return firstLineY + this.lineHeight();
|
|
}
|
|
|
|
protected int thirdLineY(int firstLineY) {
|
|
return firstLineY + this.lineHeight() * 2;
|
|
}
|
|
}
|
|
|
|
private static class CrossButton
|
|
extends ImageButton {
|
|
private static final WidgetSprites SPRITES = new WidgetSprites(Identifier.withDefaultNamespace("widget/cross_button"), Identifier.withDefaultNamespace("widget/cross_button_highlighted"));
|
|
|
|
protected CrossButton(Button.OnPress onPress, Component tooltip) {
|
|
super(0, 0, 14, 14, SPRITES, onPress);
|
|
this.setTooltip(Tooltip.create(tooltip));
|
|
}
|
|
}
|
|
|
|
private class ParentEntry
|
|
extends Entry {
|
|
private final RealmsServer server;
|
|
private final WidgetTooltipHolder tooltip = new WidgetTooltipHolder();
|
|
|
|
public ParentEntry(RealmsMainScreen realmsMainScreen, RealmsServer server) {
|
|
this.server = server;
|
|
if (!server.expired) {
|
|
this.tooltip.set(Tooltip.create(Component.translatable("mco.snapshot.parent.tooltip")));
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void renderContent(GuiGraphics graphics, int mouseX, int mouseY, boolean hovered, float a) {
|
|
this.renderStatusLights(this.server, graphics, this.getContentRight(), this.getContentY(), mouseX, mouseY);
|
|
RealmsUtil.renderPlayerFace(graphics, this.getContentX(), this.getContentY(), 32, this.server.ownerUUID);
|
|
this.renderFirstLine(graphics, this.getContentY(), this.getContentX(), this.getContentWidth(), -8355712, this.server);
|
|
this.renderSecondLine(graphics, this.getContentY(), this.getContentX(), this.getContentWidth(), this.server);
|
|
this.renderThirdLine(graphics, this.getContentY(), this.getContentX(), this.server);
|
|
this.tooltip.refreshTooltipForNextRenderPass(graphics, mouseX, mouseY, hovered, this.isFocused(), new ScreenRectangle(this.getContentX(), this.getContentY(), this.getContentWidth(), this.getContentHeight()));
|
|
}
|
|
|
|
@Override
|
|
public Component getNarration() {
|
|
return Component.literal(Objects.requireNonNullElse(this.server.name, "unknown server"));
|
|
}
|
|
}
|
|
|
|
private class AvailableSnapshotEntry
|
|
extends Entry {
|
|
private static final Component START_SNAPSHOT_REALM = Component.translatable("mco.snapshot.start");
|
|
private static final int TEXT_PADDING = 5;
|
|
private final WidgetTooltipHolder tooltip = new WidgetTooltipHolder();
|
|
private final RealmsServer parent;
|
|
|
|
public AvailableSnapshotEntry(RealmsServer parent) {
|
|
this.parent = parent;
|
|
this.tooltip.set(Tooltip.create(Component.translatable("mco.snapshot.tooltip")));
|
|
}
|
|
|
|
@Override
|
|
public void renderContent(GuiGraphics graphics, int mouseX, int mouseY, boolean hovered, float a) {
|
|
graphics.blitSprite(RenderPipelines.GUI_TEXTURED, NEW_REALM_SPRITE, this.getContentX() - 5, this.getContentYMiddle() - 10, 40, 20);
|
|
int textYPos = this.getContentYMiddle() - ((RealmsMainScreen)RealmsMainScreen.this).font.lineHeight / 2;
|
|
graphics.drawString(RealmsMainScreen.this.font, START_SNAPSHOT_REALM, this.getContentX() + 40 - 2, textYPos - 5, -8388737);
|
|
graphics.drawString(RealmsMainScreen.this.font, Component.translatable("mco.snapshot.description", Objects.requireNonNullElse(this.parent.name, "unknown server")), this.getContentX() + 40 - 2, textYPos + 5, -8355712);
|
|
this.tooltip.refreshTooltipForNextRenderPass(graphics, mouseX, mouseY, hovered, this.isFocused(), new ScreenRectangle(this.getContentX(), this.getContentY(), this.getContentWidth(), this.getContentHeight()));
|
|
}
|
|
|
|
@Override
|
|
public boolean mouseClicked(MouseButtonEvent event, boolean doubleClick) {
|
|
this.addSnapshotRealm();
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean keyPressed(KeyEvent event) {
|
|
if (event.isSelection()) {
|
|
this.addSnapshotRealm();
|
|
return false;
|
|
}
|
|
return super.keyPressed(event);
|
|
}
|
|
|
|
private void addSnapshotRealm() {
|
|
RealmsMainScreen.this.minecraft.getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1.0f));
|
|
RealmsMainScreen.this.minecraft.setScreen(new PopupScreen.Builder(RealmsMainScreen.this, Component.translatable("mco.snapshot.createSnapshotPopup.title")).setMessage(Component.translatable("mco.snapshot.createSnapshotPopup.text")).addButton(Component.translatable("mco.selectServer.create"), popup -> RealmsMainScreen.this.minecraft.setScreen(new RealmsCreateRealmScreen(RealmsMainScreen.this, this.parent, true))).addButton(CommonComponents.GUI_CANCEL, PopupScreen::onClose).build());
|
|
}
|
|
|
|
@Override
|
|
public Component getNarration() {
|
|
return Component.translatable("gui.narrate.button", CommonComponents.joinForNarration(START_SNAPSHOT_REALM, Component.translatable("mco.snapshot.description", Objects.requireNonNullElse(this.parent.name, "unknown server"))));
|
|
}
|
|
}
|
|
|
|
private class NotificationMessageEntry
|
|
extends Entry {
|
|
private static final int SIDE_MARGINS = 40;
|
|
public static final int PADDING = 7;
|
|
public static final int HEIGHT_WITHOUT_TEXT = 38;
|
|
private final Component text;
|
|
private final List<AbstractWidget> children = new ArrayList<AbstractWidget>();
|
|
private final @Nullable CrossButton dismissButton;
|
|
private final MultiLineTextWidget textWidget;
|
|
private final GridLayout gridLayout;
|
|
private final FrameLayout textFrame;
|
|
private final Button button;
|
|
private int lastEntryWidth = -1;
|
|
|
|
public NotificationMessageEntry(RealmsMainScreen realmsMainScreen2, int messageHeight, Component text, RealmsNotification.VisitUrl notification) {
|
|
this.text = text;
|
|
this.gridLayout = new GridLayout();
|
|
this.gridLayout.addChild(ImageWidget.sprite(20, 20, INFO_SPRITE), 0, 0, this.gridLayout.newCellSettings().padding(7, 7, 0, 0));
|
|
this.gridLayout.addChild(SpacerElement.width(40), 0, 0);
|
|
this.textFrame = this.gridLayout.addChild(new FrameLayout(0, messageHeight), 0, 1, this.gridLayout.newCellSettings().paddingTop(7));
|
|
this.textWidget = this.textFrame.addChild(new MultiLineTextWidget(text, RealmsMainScreen.this.font).setCentered(true), this.textFrame.newChildLayoutSettings().alignHorizontallyCenter().alignVerticallyTop());
|
|
this.gridLayout.addChild(SpacerElement.width(40), 0, 2);
|
|
this.dismissButton = notification.dismissable() ? this.gridLayout.addChild(new CrossButton(b -> RealmsMainScreen.this.dismissNotification(notification.uuid()), Component.translatable("mco.notification.dismiss")), 0, 2, this.gridLayout.newCellSettings().alignHorizontallyRight().padding(0, 7, 7, 0)) : null;
|
|
this.button = this.gridLayout.addChild(notification.buildOpenLinkButton(realmsMainScreen2), 1, 1, this.gridLayout.newCellSettings().alignHorizontallyCenter().padding(4));
|
|
this.gridLayout.visitWidgets(this.children::add);
|
|
}
|
|
|
|
@Override
|
|
public boolean keyPressed(KeyEvent event) {
|
|
if (this.button.keyPressed(event)) {
|
|
return true;
|
|
}
|
|
if (this.dismissButton != null && this.dismissButton.keyPressed(event)) {
|
|
return true;
|
|
}
|
|
return super.keyPressed(event);
|
|
}
|
|
|
|
private void updateEntryWidth() {
|
|
int entryWidth = this.getWidth();
|
|
if (this.lastEntryWidth != entryWidth) {
|
|
this.refreshLayout(entryWidth);
|
|
this.lastEntryWidth = entryWidth;
|
|
}
|
|
}
|
|
|
|
private void refreshLayout(int entryWidth) {
|
|
int width = NotificationMessageEntry.textWidth(entryWidth);
|
|
this.textFrame.setMinWidth(width);
|
|
this.textWidget.setMaxWidth(width);
|
|
this.gridLayout.arrangeElements();
|
|
}
|
|
|
|
public static int textWidth(int rowWidth) {
|
|
return rowWidth - 80;
|
|
}
|
|
|
|
@Override
|
|
public void renderContent(GuiGraphics graphics, int mouseX, int mouseY, boolean hovered, float a) {
|
|
this.gridLayout.setPosition(this.getContentX(), this.getContentY());
|
|
this.updateEntryWidth();
|
|
this.children.forEach(child -> {
|
|
if (child == this.button && this.isFocused()) {
|
|
this.button.render(graphics, this.button.getX(), this.button.getY(), a);
|
|
} else {
|
|
child.render(graphics, mouseX, mouseY, a);
|
|
}
|
|
});
|
|
}
|
|
|
|
@Override
|
|
public boolean mouseClicked(MouseButtonEvent event, boolean doubleClick) {
|
|
if (this.dismissButton != null && this.dismissButton.mouseClicked(event, doubleClick)) {
|
|
return true;
|
|
}
|
|
if (this.button.mouseClicked(event, doubleClick)) {
|
|
return true;
|
|
}
|
|
return super.mouseClicked(event, doubleClick);
|
|
}
|
|
|
|
public Component getText() {
|
|
return this.text;
|
|
}
|
|
|
|
@Override
|
|
public Component getNarration() {
|
|
return this.getText();
|
|
}
|
|
}
|
|
}
|
|
|