/* * 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 availability = RealmsAvailability.get(); private @Nullable DataFetcher.Subscription dataSubscription; private final Set handledSeenNotifications = new HashSet(); 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 availableSnapshotServers = List.of(); private RealmsServerPlayerLists onlinePlayersPerRealm = new RealmsServerPlayerLists(Map.of()); private volatile boolean trialsAvailable; private volatile @Nullable String newsLink; private final List notifications = new ArrayList(); 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)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 notifications) { ArrayList seenNotifications = new ArrayList(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 void callRealmsClient(RealmsCall supplier, Consumer 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 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 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 { 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 { 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 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 tooltipEntries = mouseX >= playersOnlineXStart && mouseX <= playersOnlineXEnd && mouseY >= playersOnlineY && mouseY <= playersOnlineY + 9 ? new ArrayList(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 { 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 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 children = new ArrayList(); 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(); } } }