RealmsMainScreen.java

com.mojang.realmsclient.RealmsMainScreen

信息

  • 全限定名:com.mojang.realmsclient.RealmsMainScreen
  • 类型:public class
  • 包:com.mojang.realmsclient
  • 源码路径:src/main/java/com/mojang/realmsclient/RealmsMainScreen.java
  • 起始行号:L90
  • 继承:RealmsScreen
  • 职责:

    TODO

字段/常量

  • INFO_SPRITE

    • 类型: Identifier
    • 修饰符: private static final
    • 源码定位: L91
    • 说明:

      TODO

  • NEW_REALM_SPRITE

    • 类型: Identifier
    • 修饰符: private static final
    • 源码定位: L92
    • 说明:

      TODO

  • EXPIRED_SPRITE

    • 类型: Identifier
    • 修饰符: private static final
    • 源码定位: L93
    • 说明:

      TODO

  • EXPIRES_SOON_SPRITE

    • 类型: Identifier
    • 修饰符: private static final
    • 源码定位: L94
    • 说明:

      TODO

  • OPEN_SPRITE

    • 类型: Identifier
    • 修饰符: private static final
    • 源码定位: L95
    • 说明:

      TODO

  • CLOSED_SPRITE

    • 类型: Identifier
    • 修饰符: private static final
    • 源码定位: L96
    • 说明:

      TODO

  • INVITE_SPRITE

    • 类型: Identifier
    • 修饰符: private static final
    • 源码定位: L97
    • 说明:

      TODO

  • NEWS_SPRITE

    • 类型: Identifier
    • 修饰符: private static final
    • 源码定位: L98
    • 说明:

      TODO

  • HARDCORE_MODE_SPRITE

    • 类型: Identifier
    • 修饰符: public static final
    • 源码定位: L99
    • 说明:

      TODO

  • LOGGER

    • 类型: Logger
    • 修饰符: private static final
    • 源码定位: L100
    • 说明:

      TODO

  • NO_REALMS_LOCATION

    • 类型: Identifier
    • 修饰符: private static final
    • 源码定位: L101
    • 说明:

      TODO

  • TITLE

    • 类型: Component
    • 修饰符: private static final
    • 源码定位: L102
    • 说明:

      TODO

  • LOADING_TEXT

    • 类型: Component
    • 修饰符: private static final
    • 源码定位: L103
    • 说明:

      TODO

  • SERVER_UNITIALIZED_TEXT

    • 类型: Component
    • 修饰符: private static final
    • 源码定位: L104
    • 说明:

      TODO

  • SUBSCRIPTION_EXPIRED_TEXT

    • 类型: Component
    • 修饰符: private static final
    • 源码定位: L105
    • 说明:

      TODO

  • SUBSCRIPTION_RENEW_TEXT

    • 类型: Component
    • 修饰符: private static final
    • 源码定位: L106
    • 说明:

      TODO

  • TRIAL_EXPIRED_TEXT

    • 类型: Component
    • 修饰符: private static final
    • 源码定位: L107
    • 说明:

      TODO

  • PLAY_TEXT

    • 类型: Component
    • 修饰符: private static final
    • 源码定位: L108
    • 说明:

      TODO

  • LEAVE_SERVER_TEXT

    • 类型: Component
    • 修饰符: private static final
    • 源码定位: L109
    • 说明:

      TODO

  • CONFIGURE_SERVER_TEXT

    • 类型: Component
    • 修饰符: private static final
    • 源码定位: L110
    • 说明:

      TODO

  • SERVER_EXPIRED_TOOLTIP

    • 类型: Component
    • 修饰符: private static final
    • 源码定位: L111
    • 说明:

      TODO

  • SERVER_EXPIRES_SOON_TOOLTIP

    • 类型: Component
    • 修饰符: private static final
    • 源码定位: L112
    • 说明:

      TODO

  • SERVER_EXPIRES_IN_DAY_TOOLTIP

    • 类型: Component
    • 修饰符: private static final
    • 源码定位: L113
    • 说明:

      TODO

  • SERVER_OPEN_TOOLTIP

    • 类型: Component
    • 修饰符: private static final
    • 源码定位: L114
    • 说明:

      TODO

  • SERVER_CLOSED_TOOLTIP

    • 类型: Component
    • 修饰符: private static final
    • 源码定位: L115
    • 说明:

      TODO

  • UNITIALIZED_WORLD_NARRATION

    • 类型: Component
    • 修饰符: private static final
    • 源码定位: L116
    • 说明:

      TODO

  • NO_REALMS_TEXT

    • 类型: Component
    • 修饰符: private static final
    • 源码定位: L117
    • 说明:

      TODO

  • NO_PENDING_INVITES

    • 类型: Component
    • 修饰符: private static final
    • 源码定位: L118
    • 说明:

      TODO

  • PENDING_INVITES

    • 类型: Component
    • 修饰符: private static final
    • 源码定位: L119
    • 说明:

      TODO

  • INCOMPATIBLE_POPUP_TITLE

    • 类型: Component
    • 修饰符: private static final
    • 源码定位: L120
    • 说明:

      TODO

  • INCOMPATIBLE_RELEASE_TYPE_POPUP_MESSAGE

    • 类型: Component
    • 修饰符: private static final
    • 源码定位: L121
    • 说明:

      TODO

  • BUTTON_WIDTH

    • 类型: int
    • 修饰符: private static final
    • 源码定位: L122
    • 说明:

      TODO

  • BUTTON_COLUMNS

    • 类型: int
    • 修饰符: private static final
    • 源码定位: L123
    • 说明:

      TODO

  • BUTTON_SPACING

    • 类型: int
    • 修饰符: private static final
    • 源码定位: L124
    • 说明:

      TODO

  • CONTENT_WIDTH

    • 类型: int
    • 修饰符: private static final
    • 源码定位: L125
    • 说明:

      TODO

  • LOGO_PADDING

    • 类型: int
    • 修饰符: private static final
    • 源码定位: L126
    • 说明:

      TODO

  • HEADER_HEIGHT

    • 类型: int
    • 修饰符: private static final
    • 源码定位: L127
    • 说明:

      TODO

  • FOOTER_PADDING

    • 类型: int
    • 修饰符: private static final
    • 源码定位: L128
    • 说明:

      TODO

  • NEW_REALM_SPRITE_WIDTH

    • 类型: int
    • 修饰符: private static final
    • 源码定位: L129
    • 说明:

      TODO

  • NEW_REALM_SPRITE_HEIGHT

    • 类型: int
    • 修饰符: private static final
    • 源码定位: L130
    • 说明:

      TODO

  • SNAPSHOT

    • 类型: boolean
    • 修饰符: private static final
    • 源码定位: L131
    • 说明:

      TODO

  • snapshotToggle

    • 类型: boolean
    • 修饰符: private static
    • 源码定位: L132
    • 说明:

      TODO

  • availability

    • 类型: CompletableFuture<RealmsAvailability.Result>
    • 修饰符: private final
    • 源码定位: L133
    • 说明:

      TODO

  • dataSubscription

    • 类型: DataFetcher.Subscription
    • 修饰符: private
    • 源码定位: L134
    • 说明:

      TODO

  • handledSeenNotifications

    • 类型: Set<UUID>
    • 修饰符: private final
    • 源码定位: L135
    • 说明:

      TODO

  • regionsPinged

    • 类型: boolean
    • 修饰符: private static
    • 源码定位: L136
    • 说明:

      TODO

  • inviteNarrationLimiter

    • 类型: RateLimiter
    • 修饰符: private final
    • 源码定位: L137
    • 说明:

      TODO

  • lastScreen

    • 类型: Screen
    • 修饰符: private final
    • 源码定位: L138
    • 说明:

      TODO

  • playButton

    • 类型: Button
    • 修饰符: private
    • 源码定位: L139
    • 说明:

      TODO

  • backButton

    • 类型: Button
    • 修饰符: private
    • 源码定位: L140
    • 说明:

      TODO

  • renewButton

    • 类型: Button
    • 修饰符: private
    • 源码定位: L141
    • 说明:

      TODO

  • configureButton

    • 类型: Button
    • 修饰符: private
    • 源码定位: L142
    • 说明:

      TODO

  • leaveButton

    • 类型: Button
    • 修饰符: private
    • 源码定位: L143
    • 说明:

      TODO

  • realmSelectionList

    • 类型: RealmsMainScreen.RealmSelectionList
    • 修饰符: private
    • 源码定位: L144
    • 说明:

      TODO

  • serverList

    • 类型: RealmsServerList
    • 修饰符: private
    • 源码定位: L145
    • 说明:

      TODO

  • availableSnapshotServers

    • 类型: List<RealmsServer>
    • 修饰符: private
    • 源码定位: L146
    • 说明:

      TODO

  • onlinePlayersPerRealm

    • 类型: RealmsServerPlayerLists
    • 修饰符: private
    • 源码定位: L147
    • 说明:

      TODO

  • trialsAvailable

    • 类型: boolean
    • 修饰符: private volatile
    • 源码定位: L148
    • 说明:

      TODO

  • newsLink

    • 类型: String
    • 修饰符: private volatile
    • 源码定位: L149
    • 说明:

      TODO

  • notifications

    • 类型: List<RealmsNotification>
    • 修饰符: private final
    • 源码定位: L150
    • 说明:

      TODO

  • addRealmButton

    • 类型: Button
    • 修饰符: private
    • 源码定位: L151
    • 说明:

      TODO

  • pendingInvitesButton

    • 类型: RealmsMainScreen.NotificationButton
    • 修饰符: private
    • 源码定位: L152
    • 说明:

      TODO

  • newsButton

    • 类型: RealmsMainScreen.NotificationButton
    • 修饰符: private
    • 源码定位: L153
    • 说明:

      TODO

  • activeLayoutState

    • 类型: RealmsMainScreen.LayoutState
    • 修饰符: private
    • 源码定位: L154
    • 说明:

      TODO

  • layout

    • 类型: HeaderAndFooterLayout
    • 修饰符: private
    • 源码定位: L155
    • 说明:

      TODO

内部类/嵌套类型

  • com.mojang.realmsclient.RealmsMainScreen.AvailableSnapshotEntry

    • 类型: class
    • 修饰符: private
    • 源码定位: L700
    • 说明:

      TODO

  • com.mojang.realmsclient.RealmsMainScreen.CrossButton

    • 类型: class
    • 修饰符: private static
    • 源码定位: L780
    • 说明:

      TODO

  • com.mojang.realmsclient.RealmsMainScreen.Entry

    • 类型: class
    • 修饰符: private abstract
    • 源码定位: L792
    • 说明:

      TODO

  • com.mojang.realmsclient.RealmsMainScreen.LayoutState

    • 类型: enum
    • 修饰符: private static
    • 源码定位: L944
    • 说明:

      TODO

  • com.mojang.realmsclient.RealmsMainScreen.NotificationButton

    • 类型: class
    • 修饰符: private static
    • 源码定位: L951
    • 说明:

      TODO

  • com.mojang.realmsclient.RealmsMainScreen.NotificationMessageEntry

    • 类型: class
    • 修饰符: private
    • 源码定位: L998
    • 说明:

      TODO

  • com.mojang.realmsclient.RealmsMainScreen.ParentEntry

    • 类型: class
    • 修饰符: private
    • 源码定位: L1102
    • 说明:

      TODO

  • com.mojang.realmsclient.RealmsMainScreen.RealmSelectionList

    • 类型: class
    • 修饰符: private
    • 源码定位: L1141
    • 说明:

      TODO

  • com.mojang.realmsclient.RealmsMainScreen.RealmsCall

    • 类型: interface
    • 修饰符: private
    • 源码定位: L1213
    • 说明:

      TODO

  • com.mojang.realmsclient.RealmsMainScreen.ServerEntry

    • 类型: class
    • 修饰符: private
    • 源码定位: L1218
    • 说明:

      TODO

构造器

public RealmsMainScreen(Screen lastScreen) @ L157

  • 构造器名:RealmsMainScreen
  • 源码定位:L157
  • 修饰符:public

参数:

  • lastScreen: Screen

说明:

TODO

方法

下面的方法块按源码顺序生成。

public void init() @ L163

  • 方法名:init
  • 源码定位:L163
  • 返回类型:void
  • 修饰符:public

参数:

说明:

TODO

public static boolean isSnapshot() @ L215

  • 方法名:isSnapshot
  • 源码定位:L215
  • 返回类型:boolean
  • 修饰符:public static

参数:

说明:

TODO

protected void repositionElements() @ L219

  • 方法名:repositionElements
  • 源码定位:L219
  • 返回类型:void
  • 修饰符:protected

参数:

说明:

TODO

public void onClose() @ L227

  • 方法名:onClose
  • 源码定位:L227
  • 返回类型:void
  • 修饰符:public

参数:

说明:

TODO

private void updateLayout() @ L232

  • 方法名:updateLayout
  • 源码定位:L232
  • 返回类型:void
  • 修饰符:private

参数:

说明:

TODO

private void updateLayout(RealmsMainScreen.LayoutState state) @ L240

  • 方法名:updateLayout
  • 源码定位:L240
  • 返回类型:void
  • 修饰符:private

参数:

  • state: RealmsMainScreen.LayoutState

说明:

TODO

private HeaderAndFooterLayout createLayout(RealmsMainScreen.LayoutState state) @ L253

  • 方法名:createLayout
  • 源码定位:L253
  • 返回类型:HeaderAndFooterLayout
  • 修饰符:private

参数:

  • state: RealmsMainScreen.LayoutState

说明:

TODO

private Layout createHeader() @ L275

  • 方法名:createHeader
  • 源码定位:L275
  • 返回类型:Layout
  • 修饰符:private

参数:

说明:

TODO

private Layout createFooter(RealmsMainScreen.LayoutState state) @ L289

  • 方法名:createFooter
  • 源码定位:L289
  • 返回类型:Layout
  • 修饰符:private

参数:

  • state: RealmsMainScreen.LayoutState

说明:

TODO

private LinearLayout createNoRealmsContent() @ L304

  • 方法名:createNoRealmsContent
  • 源码定位:L304
  • 返回类型:LinearLayout
  • 修饰符:private

参数:

说明:

TODO

private void updateButtonStates() @ L318

  • 方法名:updateButtonStates
  • 源码定位:L318
  • 返回类型:void
  • 修饰符:private

参数:

说明:

TODO

private boolean shouldRenewButtonBeActive(RealmsServer server) @ L332

  • 方法名:shouldRenewButtonBeActive
  • 源码定位:L332
  • 返回类型:boolean
  • 修饰符:private

参数:

  • server: RealmsServer

说明:

TODO

private boolean shouldConfigureButtonBeActive(RealmsServer server) @ L336

  • 方法名:shouldConfigureButtonBeActive
  • 源码定位:L336
  • 返回类型:boolean
  • 修饰符:private

参数:

  • server: RealmsServer

说明:

TODO

private boolean shouldLeaveButtonBeActive(RealmsServer server) @ L340

  • 方法名:shouldLeaveButtonBeActive
  • 源码定位:L340
  • 返回类型:boolean
  • 修饰符:private

参数:

  • server: RealmsServer

说明:

TODO

public void tick() @ L344

  • 方法名:tick
  • 源码定位:L344
  • 返回类型:void
  • 修饰符:public

参数:

说明:

TODO

public static void refreshPendingInvites() @ L352

  • 方法名:refreshPendingInvites
  • 源码定位:L352
  • 返回类型:void
  • 修饰符:public static

参数:

说明:

TODO

public static void refreshServerList() @ L356

  • 方法名:refreshServerList
  • 源码定位:L356
  • 返回类型:void
  • 修饰符:public static

参数:

说明:

TODO

private void debugRefreshDataFetchers() @ L360

  • 方法名:debugRefreshDataFetchers
  • 源码定位:L360
  • 返回类型:void
  • 修饰符:private

参数:

说明:

TODO

private DataFetcher.Subscription initDataFetcher(RealmsDataFetcher dataSource) @ L366

  • 方法名:initDataFetcher
  • 源码定位:L366
  • 返回类型:DataFetcher.Subscription
  • 修饰符:private

参数:

  • dataSource: RealmsDataFetcher

说明:

TODO

private void markNotificationsAsSeen(Collection<RealmsNotification> notifications) @ L421

  • 方法名:markNotificationsAsSeen
  • 源码定位:L421
  • 返回类型:void
  • 修饰符:private

参数:

  • notifications: Collection

说明:

TODO

private static <T> void callRealmsClient(RealmsMainScreen.RealmsCall<T> supplier, Consumer<T> callback) @ L438

  • 方法名:callRealmsClient
  • 源码定位:L438
  • 返回类型: void
  • 修饰符:private static

参数:

  • supplier: RealmsMainScreen.RealmsCall
  • callback: Consumer

说明:

TODO

private void refreshListAndLayout() @ L452

  • 方法名:refreshListAndLayout
  • 源码定位:L452
  • 返回类型:void
  • 修饰符:private

参数:

说明:

TODO

private void pingRegions() @ L458

  • 方法名:pingRegions
  • 源码定位:L458
  • 返回类型:void
  • 修饰符:private

参数:

说明:

TODO

private List<Long> getOwnedNonExpiredRealmIds() @ L472

  • 方法名:getOwnedNonExpiredRealmIds
  • 源码定位:L472
  • 返回类型:List
  • 修饰符:private

参数:

说明:

TODO

private void onRenew(RealmsServer server) @ L484

  • 方法名:onRenew
  • 源码定位:L484
  • 返回类型:void
  • 修饰符:private

参数:

  • server: RealmsServer

说明:

TODO

private void configureClicked(RealmsServer selectedServer) @ L497

  • 方法名:configureClicked
  • 源码定位:L497
  • 返回类型:void
  • 修饰符:private

参数:

  • selectedServer: RealmsServer

说明:

TODO

private void leaveClicked(RealmsServer selectedServer) @ L503

  • 方法名:leaveClicked
  • 源码定位:L503
  • 返回类型:void
  • 修饰符:private

参数:

  • selectedServer: RealmsServer

说明:

TODO

private RealmsServer getSelectedServer() @ L510

  • 方法名:getSelectedServer
  • 源码定位:L510
  • 返回类型:RealmsServer
  • 修饰符:private

参数:

说明:

TODO

private void leaveServer(RealmsServer server) @ L514

  • 方法名:leaveServer
  • 源码定位:L514
  • 返回类型:void
  • 修饰符:private

参数:

  • server: RealmsServer

说明:

TODO

private void dismissNotification(UUID uuid) @ L537

  • 方法名:dismissNotification
  • 源码定位:L537
  • 返回类型:void
  • 修饰符:private

参数:

  • uuid: UUID

说明:

TODO

public void resetScreen() @ L547

  • 方法名:resetScreen
  • 源码定位:L547
  • 返回类型:void
  • 修饰符:public

参数:

说明:

TODO

public Component getNarrationMessage() @ L552

  • 方法名:getNarrationMessage
  • 源码定位:L552
  • 返回类型:Component
  • 修饰符:public

参数:

说明:

TODO

public void extractRenderState(GuiGraphicsExtractor graphics, int xm, int ym, float a) @ L561

  • 方法名:extractRenderState
  • 源码定位:L561
  • 返回类型:void
  • 修饰符:public

参数:

  • graphics: GuiGraphicsExtractor
  • xm: int
  • ym: int
  • a: float

说明:

TODO

private void openTrialAvailablePopup() @ L581

  • 方法名:openTrialAvailablePopup
  • 源码定位:L581
  • 返回类型:void
  • 修饰符:private

参数:

说明:

TODO

public static void play(RealmsServer server, Screen cancelScreen) @ L585

  • 方法名:play
  • 源码定位:L585
  • 返回类型:void
  • 修饰符:public static

参数:

  • server: RealmsServer
  • cancelScreen: Screen

说明:

TODO

public static void play(RealmsServer server, Screen cancelScreen, boolean skipCompatibility) @ L589

  • 方法名:play
  • 源码定位:L589
  • 返回类型:void
  • 修饰符:public static

参数:

  • server: RealmsServer
  • cancelScreen: Screen
  • skipCompatibility: boolean

说明:

TODO

private static void confirmToPlay(RealmsServer server, Screen lastScreen, Component title, Component message, Component confirmButton) @ L652

  • 方法名:confirmToPlay
  • 源码定位:L652
  • 返回类型:void
  • 修饰符:private static

参数:

  • server: RealmsServer
  • lastScreen: Screen
  • title: Component
  • message: Component
  • confirmButton: Component

说明:

TODO

private static void upgradeRealmAndPlay(RealmsServer server, Screen cancelScreen) @ L659

  • 方法名:upgradeRealmAndPlay
  • 源码定位:L659
  • 返回类型:void
  • 修饰符:private static

参数:

  • server: RealmsServer
  • cancelScreen: Screen

说明:

TODO

public static Component getVersionComponent(String version, boolean isCompatible) @ L670

  • 方法名:getVersionComponent
  • 源码定位:L670
  • 返回类型:Component
  • 修饰符:public static

参数:

  • version: String
  • isCompatible: boolean

说明:

TODO

public static Component getVersionComponent(String version, int color) @ L674

  • 方法名:getVersionComponent
  • 源码定位:L674
  • 返回类型:Component
  • 修饰符:public static

参数:

  • version: String
  • color: int

说明:

TODO

public static Component getGameModeComponent(int gameMode, boolean hardcore) @ L678

  • 方法名:getGameModeComponent
  • 源码定位:L678
  • 返回类型:Component
  • 修饰符:public static

参数:

  • gameMode: int
  • hardcore: boolean

说明:

TODO

private static boolean isSelfOwnedServer(RealmsServer serverData) @ L682

  • 方法名:isSelfOwnedServer
  • 源码定位:L682
  • 返回类型:boolean
  • 修饰符:private static

参数:

  • serverData: RealmsServer

说明:

TODO

private boolean isSelfOwnedNonExpiredServer(RealmsServer serverData) @ L686

  • 方法名:isSelfOwnedNonExpiredServer
  • 源码定位:L686
  • 返回类型:boolean
  • 修饰符:private

参数:

  • serverData: RealmsServer

说明:

TODO

private void extractEnvironment(GuiGraphicsExtractor graphics, String text, int color) @ L690

  • 方法名:extractEnvironment
  • 源码定位:L690
  • 返回类型:void
  • 修饰符:private

参数:

  • graphics: GuiGraphicsExtractor
  • text: String
  • color: int

说明:

TODO

代码

@OnlyIn(Dist.CLIENT)
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 = !SharedConstants.getCurrentVersion().stable();
    private static boolean snapshotToggle = SNAPSHOT;
    private final CompletableFuture<RealmsAvailability.Result> availability = RealmsAvailability.get();
    private DataFetcher.@Nullable Subscription dataSubscription;
    private final Set<UUID> 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 RealmsMainScreen.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<>();
    private Button addRealmButton;
    private RealmsMainScreen.NotificationButton pendingInvitesButton;
    private RealmsMainScreen.NotificationButton newsButton;
    private RealmsMainScreen.LayoutState activeLayoutState;
    private @Nullable HeaderAndFooterLayout layout;
 
    public RealmsMainScreen(Screen lastScreen) {
        super(TITLE);
        this.lastScreen = lastScreen;
        this.inviteNarrationLimiter = RateLimiter.create(0.016666668F);
    }
 
    @Override
    public void init() {
        this.serverList = new RealmsServerList(this.minecraft);
        this.realmSelectionList = new RealmsMainScreen.RealmSelectionList();
        Component invitesTitle = Component.translatable("mco.invites.title");
        this.pendingInvitesButton = new RealmsMainScreen.NotificationButton(
            invitesTitle, INVITE_SPRITE, b -> this.minecraft.setScreen(new RealmsPendingInvitesScreen(this, invitesTitle)), null
        );
        Component newsTitle = Component.translatable("mco.news");
        this.newsButton = new RealmsMainScreen.NotificationButton(newsTitle, NEWS_SPRITE, b -> {
            String newsLink = this.newsLink;
            if (newsLink != null) {
                ConfirmLinkScreen.confirmLinkNow(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 -> 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(RealmsMainScreen.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(RealmsMainScreen.LayoutState.NO_REALMS);
        } else {
            this.updateLayout(RealmsMainScreen.LayoutState.LIST);
        }
    }
 
    private void updateLayout(RealmsMainScreen.LayoutState state) {
        if (this.activeLayoutState != state) {
            if (this.layout != null) {
                this.layout.visitWidgets(x$0 -> this.removeWidget(x$0));
            }
 
            this.layout = this.createLayout(state);
            this.activeLayoutState = state;
            this.layout.visitWidgets(x$0 -> this.addRenderableWidget(x$0));
            this.repositionElements();
        }
    }
 
    private HeaderAndFooterLayout createLayout(RealmsMainScreen.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) {
            case LOADING:
                layout.addToContents(new LoadingDotsWidget(this.font, LOADING_TEXT));
                break;
            case NO_REALMS:
                layout.addToContents(this.createNoRealmsContent());
                break;
            case LIST:
                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(realmsLogo(), LayoutSettings::alignHorizontallyCenter);
        header.addChild(new FrameLayout(90, 44)).addChild(buttons, LayoutSettings::alignHorizontallyRight);
        return header;
    }
 
    private Layout createFooter(RealmsMainScreen.LayoutState state) {
        GridLayout footer = new GridLayout().spacing(4);
        GridLayout.RowHelper helper = footer.createRowHelper(3);
        if (state == RealmsMainScreen.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 != RealmsMainScreen.LayoutState.LOADING;
        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 && isSelfOwnedServer(server);
    }
 
    private boolean shouldConfigureButtonBeActive(RealmsServer server) {
        return isSelfOwnedServer(server) && server.state != RealmsServer.State.UNINITIALIZED;
    }
 
    private boolean shouldLeaveButtonBeActive(RealmsServer server) {
        return !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)) {
                    ownsNonExpiredRealmServer = true;
                }
            }
 
            if (!regionsPinged && ownsNonExpiredRealmServer) {
                regionsPinged = true;
                this.pingRegions();
            }
        });
        callRealmsClient(RealmsClient::getNotifications, retrievedNotifications -> {
            this.notifications.clear();
            this.notifications.addAll(retrievedNotifications);
 
            for (RealmsNotification notification : retrievedNotifications) {
                if (notification instanceof RealmsNotification.InfoPopup popup) {
                    PopupScreen popupScreen = popup.buildScreen(this, this::dismissNotification);
                    if (popupScreen != null) {
                        this.minecraft.setScreen(popupScreen);
                        this.markNotificationsAsSeen(List.of(notification));
                        break;
                    }
                }
            }
 
            if (!this.notifications.isEmpty() && this.activeLayoutState != RealmsMainScreen.LayoutState.LOADING) {
                this.refreshListAndLayout();
            }
        });
        result.subscribe(dataSource.pendingInvitesTask, numberOfPendingInvites -> {
            this.pendingInvitesButton.setNotificationCount(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(news);
            this.newsLink = dataSource.newsManager.newsLink();
            this.newsButton.setNotificationCount(dataSource.newsManager.hasUnreadNews() ? Integer.MAX_VALUE : 0);
        });
        return result;
    }
 
    private void markNotificationsAsSeen(Collection<RealmsNotification> notifications) {
        List<UUID> seenNotifications = new ArrayList<>(notifications.size());
 
        for (RealmsNotification notification : notifications) {
            if (!notification.seen() && !this.handledSeenNotifications.contains(notification.uuid())) {
                seenNotifications.add(notification.uuid());
            }
        }
 
        if (!seenNotifications.isEmpty()) {
            callRealmsClient(realmsClient -> {
                realmsClient.notificationsSeen(seenNotifications);
                return null;
            }, ignored -> this.handledSeenNotifications.addAll(seenNotifications));
        }
    }
 
    private static <T> void callRealmsClient(RealmsMainScreen.RealmsCall<T> supplier, Consumer<T> callback) {
        Minecraft minecraft = Minecraft.getInstance();
        CompletableFuture.<T>supplyAsync(() -> {
            try {
                return supplier.request(RealmsClient.getOrCreate(minecraft));
            } catch (RealmsServiceException var3) {
                throw new RuntimeException(var3);
            }
        }).thenAcceptAsync(callback, 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 var5) {
                LOGGER.warn("Could not send ping result to Realms: ", var5);
            }
        }).start();
    }
 
    private List<Long> getOwnedNonExpiredRealmIds() {
        List<Long> ids = Lists.newArrayList();
 
        for (RealmsServer server : this.serverList) {
            if (this.isSelfOwnedNonExpiredServer(server)) {
                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)) {
            Component popupMessage = Component.translatable("mco.configure.world.leave.question.line1");
            this.minecraft.setScreen(RealmsPopups.infoPopupScreen(this, popupMessage, popup -> this.leaveServer(selectedServer)));
        }
    }
 
    private @Nullable RealmsServer getSelectedServer() {
        return this.realmSelectionList.getSelected() instanceof RealmsMainScreen.ServerEntry entry ? entry.getServer() : null;
    }
 
    private void leaveServer(RealmsServer server) {
        (new Thread("Realms-leave-server") {
                {
                    Objects.requireNonNull(RealmsMainScreen.this);
                }
 
                @Override
                public void run() {
                    try {
                        RealmsClient client = RealmsClient.getOrCreate();
                        client.uninviteMyselfFrom(server.id);
                        RealmsMainScreen.this.minecraft.execute(RealmsMainScreen::refreshServerList);
                    } catch (RealmsServiceException var2) {
                        RealmsMainScreen.LOGGER.error("Couldn't configure world", (Throwable)var2);
                        RealmsMainScreen.this.minecraft
                            .execute(() -> RealmsMainScreen.this.minecraft.setScreen(new RealmsGenericErrorScreen(var2, RealmsMainScreen.this)));
                    }
                }
            })
            .start();
        this.minecraft.setScreen(this);
    }
 
    private void dismissNotification(UUID uuid) {
        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(null);
        refreshServerList();
    }
 
    @Override
    public Component getNarrationMessage() {
        return (Component)(switch (this.activeLayoutState) {
            case LOADING -> CommonComponents.joinForNarration(super.getNarrationMessage(), LOADING_TEXT);
            case NO_REALMS -> CommonComponents.joinForNarration(super.getNarrationMessage(), NO_REALMS_TEXT);
            case LIST -> super.getNarrationMessage();
        });
    }
 
    @Override
    public void extractRenderState(GuiGraphicsExtractor graphics, int xm, int ym, float a) {
        super.extractRenderState(graphics, xm, ym, a);
        if (isSnapshot()) {
            graphics.text(this.font, "Minecraft " + SharedConstants.getCurrentVersion().name(), 2, this.height - 10, -1);
        }
 
        if (this.trialsAvailable && this.addRealmButton.active) {
            AddRealmPopupScreen.extractDiamond(graphics, this.addRealmButton);
        }
 
        switch (RealmsClient.ENVIRONMENT) {
            case STAGE:
                this.extractEnvironment(graphics, "STAGE!", -256);
                break;
            case LOCAL:
                this.extractEnvironment(graphics, "LOCAL!", -8388737);
        }
    }
 
    private void openTrialAvailablePopup() {
        this.minecraft.setScreen(new AddRealmPopupScreen(this, this.trialsAvailable));
    }
 
    public static void play(@Nullable RealmsServer server, Screen cancelScreen) {
        play(server, cancelScreen, false);
    }
 
    public static void play(@Nullable RealmsServer server, Screen cancelScreen, boolean skipCompatibility) {
        if (server != null) {
            if (!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:
                    confirmToPlay(
                        server,
                        cancelScreen,
                        Component.translatable("mco.compatibility.unverifiable.title").withColor(-171),
                        Component.translatable("mco.compatibility.unverifiable.message"),
                        CommonComponents.GUI_CONTINUE
                    );
                    break;
                case NEEDS_DOWNGRADE:
                    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:
                    upgradeRealmAndPlay(server, cancelScreen);
                    break;
                case INCOMPATIBLE:
                    Minecraft.getInstance()
                        .setScreen(
                            new PopupScreen.Builder(cancelScreen, INCOMPATIBLE_POPUP_TITLE)
                                .addMessage(
                                    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)
                                .addMessage(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).addMessage(message).addButton(confirmButton, popupScreen -> {
            Minecraft.getInstance().setScreen(new RealmsLongRunningMcoTaskScreen(lastScreen, new GetServerDetailsTask(lastScreen, server)));
            refreshServerList();
        }).addButton(CommonComponents.GUI_CANCEL, PopupScreen::onClose).build());
    }
 
    private static void upgradeRealmAndPlay(RealmsServer server, Screen cancelScreen) {
        Component title = Component.translatable("mco.compatibility.upgrade.title").withColor(-171);
        Component confirmButton = Component.translatable("mco.compatibility.upgrade");
        Component serverVersion = Component.literal(server.activeVersion).withColor(-171);
        Component clientVersion = Component.literal(SharedConstants.getCurrentVersion().name()).withColor(-171);
        Component message = isSelfOwnedServer(server)
            ? Component.translatable("mco.compatibility.upgrade.description", serverVersion, clientVersion)
            : Component.translatable("mco.compatibility.upgrade.friend.description", serverVersion, clientVersion);
        confirmToPlay(server, cancelScreen, title, message, confirmButton);
    }
 
    public static Component getVersionComponent(String version, boolean isCompatible) {
        return getVersionComponent(version, isCompatible ? -8355712 : -2142128);
    }
 
    public static Component getVersionComponent(String version, int color) {
        return (Component)(StringUtils.isBlank(version) ? CommonComponents.EMPTY : Component.literal(version).withColor(color));
    }
 
    public static Component getGameModeComponent(int gameMode, boolean hardcore) {
        return (Component)(hardcore ? Component.translatable("gameMode.hardcore").withColor(-65536) : GameType.byId(gameMode).getLongDisplayName());
    }
 
    private static boolean isSelfOwnedServer(RealmsServer serverData) {
        return Minecraft.getInstance().isLocalPlayer(serverData.ownerUUID);
    }
 
    private boolean isSelfOwnedNonExpiredServer(RealmsServer serverData) {
        return isSelfOwnedServer(serverData) && !serverData.expired;
    }
 
    private void extractEnvironment(GuiGraphicsExtractor graphics, String text, int color) {
        graphics.pose().pushMatrix();
        graphics.pose().translate(this.width / 2 - 25, 20.0F);
        graphics.pose().rotate((float) (-Math.PI / 9));
        graphics.pose().scale(1.5F, 1.5F);
        graphics.text(this.font, text, 0, 0, color);
        graphics.pose().popMatrix();
    }
 
    @OnlyIn(Dist.CLIENT)
    private class AvailableSnapshotEntry extends RealmsMainScreen.Entry {
        private static final Component START_SNAPSHOT_REALM = Component.translatable("mco.snapshot.start");
        private static final int TEXT_PADDING = 5;
        private final WidgetTooltipHolder tooltip;
        private final RealmsServer parent;
 
        public AvailableSnapshotEntry(RealmsServer parent) {
            Objects.requireNonNull(RealmsMainScreen.this);
            super();
            this.tooltip = new WidgetTooltipHolder();
            this.parent = parent;
            this.tooltip.set(Tooltip.create(Component.translatable("mco.snapshot.tooltip")));
        }
 
        @Override
        public void extractContent(GuiGraphicsExtractor graphics, int mouseX, int mouseY, boolean hovered, float a) {
            graphics.blitSprite(RenderPipelines.GUI_TEXTURED, RealmsMainScreen.NEW_REALM_SPRITE, this.getContentX() - 5, this.getContentYMiddle() - 10, 40, 20);
            int textYPos = this.getContentYMiddle() - 9 / 2;
            graphics.text(RealmsMainScreen.this.font, START_SNAPSHOT_REALM, this.getContentX() + 40 - 2, textYPos - 5, -8388737);
            graphics.text(
                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;
            } else {
                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"))
                        .addMessage(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"))
                )
            );
        }
    }
 
    @OnlyIn(Dist.CLIENT)
    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));
        }
    }
 
    @OnlyIn(Dist.CLIENT)
    private abstract class Entry extends ObjectSelectionList.Entry<RealmsMainScreen.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() {
            Objects.requireNonNull(RealmsMainScreen.this);
            super();
        }
 
        protected void extractStatusLights(RealmsServer serverData, GuiGraphicsExtractor graphics, int rowRight, int rowTop, int mouseX, int mouseY) {
            int x = rowRight - 10 - 7;
            int y = rowTop + 2;
            if (serverData.expired) {
                this.extractRealmStatus(graphics, x, y, mouseX, mouseY, RealmsMainScreen.EXPIRED_SPRITE, () -> RealmsMainScreen.SERVER_EXPIRED_TOOLTIP);
            } else if (serverData.state == RealmsServer.State.CLOSED) {
                this.extractRealmStatus(graphics, x, y, mouseX, mouseY, RealmsMainScreen.CLOSED_SPRITE, () -> RealmsMainScreen.SERVER_CLOSED_TOOLTIP);
            } else if (RealmsMainScreen.isSelfOwnedServer(serverData) && serverData.daysLeft < 7) {
                this.extractRealmStatus(
                    graphics,
                    x,
                    y,
                    mouseX,
                    mouseY,
                    RealmsMainScreen.EXPIRES_SOON_SPRITE,
                    () -> {
                        if (serverData.daysLeft <= 0) {
                            return RealmsMainScreen.SERVER_EXPIRES_SOON_TOOLTIP;
                        } else {
                            return (Component)(serverData.daysLeft == 1
                                ? RealmsMainScreen.SERVER_EXPIRES_IN_DAY_TOOLTIP
                                : Component.translatable("mco.selectServer.expires.days", serverData.daysLeft));
                        }
                    }
                );
            } else if (serverData.state == RealmsServer.State.OPEN) {
                this.extractRealmStatus(graphics, x, y, mouseX, mouseY, RealmsMainScreen.OPEN_SPRITE, () -> RealmsMainScreen.SERVER_OPEN_TOOLTIP);
            }
        }
 
        private void extractRealmStatus(GuiGraphicsExtractor 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 extractFirstLine(GuiGraphicsExtractor 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.extractClampedString(graphics, serverData.getName(), textX, firstLineY, versionTextX, serverNameColor);
            if (versionComponent != CommonComponents.EMPTY && !serverData.isMinigameActive()) {
                graphics.text(RealmsMainScreen.this.font, versionComponent, versionTextX, firstLineY, -8355712);
            }
        }
 
        protected void extractSecondLine(GuiGraphicsExtractor 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) {
                Component minigameNameComponent = Component.literal(minigameName).withStyle(ChatFormatting.GRAY);
                graphics.text(
                    RealmsMainScreen.this.font,
                    Component.translatable("mco.selectServer.minigameName", minigameNameComponent).withColor(-171),
                    textX,
                    secondLineY,
                    -1
                );
            } else {
                int maxX = this.extractGameMode(serverData, graphics, rowLeft, rowWidth, firstLineY);
                this.extractClampedString(graphics, serverData.getDescription(), textX, this.secondLineY(firstLineY), maxX, -8355712);
            }
        }
 
        protected void extractThirdLine(GuiGraphicsExtractor 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.text(RealmsMainScreen.this.font, server.owner, textX, this.thirdLineY(firstLineY), -8355712);
            } else if (server.expired) {
                Component expirationText = server.expiredTrial ? RealmsMainScreen.TRIAL_EXPIRED_TEXT : RealmsMainScreen.SUBSCRIPTION_EXPIRED_TEXT;
                graphics.text(RealmsMainScreen.this.font, expirationText, textX, thirdLineY, -2142128);
            }
        }
 
        protected void extractClampedString(GuiGraphicsExtractor graphics, @Nullable String string, int x, int y, int maxX, int color) {
            if (string != null) {
                int availableSpace = maxX - x;
                if (RealmsMainScreen.this.font.width(string) > availableSpace) {
                    String clampedName = RealmsMainScreen.this.font.plainSubstrByWidth(string, availableSpace - RealmsMainScreen.this.font.width("... "));
                    graphics.text(RealmsMainScreen.this.font, clampedName + "...", x, y, color);
                } else {
                    graphics.text(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 extractGameMode(RealmsServer server, GuiGraphicsExtractor 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.text(RealmsMainScreen.this.font, gameModeComponent, x, this.secondLineY(firstLineY), -8355712);
            }
 
            if (hardcore) {
                x -= 10;
                graphics.blitSprite(RenderPipelines.GUI_TEXTURED, RealmsMainScreen.HARDCORE_MODE_SPRITE, x, this.secondLineY(firstLineY), 8, 8);
            }
 
            return x;
        }
 
        protected int firstLineY(int rowTop) {
            return rowTop + 1;
        }
 
        protected int lineHeight() {
            return 2 + 9;
        }
 
        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;
        }
    }
 
    @OnlyIn(Dist.CLIENT)
    private static enum LayoutState {
        LOADING,
        NO_REALMS,
        LIST;
    }
 
    @OnlyIn(Dist.CLIENT)
    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 extractContents(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float a) {
            super.extractContents(graphics, mouseX, mouseY, a);
            if (this.active && this.notificationCount != 0) {
                this.extractNotificationCounter(graphics);
            }
        }
 
        private void extractNotificationCounter(GuiGraphicsExtractor graphics) {
            graphics.blitSprite(
                RenderPipelines.GUI_TEXTURED,
                NOTIFICATION_ICONS[Math.min(this.notificationCount, 6) - 1],
                this.getX() + this.getWidth() - 5,
                this.getY() - 3,
                8,
                8
            );
        }
    }
 
    @OnlyIn(Dist.CLIENT)
    private class NotificationMessageEntry extends RealmsMainScreen.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;
        private final RealmsMainScreen.@Nullable CrossButton dismissButton;
        private final MultiLineTextWidget textWidget;
        private final GridLayout gridLayout;
        private final FrameLayout textFrame;
        private final Button button;
        private int lastEntryWidth;
 
        public NotificationMessageEntry(RealmsMainScreen realmsMainScreen, int messageHeight, Component text, RealmsNotification.VisitUrl notification) {
            Objects.requireNonNull(RealmsMainScreen.this);
            super();
            this.children = new ArrayList<>();
            this.lastEntryWidth = -1;
            this.text = text;
            this.gridLayout = new GridLayout();
            this.gridLayout.addChild(ImageWidget.sprite(20, 20, RealmsMainScreen.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);
            if (notification.dismissable()) {
                this.dismissButton = this.gridLayout
                    .addChild(
                        new RealmsMainScreen.CrossButton(
                            b -> RealmsMainScreen.this.dismissNotification(notification.uuid()), Component.translatable("mco.notification.dismiss")
                        ),
                        0,
                        2,
                        this.gridLayout.newCellSettings().alignHorizontallyRight().padding(0, 7, 7, 0)
                    );
            } else {
                this.dismissButton = null;
            }
 
            this.button = this.gridLayout
                .addChild(notification.buildOpenLinkButton(realmsMainScreen), 1, 1, this.gridLayout.newCellSettings().alignHorizontallyCenter().padding(4));
            this.button.setOverrideRenderHighlightedSprite(() -> this.isFocused());
            this.gridLayout.visitWidgets(this.children::add);
        }
 
        @Override
        public boolean keyPressed(KeyEvent event) {
            if (this.button.keyPressed(event)) {
                return true;
            } else {
                return this.dismissButton != null && this.dismissButton.keyPressed(event) ? true : 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 = 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 extractContent(GuiGraphicsExtractor graphics, int mouseX, int mouseY, boolean hovered, float a) {
            this.gridLayout.setPosition(this.getContentX(), this.getContentY());
            this.updateEntryWidth();
            this.children.forEach(child -> child.extractRenderState(graphics, mouseX, mouseY, a));
        }
 
        @Override
        public boolean mouseClicked(MouseButtonEvent event, boolean doubleClick) {
            if (this.dismissButton != null && this.dismissButton.mouseClicked(event, doubleClick)) {
                return true;
            } else {
                return this.button.mouseClicked(event, doubleClick) ? true : super.mouseClicked(event, doubleClick);
            }
        }
 
        public Component getText() {
            return this.text;
        }
 
        @Override
        public Component getNarration() {
            return this.getText();
        }
    }
 
    @OnlyIn(Dist.CLIENT)
    private class ParentEntry extends RealmsMainScreen.Entry {
        private final RealmsServer server;
        private final WidgetTooltipHolder tooltip;
 
        public ParentEntry(RealmsServer server) {
            Objects.requireNonNull(RealmsMainScreen.this);
            super();
            this.tooltip = new WidgetTooltipHolder();
            this.server = server;
            if (!server.expired) {
                this.tooltip.set(Tooltip.create(Component.translatable("mco.snapshot.parent.tooltip")));
            }
        }
 
        @Override
        public void extractContent(GuiGraphicsExtractor graphics, int mouseX, int mouseY, boolean hovered, float a) {
            this.extractStatusLights(this.server, graphics, this.getContentRight(), this.getContentY(), mouseX, mouseY);
            RealmsUtil.extractPlayerFace(graphics, this.getContentX(), this.getContentY(), 32, this.server.ownerUUID);
            this.extractFirstLine(graphics, this.getContentY(), this.getContentX(), this.getContentWidth(), -8355712, this.server);
            this.extractSecondLine(graphics, this.getContentY(), this.getContentX(), this.getContentWidth(), this.server);
            this.extractThirdLine(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"));
        }
    }
 
    @OnlyIn(Dist.CLIENT)
    private class RealmSelectionList extends ObjectSelectionList<RealmsMainScreen.Entry> {
        public RealmSelectionList() {
            Objects.requireNonNull(RealmsMainScreen.this);
            super(Minecraft.getInstance(), RealmsMainScreen.this.width, RealmsMainScreen.this.height, 0, 36);
        }
 
        public void setSelected(RealmsMainScreen.@Nullable Entry selected) {
            super.setSelected(selected);
            RealmsMainScreen.this.updateButtonStates();
        }
 
        @Override
        public int getRowWidth() {
            return 300;
        }
 
        private void refreshEntries(RealmsMainScreen realmsMainScreen) {
            RealmsMainScreen.Entry previouslySelected = this.getSelected();
            this.clearEntries();
 
            for (RealmsNotification notification : RealmsMainScreen.this.notifications) {
                if (notification instanceof RealmsNotification.VisitUrl visitUrl) {
                    this.addEntriesForNotification(visitUrl, realmsMainScreen, previouslySelected);
                    RealmsMainScreen.this.markNotificationsAsSeen(List.of(notification));
                    break;
                }
            }
 
            this.refreshServerEntries(previouslySelected);
        }
 
        private void addEntriesForNotification(
            RealmsNotification.VisitUrl visitUrl, RealmsMainScreen realmsMainScreen, RealmsMainScreen.@Nullable Entry previouslySelected
        ) {
            Component message = visitUrl.getMessage();
            int messageHeight = RealmsMainScreen.this.font.wordWrapHeight(message, RealmsMainScreen.NotificationMessageEntry.textWidth(this.getRowWidth()));
            RealmsMainScreen.NotificationMessageEntry entry = RealmsMainScreen.this.new NotificationMessageEntry(
                realmsMainScreen, messageHeight, message, visitUrl
            );
            this.addEntry(entry, 38 + messageHeight);
            if (previouslySelected instanceof RealmsMainScreen.NotificationMessageEntry notificationMessageEntry
                && notificationMessageEntry.getText().equals(message)) {
                this.setSelected((RealmsMainScreen.Entry)entry);
            }
        }
 
        private void refreshServerEntries(RealmsMainScreen.@Nullable Entry previouslySelected) {
            for (RealmsServer eligibleForSnapshotServer : RealmsMainScreen.this.availableSnapshotServers) {
                this.addEntry(RealmsMainScreen.this.new AvailableSnapshotEntry(eligibleForSnapshotServer));
            }
 
            for (RealmsServer server : RealmsMainScreen.this.serverList) {
                RealmsMainScreen.Entry entry;
                if (RealmsMainScreen.isSnapshot() && !server.isSnapshotRealm()) {
                    if (server.state == RealmsServer.State.UNINITIALIZED) {
                        continue;
                    }
 
                    entry = RealmsMainScreen.this.new ParentEntry(server);
                } else {
                    entry = RealmsMainScreen.this.new ServerEntry(server);
                }
 
                this.addEntry(entry);
                if (previouslySelected instanceof RealmsMainScreen.ServerEntry serverEntry && serverEntry.serverData.id == server.id) {
                    this.setSelected(entry);
                }
            }
        }
    }
 
    @OnlyIn(Dist.CLIENT)
    private interface RealmsCall<T> {
        T request(RealmsClient realmsClient) throws RealmsServiceException;
    }
 
    @OnlyIn(Dist.CLIENT)
    private class ServerEntry extends RealmsMainScreen.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) {
            Objects.requireNonNull(RealmsMainScreen.this);
            super();
            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 extractContent(GuiGraphicsExtractor graphics, int mouseX, int mouseY, boolean hovered, float a) {
            if (this.serverData.state == RealmsServer.State.UNINITIALIZED) {
                graphics.blitSprite(
                    RenderPipelines.GUI_TEXTURED, RealmsMainScreen.NEW_REALM_SPRITE, this.getContentX() - 5, this.getContentYMiddle() - 10, 40, 20
                );
                int textYPos = this.getContentYMiddle() - 9 / 2;
                graphics.text(RealmsMainScreen.this.font, RealmsMainScreen.SERVER_UNITIALIZED_TEXT, this.getContentX() + 40 - 2, textYPos, -8388737);
            } else {
                RealmsUtil.extractPlayerFace(graphics, this.getContentX(), this.getContentY(), 32, this.serverData.ownerUUID);
                this.extractFirstLine(graphics, this.getContentY(), this.getContentX(), this.getContentWidth(), -1, this.serverData);
                this.extractSecondLine(graphics, this.getContentY(), this.getContentX(), this.getContentWidth(), this.serverData);
                this.extractThirdLine(graphics, this.getContentY(), this.getContentX(), this.serverData);
                this.extractStatusLights(this.serverData, graphics, this.getContentRight(), this.getContentY(), mouseX, mouseY);
                boolean hasTooltip = this.extractOnlinePlayers(
                    graphics, this.getContentY(), this.getContentX(), this.getContentWidth(), this.getContentHeight(), mouseX, mouseY, a
                );
                if (!hasTooltip) {
                    this.tooltip
                        .refreshTooltipForNextRenderPass(
                            graphics,
                            mouseX,
                            mouseY,
                            hovered,
                            this.isFocused(),
                            new ScreenRectangle(this.getContentX(), this.getContentY(), this.getContentWidth(), this.getContentHeight())
                        );
                }
            }
        }
 
        private boolean extractOnlinePlayers(
            GuiGraphicsExtractor 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;
                List<PlayerSkinRenderCache.RenderInfo> tooltipEntries;
                if (mouseX >= playersOnlineXStart && mouseX <= playersOnlineXEnd && mouseY >= playersOnlineY && mouseY <= playersOnlineY + 9) {
                    tooltipEntries = new ArrayList<>(playerCount);
                } else {
                    tooltipEntries = 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;
                    PlayerFaceExtractor.extractRenderState(graphics, profileRenderInfo.playerSkin(), xPos, playersOnlineY, 9);
                    if (tooltipEntries != null) {
                        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() {
            return (Component)(this.serverData.state == RealmsServer.State.UNINITIALIZED
                ? RealmsMainScreen.UNITIALIZED_WORLD_NARRATION
                : Component.translatable("narrator.select", Objects.requireNonNullElse(this.serverData.name, "unknown server")));
        }
 
        public RealmsServer getServer() {
            return this.serverData;
        }
    }
}

引用的其他类

  • RealmsAvailability

    • 引用位置: 字段/方法调用
    • 关联成员: RealmsAvailability.get()
  • Ping

    • 引用位置: 方法调用
    • 关联成员: Ping.pingAllRegions()
  • RealmsClient

    • 引用位置: 方法调用
    • 关联成员: RealmsClient.getOrCreate()
  • PingResult

    • 引用位置: 构造调用
    • 关联成员: PingResult()
  • RealmsNotification

    • 引用位置: 参数/字段
  • RealmsServer

    • 引用位置: 参数/字段/返回值
  • RealmsServerPlayerLists

    • 引用位置: 字段/构造调用
    • 关联成员: RealmsServerPlayerLists()
  • RealmsDataFetcher

    • 引用位置: 参数
  • RealmsServerList

    • 引用位置: 字段/构造调用
    • 关联成员: RealmsServerList()
  • AddRealmPopupScreen

    • 引用位置: 方法调用/构造调用
    • 关联成员: AddRealmPopupScreen(), AddRealmPopupScreen.extractDiamond()
  • RealmsCreateRealmScreen

    • 引用位置: 构造调用
    • 关联成员: RealmsCreateRealmScreen()
  • RealmsGenericErrorScreen

    • 引用位置: 构造调用
    • 关联成员: RealmsGenericErrorScreen()
  • RealmsLongRunningMcoTaskScreen

    • 引用位置: 构造调用
    • 关联成员: RealmsLongRunningMcoTaskScreen()
  • RealmsPendingInvitesScreen

    • 引用位置: 构造调用
    • 关联成员: RealmsPendingInvitesScreen()
  • RealmsPopups

    • 引用位置: 方法调用
    • 关联成员: RealmsPopups.infoPopupScreen()
  • RealmsConfigureWorldScreen

    • 引用位置: 构造调用
    • 关联成员: RealmsConfigureWorldScreen()
  • DataFetcher

    • 引用位置: 字段/返回值
  • RealmsPersistence

    • 引用位置: 方法调用
    • 关联成员: RealmsPersistence.readFile(), RealmsPersistence.writeFile()
  • RealmsUtil

    • 引用位置: 方法调用
    • 关联成员: RealmsUtil.extractPlayerFace()
  • GetServerDetailsTask

    • 引用位置: 构造调用
    • 关联成员: GetServerDetailsTask()
  • SharedConstants

    • 引用位置: 方法调用
    • 关联成员: SharedConstants.getCurrentVersion()
  • Minecraft

    • 引用位置: 方法调用
    • 关联成员: Minecraft.getInstance()
  • GuiGraphicsExtractor

    • 引用位置: 参数
  • Button

    • 引用位置: 字段/方法调用
    • 关联成员: Button.builder()
  • CycleButton

    • 引用位置: 方法调用
    • 关联成员: CycleButton.booleanBuilder()
  • FocusableTextWidget

    • 引用位置: 方法调用
    • 关联成员: FocusableTextWidget.builder()
  • ImageWidget

    • 引用位置: 方法调用
    • 关联成员: ImageWidget.sprite(), ImageWidget.texture()
  • LoadingDotsWidget

    • 引用位置: 构造调用
    • 关联成员: LoadingDotsWidget()
  • MultiLineTextWidget

    • 引用位置: 构造调用
    • 关联成员: MultiLineTextWidget()
  • PlayerFaceExtractor

    • 引用位置: 方法调用
    • 关联成员: PlayerFaceExtractor.extractRenderState()
  • PopupScreen

    • 引用位置: 方法调用/构造调用
    • 关联成员: Builder(), PopupScreen.Builder()
  • Tooltip

    • 引用位置: 方法调用
    • 关联成员: Tooltip.create()
  • WidgetSprites

    • 引用位置: 构造调用
    • 关联成员: WidgetSprites()
  • WidgetTooltipHolder

    • 引用位置: 构造调用
    • 关联成员: WidgetTooltipHolder()
  • FrameLayout

    • 引用位置: 构造调用
    • 关联成员: FrameLayout()
  • GridLayout

    • 引用位置: 构造调用
    • 关联成员: GridLayout()
  • HeaderAndFooterLayout

    • 引用位置: 字段/构造调用/返回值
    • 关联成员: HeaderAndFooterLayout()
  • Layout

    • 引用位置: 返回值
  • LinearLayout

    • 引用位置: 方法调用/返回值
    • 关联成员: LinearLayout.horizontal(), LinearLayout.vertical()
  • SpacerElement

    • 引用位置: 方法调用
    • 关联成员: SpacerElement.width()
  • ScreenRectangle

    • 引用位置: 构造调用
    • 关联成员: ScreenRectangle()
  • ConfirmLinkScreen

    • 引用位置: 方法调用/构造调用
    • 关联成员: ConfirmLinkScreen(), ConfirmLinkScreen.confirmLinkNow()
  • Screen

    • 引用位置: 参数/字段
  • ClientActivePlayersTooltip

    • 引用位置: 方法调用/构造调用
    • 关联成员: ActivePlayersTooltip(), ClientActivePlayersTooltip.ActivePlayersTooltip()
  • SimpleSoundInstance

    • 引用位置: 方法调用
    • 关联成员: SimpleSoundInstance.forUI()
  • CommonComponents

    • 引用位置: 方法调用
    • 关联成员: CommonComponents.joinForNarration()
  • Component

    • 引用位置: 参数/字段/方法调用/返回值
    • 关联成员: Component.literal(), Component.translatable()
  • RealmsScreen

    • 引用位置: 继承
  • Identifier

    • 引用位置: 字段/方法调用
    • 关联成员: Identifier.withDefaultNamespace()
  • CommonLinks

    • 引用位置: 方法调用
    • 关联成员: CommonLinks.extendRealms()
  • Util

    • 引用位置: 方法调用
    • 关联成员: Util.getPlatform()
  • GameType

    • 引用位置: 方法调用
    • 关联成员: GameType.byId(), GameType.isValidId()