From 516886089659e2d686195c631722b2b7ec0c69c3 Mon Sep 17 00:00:00 2001 From: Bradlee Barnes <69256931+StupidRepo@users.noreply.github.com> Date: Thu, 30 Oct 2025 16:12:58 +0000 Subject: [PATCH] Revert "Version 1.3.9.3" This reverts commit a051c57ce8446122974c55a5ea2659c296ade5e3. --- changelog/release-1.3.9.3.md | 12 - .../essential/gui/common/stateExtensions.kt | 2 - .../essential/gui/util/elementaExtensions.kt | 12 - .../kotlin/gg/essential/gui/util/focusable.kt | 2 +- .../elementa/state/v2/combinators/booleans.kt | 18 +- .../elementa/state/v2/combinators/state.kt | 49 +- .../elementa/state/v2/combinators/strings.kt | 9 - .../elementa/state/v2/combinators/utils.kt | 2 +- .../gui/elementa/state/v2/impl/Impl.kt | 2 - .../gui/elementa/state/v2/impl/basic/impl.kt | 117 ++--- .../gui/elementa/state/v2/impl/legacy/impl.kt | 1 - .../elementa/state/v2/impl/minimal/impl.kt | 117 ++--- .../essential/gui/elementa/state/v2/state.kt | 12 +- .../essential/gui/elementa/state/v2/time.kt | 12 +- .../gui/elementa/state/v2/EffectTest.kt | 170 ------- .../gui/elementa/state/v2/MemoTest.kt | 174 ------- .../gui/elementa/state/v2/MutableStateTest.kt | 27 -- features.properties | 1 - gradle.properties | 2 +- gradle/libs.versions.toml | 2 +- .../gg/essential/gui/common/extensions.kt | 48 +- .../event/network/server/ServerTickEvent.java | 15 - .../essential/handlers/MojangSkinManager.java | 2 +- .../gg/essential/config/EssentialConfig.kt | 2 - .../cosmetics/IngameEquippedOutfitsManager.kt | 3 - .../gg/essential/gui/EssentialPalette.kt | 4 +- .../gg/essential/gui/common/RadioButton.kt | 91 ---- .../gg/essential/gui/common/Tooltips.kt | 9 +- .../gui/common/input/AbstractTextInput.kt | 24 +- .../gui/common/input/StateTextInput.kt | 7 +- .../gui/common/input/UIMultilineTextInput.kt | 4 + .../gui/common/input/essentialInput.kt | 39 +- .../gui/common/modal/CancelableInputModal.kt | 2 +- .../gui/common/modal/EssentialModal.kt | 2 +- .../gui/common/modal/EssentialModal2.kt | 17 +- .../gg/essential/gui/common/modal/Modal.kt | 7 - .../modal/SearchableConfirmDenyModal.kt | 5 +- .../gui/common/shadow/ShadowEffect.kt | 31 +- .../essential/gui/common/shadow/ShadowIcon.kt | 3 +- .../gg/essential/gui/friends/dropdowns.kt | 297 ------------ .../gui/friends/message/MessageUtils.kt | 8 - .../message/ReportMessageConfirmationModal.kt | 136 ------ .../gui/friends/message/ReportMessageModal.kt | 111 ----- .../gui/friends/message/SocialMenuActions.kt | 42 -- .../gui/friends/message/SocialMenuState.kt | 20 - .../ScreenshotAttachmentComponents.kt | 82 ---- .../gui/friends/message/v2/ClientMessage.kt | 17 - .../message/v2/messageInputSuspended.kt | 60 --- .../gui/friends/modals/AddFriendModal.kt | 30 -- .../friends/modals/BlockConfirmationModal.kt | 24 - .../gui/friends/modals/BlockPlayerModal.kt | 30 -- .../friends/modals/ConfirmGroupLeaveModal.kt | 25 - .../gui/friends/modals/ConfirmJoinModal.kt | 33 -- .../modals/FriendRemoveConfirmationModal.kt | 24 - .../gui/friends/modals/RenameGroupModal.kt | 29 -- .../friends/modals/addFriendsToGroupModal.kt | 26 -- .../gui/friends/modals/makeGroupModalFlow.kt | 74 --- .../gui/friends/state/SocialStates.kt | 57 +-- .../friends/title/TitleManagementActions.kt | 118 ----- .../gui/modals/CommunityRulesModal.kt | 149 ------ .../essential/gui/modals/DisconnectModal.kt | 44 -- .../gui/modals/ModalPrerequisites.kt | 90 ---- .../essential/gui/modals/SuspensionModal.kt | 161 ------- .../gui/modals/select/SelectModal.kt | 4 - .../gg/essential/gui/modals/select/builder.kt | 28 +- .../gg/essential/gui/notification/helpers.kt | 7 - .../gg/essential/gui/overlay/ModalFlow.kt | 6 - .../components/ScreenshotShareModal.kt | 5 +- .../gg/essential/gui/screenshot/utils.kt | 21 - .../kotlin/gg/essential/gui/skin/skinUtils.kt | 2 +- .../wardrobe/categories/CategoryComponent.kt | 12 +- .../gui/wardrobe/components/gifting.kt | 19 +- .../wardrobe/components/skinItemFunctions.kt | 7 +- .../configuration/ConfigurationUtils.kt | 3 +- .../gui/wardrobe/modals/CoinsPurchaseModal.kt | 31 +- .../modals/PurchaseConfirmationModal.kt | 71 ++- .../cosmetics/EquippedOutfitsManager.kt | 1 - .../cosmetics/InfraEquippedOutfitsManager.kt | 3 - .../StateBasedEquippedOutfitsManager.kt | 15 - .../media/IScreenshotManager.kt | 6 - .../social/ProfileSuspension.kt | 51 --- .../connectionmanager/social/RulesManager.kt | 71 --- .../connectionmanager/social/Suspension.kt | 32 -- .../suspension/SuspensionManager.kt | 87 ---- .../telemetry/FeatureSessionTelemetry.kt | 98 ---- .../gg/essential/sps/TPSSessionMonitor.kt | 85 ---- .../gg/essential/util/ServerInfoPing.kt | 96 ---- .../gg/essential/util/channelExtensions.kt | 24 - .../essential/util/essentialGuiExtensions.kt | 46 +- .../gg/essential/util/guiEssentialPlatform.kt | 36 -- src/main/java/gg/essential/Essential.java | 3 - .../commands/impl/CommandMessage.java | 6 +- .../handlers/GameProfileManager.java | 13 +- .../handlers/McMojangSkinManager.java | 12 - .../client/gui/GuiDisconnectedAccessor.java | 33 -- .../client/gui/MixinGuiMultiplayer.java | 12 - .../Mixin_RealmsScreenOpenedTelemetry.java | 38 -- ...es.java => Mixin_RefreshSkinOnChange.java} | 9 +- .../skin_overwrites/MinecraftAccessor.java | 31 -- .../skin_overwrites/PlayerEntityAccessor.java | 19 - ...xin_MinecraftServer_FixStatusEquality.java | 92 ---- .../Mixin_IntegratedServerOnTick.java | 28 -- .../network/connectionmanager/Connection.java | 7 +- .../connectionmanager/ConnectionManager.java | 19 - .../connectionmanager/chat/ChatManager.java | 93 ++-- ...ServerChatChannelMessagePacketHandler.java | 3 +- .../ServerProfileStatusPacketHandler.java | 1 - .../media/ScreenshotManager.java | 44 +- .../profile/ProfileManager.java | 49 -- .../connectionmanager/sps/SPSManager.java | 31 +- .../telemetry/TelemetryManager.java | 17 - .../essential/commands/impl/commandMessage.kt | 32 -- .../gg/essential/gui/InternalEssentialGUI.kt | 14 - .../gg/essential/gui/friends/SocialMenu.kt | 426 ++++++++++++++---- .../gg/essential/gui/friends/TabsSelector.kt | 12 +- .../gui/friends/message/MessageInput.kt | 27 +- .../gui/friends/message/MessageScreen.kt | 5 - .../gui/friends/message/MessageTitleBar.kt | 45 +- .../gui/friends/message/OldMessageInput.kt | 159 +++++++ .../gui/friends/message/ReportMessageModal.kt | 112 +++++ .../screenshot/RemoveableScreenshotPreview.kt | 0 .../message/screenshot/ScreenshotAttacher.kt | 0 .../ScreenshotAttachmentComponents.kt | 175 +++++++ .../screenshot/ScreenshotAttachmentManager.kt | 20 +- .../message/screenshot/ScreenshotPicker.kt | 4 +- .../screenshot/SelectableScreenshotPreview.kt | 0 .../gui/friends/message/v2/DateDividerImpl.kt | 3 +- .../gui/friends/message/v2/GiftEmbedImpl.kt | 4 +- .../gui/friends/message/v2/ImageEmbedImpl.kt | 83 ++-- .../gui/friends/message/v2/InviteEmbedImpl.kt | 26 ++ .../gui/friends/message/v2/MessageLine.kt | 10 +- .../friends/message/v2/MessageWrapperImpl.kt | 88 ++-- .../friends/message/v2/ParagraphLineImpl.kt | 21 - .../message/v2/ReplyableMessageInput.kt | 163 +++++++ .../message/v2/ReplyableMessageScreen.kt | 133 +++--- .../gui/friends/message/v2/SkinEmbedImpl.kt | 8 +- .../friends/message/v2/UnreadDividerImpl.kt | 3 +- .../gui/friends/message/v2/clientMessage.kt | 13 + .../gui/friends/previews/BasicUserEntry.kt | 4 +- .../gui/friends/previews/BlockedUserEntry.kt | 6 +- .../gui/friends/previews/ChannelPreview.kt | 96 +++- .../gui/friends/previews/FriendStatus.kt | 17 +- .../gui/friends/previews/FriendUserEntry.kt | 12 +- .../gui/friends/previews/PendingUserEntry.kt | 14 +- .../gui/friends/previews/SearchableItem.kt | 0 .../gui/friends/previews/SortListener.kt | 0 .../state/MessengerStateManagerImpl.kt | 48 +- .../gui/friends/state/SocialStateManager.kt | 4 - .../gg/essential/gui/friends/tabs/ChatTab.kt | 59 +-- .../essential/gui/friends/tabs/FriendsTab.kt | 39 +- .../gui/friends/tabs/TabComponent.kt | 8 + .../title/SocialTitleManagementActions.kt | 16 +- .../friends/title/TitleManagementActions.kt | 189 ++++++++ .../gui/modals/AccountNotValidModal.kt | 9 +- .../gui/modals/CosmeticsLoadingModal.kt | 11 +- .../gui/modals/McModalPrerequisites.kt | 83 ---- .../gui/modals/NotAuthenticatedModal.kt | 44 +- .../gg/essential/gui/modals/TOSModal.kt | 24 +- .../gui/modals/UpdateAvailableModal.kt | 5 - .../multiplayer/EssentialMultiplayerGui.kt | 16 +- .../gui/screenshot/ScreenshotOverlay.kt | 19 +- .../essential/gui/sps/InviteFriendsModal.kt | 20 +- .../essential/gui/sps/WorldSelectionModal.kt | 2 +- .../kotlin/gg/essential/gui/studio/Tag.kt | 0 .../gui/vigilancev2/VigilanceV2SettingsGui.kt | 33 -- .../gg/essential/gui/wardrobe/Wardrobe.kt | 10 - .../MinecraftGameProfileTexturesRefresher.kt | 154 ------- .../gg/essential/handlers/PauseMenuDisplay.kt | 50 +- .../connectionmanager/ConnectionManagerKt.kt | 36 +- ...SocialMenuNewFriendRequestNoticeManager.kt | 0 .../profile/SuspensionDisconnectHandler.kt | 139 ------ .../profile/profileManager.kt | 22 - .../relationship/relationshipErrorResponse.kt | 12 +- .../relationship/relationshipResponse.kt | 4 +- .../suspension/McSuspensionManager.kt | 55 --- .../telemetry/FPSSessionMonitor.kt | 30 -- .../telemetry/FPSTelemetryManager.kt | 65 --- .../cosmetics/cape/CapeCosmeticsManager.kt | 5 +- .../essential/network/pingproxy/SPSUtils.kt | 42 ++ .../kotlin/gg/essential/util/AddressUtil.kt | 17 +- .../util/GuiEssentialPlatformImpl.kt | 92 ---- src/main/kotlin/gg/essential/util/GuiUtil.kt | 40 +- .../kotlin/gg/essential/util/extensions.kt | 24 + src/main/kotlin/gg/essential/util/helpers.kt | 31 ++ .../resources/assets/essential/commit.txt | 2 +- src/main/resources/mixins.essential.json | 8 +- .../gg/essential/config/HideIfDisabled.kt | 17 +- .../toolbox/chat/model/Channel.java | 14 +- .../profile/ProfilePunishmentStatus.java | 39 -- ...ientChatChannelMessagesRetrievePacket.java | 5 - ...erverChatChannelMessageRejectedPacket.java | 47 -- .../profile/ServerProfileStatusPacket.java | 19 - .../ClientCommunityRulesAgreedPacket.java | 18 - .../ServerCommunityRulesStatePacket.java | 40 -- .../ServerSocialAllowedDomainsPacket.java | 30 -- .../ServerSocialSuspensionStatePacket.java | 62 --- .../gg/essential/network/CMConnection.kt | 1 - .../common/model/profile/PunishmentType.kt | 18 - .../relationship/RelationshipErrorResponse.kt | 2 - .../skin_overwrites/PlayerEntityAccessor.java | 25 - versions/aliases.txt | 1 - 201 files changed, 2099 insertions(+), 5613 deletions(-) delete mode 100644 changelog/release-1.3.9.3.md delete mode 100644 elementa/statev2/src/test/kotlin/gg/essential/gui/elementa/state/v2/EffectTest.kt delete mode 100644 elementa/statev2/src/test/kotlin/gg/essential/gui/elementa/state/v2/MemoTest.kt delete mode 100644 elementa/statev2/src/test/kotlin/gg/essential/gui/elementa/state/v2/MutableStateTest.kt delete mode 100644 gui/essential/src/main/java/gg/essential/event/network/server/ServerTickEvent.java delete mode 100644 gui/essential/src/main/kotlin/gg/essential/gui/common/RadioButton.kt delete mode 100644 gui/essential/src/main/kotlin/gg/essential/gui/friends/dropdowns.kt delete mode 100644 gui/essential/src/main/kotlin/gg/essential/gui/friends/message/ReportMessageConfirmationModal.kt delete mode 100644 gui/essential/src/main/kotlin/gg/essential/gui/friends/message/ReportMessageModal.kt delete mode 100644 gui/essential/src/main/kotlin/gg/essential/gui/friends/message/SocialMenuActions.kt delete mode 100644 gui/essential/src/main/kotlin/gg/essential/gui/friends/message/SocialMenuState.kt delete mode 100644 gui/essential/src/main/kotlin/gg/essential/gui/friends/message/screenshot/ScreenshotAttachmentComponents.kt delete mode 100644 gui/essential/src/main/kotlin/gg/essential/gui/friends/message/v2/messageInputSuspended.kt delete mode 100644 gui/essential/src/main/kotlin/gg/essential/gui/friends/modals/AddFriendModal.kt delete mode 100644 gui/essential/src/main/kotlin/gg/essential/gui/friends/modals/BlockConfirmationModal.kt delete mode 100644 gui/essential/src/main/kotlin/gg/essential/gui/friends/modals/BlockPlayerModal.kt delete mode 100644 gui/essential/src/main/kotlin/gg/essential/gui/friends/modals/ConfirmGroupLeaveModal.kt delete mode 100644 gui/essential/src/main/kotlin/gg/essential/gui/friends/modals/ConfirmJoinModal.kt delete mode 100644 gui/essential/src/main/kotlin/gg/essential/gui/friends/modals/FriendRemoveConfirmationModal.kt delete mode 100644 gui/essential/src/main/kotlin/gg/essential/gui/friends/modals/RenameGroupModal.kt delete mode 100644 gui/essential/src/main/kotlin/gg/essential/gui/friends/modals/addFriendsToGroupModal.kt delete mode 100644 gui/essential/src/main/kotlin/gg/essential/gui/friends/modals/makeGroupModalFlow.kt delete mode 100644 gui/essential/src/main/kotlin/gg/essential/gui/friends/title/TitleManagementActions.kt delete mode 100644 gui/essential/src/main/kotlin/gg/essential/gui/modals/CommunityRulesModal.kt delete mode 100644 gui/essential/src/main/kotlin/gg/essential/gui/modals/DisconnectModal.kt delete mode 100644 gui/essential/src/main/kotlin/gg/essential/gui/modals/ModalPrerequisites.kt delete mode 100644 gui/essential/src/main/kotlin/gg/essential/gui/modals/SuspensionModal.kt delete mode 100644 gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/social/ProfileSuspension.kt delete mode 100644 gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/social/RulesManager.kt delete mode 100644 gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/social/Suspension.kt delete mode 100644 gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/suspension/SuspensionManager.kt delete mode 100644 gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/telemetry/FeatureSessionTelemetry.kt delete mode 100644 gui/essential/src/main/kotlin/gg/essential/sps/TPSSessionMonitor.kt delete mode 100644 gui/essential/src/main/kotlin/gg/essential/util/ServerInfoPing.kt delete mode 100644 gui/essential/src/main/kotlin/gg/essential/util/channelExtensions.kt delete mode 100644 src/main/java/gg/essential/mixins/transformers/client/gui/GuiDisconnectedAccessor.java delete mode 100644 src/main/java/gg/essential/mixins/transformers/client/multiplayer/Mixin_RealmsScreenOpenedTelemetry.java rename src/main/java/gg/essential/mixins/transformers/client/network/{Mixin_ApplyGameProfileOverrides.java => Mixin_RefreshSkinOnChange.java} (90%) delete mode 100644 src/main/java/gg/essential/mixins/transformers/feature/skin_overwrites/MinecraftAccessor.java delete mode 100644 src/main/java/gg/essential/mixins/transformers/feature/skin_overwrites/PlayerEntityAccessor.java delete mode 100644 src/main/java/gg/essential/mixins/transformers/server/Mixin_MinecraftServer_FixStatusEquality.java delete mode 100644 src/main/java/gg/essential/mixins/transformers/server/integrated/Mixin_IntegratedServerOnTick.java delete mode 100644 src/main/kotlin/gg/essential/commands/impl/commandMessage.kt rename {gui/essential/src => src}/main/kotlin/gg/essential/gui/friends/TabsSelector.kt (92%) rename {gui/essential/src => src}/main/kotlin/gg/essential/gui/friends/message/MessageInput.kt (94%) rename {gui/essential/src => src}/main/kotlin/gg/essential/gui/friends/message/MessageScreen.kt (95%) rename {gui/essential/src => src}/main/kotlin/gg/essential/gui/friends/message/MessageTitleBar.kt (79%) create mode 100644 src/main/kotlin/gg/essential/gui/friends/message/OldMessageInput.kt create mode 100644 src/main/kotlin/gg/essential/gui/friends/message/ReportMessageModal.kt rename {gui/essential/src => src}/main/kotlin/gg/essential/gui/friends/message/screenshot/RemoveableScreenshotPreview.kt (100%) rename {gui/essential/src => src}/main/kotlin/gg/essential/gui/friends/message/screenshot/ScreenshotAttacher.kt (100%) create mode 100644 src/main/kotlin/gg/essential/gui/friends/message/screenshot/ScreenshotAttachmentComponents.kt rename {gui/essential/src => src}/main/kotlin/gg/essential/gui/friends/message/screenshot/ScreenshotAttachmentManager.kt (87%) rename {gui/essential/src => src}/main/kotlin/gg/essential/gui/friends/message/screenshot/ScreenshotPicker.kt (97%) rename {gui/essential/src => src}/main/kotlin/gg/essential/gui/friends/message/screenshot/SelectableScreenshotPreview.kt (100%) rename {gui/essential/src => src}/main/kotlin/gg/essential/gui/friends/message/v2/DateDividerImpl.kt (94%) rename {gui/essential/src => src}/main/kotlin/gg/essential/gui/friends/message/v2/GiftEmbedImpl.kt (96%) rename {gui/essential/src => src}/main/kotlin/gg/essential/gui/friends/message/v2/ImageEmbedImpl.kt (85%) create mode 100644 src/main/kotlin/gg/essential/gui/friends/message/v2/InviteEmbedImpl.kt rename {gui/essential/src => src}/main/kotlin/gg/essential/gui/friends/message/v2/MessageLine.kt (96%) rename {gui/essential/src => src}/main/kotlin/gg/essential/gui/friends/message/v2/MessageWrapperImpl.kt (85%) rename {gui/essential/src => src}/main/kotlin/gg/essential/gui/friends/message/v2/ParagraphLineImpl.kt (82%) create mode 100644 src/main/kotlin/gg/essential/gui/friends/message/v2/ReplyableMessageInput.kt rename {gui/essential/src => src}/main/kotlin/gg/essential/gui/friends/message/v2/ReplyableMessageScreen.kt (84%) rename {gui/essential/src => src}/main/kotlin/gg/essential/gui/friends/message/v2/SkinEmbedImpl.kt (88%) rename {gui/essential/src => src}/main/kotlin/gg/essential/gui/friends/message/v2/UnreadDividerImpl.kt (93%) rename {gui/essential/src => src}/main/kotlin/gg/essential/gui/friends/previews/BasicUserEntry.kt (96%) rename {gui/essential/src => src}/main/kotlin/gg/essential/gui/friends/previews/BlockedUserEntry.kt (89%) rename {gui/essential/src => src}/main/kotlin/gg/essential/gui/friends/previews/ChannelPreview.kt (80%) rename {gui/essential/src => src}/main/kotlin/gg/essential/gui/friends/previews/FriendStatus.kt (86%) rename {gui/essential/src => src}/main/kotlin/gg/essential/gui/friends/previews/FriendUserEntry.kt (79%) rename {gui/essential/src => src}/main/kotlin/gg/essential/gui/friends/previews/PendingUserEntry.kt (89%) rename {gui/essential/src => src}/main/kotlin/gg/essential/gui/friends/previews/SearchableItem.kt (100%) rename {gui/essential/src => src}/main/kotlin/gg/essential/gui/friends/previews/SortListener.kt (100%) rename {gui/essential/src => src}/main/kotlin/gg/essential/gui/friends/tabs/ChatTab.kt (76%) rename {gui/essential/src => src}/main/kotlin/gg/essential/gui/friends/tabs/FriendsTab.kt (81%) rename {gui/essential/src => src}/main/kotlin/gg/essential/gui/friends/tabs/TabComponent.kt (85%) rename {gui/essential/src => src}/main/kotlin/gg/essential/gui/friends/title/SocialTitleManagementActions.kt (84%) create mode 100644 src/main/kotlin/gg/essential/gui/friends/title/TitleManagementActions.kt delete mode 100644 src/main/kotlin/gg/essential/gui/modals/McModalPrerequisites.kt rename {gui/essential/src => src}/main/kotlin/gg/essential/gui/studio/Tag.kt (100%) delete mode 100644 src/main/kotlin/gg/essential/handlers/MinecraftGameProfileTexturesRefresher.kt rename {gui/essential/src => src}/main/kotlin/gg/essential/network/connectionmanager/notices/SocialMenuNewFriendRequestNoticeManager.kt (100%) delete mode 100644 src/main/kotlin/gg/essential/network/connectionmanager/profile/SuspensionDisconnectHandler.kt delete mode 100644 src/main/kotlin/gg/essential/network/connectionmanager/profile/profileManager.kt rename {gui/essential/src => src}/main/kotlin/gg/essential/network/connectionmanager/relationship/relationshipErrorResponse.kt (88%) rename {gui/essential/src => src}/main/kotlin/gg/essential/network/connectionmanager/relationship/relationshipResponse.kt (89%) delete mode 100644 src/main/kotlin/gg/essential/network/connectionmanager/suspension/McSuspensionManager.kt delete mode 100644 src/main/kotlin/gg/essential/network/connectionmanager/telemetry/FPSSessionMonitor.kt delete mode 100644 src/main/kotlin/gg/essential/network/connectionmanager/telemetry/FPSTelemetryManager.kt create mode 100644 src/main/kotlin/gg/essential/network/pingproxy/SPSUtils.kt rename {gui/essential/src => src}/main/kotlin/gg/essential/util/AddressUtil.kt (77%) delete mode 100644 subprojects/infra/src/main/java/gg/essential/connectionmanager/common/model/profile/ProfilePunishmentStatus.java delete mode 100644 subprojects/infra/src/main/java/gg/essential/connectionmanager/common/packet/chat/ServerChatChannelMessageRejectedPacket.java delete mode 100644 subprojects/infra/src/main/java/gg/essential/connectionmanager/common/packet/social/ClientCommunityRulesAgreedPacket.java delete mode 100644 subprojects/infra/src/main/java/gg/essential/connectionmanager/common/packet/social/ServerCommunityRulesStatePacket.java delete mode 100644 subprojects/infra/src/main/java/gg/essential/connectionmanager/common/packet/social/ServerSocialAllowedDomainsPacket.java delete mode 100644 subprojects/infra/src/main/java/gg/essential/connectionmanager/common/packet/social/ServerSocialSuspensionStatePacket.java delete mode 100644 subprojects/infra/src/main/kotlin/gg/essential/network/connectionmanager/common/model/profile/PunishmentType.kt delete mode 100644 versions/1.21.9-fabric/src/main/java/gg/essential/mixins/transformers/feature/skin_overwrites/PlayerEntityAccessor.java diff --git a/changelog/release-1.3.9.3.md b/changelog/release-1.3.9.3.md deleted file mode 100644 index e3f5d37d..00000000 --- a/changelog/release-1.3.9.3.md +++ /dev/null @@ -1,12 +0,0 @@ -Title: Bug Patch -Summary: Minor bug fixes - -## New Versions -- Added support for 1.21.10 Fabric - -## Bug Fixes -- Fixed buttons in Multiplayer menu not moving when the game window is resized on 1.21.9+ -- Fixed incorrect scrolling when cosmetic is clicked on preview player in Wardrobe -- Fixed incorrect sub-category being highlighted in Wardrobe -- Fixed Numpad Enter key not being recognized as Enter key -- Fixed equipped cape disappearing when Essential is disabled diff --git a/elementa/layoutdsl/src/main/kotlin/gg/essential/gui/common/stateExtensions.kt b/elementa/layoutdsl/src/main/kotlin/gg/essential/gui/common/stateExtensions.kt index 5c398be4..19c9c14c 100644 --- a/elementa/layoutdsl/src/main/kotlin/gg/essential/gui/common/stateExtensions.kt +++ b/elementa/layoutdsl/src/main/kotlin/gg/essential/gui/common/stateExtensions.kt @@ -14,12 +14,10 @@ package gg.essential.gui.common import gg.essential.elementa.state.State import gg.essential.elementa.state.v2.ReferenceHolder -@Deprecated("Use StateV2 instead") fun State.onSetValueAndNow(listener: (T) -> Unit) = onSetValue(listener).also { listener(get()) } @Deprecated("See `State.onSetValue`. Use `stateBy`/`effect` instead.") fun gg.essential.gui.elementa.state.v2.State.onSetValueAndNow(owner: ReferenceHolder, listener: (T) -> Unit) = onSetValue(owner, listener).also { listener(get()) } -@Deprecated("Use StateV2 instead") operator fun State.not() = map { !it } \ No newline at end of file diff --git a/elementa/layoutdsl/src/main/kotlin/gg/essential/gui/util/elementaExtensions.kt b/elementa/layoutdsl/src/main/kotlin/gg/essential/gui/util/elementaExtensions.kt index d63b9d74..dc052423 100644 --- a/elementa/layoutdsl/src/main/kotlin/gg/essential/gui/util/elementaExtensions.kt +++ b/elementa/layoutdsl/src/main/kotlin/gg/essential/gui/util/elementaExtensions.kt @@ -35,20 +35,12 @@ inline fun UIComponent.get() = inline fun UIComponent.getOrPut(init: () -> T) = get() ?: init().also { enableEffect(it) } -@Deprecated("Use StateV2 instead", ReplaceWith("pollingStateV2(initialValue, getter)")) fun UIComponent.pollingState(initialValue: T? = null, getter: () -> T): State { val state = BasicState(initialValue ?: getter()) addUpdateFunc { _, _ -> state.set(getter()) } return state } -/** - * Turns some impure computation into a pure [StateV2] by evaluating it before every frame. - * - * This should be avoided in favor of pure State-based computations where possible, but may be necessary when - * interfacing with third-party code. - * If the impure part of your computation is the current time, [Observer.systemTime] can likely replace this method. - */ fun UIComponent.pollingStateV2(initialValue: T? = null, getter: () -> T): StateV2 { val state = mutableStateOf(initialValue ?: getter()) addUpdateFunc { _, _ -> state.set(getter()) } @@ -232,7 +224,6 @@ fun UIComponent.hoverScopeV2(parentOnly: Boolean = false): StateV2 { return consumer.state } -@Deprecated("Use StateV2 instead", ReplaceWith("hoverScopeV2(parentOnly)")) fun UIComponent.hoverScope(parentOnly: Boolean = false): State = hoverScopeV2(parentOnly).toV1(this) @@ -369,7 +360,6 @@ fun UIComponent.isComponentInParentChain(target: UIComponent): Boolean { fun UIComponent.isInComponentTree(): Boolean = this is Window || hasParent && this in parent.children && parent.isInComponentTree() -@Deprecated("ObservableList should be replaced with StateV2's `ListState`") fun ObservableList.onItemRemoved(callback: (E) -> Unit) { addObserver { _, arg -> if (arg is ObservableRemoveEvent<*>) { @@ -378,7 +368,6 @@ fun ObservableList.onItemRemoved(callback: (E) -> Unit) { } } -@Deprecated("ObservableList should be replaced with StateV2's `ListState`") fun ObservableList.onItemAdded(callback: (E) -> Unit) { addObserver { _, arg -> if (arg is ObservableAddEvent<*>) { @@ -387,7 +376,6 @@ fun ObservableList.onItemAdded(callback: (E) -> Unit) { } } -@Deprecated("ObservableList should be replaced with StateV2's `ListState`") @Suppress("UNCHECKED_CAST") fun ObservableList.toStateV2List(): ListStateV2 { val stateList = mutableStateOf(MutableTrackedList(this.toMutableList())) diff --git a/elementa/layoutdsl/src/main/kotlin/gg/essential/gui/util/focusable.kt b/elementa/layoutdsl/src/main/kotlin/gg/essential/gui/util/focusable.kt index 84ca42d6..43baa5b8 100644 --- a/elementa/layoutdsl/src/main/kotlin/gg/essential/gui/util/focusable.kt +++ b/elementa/layoutdsl/src/main/kotlin/gg/essential/gui/util/focusable.kt @@ -67,7 +67,7 @@ private fun UIComponent.setupKeyboardNavigation(): UIComponent.(Char, Int) -> Un } when (keyCode) { - UKeyboard.KEY_ENTER, UKeyboard.KEY_NUMPADENTER -> simulateLeftClick() + UKeyboard.KEY_ENTER -> simulateLeftClick() UKeyboard.KEY_TAB -> passFocusToNextComponent(backwards = UKeyboard.isShiftKeyDown()) } } diff --git a/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/combinators/booleans.kt b/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/combinators/booleans.kt index 4b559769..6093a422 100644 --- a/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/combinators/booleans.kt +++ b/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/combinators/booleans.kt @@ -14,26 +14,12 @@ package gg.essential.gui.elementa.state.v2.combinators import gg.essential.gui.elementa.state.v2.MutableState import gg.essential.gui.elementa.state.v2.State -@Deprecated("Exists primarily for easier migration from State v1. Prefer using [State] lambda (with `memo` where necessary) instead.", - replaceWith = ReplaceWith("State { this() && other() }")) -@Suppress("DEPRECATION") infix fun State.and(other: State) = zip(other) { a, b -> a && b } -@Deprecated("Exists primarily for easier migration from State v1. Prefer using [State] lambda (with `memo` where necessary) instead.", - replaceWith = ReplaceWith("State { this() || other() }")) -@Suppress("DEPRECATION") infix fun State.or(other: State) = zip(other) { a, b -> a || b } -/** - * Creates a new [State] which has the inverse value of this [State]. - * - * This is mostly a convenience method so one can write `if_(!myState) {` instead of the more verbose - * `if_({ !myState() }) {`. Both are equivalent. - * This method shouldn't be overused though. If the expression is more complex, the verbose version is usually - * preferred because it generalizes much better. - */ -operator fun State.not() = letState { !it } +operator fun State.not() = map { !it } -operator fun MutableState.not() = bimapState({ !it }, { !it }) +operator fun MutableState.not() = bimap({ !it }, { !it }) diff --git a/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/combinators/state.kt b/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/combinators/state.kt index 5a6e575d..21f0335c 100644 --- a/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/combinators/state.kt +++ b/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/combinators/state.kt @@ -12,72 +12,27 @@ package gg.essential.gui.elementa.state.v2.combinators import gg.essential.gui.elementa.state.v2.MutableState -import gg.essential.gui.elementa.state.v2.Observer import gg.essential.gui.elementa.state.v2.State import gg.essential.gui.elementa.state.v2.memo /** Maps this state into a new state */ -@Deprecated("This method always applies `memo` even though it is often unnecessary. " + - "Use `letState` instead, and explicitly call `.memo()` on the result only where required.") fun State.map(mapper: (T) -> U): State { return memo { mapper(get()) } } -/** - * Derives a new [State] from this [State]. - * - * This method is equivalent to `.let { state -> State { block(state()) } }`. - * - * Note: For repeated or more complex derivations, an explicit [State] or [memo] lambda is likely easier to use. - */ -inline fun State.letState(crossinline block: Observer.(T) -> U): State { - val sourceState = this - return State { block(sourceState()) } -} - /** Maps this mutable state into a new mutable state. */ -@Deprecated("This method always applies `memo` even though it is often unnecessary. " + - "Use `bimapState` or `bimapMemo` instead.") fun MutableState.bimap(map: (T) -> U, unmap: (U) -> T): MutableState { - return bimapMemo(map, unmap) -} - -/** - * Derives a new [MutableState] from this [MutableState]. - * - * This variant uses [memo] internally. If this is not required, use [bimapState] instead. - */ -fun MutableState.bimapMemo(map: (T) -> U, unmap: (U) -> T): MutableState { - val sourceState = this - return object : MutableState, State by (memo { map(sourceState()) }) { + return object : MutableState, State by this.map(map) { override fun set(mapper: (U) -> U) { - sourceState.set { unmap(mapper(map(it))) } + this@bimap.set { unmap(mapper(map(it))) } } } } -/** - * Derives a new [MutableState] from this [MutableState]. - * - * @see [bimapMemo] - */ -fun MutableState.bimapState(map: (T) -> U, unmap: (U) -> T): MutableState { - val sourceState = this - return object : MutableState { - override fun Observer.get(): U = map(sourceState()) - override fun set(mapper: (U) -> U) = sourceState.set { unmap(mapper(map(it))) } - } -} - /** Zips this state with another state */ -@Deprecated("Exists primarily for easier migration from State v1. Prefer using [State] lambda (with `memo` where necessary) instead.", - replaceWith = ReplaceWith("State { Pair(this(), other()) }")) -@Suppress("DEPRECATION") fun State.zip(other: State): State> = zip(other, ::Pair) /** Zips this state with another state using [mapper] */ -@Deprecated("Exists primarily for easier migration from State v1. Prefer using [State] lambda (with `memo` where necessary) instead.", - replaceWith = ReplaceWith("State { mapper(this(), other()) }")) fun State.zip(other: State, mapper: (T, U) -> V): State { return memo { mapper(this@zip(), other()) } } diff --git a/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/combinators/strings.kt b/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/combinators/strings.kt index 63d2a8fe..07c86fd7 100644 --- a/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/combinators/strings.kt +++ b/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/combinators/strings.kt @@ -13,18 +13,9 @@ package gg.essential.gui.elementa.state.v2.combinators import gg.essential.gui.elementa.state.v2.State -@Deprecated("Exists primarily for easier migration from State v1. Prefer using [State] lambda (with `memo` where necessary) instead.", - replaceWith = ReplaceWith("State { this().contains(other(), ignoreCase = ignoreCase) }")) -@Suppress("DEPRECATION") fun State.contains(other: State, ignoreCase: Boolean = false) = zip(other) { a, b -> a.contains(b, ignoreCase) } -@Deprecated("Exists primarily for easier migration from State v1. Prefer using [State] lambda (with `memo` where necessary) instead.", - replaceWith = ReplaceWith("State { this().isEmpty() }")) -@Suppress("DEPRECATION") fun State.isEmpty() = map { it.isEmpty() } -@Deprecated("Exists primarily for easier migration from State v1. Prefer using [State] lambda (with `memo` where necessary) instead.", - replaceWith = ReplaceWith("State { this().isNotEmpty() }")) -@Suppress("DEPRECATION") fun State.isNotEmpty() = map { it.isNotEmpty() } diff --git a/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/combinators/utils.kt b/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/combinators/utils.kt index a1f1d6b1..b5176e64 100644 --- a/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/combinators/utils.kt +++ b/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/combinators/utils.kt @@ -14,4 +14,4 @@ package gg.essential.gui.elementa.state.v2.combinators import gg.essential.gui.elementa.state.v2.MutableState fun MutableState.reorder(vararg mapping: Int) = - bimapState({ mapping[it] }, { mapping.indexOf(it) }) + bimap({ mapping[it] }, { mapping.indexOf(it) }) diff --git a/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/impl/Impl.kt b/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/impl/Impl.kt index 2839c37e..9f0d8c39 100644 --- a/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/impl/Impl.kt +++ b/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/impl/Impl.kt @@ -25,7 +25,6 @@ internal interface Impl { fun memo(func: Observer.() -> T): State fun effect(referenceHolder: ReferenceHolder, func: Observer.() -> Unit): () -> Unit - @Suppress("DEPRECATION") fun stateDelegatingTo(state: State): DelegatingState = object : DelegatingState { private val target = mutableStateOf(state) @@ -33,7 +32,6 @@ internal interface Impl { override fun Observer.get(): T = target()() } - @Suppress("DEPRECATION") fun mutableStateDelegatingTo(state: MutableState): DelegatingMutableState = object : DelegatingMutableState { private val target = mutableStateOf(state) diff --git a/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/impl/basic/impl.kt b/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/impl/basic/impl.kt index 57ded051..f4065e2d 100644 --- a/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/impl/basic/impl.kt +++ b/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/impl/basic/impl.kt @@ -19,7 +19,6 @@ import gg.essential.gui.elementa.state.v2.State import gg.essential.gui.elementa.state.v2.impl.Impl import java.lang.ref.ReferenceQueue import java.lang.ref.WeakReference -import kotlin.collections.asSequence /** * Semi-lazy node graph implementation. @@ -126,13 +125,9 @@ private class Node( override val observerImpl: ObserverImpl get() = this - private val allDependencies = mutableListOf() - private val allDependents = mutableListOf() - - private val dependencies: Sequence> - get() = allDependencies.asSequence().filterNot { it.suspended }.map { it.dependency } - private val dependents: Sequence> - get() = allDependents.asSequence().filterNot { it.suspended }.mapNotNull { it.dependent } + private val observed = mutableSetOf>() + private val dependencies = mutableListOf>() + private val dependents: MutableList>> = mutableListOf() override fun Observer.get(): T { return getTracked(this@get) @@ -140,32 +135,9 @@ private class Node( fun getTracked(observer: Observer): T { val impl = observer.observerImpl - if (impl !is Node<*>) return getUntracked() - if (impl.state == NodeState.Dead) return getUntracked() - - val dependency = this - val dependent = impl - - // See if there's already an existing edge - // Any existing edge will be in both lists, so we can pick the smaller one to iterate. - val listA = dependency.allDependents - val listB = dependent.allDependencies - for (edge in if (listA.size < listB.size) listA else listB) { - if (edge.dependency == dependency && edge.dependent == dependent) { - edge.suspended = false // may need to re-enable the edge if it's currently suspended - return getUntracked() - } + if (impl is Node<*>) { + impl.observed.add(this) } - - // Create a new edge - val edge = Edge(dependency, dependent) - dependency.allDependents.add(edge) - dependent.allDependencies.add(edge) - - // To prevent unbounded growth, we'll clean up any stale edges whenever we add a new one - // (this is really fast in when there isn't anything to do thanks to the ReferenceQueue) - cleanupStaleReferences() - return getUntracked() } @@ -187,7 +159,7 @@ private class Node( value = newValue val update = Update.get() - for (dep in dependents) { + for (dep in dependents.iter()) { dep.markDirty(update) } update.flush() @@ -209,7 +181,7 @@ private class Node( private fun markDirty(update: Update) { mark(update, NodeState.Dirty) - for (dep in dependents) { + for (dep in dependents.iter()) { dep.markToBeChecked(update) } } @@ -219,7 +191,7 @@ private class Node( mark(update, NodeState.ToBeChecked) - for (dep in dependents) { + for (dep in dependents.iter()) { dep.markToBeChecked(update) } } @@ -239,29 +211,31 @@ private class Node( } if (state == NodeState.Dirty) { - for (edge in allDependencies) { - edge.suspended = true - } - val newValue = func(this) if (state == NodeState.Dead) { return } - allDependencies.removeIf { edge -> - if (edge.suspended) { - edge.dependency.allDependents.remove(edge) - true - } else { - false + for (i in dependencies.indices.reversed()) { + val dep = dependencies[i] + if (dep !in observed) { + dependencies.removeAt(i) + dep.removeDependent(this) } } + for (dep in observed) { + if (dep !in dependencies) { + dependencies.add(dep) + dep.addDependent(this) + } + } + observed.clear() if (value != newValue) { value = newValue - for (dep in dependents) { + for (dep in dependents.iter()) { dep.mark(update, NodeState.Dirty) } } @@ -271,13 +245,10 @@ private class Node( } fun cleanup() { - assert(kind == NodeKind.Effect) - assert(allDependents.isEmpty()) - - for (edge in allDependencies) { - edge.dependency.allDependents.remove(edge) + for (dep in dependencies) { + dep.removeDependent(this) } - allDependencies.clear() + dependencies.clear() state = NodeState.Dead } @@ -286,6 +257,18 @@ private class Node( private val referenceQueue: ReferenceQueue> get() = referenceQueueField ?: ReferenceQueue>().also { referenceQueueField = it } + private fun addDependent(node: Node<*>) { + cleanupStaleReferences() + dependents.add(WeakReference(node, referenceQueue)) + } + + private fun removeDependent(node: Node<*>) { + val index = dependents.indexOfFirst { it.get() == node } + if (index >= 0) { + dependents.removeAt(index) + } + } + private fun cleanupStaleReferences() { val queue = referenceQueueField ?: return @@ -296,33 +279,11 @@ private class Node( @Suppress("ControlFlowWithEmptyBody") while (queue.poll() != null); - allDependents.removeIf { it.dependent == null } + dependents.removeIf { it.get() == null } } - /** - * This class represents an edge in the dependency graph between one node ([dependent]) which depends on the value - * of another node ([dependency]). - * Both nodes keep a reference to the same [Edge] instance in their [Node.allDependencies] and [Node.allDependents] - * lists respectively. - * - * The [dependent] node of an edge is stored in a [WeakReference], such that it can be garbage collected if nothing - * else is keeping it alive any more. Once garbage collected, the edge becomes "stale" and will eventually be - * cleaned up from the list of the [dependency] node by [Node.cleanupStaleReferences]. - * - * An [Edge] may also become temporarily [suspended]. In this state, the nodes should act as if the edge did not - * exist. - * This is an optimization for when the [dependent] is re-evaluated, which would naively require invalidating - * all its dependencies (and removing all its edges from all lists) and then likely re-adding most of them as they - * are re-observed. With the [suspended] flag, we can simply mark all edges as suspended and don't need to modify - * the lists unless edges are actually removed. - */ - class Edge( - val dependency: Node<*>, - dependent: Node<*>, - var suspended: Boolean = false, - ) : WeakReference>(dependent, dependency.referenceQueue) { - val dependent: Node<*>? - get() = get() + private fun MutableList>>.iter(): Iterator> { + return asSequence().mapNotNull { it.get() }.iterator() } } diff --git a/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/impl/legacy/impl.kt b/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/impl/legacy/impl.kt index 7c5c2f56..d87b8c11 100644 --- a/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/impl/legacy/impl.kt +++ b/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/impl/legacy/impl.kt @@ -9,7 +9,6 @@ * commercialize, or otherwise exploit, or create derivative works based * upon, this file or any other in this repository, all of which is reserved by Essential. */ -@file:Suppress("DEPRECATION") package gg.essential.gui.elementa.state.v2.impl.legacy import gg.essential.config.AccessedViaReflection diff --git a/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/impl/minimal/impl.kt b/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/impl/minimal/impl.kt index 4dfa8efb..9e677808 100644 --- a/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/impl/minimal/impl.kt +++ b/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/impl/minimal/impl.kt @@ -118,13 +118,9 @@ private class Node( override val observerImpl: ObserverImpl get() = this - private val allDependencies = mutableListOf() - private val allDependents = mutableListOf() - - private val dependencies: Sequence> - get() = allDependencies.asSequence().filterNot { it.suspended }.map { it.dependency } - private val dependents: Sequence> - get() = allDependents.asSequence().filterNot { it.suspended }.mapNotNull { it.dependent } + private val observed = mutableSetOf>() + private val dependencies = mutableListOf>() + private val dependents: MutableList>> = mutableListOf() override fun Observer.get(): T { return getTracked(this@get) @@ -132,32 +128,9 @@ private class Node( fun getTracked(observer: Observer): T { val impl = observer.observerImpl - if (impl !is Node<*>) return getUntracked() - if (impl.state == NodeState.Dead) return getUntracked() - - val dependency = this - val dependent = impl - - // See if there's already an existing edge - // Any existing edge will be in both lists, so we can pick the smaller one to iterate. - val listA = dependency.allDependents - val listB = dependent.allDependencies - for (edge in if (listA.size < listB.size) listA else listB) { - if (edge.dependency == dependency && edge.dependent == dependent) { - edge.suspended = false // may need to re-enable the edge if it's currently suspended - return getUntracked() - } + if (impl is Node<*>) { + impl.observed.add(this) } - - // Create a new edge - val edge = Edge(dependency, dependent) - dependency.allDependents.add(edge) - dependent.allDependencies.add(edge) - - // To prevent unbounded growth, we'll clean up any stale edges whenever we add a new one - // (this is really fast in when there isn't anything to do thanks to the ReferenceQueue) - cleanupStaleReferences() - return getUntracked() } @@ -179,7 +152,7 @@ private class Node( value = newValue val update = Update.get() - for (dep in dependents) { + for (dep in dependents.iter()) { dep.markDirty(update) } update.flush() @@ -201,7 +174,7 @@ private class Node( private fun markDirty(update: Update) { mark(update, NodeState.Dirty) - for (dep in dependents) { + for (dep in dependents.iter()) { dep.markToBeChecked(update) } } @@ -211,7 +184,7 @@ private class Node( mark(update, NodeState.ToBeChecked) - for (dep in dependents) { + for (dep in dependents.iter()) { dep.markToBeChecked(update) } } @@ -231,29 +204,31 @@ private class Node( } if (state == NodeState.Dirty) { - for (edge in allDependencies) { - edge.suspended = true - } - val newValue = func(this) if (state == NodeState.Dead) { return } - allDependencies.removeIf { edge -> - if (edge.suspended) { - edge.dependency.allDependents.remove(edge) - true - } else { - false + for (i in dependencies.indices.reversed()) { + val dep = dependencies[i] + if (dep !in observed) { + dependencies.removeAt(i) + dep.removeDependent(this) } } + for (dep in observed) { + if (dep !in dependencies) { + dependencies.add(dep) + dep.addDependent(this) + } + } + observed.clear() if (value != newValue) { value = newValue - for (dep in dependents) { + for (dep in dependents.iter()) { dep.mark(update, NodeState.Dirty) } } @@ -263,13 +238,10 @@ private class Node( } fun cleanup() { - assert(kind == NodeKind.Effect) - assert(allDependents.isEmpty()) - - for (edge in allDependencies) { - edge.dependency.allDependents.remove(edge) + for (dep in dependencies) { + dep.removeDependent(this) } - allDependencies.clear() + dependencies.clear() state = NodeState.Dead } @@ -278,6 +250,18 @@ private class Node( private val referenceQueue: ReferenceQueue> get() = referenceQueueField ?: ReferenceQueue>().also { referenceQueueField = it } + private fun addDependent(node: Node<*>) { + cleanupStaleReferences() + dependents.add(WeakReference(node, referenceQueue)) + } + + private fun removeDependent(node: Node<*>) { + val index = dependents.indexOfFirst { it.get() == node } + if (index >= 0) { + dependents.removeAt(index) + } + } + private fun cleanupStaleReferences() { val queue = referenceQueueField ?: return @@ -288,34 +272,11 @@ private class Node( @Suppress("ControlFlowWithEmptyBody") while (queue.poll() != null); - allDependents.removeIf { it.dependent == null } + dependents.removeIf { it.get() == null } } - - /** - * This class represents an edge in the dependency graph between one node ([dependent]) which depends on the value - * of another node ([dependency]). - * Both nodes keep a reference to the same [Edge] instance in their [Node.allDependencies] and [Node.allDependents] - * lists respectively. - * - * The [dependent] node of an edge is stored in a [WeakReference], such that it can be garbage collected if nothing - * else is keeping it alive any more. Once garbage collected, the edge becomes "stale" and will eventually be - * cleaned up from the list of the [dependency] node by [Node.cleanupStaleReferences]. - * - * An [Edge] may also become temporarily [suspended]. In this state, the nodes should act as if the edge did not - * exist. - * This is an optimization for when the [dependent] is re-evaluated, which would naively require invalidating - * all its dependencies (and removing all its edges from all lists) and then likely re-adding most of them as they - * are re-observed. With the [suspended] flag, we can simply mark all edges as suspended and don't need to modify - * the lists unless edges are actually removed. - */ - class Edge( - val dependency: Node<*>, - dependent: Node<*>, - var suspended: Boolean = false, - ) : WeakReference>(dependent, dependency.referenceQueue) { - val dependent: Node<*>? - get() = get() + private fun MutableList>>.iter(): Iterator> { + return asSequence().mapNotNull { it.get() }.iterator() } } diff --git a/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/state.kt b/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/state.kt index f8ba15c8..07f2288f 100644 --- a/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/state.kt +++ b/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/state.kt @@ -26,6 +26,10 @@ interface ObserverImpl /** * A marker interface for an object which may observe which states are being accessed, such that it can then subscribe * to these states to be updated when they change. + * + * Note that the duration during which a given [Observer] can be used is usually limited to the call in which it was + * received. + * It should not be stored (neither in a field, nor implicitly in an asynchronous lambda) and then used at a later time. */ interface Observer { val observerImpl: ObserverImpl @@ -247,13 +251,11 @@ interface MutableState : State { } /** A [State] delegating to a configurable target [State] */ -@Deprecated("This no longer needs to be a primitive. It is basically just a less-flexible `MutableState>`.") interface DelegatingState : State { fun rebind(newState: State) } /** A [MutableState] delegating to a configurable target [MutableState] */ -@Deprecated("This no longer needs to be a primitive. It is basically just a less-flexible `MutableState>`.") @JvmDefaultWithoutCompatibility interface DelegatingMutableState : MutableState { fun rebind(newState: MutableState) @@ -266,15 +268,9 @@ fun stateOf(value: T): State = ImmutableState(value) fun mutableStateOf(value: T): MutableState = impl.mutableState(value) /** Creates a new [DelegatingState] with the given target [State]. */ -@Deprecated("This no longer needs to be a primitive. It is basically just a less-flexible `MutableState>`.", - replaceWith = ReplaceWith("val myStateSource = mutableStateOf(state)\nval myState = myStateSource.flatten()")) -@Suppress("DEPRECATION") fun stateDelegatingTo(state: State): DelegatingState = impl.stateDelegatingTo(state) /** Creates a new [DelegatingMutableState] with the given target [MutableState]. */ -@Deprecated("This no longer needs to be a primitive. It is basically just a less-flexible `MutableState>`.", - replaceWith = ReplaceWith("val myStateSource = mutableStateOf(state)\nval myState = myStateSource.flatten()")) -@Suppress("DEPRECATION") fun mutableStateDelegatingTo(state: MutableState): DelegatingMutableState = impl.mutableStateDelegatingTo(state) diff --git a/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/time.kt b/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/time.kt index 59f14cd8..81cc9263 100644 --- a/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/time.kt +++ b/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/time.kt @@ -117,9 +117,9 @@ class StateScheduler(val time: State) { * Returns the time of the given [scheduler] as an [ObservedInstant] which will track operations applied to it and * subscribe the [Observer] to be re-evaluated when the result of any of these operations changes. * - * Note that the [ObservedInstant] or any [ObservedValue] derived from it should usually not become the value of the - * [State] because they do not implement [equals], only concrete types, e.g. as returned by [ObservedValue.getValue], - * should. + * Note that the [ObservedInstant] wraps the [Observer] and as such the same life-time restrictions apply to it. + * In particular that means that the [ObservedInstant] or any [ObservedValue] derived from it MUST NOT become the + * value of the [State], only concrete types, e.g. as returned by [ObservedValue.getValue], may. * * When performing more complex operations on the returned value, using [withSystemTime] may be more efficient. */ @@ -130,9 +130,9 @@ fun Observer.systemTime(scheduler: StateScheduler = StateScheduler.forSystemTime * Runs the given [block] with the time of the given [scheduler] as an [ObservedInstant] which will track operations * applied to it and subscribe the [Observer] to be re-evaluated when the result of any of these operations changes. * - * Note that the [ObservedInstant] or any [ObservedValue] derived from it should usually not become the value of the - * [State] because they do not implement [equals], only concrete types, e.g. as returned by [ObservedValue.getValue], - * should. + * Note that the [ObservedInstant] wraps the [Observer] and as such the same life-time restrictions apply to it. + * In particular that means that the [ObservedInstant] or any [ObservedValue] derived from it MUST NOT become the + * value of the [State], only concrete types, e.g. as returned by [ObservedValue.getValue], may. */ fun Observer.withSystemTime( scheduler: StateScheduler = StateScheduler.forSystemTime, diff --git a/elementa/statev2/src/test/kotlin/gg/essential/gui/elementa/state/v2/EffectTest.kt b/elementa/statev2/src/test/kotlin/gg/essential/gui/elementa/state/v2/EffectTest.kt deleted file mode 100644 index a32d3820..00000000 --- a/elementa/statev2/src/test/kotlin/gg/essential/gui/elementa/state/v2/EffectTest.kt +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.gui.elementa.state.v2 - -import gg.essential.elementa.state.v2.ReferenceHolder -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.time.Duration.Companion.seconds -import kotlin.time.TimeSource - -class EffectTest { - @Test - fun testEmptyEffect() { - var ran = 0 - effect(ReferenceHolder.Weak) { - ran++ - } - assertEquals(1, ran) - } - - @Test - fun testSimpleEffect() { - val state = mutableStateOf(0) - - var expecting: Int? - - expecting = 0 - val unregister = effect(ReferenceHolder.Weak) { - assertEquals(expecting, state()) - expecting = null // we're only expecting a single call per change - } - assertEquals(expecting, null, "Effect should have ran") - - expecting = 1 - state.set(1) - assertEquals(expecting, null, "Effect should have ran") - - expecting = 2 - state.set(2) - assertEquals(expecting, null, "Effect should have ran") - - unregister() - - expecting = null - state.set(3) - state.set(4) - state.set(5) - } - - @Test - fun testEffectWithMultipleAndChangingDependencies() { - val aState = mutableStateOf(0) - val bState = mutableStateOf(2) - val outerState = mutableStateOf(aState) - - var expecting: Int? - - expecting = 0 - val unregister = effect(ReferenceHolder.Weak) { - assertEquals(expecting, outerState()()) - expecting = null // we're only expecting a single call per change - } - assertEquals(expecting, null, "Effect should have ran") - - expecting = 1 - aState.set(1) - assertEquals(expecting, null, "Effect should have ran") - - expecting = 2 - outerState.set(bState) - assertEquals(expecting, null, "Effect should have ran") - - // Should no longer depend on aState - expecting = null - aState.set(42) - - expecting = 3 - bState.set(3) - assertEquals(expecting, null, "Effect should have ran") - - unregister() - - expecting = null - outerState.set(aState) - aState.set(123) - bState.set(123) - } - - @Test - fun testEffectWithDelayedDependency() { - val state = mutableStateOf(0) - - var ran = 0 - lateinit var observer: Observer - val unregister = effect(ReferenceHolder.Weak) { - ran++ - observer = this@effect - } - assertEquals(1, ran) - - with(observer) { assertEquals(0, state()) } - - state.set(1) - assertEquals(2, ran, "Effect should have ran") - - state.set(2) - assertEquals(2, ran, "Effect should not have ran, we haven't re-subscribed") - - with(observer) { assertEquals(2, state()) } - - state.set(3) - assertEquals(3, ran, "Effect should have ran") - - unregister() - - with(observer) { assertEquals(3, state()) } - - state.set(4) - assertEquals(3, ran, "Effect should not have ran, it had been unregistered") - } - - @Test - fun testEffectGarbageCollection() { - val state = mutableStateOf(0) - - // Testing whether effects are garbage collected properly isn't exactly easy due to Java not providing - // any way to **force** garbage collections. So we'll just create effects until it has no choice. - var createdEffects = 0 - var liveEffects = 0 - - // Hold on to effects for a while so the JVM can't just optimize away the whole `effect` call - val heldEffects = mutableListOf() - - val startTime = TimeSource.Monotonic.markNow() - while (true) { - createdEffects++ - heldEffects.add(effect(ReferenceHolder.Weak) { - state() - liveEffects++ - }) - - if (heldEffects.size > 100) { - heldEffects.removeFirst() // notably explicitly not invoking the unregister function here - System.gc() // beg the JVM to do GC, there's go guarantee this works - } - - liveEffects = 0 - state.set { it + 1 } - assert(liveEffects <= createdEffects) - if (liveEffects < createdEffects) { - println("Finished after creating $createdEffects effects, with only $liveEffects still live") - return - } - - val timeout = 5.seconds - if (startTime.elapsedNow() > timeout) { - throw AssertionError("Created $createdEffects effects over $timeout but not one was GCed") - } - } - } -} diff --git a/elementa/statev2/src/test/kotlin/gg/essential/gui/elementa/state/v2/MemoTest.kt b/elementa/statev2/src/test/kotlin/gg/essential/gui/elementa/state/v2/MemoTest.kt deleted file mode 100644 index e2380bdd..00000000 --- a/elementa/statev2/src/test/kotlin/gg/essential/gui/elementa/state/v2/MemoTest.kt +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.gui.elementa.state.v2 - -import gg.essential.elementa.state.v2.ReferenceHolder -import kotlin.test.Test -import kotlin.test.assertEquals - -class MemoTest { - @Test - fun testEmptyMemo() { - var ran = 0 - val state = memo { - ran++ - "value" - } - assertEquals(0, ran, "memo should be lazy") - assertEquals("value", state.getUntracked()) - assertEquals(1, ran, "memo should only be evaluated once") - assertEquals("value", state.getUntracked()) - assertEquals(1, ran, "memo result should be memoized") - } - - @Test - fun testSimpleMemo() { - val state = mutableStateOf(0) - - var ran = 0 - val memoState = memo { - ran++ - state() + 1 - } - assertEquals(0, ran, "memo should be lazy") - assertEquals(1, memoState.getUntracked()) - assertEquals(1, ran, "memo should only be evaluated once") - assertEquals(1, memoState.getUntracked()) - assertEquals(1, ran, "memo result should be memoized") - - state.set(1) - // Ideally we wouldn't run the computation if no one is subscribed, current implementation does though - // assertEquals(1, ran, "memo should not be updated if no one is subscribed") - assertEquals(2, memoState.getUntracked()) - assertEquals(2, ran, "memo should only be evaluated once") - assertEquals(2, memoState.getUntracked()) - assertEquals(2, ran, "memo result should be memoized") - - state.set(2) - // Ideally we wouldn't run the computation if no one is subscribed, current implementation does though - // assertEquals(2, ran, "memo should not be updated if no one is subscribed") - assertEquals(3, memoState.getUntracked()) - assertEquals(3, ran, "memo should only be evaluated once") - assertEquals(3, memoState.getUntracked()) - assertEquals(3, ran, "memo result should be memoized") - } - - @Test - fun testSimpleMemoWithEffect() { - val state = mutableStateOf(Pair(0, false)) - val memoState = memo { state().first } - - var expecting: Int? - - expecting = 0 - val unregister = effect(ReferenceHolder.Weak) { - assertEquals(expecting, memoState()) - expecting = null // we're only expecting a single call per change - } - assertEquals(expecting, null, "Effect should have ran") - - expecting = 1 - state.set(Pair(1, false)) - assertEquals(expecting, null, "Effect should have ran") - - expecting = null // effect shouldn't run because the memo should produce the same result as before - state.set(Pair(1, true)) - - expecting = 2 - state.set(Pair(2, false)) - assertEquals(expecting, null, "Effect should have ran") - - unregister() - - expecting = null - state.set(Pair(3, false)) - state.set(Pair(4, false)) - state.set(Pair(5, false)) - assertEquals(5, memoState.getUntracked()) - } - - @Test - fun testMemoWithEffectAndDiamondDependency() { - val state = mutableStateOf(0) - val a = memo { state() + 1 } - val b = memo { state() + 2 } - val c = memo { - val aVal = a() - val bVal = b() - assert(aVal + 1 == bVal) - aVal + bVal - } - - var expecting: Int? - - expecting = 3 - val unregister = effect(ReferenceHolder.Weak) { - assertEquals(expecting, c()) - expecting = null // we're only expecting a single call per change - } - assertEquals(expecting, null, "Effect should have ran") - - expecting = 5 - state.set(1) - assertEquals(expecting, null, "Effect should have ran") - - expecting = 7 - state.set(2) - assertEquals(expecting, null, "Effect should have ran") - - unregister() - - expecting = null - state.set(3) - state.set(4) - state.set(5) - assertEquals(13, c.getUntracked()) - assertEquals(7, b.getUntracked()) - assertEquals(6, a.getUntracked()) - } - - @Test - fun testMemoWithDelayedDependency() { - val state = mutableStateOf(0) - - val memo = memo { lazy { state() } } - - var ran = 0 - lateinit var lazyValue: Lazy - val unregister = effect(ReferenceHolder.Weak) { - ran++ - lazyValue = memo() - } - assertEquals(1, ran) - - assertEquals(0, lazyValue.value) - assertEquals(1, ran, "Merely observing shouldn't change the result of the memo") - - state.set(1) - assertEquals(2, ran, "Effect should have ran") - - state.set(2) - assertEquals(2, ran, "Effect should not have ran, we haven't re-subscribed") - - assertEquals(2, lazyValue.value) - - state.set(3) - assertEquals(3, ran, "Effect should have ran") - - unregister() - - assertEquals(3, lazyValue.value) - - state.set(4) - assertEquals(3, ran, "Effect should not have ran, it had been unregistered") - } -} diff --git a/elementa/statev2/src/test/kotlin/gg/essential/gui/elementa/state/v2/MutableStateTest.kt b/elementa/statev2/src/test/kotlin/gg/essential/gui/elementa/state/v2/MutableStateTest.kt deleted file mode 100644 index 8de47b4b..00000000 --- a/elementa/statev2/src/test/kotlin/gg/essential/gui/elementa/state/v2/MutableStateTest.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.gui.elementa.state.v2 - -import kotlin.test.Test -import kotlin.test.assertEquals - -class MutableStateTest { - @Test - fun testMutableState() { - val state = mutableStateOf(0) - assertEquals(0, state.getUntracked()) - state.set(1) - assertEquals(1, state.getUntracked()) - state.set { it + 1 } - assertEquals(2, state.getUntracked()) - } -} diff --git a/features.properties b/features.properties index a660e58c..b9fdba5e 100644 --- a/features.properties +++ b/features.properties @@ -3,4 +3,3 @@ server_discovery=true updated_gifting_modal=true updated_coins_purchase_modal=true modal_update=true -suspensions_and_chat_filtering=true diff --git a/gradle.properties b/gradle.properties index 5d91a06b..f41de7e7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,4 +10,4 @@ minecraftVersion=11202 # TODO remove once upgrading to Loom 1.10 # fabric-api 1.21.5 was built with Loom 1.10, seems to work well enough in dev with our current 1.7 though loom.ignoreDependencyLoomVersionValidation=true -version=1.3.9.3 +version=1.3.9.2 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b1fc1ef6..756a9333 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -universalcraft = "431" +universalcraft = "430" elementa = "708" vigilance = "306" mixinextras = "0.4.0" diff --git a/gui/elementa/src/main/kotlin/gg/essential/gui/common/extensions.kt b/gui/elementa/src/main/kotlin/gg/essential/gui/common/extensions.kt index 8f8156d9..fdf7df91 100644 --- a/gui/elementa/src/main/kotlin/gg/essential/gui/common/extensions.kt +++ b/gui/elementa/src/main/kotlin/gg/essential/gui/common/extensions.kt @@ -24,25 +24,23 @@ import gg.essential.elementa.utils.ObservableClearEvent import gg.essential.elementa.utils.ObservableList import gg.essential.elementa.utils.ObservableRemoveEvent import gg.essential.gui.elementa.state.v2.effect +import gg.essential.gui.elementa.state.v2.toV1 import gg.essential.gui.elementa.state.v2.toV2 import kotlin.reflect.KProperty import gg.essential.gui.elementa.state.v2.State as StateV2 @Deprecated("Using StateV2 instead") -@Suppress("DEPRECATION") fun T.bindConstraints(state: State, config: UIConstraints.(S) -> Unit) = bindConstraints(state.toV2(), config) -@Deprecated("Use the `Modifier` system instead. Or where not possible, just write an `effect` yourself.") -fun T.bindConstraints(state: StateV2, config: UIConstraints.(S) -> Unit) = apply { +fun T.bindConstraints(state: gg.essential.gui.elementa.state.v2.State, config: UIConstraints.(S) -> Unit) = apply { effect(this) { constraints.config(state()) } } @Deprecated("Use StateV2 version instead") -@Suppress("DEPRECATION") fun T.bindParent( parent: UIComponent, state: State, @@ -50,8 +48,6 @@ fun T.bindParent( index: Int? = null ) = bindParent(parent, state.toV2(), delayed, index) -@Deprecated("Use LayoutDSL instead") -@Suppress("DEPRECATION") fun T.bindParent( parent: UIComponent, state: StateV2, @@ -59,39 +55,32 @@ fun T.bindParent( index: Int? = null ) = bindParent({ if (state()) parent else null }, delayed, index) -@Deprecated("Use StateV2 version instead") -@Suppress("DEPRECATION") -fun T.bindEffect(effect: Effect, state: State, delayed: Boolean = true) = - bindEffect(effect, state.toV2(), delayed) - -@Deprecated("Use the `Modifier` system instead.", - replaceWith = ReplaceWith("Modifier.whenTrue(state, Modifier.effect { MyEffect() })")) -fun T.bindEffect(effect: Effect, state: StateV2, delayed: Boolean = true) = apply { - effect(this) { - fun update(toggle: Boolean) { - if (toggle) { - this@apply.effect(effect) +fun T.bindEffect(effect: Effect, state: State, delayed: Boolean = true) = apply { + state.onSetValueAndNow { + val update = { + if (it) { + this.effect(effect) } else { - this@apply.removeEffect(effect) + this.removeEffect(effect) } } - val toggleState = state() if (delayed) { Window.enqueueRenderOperation { - update(toggleState) + update() } } else { - update(toggleState) + update() } } } +fun T.bindEffect(effect: Effect, state: gg.essential.gui.elementa.state.v2.State, delayed: Boolean = true) = + bindEffect(effect, state.toV1(this), delayed) + @Deprecated("Use StateV2 version instead") -@Suppress("DEPRECATION") fun T.bindParent(state: State, delayed: Boolean = false, index: Int? = null) = bindParent(state.toV2(), delayed, index) -@Deprecated("Use LayoutDSL instead") fun T.bindParent(state: StateV2, delayed: Boolean = false, index: Int? = null) = apply { effect(this) { val parent = state() @@ -117,14 +106,10 @@ fun T.bindParent(state: StateV2, delayed: Boolea } } -@Deprecated("Use StateV2 instead", ReplaceWith("State { this().isBlank() }")) fun State.empty() = map { it.isBlank() } -@Deprecated("Use StateV2 instead", ReplaceWith("State { this() && other() }")) infix fun State.and(other: State) = zip(other).map { (a, b) -> a && b } -@Deprecated("Use StateV2 instead", ReplaceWith("State { this() || other() }")) infix fun State.or(other: State) = zip(other).map { (a, b) -> a || b } -@Deprecated("Use StateV2 instead, where `State` is read-only by default and a separate `MutableState` interface exists") class ReadOnlyState(private val internalState: State) : State() { init { @@ -144,16 +129,14 @@ class ReadOnlyState(private val internalState: State) : State() { } } -@Deprecated("This makes it too easy to accidentally read a state without observing it. Explicitly use `getUntracked` where this is desired.") operator fun State.getValue(obj: Any, property: KProperty<*>): T = get() -@Deprecated("The `getValue` operator function is deprecated and `setValue` cannot be used without it.") operator fun State.setValue(obj: Any, property: KProperty<*>, value: T) = set(value) -@Deprecated("Use StateV2 instead", ReplaceWith("stateOf(this)")) +fun State.mapToString() = this.map { it.toString() } + fun T.state() = BasicState(this) -@Deprecated("Use StateV2 for filtering and sorting, and LayoutDSL's `forEach` for binding instead") fun T.bindChildren( list: ObservableList, filter: (E) -> Boolean = { true }, @@ -226,7 +209,6 @@ fun T.bindChildren( * of type [V] using the given [mapper] function. * */ -@Deprecated("Use StateV2's `ListState` instead") fun ObservableList.map( mapper: (E) -> V, ): ObservableList { diff --git a/gui/essential/src/main/java/gg/essential/event/network/server/ServerTickEvent.java b/gui/essential/src/main/java/gg/essential/event/network/server/ServerTickEvent.java deleted file mode 100644 index 1bcf4d3d..00000000 --- a/gui/essential/src/main/java/gg/essential/event/network/server/ServerTickEvent.java +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.event.network.server; - -public class ServerTickEvent { -} diff --git a/gui/essential/src/main/java/gg/essential/handlers/MojangSkinManager.java b/gui/essential/src/main/java/gg/essential/handlers/MojangSkinManager.java index 7935e6f6..1107c05f 100644 --- a/gui/essential/src/main/java/gg/essential/handlers/MojangSkinManager.java +++ b/gui/essential/src/main/java/gg/essential/handlers/MojangSkinManager.java @@ -107,7 +107,7 @@ public void flushChanges(boolean notification) { } @Nullable - protected synchronized Skin updateSkinNow(boolean notification, boolean userSet) { + private synchronized Skin updateSkinNow(boolean notification, boolean userSet) { SkinUpdate queuedSkinChange = this.queuedSkinChange; this.queuedSkinChange = null; if (queuedSkinChange == null) return null; diff --git a/gui/essential/src/main/kotlin/gg/essential/config/EssentialConfig.kt b/gui/essential/src/main/kotlin/gg/essential/config/EssentialConfig.kt index 6d03f5af..5a35b80f 100644 --- a/gui/essential/src/main/kotlin/gg/essential/config/EssentialConfig.kt +++ b/gui/essential/src/main/kotlin/gg/essential/config/EssentialConfig.kt @@ -265,8 +265,6 @@ object EssentialConfig : Vigilant2(), GuiEssentialPlatform.Config { val showOwnNametag = property("Quality of Life.Nameplate.Show my nameplate in third-person", true) - val acknowledgedPermanentSuspension = property("Hidden.acknowledged_permanent_suspension", false) - override val migrations = listOf( Migration { config -> val overrideGuiScale = config.remove("general.general.gui_scale") as Boolean? ?: return@Migration diff --git a/gui/essential/src/main/kotlin/gg/essential/cosmetics/IngameEquippedOutfitsManager.kt b/gui/essential/src/main/kotlin/gg/essential/cosmetics/IngameEquippedOutfitsManager.kt index 90775a09..b923efc0 100644 --- a/gui/essential/src/main/kotlin/gg/essential/cosmetics/IngameEquippedOutfitsManager.kt +++ b/gui/essential/src/main/kotlin/gg/essential/cosmetics/IngameEquippedOutfitsManager.kt @@ -50,9 +50,6 @@ class IngameEquippedOutfitsManager( override fun getSkin(playerId: UUID): Skin? = managerImpl.getSkin(playerId) - override fun getCapeHash(playerId: UUID): String? = - managerImpl.getCapeHash(playerId) - fun applyUpdates(list: List>>) { for ((uuid, updates) in list) { applyUpdates(uuid, updates) diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/EssentialPalette.kt b/gui/essential/src/main/kotlin/gg/essential/gui/EssentialPalette.kt index 408925d4..83223a03 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/EssentialPalette.kt +++ b/gui/essential/src/main/kotlin/gg/essential/gui/EssentialPalette.kt @@ -501,9 +501,7 @@ object EssentialPalette { fun getMessageColor(hovered: State, sentByClient: Boolean, sendState: SendState): State { return hovered.map { - if (sendState is SendState.Blocked) { - COMPONENT_BACKGROUND - } else if (it) { + if (it) { if (sentByClient) { MESSAGE_SENT_BACKGROUND_HOVER } else { diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/common/RadioButton.kt b/gui/essential/src/main/kotlin/gg/essential/gui/common/RadioButton.kt deleted file mode 100644 index df92c776..00000000 --- a/gui/essential/src/main/kotlin/gg/essential/gui/common/RadioButton.kt +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.gui.common - -import gg.essential.elementa.UIComponent -import gg.essential.elementa.components.UIBlock.Companion.drawBlock -import gg.essential.gui.EssentialPalette -import gg.essential.gui.elementa.state.v2.MutableState -import gg.essential.gui.elementa.state.v2.combinators.bimap -import gg.essential.gui.layoutdsl.LayoutScope -import gg.essential.gui.layoutdsl.Modifier -import gg.essential.gui.layoutdsl.color -import gg.essential.gui.layoutdsl.height -import gg.essential.gui.layoutdsl.hoverScope -import gg.essential.gui.layoutdsl.onLeftClick -import gg.essential.gui.layoutdsl.width -import gg.essential.universal.UMatrixStack -import gg.essential.universal.USound -import java.awt.Color - -class RadioButton( - private val state: MutableState, - private val selectedColor: Color = EssentialPalette.COMPONENT_SELECTED_OUTLINE -) : UIComponent() { - - init { - Modifier.color(EssentialPalette.TEXT).width(7f).height(7f).hoverScope().onLeftClick { click -> - if (!state.getUntracked()) { - USound.playButtonPress() - state.set(true) - } - click.stopPropagation() - }.applyToComponent(this) - } - - override fun draw(matrixStack: UMatrixStack) { - beforeDraw(matrixStack) - - val x = getLeft().toDouble() - val y = getTop().toDouble() - - matrixStack.push() - matrixStack.translate(1f, 1f, 0f) - drawInner(matrixStack, EssentialPalette.BLACK, x, y) - matrixStack.pop() - - drawInner(matrixStack, if (state.getUntracked()) selectedColor else getColor(), x, y) - - super.draw(matrixStack) - } - - private fun drawInner(matrixStack: UMatrixStack, color: Color, x: Double, y: Double) { - drawBlock(matrixStack, color, x, y + 2, x + 1, y + 5) - drawBlock(matrixStack, color, x + 1, y + 1, x + 2, y + 2) - drawBlock(matrixStack, color, x + 1, y + 5, x + 2, y + 6) - drawBlock(matrixStack, color, x + 2, y, x + 5, y + 1) - drawBlock(matrixStack, color, x + 2, y + 6, x + 5, y + 7) - drawBlock(matrixStack, color, x + 5, y + 1, x + 6, y + 2) - drawBlock(matrixStack, color, x + 5, y + 5, x + 6, y + 6) - drawBlock(matrixStack, color, x + 6, y + 2, x + 7, y + 5) - if (state.getUntracked()) { - drawBlock(matrixStack, color, x + 2, y + 2, x + 5, y + 5) - } - } -} - -fun LayoutScope.radioButton( - value: T, - group: MutableState, - modifier: Modifier = Modifier, - selectedColor: Color = EssentialPalette.COMPONENT_SELECTED_OUTLINE -) { - radioButton(group.bimap({ it == value }, { value }), modifier, selectedColor) -} - -fun LayoutScope.radioButton( - state: MutableState, - modifier: Modifier = Modifier, - selectedColor: Color = EssentialPalette.COMPONENT_SELECTED_OUTLINE, -) { - RadioButton(state, selectedColor = selectedColor)(modifier) -} \ No newline at end of file diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/common/Tooltips.kt b/gui/essential/src/main/kotlin/gg/essential/gui/common/Tooltips.kt index 9e62aea4..86ecfe4b 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/common/Tooltips.kt +++ b/gui/essential/src/main/kotlin/gg/essential/gui/common/Tooltips.kt @@ -52,8 +52,9 @@ abstract class AbstractTooltip(private val logicalParent: UIComponent) : UIConta } } - effect(logicalParent) { - toggle(visible()) + toggle(visible.get()) + visible.onSetValue(logicalParent) { + toggle(it) } return this @@ -193,9 +194,9 @@ abstract class Tooltip(logicalParent: UIComponent) : AbstractTooltip(logicalPare // Old name of bindText, contrary to its name it actually supports multiple lines and you cannot call it multiple times to add more lines fun bindLine(state: StateV2, wrapAtWidth: Float? = null, configure: UIText.() -> Unit = {}): Tooltip { - effect(this) { + state.onSetValueAndNow(this) { text -> clearLines() - state().lines().forEach { fullLine -> + text.lines().forEach { fullLine -> if (wrapAtWidth != null) { val lines = getStringSplitToWidth(fullLine, wrapAtWidth, 1f, processColorCodes = false) for (line in lines) { diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/common/input/AbstractTextInput.kt b/gui/essential/src/main/kotlin/gg/essential/gui/common/input/AbstractTextInput.kt index eacdeabe..a9e45ac8 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/common/input/AbstractTextInput.kt +++ b/gui/essential/src/main/kotlin/gg/essential/gui/common/input/AbstractTextInput.kt @@ -18,11 +18,10 @@ import gg.essential.elementa.constraints.* import gg.essential.elementa.constraints.animation.* import gg.essential.elementa.dsl.* import gg.essential.elementa.effects.ScissorEffect +import gg.essential.elementa.state.BasicState import gg.essential.elementa.utils.getStringSplitToWidth import gg.essential.gui.EssentialPalette import gg.essential.gui.common.ContextOptionMenu -import gg.essential.gui.elementa.state.v2.effect -import gg.essential.gui.elementa.state.v2.mutableStateOf import gg.essential.universal.UDesktop import gg.essential.universal.UKeyboard import gg.essential.universal.UMatrixStack @@ -43,9 +42,9 @@ abstract class AbstractTextInput( var maxLength: Int = Int.MAX_VALUE, // Note, this is not enforced when updated ) : UIComponent() { - val placeholderColor = mutableStateOf(EssentialPalette.TEXT) - val placeholderShadow = mutableStateOf(true) - val textState = mutableStateOf("") + val placeholderColor = BasicState(EssentialPalette.TEXT) + val placeholderShadow = BasicState(true) + val textState = BasicState("") // Allows all characters when empty val allowedCharacters: MutableSet = mutableSetOf() @@ -248,7 +247,7 @@ abstract class AbstractTextInput( setCursorPosition(it) } } - } else if (UKeyboard.isEnterKey(keyCode)) { // Enter + } else if (keyCode == UKeyboard.KEY_ENTER) { // Enter onEnterPressed() } else if (keyCode == UKeyboard.KEY_MENU) { val (posX, posY) = cursor.toScreenPos() @@ -414,10 +413,9 @@ abstract class AbstractTextInput( enableEffect(ScissorEffect()) - effect(this) { - val text = textState() - if (text != getText()) { - setText(text) + textState.onSetValue { + if (it != getText()) { + setText(it) } } } @@ -504,6 +502,10 @@ abstract class AbstractTextInput( updateAction = listener } + fun onActivate(listener: (text: String) -> Unit) = apply { + activateAction = listener + } + protected open fun commitTextOperation(operation: TextOperation) { // Apply the operation, get the updated text operation.redo() @@ -819,7 +821,7 @@ abstract class AbstractTextInput( drawUnselectedText(UMatrixStack.Compat.get(), text, left, row) protected open fun drawPlaceholder(matrixStack: UMatrixStack) { - drawUnselectedText(matrixStack, placeholder, getLeft(), 0, placeholderColor.getUntracked(), placeholderShadow.getUntracked()) + drawUnselectedText(matrixStack, placeholder, getLeft(), 0, placeholderColor.get(), placeholderShadow.get()) } protected open fun drawUnselectedText( diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/common/input/StateTextInput.kt b/gui/essential/src/main/kotlin/gg/essential/gui/common/input/StateTextInput.kt index 678f35db..9e8e9d91 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/common/input/StateTextInput.kt +++ b/gui/essential/src/main/kotlin/gg/essential/gui/common/input/StateTextInput.kt @@ -15,6 +15,7 @@ import gg.essential.elementa.constraints.animation.* import gg.essential.elementa.dsl.* import gg.essential.gui.EssentialPalette import gg.essential.gui.common.input.StateTextInput.* +import gg.essential.gui.common.onSetValueAndNow import gg.essential.gui.elementa.state.v2.* import gg.essential.vigilance.utils.onLeftClick import org.jetbrains.annotations.ApiStatus @@ -51,8 +52,8 @@ class StateTextInput( cloneStateToInput() } } - effect(this) { - setText(formatToText(state())) + state.onSetValueAndNow(this) { + cloneStateToInput() } } @@ -60,7 +61,7 @@ class StateTextInput( * Sets the value of the input to the current value of the state */ private fun cloneStateToInput() { - setText(formatToText(state.getUntracked())) + setText(formatToText(state.get())) } override fun onEnterPressed() { diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/common/input/UIMultilineTextInput.kt b/gui/essential/src/main/kotlin/gg/essential/gui/common/input/UIMultilineTextInput.kt index 37a4751f..16878a6e 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/common/input/UIMultilineTextInput.kt +++ b/gui/essential/src/main/kotlin/gg/essential/gui/common/input/UIMultilineTextInput.kt @@ -47,6 +47,10 @@ class UIMultilineTextInput @JvmOverloads constructor( this.maxHeight = maxHeight } + fun setMaxLines(maxLines: Int) = apply { + this.maxHeight = (lineHeightWithPadding * maxLines - linePadding).pixels() + } + override fun getText() = textualLines.joinToString("\n") { it.text } override fun textToLines(text: String): List { diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/common/input/essentialInput.kt b/gui/essential/src/main/kotlin/gg/essential/gui/common/input/essentialInput.kt index 07093576..5bdabeb9 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/common/input/essentialInput.kt +++ b/gui/essential/src/main/kotlin/gg/essential/gui/common/input/essentialInput.kt @@ -18,8 +18,10 @@ import gg.essential.elementa.utils.invisible import gg.essential.gui.EssentialPalette import gg.essential.gui.common.EssentialTooltip import gg.essential.gui.common.IconButton +import gg.essential.gui.common.onSetValueAndNow import gg.essential.gui.elementa.state.v2.* import gg.essential.gui.elementa.state.v2.combinators.* +import gg.essential.gui.elementa.state.v2.stateBy import gg.essential.gui.image.ImageFactory import gg.essential.gui.layoutdsl.* import gg.essential.gui.util.hoverScope @@ -216,7 +218,7 @@ fun LayoutScope.essentialStateTextInput( return input } -// Overload to allow supplying unboxed string +// Overload to allow external control of the error state instead of providing a check function fun LayoutScope.essentialInput( input: AbstractTextInput, errorState: State, @@ -225,7 +227,7 @@ fun LayoutScope.essentialInput( icon: ImageFactory? = null, ) = essentialInput(input, errorState, stateOf(errorMessage), modifier, icon) -// Overload to allow for boolean input of error message state +// Overload to allow external control of the error state instead of providing a check function fun LayoutScope.essentialInput( input: AbstractTextInput, errorState: State, @@ -233,14 +235,27 @@ fun LayoutScope.essentialInput( modifier: Modifier = Modifier, icon: ImageFactory? = null, ) { - val state = State { if (errorState()) errorMessage() else null } - essentialInput(input, errorMessageState = state, modifier = modifier, icon = icon) + // create this outside the function, so we don't recreate it every time check is called + val state = stateBy { if (errorState()) errorMessage() else null } + essentialInput(input, state, modifier, icon) } +// Overload to allow external control of the error state instead of providing a check function. +// We enable checkImmediately to always mirror the error state immediately, since with external control +// we don't actually do the checking fun LayoutScope.essentialInput( input: AbstractTextInput, + errorMessageState: State, modifier: Modifier = Modifier, - errorMessageState: State = State { null }, + icon: ImageFactory? = null, + inputModifier: Modifier = Modifier, +) = essentialInput(input, modifier, { errorMessageState }, true, icon, inputModifier = inputModifier) + +fun LayoutScope.essentialInput( + input: AbstractTextInput, + modifier: Modifier = Modifier, + check: (String) -> State = { stateOf(null) }, + checkImmediately: Boolean = false, icon: ImageFactory? = null, backgroundColor: State = stateOf(EssentialPalette.GUI_BACKGROUND), iconColor: State = stateOf(EssentialPalette.TEXT_MID_GRAY), @@ -255,11 +270,12 @@ fun LayoutScope.essentialInput( iconAndInputPadding: Float = 6f, inputModifier: Modifier = Modifier, ) { - val errorState = errorMessageState.map { it != null } + val errorTextState = stateDelegatingTo(stateOf(null)) + val errorState = errorTextState.map { it != null } val inputFocusedState = mutableStateOf(false) val inputHoveredState = input.hoverScope().toV2() - val outlineColorState = State { + val outlineColorState = stateBy { when { errorState() -> outlineErrorColor inputFocusedState() -> outlineFocusedColor @@ -275,6 +291,13 @@ fun LayoutScope.essentialInput( input.setMaxWidth(100.percent - 8.pixels) } + if (checkImmediately) { + input.textState.onSetValueAndNow { errorTextState.rebind(check(it)) } + } else { + input.onActivate { errorTextState.rebind(check(it)) } + input.onFocusLost { errorTextState.rebind(check(input.getText())) } + } + val gradient by object : GradientComponent(BasicState(EssentialPalette.GUI_BACKGROUND), BasicState(EssentialPalette.GUI_BACKGROUND.invisible()), BasicState(GradientDirection.RIGHT_TO_LEFT)) { // Override because the gradient should be treated as if it does not exist from an input point of view override fun isPointInside(x: Float, y: Float) = false @@ -299,7 +322,7 @@ fun LayoutScope.essentialInput( icon( EssentialPalette.ROUND_WARNING_7X, Modifier.alignHorizontal(Alignment.End(4f)).color(errorColor).shadow(errorShadowColor), - ).bindHoverEssentialTooltip(errorMessageState.map { it ?: "" }.toV1(stateScope), EssentialTooltip.Position.ABOVE, wrapAtWidth = 150f) + ).bindHoverEssentialTooltip(errorTextState.map { it ?: "" }.toV1(stateScope), EssentialTooltip.Position.ABOVE, wrapAtWidth = 150f) } } diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/common/modal/CancelableInputModal.kt b/gui/essential/src/main/kotlin/gg/essential/gui/common/modal/CancelableInputModal.kt index 302edb54..0f0dfe80 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/common/modal/CancelableInputModal.kt +++ b/gui/essential/src/main/kotlin/gg/essential/gui/common/modal/CancelableInputModal.kt @@ -71,7 +71,7 @@ open class CancelableInputModal( } inputContainer.layoutAsBox { - essentialInput(input, errorMessageState = errorMessageState, modifier = Modifier.shadow(EssentialPalette.BLACK)) + essentialInput(input, errorMessageState, modifier = Modifier.shadow(EssentialPalette.BLACK)) } // Top padding diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/common/modal/EssentialModal.kt b/gui/essential/src/main/kotlin/gg/essential/gui/common/modal/EssentialModal.kt index 7c26975f..8138b603 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/common/modal/EssentialModal.kt +++ b/gui/essential/src/main/kotlin/gg/essential/gui/common/modal/EssentialModal.kt @@ -116,7 +116,7 @@ open class EssentialModal( when (keyCode) { // Activate selected button on enter or primary button if no button is selected - UKeyboard.KEY_ENTER, UKeyboard.KEY_NUMPADENTER -> selectedButton.let { + UKeyboard.KEY_ENTER -> selectedButton.let { Window.enqueueRenderOperation { if (it != null) { it.simulateLeftClick() diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/common/modal/EssentialModal2.kt b/gui/essential/src/main/kotlin/gg/essential/gui/common/modal/EssentialModal2.kt index 7ba45c20..fc8777fc 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/common/modal/EssentialModal2.kt +++ b/gui/essential/src/main/kotlin/gg/essential/gui/common/modal/EssentialModal2.kt @@ -41,7 +41,6 @@ import gg.essential.gui.layoutdsl.hoverColor import gg.essential.gui.layoutdsl.hoverScope import gg.essential.gui.layoutdsl.onLeftClick import gg.essential.gui.layoutdsl.outline -import gg.essential.gui.layoutdsl.row import gg.essential.gui.layoutdsl.shadow import gg.essential.gui.layoutdsl.spacer import gg.essential.gui.layoutdsl.tag @@ -90,7 +89,7 @@ abstract class EssentialModal2( nextComponent?.grabWindowFocus() } - UKeyboard.KEY_ENTER, UKeyboard.KEY_NUMPADENTER -> { + UKeyboard.KEY_ENTER -> { val primaryAction = window.findChildrenByTag(recursive = true).singleOrNull() // The simulated left-click event may cause the component's hierarchy to change. @@ -253,20 +252,6 @@ abstract class EssentialModal2( ) } - /** See [primaryButton], [cancelButton]. */ - fun LayoutScope.primaryAndCancelButtons( - primaryText: String, - cancelText: String, - primaryAction: suspend () -> Unit, - cancelAction: () -> Unit = ::close, - primaryStyle: StyledButton.Style = OutlineButtonStyle.BLUE, - ) { - row(Arrangement.spacedBy(8f)) { - cancelButton(cancelText, action = cancelAction) - primaryButton(primaryText, style = primaryStyle, action = primaryAction) - } - } - /** * [action] is a suspend function which is called on left-click. While the [action] is executing, * the button will be in a "disabled" state, where no click events will propagate. diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/common/modal/Modal.kt b/gui/essential/src/main/kotlin/gg/essential/gui/common/modal/Modal.kt index 4c3ac1c4..36dfabc5 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/common/modal/Modal.kt +++ b/gui/essential/src/main/kotlin/gg/essential/gui/common/modal/Modal.kt @@ -17,7 +17,6 @@ import gg.essential.elementa.dsl.* import gg.essential.gui.layoutdsl.LayoutScope import gg.essential.gui.layoutdsl.layoutAsBox import gg.essential.gui.overlay.ModalManager -import gg.essential.network.connectionmanager.telemetry.FeatureSessionTelemetry import gg.essential.universal.UKeyboard import gg.essential.util.Client import gg.essential.vigilance.utils.onLeftClick @@ -46,8 +45,6 @@ abstract class Modal(val modalManager: ModalManager) : UIContainer() { } } - open val modalName: String? = this.javaClass.name - init { constrain { x = 0.percentOfWindow() @@ -97,8 +94,6 @@ abstract class Modal(val modalManager: ModalManager) : UIContainer() { windowListListener = Window.of(this).keyTypedListeners.removeFirstOrNull() Window.of(this).onKeyType(escapeListener) - - modalName?.let { FeatureSessionTelemetry.startEvent(it) } } open fun onClose() { @@ -110,8 +105,6 @@ abstract class Modal(val modalManager: ModalManager) : UIContainer() { } window.keyTypedListeners.remove(escapeListener) // Clean ourselves up - - modalName?.let { FeatureSessionTelemetry.endEvent(it) } } open fun handleEscapeKeyPress() { diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/common/modal/SearchableConfirmDenyModal.kt b/gui/essential/src/main/kotlin/gg/essential/gui/common/modal/SearchableConfirmDenyModal.kt index 8ed539c5..abc4f0b1 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/common/modal/SearchableConfirmDenyModal.kt +++ b/gui/essential/src/main/kotlin/gg/essential/gui/common/modal/SearchableConfirmDenyModal.kt @@ -20,7 +20,6 @@ import gg.essential.gui.EssentialPalette import gg.essential.gui.common.* import gg.essential.gui.common.input.UITextInput import gg.essential.gui.common.input.essentialInput -import gg.essential.gui.elementa.state.v2.effect import gg.essential.gui.elementa.state.v2.memo import gg.essential.gui.elementa.state.v2.mutableStateOf import gg.essential.gui.elementa.state.v2.stateOf @@ -94,9 +93,7 @@ open class SearchableConfirmDenyModal( input.setColor(EssentialPalette.TEXT) input.placeholderShadow.set(false) input.placeholderColor.set(EssentialPalette.TEXT_MID_GRAY) - effect(stateScope) { - searchBarTextState.set(input.textState()) - } + input.textState.onSetValueAndNow { searchBarTextState.set(it) } essentialInput( input, icon = EssentialPalette.SEARCH_7X, diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/common/shadow/ShadowEffect.kt b/gui/essential/src/main/kotlin/gg/essential/gui/common/shadow/ShadowEffect.kt index 001cb72d..db6b9240 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/common/shadow/ShadowEffect.kt +++ b/gui/essential/src/main/kotlin/gg/essential/gui/common/shadow/ShadowEffect.kt @@ -16,21 +16,23 @@ import gg.essential.elementa.components.UIContainer import gg.essential.elementa.components.UIImage import gg.essential.elementa.constraints.CenterConstraint import gg.essential.elementa.effects.Effect +import gg.essential.elementa.state.BasicState +import gg.essential.elementa.state.State import gg.essential.gui.EssentialPalette import gg.essential.gui.common.LoadingIcon import gg.essential.gui.common.SequenceAnimatedUIImage -import gg.essential.gui.elementa.state.v2.State -import gg.essential.gui.elementa.state.v2.stateOf import gg.essential.universal.UGraphics import gg.essential.universal.UMatrixStack import java.awt.Color -class ShadowEffect(private val shadowColorState: State) : Effect() { - constructor(shadowColor: Color = EssentialPalette.COMPONENT_BACKGROUND) : - this(stateOf(shadowColor)) + +class ShadowEffect( + shadowColor: Color = EssentialPalette.COMPONENT_BACKGROUND +) : Effect() { + + private val shadowColorState = BasicState(shadowColor).map { it } override fun beforeDraw(matrixStack: UMatrixStack) { - val shadowColor = shadowColorState.getUntracked() when (val boundComponent = boundComponent) { is EssentialUIText -> { @@ -47,12 +49,13 @@ class ShadowEffect(private val shadowColorState: State) : Effect() { val fontProvider = constraints.fontProvider val x = boundComponent.getLeft() val y = boundComponent.getTop() + (if (constraints.y is CenterConstraint) fontProvider.getBelowLineHeight() * scale else 0f) + val color = shadowColorState.get() UGraphics.enableBlend() fontProvider.drawString( matrixStack, - text, shadowColor, x + 1, y + 1, + text, color, x + 1, y + 1, 10f, scale, false ) } @@ -64,7 +67,7 @@ class ShadowEffect(private val shadowColorState: State) : Effect() { boundComponent.getTop() + 1.0, boundComponent.getWidth().toDouble(), boundComponent.getHeight().toDouble(), - shadowColor + shadowColorState.get() ) } is UIBlock, is UIContainer -> { @@ -77,7 +80,9 @@ class ShadowEffect(private val shadowColorState: State) : Effect() { val x2 = boundComponent.getRight().toDouble() val y2 = boundComponent.getBottom().toDouble() - UIBlock.drawBlock(matrixStack, shadowColor, x+1, y+1, x2+1, y2+1) + val color = shadowColorState.get() + + UIBlock.drawBlock(matrixStack, color, x+1, y+1, x2+1, y2+1) } is UIImage -> { boundComponent.drawImage( @@ -86,7 +91,7 @@ class ShadowEffect(private val shadowColorState: State) : Effect() { boundComponent.getTop() + 1.0, boundComponent.getWidth().toDouble(), boundComponent.getHeight().toDouble(), - shadowColor + shadowColorState.get() ) } is LoadingIcon -> { @@ -99,7 +104,7 @@ class ShadowEffect(private val shadowColorState: State) : Effect() { yCenter + boundComponent.scale.toInt(), boundComponent.scale, boundComponent.time, - shadowColor + shadowColorState.get() ) } else -> { @@ -108,6 +113,10 @@ class ShadowEffect(private val shadowColorState: State) : Effect() { } } + fun rebindColor(state: State) = apply { + shadowColorState.rebind(state) + } + private fun getDebugInfo(): String { return boundComponent.componentName + " " + boundComponent.javaClass.name } diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/common/shadow/ShadowIcon.kt b/gui/essential/src/main/kotlin/gg/essential/gui/common/shadow/ShadowIcon.kt index b66f9998..a45d0572 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/common/shadow/ShadowIcon.kt +++ b/gui/essential/src/main/kotlin/gg/essential/gui/common/shadow/ShadowIcon.kt @@ -20,7 +20,6 @@ import gg.essential.elementa.state.toConstraint import gg.essential.gui.EssentialPalette import gg.essential.gui.common.AutoImageSize import gg.essential.gui.common.onSetValueAndNow -import gg.essential.gui.elementa.state.v2.toV2 import gg.essential.gui.image.ImageFactory import gg.essential.gui.util.hoveredState import java.awt.Color @@ -67,7 +66,7 @@ class ShadowIcon( }.setColor(CopyConstraintColor() boundTo this) childOf this@ShadowIcon if(shadow) { - image effect ShadowEffect(shadowColorState.toV2()) + image effect ShadowEffect().rebindColor(shadowColorState) } } diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/dropdowns.kt b/gui/essential/src/main/kotlin/gg/essential/gui/friends/dropdowns.kt deleted file mode 100644 index d6e46eda..00000000 --- a/gui/essential/src/main/kotlin/gg/essential/gui/friends/dropdowns.kt +++ /dev/null @@ -1,297 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.gui.friends - -import com.sparkuniverse.toolbox.chat.enums.ChannelType -import com.sparkuniverse.toolbox.chat.model.Channel -import gg.essential.elementa.components.Window -import gg.essential.gui.EssentialPalette -import gg.essential.gui.common.ContextOptionMenu -import gg.essential.gui.elementa.state.v2.filter -import gg.essential.gui.elementa.state.v2.mapList -import gg.essential.gui.friends.message.SocialMenuActions -import gg.essential.gui.friends.message.SocialMenuState -import gg.essential.gui.friends.modals.ConfirmGroupLeaveModal -import gg.essential.gui.friends.modals.RenameGroupModal -import gg.essential.gui.friends.modals.createAddFriendsToGroupModal -import gg.essential.gui.friends.previews.ChannelPreview -import gg.essential.gui.friends.state.IMessengerStates -import gg.essential.gui.util.toStateV2List -import gg.essential.util.GuiEssentialPlatform.Companion.platform -import gg.essential.util.ServerType -import gg.essential.util.USession -import gg.essential.util.UuidNameLookup -import gg.essential.util.getOtherUser -import gg.essential.util.isAnnouncement -import java.util.UUID - -fun showManagementDropdown( - window: Window, - socialMenuState: SocialMenuState, - socialMenuActions: SocialMenuActions, - preview: ChannelPreview, - position: ContextOptionMenu.Position, - extraOptions: List, - onClose: () -> Unit -) { - val otherUser = preview.otherUser - when { - otherUser != null -> { - showUserDropdown(window, socialMenuState, socialMenuActions, otherUser, position, extraOptions.toMutableList().apply { - addMarkMessagesReadOption(socialMenuState.messages, preview.channel.id, this) - }, onClose) - } - !preview.channel.isAnnouncement() -> showGroupDropdown(window, socialMenuState, preview.channel, position, extraOptions, onClose) - else -> onClose.invoke() - } -} - -private fun addMarkMessagesReadOption( - messages: IMessengerStates, - channelId: Long, - options: MutableList, -) { - if (messages.getUnreadChannelState(channelId).getUntracked()) { - options.add(ContextOptionMenu.Option("Mark as Read", image = EssentialPalette.MARK_UNREAD_10X7) { - if (platform.cmConnection.usingProtocol >= 9) { - val latestMessage = messages.getLatestMessage(channelId).getUntracked() ?: return@Option - messages.setLastReadMessage(latestMessage) - } else { - messages.getMessageListState(channelId).getUntracked().forEach { - messages.setUnreadState(it, false) - } - } - }) - } -} - -fun showUserDropdown( - window: Window, - socialMenuState: SocialMenuState, - socialMenuActions: SocialMenuActions, - user: UUID, - position: ContextOptionMenu.Position, - extraOptions: List, - onClose: () -> Unit -) { - val options = extraOptions.toMutableList() - - val joinPlayerOption = ContextOptionMenu.Option( - "Join Game", - // New default is text, so remove entirely when removing feature flag - textColor = EssentialPalette.TEXT, - hoveredColor = EssentialPalette.MESSAGE_SENT, - // New default is black, so remove entirely when removing feature flag - shadowColor = EssentialPalette.BLACK, - image = EssentialPalette.JOIN_ARROW_5X, - ) { - socialMenuActions.joinSessionWithConfirmation(user) - } - val invitePlayerOption = ContextOptionMenu.Option( - "Invite to Game", - // New default is text, so remove entirely when removing feature flag - textColor = EssentialPalette.TEXT, - hoveredColor = EssentialPalette.MESSAGE_SENT, - // New default is black, so remove entirely when removing feature flag - shadowColor = EssentialPalette.BLACK, - image = EssentialPalette.ENVELOPE_9X7, - ) { - socialMenuActions.invitePlayers(setOf(user), UuidNameLookup.nameState(user).getUntracked()) - } - - val topmostOptions: MutableList = mutableListOf() - - if (socialMenuState.activity.getActivity(user).isJoinable()) { - topmostOptions.add(joinPlayerOption) - } - if (ServerType.current()?.supportsInvites == true) { - topmostOptions.add(invitePlayerOption) - } - - if (topmostOptions.isNotEmpty()) { - options.add(0, ContextOptionMenu.Divider) - - for (optionItem in topmostOptions) { - options.add(0, optionItem) - } - } - - // Don't add a divider below is we haven't added anything above here - var addedDivider = extraOptions.isEmpty() - - if (socialMenuState.tab.getUntracked() == Tab.FRIENDS) { - options.add(ContextOptionMenu.Option("Send Message", image = EssentialPalette.MESSAGE_10X6) { - socialMenuActions.openMessageScreen(user) - }) - // We always add this divider as it's below the option - options.add(ContextOptionMenu.Divider) - addedDivider = true - } else { - val channel = socialMenuState.messages.getObservableChannelList().firstOrNull { - it.getOtherUser() == user - } - if (channel != null) { - val muted = socialMenuState.messages.getMuted(channel.id) - - if (!addedDivider) { - options.add(ContextOptionMenu.Divider) - addedDivider = true - } - options.add(ContextOptionMenu.Option( - { - if (muted()) { - "Unmute Friend" - } else { - "Mute Friend" - } - }, - image = { - if (muted()) { - EssentialPalette.UNMUTE_8X9 - } else { - EssentialPalette.MUTE_8X9 - } - }, - ) { - muted.set { !it } - }) - } - - } - if (!addedDivider) { - options.add(ContextOptionMenu.Divider) - } - - val blocked = socialMenuState.relationships.isBlocked(user) - val friend = socialMenuState.relationships.isFriend(user) - val isSuspended = socialMenuState.isSuspended(user).getUntracked() - - if (!blocked) { - if (friend || !isSuspended) { - options.add(ContextOptionMenu.Option( - if (friend) { - "Remove Friend" - } else { - "Add Friend" - }, - image = if (friend) EssentialPalette.REMOVE_FRIEND_10X5 else EssentialPalette.INVITE_10X6, - ) { - socialMenuActions.addOrRemoveFriend(user) - }) - } - } - - options.add(ContextOptionMenu.Option( - if (blocked) { - "Unblock" - } else { - "Block" - }, - image = EssentialPalette.BLOCK_10X7, - hoveredColor = EssentialPalette.TEXT_WARNING - ) { - socialMenuActions.blockOrUnblock(user) - }) - ContextOptionMenu.create(position, window, *options.toTypedArray(), onClose = onClose) -} - -private fun showGroupDropdown( - window: Window, - socialMenuState: SocialMenuState, - channel: Channel, - position: ContextOptionMenu.Position, - extraOptions: List, - onClose: () -> Unit -) { - val options = extraOptions.toMutableList() - - addMarkMessagesReadOption(socialMenuState.messages, channel.id, options) - - // Left commented if we re-add in the future - /* if (ServerType.current()?.supportsInvites == true) { - options.add( - ContextOptionMenu.Option( - "Invite Group", - // New default is text, so remove entirely when removing feature flag - textColor = EssentialPalette.TEXT, - hoveredColor = EssentialPalette.MESSAGE_SENT, - // New default is black, so remove entirely when removing feature flag - shadowColor = EssentialPalette.BLACK, - image = EssentialPalette.INVITE_10X6, - ) { - handleInvitePlayers(channel.members, channel.name) - } - ) - - options.add(ContextOptionMenu.Divider) - } */ - - val mutedState = socialMenuState.messages.getMuted(channel.id) - if (channel.type == ChannelType.GROUP_DIRECT_MESSAGE && channel.createdInfo.by == USession.activeNow().uuid) { - options.add(ContextOptionMenu.Option( - "Invite Friends", - image = EssentialPalette.MARK_UNREAD_10X7 - ) { - // We don't want to show anyone currently in the group here - val potentialFriends = socialMenuState.relationships.getObservableFriendList() - .toStateV2List() - .mapList { list -> - list.filter { !channel.members.contains(it) && !socialMenuState.isSuspended(it)() } - } - - - platform.pushModal { manager -> - createAddFriendsToGroupModal(manager, potentialFriends).onPrimaryAction { users -> - socialMenuState.messages.addMembers(channel.id, users) - } - } - }) - options.add(ContextOptionMenu.Divider) - options.add(ContextOptionMenu.Option( - "Rename Group", - image = EssentialPalette.PENCIL_7x7 - ) { - platform.pushModal { manager -> - RenameGroupModal(manager, channel.name).onPrimaryActionWithValue { it -> - socialMenuState.messages.setTitle(channel.id, it) - } - } - }) - options.add(ContextOptionMenu.Divider) - } - - options.add(ContextOptionMenu.Option({ - if (mutedState()) { - "Unmute Group" - } else { - "Mute Group" - } - }, image = { - if (mutedState()) { - EssentialPalette.UNMUTE_8X9 - } else { - EssentialPalette.MUTE_8X9 - } - }) { - mutedState.set { !it } // Will be automatically applied properly - }) - - options.add(ContextOptionMenu.Option("Leave Group", image = EssentialPalette.LEAVE_10X7) { - platform.pushModal { manager -> - ConfirmGroupLeaveModal(manager).onPrimaryAction { - socialMenuState.messages.leaveGroup(channel.id) - } - } - }) - - ContextOptionMenu.create(position, window, *options.toTypedArray(), onClose = onClose) -} diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/MessageUtils.kt b/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/MessageUtils.kt index 0f9899f0..679560d6 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/MessageUtils.kt +++ b/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/MessageUtils.kt @@ -75,14 +75,6 @@ object MessageUtils { ) ) - val blockedMessageMarkdownConfig = restrictedMarkdownConfig.copy( - textConfig = restrictedMarkdownConfig.textConfig.copy( - color = EssentialPalette.TEXT_DISABLED, - shadowColor = EssentialPalette.TEXT_SHADOW, - ), - urlConfig = URLConfig(enabled = false), - ) - val fullMarkdownConfig: MarkdownConfig = markdownStyleConfig.copy( textConfig = markdownStyleConfig.textConfig.copy( shadowColor = Color(0x101010), diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/ReportMessageConfirmationModal.kt b/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/ReportMessageConfirmationModal.kt deleted file mode 100644 index 90eb9a54..00000000 --- a/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/ReportMessageConfirmationModal.kt +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.gui.friends.message - -import com.sparkuniverse.toolbox.chat.enums.ChannelType -import com.sparkuniverse.toolbox.chat.model.Channel -import gg.essential.gui.EssentialPalette -import gg.essential.gui.common.MenuButton -import gg.essential.gui.common.StyledButton.Style -import gg.essential.gui.common.modal.EssentialModal2 -import gg.essential.gui.common.textStyle -import gg.essential.gui.elementa.state.v2.MutableState -import gg.essential.gui.elementa.state.v2.combinators.map -import gg.essential.gui.elementa.state.v2.mutableStateOf -import gg.essential.gui.elementa.state.v2.onChange -import gg.essential.gui.friends.state.SocialStates -import gg.essential.gui.layoutdsl.Alignment -import gg.essential.gui.layoutdsl.Arrangement -import gg.essential.gui.layoutdsl.LayoutScope -import gg.essential.gui.layoutdsl.Modifier -import gg.essential.gui.layoutdsl.alignHorizontal -import gg.essential.gui.layoutdsl.alignVertical -import gg.essential.gui.layoutdsl.box -import gg.essential.gui.layoutdsl.childBasedHeight -import gg.essential.gui.layoutdsl.color -import gg.essential.gui.layoutdsl.column -import gg.essential.gui.layoutdsl.fillRemainingWidth -import gg.essential.gui.layoutdsl.fillWidth -import gg.essential.gui.layoutdsl.height -import gg.essential.gui.layoutdsl.row -import gg.essential.gui.layoutdsl.shadow -import gg.essential.gui.layoutdsl.spacer -import gg.essential.gui.layoutdsl.text -import gg.essential.gui.layoutdsl.width -import gg.essential.gui.layoutdsl.wrappedText -import gg.essential.gui.overlay.ModalManager -import gg.essential.util.GuiEssentialPlatform.Companion.platform -import gg.essential.util.UuidNameLookup -import java.awt.Color -import java.util.* - -class ReportMessageConfirmationModal(modalManager: ModalManager, val sender: UUID, val debug: Boolean = false) : EssentialModal2(modalManager) { - - private val socialStates = platform.createSocialStates() - - private val dmChannel = socialStates.findDM(sender) - private val mutedState = when { - debug -> mutableStateOf(false) - dmChannel != null && socialStates.isFriend(sender) -> socialStates.messages.getMuted(dmChannel.id) - else -> null - } - - private val blockedState = mutableStateOf(if (debug) false else socialStates.isBlocked(sender)).apply { - this.onChange(this@ReportMessageConfirmationModal) { blocked -> - if (!debug && blocked) { - socialStates.relationships.blockPlayer(sender, false) - } - } - } - - override fun LayoutScope.layoutTitle() { - title("Thank you!") - } - - override fun LayoutScope.layoutBody() { - column(Modifier.fillWidth(), Arrangement.spacedBy(15f)) { - wrappedText("Your report has been submitted.\nYour feedback helps keep\nEssential safe.", - Modifier - .color(EssentialPalette.TEXT) - .shadow(EssentialPalette.BLACK), - centered = true, - lineSpacing = 10f - ) - column(Modifier.fillWidth(), Arrangement.spacedBy(3f)) { - if (mutedState != null) { - userAction("Mute", "Muted", Styles.GRAY, mutedState) - } - userAction("Block", "Blocked", Styles.RED, blockedState) - } - } - } - - private fun LayoutScope.userAction(label: String, doneLabel: String, style: Style, state: MutableState) { - val labelState = state.map { if (it) doneLabel else label } - row(Modifier.fillWidth().childBasedHeight(2f).color(EssentialPalette.COMPONENT_BACKGROUND)) { - spacer(width = 10f) - box(Modifier.fillRemainingWidth().alignVertical(Alignment.Center(true))) { - text({ "$label ${UuidNameLookup.nameState(sender)()}" }, Modifier.alignHorizontal(Alignment.Start).shadow(EssentialPalette.BLACK), truncateIfTooSmall = true) - } - spacer(width = 10f) - outlineButton(Modifier.height(17f).width(52f).shadow(EssentialPalette.BLACK), { style }, action = { - state.set(true) - }, disabled = state) { currentStyle -> - text(labelState, Modifier.textStyle(currentStyle)) - } - spacer(width = 10f) - } - } - - override fun LayoutScope.layoutButtons() { - primaryButton("Done") { close() } - } - - private fun SocialStates.isFriend(uuid: UUID): Boolean = relationships.getObservableFriendList().contains(uuid) - - private fun SocialStates.isBlocked(uuid: UUID): Boolean = relationships.getObservableBlockedList().contains(uuid) - - private fun SocialStates.findDM(uuid: UUID): Channel? = - messages.getObservableChannelList().firstOrNull { channel -> channel.type == ChannelType.DIRECT_MESSAGE && uuid in channel.members } -} - -private object Styles { - - private val transparent = Color(0, 0, 0, 0) - - val RED = Style( - MenuButton.Style(EssentialPalette.TEXT, EssentialPalette.RED_OUTLINE_BUTTON, transparent), - MenuButton.Style(EssentialPalette.TEXT, EssentialPalette.RED_OUTLINE_BUTTON_HOVER, transparent), - MenuButton.Style(EssentialPalette.TEXT_DISABLED, EssentialPalette.RED_OUTLINE_BUTTON.darker(), transparent) - ) - - val GRAY = Style( - MenuButton.Style(EssentialPalette.TEXT, EssentialPalette.GRAY_OUTLINE_BUTTON, transparent), - MenuButton.Style(EssentialPalette.TEXT, EssentialPalette.GRAY_OUTLINE_BUTTON_HOVER, transparent), - MenuButton.Style(EssentialPalette.TEXT_DISABLED, EssentialPalette.INPUT_BACKGROUND, transparent) - ) -} \ No newline at end of file diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/ReportMessageModal.kt b/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/ReportMessageModal.kt deleted file mode 100644 index 10819b4b..00000000 --- a/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/ReportMessageModal.kt +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.gui.friends.message - -import gg.essential.elementa.dsl.provideDelegate -import gg.essential.gui.EssentialPalette -import gg.essential.gui.common.OutlineButtonStyle -import gg.essential.gui.common.modal.EssentialModal2 -import gg.essential.gui.common.radioButton -import gg.essential.gui.elementa.state.v2.combinators.map -import gg.essential.gui.elementa.state.v2.mutableStateOf -import gg.essential.gui.friends.message.v2.ClientMessage -import gg.essential.gui.layoutdsl.Alignment -import gg.essential.gui.layoutdsl.Arrangement -import gg.essential.gui.layoutdsl.FloatPosition -import gg.essential.gui.layoutdsl.LayoutScope -import gg.essential.gui.layoutdsl.Modifier -import gg.essential.gui.layoutdsl.alignHorizontal -import gg.essential.gui.layoutdsl.box -import gg.essential.gui.layoutdsl.childBasedHeight -import gg.essential.gui.layoutdsl.childBasedMaxHeight -import gg.essential.gui.layoutdsl.color -import gg.essential.gui.layoutdsl.column -import gg.essential.gui.layoutdsl.fillRemainingWidth -import gg.essential.gui.layoutdsl.fillWidth -import gg.essential.gui.layoutdsl.hoverColor -import gg.essential.gui.layoutdsl.hoverScope -import gg.essential.gui.layoutdsl.inheritHoverScope -import gg.essential.gui.layoutdsl.row -import gg.essential.gui.layoutdsl.shadow -import gg.essential.gui.layoutdsl.wrappedText -import gg.essential.gui.overlay.ModalManager -import gg.essential.universal.USound -import gg.essential.util.GuiEssentialPlatform.Companion.platform -import gg.essential.util.UuidNameLookup -import gg.essential.util.centered -import gg.essential.util.onLeftClick - -class NewReportMessageModal(modalManager: ModalManager, val message: ClientMessage) : EssentialModal2(modalManager) { - - private val reasons = platform.reportReasons - - private val selectedReason = mutableStateOf(null) - private val submitDisabledState = selectedReason.map { it == null } - - override fun LayoutScope.layoutTitle() { - title({ "Report ${UuidNameLookup.nameState(message.sender)()}" }) - } - - override fun LayoutScope.layoutBody() { - column(Modifier.fillWidth(), Arrangement.spacedBy(12f)) { - wrappedText( - "Select the option that best\ndescribes the problem.", Modifier - .color(EssentialPalette.TEXT) - .shadow(EssentialPalette.BLACK), - centered = true - ) - column(Modifier.fillWidth(), Arrangement.spacedBy(3f)) { - for ((id, displayText) in reasons) { - reason(id, displayText) - } - } - } - } - - override fun LayoutScope.layoutButtons() { - row(Arrangement.spacedBy(8f)) { - cancelButton("Cancel") - primaryButton("Submit", style = OutlineButtonStyle.RED, disabled = submitDisabledState) { - platform.fileReport( - modalManager, - message.channel.id, - message.id, - message.sender, - selectedReason.getUntracked()!!, - ) - close() - } - } - } - - private fun LayoutScope.reason(id: String, displayText: String) { - box(Modifier.fillWidth().childBasedHeight(3f).hoverScope().color(EssentialPalette.COMPONENT_BACKGROUND).hoverColor(EssentialPalette.COMPONENT_BACKGROUND_HIGHLIGHT).shadow(EssentialPalette.BLACK)) { - row(Modifier.fillWidth(padding = 7f).childBasedMaxHeight(4f), Arrangement.spacedBy(8f, FloatPosition.START)) { - radioButton(id, selectedReason, Modifier.inheritHoverScope()) - box(Modifier.fillRemainingWidth()) { - wrappedText(displayText, Modifier - .alignHorizontal(Alignment.Start) - .color(EssentialPalette.TEXT) - .shadow(EssentialPalette.BLACK) - ) - } - } - }.onLeftClick { click -> - if (selectedReason.getUntracked() != id) { - USound.playButtonPress() - selectedReason.set(id) - } - click.stopPropagation() - } - } -} \ No newline at end of file diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/SocialMenuActions.kt b/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/SocialMenuActions.kt deleted file mode 100644 index 52c5c0ae..00000000 --- a/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/SocialMenuActions.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.gui.friends.message - -import com.sparkuniverse.toolbox.chat.model.Channel -import gg.essential.gui.common.ContextOptionMenu -import gg.essential.gui.friends.previews.ChannelPreview -import java.util.UUID - -interface SocialMenuActions { - fun openMessageScreen(channel: Channel) - fun openMessageScreen(user: UUID) - - fun showManagementDropdown( - preview: ChannelPreview, - position: ContextOptionMenu.Position, - extraOptions: List = emptyList(), - onClose: () -> Unit = {}, - ) - - fun showUserDropdown( - user: UUID, - position: ContextOptionMenu.Position, - extraOptions: List = emptyList(), - onClose: () -> Unit = {}, - ) - - // TODO these don't really belong in here, but they have platform-dependencies, so we can't yet move them - fun joinSessionWithConfirmation(user: UUID) - fun invitePlayers(users: Set, name: String) - fun addOrRemoveFriend(uuid: UUID) - fun blockOrUnblock(uuid: UUID) -} diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/SocialMenuState.kt b/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/SocialMenuState.kt deleted file mode 100644 index 77f29168..00000000 --- a/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/SocialMenuState.kt +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.gui.friends.message - -import gg.essential.gui.elementa.state.v2.State -import gg.essential.gui.friends.Tab -import gg.essential.gui.friends.state.SocialStates - -interface SocialMenuState : SocialStates { - val tab: State -} diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/screenshot/ScreenshotAttachmentComponents.kt b/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/screenshot/ScreenshotAttachmentComponents.kt deleted file mode 100644 index 730238be..00000000 --- a/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/screenshot/ScreenshotAttachmentComponents.kt +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.gui.friends.message.screenshot - -import gg.essential.elementa.constraints.animation.Animations -import gg.essential.elementa.dsl.pixels -import gg.essential.gui.EssentialPalette -import gg.essential.gui.common.MenuButton -import gg.essential.gui.elementa.state.v2.combinators.map -import gg.essential.gui.layoutdsl.Alignment -import gg.essential.gui.layoutdsl.LayoutScope -import gg.essential.gui.layoutdsl.Modifier -import gg.essential.gui.layoutdsl.alignHorizontal -import gg.essential.gui.layoutdsl.animateWidth -import gg.essential.gui.layoutdsl.box -import gg.essential.gui.layoutdsl.childBasedMaxHeight -import gg.essential.gui.layoutdsl.color -import gg.essential.gui.layoutdsl.fillHeight -import gg.essential.gui.layoutdsl.height -import gg.essential.gui.layoutdsl.hoverColor -import gg.essential.gui.layoutdsl.hoverScope -import gg.essential.gui.layoutdsl.icon -import gg.essential.gui.layoutdsl.row -import gg.essential.gui.layoutdsl.shadow -import gg.essential.gui.layoutdsl.spacer -import gg.essential.gui.layoutdsl.text -import gg.essential.gui.layoutdsl.width -import gg.essential.universal.USound -import gg.essential.vigilance.utils.onLeftClick -import java.awt.Color - -fun LayoutScope.screenshotAttachmentUploadBox( - screenshotAttachmentManager: ScreenshotAttachmentManager -) { - row(Modifier.childBasedMaxHeight(9f).color(EssentialPalette.COMPONENT_BACKGROUND_HIGHLIGHT)) { - spacer(width = 10f) - icon(EssentialPalette.PICTURES_10X10, Modifier.color(EssentialPalette.TEXT)) - spacer(width = 6f) - text("Uploading...", Modifier.color(EssentialPalette.TEXT).shadow(Color.BLACK)) - spacer(width = 11f) - box(Modifier.width(100f).height(3f).color(EssentialPalette.GUI_BACKGROUND).shadow(Color.BLACK)) { - box( - Modifier.animateWidth( - screenshotAttachmentManager.totalProgressPercentage.map { { it.pixels } }, - 0.5f, - Animations.LINEAR - ).fillHeight().color(EssentialPalette.TOAST_PROGRESS).alignHorizontal(Alignment.Start) - ) - } - spacer(width = 10f) - }.addUpdateFunc { _, _ -> - screenshotAttachmentManager.updateProgress() - } -} - -fun LayoutScope.screenshotAttachmentDoneButton(screenshotAttachmentManager: ScreenshotAttachmentManager) { - box( - Modifier.width(43f).height(17f) - .color(MenuButton.BLUE.buttonColor) - .hoverColor(MenuButton.LIGHT_BLUE.buttonColor) - .shadow() - .hoverScope() - ) { - text( - "Done", - Modifier.shadow(), - centeringContainsShadow = false - ) - }.onLeftClick { - USound.playButtonPress() - screenshotAttachmentManager.isPickingScreenshots.set(false) - } -} diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/v2/ClientMessage.kt b/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/v2/ClientMessage.kt index 23a2d75c..437ec8a4 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/v2/ClientMessage.kt +++ b/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/v2/ClientMessage.kt @@ -102,21 +102,4 @@ sealed interface SendState { data object Failed : SendState - data class Blocked(val reason: String) : SendState { - - val toastMessage = when(reason) { - CONTAINS_NON_WHITELISTED_DOMAIN -> "You cannot share this link" - else -> "You cannot send this message" - } - - val tooltipMessage = when(reason) { - CONTAINS_NON_WHITELISTED_DOMAIN -> "Message not sent: This link is not allowed" - else -> "Message not sent: This isn't allowed" - } - - companion object { - - private const val CONTAINS_NON_WHITELISTED_DOMAIN = "CONTAINS_NON_WHITELISTED_DOMAIN" - } - } } \ No newline at end of file diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/v2/messageInputSuspended.kt b/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/v2/messageInputSuspended.kt deleted file mode 100644 index 72741df4..00000000 --- a/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/v2/messageInputSuspended.kt +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.gui.friends.message.v2 - -import gg.essential.gui.EssentialPalette -import gg.essential.gui.elementa.state.v2.State -import gg.essential.gui.layoutdsl.Alignment -import gg.essential.gui.layoutdsl.Arrangement -import gg.essential.gui.layoutdsl.LayoutScope -import gg.essential.gui.layoutdsl.Modifier -import gg.essential.gui.layoutdsl.alignHorizontal -import gg.essential.gui.layoutdsl.childBasedHeight -import gg.essential.gui.layoutdsl.color -import gg.essential.gui.layoutdsl.column -import gg.essential.gui.layoutdsl.fillWidth -import gg.essential.gui.layoutdsl.image -import gg.essential.gui.layoutdsl.row -import gg.essential.gui.layoutdsl.shadow -import gg.essential.gui.layoutdsl.spacer -import gg.essential.gui.layoutdsl.text -import gg.essential.gui.layoutdsl.wrappedText - -fun LayoutScope.messageInputSuspended(name: State, modifier: Modifier = Modifier) { - column(Modifier.fillWidth().then(modifier)) { - column(Modifier.fillWidth(padding = 7f)) { - column(Modifier.fillWidth().childBasedHeight(10f).color(EssentialPalette.COMPONENT_BACKGROUND)) { - spacer(height = 1f) - row(Modifier.alignHorizontal(Alignment.Start(10f)), Arrangement.spacedBy(7f)) { - image( - EssentialPalette.ROUND_WARNING_7X, Modifier - .color(EssentialPalette.RED) - .shadow(EssentialPalette.TEXT_SHADOW) - ) - wrappedText("{name} is temporarily suspended from social features!", - textModifier = Modifier - .color(EssentialPalette.TEXT_DISABLED) - .shadow(EssentialPalette.TEXT_SHADOW) - ) { - "name" { - text(name, Modifier - .color(EssentialPalette.TEXT_MID_GRAY) - .shadow(EssentialPalette.TEXT_SHADOW) - ) - } - } - } - } - spacer(height = 7f) - } - } -} \ No newline at end of file diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/modals/AddFriendModal.kt b/gui/essential/src/main/kotlin/gg/essential/gui/friends/modals/AddFriendModal.kt deleted file mode 100644 index cd40245b..00000000 --- a/gui/essential/src/main/kotlin/gg/essential/gui/friends/modals/AddFriendModal.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.gui.friends.modals - -import gg.essential.gui.common.modal.UsernameInputModal -import gg.essential.gui.common.modal.configure -import gg.essential.gui.overlay.ModalManager -import java.util.UUID - -class AddFriendModal( - modalManager: ModalManager, - whenValidated: (UUID, String, UsernameInputModal) -> Unit, -) : UsernameInputModal(modalManager, "", whenValidated = whenValidated) { - init { - configure { - primaryButtonText = "Add" - titleText = "Add Friend" - contentText = "Enter a Minecraft username\nto add them as a friend." - } - } -} diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/modals/BlockConfirmationModal.kt b/gui/essential/src/main/kotlin/gg/essential/gui/friends/modals/BlockConfirmationModal.kt deleted file mode 100644 index 80454992..00000000 --- a/gui/essential/src/main/kotlin/gg/essential/gui/friends/modals/BlockConfirmationModal.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.gui.friends.modals - -import gg.essential.gui.common.modal.DangerConfirmationEssentialModal -import gg.essential.gui.common.modal.configure -import gg.essential.gui.overlay.ModalManager - -class BlockConfirmationModal(manager: ModalManager, name: String, blockText: String) : DangerConfirmationEssentialModal(manager, blockText, false) { - init { - configure { - titleText = "Are you sure you want to ${blockText.lowercase()} $name?" - } - } -} diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/modals/BlockPlayerModal.kt b/gui/essential/src/main/kotlin/gg/essential/gui/friends/modals/BlockPlayerModal.kt deleted file mode 100644 index dac7ba03..00000000 --- a/gui/essential/src/main/kotlin/gg/essential/gui/friends/modals/BlockPlayerModal.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.gui.friends.modals - -import gg.essential.gui.common.modal.UsernameInputModal -import gg.essential.gui.common.modal.configure -import gg.essential.gui.overlay.ModalManager -import java.util.UUID - -class BlockPlayerModal( - modalManager: ModalManager, - whenValidated: (UUID, String, UsernameInputModal) -> Unit, -) : UsernameInputModal(modalManager, "", whenValidated = whenValidated) { - init { - configure { - primaryButtonText = "Block" - titleText = "Block Player" - contentText = "Enter a Minecraft username\nto block them." - } - } -} diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/modals/ConfirmGroupLeaveModal.kt b/gui/essential/src/main/kotlin/gg/essential/gui/friends/modals/ConfirmGroupLeaveModal.kt deleted file mode 100644 index d3d07401..00000000 --- a/gui/essential/src/main/kotlin/gg/essential/gui/friends/modals/ConfirmGroupLeaveModal.kt +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.gui.friends.modals - -import gg.essential.gui.common.modal.ConfirmDenyModal -import gg.essential.gui.common.modal.configure -import gg.essential.gui.overlay.ModalManager - -class ConfirmGroupLeaveModal(modalManager: ModalManager) : ConfirmDenyModal(modalManager, false) { - init { - configure { - titleText = "Are you sure you want to leave this group?" - primaryButtonText = "Confirm" - } - } -} diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/modals/ConfirmJoinModal.kt b/gui/essential/src/main/kotlin/gg/essential/gui/friends/modals/ConfirmJoinModal.kt deleted file mode 100644 index fbc791f0..00000000 --- a/gui/essential/src/main/kotlin/gg/essential/gui/friends/modals/ConfirmJoinModal.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.gui.friends.modals - -import gg.essential.gui.common.modal.ConfirmDenyModal -import gg.essential.gui.common.modal.configure -import gg.essential.gui.overlay.ModalManager - -class ConfirmJoinModal(modalManager: ModalManager, user: String, isSps: Boolean) : ConfirmDenyModal(modalManager, false) { - init { - val title = buildString { - append("Are you sure you want to join $user's ") - if (isSps) { - append("world") - } else { - append("server") - } - append("?") - } - configure { - titleText = title - } - } -} diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/modals/FriendRemoveConfirmationModal.kt b/gui/essential/src/main/kotlin/gg/essential/gui/friends/modals/FriendRemoveConfirmationModal.kt deleted file mode 100644 index 271fd2fd..00000000 --- a/gui/essential/src/main/kotlin/gg/essential/gui/friends/modals/FriendRemoveConfirmationModal.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.gui.friends.modals - -import gg.essential.gui.common.modal.DangerConfirmationEssentialModal -import gg.essential.gui.common.modal.configure -import gg.essential.gui.overlay.ModalManager - -class FriendRemoveConfirmationModal(manager: ModalManager, name: String) : DangerConfirmationEssentialModal(manager, "Remove", false) { - init { - configure { - titleText = "Are you sure you want to remove $name as your friend?" - } - } -} diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/modals/RenameGroupModal.kt b/gui/essential/src/main/kotlin/gg/essential/gui/friends/modals/RenameGroupModal.kt deleted file mode 100644 index c9610dc6..00000000 --- a/gui/essential/src/main/kotlin/gg/essential/gui/friends/modals/RenameGroupModal.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.gui.friends.modals - -import gg.essential.gui.common.modal.CancelableInputModal -import gg.essential.gui.common.modal.configure -import gg.essential.gui.overlay.ModalManager - -class RenameGroupModal(manager: ModalManager, name: String) : CancelableInputModal(manager, "", name, maxLength = 24) { - init { - configure { - titleText = "Rename Group" - contentText = "Enter a new name for your group." - primaryButtonText = "Rename" - } - mapInputToEnabled { - it.isNotBlank() && it != name - } - } -} diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/modals/addFriendsToGroupModal.kt b/gui/essential/src/main/kotlin/gg/essential/gui/friends/modals/addFriendsToGroupModal.kt deleted file mode 100644 index 70bd8a9c..00000000 --- a/gui/essential/src/main/kotlin/gg/essential/gui/friends/modals/addFriendsToGroupModal.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.gui.friends.modals - -import gg.essential.gui.elementa.state.v2.ListState -import gg.essential.gui.modals.select.selectModal -import gg.essential.gui.modals.select.users -import gg.essential.gui.overlay.ModalManager -import java.util.UUID - -fun createAddFriendsToGroupModal(manager: ModalManager, potentialFriends: ListState) = - selectModal(manager, "Add friends to group", "AddFriendsToGroup") { - requiresButtonPress = false - requiresSelection = true - - users("Friends", potentialFriends) - } diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/modals/makeGroupModalFlow.kt b/gui/essential/src/main/kotlin/gg/essential/gui/friends/modals/makeGroupModalFlow.kt deleted file mode 100644 index fc79f9e3..00000000 --- a/gui/essential/src/main/kotlin/gg/essential/gui/friends/modals/makeGroupModalFlow.kt +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.gui.friends.modals - -import com.sparkuniverse.toolbox.chat.model.Channel -import gg.essential.elementa.components.Window -import gg.essential.gui.EssentialPalette -import gg.essential.gui.common.modal.CancelableInputModal -import gg.essential.gui.common.modal.configure -import gg.essential.gui.friends.state.SocialStates -import gg.essential.gui.modals.select.offlinePlayers -import gg.essential.gui.modals.select.onlinePlayers -import gg.essential.gui.modals.select.selectModal -import gg.essential.gui.overlay.ModalFlow -import kotlinx.coroutines.future.asDeferred -import java.util.UUID -import kotlin.coroutines.resume -import kotlin.coroutines.suspendCoroutine - -suspend fun ModalFlow.makeGroupModal(socialStates: SocialStates): Channel? { - while (true) { - val friends = selectFriendsForGroupModal() ?: return null - val name = enterGroupNameModal() ?: continue - - val channel = socialStates.messages.createGroup(friends, name) - .exceptionally { null }.asDeferred().await() - - // Intentionally delay one frame so that the channel preview callback can fire first - suspendCoroutine { Window.enqueueRenderOperation { it.resume(null) } } - - return channel - } -} - -suspend fun ModalFlow.enterGroupNameModal(): String? { - return awaitModal { continuation -> - CancelableInputModal(modalManager, "", "", maxLength = 24).configure { - titleText = "Make Group" - contentText = "Enter a name for your group." - primaryButtonText = "Make Group" - titleTextColor = EssentialPalette.TEXT_HIGHLIGHT - - cancelButtonText = "Back" - - mapInputToEnabled { it.isNotBlank() } - onPrimaryActionWithValue { result -> replaceWith(continuation.resumeImmediately(result)) } - onCancel { button -> if (button) replaceWith(continuation.resumeImmediately(null)) } - } - } -} - -suspend fun ModalFlow.selectFriendsForGroupModal(): Set? { - return selectModal("Select friends to make group", "SelectFriendsForGroup") { - requiresSelection = true - requiresButtonPress = false - - onlinePlayers() - offlinePlayers() - - modalSettings { - primaryButtonText = "Continue" - cancelButtonText = "Cancel" - } - } -} diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/state/SocialStates.kt b/gui/essential/src/main/kotlin/gg/essential/gui/friends/state/SocialStates.kt index 979328e6..4259cf2a 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/friends/state/SocialStates.kt +++ b/gui/essential/src/main/kotlin/gg/essential/gui/friends/state/SocialStates.kt @@ -17,13 +17,10 @@ import gg.essential.connectionmanager.common.packet.Packet import gg.essential.elementa.state.State import gg.essential.elementa.utils.ObservableList import gg.essential.gui.elementa.state.v2.ListState -import gg.essential.gui.elementa.state.v2.combinators.map -import gg.essential.gui.elementa.state.v2.stateUsingSystemTime import gg.essential.gui.friends.message.v2.ClientMessage import gg.essential.gui.elementa.state.v2.MutableState as MutableStateV2 import gg.essential.gui.elementa.state.v2.State as StateV2 import gg.essential.network.connectionmanager.relationship.RelationshipResponse -import gg.essential.network.connectionmanager.social.ProfileSuspension import java.time.Instant import java.util.* import java.util.concurrent.CompletableFuture @@ -32,21 +29,11 @@ interface SocialStates { val relationships: IRelationshipStates val messages: IMessengerStates val activity: IStatusStates - val suspensions: ListState - fun getSuspension(uuid: UUID): StateV2 = stateUsingSystemTime { now -> - suspensions().find { it.user == uuid } - } - - fun isSuspended(uuid: UUID): StateV2 = getSuspension(uuid).map { it != null } } interface IRelationshipStates { - fun isFriend(uuid: UUID): Boolean = uuid in getObservableFriendList() - - fun isBlocked(uuid: UUID): Boolean = uuid in getObservableBlockedList() - fun getObservableFriendList(): ObservableList fun getObservableBlockedList(): ObservableList @@ -117,11 +104,7 @@ interface IMessengerStates { * Calling [State.set] will propagate update to CM */ @Deprecated("Not used in protocol 9 or later") - fun getUnreadMessageState(channelId: Long, messageId: Long): StateV2 - @Deprecated("Not used in protocol 9 or later") - fun getUnreadMessageState(message: ClientMessage): StateV2 = getUnreadMessageState(message.channel.id, message.id) - @Deprecated("Not used in protocol 9 or later") - fun getUnreadMessageState(message: Message): StateV2 = getUnreadMessageState(message.channelId, message.id) + fun getUnreadMessageState(message: Message): StateV2 /** * State of the last read message in a channel @@ -131,12 +114,7 @@ interface IMessengerStates { /** * Sets the message as the last read message in the channel */ - fun setLastReadMessage(message: Message) = setLastReadMessage(message.channelId, message.id) - - /** - * Sets the message as the last read message in the channel - */ - fun setLastReadMessage(message: ClientMessage) = setLastReadMessage(message.channel.id, message.id) + fun setLastReadMessage(message: Message) /** * Sets the message id as the last read message in the channel @@ -160,7 +138,7 @@ interface IMessengerStates { /** * State reflecting the most recent message sent in this channel */ - fun getLatestMessage(channelId: Long): StateV2 + fun getLatestMessage(channelId: Long): StateV2 /** * A List State that has the initial value of all loaded message in this channel. @@ -178,11 +156,7 @@ interface IMessengerStates { fun getObservableChannelList(): ObservableList @Deprecated("Not used in protocol 9 or later") - fun setUnreadState(channelId: Long, messageId: Long, unread: Boolean) - @Deprecated("Not used in protocol 9 or later") - fun setUnreadState(message: ClientMessage, unread: Boolean) = setUnreadState(message.channel.id, message.id, unread) - @Deprecated("Not used in protocol 9 or later") - fun setUnreadState(message: Message, unread: Boolean) = setUnreadState(message.channelId, message.id, unread) + fun setUnreadState(message: Message, unread: Boolean) /** * Sets the title of a channel. Will throw an exception if this channel @@ -200,26 +174,10 @@ interface IMessengerStates { */ fun sendMessage(channelId: Long, message: String, replyTo: Long? = null, callback: ((Optional) -> Unit)? = null) - /** - * Edits the given [messageId] - */ - fun editMessage(channelId: Long, messageId: Long, content: String, callback: (Boolean) -> Unit) - fun editMessage(message: ClientMessage, content: String, callback: (Boolean) -> Unit) = editMessage(message.channel.id, message.id, content, callback) - - /** - * Deletes [messageId] from the given [channelId] it is in - */ - fun deleteMessage(channelId: Long, messageId: Long) - /** * Deletes [message] from the channel it is in */ - fun deleteMessage(message: ClientMessage) = deleteMessage(message.id, message.channel.id) - - /** - * Deletes [message] from the channel it is in - */ - fun deleteMessage(message: Message) = deleteMessage(message.id, message.channelId) + fun deleteMessage(message: Message) fun leaveGroup(channelId: Long) @@ -237,11 +195,6 @@ interface IMessengerStates { * can store its state before it is cleared */ fun registerResetListener(callback: () -> Unit) - - // FIXME these could probably be cleaned up, though their behavior wrt announcement channels doesn't match existing - // methods, so not entirely sure - fun getMessagesRaw(channelId: Long): Map? - fun retrieveMessageHistoryRaw(channelId: Long, before: Long? = null, after: Long? = null, messageLimit: Int = 50, callback: ((Optional) -> Unit)? = null) } // For callbacks from CM. Will update associated states diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/title/TitleManagementActions.kt b/gui/essential/src/main/kotlin/gg/essential/gui/friends/title/TitleManagementActions.kt deleted file mode 100644 index 083bafde..00000000 --- a/gui/essential/src/main/kotlin/gg/essential/gui/friends/title/TitleManagementActions.kt +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.gui.friends.title - -import gg.essential.elementa.components.UIContainer -import gg.essential.gui.EssentialPalette -import gg.essential.gui.common.EssentialCollapsibleSearchbar -import gg.essential.gui.common.modal.UsernameInputModal -import gg.essential.gui.friends.message.SocialMenuActions -import gg.essential.gui.friends.modals.AddFriendModal -import gg.essential.gui.friends.modals.BlockPlayerModal -import gg.essential.gui.friends.modals.makeGroupModal -import gg.essential.gui.friends.state.SocialStates -import gg.essential.gui.notification.Notifications -import gg.essential.gui.notification.iconAndMarkdownBody -import gg.essential.gui.overlay.launchModalFlow -import gg.essential.network.connectionmanager.relationship.FriendRequestState -import gg.essential.network.connectionmanager.relationship.RelationshipErrorResponse -import gg.essential.network.connectionmanager.relationship.RelationshipResponse -import gg.essential.network.connectionmanager.relationship.message -import gg.essential.util.GuiEssentialPlatform.Companion.platform -import gg.essential.util.colored -import gg.essential.util.thenAcceptOnMainThread -import java.util.concurrent.CompletableFuture - -abstract class TitleManagementActions( - private val socialStates: SocialStates, - private val socialMenuActions: SocialMenuActions, -) : UIContainer() { - - abstract val search: EssentialCollapsibleSearchbar - - protected fun addFriend() { - platform.pushModal { manager -> - AddFriendModal(manager) { uuid, username, modal -> - val future = socialStates.relationships.addFriend(uuid, false) - consumeRelationshipFutureFromModal( - modal, future - ) { - Notifications.push("", "") { - iconAndMarkdownBody( - EssentialPalette.ENVELOPE_9X7.create(), - "Friend request sent to ${username.colored(EssentialPalette.TEXT_HIGHLIGHT)}" - ) - } - } - } - } - } - - protected fun makeGroup() { - launchModalFlow(platform.createModalManager()) { - val channel = makeGroupModal(socialStates) ?: return@launchModalFlow - socialMenuActions.openMessageScreen(channel) - } - } - - protected fun blockPlayer() { - platform.pushModal { manager -> - BlockPlayerModal(manager) { uuid, username, modal -> - val future = socialStates.relationships.blockPlayer(uuid, false) - consumeRelationshipFutureFromModal( - modal, future - ) { - Notifications.push("", "") { - iconAndMarkdownBody( - EssentialPalette.BLOCK_7X7.create(), - "${username.colored(EssentialPalette.TEXT_HIGHLIGHT)} has been blocked" - ) - } - } - } - } - } - - // Adapted from RelationshipStateManagerImpl consumeRelationshipFuture - private fun consumeRelationshipFutureFromModal( - modal: UsernameInputModal, - future: CompletableFuture, - onSuccess: () -> Unit - ) { - future.thenAcceptOnMainThread { - modal.primaryButtonEnableStateOverride.set(true) - when (it.friendRequestState) { - FriendRequestState.SENT -> { - onSuccess() - modal.replaceWith(null) - } - - FriendRequestState.ERROR_HANDLED, FriendRequestState.ERROR_UNHANDLED -> { - modal.errorOverride.set( - if (it.relationshipErrorResponse == RelationshipErrorResponse.TARGET_NOT_EXIST) { - "Not an Essential user" - } else if (it.relationshipErrorResponse == RelationshipErrorResponse.USER_IS_PERMANENTLY_SUSPENDED) { - "This player is suspended" - } else if (it.relationshipErrorResponse == RelationshipErrorResponse.USER_IS_TEMPORARILY_SUSPENDED) { - "This player is temporarily suspended" - } else { - it.message - } - ) - } - } - }.whenComplete { _, _ -> - // Always re-enable the button when we complete the future - modal.primaryButtonEnableStateOverride.set(true) - } - } -} diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/modals/CommunityRulesModal.kt b/gui/essential/src/main/kotlin/gg/essential/gui/modals/CommunityRulesModal.kt deleted file mode 100644 index 796db806..00000000 --- a/gui/essential/src/main/kotlin/gg/essential/gui/modals/CommunityRulesModal.kt +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.gui.modals - -import gg.essential.gui.EssentialPalette -import gg.essential.gui.common.OutlineButtonStyle -import gg.essential.gui.common.modal.EssentialModal2 -import gg.essential.gui.elementa.state.v2.combinators.not -import gg.essential.gui.elementa.state.v2.mutableStateOf -import gg.essential.gui.layoutdsl.Alignment -import gg.essential.gui.layoutdsl.Arrangement -import gg.essential.gui.layoutdsl.LayoutScope -import gg.essential.gui.layoutdsl.Modifier -import gg.essential.gui.layoutdsl.alignHorizontal -import gg.essential.gui.layoutdsl.alignVertical -import gg.essential.gui.layoutdsl.box -import gg.essential.gui.layoutdsl.checkboxAlt -import gg.essential.gui.layoutdsl.childBasedHeight -import gg.essential.gui.layoutdsl.color -import gg.essential.gui.layoutdsl.column -import gg.essential.gui.layoutdsl.fillWidth -import gg.essential.gui.layoutdsl.height -import gg.essential.gui.layoutdsl.hoverScope -import gg.essential.gui.layoutdsl.inheritHoverScope -import gg.essential.gui.layoutdsl.onLeftClick -import gg.essential.gui.layoutdsl.row -import gg.essential.gui.layoutdsl.shadow -import gg.essential.gui.layoutdsl.text -import gg.essential.gui.layoutdsl.underline -import gg.essential.gui.layoutdsl.whenHovered -import gg.essential.gui.layoutdsl.wrappedText -import gg.essential.gui.overlay.ModalFlow -import gg.essential.gui.overlay.ModalManager -import gg.essential.network.connectionmanager.social.RulesManager -import gg.essential.universal.USound -import gg.essential.util.openInBrowser -import gg.essential.vigilance.utils.onLeftClick -import java.net.URI - -class CommunityRulesModal( - modalManager: ModalManager, - private val rulesManager: RulesManager, - private val locale: String, - private val continuation: ModalFlow.ModalContinuation, - private val allowAccept: Boolean = true -) : EssentialModal2(modalManager) { - - private val agreeState = mutableStateOf(false) - private val awaitRulesResponseState = mutableStateOf(false) - - override fun onClose() { - super.onClose() - modalManager.queueModal(continuation.resumeImmediately(rulesManager.acceptedRules)) - } - - override fun LayoutScope.layoutTitle() { - title("Community & Chat Rules") - } - - override fun LayoutScope.layoutBody() { - column(Modifier.fillWidth(), Arrangement.spacedBy(13f)) { - wrappedText("Read our full {rules}\nand always follow them.", - textModifier = Modifier.color(EssentialPalette.TEXT).shadow(EssentialPalette.BLACK), - verticalArrangement = Arrangement.spacedBy(2f) - ) { - "rules" { - text("community rules", Modifier - .color(EssentialPalette.LINK) - .shadow(EssentialPalette.BLACK) - .hoverScope() - .whenHovered(Modifier.underline().color(EssentialPalette.LINK_HIGHLIGHT)) - .onLeftClick { - openInBrowser(URI.create("https://essential.gg/wiki/community-and-chat-rules")) - } - ) - } - } - bind(rulesManager.rules(locale)) { rules -> - rules(rules) - } - if (allowAccept) { - row(Modifier.hoverScope(), Arrangement.spacedBy(5f)) { - checkboxAlt(agreeState, Modifier.inheritHoverScope().shadow(EssentialPalette.BLACK), awaitRulesResponseState) - text( - "I've read and agree to all rules", Modifier - .color(EssentialPalette.TEXT_DISABLED) - .shadow(EssentialPalette.BLACK) - .alignVertical(Alignment.Center(true)) - ) - }.onLeftClick { click -> - click.stopPropagation() - if (!awaitRulesResponseState.getUntracked()) { - USound.playButtonPress() - agreeState.set { !it } - } - } - } - } - } - - override fun LayoutScope.layoutButtons() { - row(Arrangement.spacedBy(8f)) { - cancelButton("Back") { - close() - } - if (allowAccept) { - primaryButton("Submit", style = OutlineButtonStyle.BLUE, disabled = agreeState.not()) { - try { - awaitRulesResponseState.set(true) - if (rulesManager.acceptRules()) { - close() - } - } finally { - awaitRulesResponseState.set(false) - } - } - } - } - } - - private fun LayoutScope.rules(rules: List) { - box(Modifier.color(EssentialPalette.COMPONENT_BACKGROUND).shadow(EssentialPalette.BLACK).fillWidth(padding = 5f)) { - column(Modifier.fillWidth(padding = 16f).childBasedHeight(15f), Arrangement.spacedBy(7f)) { - for ((index, rule) in rules.withIndex()) { - text("${index + 1}. $rule", Modifier - .color(EssentialPalette.TEXT) - .shadow(EssentialPalette.BLACK) - .alignHorizontal(Alignment.Start) - ) - if (index < rules.lastIndex) { - box(Modifier.color(EssentialPalette.COMPONENT_BACKGROUND_HIGHLIGHT).fillWidth().height(1f)) - } - } - } - } - } -} - -suspend fun ModalFlow.communityRulesModal(rulesManager: RulesManager, locale: String, allowAccept: Boolean = true): Boolean = - awaitModal { CommunityRulesModal(modalManager, rulesManager, locale, it, allowAccept) } \ No newline at end of file diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/modals/DisconnectModal.kt b/gui/essential/src/main/kotlin/gg/essential/gui/modals/DisconnectModal.kt deleted file mode 100644 index 0917fcf9..00000000 --- a/gui/essential/src/main/kotlin/gg/essential/gui/modals/DisconnectModal.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.gui.modals - -import gg.essential.gui.EssentialPalette -import gg.essential.gui.common.OutlineButtonStyle -import gg.essential.gui.common.modal.EssentialModal2 -import gg.essential.gui.layoutdsl.LayoutScope -import gg.essential.gui.layoutdsl.Modifier -import gg.essential.gui.layoutdsl.color -import gg.essential.gui.layoutdsl.shadow -import gg.essential.gui.layoutdsl.text -import gg.essential.gui.overlay.ModalManager - -class DisconnectModal(modalManager: ModalManager, private val reason: String) : EssentialModal2(modalManager) { - - override fun LayoutScope.layoutTitle() { - title("Connection Lost", Modifier.color(EssentialPalette.MODAL_WARNING).shadow(EssentialPalette.BLACK)) - } - - override fun LayoutScope.layoutBody() { - text(reason, Modifier.color(EssentialPalette.TEXT).shadow(EssentialPalette.BLACK)) - } - - override fun LayoutScope.layoutButtons() { - primaryButton("Understood", style = OutlineButtonStyle.BLUE) { - close() - } - } - - companion object { - - const val HOST_SUSPENDED = "The host has been suspended." - } -} \ No newline at end of file diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/modals/ModalPrerequisites.kt b/gui/essential/src/main/kotlin/gg/essential/gui/modals/ModalPrerequisites.kt deleted file mode 100644 index 0ae197b5..00000000 --- a/gui/essential/src/main/kotlin/gg/essential/gui/modals/ModalPrerequisites.kt +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.gui.modals - -import gg.essential.gui.overlay.ModalFlow -import gg.essential.util.GuiEssentialPlatform.Companion.platform -import kotlinx.coroutines.CancellationException - -abstract class ModalPrerequisites { - - abstract suspend fun ModalFlow.doTermsOfServiceModal(): PrerequisiteResult - - abstract suspend fun ModalFlow.doRequiredUpdateModal(): PrerequisiteResult - - abstract suspend fun ModalFlow.doAuthenticationModal(): PrerequisiteResult - - abstract suspend fun ModalFlow.doCosmeticsModal(): PrerequisiteResult - - abstract suspend fun ModalFlow.doCommunityRulesModal(): PrerequisiteResult - - abstract suspend fun ModalFlow.doSocialSuspensionModal(): PrerequisiteResult - - abstract suspend fun ModalFlow.doPermanentSuspensionModal(): PrerequisiteResult - - protected fun Boolean.toResult(): PrerequisiteResult = if (this) PrerequisiteResult.SUCCESS else PrerequisiteResult.FAILURE -} - -suspend fun ModalFlow.ensurePrerequisites( - cosmetics: Boolean = false, - social: Boolean = false, - rules: Boolean = social -) { - with (platform.modalPrerequisites) { - val prerequisites = mutableListOf PrerequisiteResult>() - - prerequisites.add(suspend { doPermanentSuspensionModal() }) - - prerequisites.add(suspend { doTermsOfServiceModal() }) - prerequisites.add(suspend { doRequiredUpdateModal() }) - prerequisites.add(suspend { doAuthenticationModal() }) - - if (cosmetics) { - prerequisites.add(suspend { doCosmeticsModal() }) - } - - if (social) { - prerequisites.add(suspend { doSocialSuspensionModal() }) - } - - if (rules) { - prerequisites.add(suspend { doCommunityRulesModal() }) - } - - ensurePrerequisitesInternal(prerequisites) - } -} - -private suspend fun ensurePrerequisitesInternal(prerequisites: List PrerequisiteResult>) { - loop@ while (true) { - for (prerequisite in prerequisites) { - when (prerequisite()) { - PrerequisiteResult.SUCCESS -> continue@loop - PrerequisiteResult.FAILURE -> throw CancellationException() - PrerequisiteResult.PASS -> {} - } - } - break@loop - } -} - -enum class PrerequisiteResult { - - // For when the prerequisite is checked, and the user responded positively. Used to indicate that previous passed - // prerequisites should be tried again. - SUCCESS, - // For when the prerequisite modal is shown, and the user responded negatively, or it was a modal that cannot - // be a success. This will result in a CancellationException being thrown. - FAILURE, - // For when the prerequisite is not checked. Once all prerequisites return this, the overall check passes. - PASS, -} \ No newline at end of file diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/modals/SuspensionModal.kt b/gui/essential/src/main/kotlin/gg/essential/gui/modals/SuspensionModal.kt deleted file mode 100644 index bd95cc62..00000000 --- a/gui/essential/src/main/kotlin/gg/essential/gui/modals/SuspensionModal.kt +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.gui.modals - -import gg.essential.config.EssentialConfig -import gg.essential.gui.EssentialPalette -import gg.essential.gui.common.OutlineButtonStyle -import gg.essential.gui.common.SequenceAnimatedUIImage -import gg.essential.gui.common.modal.EssentialModal2 -import gg.essential.gui.common.textStyle -import gg.essential.gui.elementa.state.v2.withSystemTime -import gg.essential.gui.layoutdsl.Alignment -import gg.essential.gui.layoutdsl.Arrangement -import gg.essential.gui.layoutdsl.LayoutScope -import gg.essential.gui.layoutdsl.Modifier -import gg.essential.gui.layoutdsl.alignHorizontal -import gg.essential.gui.layoutdsl.box -import gg.essential.gui.layoutdsl.childBasedHeight -import gg.essential.gui.layoutdsl.childBasedWidth -import gg.essential.gui.layoutdsl.color -import gg.essential.gui.layoutdsl.column -import gg.essential.gui.layoutdsl.fillWidth -import gg.essential.gui.layoutdsl.image -import gg.essential.gui.layoutdsl.row -import gg.essential.gui.layoutdsl.shadow -import gg.essential.gui.layoutdsl.spacer -import gg.essential.gui.layoutdsl.text -import gg.essential.gui.layoutdsl.width -import gg.essential.gui.layoutdsl.wrappedText -import gg.essential.gui.overlay.ModalFlow -import gg.essential.gui.overlay.ModalManager -import gg.essential.network.connectionmanager.social.Suspension -import gg.essential.util.openInBrowser -import gg.essential.util.toShortString -import java.net.URI -import java.time.Instant -import java.util.concurrent.TimeUnit - -class SuspensionModal( - modalManager: ModalManager, - private val suspension: Suspension, - private val reasons: Map, - private val continuation: ModalFlow.ModalContinuation -) : EssentialModal2(modalManager) { - - val title = if (suspension.isTOSSuspension) { - if (suspension.isPermanent) { - permanentTOSSuspensionText - } else { - temporaryTOSSuspensionText - } - } else { - if (suspension.isPermanent) { - permanentSuspensionText - } else { - temporarySuspensionText - } - } - - override fun LayoutScope.layoutTitle() { - wrappedText(title, Modifier.color(EssentialPalette.RED).shadow(EssentialPalette.BLACK).width(200f), centered = true) - } - - override fun LayoutScope.layoutBody() { - if (suspension.isPermanent && suspension.isTOSSuspension) { - return - } - - column(Modifier.fillWidth(), Arrangement.spacedBy(13f)) { - if (!suspension.isTOSSuspension) { - wrappedText("Reason: ${reasons[suspension.reason]}", Modifier.color(EssentialPalette.TEXT_MID_GRAY).shadow(EssentialPalette.BLACK)) - } - if (suspension.expiresAt != null) { - timer(suspension.expiresAt) - } - } - } - - override fun LayoutScope.layoutButtons() { - row(Arrangement.spacedBy(8f)) { - outlineButton( - Modifier.width(91f).shadow(), - action = { - openInBrowser(URI.create("https://essential.gg/wiki/banned-accounts")) - } - ) { style -> - row(Arrangement.spacedBy(5f)) { - text("Help", Modifier.textStyle(style)) - image(EssentialPalette.ARROW_UP_RIGHT_5X5, Modifier.textStyle(style)) - } - } - primaryButton("Okay", style = OutlineButtonStyle.BLUE) { - close() - } - } - } - - override fun onClose() { - super.onClose() - if (suspension.isPermanent) { - EssentialConfig.acknowledgedPermanentSuspension.set(true) - } - modalManager.queueModal(continuation.resumeImmediately(Unit)) - } - - private fun LayoutScope.timer(expiresAt: Instant) { - box(Modifier.childBasedHeight(4f).childBasedWidth(12f).color(EssentialPalette.COMPONENT_BACKGROUND).shadow(EssentialPalette.BLACK)) { - column { - spacer(height = 1f) - row(Modifier.childBasedWidth().alignHorizontal(Alignment.Center(true)), Arrangement.spacedBy(5f)) { - SequenceAnimatedUIImage( - "/assets/essential/textures/studio/clock_", ".png", - 4, - 1000, - TimeUnit.MILLISECONDS, - )(Modifier.color(EssentialPalette.RED).shadow(EssentialPalette.BLACK)) - text( - { withSystemTime { it.until(expiresAt).toShortString() } }, - Modifier.color(EssentialPalette.TEXT_MID_GRAY).shadow(EssentialPalette.BLACK) - ) - } - } - } - } - - companion object { - - private val permanentSuspensionText = """ - You broke Essential’s community - guidelines and have been permanently - suspended from Essential - """.trimIndent() - - private val temporarySuspensionText = """ - You broke Essential's community - guidelines and have been temporarily - suspended from social features - """.trimIndent() - - private val permanentTOSSuspensionText = """ - You broke Essential’s terms of - service and have been permanently - suspended from Essential - """.trimIndent() - - private val temporaryTOSSuspensionText = """ - You broke Essential’s terms of - service and have been temporarily - suspended from Essential - """.trimIndent() - } -} \ No newline at end of file diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/modals/select/SelectModal.kt b/gui/essential/src/main/kotlin/gg/essential/gui/modals/select/SelectModal.kt index 3bf42ad5..d84d4b31 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/modals/select/SelectModal.kt +++ b/gui/essential/src/main/kotlin/gg/essential/gui/modals/select/SelectModal.kt @@ -49,7 +49,6 @@ private typealias SelectionListenerBlock = SelectModal.(identifier: T, sel */ class SelectModal( modalManager: ModalManager, - private val modalSimpleName: String, private val sections: List>, requiresButtonPress: Boolean, requiresSelection: Boolean, @@ -66,9 +65,6 @@ class SelectModal( private val sectionTitleScaleOffset = -1f - override val modalName: String - get() = "${super.modalName}-$modalSimpleName" - /** * Returns a list of selected identifiers */ diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/modals/select/builder.kt b/gui/essential/src/main/kotlin/gg/essential/gui/modals/select/builder.kt index 364f5285..bb4b0d74 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/modals/select/builder.kt +++ b/gui/essential/src/main/kotlin/gg/essential/gui/modals/select/builder.kt @@ -31,7 +31,6 @@ import gg.essential.gui.elementa.state.v2.combinators.map import gg.essential.gui.elementa.state.v2.combinators.not import gg.essential.gui.elementa.state.v2.filter import gg.essential.gui.elementa.state.v2.mapEach -import gg.essential.gui.elementa.state.v2.mapList import gg.essential.gui.elementa.state.v2.stateBy import gg.essential.gui.elementa.state.v2.stateOf import gg.essential.gui.elementa.state.v2.toListState @@ -72,7 +71,6 @@ data class Section( @Suppress("UNUSED_PARAMETER", "MemberVisibilityCanBePrivate") class SelectModalBuilder( val title: String, - val modalSimpleName: String, ) { private val socialStates by lazy { platform.createSocialStates() } private val relationshipStates by lazy { socialStates.relationships } @@ -207,7 +205,7 @@ class SelectModalBuilder( } fun friends(map: (UUID) -> T, block: SectionLayoutBlock = defaultUserRow) { - val friendsList = relationshipStates.getObservableFriendList().toStateV2List().mapList { list -> list.filter { !socialStates.isSuspended(it)() } } + val friendsList = relationshipStates.getObservableFriendList().toStateV2List() users("Friends", map, friendsList, block) if (whenEmpty == null) { emptyTextNoFriends() @@ -255,16 +253,7 @@ class SelectModalBuilder( map: (Channel) -> T, block: SectionLayoutBlock = defaultUserOrGroupRow, ) { - val channelList = messageStates.getObservableChannelList().toStateV2List().mapList { list -> - list.filter { - if (it.type == ChannelType.DIRECT_MESSAGE) { - val other = it.getOtherUser() - other == null || !socialStates.isSuspended(other)() - } else { - it.type == ChannelType.GROUP_DIRECT_MESSAGE - } - } - } + val channelList = messageStates.getObservableChannelList().toStateV2List().filter { it.type == ChannelType.DIRECT_MESSAGE || it.type == ChannelType.GROUP_DIRECT_MESSAGE } val friendsAndGroupsState = stateBy { // Adapted from ChatTab @@ -350,7 +339,6 @@ class SelectModalBuilder( fun build(modalManager: ModalManager) = SelectModal( modalManager, - modalSimpleName, sections, requiresButtonPress, requiresSelection, @@ -373,11 +361,7 @@ class SelectModalBuilder( return stateBy { mappedFriends().mapNotNull { (uuid, isActivity) -> if (isActivity()) { - if (!socialStates.isSuspended(uuid)()) { - uuid - } else { - null - } + uuid } else { null } @@ -414,9 +398,8 @@ fun SelectModalBuilder.friendsAndGroups(block: SectionLayoutBlock selectModal( modalManager: ModalManager, title: String, - modalSimpleName: String, block: SelectModalBuilder.() -> Unit = {} -) = SelectModalBuilder(title, modalSimpleName) +) = SelectModalBuilder(title) .apply(block) .build(modalManager) @@ -425,10 +408,9 @@ fun selectModal( */ suspend fun ModalFlow.selectModal( title: String, - modalSimpleName: String, block: SelectModalBuilder.() -> Unit = {} ): Set? = awaitModal { continuation -> - SelectModalBuilder(title, modalSimpleName) + SelectModalBuilder(title) .modalSettings { onPrimaryAction { result -> replaceWith(continuation.resumeImmediately(result)) } onCancel { button -> if (button) replaceWith(continuation.resumeImmediately(null)) } diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/notification/helpers.kt b/gui/essential/src/main/kotlin/gg/essential/gui/notification/helpers.kt index 58b62ab8..5ef69b87 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/notification/helpers.kt +++ b/gui/essential/src/main/kotlin/gg/essential/gui/notification/helpers.kt @@ -22,7 +22,6 @@ import gg.essential.sps.SpsAddress import gg.essential.util.CachedAvatarImage import gg.essential.util.GuiEssentialPlatform.Companion.platform import gg.essential.util.UuidNameLookup -import gg.essential.util.colored import gg.essential.util.thenAcceptOnMainThread import java.awt.Color import java.util.UUID @@ -71,9 +70,3 @@ fun sendSpsInviteNotification(uuid: UUID, name: String) { withCustomComponent(Slot.ACTION, button) } } - -fun sendOutgoingSpsInviteNotification(name: String) { - Notifications.push("", "") { - iconAndMarkdownBody(EssentialPalette.ENVELOPE_9X7.create(), "${name.colored(EssentialPalette.TEXT_HIGHLIGHT)} invited") - } -} diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/overlay/ModalFlow.kt b/gui/essential/src/main/kotlin/gg/essential/gui/overlay/ModalFlow.kt index 9061d51e..6a24702d 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/overlay/ModalFlow.kt +++ b/gui/essential/src/main/kotlin/gg/essential/gui/overlay/ModalFlow.kt @@ -55,10 +55,6 @@ fun launchModalFlow(modalManager: ModalManager, block: suspend ModalFlow.() -> U } } } - - override val modalName: String? - get() = null - override fun LayoutScope.layoutModal() {} override fun handleEscapeKeyPress() {} }) @@ -156,8 +152,6 @@ class ModalFlow(val modalManager: ModalManager) { fun resumeImmediately(result: T): Modal { return object : Modal(modalManager) { - override val modalName: String? - get() = null override fun onOpen() { super.onOpen() diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/screenshot/components/ScreenshotShareModal.kt b/gui/essential/src/main/kotlin/gg/essential/gui/screenshot/components/ScreenshotShareModal.kt index df2b8495..015d9f51 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/screenshot/components/ScreenshotShareModal.kt +++ b/gui/essential/src/main/kotlin/gg/essential/gui/screenshot/components/ScreenshotShareModal.kt @@ -12,7 +12,6 @@ package gg.essential.gui.screenshot.components import com.sparkuniverse.toolbox.chat.model.Channel -import gg.essential.gui.modals.ensurePrerequisites import gg.essential.gui.modals.select.friendsAndGroups import gg.essential.gui.modals.select.selectModal import gg.essential.gui.overlay.ModalFlow @@ -27,8 +26,6 @@ suspend fun ModalFlow.shareScreenshotModal( screenshot: ScreenshotId, metadata: ClientScreenshotMetadata? = null, ) { - ensurePrerequisites(social = true) - val selectedChannels = selectScreenshotShareTargetsModal()?.toList() ?: return val screenshotManager = platform.screenshotManager when (screenshot) { @@ -43,7 +40,7 @@ suspend fun ModalFlow.shareScreenshotModal( } suspend fun ModalFlow.selectScreenshotShareTargetsModal(): Set? { - return selectModal("Share picture", "SelectScreenshotShare") { + return selectModal("Share picture") { friendsAndGroups() modalSettings { diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/screenshot/utils.kt b/gui/essential/src/main/kotlin/gg/essential/gui/screenshot/utils.kt index ba44a905..1c9c3118 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/screenshot/utils.kt +++ b/gui/essential/src/main/kotlin/gg/essential/gui/screenshot/utils.kt @@ -12,8 +12,6 @@ package gg.essential.gui.screenshot import com.sparkuniverse.toolbox.util.DateTime -import gg.essential.api.gui.Slot -import gg.essential.gui.EssentialPalette import gg.essential.gui.common.modal.PropertiesModal import gg.essential.gui.elementa.state.v2.State import gg.essential.gui.elementa.state.v2.asyncMap @@ -37,7 +35,6 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.slf4j.LoggerFactory -import java.awt.image.BufferedImage import java.io.IOException import java.nio.file.Files import java.nio.file.Path @@ -46,10 +43,7 @@ import java.text.SimpleDateFormat import java.time.Instant import java.time.ZoneId import java.util.* -import javax.imageio.ImageIO import kotlin.io.path.deleteIfExists -import kotlin.io.path.exists -import kotlin.io.path.outputStream fun createDateOnlyCalendar(time: Long = System.currentTimeMillis()): Calendar { val date: Calendar = GregorianCalendar() @@ -180,18 +174,3 @@ fun copyScreenshotToClipboard(screenshot: Path) { } } } - -suspend fun saveImageAsScreenshot(image: BufferedImage) { - withContext(Dispatchers.IO) { - val folder = platform.screenshotFolder - val baseName = "saved_" + SCREENSHOT_DATETIME_FORMAT.format(Date()) - val file = (0..Int.MAX_VALUE).firstNotNullOf { i -> - folder.resolve(baseName + (if (i > 0) "_$i" else "") + ".png") - .takeUnless { it.exists() } - } - file.outputStream().use { ImageIO.write(image, "png", it) } - } - Notifications.push("Picture saved", "", action = { platform.openScreenshotBrowser() }) { - withCustomComponent(Slot.ICON, EssentialPalette.PICTURES_SHORT_9X7.create()) - } -} diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/skin/skinUtils.kt b/gui/essential/src/main/kotlin/gg/essential/gui/skin/skinUtils.kt index cb7b2c28..104a0236 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/skin/skinUtils.kt +++ b/gui/essential/src/main/kotlin/gg/essential/gui/skin/skinUtils.kt @@ -43,7 +43,7 @@ fun createSkinShareModal( onModalCancelled: (Boolean) -> Unit = {}, onComplete: () -> Unit = {} ): SelectModal { - return selectModal(modalManager, "Share Skin", "ShareSkin") { + return selectModal(modalManager, "Share Skin") { friendsAndGroups() modalSettings { diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/wardrobe/categories/CategoryComponent.kt b/gui/essential/src/main/kotlin/gg/essential/gui/wardrobe/categories/CategoryComponent.kt index dbdcd41b..da5c2fbb 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/wardrobe/categories/CategoryComponent.kt +++ b/gui/essential/src/main/kotlin/gg/essential/gui/wardrobe/categories/CategoryComponent.kt @@ -151,10 +151,9 @@ class CategoryComponent( if (previousCategory == category && highlightedItem == null) return@onSetValueAndNow previousCategory = category + updatingScrollBasedOnCategory = true fun doScroll() { - updatingScrollBasedOnCategory = true - scroller.animationFrame() // Force recalculate of position to avoid scrolling an incorrect amount if (category == this.category) return scroller.scrollToTop() @@ -175,10 +174,11 @@ class CategoryComponent( scroller.scrollToTopOf(target, offset = -CosmeticGroup.headerHeight) } - // Delay is needed because the effect which swaps out the current category component has undefined ordering - // compared to this effect, so we'll wait until before the next frame, by which point it will definitely - // have ran. - Window.enqueueRenderOperation(::doScroll) + // Double delay is needed because this component isn't added to the component tree until the next frame + // because WardrobeContainer calls layoutSafe() on the current category. Additionally, this listener + // on the state is called before the component is added to the component tree, so we need to wait for + // the next frame to scroll to the correct position. + Window.enqueueRenderOperation { Window.enqueueRenderOperation(::doScroll) } } scroller.addScrollAdjustEvent(false) { _, _ -> diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/wardrobe/components/gifting.kt b/gui/essential/src/main/kotlin/gg/essential/gui/wardrobe/components/gifting.kt index efa786ad..b3d13720 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/wardrobe/components/gifting.kt +++ b/gui/essential/src/main/kotlin/gg/essential/gui/wardrobe/components/gifting.kt @@ -30,7 +30,6 @@ import gg.essential.gui.elementa.state.v2.MutableState import gg.essential.gui.elementa.state.v2.State import gg.essential.gui.elementa.state.v2.addAll import gg.essential.gui.elementa.state.v2.collections.MutableTrackedList -import gg.essential.gui.elementa.state.v2.combinators.letState import gg.essential.gui.elementa.state.v2.combinators.map import gg.essential.gui.elementa.state.v2.combinators.not import gg.essential.gui.elementa.state.v2.isNotEmpty @@ -38,7 +37,6 @@ import gg.essential.gui.elementa.state.v2.memo import gg.essential.gui.elementa.state.v2.mutableListStateOf import gg.essential.gui.elementa.state.v2.mutableStateOf import gg.essential.gui.layoutdsl.* -import gg.essential.gui.modals.ensurePrerequisites import gg.essential.gui.modals.select.selectModal import gg.essential.gui.modals.select.users import gg.essential.gui.notification.Notifications @@ -98,7 +96,7 @@ fun openGiftModal(item: Item.CosmeticOrEmote, state: WardrobeState) { ServerCosmeticBulkRequestUnlockStateResponsePacket::class.java // FIXME workaround for feature-flag-processor eating the packet when (val packet = maybePacket.orElse(null)) { is ServerCosmeticBulkRequestUnlockStateResponsePacket -> { - validFriends.addAll(packet.unlockStates.filter { !it.value }.keys.toList().filter { !socialStates.isSuspended(it).getUntracked() }) + validFriends.addAll(packet.unlockStates.filter { !it.value }.keys.toList()) } else -> { showErrorToast("Something went wrong, please try again.") @@ -110,8 +108,6 @@ fun openGiftModal(item: Item.CosmeticOrEmote, state: WardrobeState) { } launchModalFlow(platform.createModalManager()) { - ensurePrerequisites(social = true) - val selectedUsers = selectFriendsToGiftModal(item, state, allFriends, validFriends, loadingFriends) ?: return@launchModalFlow val multiplier = selectedUsers.size @@ -119,9 +115,14 @@ fun openGiftModal(item: Item.CosmeticOrEmote, state: WardrobeState) { awaitModal { continuation -> PurchaseConfirmationModal( modalManager, - List(multiplier) { item to priceInfo.letState { it?.baseCost ?: 0 } }, - priceInfo.letState { (it?.realCost ?: 0) * multiplier } - ) { this.replaceWith(continuation.resumeImmediately(Unit)) } + List(multiplier) { item to priceInfo.map { it?.baseCost ?: 0 } }, + priceInfo.map { (it?.realCost ?: 0) * multiplier }, + {} + ).apply { + onPrimaryAction { + this.replaceWith(continuation.resumeImmediately(Unit)) + } + } } giftItemToFriends(item, selectedUsers, state) } @@ -222,7 +223,7 @@ suspend fun ModalFlow.selectFriendsToGiftModal( } return selectModal( - "Select friends to gift\nthem ${ChatColor.WHITE + item.name + ChatColor.RESET}.", "SelectFriendsToGift" + "Select friends to gift\nthem ${ChatColor.WHITE + item.name + ChatColor.RESET}." ) { modalSettings { primaryButtonText = "Purchase" diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/wardrobe/components/skinItemFunctions.kt b/gui/essential/src/main/kotlin/gg/essential/gui/wardrobe/components/skinItemFunctions.kt index a4a434df..3c9624be 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/wardrobe/components/skinItemFunctions.kt +++ b/gui/essential/src/main/kotlin/gg/essential/gui/wardrobe/components/skinItemFunctions.kt @@ -15,8 +15,6 @@ import gg.essential.elementa.components.Window import gg.essential.elementa.events.UIClickEvent import gg.essential.gui.EssentialPalette import gg.essential.gui.common.ContextOptionMenu -import gg.essential.gui.modals.ensurePrerequisites -import gg.essential.gui.overlay.launchModalFlow import gg.essential.gui.sendCheckmarkNotification import gg.essential.gui.skin.createSkinShareModal import gg.essential.gui.wardrobe.Item @@ -43,10 +41,7 @@ fun handleSkinRightClick(skin: Item.SkinItem, wardrobeState: WardrobeState, even sendCheckmarkNotification("Link copied to clipboard.") }, ContextOptionMenu.Option("Share", EssentialPalette.UPLOAD_9X) { - launchModalFlow(platform.createModalManager()) { - ensurePrerequisites(social = true) - modalManager.queueModal(createSkinShareModal(modalManager, skin)) - } + platform.pushModal { createSkinShareModal(it, skin) } }, ContextOptionMenu.Option(if (skin.isFavorite) "Remove Favorite" else "Favorite", EssentialPalette.HEART_7X6) { wardrobeState.skinsManager.setFavoriteState(skin.id, !skin.isFavorite) diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/wardrobe/configuration/ConfigurationUtils.kt b/gui/essential/src/main/kotlin/gg/essential/gui/wardrobe/configuration/ConfigurationUtils.kt index 3d4e2b85..09017101 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/wardrobe/configuration/ConfigurationUtils.kt +++ b/gui/essential/src/main/kotlin/gg/essential/gui/wardrobe/configuration/ConfigurationUtils.kt @@ -327,8 +327,9 @@ object ConfigurationUtils { items: ListState>, ) { val minChars = items.map { if (it.size > 20) 2 else 0 } + val inputTextState = input.textState.toV2() val options = memo { - val text = input.textState() + val text = inputTextState() if (text.length < minChars()) return@memo listOf() return@memo items().filter { it.first.contains(text, ignoreCase = true) || it.second.contains(text, ignoreCase = true) } }.toListState().mapEach { ContextOptionMenu.Option("${it.second} (${it.first})", null) { input.setText(it.first) } } diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/wardrobe/modals/CoinsPurchaseModal.kt b/gui/essential/src/main/kotlin/gg/essential/gui/wardrobe/modals/CoinsPurchaseModal.kt index fce393de..527a87a8 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/wardrobe/modals/CoinsPurchaseModal.kt +++ b/gui/essential/src/main/kotlin/gg/essential/gui/wardrobe/modals/CoinsPurchaseModal.kt @@ -19,13 +19,16 @@ import gg.essential.gui.EssentialPalette import gg.essential.gui.common.EssentialDropDown import gg.essential.gui.common.EssentialTooltip import gg.essential.gui.common.HighlightedBlock +import gg.essential.gui.common.and import gg.essential.gui.common.input.UITextInput import gg.essential.gui.common.input.essentialInput import gg.essential.gui.common.modal.Modal import gg.essential.gui.elementa.state.v2.* import gg.essential.gui.elementa.state.v2.combinators.* +import gg.essential.gui.elementa.state.v2.stateBy import gg.essential.gui.layoutdsl.* import gg.essential.gui.overlay.ModalManager +import gg.essential.gui.util.hoveredState import gg.essential.gui.wardrobe.WardrobeState import gg.essential.gui.wardrobe.components.coinPackImage import gg.essential.gui.wardrobe.components.coinsText @@ -58,13 +61,13 @@ class CoinsPurchaseModal private constructor( val coinsManager = state.coinsManager - val coinsNeededState = State { 0 } - val hasCoinsNeededState = coinsNeededState.letState { it > 0 } + val coinsNeededState = stateBy { 0 } + val hasCoinsNeededState = coinsNeededState.map { it > 0 } - val creatorCodeTooltip = coinsManager.creatorCodeName.letState { "Your purchase supports $it." } + val creatorCodeTooltip = coinsManager.creatorCodeName.map { "Your purchase supports $it." } val creatorCodeInput = UITextInput("Creator Code", shadowColor = EssentialPalette.BLACK).apply { - setText(coinsManager.creatorCode.getUntracked()) + setText(coinsManager.creatorCode.get()) onUpdate { newText -> val upper = newText.uppercase() // If not all uppercase, set it to uppercase. The resulting new update call will then actually save it @@ -77,18 +80,24 @@ class CoinsPurchaseModal private constructor( } // We disable the dropdown if we have more one or fewer currencies configured - val currencyDropdownDisabled = coinsManager.currencies.letState { it.size <= 1 } + val currencyDropdownDisabled = coinsManager.currencies.map { it.size <= 1 } val dropdown = EssentialDropDown( - coinsManager.currency.getUntracked() ?: USD_CURRENCY, + coinsManager.currency.get() ?: USD_CURRENCY, coinsManager.currencies.mapEach { EssentialDropDown.Option(it.currencyCode, it) }, disabled = currencyDropdownDisabled, ) - dropdown.selectedOption.onChange(this) { + dropdown.selectedOption.onSetValue(this) { coinsManager.currencyRaw.set(it.value.currencyCode) } + dropdown.bindEssentialTooltip( + dropdown.hoveredState() and currencyDropdownDisabled.toV1(this@CoinsPurchaseModal), + stateOf("Other currencies coming soon!").toV1(this@CoinsPurchaseModal), + EssentialTooltip.Position.ABOVE, + ) + fun LayoutScope.bundleBox(originalBundle: CoinBundle) { val bundleState = memo { val missingCoins = coinsNeededState() @@ -177,9 +186,9 @@ class CoinsPurchaseModal private constructor( spacer(height = 11f) box(Modifier.fillWidth().height(17f)) { row(Modifier.fillHeight().alignHorizontal(Alignment.Start), Arrangement.spacedBy(5f)) { - essentialInput(creatorCodeInput, coinsManager.creatorCodeValid.letState { it == false }, "Invalid Creator Code", Modifier.width(90f).fillHeight()) + essentialInput(creatorCodeInput, coinsManager.creatorCodeValid.map { it == false }, "Invalid Creator Code", Modifier.width(90f).fillHeight()) - if_({ coinsManager.creatorCodeValid() == true }) { + if_(coinsManager.creatorCodeValid.map { it == true }) { box(Modifier.width(13f).height(13f).color(EssentialPalette.GREEN_BUTTON_HOVER).shadow().hoverScope().hoverTooltip(creatorCodeTooltip.toV1(this.stateScope), position = EssentialTooltip.Position.ABOVE)) { image(EssentialPalette.CHECKMARK_7X5, Modifier.color(EssentialPalette.TEXT_HIGHLIGHT).shadow(EssentialPalette.TEXT_TRANSPARENT_SHADOW)) } @@ -188,13 +197,13 @@ class CoinsPurchaseModal private constructor( } } row(Modifier.fillHeight(), Arrangement.spacedBy(5f)) { - val minimumAmount = state.settings.youNeedMinimumAmount.getUntracked() + val minimumAmount = state.settings.youNeedMinimumAmount.get() text("Essential Coins", Modifier.alignVertical(Alignment.Center(true)).shadow(EssentialPalette.BLACK)) infoIcon("Unlock cosmetics and emotes with Essential Coins", position = EssentialTooltip.Position.ABOVE) } row(Modifier.fillHeight().alignHorizontal(Alignment.End), Arrangement.spacedBy(5f)) { - dropdown(Modifier.width(47f).shadow().hoverScope().whenTrue(currencyDropdownDisabled, Modifier.hoverTooltip(stateOf("Other currencies coming soon!"), position = EssentialTooltip.Position.ABOVE))) + dropdown(Modifier.width(47f).shadow()) box(Modifier.widthAspect(1f).fillHeight().color(EssentialPalette.COMPONENT_BACKGROUND_HIGHLIGHT).hoverColor(EssentialPalette.GRAY_BUTTON_HOVER).shadow().hoverScope()) { icon(EssentialPalette.CANCEL_5X, Modifier.color(EssentialPalette.TEXT)) }.onLeftClick { diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/wardrobe/modals/PurchaseConfirmationModal.kt b/gui/essential/src/main/kotlin/gg/essential/gui/wardrobe/modals/PurchaseConfirmationModal.kt index ece994d8..ea7c2b41 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/wardrobe/modals/PurchaseConfirmationModal.kt +++ b/gui/essential/src/main/kotlin/gg/essential/gui/wardrobe/modals/PurchaseConfirmationModal.kt @@ -11,10 +11,14 @@ */ package gg.essential.gui.wardrobe.modals +import gg.essential.elementa.constraints.SiblingConstraint +import gg.essential.elementa.dsl.constrain +import gg.essential.elementa.dsl.pixels +import gg.essential.elementa.state.BasicState import gg.essential.gui.EssentialPalette -import gg.essential.gui.common.modal.EssentialModal2 +import gg.essential.gui.common.modal.ConfirmDenyModal import gg.essential.gui.elementa.state.v2.State -import gg.essential.gui.elementa.state.v2.combinators.letState +import gg.essential.gui.elementa.state.v2.combinators.map import gg.essential.gui.elementa.state.v2.memo import gg.essential.gui.elementa.state.v2.stateOf import gg.essential.gui.layoutdsl.Arrangement @@ -23,18 +27,17 @@ import gg.essential.gui.layoutdsl.LayoutScope import gg.essential.gui.layoutdsl.Modifier import gg.essential.gui.layoutdsl.box import gg.essential.gui.layoutdsl.childBasedHeight -import gg.essential.gui.layoutdsl.childBasedMaxHeight import gg.essential.gui.layoutdsl.color import gg.essential.gui.layoutdsl.column import gg.essential.gui.layoutdsl.fillRemainingWidth import gg.essential.gui.layoutdsl.fillWidth import gg.essential.gui.layoutdsl.height import gg.essential.gui.layoutdsl.image +import gg.essential.gui.layoutdsl.layout import gg.essential.gui.layoutdsl.row import gg.essential.gui.layoutdsl.shadow import gg.essential.gui.layoutdsl.spacer import gg.essential.gui.layoutdsl.text -import gg.essential.gui.layoutdsl.width import gg.essential.gui.layoutdsl.wrappedText import gg.essential.gui.overlay.ModalManager import gg.essential.gui.wardrobe.Item @@ -42,32 +45,31 @@ import gg.essential.gui.wardrobe.WardrobeState import gg.essential.network.connectionmanager.coins.CoinsManager import java.awt.Color -// TODO: rewrite usages to use modalflow so passing primary action is no longer required class PurchaseConfirmationModal( modalManager: ModalManager, private val items: List>>, private val totalAmount: State, - private val primaryAction: PurchaseConfirmationModal.() -> Unit, -) : EssentialModal2(modalManager, requiresButtonPress = false) { + primaryAction: () -> Unit, +) : ConfirmDenyModal(modalManager, requiresButtonPress = false) { private val discountAmount = memo { items.sumOf { it.second() } - totalAmount() } - override fun LayoutScope.layoutContent(modifier: Modifier) { - column(Modifier.width(222f), Arrangement.spacedBy(14f)) { - box(Modifier.fillWidth(padding = 16f)) { - layoutTitle() - } - box(Modifier.fillWidth(padding = 16f).childBasedMaxHeight()) { - purchaseSummary() - } - box(Modifier.fillWidth(padding = 16f)) { - spacer(height = 3f) - layoutButtons() - } + init { + titleText = "Confirm your purchase!" + titleTextColor = EssentialPalette.TEXT + primaryButtonText = "Purchase" + + contentTextSpacingState.rebind(BasicState(0f)) + spacer.setHeight(17.pixels) + + customContent.constrain { + y = SiblingConstraint(14f) + } + + customContent.layout { + purchaseSummary() } - } - override fun LayoutScope.layoutTitle() { - text("Confirm your purchase!", Modifier.color(EssentialPalette.TEXT)) + onPrimaryAction(primaryAction) } private fun LayoutScope.purchaseSummary() { @@ -149,14 +151,6 @@ class PurchaseConfirmationModal( } } - override fun LayoutScope.layoutButtons() { - primaryAndCancelButtons( - "Purchase", - "Cancel", - { this@PurchaseConfirmationModal.primaryAction() } - ) - } - companion object { fun forEquippedItemsPurchasable(modalManager: ModalManager, state: WardrobeState, primaryAction: () -> Unit): PurchaseConfirmationModal { val itemsAndPriceInfo = state.equippedCosmeticsPurchasable.getUntracked().map { it to it.getPricingInfo(state) } @@ -164,9 +158,10 @@ class PurchaseConfirmationModal( return PurchaseConfirmationModal( modalManager, - itemsAndPriceInfo.map { (item, price) -> item to price.letState { it?.baseCost ?: 0 } }, + itemsAndPriceInfo.map { (item, price) -> item to price.map { it?.baseCost ?: 0 } }, totalCost, - ) { primaryAction() } + primaryAction, + ) } fun forItem( @@ -179,9 +174,10 @@ class PurchaseConfirmationModal( return PurchaseConfirmationModal( modalManager, - listOf(item to priceInfo.letState { it?.baseCost ?: 0 }), - priceInfo.letState { it?.realCost ?: 0 }, - ) { primaryAction() } + listOf(item to priceInfo.map { it?.baseCost ?: 0 }), + priceInfo.map { it?.realCost ?: 0 }, + primaryAction, + ) } fun forBundle(modalManager: ModalManager, bundle: Item.Bundle, state: WardrobeState, primaryAction: () -> Unit): PurchaseConfirmationModal { @@ -204,8 +200,9 @@ class PurchaseConfirmationModal( return PurchaseConfirmationModal( modalManager, itemsAndPriceInfo, - bundleInfo.letState { it?.realCost ?: 0 }, - ) { primaryAction() } + bundleInfo.map { it?.realCost ?: 0 }, + primaryAction, + ) } } } \ No newline at end of file diff --git a/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/cosmetics/EquippedOutfitsManager.kt b/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/cosmetics/EquippedOutfitsManager.kt index 1e1a5c92..afb6b5a5 100644 --- a/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/cosmetics/EquippedOutfitsManager.kt +++ b/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/cosmetics/EquippedOutfitsManager.kt @@ -22,7 +22,6 @@ interface EquippedOutfitsManager { fun getEquippedCosmeticsState(playerId: UUID): State fun getVisibleCosmeticsState(playerId: UUID): State> fun getSkin(playerId: UUID): Skin? // must be thread-safe - fun getCapeHash(playerId: UUID): String? // must be thread-safe data class Outfit( val cosmetics: Map, diff --git a/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/cosmetics/InfraEquippedOutfitsManager.kt b/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/cosmetics/InfraEquippedOutfitsManager.kt index a4d84501..8a4a69bb 100644 --- a/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/cosmetics/InfraEquippedOutfitsManager.kt +++ b/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/cosmetics/InfraEquippedOutfitsManager.kt @@ -124,9 +124,6 @@ class InfraEquippedOutfitsManager( override fun getSkin(playerId: UUID): Skin? = managerImpl.getSkin(playerId) - override fun getCapeHash(playerId: UUID): String?= - managerImpl.getCapeHash(playerId) - fun update(playerId: UUID, outfit: InfraOutfit) { if (subscriptionManager.isSubscribedOrSelf(playerId)) { infraCosmeticsData.requestCosmeticsIfMissing(outfit.cosmetics.values) diff --git a/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/cosmetics/StateBasedEquippedOutfitsManager.kt b/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/cosmetics/StateBasedEquippedOutfitsManager.kt index 22e51f07..d5167f95 100644 --- a/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/cosmetics/StateBasedEquippedOutfitsManager.kt +++ b/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/cosmetics/StateBasedEquippedOutfitsManager.kt @@ -98,19 +98,4 @@ class StateBasedEquippedOutfitsManager( } } override fun getSkin(playerId: UUID): Skin? = skins[playerId] - - // Similar to getSkin, getCapeHash must also be thread safe as it's called from the same place - private var capes = emptyMap() - init { - val capeStates = managedPlayers.toList().mapEach { uuid -> - uuid to getEquippedCosmeticsState(uuid).map { it.cosmetics[CosmeticSlot.CAPE]?.id } - } - effect(refHolder) { - capes = capeStates().associateNotNull { (uuid, capeState) -> - val cape = capeState() ?: return@associateNotNull null - uuid to cape - } - } - } - override fun getCapeHash(playerId: UUID): String? = capes[playerId] } diff --git a/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/media/IScreenshotManager.kt b/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/media/IScreenshotManager.kt index 9afa1075..82dd40ab 100644 --- a/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/media/IScreenshotManager.kt +++ b/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/media/IScreenshotManager.kt @@ -15,14 +15,12 @@ import com.sparkuniverse.toolbox.chat.model.Channel import gg.essential.gui.elementa.state.v2.ListState import gg.essential.gui.screenshot.ScreenshotId import gg.essential.gui.screenshot.ScreenshotInfo -import gg.essential.gui.screenshot.ScreenshotUploadToast import gg.essential.handlers.screenshot.ClientScreenshotMetadata import gg.essential.media.model.Media import java.awt.image.BufferedImage import java.io.File import java.nio.file.Path import java.util.concurrent.CompletableFuture -import java.util.function.Consumer interface IScreenshotManager { val screenshotFolder: Path @@ -40,8 +38,6 @@ interface IScreenshotManager { fun handleScreenshotEdited(source: ScreenshotId, originalMetadata: ClientScreenshotMetadata, screenshot: BufferedImage, favorite: Boolean): File - fun upload(path: Path, metadata: ClientScreenshotMetadata?, onProgress: Consumer): CompletableFuture - fun uploadAndCopyLinkToClipboard(path: Path): CompletableFuture fun uploadAndCopyLinkToClipboard(path: Path, metadata: ClientScreenshotMetadata?): CompletableFuture fun copyLinkToClipboard(media: Media) @@ -49,6 +45,4 @@ interface IScreenshotManager { fun uploadAndShareLinkToChannels(channels: List, path: Path): CompletableFuture fun uploadAndShareLinkToChannels(channels: List,path: Path, metadata: ClientScreenshotMetadata?): CompletableFuture fun shareLinkToChannels(channels: List, media: Media) - - fun getUploadedLocalPathsCache(mediaId: String): List } diff --git a/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/social/ProfileSuspension.kt b/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/social/ProfileSuspension.kt deleted file mode 100644 index a27b6a4b..00000000 --- a/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/social/ProfileSuspension.kt +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.network.connectionmanager.social - -import gg.essential.connectionmanager.common.model.profile.ProfilePunishmentStatus -import gg.essential.gui.elementa.state.v2.ObservedInstant -import gg.essential.network.connectionmanager.common.model.profile.PunishmentType -import java.time.Instant -import java.util.* - -sealed interface ProfileSuspension { - - data class PermanentBan(override val user: UUID) : ProfileSuspension { - - override fun isActive(now: ObservedInstant) = true - - override fun isActiveNow() = true - } - - data class SocialBan(override val user: UUID, val expiresAt: Instant) : ProfileSuspension { - - override fun isActive(now: ObservedInstant) = now.isBefore(expiresAt) - - override fun isActiveNow() = expiresAt.isAfter(Instant.now()) - } - - val user: UUID - - fun isActive(now: ObservedInstant): Boolean - - fun isActiveNow(): Boolean - - companion object { - - fun fromInfra(user: UUID, punishment: ProfilePunishmentStatus): ProfileSuspension { - return when (punishment.punishmentType) { - PunishmentType.PERMANENT_BAN -> PermanentBan(user) - PunishmentType.SOCIAL_BAN -> SocialBan(user, Instant.ofEpochMilli(punishment.expiresAt ?: 0L)) - } - } - } -} \ No newline at end of file diff --git a/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/social/RulesManager.kt b/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/social/RulesManager.kt deleted file mode 100644 index af5ba886..00000000 --- a/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/social/RulesManager.kt +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.network.connectionmanager.social - -import gg.essential.connectionmanager.common.packet.response.ResponseActionPacket -import gg.essential.connectionmanager.common.packet.social.ClientCommunityRulesAgreedPacket -import gg.essential.connectionmanager.common.packet.social.ServerCommunityRulesStatePacket -import gg.essential.gui.elementa.state.v2.State -import gg.essential.gui.elementa.state.v2.combinators.letState -import gg.essential.gui.elementa.state.v2.memo -import gg.essential.gui.elementa.state.v2.mutableStateOf -import gg.essential.network.CMConnection -import gg.essential.network.connectionmanager.NetworkedManager -import gg.essential.network.registerPacketHandler - -class RulesManager(private val connectionManager: CMConnection) : NetworkedManager { - - private var rules = mutableStateOf(null) - private val communityRules = rules.letState { it?.rules ?: listOf() }.memo() - val isLoaded: State = rules.letState { it != null } - val hasRules: State = communityRules.letState { it.isNotEmpty() } - - var acceptedRules = false - private set - - init { - connectionManager.registerPacketHandler { packet -> - rules.set(Rules(packet.rules)) - acceptedRules = packet.accepted - } - } - - override fun onConnected() { - super.onConnected() - - if (connectionManager.usingProtocol < REQUIRED_PROTOCOL) { - rules.set(Rules()) - } - } - - suspend fun acceptRules(): Boolean { - acceptedRules = connectionManager.call(ClientCommunityRulesAgreedPacket()).exponentialBackoff().await().isSuccessful - return acceptedRules - } - - @Override - override fun resetState() { - rules.set(null) - acceptedRules = false - } - - fun rules(preferredLocale: String): State> = State { - communityRules().mapNotNull { it[preferredLocale] ?: it["en_us"] } - } - - private data class Rules(val rules: List> = listOf()) - - private companion object { - - const val REQUIRED_PROTOCOL = 9 - } -} diff --git a/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/social/Suspension.kt b/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/social/Suspension.kt deleted file mode 100644 index 774e060e..00000000 --- a/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/social/Suspension.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.network.connectionmanager.social - -import gg.essential.gui.elementa.state.v2.ObservedInstant -import gg.essential.model.util.Instant - -data class Suspension(val reason: String, val expiresAt: Instant?, val unseen: Boolean = false) { - - fun isActive(now: ObservedInstant) = expiresAt == null || now.isBefore(expiresAt) - fun isActiveNow() = expiresAt?.isAfter(Instant.now()) ?: true - - val isPermanent: Boolean - get() = expiresAt == null - - val isTOSSuspension: Boolean - get() = reason == USER_BROKE_TOS_REASON - - companion object { - - const val USER_BROKE_TOS_REASON = "TOS_BAN" - } -} \ No newline at end of file diff --git a/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/suspension/SuspensionManager.kt b/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/suspension/SuspensionManager.kt deleted file mode 100644 index 7f6cfa8d..00000000 --- a/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/suspension/SuspensionManager.kt +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.network.connectionmanager.suspension - -import gg.essential.config.EssentialConfig -import gg.essential.connectionmanager.common.packet.social.ServerSocialSuspensionStatePacket -import gg.essential.gui.elementa.state.v2.ReferenceHolderImpl -import gg.essential.gui.elementa.state.v2.State -import gg.essential.gui.elementa.state.v2.combinators.letState -import gg.essential.gui.elementa.state.v2.effect -import gg.essential.gui.elementa.state.v2.mutableStateOf -import gg.essential.gui.elementa.state.v2.stateUsingSystemTime -import gg.essential.network.CMConnection -import gg.essential.network.connectionmanager.NetworkedManager -import gg.essential.network.connectionmanager.social.Suspension -import gg.essential.network.registerPacketHandler - -abstract class SuspensionManager(protected val connectionManager: CMConnection) : NetworkedManager { - - private val referenceHolder = ReferenceHolderImpl() - - private var suspension = mutableStateOf(null) - val activeSuspension: State = stateUsingSystemTime { now -> suspension()?.value?.takeIf { it.isActive(now) } } - val isLoaded: State = suspension.letState { it != null } - - init { - connectionManager.registerPacketHandler { packet -> - if (packet.isSuspended) { - suspension.set(SuspensionStatus(Suspension(packet.reason, packet.expiresAt, packet.isRecentlyStarted))) - } else { - suspension.set(SuspensionStatus(null)) - } - EssentialConfig.acknowledgedPermanentSuspension.set(false) - } - - effect(referenceHolder) { - activeSuspension()?.let { suspension -> - if (suspension.unseen && isSuspensionShowable()) { - showSuspension(suspension) - } - } - } - } - - fun setPermanentlySuspended(reason: String) { - suspension.set(SuspensionStatus(Suspension(reason, null, !EssentialConfig.acknowledgedPermanentSuspension.getUntracked()))) - } - - override fun onConnected() { - if (connectionManager.usingProtocol < REQUIRED_PROTOCOL) { - suspension.set(SuspensionStatus(null)) - EssentialConfig.acknowledgedPermanentSuspension.set(false) - } - } - - protected abstract fun isSuspensionShowable(): Boolean - - protected abstract fun showSuspension(suspension: Suspension) - - fun markSeen() { - suspension.set { it?.copy(unseen = false) } - } - - @Override - override fun resetState() { - suspension.set(null) - } - - private data class SuspensionStatus(val value: Suspension?) { - - fun copy(unseen: Boolean) = SuspensionStatus(value?.copy(unseen = unseen)) - } - - private companion object { - - const val REQUIRED_PROTOCOL = 9 - } -} diff --git a/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/telemetry/FeatureSessionTelemetry.kt b/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/telemetry/FeatureSessionTelemetry.kt deleted file mode 100644 index 27e81882..00000000 --- a/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/telemetry/FeatureSessionTelemetry.kt +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.network.connectionmanager.telemetry - -import gg.essential.connectionmanager.common.packet.telemetry.ClientTelemetryPacket -import gg.essential.handlers.ShutdownHook -import gg.essential.util.GuiEssentialPlatform.Companion.platform - -typealias FeatureName = String - -object FeatureSessionTelemetry { - - private var startTime: Long = 0L - private val featureSessionEvents = mutableListOf() - - fun start() { - featureSessionEvents.clear() - startTime = System.currentTimeMillis() - ShutdownHook.INSTANCE.register { end() } - } - - fun startEvent(featureName: FeatureName) { - featureSessionEvents.add(FeatureSessionEvent(featureName, true, System.currentTimeMillis())) - } - - fun endEvent(featureName: FeatureName) { - featureSessionEvents.add(FeatureSessionEvent(featureName, false, System.currentTimeMillis())) - } - - private fun end() { - val endTime = System.currentTimeMillis() - sendDetailedTimings(endTime) - sendSimplifiedTimings(endTime) - } - - private fun sendDetailedTimings(endTime: Long) { - val events = featureSessionEvents.take(5000) - platform.enqueueTelemetry( - ClientTelemetryPacket( - "FEATURE_SESSION_DETAILED_TIMINGS", mapOf( - "startTime" to startTime, - "endTime" to endTime, - "featureSessionEvents" to events, - "wasLimited" to (events.size < featureSessionEvents.size) - ) - ) - ) - } - - - private fun sendSimplifiedTimings(endTime: Long) { - val activeEvents = mutableListOf() - val featureSessionTimes = mutableMapOf() - var last: Long? = null - - for (event in featureSessionEvents) { - if (last != null && activeEvents.isNotEmpty()) { - val top = activeEvents.last() - featureSessionTimes[top.featureName] = (featureSessionTimes[top.featureName] ?: 0L) + (event.time - last) - } - - if (event.isStart) { - activeEvents.add(event) - } else { - val eventToRemove = activeEvents.findLast { it.featureName == event.featureName } - eventToRemove?.let { activeEvents.remove(it) } - } - - last = event.time - } - - platform.enqueueTelemetry( - ClientTelemetryPacket( - "FEATURE_SESSION_TIMINGS", mapOf( - "startTime" to startTime, - "endTime" to endTime, - "featureSessionTimes" to featureSessionTimes - ) - ) - ) - } - - data class FeatureSessionEvent( - val featureName: FeatureName, - val isStart: Boolean, - val time: Long, - ) - -} \ No newline at end of file diff --git a/gui/essential/src/main/kotlin/gg/essential/sps/TPSSessionMonitor.kt b/gui/essential/src/main/kotlin/gg/essential/sps/TPSSessionMonitor.kt deleted file mode 100644 index ab18c30f..00000000 --- a/gui/essential/src/main/kotlin/gg/essential/sps/TPSSessionMonitor.kt +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.sps - -import kotlin.math.max -import kotlin.math.min - -class TPSSessionMonitor { - - private val allSession = Session() - private val windowedSession = Session() - - private var minTPS: Float = Float.MAX_VALUE - private var maxTPS: Float = 0f - - fun tick() { - val now = System.nanoTime() - allSession.tick(now) - windowedSession.tick(now) - // Get results and reset windowed session after 100 ticks (~5s) - if (windowedSession.tickCount >= 100) { - val windowedTPS = windowedSession.getAverageTPS() - minTPS = min(minTPS, windowedTPS) - maxTPS = max(maxTPS, windowedTPS) - windowedSession.reset() - } - } - - fun getAverageTPS(): Float { - return allSession.getAverageTPS() - } - - fun getMinTPS(): Float { - if (minTPS == Float.MAX_VALUE) return getAverageTPS() - return minTPS - } - - fun getMaxTPS(): Float { - if (maxTPS == 0f) return getAverageTPS() - return maxTPS - } - - class Session { - var tickCount: Long = 0L - // Time in nanoseconds - private var firstTickTime: Long = 0L - private var lastTickTime: Long = 0L - - init { - reset() - } - - fun tick(now: Long) { - if (firstTickTime == 0L) { - firstTickTime = now - lastTickTime = now - return - } - lastTickTime = now - tickCount++ - } - - fun getAverageTPS(): Float { - val totalTime = lastTickTime - firstTickTime - if (totalTime == 0L || tickCount == 0L) return 0F - val averageTimePerTick = (totalTime / tickCount).toDouble() - return (1_000_000_000.0 / averageTimePerTick).toFloat() - } - - fun reset() { - tickCount = 0L - firstTickTime = 0L - lastTickTime = 0L - } - } -} \ No newline at end of file diff --git a/gui/essential/src/main/kotlin/gg/essential/util/ServerInfoPing.kt b/gui/essential/src/main/kotlin/gg/essential/util/ServerInfoPing.kt deleted file mode 100644 index ec1bbcc1..00000000 --- a/gui/essential/src/main/kotlin/gg/essential/util/ServerInfoPing.kt +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.util - -import com.google.gson.JsonElement -import com.google.gson.JsonObject -import com.google.gson.JsonParser -import gg.essential.connectionmanager.common.packet.pingproxy.ClientPingProxyPacket -import gg.essential.connectionmanager.common.packet.pingproxy.ServerPingProxyResponsePacket -import gg.essential.util.GuiEssentialPlatform.Companion.platform -import org.slf4j.LoggerFactory -import java.awt.image.BufferedImage -import java.util.* -import javax.imageio.ImageIO - -class ServerPingInfo( - val host: String, - val port: Int, - val onlinePlayers: Int, - val maxPlayers: Int, - val version: String, - val description: JsonElement, - val iconBytes: ByteArray?, -) { - /** Name of the world. Assumes description to be formatted like vanilla. `null` if parsing fails. */ - val worldName: String? - get() = try { - if (description is JsonObject) { - description.get("text").asString - } else { - description.asString - }.split(" - ").drop(1).joinToString(" - ") - } catch (exception: Exception) { - LOGGER.warn("Failed to parse world name of `$host:$port`", exception) - null - } - - val iconImage: BufferedImage? by lazy { - try { - iconBytes?.let { ImageIO.read(it.inputStream()) } - } catch (exception: Exception) { - LOGGER.warn("Failed to decode icon of `$host:$port`", exception) - null - } - } - - companion object { - private val LOGGER = LoggerFactory.getLogger(ServerPingInfo::class.java) - - suspend fun fetchViaPingProxy(address: String): ServerPingInfo? { - val (host, port) = platform.splitHostAndPort(address) - return fetchViaPingProxy(host, port) - } - - suspend fun fetchViaPingProxy(host: String, port: Int): ServerPingInfo? { - try { - val request = ClientPingProxyPacket(host, port, platform.mcProtocolVersion) - val response = platform.cmConnection.call(request).await() - ?: return null - - val json = JsonParser().parse(response.rawJson).asJsonObject - - val players = json.get("players").asJsonObject - val onlinePlayers = players.get("online").asInt - val maxPlayers = players.get("max").asInt - val version = json.get("version").asJsonObject.get("name").asString - val description = json.get("description") - val icon = json.get("favicon")?.asString?.let { iconStr -> - val prefix = "data:image/png;base64," - if (iconStr.startsWith(prefix)) { - val base64 = iconStr.substring(prefix.length) - // older versions have the base64 string split over multiple lines - .replace("\n", "") - Base64.getDecoder().decode(base64) - } else { - LOGGER.warn("Don't know how to decode icon from `$host:$port`: `$iconStr`") - null - } - } - return ServerPingInfo(host, port, onlinePlayers, maxPlayers, version, description, icon) - } catch (exception: Exception) { - LOGGER.warn("Failed to fetch server ping info from `$host:$port", exception) - return null - } - } - } -} diff --git a/gui/essential/src/main/kotlin/gg/essential/util/channelExtensions.kt b/gui/essential/src/main/kotlin/gg/essential/util/channelExtensions.kt deleted file mode 100644 index 55d03d70..00000000 --- a/gui/essential/src/main/kotlin/gg/essential/util/channelExtensions.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.util - -import com.sparkuniverse.toolbox.chat.enums.ChannelType -import com.sparkuniverse.toolbox.chat.model.Channel -import java.util.UUID - -fun Channel.getOtherUser(): UUID? = - if (type == ChannelType.DIRECT_MESSAGE) members.firstOrNull { it != USession.activeNow().uuid } else null - -private val BOT_UUID = UUID.fromString("cd899a14-de78-4de8-8d31-9d42fff31d7a") // EssentialBot -fun Channel.isAnnouncement(): Boolean = - this.type == ChannelType.ANNOUNCEMENT || BOT_UUID in members - diff --git a/gui/essential/src/main/kotlin/gg/essential/util/essentialGuiExtensions.kt b/gui/essential/src/main/kotlin/gg/essential/util/essentialGuiExtensions.kt index cb4c91db..9de233c4 100644 --- a/gui/essential/src/main/kotlin/gg/essential/util/essentialGuiExtensions.kt +++ b/gui/essential/src/main/kotlin/gg/essential/util/essentialGuiExtensions.kt @@ -29,7 +29,6 @@ import gg.essential.elementa.utils.withAlpha import gg.essential.gui.EssentialPalette import gg.essential.gui.common.AbstractTooltip import gg.essential.gui.common.EssentialTooltip -import gg.essential.gui.common.ImageLoadCallback import gg.essential.gui.common.LayoutDslTooltip import gg.essential.gui.common.bindParent import gg.essential.gui.common.constraints.DivisionConstraint @@ -43,7 +42,7 @@ import gg.essential.gui.elementa.state.v2.combinators.map import gg.essential.gui.elementa.state.v2.utils.toState import gg.essential.gui.image.ImageFactory import gg.essential.gui.layoutdsl.* -import gg.essential.gui.util.hoveredStateV2 +import gg.essential.gui.util.hoveredState import gg.essential.gui.util.isComponentInParentChain import gg.essential.universal.UMatrixStack import gg.essential.universal.UMouse @@ -53,19 +52,14 @@ import kotlinx.coroutines.asExecutor import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.Logger import java.awt.Color -import java.awt.image.BufferedImage import java.util.concurrent.CompletableFuture -import kotlin.coroutines.resume -import kotlin.coroutines.suspendCoroutine -@Deprecated("Use LayoutDSL instead") infix fun T.hiddenChildOf(parent: UIComponent) = apply { parent.addChild(this) hide(instantly = true) } -@Deprecated("Use `Modifier.animateColor` instead") fun T.animateColor( color: ColorConstraint, time: Float = .3f, @@ -76,8 +70,6 @@ fun T.animateColor( } } -@Deprecated("Use `Modifier.animateColor` instead") -@Suppress("DEPRECATION") fun T.animateColor( color: Color, time: Float = .3f, @@ -141,13 +133,6 @@ fun UIComponent.createEssentialTooltip( /** * @param windowPadding Sets the padding from the window borders. The tooltip will be constrained to stay within the window+padding. Disabled if null */ -@Deprecated("Replace with StateV2 version", - ReplaceWith( - "component.bindEssentialTooltip(display.toV2(), tooltipContent.toV2(), position, padding, wrapAtWidth, configure, windowPadding)", - "gg.essential.gui.elementa.state.v2.toV2" - ) -) -@Suppress("DEPRECATION") fun T.bindEssentialTooltip( display: State, tooltipContent: State, @@ -156,31 +141,16 @@ fun T.bindEssentialTooltip( wrapAtWidth: Float? = null, configure: UIText.() -> Unit = {}, windowPadding: Float? = null, -): T = bindEssentialTooltip(display.toV2(), tooltipContent.toV2(), position, padding, wrapAtWidth, configure, windowPadding) - -/** - * @param windowPadding Sets the padding from the window borders. The tooltip will be constrained to stay within the window+padding. Disabled if null - */ -@Deprecated("Use `Modifier.whenTrue` + `Modifier.tooltip` instead") -fun T.bindEssentialTooltip( - display: StateV2, - tooltipContent: StateV2, - position: EssentialTooltip.Position = EssentialTooltip.Position.BELOW, - padding: Float = 5f, - wrapAtWidth: Float? = null, - configure: UIText.() -> Unit = {}, - windowPadding: Float? = null, -): T = apply { +): T { val tooltip = createEssentialTooltip(tooltipContent, position, padding, wrapAtWidth, configure, windowPadding) tooltip.bindVisibility(display) + return this } /** * @param windowPadding Sets the padding from the window borders. The tooltip will be constrained to stay within the window+padding. Disabled if null */ @JvmOverloads -@Deprecated("Use `Modifier.hoverTooltip` instead") -@Suppress("DEPRECATION") fun T.bindHoverEssentialTooltip( tooltipContent: State, position: EssentialTooltip.Position = EssentialTooltip.Position.BELOW, @@ -189,7 +159,7 @@ fun T.bindHoverEssentialTooltip( configure: UIText.() -> Unit = {}, windowPadding: Float? = null, ): T { - return bindEssentialTooltip(hoveredStateV2(), tooltipContent.toV2(), position, padding, wrapAtWidth, configure, windowPadding) + return bindEssentialTooltip(hoveredState(), tooltipContent, position, padding, wrapAtWidth, configure, windowPadding) } private fun UIComponent.positionTooltip( @@ -248,7 +218,6 @@ private fun UIComponent.positionTooltip( } } -@Deprecated("Use LayoutDSL instead, centering is the default behavior for LayoutDSL containers") fun T.centered(): T = apply { constrain { x = CenterConstraint() @@ -542,10 +511,3 @@ fun Color.darker(percentage: Float): Color { alpha ) } - -suspend fun loadUIImage(image: BufferedImage): UIImage = suspendCoroutine { continuation -> - val uiImage = UIImage(CompletableFuture.completedFuture(image)) - uiImage.supply(ImageLoadCallback { - continuation.resume(uiImage) - }) -} diff --git a/gui/essential/src/main/kotlin/gg/essential/util/guiEssentialPlatform.kt b/gui/essential/src/main/kotlin/gg/essential/util/guiEssentialPlatform.kt index e06fdc93..1f1af8df 100644 --- a/gui/essential/src/main/kotlin/gg/essential/util/guiEssentialPlatform.kt +++ b/gui/essential/src/main/kotlin/gg/essential/util/guiEssentialPlatform.kt @@ -20,7 +20,6 @@ import gg.essential.gui.elementa.state.v2.MutableState import gg.essential.gui.elementa.state.v2.State import gg.essential.gui.friends.message.v2.MessageRef import gg.essential.gui.friends.state.SocialStates -import gg.essential.gui.modals.ModalPrerequisites import gg.essential.gui.notification.NotificationsManager import gg.essential.gui.overlay.ModalManager import gg.essential.gui.overlay.OverlayManager @@ -35,9 +34,7 @@ import gg.essential.mod.cosmetics.preview.PerspectiveCamera import gg.essential.model.backend.RenderBackend import gg.essential.network.CMConnection import gg.essential.network.connectionmanager.cosmetics.AssetLoader -import gg.essential.network.connectionmanager.cosmetics.ICosmeticsManager import gg.essential.network.connectionmanager.cosmetics.ModelLoader -import gg.essential.network.connectionmanager.cosmetics.WardrobeSettings import gg.essential.network.connectionmanager.features.DisabledFeaturesManager import gg.essential.network.connectionmanager.media.IScreenshotManager import gg.essential.network.connectionmanager.notices.INoticesManager @@ -49,10 +46,8 @@ import gg.essential.util.image.bitmap.MutableBitmap import gg.essential.util.lwjgl3.Lwjgl3Loader import io.netty.buffer.ByteBuf import kotlinx.coroutines.CoroutineDispatcher -import java.awt.image.BufferedImage import java.io.IOException import java.io.InputStream -import java.net.InetAddress import java.nio.file.Path import java.util.UUID import kotlin.jvm.Throws @@ -97,16 +92,11 @@ interface GuiEssentialPlatform { fun getGlId(identifier: UIdentifier): Int fun playSound(identifier: UIdentifier) - fun playNoteHatSound(volume: Float, pitch: Float) fun registerCosmeticTexture(name: String, texture: ReleasedDynamicTexture): UIdentifier fun dismissModalOnScreenChange(modal: Modal, dismiss: () -> Unit) - fun registerEventBusListener(cls: Class, listener: (T) -> Unit, priority: Int = 0) - - fun unregisterEventBusListener(cls: Class, listener: (T) -> Unit) - val essentialBaseDir: Path val config: Config @@ -121,36 +111,22 @@ interface GuiEssentialPlatform { fun resolveMessageRef(messageRef: MessageRef) - // TODO inline once everything's accessible - fun haveActiveRemoteSpsSession(address: String): Boolean - val essentialUriListener: EssentialMarkdown.(EssentialMarkdown.LinkClickEvent) -> Unit val noticesManager: INoticesManager val skinsManager: SkinsManager - val cosmeticsManager: ICosmeticsManager - - val wardrobeSettings: WardrobeSettings - val mojangSkinManager: MojangSkinManager val screenshotManager: IScreenshotManager val disabledFeaturesManager: DisabledFeaturesManager - val screenshotFolder: Path - val isOptiFineInstalled: Boolean val trustedHosts: Set - val reportReasons: Map - - // TODO move to :gui:essential project - fun fileReport(modalManager: ModalManager, channelId: Long, messageId: Long, sender: UUID, reason: String) - fun enqueueTelemetry(packet: ClientTelemetryPacket) fun findCodeSource(javaClass: Class<*>): CodeSource? @@ -184,24 +160,12 @@ interface GuiEssentialPlatform { fun openSocialMenu(channelId: Long? = null) - fun openScreenshotBrowser() - fun connectToServer(name: String, address: String) val openEmoteWheelKeybind: Keybind fun restoreMcStateAfterNanoVGDrawCall() - fun splitHostAndPort(address: String, defaultPort: Int = 25565): Pair - - /** Parses the given IP address. Returns `null` if the address is invalid. Never does any DNS lookups. */ - fun parseIpAddress(address: String): InetAddress? - - fun loadIntegratedServerIcon(): BufferedImage? - - // TODO: Eventually move override to :gui:essential project - val modalPrerequisites: ModalPrerequisites - interface Keybind { val isBound: Boolean val boundKeyName: String? diff --git a/src/main/java/gg/essential/Essential.java b/src/main/java/gg/essential/Essential.java index 231d64d3..92eb02ec 100644 --- a/src/main/java/gg/essential/Essential.java +++ b/src/main/java/gg/essential/Essential.java @@ -54,7 +54,6 @@ import gg.essential.lib.gson.GsonBuilder; import gg.essential.network.connectionmanager.ConnectionManager; import gg.essential.network.connectionmanager.skins.PlayerSkinLookup; -import gg.essential.network.connectionmanager.telemetry.FeatureSessionTelemetry; import gg.essential.sps.McIntegratedServerManager; import gg.essential.sps.WindowTitleManager; import gg.essential.universal.UMinecraft; @@ -390,8 +389,6 @@ private void init() { // Fetch update changelog now so it is preloaded for later use AutoUpdate.INSTANCE.getChangelog(); - - FeatureSessionTelemetry.INSTANCE.start(); } private File createEssentialDir() { diff --git a/src/main/java/gg/essential/commands/impl/CommandMessage.java b/src/main/java/gg/essential/commands/impl/CommandMessage.java index f7fbedc8..56e7f27a 100644 --- a/src/main/java/gg/essential/commands/impl/CommandMessage.java +++ b/src/main/java/gg/essential/commands/impl/CommandMessage.java @@ -13,10 +13,12 @@ import com.google.common.collect.ImmutableSet; import com.sparkuniverse.toolbox.chat.model.Message; +import gg.essential.Essential; import gg.essential.api.commands.*; import gg.essential.commands.engine.EssentialFriend; import gg.essential.connectionmanager.common.packet.Packet; import gg.essential.connectionmanager.common.packet.chat.ServerChatChannelMessagePacket; +import gg.essential.network.connectionmanager.chat.ChatManager; import gg.essential.util.MinecraftUtils; import kotlin.collections.CollectionsKt; import org.jetbrains.annotations.NotNull; @@ -28,6 +30,8 @@ public class CommandMessage extends Command { + private static final ChatManager cm = Essential.getInstance().getConnectionManager().getChatManager(); + public CommandMessage() { super("essentialmessage"); } @@ -40,7 +44,7 @@ public Set getCommandAliases() { @DefaultHandler public void handle(@DisplayName("ign")EssentialFriend friend, @DisplayName("message") @Greedy String message) throws ExecutionException, InterruptedException { - CommandMessageKt.handleMessageCommand(friend, message, new EarlyResponseHandler(friend)); + cm.sendMessage(friend.getChannel().getId(), message, new EarlyResponseHandler(friend)); } public void onConfirm(String message) { diff --git a/src/main/java/gg/essential/handlers/GameProfileManager.java b/src/main/java/gg/essential/handlers/GameProfileManager.java index 87cfa477..b71ba886 100644 --- a/src/main/java/gg/essential/handlers/GameProfileManager.java +++ b/src/main/java/gg/essential/handlers/GameProfileManager.java @@ -19,7 +19,6 @@ import gg.essential.Essential; import gg.essential.api.utils.JsonHolder; import gg.essential.mod.Skin; -import gg.essential.mod.cosmetics.CapeDisabledKt; import gg.essential.network.connectionmanager.subscription.SubscriptionManager; import gg.essential.util.UUIDUtil; import net.minecraft.client.Minecraft; @@ -53,15 +52,11 @@ public class GameProfileManager implements SubscriptionManager.Listener { public static final String SKIN_URL = Skin.SKIN_URL; - public static GameProfile handleGameProfile(GameProfile gameProfile, Skin skin) { - return handleGameProfile(gameProfile, skin, null); - } - - public static GameProfile handleGameProfile(GameProfile profile, Skin skin, String capeHash) { + public static GameProfile handleGameProfile(GameProfile profile, Skin skin) { if (skin == null) { return null; } - Overwrites overwrites = new Overwrites(skin.getHash(), skin.getModel().getType(), capeHash); + Overwrites overwrites = new Overwrites(skin.getHash(), skin.getModel().getType(), null); final Property property = profile.getProperties().get("textures").stream().findFirst().orElse(null); if (property == null || ManagedTexturesProperty.hasOverwrites(property, overwrites)) { @@ -107,9 +102,9 @@ public String apply(String originalValue, UUID id) { } if (this.capeHash != null) { - if (this.capeHash.isEmpty() || this.capeHash.equals(CapeDisabledKt.CAPE_DISABLED_COSMETIC_ID)) { + if (this.capeHash.isEmpty()) { textures.remove("CAPE"); - } else if (this.capeHash.length() == 64) { + } else { final JsonHolder cape = textures.optOrCreateJsonHolder("CAPE"); String url = cape.optString("url"); if (!url.endsWith(this.capeHash)) { diff --git a/src/main/java/gg/essential/handlers/McMojangSkinManager.java b/src/main/java/gg/essential/handlers/McMojangSkinManager.java index 1aea586e..e7f26802 100644 --- a/src/main/java/gg/essential/handlers/McMojangSkinManager.java +++ b/src/main/java/gg/essential/handlers/McMojangSkinManager.java @@ -22,7 +22,6 @@ import me.kbrewster.eventbus.Subscribe; import net.minecraft.client.Minecraft; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.util.concurrent.CompletableFuture; import java.util.function.BooleanSupplier; @@ -70,15 +69,4 @@ protected CompletableFuture getSkinFromMinecraft() { ) ); } - - @Nullable - protected synchronized Skin updateSkinNow(boolean notification, boolean userSet) { - Skin updatedSkin = super.updateSkinNow(notification, userSet); - // If the skin is set in the mojang api successfully, we want to also update the client's values - if (updatedSkin != null) { - MinecraftGameProfileTexturesRefresher.INSTANCE.updateTextures(updatedSkin.getHash(), "SKIN"); - } - return updatedSkin; - } - } diff --git a/src/main/java/gg/essential/mixins/transformers/client/gui/GuiDisconnectedAccessor.java b/src/main/java/gg/essential/mixins/transformers/client/gui/GuiDisconnectedAccessor.java deleted file mode 100644 index 5ac175c0..00000000 --- a/src/main/java/gg/essential/mixins/transformers/client/gui/GuiDisconnectedAccessor.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.mixins.transformers.client.gui; - -import net.minecraft.client.gui.GuiDisconnected; -import net.minecraft.util.text.ITextComponent; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.gen.Accessor; - -//#if MC>=12100 -//$$ import net.minecraft.network.DisconnectionInfo; -//#endif - -@Mixin(GuiDisconnected.class) -public interface GuiDisconnectedAccessor { - - //#if MC>=12100 - //$$ @Accessor("info") - //$$ DisconnectionInfo getInfo(); - //#else - @Accessor("message") - ITextComponent getMessage(); - //#endif -} \ No newline at end of file diff --git a/src/main/java/gg/essential/mixins/transformers/client/gui/MixinGuiMultiplayer.java b/src/main/java/gg/essential/mixins/transformers/client/gui/MixinGuiMultiplayer.java index ef6fba58..f621fc6b 100644 --- a/src/main/java/gg/essential/mixins/transformers/client/gui/MixinGuiMultiplayer.java +++ b/src/main/java/gg/essential/mixins/transformers/client/gui/MixinGuiMultiplayer.java @@ -124,18 +124,6 @@ private void initEssentialGui(CallbackInfo ci) { ); } - //#if MC>=12109 - //$$ @Inject(method = "refreshWidgetPositions", at = @At("RETURN")) - //$$ private void essential$updateButtonPositions(CallbackInfo ci) { - //$$ essentialGui.initGui((MultiplayerScreen) (Object) this); - //$$ essentialGui.setupButtons( - //$$ CollectionsKt.filterIsInstance(this.children(), ButtonWidget.class), - //$$ this::addDrawableChild, - //$$ this::removeButton - //$$ ); - //$$ } - //#endif - @Inject(method = "refreshServerList", at = @At("HEAD")) private void essential$markRefresh(CallbackInfo ci) { EssentialMultiplayerGui.Companion.setRefreshing(true); diff --git a/src/main/java/gg/essential/mixins/transformers/client/multiplayer/Mixin_RealmsScreenOpenedTelemetry.java b/src/main/java/gg/essential/mixins/transformers/client/multiplayer/Mixin_RealmsScreenOpenedTelemetry.java deleted file mode 100644 index 8d88ed10..00000000 --- a/src/main/java/gg/essential/mixins/transformers/client/multiplayer/Mixin_RealmsScreenOpenedTelemetry.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.mixins.transformers.client.multiplayer; - -import com.mojang.realmsclient.RealmsMainScreen; -import gg.essential.Essential; -import gg.essential.connectionmanager.common.packet.telemetry.ClientTelemetryPacket; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -@Mixin(RealmsMainScreen.class) -public class Mixin_RealmsScreenOpenedTelemetry { - - @Inject( - method = "init", - at = @At("HEAD"), - //#if MC>=11600 - //$$ remap = true - //#else - remap = false - //#endif - ) - private void onInit(CallbackInfo ci) { - Essential.getInstance().getConnectionManager().getTelemetryManager().enqueue(ClientTelemetryPacket.forAction("REALMS_SCREEN_OPENED")); - } - -} diff --git a/src/main/java/gg/essential/mixins/transformers/client/network/Mixin_ApplyGameProfileOverrides.java b/src/main/java/gg/essential/mixins/transformers/client/network/Mixin_RefreshSkinOnChange.java similarity index 90% rename from src/main/java/gg/essential/mixins/transformers/client/network/Mixin_ApplyGameProfileOverrides.java rename to src/main/java/gg/essential/mixins/transformers/client/network/Mixin_RefreshSkinOnChange.java index f8a997c6..3bed952f 100644 --- a/src/main/java/gg/essential/mixins/transformers/client/network/Mixin_ApplyGameProfileOverrides.java +++ b/src/main/java/gg/essential/mixins/transformers/client/network/Mixin_RefreshSkinOnChange.java @@ -36,7 +36,7 @@ //#endif @Mixin(NetworkPlayerInfo.class) -public class Mixin_ApplyGameProfileOverrides { +public class Mixin_RefreshSkinOnChange { @Shadow @@ -71,14 +71,13 @@ public class Mixin_ApplyGameProfileOverrides { //$$ @Inject(method = "getSkinTextures", at = @At("HEAD")) //$$ public void getSkinTextures(CallbackInfoReturnable info) { //#else - @Inject(method = {"getLocationSkin", "getLocationCape"}, at = @At("HEAD")) - public void updateGameProfileTextures(CallbackInfoReturnable info) { + @Inject(method = "getLocationSkin", at = @At("HEAD")) + public void getLocationSkin(CallbackInfoReturnable info) { //#endif EquippedOutfitsManager manager = ((NetworkPlayerInfoExt) this).getEssential$equippedOutfitsManager(); if (manager == null) manager = Essential.getInstance().getConnectionManager().getCosmeticsManager().getInfraEquippedOutfitsManager(); Skin skin = manager.getSkin(this.gameProfile.getId()); - String equippedCapeHash = manager.getCapeHash(this.gameProfile.getId()); - GameProfile updatedProfile = GameProfileManager.handleGameProfile(this.gameProfile, skin, equippedCapeHash); + GameProfile updatedProfile = GameProfileManager.handleGameProfile(this.gameProfile, skin); if (updatedProfile != null) { this.gameProfile = updatedProfile; //#if MC>=12002 diff --git a/src/main/java/gg/essential/mixins/transformers/feature/skin_overwrites/MinecraftAccessor.java b/src/main/java/gg/essential/mixins/transformers/feature/skin_overwrites/MinecraftAccessor.java deleted file mode 100644 index 5b56bc85..00000000 --- a/src/main/java/gg/essential/mixins/transformers/feature/skin_overwrites/MinecraftAccessor.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.mixins.transformers.feature.skin_overwrites; - -//#if MC>=12002 -//$$ import com.mojang.authlib.yggdrasil.ProfileResult; -//$$ import net.minecraft.client.MinecraftClient; -//$$ import org.spongepowered.asm.mixin.Mixin; -//$$ import org.spongepowered.asm.mixin.gen.Accessor; -//$$ -//$$ import java.util.concurrent.CompletableFuture; -//$$ -//$$ @Mixin(MinecraftClient.class) -//$$ public interface MinecraftAccessor { -//$$ @Accessor -//$$ void setGameProfileFuture(CompletableFuture future); -//$$ } -//#else -@org.spongepowered.asm.mixin.Mixin(gg.essential.mixins.DummyTarget.class) -public interface MinecraftAccessor { -} -//#endif diff --git a/src/main/java/gg/essential/mixins/transformers/feature/skin_overwrites/PlayerEntityAccessor.java b/src/main/java/gg/essential/mixins/transformers/feature/skin_overwrites/PlayerEntityAccessor.java deleted file mode 100644 index 0d03e9f1..00000000 --- a/src/main/java/gg/essential/mixins/transformers/feature/skin_overwrites/PlayerEntityAccessor.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.mixins.transformers.feature.skin_overwrites; - -import gg.essential.mixins.DummyTarget; -import org.spongepowered.asm.mixin.Mixin; - -@Mixin(DummyTarget.class) -public interface PlayerEntityAccessor { -} diff --git a/src/main/java/gg/essential/mixins/transformers/server/Mixin_MinecraftServer_FixStatusEquality.java b/src/main/java/gg/essential/mixins/transformers/server/Mixin_MinecraftServer_FixStatusEquality.java deleted file mode 100644 index bbee1a04..00000000 --- a/src/main/java/gg/essential/mixins/transformers/server/Mixin_MinecraftServer_FixStatusEquality.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.mixins.transformers.server; - -import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; -import com.llamalad7.mixinextras.sugar.Share; -import com.llamalad7.mixinextras.sugar.ref.LocalBooleanRef; -import gg.essential.Essential; -import gg.essential.network.connectionmanager.sps.SPSManager; -import net.minecraft.server.MinecraftServer; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.ModifyArg; - -//#if MC>=11900 -//$$ import java.util.Collections; -//$$ import java.util.List; -//$$ import net.minecraft.util.math.random.Random; -//#else -import java.util.Collections; -import java.util.List; -import java.util.Random; -//#endif - -@Mixin(MinecraftServer.class) -public abstract class Mixin_MinecraftServer_FixStatusEquality { - - @ModifyArg(method = - //#if MC>=11904 - //$$ "createMetadataPlayers" - //#else - "tick" - //#endif - , at = @At(value = "INVOKE", target = - //#if MC>=11900 - //$$ "Lnet/minecraft/util/math/MathHelper;nextInt(Lnet/minecraft/util/math/random/Random;II)I" - //#else - "Lnet/minecraft/util/math/MathHelper;getInt(Ljava/util/Random;II)I" - //#endif - )) - private Random modifyPlayerSampleIndexRandom(Random original, @Share("hosting") LocalBooleanRef hostingRef) { - - // establish if we are hosting via essential - SPSManager spsManager = Essential.getInstance().getConnectionManager().getSpsManager(); - hostingRef.set(spsManager.getLocalSession() != null); - - if (!hostingRef.get()) return original; - - // always return the same seeded random when hosting to avoid status equality issues - //#if MC>=11900 - //$$ return Random.create(0); - //#else - return new Random(0); - //#endif - } - - //#if MC>=11904 - //$$ @ModifyArg(method = "createMetadataPlayers", at = @At(value = "INVOKE", - //$$ target = - //#if MC>=12003 - //$$ "Lnet/minecraft/util/Util;shuffle(Ljava/util/List;Lnet/minecraft/util/math/random/Random;)V", - //#else - //$$ "Lnet/minecraft/util/Util;shuffle(Lit/unimi/dsi/fastutil/objects/ObjectArrayList;Lnet/minecraft/util/math/random/Random;)V", - //#endif - //$$ ordinal = 0)) - //$$ private Random modifyShuffleRandom(Random original, @Share("hosting") LocalBooleanRef hostingRef) { - //$$ // always return the same seeded random when hosting to avoid status equality issues - //$$ return hostingRef.get() ? Random.create(0) : original; - //$$ } - //#else - // pre 1.19.4 vanilla uses a shuffle method without a random parameter - @WrapWithCondition(method = "tick", at = @At(value = "INVOKE", target = "Ljava/util/Collections;shuffle(Ljava/util/List;)V")) - private boolean modifyShuffleRandom(final List list, @Share("hosting") LocalBooleanRef hostingRef) { - if (hostingRef.get()) { - // shuffle consistently when hosting to avoid status equality issues - Collections.shuffle(list, new java.util.Random(0)); - return false; - } - return true; - } - //#endif - -} diff --git a/src/main/java/gg/essential/mixins/transformers/server/integrated/Mixin_IntegratedServerOnTick.java b/src/main/java/gg/essential/mixins/transformers/server/integrated/Mixin_IntegratedServerOnTick.java deleted file mode 100644 index dfb0108d..00000000 --- a/src/main/java/gg/essential/mixins/transformers/server/integrated/Mixin_IntegratedServerOnTick.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.mixins.transformers.server.integrated; - -import gg.essential.Essential; -import gg.essential.event.network.server.ServerTickEvent; -import net.minecraft.server.integrated.IntegratedServer; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -@Mixin(IntegratedServer.class) -public abstract class Mixin_IntegratedServerOnTick { - @Inject(method = "tick", at = @At("HEAD")) - protected void tick(CallbackInfo ci) { - Essential.EVENT_BUS.post(new ServerTickEvent()); - } -} diff --git a/src/main/java/gg/essential/network/connectionmanager/Connection.java b/src/main/java/gg/essential/network/connectionmanager/Connection.java index 8c59cd9f..3b2063a2 100644 --- a/src/main/java/gg/essential/network/connectionmanager/Connection.java +++ b/src/main/java/gg/essential/network/connectionmanager/Connection.java @@ -123,7 +123,7 @@ public class Connection extends WebSocketClient { private int usingProtocol = 1; private ScheduledFuture timeoutTask; - private static final int MAX_PROTOCOL = 9; + private static final int MAX_PROTOCOL = 8; public Connection(@NotNull Callbacks callbacks) { super(CM_HOST_URI); @@ -174,8 +174,6 @@ private void onClosingOrClosed(int code, @NotNull String reason, boolean remote) KnownCloseReason knownReason; if (reason.contains("Invalid status code received: 410") || reason.contains("Invalid status code received: 404")) { knownReason = KnownCloseReason.OUTDATED; - } else if (code == 4008) { - knownReason = KnownCloseReason.SUSPENDED; } else { knownReason = null; } @@ -387,7 +385,6 @@ interface Callbacks { public enum KnownCloseReason { - OUTDATED, - SUSPENDED + OUTDATED } } diff --git a/src/main/java/gg/essential/network/connectionmanager/ConnectionManager.java b/src/main/java/gg/essential/network/connectionmanager/ConnectionManager.java index d7838b1c..7629f839 100644 --- a/src/main/java/gg/essential/network/connectionmanager/ConnectionManager.java +++ b/src/main/java/gg/essential/network/connectionmanager/ConnectionManager.java @@ -50,18 +50,14 @@ import gg.essential.network.connectionmanager.notices.SaleNoticeManager; import gg.essential.network.connectionmanager.notices.SocialMenuNewFriendRequestNoticeManager; import gg.essential.network.connectionmanager.profile.ProfileManager; -import gg.essential.network.connectionmanager.profile.SuspensionDisconnectHandler; import gg.essential.network.connectionmanager.relationship.RelationshipManager; import gg.essential.network.connectionmanager.serverdiscovery.NewServerDiscoveryManager; import gg.essential.network.connectionmanager.serverdiscovery.ServerDiscoveryManager; import gg.essential.network.connectionmanager.skins.PlayerSkinLookup; import gg.essential.network.connectionmanager.skins.SkinsManager; -import gg.essential.network.connectionmanager.social.RulesManager; import gg.essential.network.connectionmanager.social.SocialManager; import gg.essential.network.connectionmanager.sps.SPSManager; import gg.essential.network.connectionmanager.subscription.SubscriptionManager; -import gg.essential.network.connectionmanager.suspension.McSuspensionManager; -import gg.essential.network.connectionmanager.suspension.SuspensionManager; import gg.essential.network.connectionmanager.telemetry.TelemetryManager; import gg.essential.sps.McIntegratedServerManager; import gg.essential.util.ModLoaderUtil; @@ -142,8 +138,6 @@ public class ConnectionManager extends ConnectionManagerKt { private final SocialMenuNewFriendRequestNoticeManager socialMenuNewFriendRequestNoticeManager; @NotNull private final NoticeBannerManager noticeBannerManager; - private /* final */ SuspensionManager suspensionManager; - private /* final */ RulesManager rulesManager; private boolean modsSent = false; private int previouslyConnectedProtocol = 1; @@ -152,7 +146,6 @@ public enum Status { NO_TOS, ESSENTIAL_DISABLED, OUTDATED, - USER_SUSPENDED, CANCELLED, ALREADY_CONNECTED, NO_RESPONSE, @@ -291,10 +284,6 @@ public ConnectionManager( this.telemetryManager::enqueue )); - this.managers.add(this.suspensionManager = new McSuspensionManager(this)); - this.managers.add(this.rulesManager = new RulesManager(this)); - SuspensionDisconnectHandler.INSTANCE.setupEffects(this); - PlayerSkinLookup.INSTANCE.register(this); } @@ -409,14 +398,6 @@ public KnownServersManager getKnownServersManager() { return noticeBannerManager; } - public @NotNull SuspensionManager getSuspensionManager() { - return this.suspensionManager; - } - - public @NotNull RulesManager getRulesManager() { - return this.rulesManager; - } - @Override public boolean isOpen() { Connection connection = this.connection; diff --git a/src/main/java/gg/essential/network/connectionmanager/chat/ChatManager.java b/src/main/java/gg/essential/network/connectionmanager/chat/ChatManager.java index daf0ebf3..235465cc 100644 --- a/src/main/java/gg/essential/network/connectionmanager/chat/ChatManager.java +++ b/src/main/java/gg/essential/network/connectionmanager/chat/ChatManager.java @@ -18,17 +18,17 @@ import com.sparkuniverse.toolbox.chat.model.Channel; import com.sparkuniverse.toolbox.chat.model.Message; import gg.essential.Essential; +import gg.essential.api.gui.Slot; import gg.essential.connectionmanager.common.packet.Packet; import gg.essential.connectionmanager.common.packet.chat.*; import gg.essential.connectionmanager.common.packet.response.ResponseActionPacket; -import gg.essential.connectionmanager.common.packet.social.ServerSocialAllowedDomainsPacket; import gg.essential.gui.elementa.state.v2.ListKt; import gg.essential.gui.elementa.state.v2.MutableState; import gg.essential.gui.elementa.state.v2.State; import gg.essential.gui.EssentialPalette; import gg.essential.gui.elementa.state.v2.StateByKt; import gg.essential.gui.elementa.state.v2.collections.MutableTrackedList; -import gg.essential.gui.friends.message.ReportMessageConfirmationModal; +import gg.essential.gui.friends.message.v2.ClientMessage; import gg.essential.gui.friends.message.v2.ClientMessageKt; import gg.essential.gui.friends.message.v2.MessageRef; import gg.essential.gui.friends.state.IMessengerManager; @@ -55,18 +55,17 @@ import gg.essential.util.StringsKt; import gg.essential.util.USession; import gg.essential.util.UUIDUtil; -import gg.essential.util.UuidNameLookup; import kotlin.Unit; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.time.Instant; import java.util.*; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentMap; import java.util.function.Consumer; import java.util.stream.Stream; -import static gg.essential.util.ChannelExtensionsKt.isAnnouncement; +import static gg.essential.util.ExtensionsKt.isAnnouncement; public class ChatManager extends StateCallbackManager implements NetworkedManager { @@ -82,8 +81,6 @@ public class ChatManager extends StateCallbackManager impleme @NotNull private final Map> reportReasons = Maps.newConcurrentMap(); - private @NotNull List allowedDomains = Collections.emptyList(); - @NotNull private final Set announcementChannelIds = Sets.newConcurrentHashSet(); @@ -134,10 +131,6 @@ public ChatManager(@NotNull final ConnectionManager connectionManager) { connectionManager.registerPacketHandler(ServerChatChannelRemovePacket.class, new ServerChatChannelRemovePacketHandler()); connectionManager.registerPacketHandler(ServerChatChannelMessageReportReasonsPacket.class, new ServerChatChannelMessageReportReasonsPacketHandler()); - connectionManager.registerPacketHandler(ServerSocialAllowedDomainsPacket.class, packet -> { - this.allowedDomains = packet.getDomains(); - return Unit.INSTANCE; - }); } @NotNull @@ -291,8 +284,6 @@ public void clearChannels() { this.reportReasons.clear(); this.channelEagerMessageResolverMap.clear(); - this.allowedDomains = Collections.emptyList(); - for (IMessengerManager callback : getCallbacks()) { callback.reset(); } @@ -377,35 +368,6 @@ public void sendMessage( this.sendMessage(channelId, messageContent, null, callback); } - public void editMessage( - final long channelId, - final long messageId, - @NotNull final String messageContent, - @Nullable final Consumer callback - ) { - sendMessageQueue.enqueue(new ClientChatChannelMessageUpdatePacket(channelId, messageId, messageContent), maybePacket -> { - Packet packet = maybePacket.orElse(null); - if (packet instanceof ResponseActionPacket && ((ResponseActionPacket) packet).isSuccessful()) { - Message message = getMessageById(channelId, messageId); - if (message != null) { - upsertMessageToChannel(channelId, new Message( - message.getId(), - message.getChannelId(), - message.getSender(), - messageContent, - message.isRead(), - message.getReplyTargetId(), - message.getLastEditTime(), - message.getCreatedAt() - ), false); - } - if (callback != null) callback.accept(true); - } else { - if (callback != null) callback.accept(false); - } - }); - } - public void removeMessage(final long channelId, final long messageId) { ConcurrentMap channelMessages = this.channelMessages.get(channelId); if (channelMessages != null) { @@ -424,6 +386,20 @@ public void removeMessage(final long channelId, final long messageId) { } } + public void updateMessage(final ClientMessage message, final String newContents) { + Message messageCopy = new Message( + message.getId(), + message.getChannel().getId(), + message.getSender(), + newContents, + true, + message.getReplyTo() == null ? null : message.getReplyTo().getMessageId(), + Instant.now().toEpochMilli(), + message.getCreatedAt() + ); + upsertMessageToChannel(messageCopy.getChannelId(), messageCopy, false); + } + public void deleteMessage(final long channelId, final long messageId) { this.deleteMessage(channelId, messageId, null); this.removeMessage(channelId, messageId); @@ -638,23 +614,6 @@ public void updateMutedState(@NotNull final Channel channel, final boolean muted boolean success = packet instanceof ResponseActionPacket && ((ResponseActionPacket) packet).isSuccessful(); if (success) { channel.setMuted(muted); - if (muted) { - CompletableFuture nameFuture; - if (channel.getType() == ChannelType.DIRECT_MESSAGE) { - nameFuture = channel.getMembers().stream().filter(uuid -> !uuid.equals(UUIDUtil.getClientUUID())).findFirst().map(UuidNameLookup::getName).orElse(CompletableFuture.completedFuture("Unknown")); - } else { - nameFuture = CompletableFuture.completedFuture(channel.getName()); - } - nameFuture.whenCompleteAsync((name, throwable) -> - Notifications.INSTANCE.push("", "", notificationBuilder -> { - ExtensionsKt.iconAndMarkdownBody(notificationBuilder, - EssentialPalette.MUTE_8X9.create(), - StringsKt.colored(name, EssentialPalette.TEXT_HIGHLIGHT) + " has been muted" - ); - return Unit.INSTANCE; - }) - ); - } } else { ExtensionsKt.error(Notifications.INSTANCE, "Error", "Failed to mute channel.\nPlease try again."); } @@ -669,7 +628,17 @@ public void fileReport(ModalManager modalManager, long channelId, long messageId mutedStateUpdateQueue.enqueue(new ClientChatChannelMessageReportPacket(channelId, messageId, reason), response -> { Packet packet = response.orElse(null); if (packet instanceof ServerChatChannelMessageReportPacket) { - modalManager.queueModal(new ReportMessageConfirmationModal(modalManager, sender, false)); + Notifications.INSTANCE.push( + "Player Reported", + "Thank you for reporting\nthis player.", + 4f, + () -> Unit.INSTANCE, + () -> Unit.INSTANCE, + (builder) -> { + builder.withCustomComponent(Slot.ICON, EssentialPalette.ROUND_WARNING_7X.create()); + return Unit.INSTANCE; + } + ); } else { ExtensionsKt.error(Notifications.INSTANCE, "Report Failed", "Failed to report player.\nPlease try again."); } @@ -681,6 +650,10 @@ private void updateChannelListState() { ListKt.setAll(channelsWithMessagesListState, new ArrayList<>(channelMessages.keySet())); } + public void setLastReadMessage(@NotNull final Message lastReadMessage) { + setLastReadMessage(lastReadMessage.getChannelId(), lastReadMessage.getId()); + } + public void setLastReadMessage(final long channelId, @Nullable final Long lastReadMessageId) { Channel channel = getChannel(channelId).orElseThrow(() -> new IllegalStateException("Can not find channel for message id: " + lastReadMessageId)); if (Objects.equals(channel.getLastReadMessageId(), lastReadMessageId)) { diff --git a/src/main/java/gg/essential/network/connectionmanager/handler/chat/ServerChatChannelMessagePacketHandler.java b/src/main/java/gg/essential/network/connectionmanager/handler/chat/ServerChatChannelMessagePacketHandler.java index 3b7e2c81..bb567442 100644 --- a/src/main/java/gg/essential/network/connectionmanager/handler/chat/ServerChatChannelMessagePacketHandler.java +++ b/src/main/java/gg/essential/network/connectionmanager/handler/chat/ServerChatChannelMessagePacketHandler.java @@ -44,7 +44,6 @@ import java.util.stream.Collectors; import static gg.essential.gui.skin.SkinUtilsKt.showSkinReceivedToast; -import static gg.essential.util.ChannelExtensionsKt.getOtherUser; import static gg.essential.util.ExtensionsKt.getExecutor; public class ServerChatChannelMessagePacketHandler extends PacketHandler { @@ -110,7 +109,7 @@ protected void onHandle(@NotNull final ConnectionManager connectionManager, @Not boolean notification = !(GuiUtil.INSTANCE.openedScreen() instanceof SocialMenu); if (notification) { - final UUID uuid = channel.getType() == ChannelType.DIRECT_MESSAGE ? getOtherUser(channel) : message.getSender(); + final UUID uuid = channel.getType() == ChannelType.DIRECT_MESSAGE ? ExtensionsKt.getOtherUser(channel) : message.getSender(); UUIDUtil.getName(uuid).thenAcceptAsync(new NotificationHandler(channel, message), getExecutor(Minecraft.getMinecraft())); } } diff --git a/src/main/java/gg/essential/network/connectionmanager/handler/profile/ServerProfileStatusPacketHandler.java b/src/main/java/gg/essential/network/connectionmanager/handler/profile/ServerProfileStatusPacketHandler.java index a69b80c1..4b2b0a85 100644 --- a/src/main/java/gg/essential/network/connectionmanager/handler/profile/ServerProfileStatusPacketHandler.java +++ b/src/main/java/gg/essential/network/connectionmanager/handler/profile/ServerProfileStatusPacketHandler.java @@ -26,7 +26,6 @@ protected void onHandle( ProfileManager profileManager = connectionManager.getProfileManager(); profileManager.setPlayerStatus(packet.getUUID(), packet.getStatus(), packet.getLastOnlineTimestamp()); - profileManager.setPlayerSuspension(packet.getUUID(), packet.getPunishmentStatus()); } } diff --git a/src/main/java/gg/essential/network/connectionmanager/media/ScreenshotManager.java b/src/main/java/gg/essential/network/connectionmanager/media/ScreenshotManager.java index b019a247..24d2c232 100644 --- a/src/main/java/gg/essential/network/connectionmanager/media/ScreenshotManager.java +++ b/src/main/java/gg/essential/network/connectionmanager/media/ScreenshotManager.java @@ -26,7 +26,6 @@ import gg.essential.connectionmanager.common.packet.media.ServerMediaPopulatePacket; import gg.essential.connectionmanager.common.packet.media.ServerMediaUploadUrlPacket; import gg.essential.connectionmanager.common.packet.response.ResponseActionPacket; -import gg.essential.connectionmanager.common.packet.telemetry.ClientTelemetryPacket; import gg.essential.event.render.RenderTickEvent; import gg.essential.gui.NotificationsKt; import gg.essential.gui.elementa.state.v2.MutableState; @@ -95,8 +94,10 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.text.SimpleDateFormat; import java.util.Collection; import java.util.Collections; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -223,7 +224,6 @@ public CompletableFuture upload(Path path, ClientScreenshotMetadata metad return upload(path, metadata, ScreenshotUploadToast.create()); } - @Override public CompletableFuture upload(Path path, ClientScreenshotMetadata metadata, Consumer progressConsumer) { Media existingMediaIfPresent = getUploadedMedia(path); if (existingMediaIfPresent != null) { @@ -251,8 +251,6 @@ public CompletableFuture upload(Path path, ClientScreenshotMetadata metad } else { progressConsumer.accept(new ScreenshotUploadToast.ToastProgress.Complete("Failed: An unknown error occurred", false)); } - } else { - connectionManager.getTelemetryManager().enqueue(ClientTelemetryPacket.forAction("SCREENSHOT_UPLOADED")); } }, ExtensionsKt.getExecutor(Minecraft.getMinecraft())); return uploadFuture; @@ -299,7 +297,6 @@ private void copyLinkToClipboard(Media media, Consumer channels, Media media, Consumer saveDownloadedImageAsync(RenderedImage img) { + CompletableFuture future = new CompletableFuture<>(); + Multithreading.runAsync(() -> { + try { + File imgFile = getDownloadedName(new SimpleDateFormat("yyyy-MM-dd_HH.mm.ss").format(new Date())); + saveScreenshot(img, imgFile); + future.complete(null); + } catch (IOException e) { + future.completeExceptionally(e); + throw new RuntimeException(e); + } + }); + return future; + } + + private File getDownloadedName(String name) { + int i = 0; + + while (true) { + File file1 = new File(HelpersKt.getScreenshotFolder(), "saved_" + name + (i == 0 ? "" : "_" + i) + ".png"); + if (!file1.exists()) { + return file1; + } + + ++i; + } + } + private void precompute(File source) { backgroundExecutor.submit(new PrecomputeTask(source, minResolutionProvider)); } @@ -562,7 +589,6 @@ public Media getUploadedMedia(String mediaId) { } @NotNull - @Override public List getUploadedLocalPathsCache(String mediaId) { ClientScreenshotMetadata metadata = screenshotMetadataManager.getMetadataCache(mediaId); if (metadata == null) { @@ -779,8 +805,6 @@ public File handleScreenshotEdited(@NotNull ScreenshotId source, @NotNull Client localScreenshots.set(list -> list.add(new Pair<>(output.getName(), checksum))); NotificationsKt.sendCheckmarkNotification("Picture saved as copy"); - - connectionManager.getTelemetryManager().enqueue(ClientTelemetryPacket.forAction("SCREENSHOT_EDITED")); }); return output; @@ -842,8 +866,6 @@ private void takeNow() { //#else //$$ ScreenshotRecorder.saveScreenshot(mc.runDirectory, mc.getFramebuffer(), message -> screenshotMessageCallback(message)); //#endif - - connectionManager.getTelemetryManager().enqueue(ClientTelemetryPacket.forAction("SCREENSHOT_TAKEN")); } private void screenshotMessageCallback(ITextComponent component) { diff --git a/src/main/java/gg/essential/network/connectionmanager/profile/ProfileManager.java b/src/main/java/gg/essential/network/connectionmanager/profile/ProfileManager.java index 88a06d52..52674971 100644 --- a/src/main/java/gg/essential/network/connectionmanager/profile/ProfileManager.java +++ b/src/main/java/gg/essential/network/connectionmanager/profile/ProfileManager.java @@ -17,7 +17,6 @@ import gg.essential.config.EssentialConfig; import gg.essential.connectionmanager.common.enums.ActivityType; import gg.essential.connectionmanager.common.enums.ProfileStatus; -import gg.essential.connectionmanager.common.model.profile.ProfilePunishmentStatus; import gg.essential.connectionmanager.common.packet.profile.ClientProfileActivityPacket; import gg.essential.connectionmanager.common.packet.profile.ServerProfileActivityPacket; import gg.essential.connectionmanager.common.packet.profile.ServerProfileStatusPacket; @@ -28,9 +27,6 @@ import gg.essential.elementa.state.BasicState; import gg.essential.elementa.state.State; import gg.essential.gui.EssentialPalette; -import gg.essential.gui.elementa.state.v2.MutableState; -import gg.essential.gui.elementa.state.v2.collections.MutableTrackedList; -import gg.essential.gui.elementa.state.v2.collections.TrackedList; import gg.essential.gui.friends.SocialMenu; import gg.essential.gui.friends.previews.ChannelPreview; import gg.essential.gui.friends.state.IStatusManager; @@ -50,7 +46,6 @@ import gg.essential.network.connectionmanager.handler.profile.trustedhosts.ServerProfileTrustedHostsRemovePacketHandler; import gg.essential.network.connectionmanager.queue.PacketQueue; import gg.essential.network.connectionmanager.queue.SequentialPacketQueue; -import gg.essential.network.connectionmanager.social.ProfileSuspension; import gg.essential.network.connectionmanager.subscription.SubscriptionManager; import gg.essential.profiles.model.TrustedHost; import gg.essential.util.CachedAvatarImage; @@ -73,7 +68,6 @@ import java.util.stream.Collectors; import static gg.essential.gui.elementa.state.v2.ListKt.clear; -import static gg.essential.gui.elementa.state.v2.ListKt.mutableListStateOf; public class ProfileManager extends StateCallbackManager implements NetworkedManager, SubscriptionManager.Listener { @NotNull @@ -97,12 +91,6 @@ public class ProfileManager extends StateCallbackManager impleme @NotNull private final State> userTrustedHostsState = new BasicState<>(new HashSet<>()); - @NotNull - private final MutableState> suspensions = mutableListStateOf(); - - @NotNull - private final gg.essential.gui.elementa.state.v2.State> activeSuspensions = ProfileManagerKt.filterActiveSuspensions(suspensions); - /** * Used to track whether the default trusted hosts have been loaded in the * case the user has not accepted the TOS and therefore cannot receive them @@ -161,29 +149,6 @@ public ProfileStatus getStatus(@NotNull final UUID uuid) { return this.getStatusIfLoaded(uuid).orElse(ProfileStatus.OFFLINE); } - @NotNull - public gg.essential.gui.elementa.state.v2.State> getSuspensions() { - return this.activeSuspensions; - } - - @Nullable - public ProfileSuspension getSuspension(@NotNull final UUID uuid) { - for (final ProfileSuspension suspension : this.activeSuspensions.getUntracked()) { - if (suspension.getUser().equals(uuid)) { - return suspension; - } - } - return null; - } - - public boolean isSuspended(@NotNull final UUID uuid) { - ProfileSuspension suspension = this.getSuspension(uuid); - if (suspension == null) { - return false; - } - return suspension.isActiveNow(); - } - @NotNull public Optional getStatusIfLoaded(@NotNull final UUID uuid) { return Optional.ofNullable(this.statuses.get(uuid)); @@ -243,19 +208,6 @@ public void setPlayerStatus(@NotNull final UUID uuid, @NotNull final ProfileStat } } - public void setPlayerSuspension(@NotNull final UUID uuid, @Nullable final ProfilePunishmentStatus suspension) { - MutableTrackedList newSuspensions = this.suspensions.getUntracked(); - for (ProfileSuspension profileSuspension : newSuspensions) { - if (profileSuspension.getUser().equals(uuid)) { - newSuspensions = newSuspensions.remove(profileSuspension); - } - } - if (suspension != null) { - newSuspensions = newSuspensions.add(ProfileSuspension.Companion.fromInfra(uuid, suspension)); - } - this.suspensions.set(newSuspensions); - } - public void setPlayerActivity( @NotNull final UUID uuid, @Nullable final ActivityType activityType, @Nullable final String metadata ) { @@ -329,7 +281,6 @@ public void resetState() { this.trustedHosts.clear(); this.activities.clear(); this.statuses.clear(); - clear(this.suspensions); updateTrustedHostState(); } diff --git a/src/main/java/gg/essential/network/connectionmanager/sps/SPSManager.java b/src/main/java/gg/essential/network/connectionmanager/sps/SPSManager.java index 01060303..847c2056 100644 --- a/src/main/java/gg/essential/network/connectionmanager/sps/SPSManager.java +++ b/src/main/java/gg/essential/network/connectionmanager/sps/SPSManager.java @@ -21,7 +21,6 @@ import gg.essential.connectionmanager.common.packet.upnp.*; import gg.essential.data.SPSData; import gg.essential.event.network.server.ServerLeaveEvent; -import gg.essential.event.network.server.ServerTickEvent; import gg.essential.event.render.RenderTickEvent; import gg.essential.event.sps.PlayerJoinSessionEvent; import gg.essential.event.sps.PlayerLeaveSessionEvent; @@ -41,7 +40,6 @@ import gg.essential.network.connectionmanager.queue.PacketQueue; import gg.essential.network.connectionmanager.queue.SequentialPacketQueue; import gg.essential.sps.ResourcePackSharingHttpServer; -import gg.essential.sps.TPSSessionMonitor; import gg.essential.sps.WindowTitleManager; import gg.essential.sps.SpsAddress; import gg.essential.universal.UMinecraft; @@ -132,8 +130,6 @@ public class SPSManager extends StateCallbackManager implements private Instant sessionStartTime = Instant.now(); - private TPSSessionMonitor tpsSessionMonitor = null; - /** * A random ID for each session, used for telemetry purposes. */ @@ -375,8 +371,6 @@ public void startLocalSession(SPSSessionSource sessionSource) { EssentialCommandRegistry.INSTANCE.registerSPSHostCommands(); WindowTitleManager.INSTANCE.updateTitle(); - - tpsSessionMonitor = new TPSSessionMonitor(); } public synchronized void updateLocalSession(@NotNull String ip, int port) { @@ -433,7 +427,6 @@ public synchronized void closeLocalSession() { resourcePackUrl = null; resourcePackChecksum = null; shareResourcePack = false; - tpsSessionMonitor = null; ExtensionsKt.getExecutor(Minecraft.getMinecraft()).execute(EssentialCommandRegistry.INSTANCE::unregisterSPSHostCommands); @@ -471,18 +464,6 @@ private static String cpuInfo() { private void sendSessionTelemetry(IntegratedServer server, UPnPSession oldSession) { File worldDirectory = ExtensionsKt.getWorldDirectory(server).toFile(); - float averageTPS; - float minTPS; - float maxTPS; - if (tpsSessionMonitor != null) { - averageTPS = tpsSessionMonitor.getAverageTPS(); - minTPS = tpsSessionMonitor.getMinTPS(); - maxTPS = tpsSessionMonitor.getMaxTPS(); - } else { - averageTPS = 0f; - minTPS = 0f; - maxTPS = 0f; - } HashMap metadata = new HashMap() {{ put("userCPU", cpuInfo()); put("worldNameHash", DigestUtils.sha256Hex(UUIDUtil.getClientUUID() + worldDirectory.getName())); @@ -493,9 +474,6 @@ private void sendSessionTelemetry(IntegratedServer server, UPnPSession oldSessio put("sessionDurationSeconds", TimeUnit.MILLISECONDS.toSeconds(Duration.between(sessionStartTime, Instant.now()).toMillis())); put("sessionId", sessionId); put("initiatedFrom", localSessionSource); - put("averageTPS", averageTPS); - put("minTPS", minTPS); - put("maxTPS", maxTPS); }}; // Fork so calculating the world size doesn't block the main thread @@ -505,7 +483,7 @@ private void sendSessionTelemetry(IntegratedServer server, UPnPSession oldSessio metadata.put("worldSizeMb", worldSizeBytes / 1_000_000); // Return to main thread because enqueue is not thread safe - ExtensionsKt.getExecutor(Minecraft.getMinecraft()).execute(() -> connectionManager.getTelemetryManager().enqueue(new ClientTelemetryPacket("SPS_SESSION_4", metadata))); + ExtensionsKt.getExecutor(Minecraft.getMinecraft()).execute(() -> connectionManager.getTelemetryManager().enqueue(new ClientTelemetryPacket("SPS_SESSION_3", metadata))); }); } @@ -828,13 +806,6 @@ public void onPlayerLeaveSession(PlayerLeaveSessionEvent event) { } } - @Subscribe - public void onServerTick(ServerTickEvent event) { - if (tpsSessionMonitor != null) { - tpsSessionMonitor.tick(); - } - } - public boolean isShareResourcePack() { return shareResourcePack; } diff --git a/src/main/java/gg/essential/network/connectionmanager/telemetry/TelemetryManager.java b/src/main/java/gg/essential/network/connectionmanager/telemetry/TelemetryManager.java index 2ddfb4f5..89da813f 100644 --- a/src/main/java/gg/essential/network/connectionmanager/telemetry/TelemetryManager.java +++ b/src/main/java/gg/essential/network/connectionmanager/telemetry/TelemetryManager.java @@ -17,7 +17,6 @@ import gg.essential.elementa.state.v2.ReferenceHolder; import gg.essential.event.client.InitializationEvent; import gg.essential.event.essential.TosAcceptedEvent; -import gg.essential.event.gui.InitGuiEvent; import gg.essential.event.network.server.ServerJoinEvent; import gg.essential.gui.elementa.state.v2.ReferenceHolderImpl; import gg.essential.gui.elementa.state.v2.StateKt; @@ -30,7 +29,6 @@ import gg.essential.network.connectionmanager.queue.SequentialPacketQueue; import gg.essential.sps.SpsAddress; import gg.essential.universal.UMinecraft; -import gg.essential.universal.UResolution; import gg.essential.util.ModLoaderUtil; import gg.essential.util.Multithreading; import kotlin.jvm.functions.Function0; @@ -74,8 +72,6 @@ public class TelemetryManager implements NetworkedManager { private final ReferenceHolder referenceHolder = new ReferenceHolderImpl(); @Nullable private Function0 modPartnerEffect = null; - @Nullable - private Map previousDisplayMetadata = null; public TelemetryManager(@NotNull final ConnectionManager connectionManager) { this.connectionManager = connectionManager; @@ -160,7 +156,6 @@ private void init(InitializationEvent event) { setupAbFeatureTracking(this, referenceHolder); setupSettingsTracking(this, referenceHolder); ImpressionTelemetryManager.INSTANCE.initialize(); - FPSTelemetryManager.INSTANCE.initialize(); enqueue(new ClientTelemetryPacket("LANGUAGE", new HashMap(){{ put("lang", UMinecraft.getMinecraft().gameSettings.language); @@ -246,18 +241,6 @@ public void sendHardwareAndOSTelemetry(@NotNull final TosAcceptedEvent event) { enqueue(new ClientTelemetryPacket("HARDWARE_V2", hardwareMap)); } - @Subscribe - public void sendDisplayData(@NotNull final InitGuiEvent event) { - final Map displayMetadata = new HashMap<>(); - displayMetadata.put("windowWidth", UResolution.getWindowWidth()); - displayMetadata.put("windowHeight", UResolution.getWindowHeight()); - displayMetadata.put("guiSize", UResolution.getScaleFactor()); - if (!displayMetadata.equals(previousDisplayMetadata)) { - enqueue(new ClientTelemetryPacket("DISPLAY_DATA", displayMetadata)); - this.previousDisplayMetadata = displayMetadata; - } - } - private void queueInstallerTelemetryPacket() { // We go async, since we are reading a file Multithreading.runAsync(() -> { diff --git a/src/main/kotlin/gg/essential/commands/impl/commandMessage.kt b/src/main/kotlin/gg/essential/commands/impl/commandMessage.kt deleted file mode 100644 index 31ee624b..00000000 --- a/src/main/kotlin/gg/essential/commands/impl/commandMessage.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.commands.impl - -import gg.essential.Essential -import gg.essential.commands.engine.EssentialFriend -import gg.essential.gui.modals.ensurePrerequisites -import gg.essential.network.connectionmanager.EarlyResponseHandler -import gg.essential.util.GuiUtil -import java.util.* -import kotlin.coroutines.cancellation.CancellationException - -fun handleMessageCommand(friend: EssentialFriend, message: String, handler: EarlyResponseHandler) { - GuiUtil.launchModalFlow { - try { - ensurePrerequisites(social = true) - } catch (exception: CancellationException) { - handler.accept(Optional.empty()) - throw exception - } - Essential.getInstance().connectionManager.chatManager.sendMessage(friend.channel.id, message, handler) - } -} \ No newline at end of file diff --git a/src/main/kotlin/gg/essential/gui/InternalEssentialGUI.kt b/src/main/kotlin/gg/essential/gui/InternalEssentialGUI.kt index 11d428c3..31341548 100644 --- a/src/main/kotlin/gg/essential/gui/InternalEssentialGUI.kt +++ b/src/main/kotlin/gg/essential/gui/InternalEssentialGUI.kt @@ -17,11 +17,8 @@ import gg.essential.api.gui.EssentialGUI import gg.essential.connectionmanager.common.packet.telemetry.ClientTelemetryPacket import gg.essential.elementa.ElementaVersion import gg.essential.elementa.components.UIBlock -import gg.essential.gui.elementa.state.v2.ReferenceHolderImpl import gg.essential.gui.elementa.state.v2.State import gg.essential.gui.elementa.state.v2.mutableStateOf -import gg.essential.gui.elementa.state.v2.onChange -import gg.essential.network.connectionmanager.telemetry.FeatureSessionTelemetry import gg.essential.universal.UGraphics import gg.essential.universal.UMatrixStack import gg.essential.universal.render.URenderPipeline @@ -36,22 +33,11 @@ abstract class InternalEssentialGUI( restorePreviousGuiOnClose: Boolean = true, discordActivityDescription: String? = null, ): EssentialGUI(version, guiTitle, newGuiScale, restorePreviousGuiOnClose, discordActivityDescription) { - private val reference = ReferenceHolderImpl() private val screenOpenMutable = mutableStateOf(false) protected val screenOpen: State = screenOpenMutable private var openedAt: Long? = null - init { - screenOpen.onChange(reference) { open -> - if (open) { - FeatureSessionTelemetry.startEvent(this@InternalEssentialGUI.javaClass.name) - } else { - FeatureSessionTelemetry.endEvent(this@InternalEssentialGUI.javaClass.name) - } - } - } - override fun initScreen(width: Int, height: Int) { super.initScreen(width, height) diff --git a/src/main/kotlin/gg/essential/gui/friends/SocialMenu.kt b/src/main/kotlin/gg/essential/gui/friends/SocialMenu.kt index 560d9642..e60dba38 100644 --- a/src/main/kotlin/gg/essential/gui/friends/SocialMenu.kt +++ b/src/main/kotlin/gg/essential/gui/friends/SocialMenu.kt @@ -11,41 +11,42 @@ */ package gg.essential.gui.friends +import com.sparkuniverse.toolbox.chat.enums.ChannelType import com.sparkuniverse.toolbox.chat.model.Channel import gg.essential.Essential import gg.essential.api.gui.GuiRequiresTOS +import gg.essential.config.EssentialConfig import gg.essential.elementa.ElementaVersion import gg.essential.elementa.components.Window import gg.essential.elementa.constraints.* import gg.essential.elementa.dsl.* +import gg.essential.gui.EssentialPalette import gg.essential.gui.InternalEssentialGUI import gg.essential.gui.common.ContextOptionMenu import gg.essential.gui.common.bindConstraints import gg.essential.gui.common.constraints.CenterPixelConstraint +import gg.essential.gui.common.modal.CancelableInputModal +import gg.essential.gui.common.modal.ConfirmDenyModal +import gg.essential.gui.common.modal.DangerConfirmationEssentialModal +import gg.essential.gui.common.modal.configure import gg.essential.gui.elementa.essentialmarkdown.EssentialMarkdown import gg.essential.gui.elementa.state.v2.* -import gg.essential.gui.friends.message.SocialMenuActions -import gg.essential.gui.friends.message.SocialMenuState -import gg.essential.gui.friends.modals.BlockConfirmationModal -import gg.essential.gui.friends.modals.ConfirmJoinModal -import gg.essential.gui.friends.modals.FriendRemoveConfirmationModal +import gg.essential.gui.friends.message.v2.getInfraInstance import gg.essential.gui.friends.previews.ChannelPreview import gg.essential.gui.friends.state.SocialStateManager -import gg.essential.gui.friends.state.SocialStates import gg.essential.gui.friends.tabs.ChatTab import gg.essential.gui.friends.tabs.FriendsTab import gg.essential.gui.friends.title.SocialTitleManagementActions -import gg.essential.gui.layoutdsl.layout -import gg.essential.gui.modals.communityRulesModal +import gg.essential.gui.modals.select.selectModal +import gg.essential.gui.modals.select.users import gg.essential.gui.notification.Notifications -import gg.essential.gui.notification.sendOutgoingSpsInviteNotification import gg.essential.gui.notification.warning +import gg.essential.gui.overlay.ModalManager +import gg.essential.gui.sps.InviteFriendsModal import gg.essential.gui.util.onItemAdded +import gg.essential.gui.util.toStateV2List import gg.essential.universal.UMinecraft import gg.essential.util.* -import gg.essential.util.GuiUtil.launchModalFlow -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext import java.util.* class SocialMenu @JvmOverloads constructor( @@ -54,7 +55,7 @@ class SocialMenu @JvmOverloads constructor( ElementaVersion.V6, "Social", discordActivityDescription = "Messaging friends", -), GuiRequiresTOS, SocialMenuActions { +), GuiRequiresTOS { private val connectionManager = Essential.getInstance().connectionManager private val referenceHolder = ReferenceHolderImpl() @@ -65,41 +66,16 @@ class SocialMenu @JvmOverloads constructor( var selectedTab = mutableStateOf(Tab.CHAT) - private val socialMenuState = object : SocialMenuState, SocialStates by socialStateManager { - override val tab: State - get() = selectedTab - } + val dividerWidth = rightDivider.getWidth() - val tabsSelector by TabsSelector( - selectedTab, - connectionManager.socialMenuNewFriendRequestNoticeManager.unseenFriendRequestCount().toV2() - ).constrain { + val tabsSelector by TabsSelector(selectedTab).constrain { width = 215.pixels.coerceAtMost(50.percent).coerceAtLeast(ChildBasedSizeConstraint()) height = 27.pixels } childOf content - val chatTab by ChatTab( - selectedTab, - socialStateManager, - this, - tabsSelector, - titleBar, - rightDivider, - isScreenOpen, - ) - val friendsTab by FriendsTab( - selectedTab, - socialStateManager, - this, - connectionManager.socialMenuNewFriendRequestNoticeManager, - tabsSelector, - rightDivider, - ) - private val titleManagementActions by SocialTitleManagementActions( - selectedTab, - socialStateManager, - this, - ).constrain { + val chatTab by ChatTab(this, selectedTab) + val friendsTab by FriendsTab(this, selectedTab) + private val titleManagementActions by SocialTitleManagementActions(this).constrain { y = CenterPixelConstraint() }.bindConstraints(selectedTab) { x = if (it == Tab.CHAT) { @@ -125,15 +101,6 @@ class SocialMenu @JvmOverloads constructor( chatTab.populate() friendsTab.populate() - content.layout { - bind(selectedTab) { tab -> - when (tab) { - Tab.CHAT -> chatTab() - Tab.FRIENDS -> friendsTab() - } - } - } - if (channelIdToOpen != null) { val preview = chatTab[connectionManager.chatManager.mergeAnnouncementChannel(channelIdToOpen)] if (preview != null) { @@ -166,19 +133,6 @@ class SocialMenu @JvmOverloads constructor( } effect(referenceHolder) { - val rulesManager = connectionManager.rulesManager - if (isScreenOpen() && rulesManager.hasRules() && !rulesManager.acceptedRules) { - launchModalFlow { - if (!communityRulesModal(rulesManager, UMinecraft.getSettings().language)) { - // FIXME this double withContext is a workaround for EM-3456 - withContext(Dispatchers.IO) { - withContext(Dispatchers.Client) { - restorePreviousScreen() - } - } - } - } - } } } @@ -214,32 +168,270 @@ class SocialMenu @JvmOverloads constructor( selectedTab.set(Tab.CHAT) } - override fun openMessageScreen(channel: Channel) { + fun openMessageScreen(channel: Channel) { chatTab[channel.id]?.let { openMessageScreen(it) } } - override fun openMessageScreen(user: UUID) { + fun openMessageFor(user: UUID) { val channelPreview = chatTab[user] if (channelPreview != null) { openMessageScreen(channelPreview) } } - override fun showManagementDropdown( + fun showManagementDropdown( preview: ChannelPreview, position: ContextOptionMenu.Position, - extraOptions: List, + extraOptions: List = emptyList(), onClose: () -> Unit - ) = showManagementDropdown(window, socialMenuState, this, preview, position, extraOptions, onClose) + ) { + when { + preview.otherUser != null -> { + showUserDropdown(preview.otherUser, position, extraOptions.toMutableList().apply { + addMarkMessagesReadOption(preview.channel.id, this) + }, onClose) + } + !preview.channel.isAnnouncement() -> showGroupDropdown(preview.channel, position, extraOptions, onClose) + else -> onClose.invoke() + } + } - override fun showUserDropdown( + private fun addMarkMessagesReadOption( + channelId: Long, + options: MutableList, + ) { + if (socialStateManager.messengerStates.getUnreadChannelState(channelId).getUntracked()) { + options.add(ContextOptionMenu.Option("Mark as Read", image = EssentialPalette.MARK_UNREAD_10X7) { + if (connectionManager.usingProtocol >= 9) { + val latestMessage = socialStateManager.messengerStates.getLatestMessage(channelId).getUntracked() ?: return@Option + socialStateManager.messengerStates.setLastReadMessage(latestMessage) + } else { + socialStateManager.messengerStates.getMessageListState(channelId).getUntracked().forEach { + socialStateManager.messengerStates.setUnreadState(it.getInfraInstance(), false) + } + } + }) + } + } + + fun showUserDropdown(user: UUID, position: ContextOptionMenu.Position, onClose: () -> Unit) { + showUserDropdown(user, position, emptyList(), onClose) + } + + fun showUserDropdown( user: UUID, position: ContextOptionMenu.Position, extraOptions: List, onClose: () -> Unit - ) = showUserDropdown(window, socialMenuState, this, user, position, extraOptions, onClose) + ) { + val options = extraOptions.toMutableList() + + val joinPlayerOption = ContextOptionMenu.Option( + "Join Game", + // New default is text, so remove entirely when removing feature flag + textColor = EssentialPalette.TEXT, + hoveredColor = EssentialPalette.MESSAGE_SENT, + // New default is black, so remove entirely when removing feature flag + shadowColor = EssentialPalette.BLACK, + image = EssentialPalette.JOIN_ARROW_5X, + ) { + handleJoinSession(user) + } + val invitePlayerOption = ContextOptionMenu.Option( + "Invite to Game", + // New default is text, so remove entirely when removing feature flag + textColor = EssentialPalette.TEXT, + hoveredColor = EssentialPalette.MESSAGE_SENT, + // New default is black, so remove entirely when removing feature flag + shadowColor = EssentialPalette.BLACK, + image = EssentialPalette.ENVELOPE_9X7, + ) { + handleInvitePlayers(setOf(user), UuidNameLookup.nameState(user).getUntracked()) + } + + val topmostOptions: MutableList = mutableListOf() + + if (socialStateManager.statusStates.getActivity(user).isJoinable()) { + topmostOptions.add(joinPlayerOption) + } + if (ServerType.current()?.supportsInvites == true) { + topmostOptions.add(invitePlayerOption) + } + + if (topmostOptions.isNotEmpty()) { + options.add(0, ContextOptionMenu.Divider) + + for (optionItem in topmostOptions) { + options.add(0, optionItem) + } + } + + // Don't add a divider below is we haven't added anything above here + var addedDivider = extraOptions.isEmpty() + + val messageScreen = chatTab.currentMessageView.get() + if (selectedTab.get() == Tab.FRIENDS) { + options.add(ContextOptionMenu.Option("Send Message", image = EssentialPalette.MESSAGE_10X6) { + openMessageFor(user) + }) + // We always add this divider as it's below the option + options.add(ContextOptionMenu.Divider) + addedDivider = true + } else if (messageScreen != null) { + val channel = socialStateManager.messengerStates.getObservableChannelList().firstOrNull { + it.getOtherUser() == user + } + if (channel != null) { + val muted = socialStateManager.messengerStates.getMuted(channel.id) + + if (!addedDivider) { + options.add(ContextOptionMenu.Divider) + addedDivider = true + } + options.add(ContextOptionMenu.Option( + { + if (muted()) { + "Unmute Friend" + } else { + "Mute Friend" + } + }, + image = { + if (muted()) { + EssentialPalette.UNMUTE_8X9 + } else { + EssentialPalette.MUTE_8X9 + } + }, + ) { + muted.set { !it } + }) + } + + } + if (!addedDivider) { + options.add(ContextOptionMenu.Divider) + } + + val blocked = isBlocked(user) + val friend = isFriend(user) + + if (!blocked) { + options.add(ContextOptionMenu.Option( + if (friend) { + "Remove Friend" + } else { + "Add Friend" + }, + image = if (friend) EssentialPalette.REMOVE_FRIEND_10X5 else EssentialPalette.INVITE_10X6, + ) { + handleAddOrRemove(user) + }) + } + + options.add(ContextOptionMenu.Option( + if (blocked) { + "Unblock" + } else { + "Block" + }, + image = EssentialPalette.BLOCK_10X7, + hoveredColor = EssentialPalette.TEXT_WARNING + ) { + handleBlockOrUnblock(user) + }) + ContextOptionMenu.create(position, window, *options.toTypedArray(), onClose = onClose) + } + + fun showGroupDropdown( + channel: Channel, + position: ContextOptionMenu.Position, + extraOptions: List, + onClose: () -> Unit + ) { + val options = extraOptions.toMutableList() + + addMarkMessagesReadOption(channel.id, options) + + // Left commented if we re-add in the future + /* if (ServerType.current()?.supportsInvites == true) { + options.add( + ContextOptionMenu.Option( + "Invite Group", + // New default is text, so remove entirely when removing feature flag + textColor = EssentialPalette.TEXT, + hoveredColor = EssentialPalette.MESSAGE_SENT, + // New default is black, so remove entirely when removing feature flag + shadowColor = EssentialPalette.BLACK, + image = EssentialPalette.INVITE_10X6, + ) { + handleInvitePlayers(channel.members, channel.name) + } + ) + + options.add(ContextOptionMenu.Divider) + } */ + + val mutedState = socialStateManager.messengerStates.getMuted(channel.id) + if (channel.type == ChannelType.GROUP_DIRECT_MESSAGE && channel.createdInfo.by == UUIDUtil.getClientUUID()) { + options.add(ContextOptionMenu.Option( + "Invite Friends", + image = EssentialPalette.MARK_UNREAD_10X7 + ) { + // We don't want to show anyone currently in the group here + val potentialFriends = socialStateManager.relationshipStates.getObservableFriendList() + .toStateV2List() + .filter { !channel.members.contains(it) } - override fun joinSessionWithConfirmation(user: UUID) { + + GuiUtil.pushModal { manager -> + createAddFriendsToGroupModal(manager, potentialFriends).onPrimaryAction { users -> + socialStateManager.messengerStates.addMembers(channel.id, users) + } + } + }) + options.add(ContextOptionMenu.Divider) + options.add(ContextOptionMenu.Option( + "Rename Group", + image = EssentialPalette.PENCIL_7x7 + ) { + GuiUtil.pushModal { manager -> + RenameGroupModal(manager, channel.name).onPrimaryActionWithValue { it -> + socialStateManager.messengerStates.setTitle(channel.id, it) + } + } + }) + options.add(ContextOptionMenu.Divider) + } + + options.add(ContextOptionMenu.Option({ + if (mutedState()) { + "Unmute Group" + } else { + "Mute Group" + } + }, image = { + if (mutedState()) { + EssentialPalette.UNMUTE_8X9 + } else { + EssentialPalette.MUTE_8X9 + } + }) { + mutedState.set { !it } // Will be automatically applied properly + }) + + options.add(ContextOptionMenu.Option("Leave Group", image = EssentialPalette.LEAVE_10X7) { + GuiUtil.pushModal { manager -> + ConfirmGroupLeaveModal(manager).onPrimaryAction { + socialStateManager.messengerStates.leaveGroup(channel.id) + } + } + }) + + ContextOptionMenu.create(position, window, *options.toTypedArray(), onClose = onClose) + } + + fun handleJoinSession(user: UUID) { if (UMinecraft.getWorld() != null) { if (!socialStateManager.statusStates.joinSession(user)) { Notifications.warning("World invite expired", "") @@ -259,21 +451,25 @@ class SocialMenu @JvmOverloads constructor( } } - override fun invitePlayers(users: Set, name: String) { + fun handleInvitePlayers(users: Set, name: String) { val currentServerData = UMinecraft.getMinecraft().currentServerData val spsManager = connectionManager.spsManager if (spsManager.localSession != null) { spsManager.reinviteUsers(users) - sendOutgoingSpsInviteNotification(name) + InviteFriendsModal.sendInviteNotification(name) } else if (currentServerData != null) { connectionManager.socialManager.reinviteFriendsOnServer(currentServerData.serverIP, users) } } - override fun blockOrUnblock(uuid: UUID) { + private fun isBlocked(uuid: UUID) = uuid in socialStateManager.relationshipStates.getObservableBlockedList() + + private fun isFriend(uuid: UUID) = uuid in socialStateManager.relationshipStates.getObservableFriendList() + + fun handleBlockOrUnblock(uuid: UUID) { UUIDUtil.getName(uuid).thenAcceptOnMainThread { - val block = !socialStateManager.relationships.isBlocked(uuid) + val block = !isBlocked(uuid) val blockText = if (block) { "Block" } else { @@ -291,9 +487,9 @@ class SocialMenu @JvmOverloads constructor( } } - override fun addOrRemoveFriend(uuid: UUID) { + fun handleAddOrRemove(uuid: UUID) { UUIDUtil.getName(uuid).thenAcceptOnMainThread { - if (socialStateManager.relationships.isFriend(uuid)) { + if (isFriend(uuid)) { GuiUtil.pushModal { manager -> FriendRemoveConfirmationModal(manager, it).onPrimaryAction { socialStateManager.relationshipStates.removeFriend(uuid, false) @@ -306,11 +502,83 @@ class SocialMenu @JvmOverloads constructor( } } + class ConfirmJoinModal(modalManager: ModalManager, user: String, isSps: Boolean) : ConfirmDenyModal(modalManager, false) { + init { + val title = buildString { + append("Are you sure you want to join $user's ") + if (isSps) { + append("world") + } else { + append("server") + } + append("?") + } + configure { + titleText = title + } + } + } + + class ConfirmGroupLeaveModal(modalManager: ModalManager) : ConfirmDenyModal(modalManager, false) { + init { + configure { + titleText = "Are you sure you want to leave this group?" + primaryButtonText = "Confirm" + } + } + } + + class RenameGroupModal(manager: ModalManager, name: String) : CancelableInputModal(manager, "", name, maxLength = 24) { + init { + configure { + titleText = "Rename Group" + contentText = "Enter a new name for your group." + primaryButtonText = "Rename" + } + mapInputToEnabled { + it.isNotBlank() && it != name + } + } + } + companion object { @JvmStatic fun getInstance(): SocialMenu? { return GuiUtil.openedScreen() as? SocialMenu } + + fun getGuiScaleOffset(): Float { + return if (EssentialConfig.enlargeSocialMenuChatMetadata) { + 0f + } else { + -1f + } + } + + fun createAddFriendsToGroupModal(manager: ModalManager, potentialFriends: ListState) = + selectModal(manager, "Add friends to group") { + requiresButtonPress = false + requiresSelection = true + + users("Friends", potentialFriends) + } } + + class BlockConfirmationModal(manager: ModalManager, name: String, blockText: String) : DangerConfirmationEssentialModal(manager, blockText, false) { + init { + configure { + titleText = "Are you sure you want to ${blockText.lowercase()} $name?" + } + } + } + + class FriendRemoveConfirmationModal(manager: ModalManager, name: String) : DangerConfirmationEssentialModal(manager, "Remove", false) { + init { + configure { + titleText = "Are you sure you want to remove $name as your friend?" + } + } + } + } diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/TabsSelector.kt b/src/main/kotlin/gg/essential/gui/friends/TabsSelector.kt similarity index 92% rename from gui/essential/src/main/kotlin/gg/essential/gui/friends/TabsSelector.kt rename to src/main/kotlin/gg/essential/gui/friends/TabsSelector.kt index 5677f4ef..dd6f2cc0 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/friends/TabsSelector.kt +++ b/src/main/kotlin/gg/essential/gui/friends/TabsSelector.kt @@ -11,6 +11,7 @@ */ package gg.essential.gui.friends +import gg.essential.Essential import gg.essential.elementa.constraints.ChildBasedMaxSizeConstraint import gg.essential.elementa.constraints.SiblingConstraint import gg.essential.elementa.dsl.* @@ -19,10 +20,10 @@ import gg.essential.elementa.components.UIContainer import gg.essential.elementa.constraints.* import gg.essential.gui.EssentialPalette import gg.essential.gui.common.bindParent +import gg.essential.gui.common.mapToString import gg.essential.gui.common.shadow.EssentialUIText import gg.essential.gui.common.shadow.ShadowEffect import gg.essential.gui.elementa.state.v2.MutableState -import gg.essential.gui.elementa.state.v2.State import gg.essential.gui.elementa.state.v2.color.toConstraint import gg.essential.gui.elementa.state.v2.combinators.map import gg.essential.gui.elementa.state.v2.memo @@ -34,10 +35,7 @@ import gg.essential.gui.util.hoveredState import gg.essential.universal.USound import gg.essential.vigilance.utils.onLeftClick -class TabsSelector( - selectedTab: MutableState, - unseenFriendRequests: State, -) : UIContainer() { +class TabsSelector(selectedTab: MutableState) : UIContainer() { private val tabContainer by UIContainer().constrain { x = 4.pixels @@ -78,12 +76,12 @@ class TabsSelector( }.bindShadowColor(selectedTab.map { if (it == tab) EssentialPalette.BLUE_SHADOW else EssentialPalette.COMPONENT_BACKGROUND }.toV1(this)) childOf content if (tab == Tab.FRIENDS) { - val count = unseenFriendRequests + val count = Essential.getInstance().connectionManager.socialMenuNewFriendRequestNoticeManager.unseenFriendRequestCount() val unseenFriendRequestIndicator by Tag( stateOf(EssentialPalette.RED), stateOf(EssentialPalette.TEXT_HIGHLIGHT), - text = { count().toString() }, + count.mapToString().toV2(), ).constrain { x = SiblingConstraint(5f) y = CenterConstraint() diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/MessageInput.kt b/src/main/kotlin/gg/essential/gui/friends/message/MessageInput.kt similarity index 94% rename from gui/essential/src/main/kotlin/gg/essential/gui/friends/message/MessageInput.kt rename to src/main/kotlin/gg/essential/gui/friends/message/MessageInput.kt index 957819f5..64eb66bb 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/MessageInput.kt +++ b/src/main/kotlin/gg/essential/gui/friends/message/MessageInput.kt @@ -11,7 +11,6 @@ */ package gg.essential.gui.friends.message -import com.sparkuniverse.toolbox.chat.model.Channel import gg.essential.elementa.UIComponent import gg.essential.elementa.components.ScrollComponent import gg.essential.elementa.components.UIContainer @@ -36,7 +35,7 @@ import gg.essential.gui.friends.message.screenshot.ScreenshotAttachmentManager import gg.essential.gui.friends.message.screenshot.ScreenshotPicker import gg.essential.gui.friends.message.screenshot.screenshotAttachmentUploadBox import gg.essential.gui.friends.message.v2.ClientMessage -import gg.essential.gui.friends.state.SocialStates +import gg.essential.gui.friends.message.v2.ReplyableMessageScreen import gg.essential.gui.image.ImageFactory import gg.essential.gui.layoutdsl.* import gg.essential.gui.notification.Notifications @@ -44,20 +43,16 @@ import gg.essential.gui.notification.warning import gg.essential.universal.ChatColor import gg.essential.universal.UKeyboard import gg.essential.universal.USound -import gg.essential.util.UuidNameLookup +import gg.essential.util.UUIDUtil import gg.essential.util.scrollGradient import gg.essential.vigilance.utils.onLeftClick class MessageInput( - channel: Channel, - isScreenOpen: State, - socialStates: SocialStates, private val channelName: State, private val replyTo: MutableState, private val editingMessage: MutableState, - private val scrollToMessage: (ClientMessage) -> Unit, - private val onSendAction: (String) -> Unit, - private val onEditAction: (String) -> Unit, + private val replyableMessageScreen: ReplyableMessageScreen, + private val onSendAction: (String) -> Unit ) : UIContainer() { private val isReplying = replyTo.map { it != null } @@ -74,7 +69,7 @@ class MessageInput( private var stashedMessage: String? = "" - private val usernameState = memo { replyTo()?.sender?.let(UuidNameLookup::nameState)?.invoke() ?: "Nobody" } + private val usernameState = memo { replyTo()?.sender?.let(UUIDUtil::nameState)?.invoke() ?: "Nobody" } private val message = mutableStateOf("") private val messageLength = memo { message().length } @@ -91,7 +86,7 @@ class MessageInput( EssentialPalette.TEXT_HIGHLIGHT } }) - onKeyType { keyChar, keyCode -> + onKeyType { _, keyCode -> // Cancel reply on escape if (keyCode == UKeyboard.KEY_ESCAPE) { if (isReplying.get()) { @@ -99,11 +94,11 @@ class MessageInput( } else if (isEditing.get()) { editingMessage.set(null) } else { - Window.of(this).keyTypedListeners.forEach { it(keyChar, keyCode) } + replyableMessageScreen.preview.gui.restorePreviousScreen() } } - if (UKeyboard.isEnterKey(keyCode)) { + if (keyCode == UKeyboard.KEY_ENTER) { if (!UKeyboard.isShiftKeyDown()) { handleSendMessage() @@ -120,7 +115,7 @@ class MessageInput( private var cachedCursorRelativeYPosition = 0f - val screenshotAttachmentManager = ScreenshotAttachmentManager(channel, isScreenOpen, socialStates) + val screenshotAttachmentManager = ScreenshotAttachmentManager(replyableMessageScreen.preview.channel, replyableMessageScreen.isScreenOpen) init { @@ -236,7 +231,7 @@ class MessageInput( }.onLeftClick { val message = replyTo.get() ?: editingMessage.get() - message?.let { scrollToMessage(it) } + message?.let { replyableMessageScreen.scrollToMessage(it) } } } @@ -348,7 +343,7 @@ class MessageInput( } while (modified) if (isEditing.get()) { - onEditAction(text) + replyableMessageScreen.editMessage(text) } else { onSendAction(text) screenshotAttachmentManager.uploadAndSend() diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/MessageScreen.kt b/src/main/kotlin/gg/essential/gui/friends/message/MessageScreen.kt similarity index 95% rename from gui/essential/src/main/kotlin/gg/essential/gui/friends/message/MessageScreen.kt rename to src/main/kotlin/gg/essential/gui/friends/message/MessageScreen.kt index 9c1c3722..b7679f45 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/MessageScreen.kt +++ b/src/main/kotlin/gg/essential/gui/friends/message/MessageScreen.kt @@ -52,11 +52,6 @@ abstract class MessageScreen : UIContainer(){ */ abstract fun retrySend(message: ClientMessage) - /** - * Removes a given unsent message - */ - abstract fun removeUnsent(message: ClientMessage) - /** * Called when [messageWrapper] has marked itself as manually unread */ diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/MessageTitleBar.kt b/src/main/kotlin/gg/essential/gui/friends/message/MessageTitleBar.kt similarity index 79% rename from gui/essential/src/main/kotlin/gg/essential/gui/friends/message/MessageTitleBar.kt rename to src/main/kotlin/gg/essential/gui/friends/message/MessageTitleBar.kt index 3b56da2a..8e2cea32 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/MessageTitleBar.kt +++ b/src/main/kotlin/gg/essential/gui/friends/message/MessageTitleBar.kt @@ -24,7 +24,7 @@ import gg.essential.gui.common.shadow.EssentialUIText import gg.essential.gui.elementa.state.v2.combinators.map import gg.essential.gui.elementa.state.v2.memo import gg.essential.gui.elementa.state.v2.toV1 -import gg.essential.gui.friends.state.SocialStates +import gg.essential.gui.friends.SocialMenu import gg.essential.gui.layoutdsl.LayoutScope import gg.essential.gui.layoutdsl.Modifier import gg.essential.gui.layoutdsl.heightAspect @@ -40,8 +40,7 @@ import java.util.* class MessageTitleBar( private val messageScreen: MessageScreen, - private val socialStates: SocialStates, - private val socialMenuActions: SocialMenuActions, + private val gui: SocialMenu, ) : UIContainer() { private val preview = messageScreen.preview @@ -84,16 +83,12 @@ class MessageTitleBar( // Member container is not bound if this is an announcement channel memberContainer.bindChildren( - socialStates.messages.getObservableMemberList(preview.channel.id), - filter = { it != USession.activeNow().uuid } + gui.socialStateManager.messengerStates.getObservableMemberList(preview.channel.id), + filter = { it != UUIDUtil.getClientUUID() } ) { uuid -> Member(uuid).also { member -> member.bindHoverEssentialTooltip(memo { - if (member.isMemberSuspendedState()) { - "Temporarily suspended from Essential social features" - } else { - UuidNameLookup.nameState(uuid, "Loading...")() - } + UUIDUtil.nameState(uuid, "Loading...")() }.toV1(member)) } } @@ -103,14 +98,13 @@ class MessageTitleBar( it.stopPropagation() dropdownOpen.set(true) - socialMenuActions.showManagementDropdown(preview, ContextOptionMenu.Position(managementButton, true)) { + gui.showManagementDropdown(preview, ContextOptionMenu.Position(managementButton, true)) { dropdownOpen.set(false) } } - val otherUser = preview.otherUser - if (otherUser != null) { - val activityState = socialStates.activity.getActivityState(otherUser) + if (preview.otherUser != null) { + val activityState = gui.socialStateManager.statusStates.getActivityState(preview.otherUser) var imageIcon = EssentialPalette.JOIN_ARROW_10X5 @@ -122,7 +116,7 @@ class MessageTitleBar( }.bindParent(this, activityState.map { it.isJoinable() }) button.onLeftClick { USound.playButtonPress() - socialMenuActions.joinSessionWithConfirmation(otherUser) + gui.handleJoinSession(preview.otherUser) } button @@ -157,7 +151,7 @@ class MessageTitleBar( if (!invited.get()) { USound.playButtonPress() invited.set(true) - socialMenuActions.invitePlayers(preview.channel.members, preview.titleState.getUntracked()) + gui.handleInvitePlayers(preview.channel.members, preview.titleState.getUntracked()) } } button.onMouseLeave { @@ -181,8 +175,6 @@ class MessageTitleBar( inner class Member(private val member: UUID) : UIContainer() { - val isMemberSuspendedState = socialStates.isSuspended(member) - init { constrain { width = ChildBasedSizeConstraint() @@ -193,25 +185,21 @@ class MessageTitleBar( layout { val headModifier = Modifier.width(16f).heightAspect(1f).shadow() - if_(isMemberSuspendedState) { - headGrayscale(headModifier) - } `else` { - head(headModifier) - } + head(headModifier) } this.onMouseClick { if (it.mouseButton > 1) { return@onMouseClick } - if (member != USession.activeNow().uuid) { + if (member != UUIDUtil.getClientUUID()) { val options = mutableListOf() - if (preview.channel.createdInfo.by == USession.activeNow().uuid && preview.channel.type == ChannelType.GROUP_DIRECT_MESSAGE) { + if (preview.channel.createdInfo.by == UUIDUtil.getClientUUID() && preview.channel.type == ChannelType.GROUP_DIRECT_MESSAGE) { options.add(ContextOptionMenu.Option("Remove from group", image = EssentialPalette.CANCEL_5X) { - socialStates.messages.removeUser(preview.channel.id, member) + gui.socialStateManager.messengerStates.removeUser(preview.channel.id, member) }) } - socialMenuActions.showUserDropdown(member, ContextOptionMenu.Position(this, false), options) { + gui.showUserDropdown(member, ContextOptionMenu.Position(this, false), options) { } } @@ -222,8 +210,5 @@ class MessageTitleBar( CachedAvatarImage.create(member)(modifier) } - private fun LayoutScope.headGrayscale(modifier: Modifier = Modifier) { - CachedAvatarImage.create(member, grayscale = true)(modifier) - } } } diff --git a/src/main/kotlin/gg/essential/gui/friends/message/OldMessageInput.kt b/src/main/kotlin/gg/essential/gui/friends/message/OldMessageInput.kt new file mode 100644 index 00000000..9126b81e --- /dev/null +++ b/src/main/kotlin/gg/essential/gui/friends/message/OldMessageInput.kt @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.gui.friends.message + +import gg.essential.elementa.components.UIBlock +import gg.essential.elementa.components.UIContainer +import gg.essential.elementa.constraints.* +import gg.essential.elementa.dsl.* +import gg.essential.elementa.state.BasicState +import gg.essential.elementa.state.State +import gg.essential.gui.EssentialPalette +import gg.essential.gui.common.IconButton +import gg.essential.gui.common.constraints.CenterPixelConstraint +import gg.essential.gui.common.input.UIMultilineTextInput +import gg.essential.gui.common.onSetValueAndNow +import gg.essential.gui.friends.message.v2.ClientMessage +import gg.essential.gui.friends.message.v2.ReplyableMessageScreen +import gg.essential.gui.notification.Notifications +import gg.essential.gui.notification.warning +import gg.essential.universal.ChatColor +import gg.essential.universal.UKeyboard +import gg.essential.vigilance.utils.onLeftClick + +open class OldMessageInput( + private val groupName: State, + editingMessage: State, + private val replyableMessageScreen: ReplyableMessageScreen, +) : UIContainer() { + private lateinit var onSendAction: (String) -> Unit + + val messageEditingState = editingMessage.map { it != null } + + protected val topDivider by UIBlock(EssentialPalette.COMPONENT_BACKGROUND).constrain { + width = 100.percent + height = 3.pixels + } childOf this + + private val inputBlock by UIBlock(EssentialPalette.INPUT_BACKGROUND).constrain { + y = SiblingConstraint() + width = 100.percent + height = 30.pixels + } childOf this + + internal val input by UIMultilineTextInput(shadowColor = EssentialPalette.TEXT_SHADOW_LIGHT).constrain { + x = 10.pixels + y = CenterPixelConstraint() + width = 100.percent - 63.pixels + height = 10.pixels + }.apply { + groupName.onSetValueAndNow { + placeholder = "Message $it" + } + setMaxLines(2) + setColor(basicColorConstraint { + if (getText().isEmpty()) { + EssentialPalette.TEXT + } else { + EssentialPalette.TEXT_HIGHLIGHT + } + }) + } childOf inputBlock + + private val sendButtonEnabled = BasicState(false) + + private val imageContainer by IconButton( + EssentialPalette.ARROW_RIGHT_4X7, + buttonText = "", + tooltipText = "Send", + ).constrain { + x = 10.pixels(alignOpposite = true) + y = CenterConstraint() + width = 17.pixels + height = AspectConstraint() + }.apply { + onLeftClick { + handleSendMessage() + } + rebindEnabled(sendButtonEnabled) + } childOf inputBlock + + + init { + + constrain { + y = SiblingConstraint() + width = 100.percent + height = ChildBasedSizeConstraint() + } + + onLeftClick { + grabFocus() + } + + input.onKeyType { _, keyCode -> + if (keyCode == UKeyboard.KEY_ENTER) { + if (!UKeyboard.isShiftKeyDown()) { + handleSendMessage() + } + } + } + + input.onUpdate { + sendButtonEnabled.set(it.isNotEmpty()) + } + } + + fun grabFocus() { + input.grabWindowFocus() + } + + private fun handleSendMessage() { + var text = input.getText() + val charLimit = 500 + if (text.length > charLimit) { + Notifications.warning("Too many characters", "You have exceeded the\n$charLimit character limit.") + return + } + //Keep calling the replacement until there are no more changes + //§§; will pass the filter because on the first pass + // § is replaced but still leaves § + var previous = text + var modified = false + do { + for (colorSymbol in colorSymbols) { + text = text.replace(colorSymbol, "") + } + if (text != previous) { + previous = text + modified = true + } else { + modified = false + } + } while (modified) + + if (messageEditingState.get()) { + replyableMessageScreen.editMessage(text) + } else { + onSendAction(text) + input.setText("") + } + } + + override fun afterInitialization() { + input.grabWindowFocus() + } + + companion object { + val colorSymbols = listOf("§", "§", "§", "${ChatColor.COLOR_CHAR}") + } +} diff --git a/src/main/kotlin/gg/essential/gui/friends/message/ReportMessageModal.kt b/src/main/kotlin/gg/essential/gui/friends/message/ReportMessageModal.kt new file mode 100644 index 00000000..dbb5eca8 --- /dev/null +++ b/src/main/kotlin/gg/essential/gui/friends/message/ReportMessageModal.kt @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.gui.friends.message + +import com.sparkuniverse.toolbox.chat.model.Message +import gg.essential.Essential +import gg.essential.elementa.components.UIBlock +import gg.essential.elementa.components.UIContainer +import gg.essential.elementa.constraints.AspectConstraint +import gg.essential.elementa.constraints.CenterConstraint +import gg.essential.elementa.constraints.ChildBasedMaxSizeConstraint +import gg.essential.elementa.constraints.ChildBasedSizeConstraint +import gg.essential.elementa.constraints.SiblingConstraint +import gg.essential.elementa.dsl.childOf +import gg.essential.elementa.dsl.constrain +import gg.essential.elementa.dsl.pixels +import gg.essential.elementa.dsl.provideDelegate +import gg.essential.elementa.dsl.toConstraint +import gg.essential.elementa.state.BasicState +import gg.essential.gui.EssentialPalette +import gg.essential.gui.common.Spacer +import gg.essential.gui.common.bindParent +import gg.essential.gui.common.modal.DangerConfirmationEssentialModal +import gg.essential.gui.common.modal.configure +import gg.essential.gui.common.shadow.EssentialUIText +import gg.essential.gui.elementa.state.v2.combinators.map +import gg.essential.gui.layoutdsl.color +import gg.essential.gui.layoutdsl.shadow +import gg.essential.gui.layoutdsl.width +import gg.essential.gui.overlay.ModalManager +import gg.essential.universal.UMinecraft +import gg.essential.universal.USound +import gg.essential.util.centered +import gg.essential.util.onLeftClick + +class ReportMessageModal(modalManager: ModalManager, val message: Message, username: String) : + DangerConfirmationEssentialModal(modalManager, confirmText = "Report", requiresButtonPress = false) { + + private val reasonMap = Essential.getInstance().connectionManager.chatManager + .getReportReasons(UMinecraft.getSettings().language) + + private val reportReasons = reasonMap.values.toList() + private var reportReason = BasicState(reportReasons[0]) + + init { + + configure { + titleText = "Report $username" + } + + configureLayout { content -> + val reasonsContainer by UIContainer().constrain { + x = CenterConstraint() + y = SiblingConstraint() + width = ChildBasedMaxSizeConstraint() + height = ChildBasedSizeConstraint() + } childOf content + + reportReasons.forEach { reason -> + val container by UIContainer().constrain { + y = SiblingConstraint(8f) + width = ChildBasedSizeConstraint() + height = ChildBasedMaxSizeConstraint() + }.onLeftClick { + USound.playButtonPress() + reportReason.set(reason) + } childOf reasonsContainer + + val checkbox by UIBlock().constrain { + width = 9.pixels + height = AspectConstraint() + color = EssentialPalette.BUTTON.toConstraint() + } childOf container + + val checkmark by EssentialPalette.CHECKMARK_7X5.withColor(EssentialPalette.TEXT) + .create().centered().bindParent(checkbox, reportReason.map { + it == reason + }) + + val text by EssentialUIText(reason).constrain { + x = SiblingConstraint(5f) + color = EssentialPalette.TEXT.toConstraint() + } childOf container + } + + // Bottom padding + Spacer(height = 26f) childOf content + } + + onPrimaryAction { + Essential.getInstance().connectionManager.chatManager.fileReport( + modalManager, + message.channelId, + message.id, + message.sender, + reasonMap.entries.first { + it.value == reportReason.get() + }.key + ) + } + } +} + diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/screenshot/RemoveableScreenshotPreview.kt b/src/main/kotlin/gg/essential/gui/friends/message/screenshot/RemoveableScreenshotPreview.kt similarity index 100% rename from gui/essential/src/main/kotlin/gg/essential/gui/friends/message/screenshot/RemoveableScreenshotPreview.kt rename to src/main/kotlin/gg/essential/gui/friends/message/screenshot/RemoveableScreenshotPreview.kt diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/screenshot/ScreenshotAttacher.kt b/src/main/kotlin/gg/essential/gui/friends/message/screenshot/ScreenshotAttacher.kt similarity index 100% rename from gui/essential/src/main/kotlin/gg/essential/gui/friends/message/screenshot/ScreenshotAttacher.kt rename to src/main/kotlin/gg/essential/gui/friends/message/screenshot/ScreenshotAttacher.kt diff --git a/src/main/kotlin/gg/essential/gui/friends/message/screenshot/ScreenshotAttachmentComponents.kt b/src/main/kotlin/gg/essential/gui/friends/message/screenshot/ScreenshotAttachmentComponents.kt new file mode 100644 index 00000000..3a2f67d8 --- /dev/null +++ b/src/main/kotlin/gg/essential/gui/friends/message/screenshot/ScreenshotAttachmentComponents.kt @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.gui.friends.message.screenshot + +import gg.essential.elementa.UIComponent +import gg.essential.elementa.constraints.CenterConstraint +import gg.essential.elementa.constraints.animation.Animations +import gg.essential.elementa.dsl.boundTo +import gg.essential.elementa.dsl.coerceIn +import gg.essential.elementa.dsl.pixels +import gg.essential.elementa.effects.ScissorEffect +import gg.essential.gui.EssentialPalette +import gg.essential.gui.common.MenuButton +import gg.essential.gui.elementa.state.v2.MutableState +import gg.essential.gui.elementa.state.v2.State +import gg.essential.gui.elementa.state.v2.combinators.map +import gg.essential.gui.layoutdsl.Alignment +import gg.essential.gui.layoutdsl.Arrangement +import gg.essential.gui.layoutdsl.BasicYModifier +import gg.essential.gui.layoutdsl.LayoutScope +import gg.essential.gui.layoutdsl.Modifier +import gg.essential.gui.layoutdsl.alignHorizontal +import gg.essential.gui.layoutdsl.animateWidth +import gg.essential.gui.layoutdsl.box +import gg.essential.gui.layoutdsl.childBasedHeight +import gg.essential.gui.layoutdsl.childBasedMaxHeight +import gg.essential.gui.layoutdsl.childBasedWidth +import gg.essential.gui.layoutdsl.color +import gg.essential.gui.layoutdsl.column +import gg.essential.gui.layoutdsl.effect +import gg.essential.gui.layoutdsl.fillHeight +import gg.essential.gui.layoutdsl.fillWidth +import gg.essential.gui.layoutdsl.floatingBox +import gg.essential.gui.layoutdsl.height +import gg.essential.gui.layoutdsl.heightAspect +import gg.essential.gui.layoutdsl.hoverColor +import gg.essential.gui.layoutdsl.hoverScope +import gg.essential.gui.layoutdsl.icon +import gg.essential.gui.layoutdsl.row +import gg.essential.gui.layoutdsl.shadow +import gg.essential.gui.layoutdsl.spacer +import gg.essential.gui.layoutdsl.text +import gg.essential.gui.layoutdsl.width +import gg.essential.gui.screenshot.DateRange +import gg.essential.gui.screenshot.ScreenshotId +import gg.essential.universal.USound +import gg.essential.vigilance.utils.onLeftClick +import java.awt.Color + +fun LayoutScope.screenshotAttachmentUploadBox( + screenshotAttachmentManager: ScreenshotAttachmentManager +) { + row(Modifier.childBasedMaxHeight(9f).color(EssentialPalette.COMPONENT_BACKGROUND_HIGHLIGHT)) { + spacer(width = 10f) + icon(EssentialPalette.PICTURES_10X10, Modifier.color(EssentialPalette.TEXT)) + spacer(width = 6f) + text("Uploading...", Modifier.color(EssentialPalette.TEXT).shadow(Color.BLACK)) + spacer(width = 11f) + box(Modifier.width(100f).height(3f).color(EssentialPalette.GUI_BACKGROUND).shadow(Color.BLACK)) { + box( + Modifier.animateWidth( + screenshotAttachmentManager.totalProgressPercentage.map { { it.pixels } }, + 0.5f, + Animations.LINEAR + ).fillHeight().color(EssentialPalette.TOAST_PROGRESS).alignHorizontal(Alignment.Start) + ) + } + spacer(width = 10f) + }.addUpdateFunc { _, _ -> + screenshotAttachmentManager.updateProgress() + } +} + +fun LayoutScope.screenshotAttachmentDoneButton(screenshotAttachmentManager: ScreenshotAttachmentManager) { + box( + Modifier.width(43f).height(17f) + .color(MenuButton.BLUE.buttonColor) + .hoverColor(MenuButton.LIGHT_BLUE.buttonColor) + .shadow() + .hoverScope() + ) { + text( + "Done", + Modifier.shadow(), + centeringContainsShadow = false + ) + }.onLeftClick { + USound.playButtonPress() + screenshotAttachmentManager.isPickingScreenshots.set(false) + } +} + +fun LayoutScope.screenshotDateGroup( + range: DateRange, + startTime: Long, + numberOfItemsPerRow: State, + screenshotIds: List, + desiredImageSize: MutableState>, + screenshotAttachmentManager: ScreenshotAttachmentManager, + navigation: UIComponent, + contentBox: UIComponent +) { + val divider: UIComponent + val content: UIComponent + + box(Modifier.fillWidth().childBasedMaxHeight()) { + content = containerDontUseThisUnlessYouReallyHaveTo + column(Modifier.fillWidth().childBasedHeight()) { + divider = box(Modifier.fillWidth().childBasedMaxHeight(11f)) { + box( + Modifier.fillWidth(padding = ScreenshotPicker.SCREENSHOT_SIDE_PADDING + 2f).height(1f) + .color(EssentialPalette.COMPONENT_BACKGROUND) + ) + box(Modifier.childBasedWidth(4f).childBasedHeight().color(EssentialPalette.GUI_BACKGROUND)) { + // Blank text to measure correct size for background of real text + text( + range.getName(startTime), + modifier = Modifier.color(EssentialPalette.GUI_BACKGROUND) + .shadow(EssentialPalette.GUI_BACKGROUND) + ) + } + } + column( + Modifier.fillWidth().childBasedHeight(), + Arrangement.spacedBy(ScreenshotPicker.SCREENSHOT_PADDING) + ) { + bind(numberOfItemsPerRow) { itemsPerRows -> + for (list in screenshotIds.chunked(itemsPerRows)) { + row( + Modifier.fillWidth(padding = ScreenshotPicker.SCREENSHOT_SIDE_PADDING), + Arrangement.equalWeight(ScreenshotPicker.SCREENSHOT_PADDING) + ) { + val imageModifier = Modifier.heightAspect(9 / 16f) + for (i in 0 until itemsPerRows) { + if (i < list.size) { + SelectableScreenshotPreview( + list[i], + desiredImageSize, + screenshotAttachmentManager + )(imageModifier) + } else { + box(imageModifier) + } + } + } + } + } + } + } + floatingBox( + Modifier.childBasedWidth(4f) + .effect { ScissorEffect(contentBox) } + .then(BasicYModifier { + // Position the title in the center of navigation + (CenterConstraint() boundTo navigation) + // but force it to stay within the content's bounds, so the titles of different groups never overlap + .coerceIn( + CenterConstraint() boundTo divider, + 0.pixels(alignOpposite = true) boundTo content + ) + }) + ) { + text(range.getName(startTime), centeringContainsShadow = true) + } + } +} \ No newline at end of file diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/screenshot/ScreenshotAttachmentManager.kt b/src/main/kotlin/gg/essential/gui/friends/message/screenshot/ScreenshotAttachmentManager.kt similarity index 87% rename from gui/essential/src/main/kotlin/gg/essential/gui/friends/message/screenshot/ScreenshotAttachmentManager.kt rename to src/main/kotlin/gg/essential/gui/friends/message/screenshot/ScreenshotAttachmentManager.kt index 4e4d6596..c0d7ed43 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/screenshot/ScreenshotAttachmentManager.kt +++ b/src/main/kotlin/gg/essential/gui/friends/message/screenshot/ScreenshotAttachmentManager.kt @@ -12,6 +12,7 @@ package gg.essential.gui.friends.message.screenshot import com.sparkuniverse.toolbox.chat.model.Channel +import gg.essential.Essential import gg.essential.connectionmanager.common.packet.Packet import gg.essential.connectionmanager.common.packet.chat.ServerChatChannelMessagePacket import gg.essential.elementa.components.Window @@ -23,15 +24,12 @@ import gg.essential.gui.elementa.state.v2.combinators.map import gg.essential.gui.elementa.state.v2.combinators.not import gg.essential.gui.elementa.state.v2.mutableListStateOf import gg.essential.gui.elementa.state.v2.mutableStateOf -import gg.essential.gui.friends.state.SocialStates import gg.essential.gui.screenshot.LocalScreenshot import gg.essential.gui.screenshot.RemoteScreenshot import gg.essential.gui.screenshot.ScreenshotId import gg.essential.gui.screenshot.ScreenshotUploadToast.ToastProgress import gg.essential.media.model.Media import gg.essential.media.model.MediaVariant -import gg.essential.util.GuiEssentialPlatform.Companion.platform -import org.slf4j.LoggerFactory import java.util.* import java.util.concurrent.CompletableFuture import java.util.concurrent.ExecutionException @@ -39,7 +37,6 @@ import java.util.concurrent.ExecutionException class ScreenshotAttachmentManager( val channel: Channel, val isScreenOpen: State, - val socialStates: SocialStates, ) { val maxSelectAmount = 10 @@ -56,7 +53,7 @@ class ScreenshotAttachmentManager( fun uploadAndSend() { - val screenshotManager = platform.screenshotManager + val screenshotManager = Essential.getInstance().connectionManager.screenshotManager val uploadTasks = mutableMapOf>() @@ -91,7 +88,7 @@ class ScreenshotAttachmentManager( val media = try { task.get() } catch (e: ExecutionException) { - LOGGER.error("Unable to upload image ${id.name}", e.cause) + Essential.logger.error("Unable to upload image ${id.name}", e.cause) // Set progress to failed unless it already is, in which case it'll likely have a better message if ((progresses[id] as? ToastProgress.Complete)?.success != false) { progresses[id] = ToastProgress.Complete("Failed: An unknown error occurred", false) @@ -102,7 +99,7 @@ class ScreenshotAttachmentManager( progresses[id] = ToastProgress.Complete("Screenshot was uploaded.", true) val embed = media.variants["embed"] if (embed == null) { - LOGGER.error("Unable to share screenshot ${media.id} to channel as it's missing its embed.") + Essential.logger.error("Unable to share screenshot ${media.id} to channel as it's missing its embed.") continue } embeds.add(embed) @@ -114,12 +111,12 @@ class ScreenshotAttachmentManager( val message = embeds.joinToString("\n") { it.url } - socialStates.messages.sendMessage( + Essential.getInstance().connectionManager.chatManager.sendMessage( channel.id, message - ) { response: Optional -> + ) { response: Optional -> if (!response.isPresent || response.get() !is ServerChatChannelMessagePacket) { - LOGGER.error("Failed to send message of ${embeds.size} screenshot(s) to channel.") + Essential.logger.error("Failed to send message of ${embeds.size} screenshot(s) to channel.") } } }, Window::enqueueRenderOperation) @@ -144,7 +141,4 @@ class ScreenshotAttachmentManager( totalProgressPercentage.set(0) } - companion object { - private val LOGGER = LoggerFactory.getLogger(ScreenshotAttachmentManager::class.java) - } } \ No newline at end of file diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/screenshot/ScreenshotPicker.kt b/src/main/kotlin/gg/essential/gui/friends/message/screenshot/ScreenshotPicker.kt similarity index 97% rename from gui/essential/src/main/kotlin/gg/essential/gui/friends/message/screenshot/ScreenshotPicker.kt rename to src/main/kotlin/gg/essential/gui/friends/message/screenshot/ScreenshotPicker.kt index 496358b6..f46ba962 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/screenshot/ScreenshotPicker.kt +++ b/src/main/kotlin/gg/essential/gui/friends/message/screenshot/ScreenshotPicker.kt @@ -124,7 +124,7 @@ class ScreenshotPicker( } onKeyType { char, keyCode -> - if (UKeyboard.isEnterKey(keyCode) || keyCode == UKeyboard.KEY_ESCAPE) { + if (keyCode == UKeyboard.KEY_ENTER || keyCode == UKeyboard.KEY_ESCAPE) { screenshotAttachmentManager.isPickingScreenshots.set(false) } else { for (listener in Window.of(this).keyTypedListeners) { @@ -141,6 +141,8 @@ class ScreenshotPicker( } companion object { + val SCREENSHOT_PADDING = 3f + val SCREENSHOT_SIDE_PADDING = 7f } } diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/screenshot/SelectableScreenshotPreview.kt b/src/main/kotlin/gg/essential/gui/friends/message/screenshot/SelectableScreenshotPreview.kt similarity index 100% rename from gui/essential/src/main/kotlin/gg/essential/gui/friends/message/screenshot/SelectableScreenshotPreview.kt rename to src/main/kotlin/gg/essential/gui/friends/message/screenshot/SelectableScreenshotPreview.kt diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/v2/DateDividerImpl.kt b/src/main/kotlin/gg/essential/gui/friends/message/v2/DateDividerImpl.kt similarity index 94% rename from gui/essential/src/main/kotlin/gg/essential/gui/friends/message/v2/DateDividerImpl.kt rename to src/main/kotlin/gg/essential/gui/friends/message/v2/DateDividerImpl.kt index 03c9a596..773be93e 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/v2/DateDividerImpl.kt +++ b/src/main/kotlin/gg/essential/gui/friends/message/v2/DateDividerImpl.kt @@ -18,6 +18,7 @@ import gg.essential.elementa.state.BasicState import gg.essential.elementa.state.State import gg.essential.gui.EssentialPalette import gg.essential.gui.about.components.ColoredDivider +import gg.essential.gui.friends.SocialMenu import gg.essential.util.formatDate import java.time.Instant import java.time.LocalDate @@ -37,7 +38,7 @@ class DateDividerImpl( color, false, dividerColor = color, - scaleOffset = getGuiScaleOffset(), + scaleOffset = SocialMenu.getGuiScaleOffset(), ).constrain { y = 12.pixels } childOf this diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/v2/GiftEmbedImpl.kt b/src/main/kotlin/gg/essential/gui/friends/message/v2/GiftEmbedImpl.kt similarity index 96% rename from gui/essential/src/main/kotlin/gg/essential/gui/friends/message/v2/GiftEmbedImpl.kt rename to src/main/kotlin/gg/essential/gui/friends/message/v2/GiftEmbedImpl.kt index 503ae1a2..963c0ab7 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/v2/GiftEmbedImpl.kt +++ b/src/main/kotlin/gg/essential/gui/friends/message/v2/GiftEmbedImpl.kt @@ -11,6 +11,7 @@ */ package gg.essential.gui.friends.message.v2 +import gg.essential.Essential import gg.essential.elementa.constraints.ChildBasedMaxSizeConstraint import gg.essential.elementa.dsl.constrain import gg.essential.gui.EssentialPalette @@ -21,14 +22,13 @@ import gg.essential.gui.layoutdsl.* import gg.essential.gui.wardrobe.components.openWardrobeWithHighlight import gg.essential.gui.util.hoveredState import gg.essential.gui.wardrobe.ItemId -import gg.essential.util.GuiEssentialPlatform.Companion.platform import gg.essential.vigilance.utils.onLeftClick class GiftEmbedImpl( cosmeticId: String, messageWrapper: MessageWrapper ) : GiftEmbed(messageWrapper) { - private val cosmeticsManager = platform.cosmeticsManager + private val cosmeticsManager = Essential.getInstance().connectionManager.cosmeticsManager private val sent = messageWrapper.sentByClient private val giftText = if (sent) "Gift sent" else "Gift" diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/v2/ImageEmbedImpl.kt b/src/main/kotlin/gg/essential/gui/friends/message/v2/ImageEmbedImpl.kt similarity index 85% rename from gui/essential/src/main/kotlin/gg/essential/gui/friends/message/v2/ImageEmbedImpl.kt rename to src/main/kotlin/gg/essential/gui/friends/message/v2/ImageEmbedImpl.kt index f4bbd89a..781bb7a4 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/v2/ImageEmbedImpl.kt +++ b/src/main/kotlin/gg/essential/gui/friends/message/v2/ImageEmbedImpl.kt @@ -12,6 +12,8 @@ package gg.essential.gui.friends.message.v2 import com.sparkuniverse.toolbox.chat.enums.ChannelType +import gg.essential.Essential +import gg.essential.api.gui.Slot import gg.essential.elementa.components.UIBlock import gg.essential.elementa.components.UIContainer import gg.essential.elementa.components.UIImage @@ -28,22 +30,18 @@ import gg.essential.gui.common.shadow.EssentialUIText import gg.essential.gui.elementa.state.v2.toV1 import gg.essential.gui.friends.message.MessageUtils import gg.essential.gui.layoutdsl.* +import gg.essential.gui.notification.Notifications +import gg.essential.gui.screenshot.components.ScreenshotBrowser import gg.essential.gui.screenshot.constraints.AspectPreservingFillConstraint import gg.essential.gui.screenshot.copyScreenshotToClipboard -import gg.essential.gui.screenshot.saveImageAsScreenshot import gg.essential.gui.util.hoveredState import gg.essential.gui.util.stateBy import gg.essential.universal.ChatColor import gg.essential.universal.UKeyboard import gg.essential.util.* -import gg.essential.util.GuiEssentialPlatform.Companion.platform import gg.essential.vigilance.utils.onLeftClick -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import org.slf4j.LoggerFactory +import net.minecraft.client.Minecraft +import org.apache.commons.io.FileUtils import java.awt.Color import java.awt.image.BufferedImage import java.io.ByteArrayInputStream @@ -61,11 +59,9 @@ class ImageEmbedImpl( wrapper: MessageWrapper, ) : ImageEmbed(url, wrapper) { - private val coroutineScope = CoroutineScope(Dispatchers.Client) - private val loadingState = BasicState(false) private var loadedImage: BufferedImage? = null - private var loadingJob: Job? = null + private var loading: Int? = null private val aspectRatio = BasicState(16 / 9f) private val highlightedState = BasicState(false) @@ -87,7 +83,7 @@ class ImageEmbedImpl( height = ChildBasedMaxSizeConstraint() } - if (loadingJob == null) { + if (loading == null) { prepareAndLoad() } @@ -97,30 +93,28 @@ class ImageEmbedImpl( private fun prepareAndLoad() { loadingState.set(true) + val nextId = nextLoadingId++ + loading = nextId + val localPaths = MessageUtils.SCREENSHOT_URL_REGEX.find(url.toString()) - ?.let { platform.screenshotManager.getUploadedLocalPathsCache(it.groupValues[1]) } + ?.let { Essential.getInstance().connectionManager.screenshotManager.getUploadedLocalPathsCache(it.groupValues[1]) } ?: emptyList() - - loadingJob?.cancel() - loadingJob = coroutineScope.launch { - val bufferedImage = withContext(Dispatchers.IO) { - localPaths.firstNotNullOfOrNull { fetchLocalImage(it) } ?: fetchRemoteImage() - } - val uiImage = bufferedImage?.let { loadUIImage(it) } - loadImage(bufferedImage, uiImage) + Multithreading.runAsync { + loadImage(nextId, localPaths.firstNotNullOfOrNull { fetchLocalImage(it) } ?: fetchRemoteImage()) } } override fun copyImageToClipboard() { val loadedImage = loadedImage if (loadedImage != null && loadedImage != noImage) { - coroutineScope.launch(Dispatchers.IO) { + Multithreading.runAsync { val tempFile = Files.createTempFile("essential-screenshot", "png").toFile() ImageIO.write(loadedImage, "png", tempFile) - withContext(Dispatchers.Client) { + Minecraft.getMinecraft().executor.execute { copyScreenshotToClipboard(tempFile.toPath()) + // Cleanup temp file + FileUtils.deleteQuietly(tempFile) } - tempFile.delete() } } } @@ -128,8 +122,16 @@ class ImageEmbedImpl( override fun saveImageToScreenshotBrowser() { val loadedImage = loadedImage if (loadedImage != null && loadedImage != noImage) { - coroutineScope.launch { - saveImageAsScreenshot(loadedImage) + val future = Essential.getInstance().connectionManager.screenshotManager.saveDownloadedImageAsync(loadedImage) + future.whenComplete { _, throwable -> + if (throwable == null) { + Notifications.push( + "Picture saved", "", + action = { GuiUtil.openScreen { ScreenshotBrowser() } } + ) { + withCustomComponent(Slot.ICON, EssentialPalette.PICTURES_SHORT_9X7.create()) + } + } } } } @@ -142,13 +144,13 @@ class ImageEmbedImpl( highlightedState.set(false) } - private suspend fun download(): BufferedImage? { - val original = httpGetToBytes(url.toString()) + private fun download(): BufferedImage? { + val original = httpGetToBytesBlocking(url.toString()) try { ImageIO.read(ByteArrayInputStream(original))?.let { return it } } catch (e: Exception) { - LOGGER.debug("Error parsing image", e) + Essential.logger.debug("Error parsing image", e) } // Follow metadata if present @@ -159,12 +161,12 @@ class ImageEmbedImpl( return noImage } - val embedUrlBytes = httpGetToBytes(embedUrl) + val embedUrlBytes = httpGetToBytesBlocking(embedUrl) return try { ImageIO.read(ByteArrayInputStream(embedUrlBytes)) } catch (e: Exception) { - LOGGER.debug("Error parsing image", e) + Essential.logger.debug("Error parsing image", e) null } } @@ -175,12 +177,12 @@ class ImageEmbedImpl( private fun fetchLocalImage(localPath: Path): BufferedImage? { return try { if (!localPath.isRegularFile()) { - LOGGER.debug("Local image path does not point to a file: {}", localPath) + Essential.logger.debug("Local image path does not point to a file: {}", localPath) return null } localPath.inputStream().use { ImageIO.read(it) } } catch (e: Exception) { - LOGGER.error("Error loading local image using path: $localPath", e) + Essential.logger.error("Error loading local image using path: $localPath", e) null } } @@ -188,17 +190,21 @@ class ImageEmbedImpl( /** * Attempts to download remote image and returns BufferedImage if successful */ - private suspend fun fetchRemoteImage(): BufferedImage? { + private fun fetchRemoteImage(): BufferedImage? { return try { download() } catch (e: IOException) { - LOGGER.debug("Error downloading image", e) + Essential.logger.debug("Error downloading image", e) null } } - private fun loadImage(loadedImage: BufferedImage?, uiImage: UIImage?) { - run maybeLoadUIImage@{ + private fun loadImage(id: Int, loadedImage: BufferedImage?) { + maybeLoadUIImage(loadedImage) { uiImage -> + if (id != loading) { + return@maybeLoadUIImage + } + loadingState.set(false) this.loadedImage = loadedImage @@ -394,8 +400,7 @@ class ImageEmbedImpl( } companion object { - private val LOGGER = LoggerFactory.getLogger(ImageEmbedImpl::class.java) - + private var nextLoadingId: Int = 0 private const val animationTime = 0.25f // Used to denote that no image should be displayed. This is returned by download() diff --git a/src/main/kotlin/gg/essential/gui/friends/message/v2/InviteEmbedImpl.kt b/src/main/kotlin/gg/essential/gui/friends/message/v2/InviteEmbedImpl.kt new file mode 100644 index 00000000..5c087257 --- /dev/null +++ b/src/main/kotlin/gg/essential/gui/friends/message/v2/InviteEmbedImpl.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.gui.friends.message.v2 + +import gg.essential.gui.elementa.state.v2.MutableState +import gg.essential.gui.elementa.state.v2.mutableStateOf +import gg.essential.gui.layoutdsl.* +import gg.essential.network.connectionmanager.ConnectionManager +import java.awt.image.BufferedImage + +class ServerInfo(private val address: String, private val connectionManager: ConnectionManager) { + val playerCount: MutableState = mutableStateOf(null) + val maxPlayers: MutableState = mutableStateOf(null) + val version: MutableState = mutableStateOf(null) + val icon: MutableState = mutableStateOf(null) + +} diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/v2/MessageLine.kt b/src/main/kotlin/gg/essential/gui/friends/message/v2/MessageLine.kt similarity index 96% rename from gui/essential/src/main/kotlin/gg/essential/gui/friends/message/v2/MessageLine.kt rename to src/main/kotlin/gg/essential/gui/friends/message/v2/MessageLine.kt index a586ce73..db0f6495 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/v2/MessageLine.kt +++ b/src/main/kotlin/gg/essential/gui/friends/message/v2/MessageLine.kt @@ -11,7 +11,6 @@ */ package gg.essential.gui.friends.message.v2 -import gg.essential.config.EssentialConfig import gg.essential.elementa.components.UIBlock import gg.essential.elementa.components.UIContainer import gg.essential.elementa.constraints.AspectConstraint @@ -37,13 +36,6 @@ import java.net.URL import java.time.Instant import java.util.concurrent.TimeUnit -fun getGuiScaleOffset(): Float { - return if (EssentialConfig.enlargeSocialMenuChatMetadata) { - 0f - } else { - -1f - } -} /** * Parent type for all components directly added to the scroller for a channel. @@ -132,7 +124,7 @@ abstract class MessageWrapper( val showTimestamp = mutableStateOf(true) val sender = message.sender - val sentByClient = sender == USession.activeNow().uuid + val sentByClient = sender == UUIDUtil.getClientUUID() val sendTime = message.sendTime val channelType = message.channel.type val sendingMessageAlpha = 0.7f diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/v2/MessageWrapperImpl.kt b/src/main/kotlin/gg/essential/gui/friends/message/v2/MessageWrapperImpl.kt similarity index 85% rename from gui/essential/src/main/kotlin/gg/essential/gui/friends/message/v2/MessageWrapperImpl.kt rename to src/main/kotlin/gg/essential/gui/friends/message/v2/MessageWrapperImpl.kt index a90483a9..244083b2 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/v2/MessageWrapperImpl.kt +++ b/src/main/kotlin/gg/essential/gui/friends/message/v2/MessageWrapperImpl.kt @@ -40,11 +40,11 @@ import gg.essential.gui.elementa.state.v2.onChange import gg.essential.gui.elementa.state.v2.stateOf import gg.essential.gui.elementa.state.v2.toV1 import gg.essential.gui.elementa.state.v2.toV2 +import gg.essential.gui.friends.SocialMenu import gg.essential.gui.friends.message.MessageScreen import gg.essential.gui.friends.message.MessageUtils import gg.essential.gui.friends.message.MessageUtils.handleMarkdownUrls -import gg.essential.gui.friends.message.NewReportMessageModal -import gg.essential.gui.friends.state.IMessengerStates +import gg.essential.gui.friends.message.ReportMessageModal import gg.essential.gui.overlay.ModalManager import gg.essential.gui.sendCheckmarkNotification import gg.essential.gui.util.hoveredState @@ -61,8 +61,7 @@ import gg.essential.gui.elementa.state.v2.stateBy as stateByV2 class MessageWrapperImpl( message: ClientMessage, - private val messageScreen: MessageScreen, - private val messengerStates: IMessengerStates, + private val messageScreen: MessageScreen ) : MessageWrapper(message) { private val replyTo = message.replyTo @@ -73,7 +72,7 @@ class MessageWrapperImpl( private val isEdited = stateOf(message.lastEditTime != null) private val shouldShowTimestamp = isEditing or isEdited or stateOf(replyTo != null) - private val senderUsernameState = UuidNameLookup.getNameAsState(sender) + private val senderUsernameState = UUIDUtil.getNameAsState(sender) private val messageLines = mutableListStateOf() private val messageLinesHoveredStates = messageLines.mapEach { if (it is ParagraphLineImpl) { @@ -85,8 +84,9 @@ class MessageWrapperImpl( @Deprecated("Not used in protocol 9 or later") private val markedUnreadManually = BasicState(false) + private val messengerStates = messageScreen.preview.gui.socialStateManager.messengerStates @Deprecated("Not used in protocol 9 or later") - private val unreadState = messengerStates.getUnreadMessageState(message) + private val unreadState = messengerStates.getUnreadMessageState(message.getInfraInstance()) private val topSpacer by Spacer(height = 5f) childOf this @@ -102,11 +102,11 @@ class MessageWrapperImpl( // Height setup in init }.bindParent(messageContainer, showTimestamp or shouldShowTimestamp, index = 0) - private val usernameVisible = sender != USession.activeNow().uuid && channelType == ChannelType.GROUP_DIRECT_MESSAGE + private val usernameVisible = sender != UUIDUtil.getClientUUID() && channelType == ChannelType.GROUP_DIRECT_MESSAGE private val usernameText by EssentialUIText(shadow = false).bindText(senderUsernameState).constrain { color = EssentialPalette.TEXT.toConstraint() - textScale = GuiScaleOffsetConstraint(getGuiScaleOffset()) + textScale = GuiScaleOffsetConstraint(SocialMenu.getGuiScaleOffset()) }.bindParent(usernameTimestampBox, BasicState(usernameVisible)) private val replyContextContainer by UIContainer().constrain { @@ -131,7 +131,7 @@ class MessageWrapperImpl( constrain { color = colorState.toConstraint() x = SiblingConstraint(2f) - textScale = GuiScaleOffsetConstraint(getGuiScaleOffset()) + textScale = GuiScaleOffsetConstraint(SocialMenu.getGuiScaleOffset()) } } if (replyTo == null) { @@ -141,8 +141,8 @@ class MessageWrapperImpl( val replyIcon by EssentialPalette.REPLY_7X5.create().constrain { y = CenterConstraint() color = colorState.toConstraint() - width *= GuiScaleOffsetConstraint(getGuiScaleOffset()) - height *= GuiScaleOffsetConstraint(getGuiScaleOffset()) + width *= GuiScaleOffsetConstraint(SocialMenu.getGuiScaleOffset()) + height *= GuiScaleOffsetConstraint(SocialMenu.getGuiScaleOffset()) } childOf this @@ -160,7 +160,7 @@ class MessageWrapperImpl( val replyTextContent = if (replyTo == MessageRef.DELETED) { BasicState("Deleted Message") } else { - UuidNameLookup.getNameAsState(replyTo.sender).map { username -> + UUIDUtil.getNameAsState(replyTo.sender).map { username -> "$username: ${messagePreviewText.replace(Regex("(\r\n|\r|\n)"), "")}" } } @@ -174,7 +174,7 @@ class MessageWrapperImpl( val replyText by EssentialUIText(shadow = false, truncateIfTooSmall = true, showTooltipForTruncatedText = false) .apply { replyTextContainer.setWidth(textWidth.pixels()) } .bindText(replyTextContent).applyConstraints().constrain { - textScale = GuiScaleOffsetConstraint(getGuiScaleOffset()) + textScale = GuiScaleOffsetConstraint(SocialMenu.getGuiScaleOffset()) width = width.coerceAtMost(MessageUtils.getMessageWidth(false, this@MessageWrapperImpl) / 2) } childOf replyTextContainer } @@ -202,14 +202,14 @@ class MessageWrapperImpl( private val timestampText by EssentialUIText(formatTime(sendTime, includeSeconds = false), shadow = false).constrain { x = SiblingConstraint(5f) - textScale = GuiScaleOffsetConstraint(getGuiScaleOffset()) + textScale = GuiScaleOffsetConstraint(SocialMenu.getGuiScaleOffset()) color = EssentialPalette.TEXT_DISABLED.toConstraint() } childOf usernameTimestampBox init { val editedText by EssentialUIText().bindText(isEditing.map { if (it) "editing" else "(edited)" }.toV1(this)).constrain { x = SiblingConstraint(4f) - textScale = GuiScaleOffsetConstraint(getGuiScaleOffset()) + textScale = GuiScaleOffsetConstraint(SocialMenu.getGuiScaleOffset()) color = isEditing.map { if (it) EssentialPalette.BANNER_BLUE else EssentialPalette.TEXT_DISABLED }.toConstraint() }.bindParent(usernameTimestampBox, isEditing or isEdited).apply { bindEssentialTooltip( @@ -258,11 +258,10 @@ class MessageWrapperImpl( messageLines.add(line) line.constrain { y = SiblingConstraint(3f) - x = 0.pixels(alignOpposite = message.sender == USession.activeNow().uuid) + x = 0.pixels(alignOpposite = message.sender == UUIDUtil.getClientUUID()) } childOf messageContainer - if (!actionButtonHitbox.hasParent && !message.channel.isAnnouncement() // Don't add reply button to invite embeds - && !((message.sendState is SendState.Blocked || messageScreen.preview.isChannelSuspendedState.getUntracked())) + if (!actionButtonHitbox.hasParent && messageScreen is ReplyableMessageScreen && !message.channel.isAnnouncement() && !(line is GiftEmbed) && !(line is SkinEmbed) ) { @@ -321,8 +320,9 @@ class MessageWrapperImpl( messageScreen.replyingTo.set(message) } + val messengerStates = messageScreen.preview.gui.socialStateManager.messengerStates fun doDelete() { - messengerStates.deleteMessage(message) + messengerStates.deleteMessage(message.getInfraInstance()) } val deleteOption = ContextOptionMenu.Option( @@ -334,7 +334,7 @@ class MessageWrapperImpl( if (UKeyboard.isShiftKeyDown()) { doDelete() } else { - platform.pushModal { manager -> + GuiUtil.pushModal { manager -> DeleteMessageConfirmationModal(manager).onPrimaryAction { doDelete() } @@ -356,34 +356,30 @@ class MessageWrapperImpl( image = EssentialPalette.REPORT_10X7, hoveredColor = EssentialPalette.TEXT_WARNING ) { - platform.pushModal { manager -> - NewReportMessageModal(manager, message) + UUIDUtil.getName(sender).thenAcceptOnMainThread { name -> + GuiUtil.pushModal { manager -> + ReportMessageModal(manager, message.getInfraInstance(), name) + } } } when (component) { is ParagraphLine -> { - if (message.sendState is SendState.Blocked) { - options.add(deleteOption.copy(action = { - messageScreen.removeUnsent(message) - })) - } else { - val copyOption = ContextOptionMenu.Option("Copy", image = EssentialPalette.COPY_10X7) { - UDesktop.setClipboardString( - component.selectedText.ifEmpty { component.messageContent }.trim().removePrefix("<") - .removeSuffix(">") - ) - } - if (sentByClient && (!messageScreen.preview.isChannelSuspendedState.getUntracked())) { - options.add(ContextOptionMenu.Option("Edit", image = EssentialPalette.PENCIL_7x7) { - messageScreen.editingMessage.set(message) - }) - } - if (channelType != ChannelType.ANNOUNCEMENT && (!messageScreen.preview.isChannelSuspendedState.getUntracked())) { - options.add(replyOption) - } - options.add(copyOption) + val copyOption = ContextOptionMenu.Option("Copy", image = EssentialPalette.COPY_10X7) { + UDesktop.setClipboardString( + component.selectedText.ifEmpty { component.messageContent }.trim().removePrefix("<") + .removeSuffix(">") + ) } + if (sentByClient) { + options.add(ContextOptionMenu.Option("Edit", image = EssentialPalette.PENCIL_7x7) { + messageScreen.editingMessage.set(message) + }) + } + if (channelType != ChannelType.ANNOUNCEMENT) { + options.add(replyOption) + } + options.add(copyOption) } is ImageEmbed -> { @@ -408,9 +404,7 @@ class MessageWrapperImpl( } if (channelType != ChannelType.ANNOUNCEMENT) { - if (!messageScreen.preview.isChannelSuspendedState.getUntracked()) { - options.add(replyOption) - } + options.add(replyOption) options.add(ContextOptionMenu.Divider) } options.add(copyImageOption) @@ -460,7 +454,7 @@ class MessageWrapperImpl( */ @Deprecated("Not used in protocol 9 or later") fun markSelfUnread() { - messengerStates.setUnreadState(message, true) + messengerStates.setUnreadState(message.getInfraInstance(), true) markedUnreadManually.set(true) } @@ -491,7 +485,7 @@ class MessageWrapperImpl( // Check the message should be marked as read if (message.sent && !markedUnreadManually.get() && unreadState.get()) { Window.enqueueRenderOperation { - messengerStates.setUnreadState(message, false) + messengerStates.setUnreadState(message.getInfraInstance(), false) } } } diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/v2/ParagraphLineImpl.kt b/src/main/kotlin/gg/essential/gui/friends/message/v2/ParagraphLineImpl.kt similarity index 82% rename from gui/essential/src/main/kotlin/gg/essential/gui/friends/message/v2/ParagraphLineImpl.kt rename to src/main/kotlin/gg/essential/gui/friends/message/v2/ParagraphLineImpl.kt index e696e606..83667a6d 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/v2/ParagraphLineImpl.kt +++ b/src/main/kotlin/gg/essential/gui/friends/message/v2/ParagraphLineImpl.kt @@ -11,7 +11,6 @@ */ package gg.essential.gui.friends.message.v2 -import gg.essential.elementa.components.UIBlock import gg.essential.elementa.components.UIContainer import gg.essential.elementa.constraints.AspectConstraint import gg.essential.elementa.constraints.CenterConstraint @@ -22,22 +21,14 @@ import gg.essential.elementa.dsl.* import gg.essential.elementa.state.BasicState import gg.essential.elementa.state.toConstraint import gg.essential.gui.EssentialPalette -import gg.essential.gui.common.EssentialTooltip import gg.essential.gui.common.IconButton import gg.essential.gui.common.bindParent import gg.essential.gui.common.constraints.MarkdownContentWidthConstraint import gg.essential.gui.common.modal.OpenLinkModal import gg.essential.gui.elementa.essentialmarkdown.EssentialMarkdown import gg.essential.gui.elementa.essentialmarkdown.drawables.HeaderDrawable -import gg.essential.gui.elementa.state.v2.stateOf import gg.essential.gui.friends.message.MessageUtils -import gg.essential.gui.layoutdsl.Modifier import gg.essential.gui.layoutdsl.color -import gg.essential.gui.layoutdsl.hoverScope -import gg.essential.gui.layoutdsl.hoverTooltip -import gg.essential.gui.layoutdsl.image -import gg.essential.gui.layoutdsl.layoutAsBox -import gg.essential.gui.layoutdsl.shadow import gg.essential.util.hiddenChildOf import gg.essential.util.isAnnouncement import java.net.URI @@ -57,7 +48,6 @@ class ParagraphLineImpl( private val markdownConfig = when { message.sendState == SendState.Failed -> MessageUtils.failedMessageMarkdownConfig - message.sendState is SendState.Blocked -> MessageUtils.blockedMessageMarkdownConfig message.channel.isAnnouncement() -> MessageUtils.fullMarkdownConfig wrapper.sentByClient -> MessageUtils.outgoingMessageMarkdownConfig else -> MessageUtils.incomingMessageMarkdownConfig @@ -108,20 +98,9 @@ class ParagraphLineImpl( } SendState.Sending -> EssentialPalette.PENDING_MESSAGE_TEXT SendState.Failed -> EssentialPalette.FAILED_MESSAGE_TEXT - is SendState.Blocked -> EssentialPalette.TEXT_DISABLED }.toConstraint() } childOf messageContainer - private val blockedInfo by UIBlock().constrain { - x = bubble.constraints.x - 21.pixels - y = CenterConstraint() - width = 17.pixels - height = AspectConstraint() - color = EssentialPalette.COMPONENT_BACKGROUND_HIGHLIGHT.toConstraint() - }.layoutAsBox(if (message.sendState is SendState.Blocked) Modifier.hoverScope().hoverTooltip((message.sendState as SendState.Blocked).tooltipMessage, position = EssentialTooltip.Position.ABOVE) else Modifier) { - image(EssentialPalette.ROUND_WARNING_7X, Modifier.color(EssentialPalette.RED).shadow(EssentialPalette.TEXT_SHADOW)) - }.bindParent(this, stateOf(wrapper.message.sendState is SendState.Blocked)) - init { constrain { width = MessageUtils.getMessageWidth(announcementMessage) diff --git a/src/main/kotlin/gg/essential/gui/friends/message/v2/ReplyableMessageInput.kt b/src/main/kotlin/gg/essential/gui/friends/message/v2/ReplyableMessageInput.kt new file mode 100644 index 00000000..e94a1e08 --- /dev/null +++ b/src/main/kotlin/gg/essential/gui/friends/message/v2/ReplyableMessageInput.kt @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.gui.friends.message.v2 + +import gg.essential.elementa.components.UIContainer +import gg.essential.elementa.constraints.* +import gg.essential.elementa.dsl.* +import gg.essential.elementa.state.BasicState +import gg.essential.elementa.state.State +import gg.essential.elementa.state.pixels +import gg.essential.elementa.state.toConstraint +import gg.essential.gui.EssentialPalette +import gg.essential.gui.common.bindParent +import gg.essential.gui.common.or +import gg.essential.gui.common.shadow.EssentialUIText +import gg.essential.gui.common.shadow.ShadowIcon +import gg.essential.gui.friends.message.OldMessageInput +import gg.essential.gui.util.hoveredState +import gg.essential.universal.UKeyboard +import gg.essential.universal.USound +import gg.essential.util.* +import gg.essential.vigilance.utils.onLeftClick + +class ReplyMessageInput( + groupName: State, + private val replyTo: State, + private val editingMessage: State, + replyableMessageScreen: ReplyableMessageScreen, +) : OldMessageInput(groupName, editingMessage, replyableMessageScreen) { + + private val isReplying = replyTo.map { it != null } + private val showActionBar = isReplying or messageEditingState + + private val actionBarIcon = replyTo.zip(editingMessage).map { (replyMessage, editMessage) -> + if (replyMessage != null) { + EssentialPalette.REPLY_7X5 + } else if (editMessage != null) { + EssentialPalette.PENCIL_7x7 + } else { + EssentialPalette.NONE + } + } + private val actionBarText = replyTo.zip(editingMessage).map { (replyMessage, editMessage) -> + if (replyMessage != null) { + "Replying to " + } else if (editMessage != null) { + "Editing Message" + } else { + "" + } + } + + private var stashedMessage: String? = "" + + private val usernameState = BasicState("").map { it }.apply { + replyTo.onSetValue { + if (it != null) { + rebind(UUIDUtil.getNameAsState(it.sender)) + } + } + } + + private val actionBarContainer by UIContainer().constrain { + y = CenterConstraint() + height = ChildBasedMaxSizeConstraint() + width = 100.percent + }.bindParent(topDivider, showActionBar) + + private val actionIcon by ShadowIcon(actionBarIcon, BasicState(true)).constrain { + x = 10.pixels + y = CenterConstraint() + } childOf actionBarContainer + + private val actionText by EssentialUIText(shadowColor = EssentialPalette.TEXT_SHADOW).bindText(actionBarText).constrain { + x = SiblingConstraint(4f) + y = CenterConstraint() + color = EssentialPalette.TEXT_MID_GRAY.toConstraint() + } childOf actionBarContainer + + private val usernameText by EssentialUIText(shadowColor = EssentialPalette.TEXT_SHADOW).bindText(usernameState).constrain { + x = SiblingConstraint() // Padding between components is handled by the space in the previous text + y = CenterConstraint() + color = EssentialPalette.TEXT_HIGHLIGHT.toConstraint() + }.bindParent(actionBarContainer, isReplying, index = 2) + + // Makes the hit box for the icon larger so its easier to click + private val closeIconContainer by UIContainer().constrain { + y = CenterConstraint() + x = 14.pixels(alignOpposite = true) + width = ChildBasedSizeConstraint() + 4.pixels + height = ChildBasedSizeConstraint() + 4.pixels + }.onLeftClick { + if (isReplying.get()) { + replyTo.set(null) + } else if (messageEditingState.get()) { + editingMessage.set(null) + } + USound.playButtonPress() + }.bindHoverEssentialTooltip(BasicState("Cancel")) childOf actionBarContainer + + private val closeIcon by EssentialPalette.CANCEL_5X.create().centered().constrain { + color = EssentialPalette.getTextColor(closeIconContainer.hoveredState()).toConstraint() + } childOf closeIconContainer + + init { + replyTo.onSetValue { + if (it != null) { + grabFocus() + } + } + editingMessage.onSetValue { + if (it != null) { + grabFocus() + if (stashedMessage == null) { + stashedMessage = input.getText() + } + input.setText(it.contents) + } else { + stashedMessage?.let { text -> input.setText(text) } + stashedMessage = "" + } + } + + topDivider.constrain { + height += showActionBar.map { + if (it) { + ACTION_BAR_HEIGHT + } else { + 0f + } + }.pixels() + }.onLeftClick { + val message = replyTo.get() ?: editingMessage.get() + message?.let { replyableMessageScreen.scrollToMessage(it) } + } + + // Cancel reply on escape + input.onKeyType { _, keyCode -> + if (keyCode == UKeyboard.KEY_ESCAPE) { + if (isReplying.get()) { + replyTo.set(null) + } else if (messageEditingState.get()) { + editingMessage.set(null) + } else { + replyableMessageScreen.preview.gui.restorePreviousScreen() + } + } + } + } + + companion object { + const val ACTION_BAR_HEIGHT = 14f + } +} diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/v2/ReplyableMessageScreen.kt b/src/main/kotlin/gg/essential/gui/friends/message/v2/ReplyableMessageScreen.kt similarity index 84% rename from gui/essential/src/main/kotlin/gg/essential/gui/friends/message/v2/ReplyableMessageScreen.kt rename to src/main/kotlin/gg/essential/gui/friends/message/v2/ReplyableMessageScreen.kt index fddb1631..1371b2d7 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/v2/ReplyableMessageScreen.kt +++ b/src/main/kotlin/gg/essential/gui/friends/message/v2/ReplyableMessageScreen.kt @@ -11,8 +11,15 @@ */ package gg.essential.gui.friends.message.v2 +//#if MC>10809 +import net.minecraft.init.SoundEvents +//#else +//$$ import net.minecraft.util.ResourceLocation +//#endif +import gg.essential.Essential +import gg.essential.connectionmanager.common.packet.chat.ClientChatChannelMessageUpdatePacket import gg.essential.connectionmanager.common.packet.chat.ServerChatChannelMessagePacket -import gg.essential.connectionmanager.common.packet.chat.ServerChatChannelMessageRejectedPacket +import gg.essential.connectionmanager.common.packet.response.ResponseActionPacket import gg.essential.elementa.UIComponent import gg.essential.elementa.components.ScrollComponent import gg.essential.elementa.components.UIContainer @@ -22,9 +29,9 @@ import gg.essential.gui.EssentialPalette import gg.essential.gui.common.* import gg.essential.gui.common.shadow.EssentialUIText import gg.essential.gui.elementa.state.v2.ReferenceHolderImpl -import gg.essential.gui.elementa.state.v2.State import gg.essential.gui.elementa.state.v2.add import gg.essential.gui.elementa.state.v2.combinators.and +import gg.essential.gui.elementa.state.v2.combinators.map import gg.essential.gui.elementa.state.v2.combinators.zip import gg.essential.gui.elementa.state.v2.mapEach import gg.essential.gui.elementa.state.v2.memo @@ -36,19 +43,16 @@ import gg.essential.gui.elementa.state.v2.toList import gg.essential.gui.elementa.state.v2.toListState import gg.essential.gui.elementa.state.v2.toSet import gg.essential.gui.elementa.state.v2.toV1 +import gg.essential.gui.friends.Tab import gg.essential.gui.friends.message.MessageInput import gg.essential.gui.friends.message.MessageScreen import gg.essential.gui.friends.message.MessageTitleBar -import gg.essential.gui.friends.message.SocialMenuActions import gg.essential.gui.friends.previews.ChannelPreview -import gg.essential.gui.friends.state.SocialStates -import gg.essential.gui.layoutdsl.Alignment -import gg.essential.gui.layoutdsl.Modifier -import gg.essential.gui.layoutdsl.alignVertical import gg.essential.gui.layoutdsl.layout import gg.essential.gui.notification.Notifications -import gg.essential.gui.notification.error +import gg.essential.network.connectionmanager.EarlyResponseHandler import gg.essential.universal.UMatrixStack +import gg.essential.universal.USound import gg.essential.util.* import gg.essential.util.GuiEssentialPlatform.Companion.platform import gg.essential.vigilance.utils.onLeftClick @@ -58,18 +62,17 @@ import java.util.concurrent.TimeUnit class ReplyableMessageScreen( - override val preview: ChannelPreview, - private val socialStates: SocialStates, - socialMenuActions: SocialMenuActions, - titleBar: UIComponent, - rightDivider: UIComponent, - active: State, - val isScreenOpen: State, + override val preview: ChannelPreview ) : MessageScreen() { + private val gui = preview.gui + private val active = + gui.chatTab.currentMessageView.map { it == this@ReplyableMessageScreen } and gui.selectedTab.map { it == Tab.CHAT } private val refHolder = ReferenceHolderImpl() - private val standardBar by MessageTitleBar(this, socialStates, socialMenuActions).bindParent(titleBar, active) + val isScreenOpen = memo { gui.isScreenOpen() && active() } + + private val standardBar by MessageTitleBar(this, gui).bindParent(gui.titleBar, active) private val scroller by ScrollComponent( verticalScrollOpposite = true, @@ -109,11 +112,12 @@ class ReplyableMessageScreen( private val scrollCleanup: () -> Unit private var lastRequest = 0L + private val cm = Essential.getInstance().connectionManager private val channel = preview.channel private var receivedAllMessages = false private val sendQueue = mutableListStateOf() - private val baseMessageListState = socialStates.messages.getMessageListState(preview.channel.id) + private val baseMessageListState = gui.socialStateManager.messengerStates.getMessageListState(preview.channel.id) private val messageListState = memo { val list = baseMessageListState().toMutableList() @@ -166,32 +170,16 @@ class ReplyableMessageScreen( init { if (!preview.channel.isAnnouncement()) { - messageInput = MessageInput( - channel, - isScreenOpen, - socialStates, - preview.titleState, - replyingTo, - editingMessage, - ::scrollToMessage, - ::sendMessage, - ::editMessage, - ) + messageInput = MessageInput(preview.titleState, replyingTo, editingMessage, this, ::sendMessage) } layout { scroller() - if_(preview.isChannelSuspendedState) { - messageInputSuspended(preview.titleState, Modifier.alignVertical(Alignment.End)) - } `else` { - messageInput?.invoke() - } + messageInput?.invoke() } - createScrollbarRelativeTo( + gui.createRightDividerScroller( active.toV1(this), - xPositionAndWidth = rightDivider, - parent = rightDivider, yPositionAndHeight = scroller, initializeToBottom = true, ).let { (component, cleanup) -> @@ -218,7 +206,7 @@ class ReplyableMessageScreen( DateDividerImpl(it.toInstant())() } forEach(messageListState) { message -> - MessageWrapperImpl(message, this@ReplyableMessageScreen, socialStates.messages).apply { + MessageWrapperImpl(message, this@ReplyableMessageScreen).apply { parseComponents(message, this).forEach { addComponent(it) } }() } @@ -246,12 +234,12 @@ class ReplyableMessageScreen( recalculateTimestampVisibility() } - val channelMessages = socialStates.messages.getMessagesRaw(channel.id) + val channelMessages = cm.chatManager.getMessages(channel.id) if (channelMessages == null) { // Even for announcements this will still be true first round since personal channel isn't requested until after type ANNOUNCEMENT - socialStates.messages.retrieveMessageHistoryRaw(channel.id) + cm.chatManager.retrieveRecentMessageHistory(channel.id, null) } else if (channelMessages.size < 50) { - socialStates.messages.retrieveMessageHistoryRaw( + cm.chatManager.retrieveMessageHistory( channel.id, channelMessages.values.minByOrNull { it.id }?.id, null, @@ -268,9 +256,9 @@ class ReplyableMessageScreen( // If the user is not scrolled all the way down, we need to adjust the scroll to avoid moving content val scrollAdjust = if (it.first == null && it.second == null) { - -14f + -ReplyMessageInput.ACTION_BAR_HEIGHT } else { - 14f + ReplyMessageInput.ACTION_BAR_HEIGHT } scroller.scrollTo(verticalOffset = scroller.verticalOffset + scrollAdjust, smoothScroll = false) } @@ -338,7 +326,7 @@ class ReplyableMessageScreen( insertDividerAtInstant(clientMessage.sendTime) } - val messengerStates = socialStates.messages + val messengerStates = gui.socialStateManager.messengerStates // Insert at the oldest message val sortedMessages = messageListState.getUntracked().sortedBy { it.sendTime } @@ -377,7 +365,7 @@ class ReplyableMessageScreen( return } - if (sortedMessages.none { messengerStates.getUnreadMessageState(it).getUntracked() }) { + if (sortedMessages.none { messengerStates.getUnreadMessageState(it.getInfraInstance()).getUntracked() }) { // There are no unread messages. All messages are already read, so we won't need to place any divider this // time. // In fact, we mustn't place any divider in the future because if we do, it's probably on a message that @@ -395,14 +383,14 @@ class ReplyableMessageScreen( // 1. All messages are unread and the new line divider should appear at the top of the list // 2. There is only a single unread message in the channel val first = sortedMessages.first() - if (messengerStates.getUnreadMessageState(first).getUntracked() && receivedAllMessages) { + if (messengerStates.getUnreadMessageState(first.getInfraInstance()).getUntracked() && receivedAllMessages) { insertDividerAt(first) return } sortedMessages.zipWithNext { current, next -> - val currentUnread = messengerStates.getUnreadMessageState(current).getUntracked() - val nextUnread = messengerStates.getUnreadMessageState(next).getUntracked() + val currentUnread = messengerStates.getUnreadMessageState(current.getInfraInstance()).getUntracked() + val nextUnread = messengerStates.getUnreadMessageState(next.getInfraInstance()).getUntracked() if (!currentUnread && nextUnread) { insertDividerAt(next) @@ -413,13 +401,13 @@ class ReplyableMessageScreen( } private fun requestMoreMessages() { - val messages = socialStates.messages.getMessagesRaw(channel.id) ?: return + val messages = cm.chatManager.getMessages(channel.id) ?: return - socialStates.messages.retrieveMessageHistoryRaw( + cm.chatManager.retrieveMessageHistory( channel.id, messages.values.minOfOrNull { it.id } ?: return, null, - ) retrieveMessageHistory@{ + ) { if (!it.isPresent) { return@retrieveMessageHistory } @@ -481,8 +469,10 @@ class ReplyableMessageScreen( return } - socialStates.messages.editMessage(originalMessage.channel.id, originalMessage.id, message) { success -> - if (!success) { + cm.send(ClientChatChannelMessageUpdatePacket(originalMessage.channel.id, originalMessage.id, message)) { optionalPacket -> + if ((optionalPacket.orElse(null) as? ResponseActionPacket)?.isSuccessful == true) { + cm.chatManager.updateMessage(originalMessage, message) + } else { Notifications.push("Error editing message", "An error occurred while editing your message") } } @@ -497,12 +487,18 @@ class ReplyableMessageScreen( scroller.scrollToBottom() - platform.playNoteHatSound(0.25f, 0.75f) + //#if MC>10809 + USound.playSoundStatic(SoundEvents.BLOCK_NOTE_HAT, .25f, 0.75f) + //#else + //$$ USound.playSoundStatic(ResourceLocation("note.hat"), .25f, 0.75f) + //#endif + + val connectionManager = Essential.getInstance().connectionManager val fakeMessage = ClientMessage( fakeID, preview.channel, - USession.activeNow().uuid, + UUIDUtil.getClientUUID(), message, SendState.Sending, replyingTo?.let { @@ -521,20 +517,14 @@ class ReplyableMessageScreen( val trimmed = message.contents.trim().replace("`", "").replace("(? + Essential.getInstance().connectionManager.chatManager.sendMessage(message.channel.id, trimmed, message.replyTo?.messageId, EarlyResponseHandler { packet -> sendQueue.removeAll { it.id == message.id } val response = packet.orElse(null) if (response !is ServerChatChannelMessagePacket) { - if (response is ServerChatChannelMessageRejectedPacket) { - val blockedSendState = SendState.Blocked(response.reason) - sendQueue.add(message.copy(sendState = blockedSendState)) - Notifications.error(blockedSendState.toastMessage, "") - } else { - sendQueue.add(message.copy(sendState = SendState.Failed)) - } + sendQueue.add(message.copy(sendState = SendState.Failed)) } - } + }) } override fun onOpen() { @@ -561,20 +551,13 @@ class ReplyableMessageScreen( } override fun retrySend(message: ClientMessage) { - if (message.sendState != SendState.Failed || message.sender != USession.activeNow().uuid) { + if (message.sendState != SendState.Failed || message.sender != UUIDUtil.getClientUUID()) { throw IllegalArgumentException("Message was already sent or was not sent by the client") } sendQueue.removeAll { it.id == message.id } sendMessage(message.copy(sendState = SendState.Sending)) } - override fun removeUnsent(message: ClientMessage) { - if (!((message.sendState == SendState.Failed || message.sendState is SendState.Blocked) && message.sender == USession.activeNow().uuid)) { - throw IllegalArgumentException("Cannot remove a message which has been sent successfully or received") - } - sendQueue.removeAll { it.id == message.id } - } - @Deprecated("Not used in protocol 9 or later") override fun markAllAsRead() { content.childrenOfType().forEach { @@ -588,22 +571,22 @@ class ReplyableMessageScreen( var latestMessage = messages.lastOrNull { USession.activeNow().uuid != it.sender } if (latestMessage == null) { // If none can be found then find the latest message in general unless the latest read message is already in the message list - val currentLastReadMessageId = socialStates.messages.getLastReadMessageId(channel.id).getUntracked() + val currentLastReadMessageId = gui.socialStateManager.messengerStates.getLastReadMessageId(channel.id).getUntracked() if (currentLastReadMessageId != null && messages.any { it.id == currentLastReadMessageId }) { return } latestMessage = messages.lastOrNull() ?: return } - socialStates.messages.setLastReadMessage(latestMessage) + gui.socialStateManager.messengerStates.setLastReadMessage(latestMessage.getInfraInstance()) } override fun markMessageAsUnread(messageWrapper: MessageWrapper) { val messages = content.childrenOfType() val readMessageIndex = messages.indexOf(messageWrapper) - 1 if (readMessageIndex >= 0) { - socialStates.messages.setLastReadMessage(messages[readMessageIndex].message) + gui.socialStateManager.messengerStates.setLastReadMessage(messages[readMessageIndex].message.getInfraInstance()) } else { - socialStates.messages.setLastReadMessage(channel.id, null) + gui.socialStateManager.messengerStates.setLastReadMessage(channel.id, null) } markedManuallyUnread = true scroller.holdScrollVerticalLocation(messageWrapper) { diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/v2/SkinEmbedImpl.kt b/src/main/kotlin/gg/essential/gui/friends/message/v2/SkinEmbedImpl.kt similarity index 88% rename from gui/essential/src/main/kotlin/gg/essential/gui/friends/message/v2/SkinEmbedImpl.kt rename to src/main/kotlin/gg/essential/gui/friends/message/v2/SkinEmbedImpl.kt index ac834b12..1186a4b9 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/v2/SkinEmbedImpl.kt +++ b/src/main/kotlin/gg/essential/gui/friends/message/v2/SkinEmbedImpl.kt @@ -11,6 +11,7 @@ */ package gg.essential.gui.friends.message.v2 +import gg.essential.Essential import gg.essential.elementa.constraints.* import gg.essential.elementa.dsl.* import gg.essential.gui.EssentialPalette @@ -51,15 +52,16 @@ class SkinEmbedImpl( icon(EssentialPalette.EXPAND_6X, Modifier.alignBoth(Alignment.End(6f)).color(EssentialPalette.TEXT).hoverColor(EssentialPalette.TEXT_HIGHLIGHT)) } - val skinsManager = platform.skinsManager + val cosmeticsManager = Essential.getInstance().connectionManager.cosmeticsManager + val skinsManager = Essential.getInstance().connectionManager.skinsManager bubble.onLeftClick { - if (skinsManager.skins.get().size >= platform.wardrobeSettings.skinsLimit.get()) { + if (skinsManager.skins.get().size >= cosmeticsManager.wardrobeSettings.skinsLimit.get()) { Notifications.push("Error adding skin", "You have the maximum number of skins!") return@onLeftClick } USound.playButtonPress() - platform.pushModal { + GuiUtil.pushModal { SkinModal.add(it, skin, initialName = skinsManager.getNextIncrementalSkinName()) } } diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/v2/UnreadDividerImpl.kt b/src/main/kotlin/gg/essential/gui/friends/message/v2/UnreadDividerImpl.kt similarity index 93% rename from gui/essential/src/main/kotlin/gg/essential/gui/friends/message/v2/UnreadDividerImpl.kt rename to src/main/kotlin/gg/essential/gui/friends/message/v2/UnreadDividerImpl.kt index 6a95d657..ec4f7e65 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/v2/UnreadDividerImpl.kt +++ b/src/main/kotlin/gg/essential/gui/friends/message/v2/UnreadDividerImpl.kt @@ -16,6 +16,7 @@ import gg.essential.elementa.constraints.SiblingConstraint import gg.essential.elementa.dsl.* import gg.essential.gui.EssentialPalette import gg.essential.gui.about.components.ColoredDivider +import gg.essential.gui.friends.SocialMenu import java.time.Instant class UnreadDividerImpl( @@ -27,7 +28,7 @@ class UnreadDividerImpl( EssentialPalette.RED, false, dividerColor = EssentialPalette.RED, - scaleOffset = getGuiScaleOffset(), + scaleOffset = SocialMenu.getGuiScaleOffset(), ).constrain { y = 12.pixels } childOf this diff --git a/src/main/kotlin/gg/essential/gui/friends/message/v2/clientMessage.kt b/src/main/kotlin/gg/essential/gui/friends/message/v2/clientMessage.kt index 427e92dc..e18cb62c 100644 --- a/src/main/kotlin/gg/essential/gui/friends/message/v2/clientMessage.kt +++ b/src/main/kotlin/gg/essential/gui/friends/message/v2/clientMessage.kt @@ -32,3 +32,16 @@ fun infraInstanceToClient(message: Message): ClientMessage { message.createdAt, ) } + +fun ClientMessage.getInfraInstance(): Message { + return Essential.getInstance().connectionManager.chatManager.getMessageById(channel.id, id) ?: Message( + id, + channel.id, + sender, + contents, + true, // So the social menu doesn't try to mark this message as read + replyTo?.messageId, + lastEditTime, + createdAt, + ) +} diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/previews/BasicUserEntry.kt b/src/main/kotlin/gg/essential/gui/friends/previews/BasicUserEntry.kt similarity index 96% rename from gui/essential/src/main/kotlin/gg/essential/gui/friends/previews/BasicUserEntry.kt rename to src/main/kotlin/gg/essential/gui/friends/previews/BasicUserEntry.kt index a2e0fee3..352023c1 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/friends/previews/BasicUserEntry.kt +++ b/src/main/kotlin/gg/essential/gui/friends/previews/BasicUserEntry.kt @@ -22,8 +22,8 @@ import gg.essential.gui.elementa.state.v2.onChange import gg.essential.gui.elementa.state.v2.toV1 import gg.essential.gui.image.ImageFactory import gg.essential.util.CachedAvatarImage +import gg.essential.util.UUIDUtil import gg.essential.gui.util.hoveredState -import gg.essential.util.UuidNameLookup import java.awt.Color import java.util.* @@ -33,7 +33,7 @@ abstract class BasicUserEntry( hoverIconColor: Color, sortListener: SortListener ) : UIBlock(EssentialPalette.COMPONENT_BACKGROUND), SearchableItem { - val usernameState = UuidNameLookup.nameState(user, "Loading...") + val usernameState = UUIDUtil.nameState(user, "Loading...") protected val imageContainer by CachedAvatarImage.create(user).constrain { x = 8.pixels diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/previews/BlockedUserEntry.kt b/src/main/kotlin/gg/essential/gui/friends/previews/BlockedUserEntry.kt similarity index 89% rename from gui/essential/src/main/kotlin/gg/essential/gui/friends/previews/BlockedUserEntry.kt rename to src/main/kotlin/gg/essential/gui/friends/previews/BlockedUserEntry.kt index d35fb9a6..c7abba6d 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/friends/previews/BlockedUserEntry.kt +++ b/src/main/kotlin/gg/essential/gui/friends/previews/BlockedUserEntry.kt @@ -16,14 +16,14 @@ import gg.essential.elementa.dsl.* import gg.essential.gui.EssentialPalette import gg.essential.gui.elementa.state.v2.combinators.map import gg.essential.gui.elementa.state.v2.toV1 -import gg.essential.gui.friends.message.SocialMenuActions +import gg.essential.gui.friends.SocialMenu import gg.essential.util.* import gg.essential.vigilance.utils.onLeftClick import java.util.* class BlockedUserEntry( user: UUID, - socialMenuActions: SocialMenuActions, + gui: SocialMenu, sortListener: SortListener ) : BasicUserEntry(user, EssentialPalette.CANCEL_5X, EssentialPalette.RED, sortListener) { @@ -34,7 +34,7 @@ class BlockedUserEntry( button.bindHoverEssentialTooltip(usernameState.map { "Unblock $it" }.toV1(this)) button.onLeftClick { - socialMenuActions.blockOrUnblock(user) + gui.handleBlockOrUnblock(user) } } } diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/previews/ChannelPreview.kt b/src/main/kotlin/gg/essential/gui/friends/previews/ChannelPreview.kt similarity index 80% rename from gui/essential/src/main/kotlin/gg/essential/gui/friends/previews/ChannelPreview.kt rename to src/main/kotlin/gg/essential/gui/friends/previews/ChannelPreview.kt index fe11bae8..863bc2f1 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/friends/previews/ChannelPreview.kt +++ b/src/main/kotlin/gg/essential/gui/friends/previews/ChannelPreview.kt @@ -12,7 +12,10 @@ package gg.essential.gui.friends.previews import com.sparkuniverse.toolbox.chat.model.Channel +import com.sparkuniverse.toolbox.chat.model.Message +import gg.essential.Essential import gg.essential.elementa.components.UIBlock +import gg.essential.elementa.components.UIContainer import gg.essential.elementa.constraints.* import gg.essential.elementa.dsl.* import gg.essential.elementa.impl.commonmark.node.BlockQuote @@ -23,6 +26,9 @@ import gg.essential.elementa.impl.commonmark.renderer.text.TextContentNodeRender import gg.essential.elementa.impl.commonmark.renderer.text.TextContentRenderer import gg.essential.gui.EssentialPalette import gg.essential.gui.common.ContextOptionMenu +import gg.essential.gui.common.bindParent +import gg.essential.gui.common.not +import gg.essential.gui.common.shadow.EssentialUIText import gg.essential.gui.common.shadow.ShadowEffect import gg.essential.gui.elementa.state.v2.Observer import gg.essential.gui.elementa.state.v2.State @@ -35,10 +41,10 @@ import gg.essential.gui.elementa.state.v2.mutableStateOf import gg.essential.gui.elementa.state.v2.stateOf import gg.essential.gui.elementa.state.v2.toV1 import gg.essential.gui.elementa.state.v2.toV2 +import gg.essential.gui.friends.SocialMenu +import gg.essential.gui.friends.Tab import gg.essential.gui.friends.message.MessageUtils -import gg.essential.gui.friends.message.v2.ClientMessage import gg.essential.gui.friends.state.PlayerActivity -import gg.essential.gui.friends.state.SocialStates import gg.essential.gui.image.ImageFactory import gg.essential.gui.layoutdsl.* import gg.essential.gui.studio.Tag @@ -46,7 +52,6 @@ import gg.essential.gui.util.hoveredState import gg.essential.sps.SpsAddress import gg.essential.universal.USound import gg.essential.util.* -import gg.essential.util.GuiEssentialPlatform.Companion.platform import gg.essential.vigilance.utils.onLeftClick import okhttp3.HttpUrl import java.time.Instant @@ -59,26 +64,24 @@ import java.util.* import gg.essential.gui.elementa.state.v2.stateBy as stateByV2 class ChannelPreview( - val channel: Channel, - socialStates: SocialStates, - active: State, - openMessageScreen: () -> Unit, - openManagementDropdown: (position: ContextOptionMenu.Position, onClose: () -> Unit) -> Unit, + val gui: SocialMenu, + val channel: Channel ) : UIBlock(), SearchableItem { val otherUser: UUID? = channel.getOtherUser() - private val messengerStates = socialStates.messages - private val activityStates = socialStates.activity + private val cm = Essential.getInstance().connectionManager + + private val messengerStates = gui.socialStateManager.messengerStates private val latestMessageState = messengerStates.getLatestMessage(channel.id) - private val uuid = otherUser ?: UUID(0, 0) + private val uuid = otherUser ?: UUIDUtil.formatWithDashes("00000000000000000000000000000000") - private val activity = activityStates.getActivityState(uuid) + private val activity = gui.socialStateManager.statusStates.getActivityState(uuid) private val joinable = activity.map { it.isJoinable() } private val isOnline = memo { activity() !is PlayerActivity.Offline } val titleState = if (otherUser != null) { - UuidNameLookup.nameState(otherUser) + UUIDUtil.nameState(otherUser) } else { messengerStates.getTitle(channel.id) } @@ -87,16 +90,14 @@ class ChannelPreview( val isChannelMutedState = messengerStates.getMuted(channel.id) - val isChannelSuspendedState = channel.getOtherUser()?.let { otherUser -> - socialStates.isSuspended(otherUser) - } ?: stateOf(false) - val latestMessageTimestamp = latestMessageState.map { it?.createdAt ?: channel.joinedAt } private val doShowTimestampState = hasUnreadState.not() and latestMessageState.map { it != null } init { val dropdownOpen = mutableStateOf(false) + val active = + gui.chatTab.currentMessageView.map { it?.preview == this } and gui.selectedTab.map { it == Tab.CHAT } val unreadQuantity = Tag( stateOf(EssentialPalette.RED), stateOf(EssentialPalette.TEXT_HIGHLIGHT), @@ -180,14 +181,14 @@ class ChannelPreview( onRightClick { dropdownOpen.set(true) - openManagementDropdown(ContextOptionMenu.Position(it.absoluteX, it.absoluteY)) { + gui.showManagementDropdown(this@ChannelPreview, ContextOptionMenu.Position(it.absoluteX, it.absoluteY)) { dropdownOpen.set(false) } } onLeftClick { USound.playButtonPress() - openMessageScreen() + gui.openMessageScreen(this@ChannelPreview) it.stopPropagation() } } @@ -195,6 +196,53 @@ class ChannelPreview( override fun getSearchTag() = titleState.get() + inner class Description : UIContainer() { + + private val uuid = otherUser ?: UUIDUtil.formatWithDashes("00000000000000000000000000000000") + + private val activity = gui.socialStateManager.statusStates.getActivityState(uuid) + private val joinable = activity.map { it.isJoinable() } + + private val descriptionState = latestMessageState.map { message -> + val msg = message?.contents + ?: if (channel.isAnnouncement()) { + "There are no announcements" + } else { + "Click to send a message!" + } + + markdownRenderer.render( + Parser.builder() + .build() + .parse(msg) + ).split("\n")[0]; // stop at new line + } + + private val friendStatus by FriendStatus(uuid, gui.socialStateManager.statusStates).bindParent(this, joinable) + + private val descriptionText by EssentialUIText( + shadow = false, + shadowColor = EssentialPalette.BLACK, + truncateIfTooSmall = true, + showTooltipForTruncatedText = false, + ).bindText(descriptionState.toV1(this)).constrain { + width = width.coerceAtMost(100.percent) + color = EssentialPalette.TEXT_DISABLED.toConstraint() + }.bindParent(this, !joinable) + + init { + constrain { + height = ChildBasedSizeConstraint() + } + + Modifier.whenTrue( + doShowTimestampState, + Modifier.fillWidth(0.75f), + Modifier.fillRemainingWidth() + ).applyToComponent(this) + } + } + companion object { private val markdownRenderer = TextContentRenderer.builder() .stripNewlines(false) @@ -235,7 +283,7 @@ class ChannelPreview( private fun LayoutScope.description(modifier: Modifier) { box(modifier) { if_(joinable) { - FriendStatus(uuid, activityStates)() + FriendStatus(uuid, gui.socialStateManager.statusStates)() } `else` { bind(latestMessageState) { @@ -305,7 +353,7 @@ class ChannelPreview( return Pair(null, textDescription(message)) } - private fun textDescription(message: ClientMessage): State { + private fun textDescription(message: Message): State { return stateOf(markdownRenderer.render( Parser.builder() .build() @@ -313,7 +361,7 @@ class ChannelPreview( ).split("\n")[0]) // stop at new line } - private fun Observer.pictureDescription(message: ClientMessage): String { + private fun Observer.pictureDescription(message: Message): String { var numberOfPictures = 0 for (loopMessage in messengerStates.getMessageListState(channel.id)() .sortedByDescending { it.id }) { @@ -342,7 +390,7 @@ class ChannelPreview( ?: return stateOf(address) return stateByV2 { - val username = UuidNameLookup.nameState(host)() + val username = UUIDUtil.nameState(host)() if (username.isNotBlank()) "$username's World" else "Invite" } } @@ -350,7 +398,7 @@ class ChannelPreview( private fun giftDescription(giftUrl: String): State { val url = HttpUrl.parse(giftUrl) ?: return stateOf("Gift") val cosmeticId = url.pathSegments()[1] ?: return stateOf("Gift") - val cosmetic = platform.cosmeticsManager.cosmeticsData.getCosmetic(cosmeticId) ?: return stateOf("Gift") + val cosmetic = Essential.getInstance().connectionManager.cosmeticsManager.getCosmetic(cosmeticId) ?: return stateOf("Gift") return stateOf("Gift: ${cosmetic.displayName}") } diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/previews/FriendStatus.kt b/src/main/kotlin/gg/essential/gui/friends/previews/FriendStatus.kt similarity index 86% rename from gui/essential/src/main/kotlin/gg/essential/gui/friends/previews/FriendStatus.kt rename to src/main/kotlin/gg/essential/gui/friends/previews/FriendStatus.kt index 479a50a3..730ec3c3 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/friends/previews/FriendStatus.kt +++ b/src/main/kotlin/gg/essential/gui/friends/previews/FriendStatus.kt @@ -23,21 +23,15 @@ import gg.essential.gui.EssentialPalette import gg.essential.gui.common.shadow.EssentialUIText import gg.essential.gui.common.shadow.ShadowIcon import gg.essential.gui.common.state -import gg.essential.gui.elementa.state.v2.asyncMap import gg.essential.gui.elementa.state.v2.effect import gg.essential.gui.elementa.state.v2.memo import gg.essential.gui.elementa.state.v2.stateOf import gg.essential.gui.elementa.state.v2.toV1 import gg.essential.gui.friends.state.IStatusStates import gg.essential.gui.friends.state.PlayerActivity -import gg.essential.sps.SpsAddress +import gg.essential.network.pingproxy.fetchWorldNameFromSPSHost import gg.essential.util.AddressUtil -import gg.essential.util.Client -import gg.essential.util.ServerPingInfo -import gg.essential.util.UuidNameLookup -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.plus +import gg.essential.util.UUIDUtil import java.util.* class FriendStatus( @@ -83,11 +77,8 @@ class FriendStatus( } is PlayerActivity.SPSSession -> { if (activity.invited) { - @Suppress("OPT_IN_USAGE") // FIXME ideally shouldn't use GlobalScope - val worldName = stateOf(uuid).asyncMap(GlobalScope + Dispatchers.Client) { - ServerPingInfo.fetchViaPingProxy(SpsAddress(uuid).toString())?.worldName - } - val username = UuidNameLookup.nameState(activity.host) + val worldName = fetchWorldNameFromSPSHost(uuid) + val username = UUIDUtil.nameState(activity.host) createJoinableEntry(memo { worldName() ?: "${username()}'s world" }.toV1(this@FriendStatus)) } else { EssentialUIText("In World", truncateIfTooSmall = true).constrain { diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/previews/FriendUserEntry.kt b/src/main/kotlin/gg/essential/gui/friends/previews/FriendUserEntry.kt similarity index 79% rename from gui/essential/src/main/kotlin/gg/essential/gui/friends/previews/FriendUserEntry.kt rename to src/main/kotlin/gg/essential/gui/friends/previews/FriendUserEntry.kt index 9ea76821..cc0caebc 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/friends/previews/FriendUserEntry.kt +++ b/src/main/kotlin/gg/essential/gui/friends/previews/FriendUserEntry.kt @@ -20,20 +20,18 @@ import gg.essential.elementa.state.toConstraint import gg.essential.gui.EssentialPalette import gg.essential.gui.common.ContextOptionMenu import gg.essential.gui.common.or -import gg.essential.gui.friends.message.SocialMenuActions -import gg.essential.gui.friends.state.SocialStates +import gg.essential.gui.friends.SocialMenu import gg.essential.gui.util.hoveredState import gg.essential.vigilance.utils.onLeftClick import java.util.* class FriendUserEntry( + gui: SocialMenu, user: UUID, - socialStates: SocialStates, - socialMenuActions: SocialMenuActions, sortListener: SortListener ) : BasicUserEntry(user, EssentialPalette.BURGER_7X5, EssentialPalette.TEXT_HIGHLIGHT, sortListener) { - private val friendStatus by FriendStatus(user, socialStates.activity, sortListener).constrain { + private val friendStatus by FriendStatus(user, gui.socialStateManager.statusStates, sortListener).constrain { y = SiblingConstraint(5f) } childOf textContainer @@ -43,7 +41,7 @@ class FriendUserEntry( button.setColor(EssentialPalette.getButtonColor(button.hoveredState() or dropdownOpen).toConstraint()) button.onLeftClick { dropdownOpen.set(true) - socialMenuActions.showUserDropdown(user, ContextOptionMenu.Position(button, false)) { + gui.showUserDropdown(user, ContextOptionMenu.Position(button, false)) { dropdownOpen.set(false) } it.stopPropagation() @@ -52,7 +50,7 @@ class FriendUserEntry( if (it.mouseButton > 1) { return@onMouseClick } - socialMenuActions.showUserDropdown(user, ContextOptionMenu.Position(it)) { + gui.showUserDropdown(user, ContextOptionMenu.Position(it)) { } } diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/previews/PendingUserEntry.kt b/src/main/kotlin/gg/essential/gui/friends/previews/PendingUserEntry.kt similarity index 89% rename from gui/essential/src/main/kotlin/gg/essential/gui/friends/previews/PendingUserEntry.kt rename to src/main/kotlin/gg/essential/gui/friends/previews/PendingUserEntry.kt index b889e3e9..2f0c4661 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/friends/previews/PendingUserEntry.kt +++ b/src/main/kotlin/gg/essential/gui/friends/previews/PendingUserEntry.kt @@ -11,6 +11,7 @@ */ package gg.essential.gui.friends.previews +import gg.essential.Essential import gg.essential.elementa.components.Window import gg.essential.elementa.constraints.* import gg.essential.elementa.dsl.* @@ -21,10 +22,8 @@ import gg.essential.gui.common.IconButton import gg.essential.gui.common.bindParent import gg.essential.gui.common.onSetValueAndNow import gg.essential.gui.common.shadow.EssentialUIText -import gg.essential.gui.friends.message.SocialMenuActions -import gg.essential.gui.friends.state.SocialStates +import gg.essential.gui.friends.SocialMenu import gg.essential.gui.util.hoveredState -import gg.essential.network.connectionmanager.notices.SocialMenuNewFriendRequestNoticeManager import gg.essential.util.* import gg.essential.vigilance.utils.onLeftClick import java.util.* @@ -32,13 +31,12 @@ import java.util.* class PendingUserEntry( user: UUID, val incoming: Boolean, - socialStates: SocialStates, - socialMenuActions: SocialMenuActions, - friendRequestNoticeManager: SocialMenuNewFriendRequestNoticeManager, + gui: SocialMenu, sortListener: SortListener ) : BasicUserEntry(user, EssentialPalette.CANCEL_5X, EssentialPalette.RED, sortListener) { - private val actions = socialStates.relationships + private val actions = gui.socialStateManager.relationshipStates + private val friendRequestNoticeManager = Essential.getInstance().connectionManager.socialMenuNewFriendRequestNoticeManager private val hasUnseenRequest = friendRequestNoticeManager.hasUnseenFriendRequests(user) // True when this request was unseen and holds as true for the lifetime of this entry @@ -103,7 +101,7 @@ class PendingUserEntry( ContextOptionMenu.Position(it), Window.of(this), ContextOptionMenu.Option("Block Player", image = EssentialPalette.BLOCK_10X7) { - socialMenuActions.blockOrUnblock(user) + gui.handleBlockOrUnblock(user) }) } diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/previews/SearchableItem.kt b/src/main/kotlin/gg/essential/gui/friends/previews/SearchableItem.kt similarity index 100% rename from gui/essential/src/main/kotlin/gg/essential/gui/friends/previews/SearchableItem.kt rename to src/main/kotlin/gg/essential/gui/friends/previews/SearchableItem.kt diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/previews/SortListener.kt b/src/main/kotlin/gg/essential/gui/friends/previews/SortListener.kt similarity index 100% rename from gui/essential/src/main/kotlin/gg/essential/gui/friends/previews/SortListener.kt rename to src/main/kotlin/gg/essential/gui/friends/previews/SortListener.kt diff --git a/src/main/kotlin/gg/essential/gui/friends/state/MessengerStateManagerImpl.kt b/src/main/kotlin/gg/essential/gui/friends/state/MessengerStateManagerImpl.kt index dae4ef06..5f663f96 100644 --- a/src/main/kotlin/gg/essential/gui/friends/state/MessengerStateManagerImpl.kt +++ b/src/main/kotlin/gg/essential/gui/friends/state/MessengerStateManagerImpl.kt @@ -32,6 +32,7 @@ import gg.essential.gui.elementa.state.v2.removeAll import gg.essential.gui.elementa.state.v2.set import gg.essential.gui.elementa.state.v2.toListState import gg.essential.gui.friends.message.v2.ClientMessage +import gg.essential.gui.friends.message.v2.getInfraInstance import gg.essential.gui.friends.message.v2.infraInstanceToClient import gg.essential.network.connectionmanager.chat.ChatManager import gg.essential.universal.UMinecraft @@ -47,7 +48,7 @@ class MessengerStateManagerImpl(private val chatManager: ChatManager) : IMesseng /** IMessengerStates Fields*/ private val channelStates = mutableMapOf() @Deprecated("Not used in protocol 9 or later") - private val messageUnreadMap = mutableMapOf, MutableState>() + private val messageUnreadMap = mutableMapOf>() private val observableMessageList = mutableMapOf, ListState>>() /** IMessengerActions Fields **/ @@ -77,7 +78,7 @@ class MessengerStateManagerImpl(private val chatManager: ChatManager) : IMesseng return getOrCreateChannelStates(channelId).mutedState } - override fun getLatestMessage(channelId: Long): State { + override fun getLatestMessage(channelId: Long): State { return getOrCreateChannelStates(channelId).latestMessage } @@ -120,21 +121,25 @@ class MessengerStateManagerImpl(private val chatManager: ChatManager) : IMesseng } @Deprecated("Not used in protocol 9 or later") - private fun getUnreadMessageStates(channelId: Long, messageId: Long): State { - return messageUnreadMap.computeIfAbsent(Pair(channelId, messageId)) { - mutableStateOf(chatManager.getMessageById(channelId, messageId)?.isRead?.not() ?: false) + private fun getUnreadMessageStates(message: Message): State { + return messageUnreadMap.computeIfAbsent(message) { + mutableStateOf(!message.isRead) } } @Deprecated("Not used in protocol 9 or later") - override fun getUnreadMessageState(channelId: Long, messageId: Long): State { - return getUnreadMessageStates(channelId, messageId) + override fun getUnreadMessageState(message: Message): State { + return getUnreadMessageStates(message) } override fun getLastReadMessageId(channelId: Long): State { return getOrCreateChannelStates(channelId).lastReadMessageId } + override fun setLastReadMessage(message: Message) { + chatManager.setLastReadMessage(message) + } + override fun setLastReadMessage(channelId: Long, messageId: Long?) { chatManager.setLastReadMessage(channelId, messageId) } @@ -162,11 +167,11 @@ class MessengerStateManagerImpl(private val chatManager: ChatManager) : IMesseng if (platform.cmConnection.usingProtocol >= 9) { mutableStateOf(channel.unreadMessages) } else { - memo { getMessageListState(channelId)().count { getUnreadMessageState(it)() } } + memo { getMessageListState(channelId)().count { getUnreadMessageState(it.getInfraInstance())() } } }, mutableStateOf(channel.isMuted), mutableStateOf("Loading..."), - getMessageListState(channelId).map { list -> list.maxByOrNull { it.id } }, + getMessageListState(channelId).map { list -> list.maxByOrNull { it.id }?.getInfraInstance() }, ObservableList(channel.members.toMutableList()), mutableStateOf(channel.lastReadMessageId) ).apply { @@ -209,18 +214,9 @@ class MessengerStateManagerImpl(private val chatManager: ChatManager) : IMesseng } } - override fun getMessagesRaw(channelId: Long): Map? { - return chatManager.getMessages(channelId) - } - - override fun retrieveMessageHistoryRaw(channelId: Long, before: Long?, after: Long?, messageLimit: Int, callback: ((Optional) -> Unit)?) { - return chatManager.retrieveMessageHistory(channelId, before, after, messageLimit, callback) - } - /** IMessengerActions **/ @Deprecated("Not used in protocol 9 or later") - override fun setUnreadState(channelId: Long, messageId: Long, unread: Boolean) { - val message = chatManager.getMessageById(channelId, messageId) ?: return + override fun setUnreadState(message: Message, unread: Boolean) { chatManager.updateReadState(message, !unread) } @@ -256,12 +252,8 @@ class MessengerStateManagerImpl(private val chatManager: ChatManager) : IMesseng chatManager.sendMessage(channelId, message, replyTo, callback) } - override fun editMessage(channelId: Long, messageId: Long, content: String, callback: (Boolean) -> Unit) { - chatManager.editMessage(channelId, messageId, content, callback) - } - - override fun deleteMessage(channelId: Long, messageId: Long) { - chatManager.deleteMessage(channelId, messageId) + override fun deleteMessage(message: Message) { + chatManager.deleteMessage(message.channelId, message.id) } override fun leaveGroup(channelId: Long) { @@ -297,7 +289,7 @@ class MessengerStateManagerImpl(private val chatManager: ChatManager) : IMesseng override fun messageDeleted(message: Message) { val channelId = chatManager.mergeAnnouncementChannel(message.channelId) observableMessageList[channelId]?.first?.removeAll { it.id == message.id } - messageUnreadMap.remove(Pair(message.channelId, message.id)) + messageUnreadMap.remove(message) val channelState = channelStates[channelId] ?: return updateChannelStates(getChannel(channelId), channelState) @@ -332,7 +324,7 @@ class MessengerStateManagerImpl(private val chatManager: ChatManager) : IMesseng @Deprecated("Not used in protocol 9 or later") override fun messageReadStateUpdated(message: Message, read: Boolean) { - messageUnreadMap[Pair(message.channelId, message.id)]?.set(!read) + messageUnreadMap[message]?.set(!read) chatManager.getChannel(chatManager.mergeAnnouncementChannel(message.channelId)).ifPresent { channelUpdated(it) } @@ -382,7 +374,7 @@ class MessengerStateManagerImpl(private val chatManager: ChatManager) : IMesseng val numUnreadMessages: State, val internalMutedState: MutableState, val title: MutableState, - val latestMessage: State, + val latestMessage: State, val members: ObservableList, val lastReadMessageId: MutableState, ) { diff --git a/src/main/kotlin/gg/essential/gui/friends/state/SocialStateManager.kt b/src/main/kotlin/gg/essential/gui/friends/state/SocialStateManager.kt index 784d15d1..57bbc342 100644 --- a/src/main/kotlin/gg/essential/gui/friends/state/SocialStateManager.kt +++ b/src/main/kotlin/gg/essential/gui/friends/state/SocialStateManager.kt @@ -12,9 +12,7 @@ package gg.essential.gui.friends.state import gg.essential.elementa.utils.ObservableAddEvent -import gg.essential.gui.elementa.state.v2.ListState import gg.essential.network.connectionmanager.ConnectionManager -import gg.essential.network.connectionmanager.social.ProfileSuspension import gg.essential.util.UUIDUtil import gg.essential.util.getOtherUser import gg.essential.util.thenAcceptOnMainThread @@ -39,8 +37,6 @@ class SocialStateManager(connectionManager: ConnectionManager) : SocialStates { override val activity: IStatusStates get() = statusStates - override val suspensions: ListState = connectionManager.profileManager.suspensions - init { val observableFriendList = relationshipStates.getObservableFriendList() val observableChannelList = messengerStates.getObservableChannelList() diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/tabs/ChatTab.kt b/src/main/kotlin/gg/essential/gui/friends/tabs/ChatTab.kt similarity index 76% rename from gui/essential/src/main/kotlin/gg/essential/gui/friends/tabs/ChatTab.kt rename to src/main/kotlin/gg/essential/gui/friends/tabs/ChatTab.kt index a0158343..9d87a55a 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/friends/tabs/ChatTab.kt +++ b/src/main/kotlin/gg/essential/gui/friends/tabs/ChatTab.kt @@ -12,6 +12,7 @@ package gg.essential.gui.friends.tabs import com.sparkuniverse.toolbox.chat.model.Channel +import gg.essential.Essential import gg.essential.elementa.UIComponent import gg.essential.elementa.components.* import gg.essential.elementa.constraints.CenterConstraint @@ -24,37 +25,28 @@ import gg.essential.gui.common.ScrollSpacer import gg.essential.gui.common.bindChildren import gg.essential.gui.common.bindParent import gg.essential.gui.elementa.state.v2.State -import gg.essential.gui.elementa.state.v2.combinators.and -import gg.essential.gui.elementa.state.v2.combinators.map -import gg.essential.gui.elementa.state.v2.flatten import gg.essential.gui.elementa.state.v2.mutableStateOf -import gg.essential.gui.elementa.state.v2.stateOf +import gg.essential.gui.friends.SocialMenu import gg.essential.gui.friends.Tab import gg.essential.gui.friends.message.MessageScreen -import gg.essential.gui.friends.message.SocialMenuActions import gg.essential.gui.friends.message.v2.ReplyableMessageScreen import gg.essential.gui.friends.previews.ChannelPreview -import gg.essential.gui.friends.state.SocialStates import gg.essential.gui.util.onItemRemoved import gg.essential.util.* import gg.essential.vigilance.gui.VigilancePalette import java.util.* class ChatTab( - selectedTab: State, - private val socialStates: SocialStates, - private val socialMenuActions: SocialMenuActions, - private val tabsSelector: UIComponent, - private val titleBar: UIComponent, - private val rightDivider: UIComponent, - private val isScreenOpen: State, -) : TabComponent(Tab.CHAT, selectedTab) { - private val messengerStates = socialStates.messages + gui: SocialMenu, + selectedTab: State +) : TabComponent(Tab.CHAT, gui, selectedTab) { + private val connectionManager = Essential.getInstance().connectionManager + private val chatManager = connectionManager.chatManager private val horizontalDivider by UIBlock(VigilancePalette.getDarkDivider().darker()).constrain { - y = SiblingConstraint() boundTo tabsSelector - width = 100.percent boundTo tabsSelector - height = rightDivider.getWidth().pixels + y = SiblingConstraint() boundTo gui.tabsSelector + width = 100.percent boundTo gui.tabsSelector + height = gui.dividerWidth.pixels } childOf this @@ -69,7 +61,7 @@ class ChatTab( private val divider by UIBlock(EssentialPalette.COMPONENT_BACKGROUND).constrain { height = 100.percent - width = 100.percent boundTo rightDivider + width = 100.percent boundTo gui.rightDivider x = SiblingConstraint() } childOf this @@ -95,7 +87,7 @@ class ChatTab( height = 100.percent width = 100.percent boundTo divider x = CenterConstraint() boundTo divider - }.bindParent(titleBar, active) + }.bindParent(gui.titleBar, active) private val previews: Sequence get() = channelListScroller.allChildren.asSequence().filterIsInstance() @@ -137,14 +129,8 @@ class ChatTab( channelListScroller.bindChildren( observableChannelList, comparator = channelSorter - ) { channel -> - ChannelPreview( - channel, - socialStates, - currentMessageView.map { it?.preview?.channel?.id == channel.id } and this@ChatTab.active, - openMessageScreen = { openMessage(get(channel.id)!!) }, - openManagementDropdown = { pos, onClose -> socialMenuActions.showManagementDropdown(get(channel.id)!!, pos, onClose = onClose)} - ) + ) { + ChannelPreview(gui, it) } messengerStates.onChannelStateChange { @@ -156,7 +142,7 @@ class ChatTab( val firstOrNull = channelListScroller.allChildren.filterIsInstance().filter { it.channel != exclude } .sortedWith(channelSorter).firstOrNull() if (firstOrNull != null) { - openMessage(firstOrNull) + gui.openMessageScreen(firstOrNull) } } @@ -184,20 +170,7 @@ class ChatTab( messageScreenArea.clearChildren() - val activeSource = mutableStateOf(stateOf(false)) - val active = activeSource.flatten() - - val messageScreen = ReplyableMessageScreen( - preview, - socialStates, - socialMenuActions, - titleBar, - rightDivider, - active = active, - isScreenOpen = { isScreenOpen() && active() }, - ) - - activeSource.set(currentMessageView.map { it == messageScreen } and this@ChatTab.active) + val messageScreen = ReplyableMessageScreen(preview) currentMessageView.set(messageScreen.constrain { width = 100.percent diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/tabs/FriendsTab.kt b/src/main/kotlin/gg/essential/gui/friends/tabs/FriendsTab.kt similarity index 81% rename from gui/essential/src/main/kotlin/gg/essential/gui/friends/tabs/FriendsTab.kt rename to src/main/kotlin/gg/essential/gui/friends/tabs/FriendsTab.kt index d5359d9a..de11d378 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/friends/tabs/FriendsTab.kt +++ b/src/main/kotlin/gg/essential/gui/friends/tabs/FriendsTab.kt @@ -23,28 +23,23 @@ import gg.essential.gui.common.bindChildren import gg.essential.gui.common.bindParent import gg.essential.gui.common.shadow.EssentialUIText import gg.essential.gui.elementa.state.v2.State +import gg.essential.gui.friends.SocialMenu import gg.essential.gui.friends.Tab -import gg.essential.gui.friends.message.SocialMenuActions import gg.essential.gui.friends.previews.* import gg.essential.gui.friends.state.PlayerActivity -import gg.essential.gui.friends.state.SocialStates -import gg.essential.network.connectionmanager.notices.SocialMenuNewFriendRequestNoticeManager import gg.essential.util.scrollGradient import kotlin.Comparator class FriendsTab( - selectedTab: State, - private val socialStates: SocialStates, - private val socialMenuActions: SocialMenuActions, - private val friendRequestNoticeManager: SocialMenuNewFriendRequestNoticeManager, - private val tabsSelector: UIComponent, - private val rightDivider: UIComponent, -) : TabComponent(Tab.FRIENDS, selectedTab) { + gui: SocialMenu, + selectedTab: State +) : TabComponent(Tab.FRIENDS, gui, selectedTab) { + private val socialStateManager = gui.socialStateManager private val horizontalDivider by UIBlock(EssentialPalette.COMPONENT_BACKGROUND).constrain { - y = SiblingConstraint() boundTo tabsSelector + y = SiblingConstraint() boundTo gui.tabsSelector width = 100.percent - height = rightDivider.getWidth().pixels + height = gui.dividerWidth.pixels } childOf this private val sectionContainer by UIContainer().constrain { @@ -56,13 +51,13 @@ class FriendsTab( private val pendingSorter: Comparator = compareBy( { (it as PendingUserEntry).incoming }, { - -(socialStates.relationships.getPendingRequestTime((it as PendingUserEntry).user)?.toEpochMilli() + -(socialStateManager.relationshipStates.getPendingRequestTime((it as PendingUserEntry).user)?.toEpochMilli() ?: 0) } ) private val friendSorter: Comparator = compareBy { - val activity = socialStates.activity.getActivity((it as BasicUserEntry).user) + val activity = socialStateManager.statusStates.getActivity((it as BasicUserEntry).user) if (activity.isJoinable()) { return@compareBy 0L } @@ -88,7 +83,7 @@ class FriendsTab( y = CopyConstraintFloat() boundTo blockedSection height = CopyConstraintFloat() boundTo blockedSection width = 100.percent - }.bindParent(rightDivider, active).also { + }.bindParent(gui.rightDivider, active).also { blockedSection.setupScrollbar(it) } @@ -99,7 +94,7 @@ class FriendsTab( private fun createDivider(section: Section): UIBlock { return UIBlock(EssentialPalette.COMPONENT_BACKGROUND).constrain { x = SiblingConstraint() - width = rightDivider.getWidth().pixels + width = gui.dividerWidth.pixels height = 100.percent boundTo section }.also { section.setupScrollbar(it) @@ -107,34 +102,34 @@ class FriendsTab( } override fun populate() { - val relationshipStates = socialStates.relationships + val relationshipStates = socialStateManager.relationshipStates friendSection.scrollList.bindChildren( relationshipStates.getObservableFriendList(), comparator = friendSorter ) { - FriendUserEntry(it, socialStates, socialMenuActions, friendSection) + FriendUserEntry(gui, it, friendSection) } blockedSection.scrollList.bindChildren( relationshipStates.getObservableBlockedList(), comparator = blockedSorter ) { - BlockedUserEntry(it, socialMenuActions, blockedSection) + BlockedUserEntry(it, gui, blockedSection) } pendingSection.scrollList.bindChildren( relationshipStates.getObservableIncomingRequests(), comparator = pendingSorter ) { - PendingUserEntry(it, true, socialStates, socialMenuActions, friendRequestNoticeManager, pendingSection) + PendingUserEntry(it, true, gui, pendingSection) } pendingSection.scrollList.bindChildren( relationshipStates.getObservableOutgoingRequests(), comparator = pendingSorter ) { - PendingUserEntry(it, false, socialStates, socialMenuActions, friendRequestNoticeManager, pendingSection) + PendingUserEntry(it, false, gui, pendingSection) } } @@ -163,7 +158,7 @@ class FriendsTab( init { constrain { x = SiblingConstraint() - width = (100.percent - (rightDivider.getWidth().pixels * 2)) / 3 + width = (100.percent - (gui.dividerWidth.pixels * 2)) / 3 height = 100.percent } } diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/tabs/TabComponent.kt b/src/main/kotlin/gg/essential/gui/friends/tabs/TabComponent.kt similarity index 85% rename from gui/essential/src/main/kotlin/gg/essential/gui/friends/tabs/TabComponent.kt rename to src/main/kotlin/gg/essential/gui/friends/tabs/TabComponent.kt index c5ed0d95..fd3a0c12 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/friends/tabs/TabComponent.kt +++ b/src/main/kotlin/gg/essential/gui/friends/tabs/TabComponent.kt @@ -15,16 +15,23 @@ import gg.essential.elementa.components.ScrollComponent import gg.essential.elementa.dsl.constrain import gg.essential.elementa.dsl.percent import gg.essential.gui.common.HollowUIContainer +import gg.essential.gui.common.bindParent import gg.essential.gui.elementa.state.v2.State import gg.essential.gui.elementa.state.v2.combinators.map +import gg.essential.gui.friends.SocialMenu import gg.essential.gui.friends.Tab import gg.essential.gui.friends.previews.SearchableItem abstract class TabComponent( protected val tab: Tab, + protected val gui: SocialMenu, selectedTab: State ) : HollowUIContainer() { + val stateManager = gui.socialStateManager + val messengerStates = stateManager.messengerStates + val relationshipStates = stateManager.relationshipStates + val active = selectedTab.map { it == tab } /** @@ -35,6 +42,7 @@ abstract class TabComponent( abstract val userLists: List init { + bindParent(gui.content, active) constrain { width = 100.percent height = 100.percent diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/friends/title/SocialTitleManagementActions.kt b/src/main/kotlin/gg/essential/gui/friends/title/SocialTitleManagementActions.kt similarity index 84% rename from gui/essential/src/main/kotlin/gg/essential/gui/friends/title/SocialTitleManagementActions.kt rename to src/main/kotlin/gg/essential/gui/friends/title/SocialTitleManagementActions.kt index ab262390..16686482 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/friends/title/SocialTitleManagementActions.kt +++ b/src/main/kotlin/gg/essential/gui/friends/title/SocialTitleManagementActions.kt @@ -16,10 +16,7 @@ import gg.essential.gui.EssentialPalette import gg.essential.gui.common.EssentialCollapsibleSearchbar import gg.essential.gui.common.IconButton import gg.essential.gui.common.state -import gg.essential.gui.elementa.state.v2.State -import gg.essential.gui.friends.Tab -import gg.essential.gui.friends.message.SocialMenuActions -import gg.essential.gui.friends.state.SocialStates +import gg.essential.gui.friends.SocialMenu import gg.essential.gui.layoutdsl.Arrangement import gg.essential.gui.layoutdsl.Modifier import gg.essential.gui.layoutdsl.childBasedMaxHeight @@ -32,14 +29,7 @@ import gg.essential.gui.layoutdsl.layout import gg.essential.gui.layoutdsl.row import gg.essential.gui.layoutdsl.widthAspect -class SocialTitleManagementActions( - private val currentTab: State, - socialStates: SocialStates, - socialMenuActions: SocialMenuActions, -) : TitleManagementActions( - socialStates, - socialMenuActions, -) { +class SocialTitleManagementActions(gui: SocialMenu) : TitleManagementActions(gui) { override val search by EssentialCollapsibleSearchbar( placeholder = "Search...", @@ -75,7 +65,7 @@ class SocialTitleManagementActions( row(Arrangement.spacedBy(3f)) { friendButton(buttonModifier.color(EssentialPalette.BLUE_BUTTON).hoverColor(EssentialPalette.BLUE_BUTTON_HOVER).hoverScope()) makeGroupButton(buttonModifier) - if_({ currentTab() == Tab.FRIENDS }) { + if_(gui.friendsTab.active) { blockButton(buttonModifier) } search() diff --git a/src/main/kotlin/gg/essential/gui/friends/title/TitleManagementActions.kt b/src/main/kotlin/gg/essential/gui/friends/title/TitleManagementActions.kt new file mode 100644 index 00000000..726ce154 --- /dev/null +++ b/src/main/kotlin/gg/essential/gui/friends/title/TitleManagementActions.kt @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.gui.friends.title + +import gg.essential.elementa.components.UIContainer +import gg.essential.elementa.components.Window +import gg.essential.gui.EssentialPalette +import gg.essential.gui.common.EssentialCollapsibleSearchbar +import gg.essential.gui.common.modal.CancelableInputModal +import gg.essential.gui.common.modal.UsernameInputModal +import gg.essential.gui.common.modal.configure +import gg.essential.gui.friends.SocialMenu +import gg.essential.gui.modals.select.offlinePlayers +import gg.essential.gui.modals.select.onlinePlayers +import gg.essential.gui.modals.select.selectModal +import gg.essential.gui.notification.Notifications +import gg.essential.gui.notification.iconAndMarkdownBody +import gg.essential.gui.overlay.ModalFlow +import gg.essential.gui.overlay.ModalManager +import gg.essential.network.connectionmanager.relationship.FriendRequestState +import gg.essential.network.connectionmanager.relationship.RelationshipErrorResponse +import gg.essential.network.connectionmanager.relationship.RelationshipResponse +import gg.essential.network.connectionmanager.relationship.message +import gg.essential.util.GuiUtil +import gg.essential.util.colored +import gg.essential.util.thenAcceptOnMainThread +import java.util.* +import java.util.concurrent.CompletableFuture + +abstract class TitleManagementActions(private val gui: SocialMenu) : UIContainer() { + + abstract val search: EssentialCollapsibleSearchbar + + protected fun addFriend() { + GuiUtil.pushModal { manager -> + AddFriendModal(manager) { uuid, username, modal -> + val future = gui.socialStateManager.relationshipStates.addFriend(uuid, false) + consumeRelationshipFutureFromModal( + modal, future + ) { + Notifications.push("", "") { + iconAndMarkdownBody( + EssentialPalette.ENVELOPE_9X7.create(), + "Friend request sent to ${username.colored(EssentialPalette.TEXT_HIGHLIGHT)}" + ) + } + } + } + } + } + + protected fun makeGroup() { + GuiUtil.launchModalFlow { + makeGroupModal(gui) + } + } + + protected fun blockPlayer() { + GuiUtil.pushModal { manager -> + BlockPlayerModal(manager) { uuid, username, modal -> + val future = gui.socialStateManager.relationshipStates.blockPlayer(uuid, false) + consumeRelationshipFutureFromModal( + modal, future + ) { + Notifications.push("", "") { + iconAndMarkdownBody( + EssentialPalette.BLOCK_7X7.create(), + "${username.colored(EssentialPalette.TEXT_HIGHLIGHT)} has been blocked" + ) + } + } + } + } + } + + // Adapted from RelationshipStateManagerImpl consumeRelationshipFuture + private fun consumeRelationshipFutureFromModal( + modal: UsernameInputModal, + future: CompletableFuture, + onSuccess: () -> Unit + ) { + future.thenAcceptOnMainThread { + modal.primaryButtonEnableStateOverride.set(true) + when (it.friendRequestState) { + FriendRequestState.SENT -> { + onSuccess() + modal.replaceWith(null) + } + + FriendRequestState.ERROR_HANDLED, FriendRequestState.ERROR_UNHANDLED -> { + modal.errorOverride.set( + if (it.relationshipErrorResponse == RelationshipErrorResponse.TARGET_NOT_EXIST) { + "Not an Essential user" + } else { + it.message + } + ) + } + } + }.whenComplete { _, _ -> + // Always re-enable the button when we complete the future + modal.primaryButtonEnableStateOverride.set(true) + } + } + + class AddFriendModal( + modalManager: ModalManager, + whenValidated: (UUID, String, UsernameInputModal) -> Unit, + ) : UsernameInputModal(modalManager, "", whenValidated = whenValidated) { + init { + configure { + primaryButtonText = "Add" + titleText = "Add Friend" + contentText = "Enter a Minecraft username\nto add them as a friend." + } + } + } + + class BlockPlayerModal( + modalManager: ModalManager, + whenValidated: (UUID, String, UsernameInputModal) -> Unit, + ) : UsernameInputModal(modalManager, "", whenValidated = whenValidated) { + init { + configure { + primaryButtonText = "Block" + titleText = "Block Player" + contentText = "Enter a Minecraft username\nto block them." + } + } + } + + companion object { + suspend fun ModalFlow.makeGroupModal(socialMenu: SocialMenu) { + while (true) { + val friends = selectFriendsForGroupModal() ?: return + val name = enterGroupNameModal() ?: continue + socialMenu.socialStateManager.messengerStates.createGroup(friends, name).thenAcceptOnMainThread { + // Intentionally delayed one frame so that the channel preview callback can fire first + Window.enqueueRenderOperation { + socialMenu.openMessageScreen(it) + } + } + return + } + } + + suspend fun ModalFlow.enterGroupNameModal(): String? { + return awaitModal { continuation -> + CancelableInputModal(modalManager, "", "", maxLength = 24).configure { + titleText = "Make Group" + contentText = "Enter a name for your group." + primaryButtonText = "Make Group" + titleTextColor = EssentialPalette.TEXT_HIGHLIGHT + + cancelButtonText = "Back" + + mapInputToEnabled { it.isNotBlank() } + onPrimaryActionWithValue { result -> replaceWith(continuation.resumeImmediately(result)) } + onCancel { button -> if (button) replaceWith(continuation.resumeImmediately(null)) } + } + } + } + + suspend fun ModalFlow.selectFriendsForGroupModal(): Set? { + return selectModal("Select friends to make group") { + requiresSelection = true + requiresButtonPress = false + + onlinePlayers() + offlinePlayers() + + modalSettings { + primaryButtonText = "Continue" + cancelButtonText = "Cancel" + } + } + } + } + +} diff --git a/src/main/kotlin/gg/essential/gui/modals/AccountNotValidModal.kt b/src/main/kotlin/gg/essential/gui/modals/AccountNotValidModal.kt index 6196149d..75b99067 100644 --- a/src/main/kotlin/gg/essential/gui/modals/AccountNotValidModal.kt +++ b/src/main/kotlin/gg/essential/gui/modals/AccountNotValidModal.kt @@ -23,8 +23,7 @@ import gg.essential.gui.util.pollingState class AccountNotValidModal( modalManager: ModalManager, private val skipAuthCheck: Boolean = false, - private val successCallback: Modal.() -> Unit = {}, - private val failureCallback: Modal.() -> Unit = {}, + private val successCallback: Modal.() -> Unit = {} ) : ConfirmDenyModal(modalManager, false) { private val authStatus = pollingState { Essential.getInstance().connectionManager.isAuthenticated } @@ -51,10 +50,4 @@ class AccountNotValidModal( } } } - - override fun onClose() { - if (!authStatus.get()) { - failureCallback.invoke(this) - } - } } diff --git a/src/main/kotlin/gg/essential/gui/modals/CosmeticsLoadingModal.kt b/src/main/kotlin/gg/essential/gui/modals/CosmeticsLoadingModal.kt index 274d7092..675921f4 100644 --- a/src/main/kotlin/gg/essential/gui/modals/CosmeticsLoadingModal.kt +++ b/src/main/kotlin/gg/essential/gui/modals/CosmeticsLoadingModal.kt @@ -17,7 +17,6 @@ import gg.essential.gui.common.Spacer import gg.essential.gui.common.modal.EssentialModal import gg.essential.gui.common.modal.configure import gg.essential.gui.elementa.state.v2.awaitValue -import gg.essential.gui.overlay.ModalFlow import gg.essential.gui.overlay.ModalManager import gg.essential.network.connectionmanager.cosmetics.CosmeticsManager import kotlinx.coroutines.launch @@ -28,7 +27,7 @@ import kotlin.time.Duration.Companion.seconds * Displays when the user attempts to open the Wardrobe, but cosmetics are not fully loaded yet. * This modal constructs a new instance of either [CosmeticStudio] or [Wardrobe] when complete. */ -class CosmeticsLoadingModal(modalManager: ModalManager, continuation: ModalFlow.ModalContinuation) : EssentialModal(modalManager) { +class CosmeticsLoadingModal(modalManager: ModalManager, callback: () -> Unit) : EssentialModal(modalManager) { init { configure { @@ -44,11 +43,9 @@ class CosmeticsLoadingModal(modalManager: ModalManager, continuation: ModalFlow. val completed = withTimeoutOrNull(CosmeticsManager.LOAD_TIMEOUT_SECONDS.seconds) { cosmeticsLoaded.awaitValue(true) } - replaceWith(continuation.resumeImmediately(completed == true)) + if (completed == true) { + callback() + } } } -} - -suspend fun ModalFlow.cosmeticsLoadingModal(): Boolean { - return awaitModal { CosmeticsLoadingModal(modalManager, it) } } \ No newline at end of file diff --git a/src/main/kotlin/gg/essential/gui/modals/McModalPrerequisites.kt b/src/main/kotlin/gg/essential/gui/modals/McModalPrerequisites.kt deleted file mode 100644 index 02fc5f0f..00000000 --- a/src/main/kotlin/gg/essential/gui/modals/McModalPrerequisites.kt +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.gui.modals - -import gg.essential.Essential -import gg.essential.gui.overlay.ModalFlow -import gg.essential.data.OnboardingData -import gg.essential.network.connectionmanager.suspension.suspensionModal -import gg.essential.universal.UMinecraft -import gg.essential.util.AutoUpdate - -object McModalPrerequisites : ModalPrerequisites() { - - override suspend fun ModalFlow.doTermsOfServiceModal(): PrerequisiteResult { - return if (!OnboardingData.hasAcceptedTos()) { - tosModal().toResult() - } else { - PrerequisiteResult.PASS - } - } - - override suspend fun ModalFlow.doRequiredUpdateModal(): PrerequisiteResult { - return if (AutoUpdate.requiresUpdate()) { - updateRequiredModal() - PrerequisiteResult.FAILURE - } else { - PrerequisiteResult.PASS - } - } - - override suspend fun ModalFlow.doAuthenticationModal(): PrerequisiteResult { - val connectionManager = Essential.getInstance().connectionManager - return if (!connectionManager.isAuthenticated || ((!connectionManager.suspensionManager.isLoaded.getUntracked() || !connectionManager.rulesManager.isLoaded.getUntracked()))) { - notAuthenticatedModal().toResult() - } else { - PrerequisiteResult.PASS - } - } - - override suspend fun ModalFlow.doCosmeticsModal(): PrerequisiteResult { - if (!Essential.getInstance().connectionManager.cosmeticsManager.cosmeticsLoaded.getUntracked()) { - cosmeticsLoadingModal() - return PrerequisiteResult.SUCCESS - } - return PrerequisiteResult.PASS - } - - override suspend fun ModalFlow.doCommunityRulesModal(): PrerequisiteResult { - val rulesManager = Essential.getInstance().connectionManager.rulesManager - return if (rulesManager.hasRules.getUntracked() && !rulesManager.acceptedRules) { - communityRulesModal(rulesManager, UMinecraft.getSettings().language).toResult() - } else { - PrerequisiteResult.PASS - } - } - - override suspend fun ModalFlow.doSocialSuspensionModal(): PrerequisiteResult { - Essential.getInstance().connectionManager.suspensionManager.activeSuspension.getUntracked()?.let { suspension -> - suspensionModal(suspension) - return PrerequisiteResult.FAILURE - } - return PrerequisiteResult.PASS - } - - override suspend fun ModalFlow.doPermanentSuspensionModal(): PrerequisiteResult { - Essential.getInstance().connectionManager.suspensionManager.activeSuspension.getUntracked()?.let { suspension -> - if (suspension.isPermanent) { - suspensionModal(suspension) - return PrerequisiteResult.FAILURE - } - } - return PrerequisiteResult.PASS - } -} \ No newline at end of file diff --git a/src/main/kotlin/gg/essential/gui/modals/NotAuthenticatedModal.kt b/src/main/kotlin/gg/essential/gui/modals/NotAuthenticatedModal.kt index c251e3b8..6a155206 100644 --- a/src/main/kotlin/gg/essential/gui/modals/NotAuthenticatedModal.kt +++ b/src/main/kotlin/gg/essential/gui/modals/NotAuthenticatedModal.kt @@ -17,48 +17,30 @@ import gg.essential.gui.common.modal.ConfirmDenyModal import gg.essential.gui.common.modal.Modal import gg.essential.gui.common.modal.configure import gg.essential.gui.common.not -import gg.essential.gui.elementa.state.v2.State +import gg.essential.gui.common.onSetValueAndNow import gg.essential.gui.elementa.state.v2.await import gg.essential.gui.elementa.state.v2.combinators.map -import gg.essential.gui.elementa.state.v2.effect import gg.essential.gui.elementa.state.v2.toV1 import gg.essential.gui.notification.Notifications import gg.essential.gui.notification.error -import gg.essential.gui.overlay.ModalFlow import gg.essential.gui.overlay.ModalManager import gg.essential.network.connectionmanager.ConnectionManager -import gg.essential.gui.util.pollingStateV2 +import gg.essential.gui.util.pollingState import kotlinx.coroutines.launch class NotAuthenticatedModal( modalManager: ModalManager, private val skipAuthCheck: Boolean = false, - private val successCallback: Modal.() -> Unit = {}, - private val failureCallback: Modal.() -> Unit = {}, + private val successCallback: Modal.() -> Unit = {} ) : ConfirmDenyModal(modalManager, false) { private val connectionManager = Essential.getInstance().connectionManager private val connecting = connectionManager.connectionStatus.map { it == null }.toV1(this) private val triedConnect = BasicState(false) - private val authenticated = pollingStateV2 { connectionManager.isAuthenticated } - private val status = State { - authenticated() && connectionManager.suspensionManager.isLoaded() && connectionManager.rulesManager.isLoaded() - } + private val authStatus = pollingState { connectionManager.isAuthenticated } private val buttonText = connecting.zip(triedConnect).map { (connect, tried) -> if (connect) "Connecting..." else if (tried) "Retry" else "Connect" } - private var unregisterEffect: (() -> Unit)? = null - - constructor( - modalManager: ModalManager, - skipAuthCheck: Boolean, - continuation: ModalFlow.ModalContinuation, - ) : this( - modalManager, - skipAuthCheck, - { replaceWith(continuation.resumeImmediately(true)) }, - { modalManager.queueModal(continuation.resumeImmediately(false)) }, - ) init { configure { @@ -74,7 +56,7 @@ class NotAuthenticatedModal( when (connectionManager.connectionStatus.await { it != null }) { ConnectionManager.Status.SUCCESS -> {} ConnectionManager.Status.MOJANG_UNAUTHORIZED -> { - replaceWith(AccountNotValidModal(modalManager, successCallback = successCallback, failureCallback = failureCallback)) + replaceWith(AccountNotValidModal(modalManager, successCallback = successCallback)) } else -> { @@ -90,22 +72,12 @@ class NotAuthenticatedModal( super.onOpen() if (skipAuthCheck) return // Immediately move on if a connection is established and authenticated - unregisterEffect = effect(this) { - if (status()) { - successCallback.invoke(this@NotAuthenticatedModal) + authStatus.onSetValueAndNow { + if (it) { + successCallback.invoke(this) replaceWith(null) } } } - override fun onClose() { - unregisterEffect?.invoke() - if (!status.getUntracked() && connectionManager.connectionStatus.getUntracked() != ConnectionManager.Status.MOJANG_UNAUTHORIZED) { - failureCallback.invoke(this) - } - } } - -suspend fun ModalFlow.notAuthenticatedModal(skipAuthCheck: Boolean = false): Boolean = - awaitModal { NotAuthenticatedModal(modalManager, skipAuthCheck, it) } - diff --git a/src/main/kotlin/gg/essential/gui/modals/TOSModal.kt b/src/main/kotlin/gg/essential/gui/modals/TOSModal.kt index 8a2c33b6..07ab6224 100644 --- a/src/main/kotlin/gg/essential/gui/modals/TOSModal.kt +++ b/src/main/kotlin/gg/essential/gui/modals/TOSModal.kt @@ -26,8 +26,6 @@ import gg.essential.gui.common.outline.GuiScaleOffsetOutline import gg.essential.gui.common.shadow.EssentialUIText import gg.essential.gui.elementa.GuiScaleOffsetConstraint import gg.essential.gui.elementa.state.v2.await -import gg.essential.gui.elementa.state.v2.combinators.letState -import gg.essential.gui.overlay.ModalFlow import gg.essential.gui.overlay.ModalManager import gg.essential.network.connectionmanager.ConnectionManager import gg.essential.universal.ChatColor @@ -198,16 +196,7 @@ class TOSModal( val connectionManager = Essential.getInstance().connectionManager coroutineScope.launch { - val status = connectionManager.connectionStatus - .letState { status -> - if (status == ConnectionManager.Status.NO_TOS) return@letState null - if (status != ConnectionManager.Status.SUCCESS) return@letState status - if (!connectionManager.suspensionManager.isLoaded()) return@letState null - if (!connectionManager.rulesManager.isLoaded()) return@letState null - ConnectionManager.Status.SUCCESS - } - .await { it != null } - + val status = connectionManager.connectionStatus.await { it != null && it != ConnectionManager.Status.NO_TOS } val modal = when { connectionManager.outdated -> AutoUpdate.createUpdateModal(modalManager) status == ConnectionManager.Status.MOJANG_UNAUTHORIZED -> AccountNotValidModal(modalManager, successCallback = confirmAction) @@ -242,14 +231,3 @@ class TOSModal( return GuiScaleOffsetConstraint(if (forceUnicodeEnabled) 0f else -1f) } } - -suspend fun ModalFlow.tosModal(): Boolean = - awaitModal { continuation -> - TOSModal( - modalManager, - unprompted = false, - requiresAuth = true, - confirmAction = { modalManager.queueModal(continuation.resumeImmediately(true)) }, - cancelAction = { modalManager.queueModal(continuation.resumeImmediately(false)) } - ) - } diff --git a/src/main/kotlin/gg/essential/gui/modals/UpdateAvailableModal.kt b/src/main/kotlin/gg/essential/gui/modals/UpdateAvailableModal.kt index 0438454f..6c32bf94 100644 --- a/src/main/kotlin/gg/essential/gui/modals/UpdateAvailableModal.kt +++ b/src/main/kotlin/gg/essential/gui/modals/UpdateAvailableModal.kt @@ -41,7 +41,6 @@ import gg.essential.gui.layoutdsl.shadow import gg.essential.gui.layoutdsl.spacer import gg.essential.gui.layoutdsl.text import gg.essential.gui.notification.Notifications -import gg.essential.gui.overlay.ModalFlow import gg.essential.gui.overlay.ModalManager import gg.essential.universal.USound import gg.essential.util.AutoUpdate @@ -146,7 +145,3 @@ class UpdateRequiredModal(modalManager: ModalManager) : ConfirmDenyModal(modalMa onPrimaryAction { shutdown() } } } - -suspend fun ModalFlow.updateRequiredModal() { - awaitModal { UpdateRequiredModal(modalManager) } -} diff --git a/src/main/kotlin/gg/essential/gui/multiplayer/EssentialMultiplayerGui.kt b/src/main/kotlin/gg/essential/gui/multiplayer/EssentialMultiplayerGui.kt index 041d8f61..abd922d7 100644 --- a/src/main/kotlin/gg/essential/gui/multiplayer/EssentialMultiplayerGui.kt +++ b/src/main/kotlin/gg/essential/gui/multiplayer/EssentialMultiplayerGui.kt @@ -34,7 +34,7 @@ import gg.essential.gui.elementa.VanillaButtonConstraint.Companion.constrainTo import gg.essential.gui.elementa.state.v2.ReferenceHolderImpl import gg.essential.gui.elementa.state.v2.mutableStateOf import gg.essential.gui.elementa.state.v2.stateOf -import gg.essential.gui.modals.ensurePrerequisites +import gg.essential.gui.modals.TOSModal import gg.essential.gui.overlay.ModalManager import gg.essential.gui.serverdiscovery.VersionDownloadModal import gg.essential.mixins.ext.client.gui.acc @@ -206,8 +206,6 @@ class EssentialMultiplayerGui { } } - private val oldButtons = mutableListOf() - fun setupButtons( buttons: List, addButton: (GuiButton) -> GuiButton, @@ -239,13 +237,6 @@ class EssentialMultiplayerGui { } when (EssentialConfig.currentMultiplayerTab) { - 0 -> { - oldButtons.forEach { removeButton(it) } - oldButtons.clear() - oldButtons.add(favouritesTabButton) - oldButtons.add(friendsTabButton) - oldButtons.add(discoverTabButton) - } 1 -> { removeAllButtons() repositionJoinServerButton(false, "Join Friend") @@ -378,10 +369,7 @@ class EssentialMultiplayerGui { private fun withTosAccepted(block: () -> Unit) { if (!OnboardingData.hasAcceptedTos()) { - GuiUtil.launchModalFlow { - ensurePrerequisites() - block() - } + GuiUtil.pushModal { TOSModal(it, unprompted = false, requiresAuth = true, { block() }) } } else { block() } diff --git a/src/main/kotlin/gg/essential/gui/screenshot/ScreenshotOverlay.kt b/src/main/kotlin/gg/essential/gui/screenshot/ScreenshotOverlay.kt index ed93e016..f21f6f3b 100644 --- a/src/main/kotlin/gg/essential/gui/screenshot/ScreenshotOverlay.kt +++ b/src/main/kotlin/gg/essential/gui/screenshot/ScreenshotOverlay.kt @@ -13,6 +13,7 @@ package gg.essential.gui.screenshot import gg.essential.Essential import gg.essential.config.EssentialConfig +import gg.essential.data.OnboardingData import gg.essential.elementa.UIComponent import gg.essential.elementa.components.UIBlock import gg.essential.elementa.components.UIContainer @@ -54,7 +55,8 @@ import gg.essential.gui.layoutdsl.fillHeight import gg.essential.gui.layoutdsl.fillWidth import gg.essential.gui.layoutdsl.layoutAsColumn import gg.essential.gui.layoutdsl.row -import gg.essential.gui.modals.ensurePrerequisites +import gg.essential.gui.modals.NotAuthenticatedModal +import gg.essential.gui.modals.TOSModal import gg.essential.gui.overlay.Layer import gg.essential.gui.overlay.LayerPriority import gg.essential.gui.screenshot.ScreenshotOverlay.animating @@ -342,8 +344,19 @@ class ScreenshotPreviewToast(val file: File) : ScreenshotToast() { val upload: () -> Unit = { connectionManager.screenshotManager.uploadAndCopyLinkToClipboard(file.toPath()) } - GuiUtil.launchModalFlow { - ensurePrerequisites() + if (!OnboardingData.hasAcceptedTos()) { + GuiUtil.pushModal { manager -> + TOSModal( + manager, + unprompted = false, + requiresAuth = true, + confirmAction = { upload() }, + cancelAction = {}, + ) + } + } else if (!connectionManager.isAuthenticated) { + GuiUtil.pushModal { NotAuthenticatedModal(it) { upload() } } + } else { upload() } } diff --git a/src/main/kotlin/gg/essential/gui/sps/InviteFriendsModal.kt b/src/main/kotlin/gg/essential/gui/sps/InviteFriendsModal.kt index 77df905c..c62337ac 100644 --- a/src/main/kotlin/gg/essential/gui/sps/InviteFriendsModal.kt +++ b/src/main/kotlin/gg/essential/gui/sps/InviteFriendsModal.kt @@ -35,7 +35,8 @@ import gg.essential.gui.modals.select.SelectModal import gg.essential.gui.modals.select.offlinePlayers import gg.essential.gui.modals.select.onlinePlayers import gg.essential.gui.modals.select.selectModal -import gg.essential.gui.notification.sendOutgoingSpsInviteNotification +import gg.essential.gui.notification.Notifications +import gg.essential.gui.notification.iconAndMarkdownBody import gg.essential.gui.overlay.ModalManager import gg.essential.handlers.PauseMenuDisplay import gg.essential.network.connectionmanager.sps.SPSSessionSource @@ -274,7 +275,7 @@ object InviteFriendsModal { callbackAfterOpen = onComplete, )) } else { - PauseMenuDisplay.showInviteOrHostModalInternal( + PauseMenuDisplay.showInviteOrHostModal( source, previousModal = this, worldSummary = worldSummary, @@ -340,11 +341,8 @@ object InviteFriendsModal { return true } - val isServer = getMinecraft().currentServerData != null - val title = if (isServer) "Invite friends to server" else "Invite friends to world" - val modalSimpleName = "InviteFriends" + - if (isServer) "ToServer" else "ToWorld" - return selectModal(modalManager, title, modalSimpleName) { + val title = if (getMinecraft().currentServerData != null) "Invite friends to server" else "Invite friends to world" + return selectModal(modalManager, title) { fun LayoutScope.customPlayerEntry(selected: MutableState, uuid: UUID) { val onlineState = connectionManager.spsManager.getOnlineState(uuid) val reInviteEnabled = getReInviteEnabledState(uuid) @@ -465,7 +463,13 @@ object InviteFriendsModal { } fun sendInviteNotification(uuid: UUID) { - UUIDUtil.getName(uuid).thenAcceptOnMainThread { sendOutgoingSpsInviteNotification(it) } + UUIDUtil.getName(uuid).thenAcceptOnMainThread { sendInviteNotification(it) } + } + + fun sendInviteNotification(name: String) { + Notifications.push("", "") { + iconAndMarkdownBody(EssentialPalette.ENVELOPE_9X7.create(), "${name.colored(EssentialPalette.TEXT_HIGHLIGHT)} invited") + } } private class WorldSetting(text: String, component: UIComponent, tooltip: State? = null) : UIContainer() { diff --git a/src/main/kotlin/gg/essential/gui/sps/WorldSelectionModal.kt b/src/main/kotlin/gg/essential/gui/sps/WorldSelectionModal.kt index a2924d05..ef578dc5 100644 --- a/src/main/kotlin/gg/essential/gui/sps/WorldSelectionModal.kt +++ b/src/main/kotlin/gg/essential/gui/sps/WorldSelectionModal.kt @@ -154,7 +154,7 @@ class WorldSelectionModal(modalManager: ModalManager) : SearchableConfirmDenyMod onPrimaryAction { selectedWorld.get()?.let { world -> - PauseMenuDisplay.showInviteOrHostModalInternal( + PauseMenuDisplay.showInviteOrHostModal( SPSSessionSource.MAIN_MENU, previousModal = this, worldSummary = world, diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/studio/Tag.kt b/src/main/kotlin/gg/essential/gui/studio/Tag.kt similarity index 100% rename from gui/essential/src/main/kotlin/gg/essential/gui/studio/Tag.kt rename to src/main/kotlin/gg/essential/gui/studio/Tag.kt diff --git a/src/main/kotlin/gg/essential/gui/vigilancev2/VigilanceV2SettingsGui.kt b/src/main/kotlin/gg/essential/gui/vigilancev2/VigilanceV2SettingsGui.kt index 4b20ed63..a6a6fc56 100644 --- a/src/main/kotlin/gg/essential/gui/vigilancev2/VigilanceV2SettingsGui.kt +++ b/src/main/kotlin/gg/essential/gui/vigilancev2/VigilanceV2SettingsGui.kt @@ -11,7 +11,6 @@ */ package gg.essential.gui.vigilancev2 -import gg.essential.Essential import gg.essential.data.VersionData import gg.essential.elementa.ElementaVersion import gg.essential.elementa.UIComponent @@ -27,16 +26,13 @@ import gg.essential.gui.elementa.state.v2.* import gg.essential.gui.elementa.state.v2.combinators.map import gg.essential.gui.layoutdsl.* import gg.essential.gui.modals.UpdateAvailableModal -import gg.essential.gui.modals.communityRulesModal import gg.essential.gui.vigilancev2.components.vigilanceCategoryTextColor import gg.essential.gui.vigilancev2.palette.VigilancePalette -import gg.essential.network.connectionmanager.telemetry.FeatureSessionTelemetry import gg.essential.universal.UDesktop import gg.essential.universal.UMinecraft import gg.essential.universal.USound import gg.essential.util.AutoUpdate import gg.essential.util.GuiUtil -import gg.essential.util.GuiUtil.launchModalFlow import gg.essential.util.openInBrowser import gg.essential.vigilance.data.PropertyData import java.awt.Color @@ -53,8 +49,6 @@ class VigilanceV2SettingsGui @JvmOverloads constructor( initialCategory ) - private val reference = ReferenceHolderImpl() - val categories = stateBy { properties() .groupBy { it.attributesExt.category } @@ -110,14 +104,6 @@ class VigilanceV2SettingsGui @JvmOverloads constructor( "Terms of Service" to URI("https://essential.gg/terms-of-use") ).forEach { (text, uri) -> sidebarLink(text, uri) } - val rulesManager = Essential.getInstance().connectionManager.rulesManager - if_(rulesManager.hasRules) { - sidebarElement("Community & Chat Rules") { - launchModalFlow { - communityRulesModal(rulesManager, UMinecraft.getSettings().language, false) - } - } - } }, { listOf( @@ -146,14 +132,6 @@ class VigilanceV2SettingsGui @JvmOverloads constructor( } } } - - var oldCategory: String? = null - effect(reference) { - val newCategory = currentCategoryName().takeIf { screenOpen() } - oldCategory?.let { FeatureSessionTelemetry.endEvent("${this@VigilanceV2SettingsGui::class.qualifiedName}-$it") } - newCategory?.let { FeatureSessionTelemetry.startEvent("${this@VigilanceV2SettingsGui::class.qualifiedName}-$it") } - oldCategory = newCategory - } } private fun LayoutScope.leftTitleBarContent() { @@ -239,17 +217,6 @@ class VigilanceV2SettingsGui @JvmOverloads constructor( } } - private fun LayoutScope.sidebarElement(text: String, onClick: () -> Unit) { - row( - Modifier.hoverScope().onLeftClick { - USound.playButtonPress() - onClick() - } - ) { - text(text, vigilanceCategoryTextColor()) - } - } - override fun updateGuiScale() { newGuiScale = GuiUtil.getGuiScale() super.updateGuiScale() diff --git a/src/main/kotlin/gg/essential/gui/wardrobe/Wardrobe.kt b/src/main/kotlin/gg/essential/gui/wardrobe/Wardrobe.kt index 614a22b9..413cc7dc 100644 --- a/src/main/kotlin/gg/essential/gui/wardrobe/Wardrobe.kt +++ b/src/main/kotlin/gg/essential/gui/wardrobe/Wardrobe.kt @@ -14,7 +14,6 @@ package gg.essential.gui.wardrobe import gg.essential.Essential import gg.essential.api.gui.GuiRequiresTOS import gg.essential.config.EssentialConfig -import gg.essential.connectionmanager.common.packet.telemetry.ClientTelemetryPacket import gg.essential.elementa.ElementaVersion import gg.essential.elementa.UIComponent import gg.essential.elementa.components.ScrollComponent @@ -92,15 +91,6 @@ class Wardrobe( ) init { state.inEmoteWheel.set(initialEmoteWheel) - - var previousColumnCount = -1 - effect(window) { - val columnCount = state.featuredPageLayout().second?.key ?: return@effect - if (previousColumnCount != columnCount) { - connectionManager.telemetryManager.enqueue(ClientTelemetryPacket("WARDROBE_LAYOUT", mapOf("columnCount" to columnCount))) - previousColumnCount = columnCount - } - } } private val searchbar = EssentialCollapsibleSearchbar(activateOnType = false).apply { diff --git a/src/main/kotlin/gg/essential/handlers/MinecraftGameProfileTexturesRefresher.kt b/src/main/kotlin/gg/essential/handlers/MinecraftGameProfileTexturesRefresher.kt deleted file mode 100644 index 7a058e0e..00000000 --- a/src/main/kotlin/gg/essential/handlers/MinecraftGameProfileTexturesRefresher.kt +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.handlers - -import com.mojang.authlib.properties.Property -import gg.essential.mixins.ext.server.dispatcher -import gg.essential.universal.UMinecraft -import gg.essential.util.Client -import gg.essential.util.JsonHolder -import gg.essential.util.USession -import gg.essential.util.httpGetToString -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.withContext -import net.minecraft.client.Minecraft -import org.slf4j.LoggerFactory -import java.io.IOException -import java.util.Base64 -import java.util.Locale -import java.util.UUID -import kotlin.coroutines.EmptyCoroutineContext - -//#if MC>=12002 -//$$ import com.google.common.collect.LinkedHashMultimap -//$$ import com.mojang.authlib.yggdrasil.ProfileResult -//$$ import gg.essential.mixins.transformers.feature.skin_overwrites.MinecraftAccessor -//$$ import com.mojang.authlib.GameProfile -//$$ import java.util.concurrent.CompletableFuture -//#endif - -//#if MC>=12109 -//$$ import com.mojang.authlib.properties.PropertyMap -//$$ import gg.essential.mixins.transformers.feature.skin_overwrites.PlayerEntityAccessor -//#endif - -object MinecraftGameProfileTexturesRefresher { - private val LOGGER = LoggerFactory.getLogger(MinecraftGameProfileTexturesRefresher::class.java) - const val SESSION_URL: String = "https://sessionserver.mojang.com/session/minecraft/profile/%s?unsigned=false" - - fun updateTextures(newHash: String?, textureType: String) { - runBlocking(if (!UMinecraft.isCallingFromMinecraftThread()) Dispatchers.Client else EmptyCoroutineContext) { - val property = createTextureProperty(USession.activeNow().uuid, newHash ?: "") { base64 -> getTextureValue(base64, textureType) } - if (property != null) { - updateClientTextures(property) - updateIntegratedServerTextures(property) - } - } - } - - private fun updateClientTextures(property: Property) { - //#if MC>=12002 - //$$ val profile = MinecraftClient.getInstance().gameProfile - //$$ val propertyMap = LinkedHashMultimap.create(profile.properties) - //$$ propertyMap.removeAll("textures") - //$$ propertyMap.put("textures", property) - //#if MC>=12109 - //$$ val newProfile = GameProfile(profile.id, profile.name, PropertyMap(propertyMap)) - //#else - //$$ val newProfile = GameProfile(profile.id, profile.name) - //$$ profile.properties.putAll(propertyMap) - //#endif - //$$ (MinecraftClient.getInstance() as MinecraftAccessor).setGameProfileFuture( - //$$ CompletableFuture.completedFuture(ProfileResult(newProfile, setOf())) - //$$ ) - //#else - val propertyMap = Minecraft.getMinecraft().profileProperties - propertyMap.removeAll("textures") - propertyMap.put("textures", property) - //#endif - } - - private suspend fun createTextureProperty(uuid: UUID, newHash: String, getHashValue: (String) -> String): Property? { - var propertyValues = getTextureProperty(uuid) - val maxDelay = 60000L - var delay = 500L - // If the hash of the texture property from Mojang doesn't match, it means the API instance hasn't updated yet - // See: EM-2640 - while (propertyValues == null || getHashValue(propertyValues.first) != newHash) { - delay *= 2 - if (delay >= maxDelay) { - LOGGER.warn("Unable to get new cape property") - return null - } - delay(delay) - propertyValues = getTextureProperty(uuid) - } - return Property("textures", propertyValues.first, propertyValues.second) - } - - private fun getTextureValue(base64: String, value: String): String { - val skinHolder = JsonHolder(String(Base64.getDecoder().decode(base64))) - .optJSONObject("textures") - .optJSONObject(value) - return skinHolder.optString("url").substringAfterLast('/') - } - - private suspend fun getTextureProperty(uuid: UUID): Pair? { - try { - val jsonHolder = JsonHolder( - httpGetToString( - String.format( - Locale.ROOT, - SESSION_URL, - uuid.toString().replace("-", "") - ) - ) - ) - if (jsonHolder.keys.isEmpty()) { - return null - } - - val properties = jsonHolder.optJSONArray("properties")[0].asJsonObject - return Pair( - properties["value"].asString, - properties["signature"].asString - ) - } catch (e: IOException) { - LOGGER.error("Failed to fetch texture property", e) - return null - } - } - - private suspend fun updateIntegratedServerTextures(property: Property) { - Minecraft.getMinecraft().integratedServer?.let { integratedServer -> - val uuid = USession.activeNow().uuid - withContext(integratedServer.dispatcher) { - val player = integratedServer.playerList.players.find { it.uniqueID == uuid } - //#if MC>=12109 - //$$ player?.gameProfile?.let { profile -> - //$$ val propertyMap = LinkedHashMultimap.create(profile.properties()) - //$$ propertyMap.removeAll("textures") - //$$ propertyMap.put("textures", property) - //$$ (player as? PlayerEntityAccessor)?.setGameProfile(GameProfile(profile.id, profile.name, PropertyMap(propertyMap))) - //$$ } - //#else - player?.gameProfile?.properties?.run { - removeAll("textures") - put("textures", property) - } - //#endif - } - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/gg/essential/handlers/PauseMenuDisplay.kt b/src/main/kotlin/gg/essential/handlers/PauseMenuDisplay.kt index 0b9afdab..5ba62c67 100644 --- a/src/main/kotlin/gg/essential/handlers/PauseMenuDisplay.kt +++ b/src/main/kotlin/gg/essential/handlers/PauseMenuDisplay.kt @@ -59,7 +59,6 @@ import gg.essential.gui.elementa.state.v2.stateOf import gg.essential.gui.elementa.state.v2.toV2 import gg.essential.gui.layoutdsl.Alignment import gg.essential.gui.layoutdsl.Arrangement -import gg.essential.gui.layoutdsl.LayoutScope import gg.essential.gui.layoutdsl.Modifier import gg.essential.gui.layoutdsl.alignBoth import gg.essential.gui.layoutdsl.alignVertical @@ -84,7 +83,6 @@ import gg.essential.gui.modals.NotAuthenticatedModal import gg.essential.gui.modals.TOSModal import gg.essential.gui.modals.UpdateAvailableModal import gg.essential.gui.modals.UpdateNotificationModal -import gg.essential.gui.modals.ensurePrerequisites import gg.essential.gui.notification.Notifications import gg.essential.gui.notification.error import gg.essential.gui.notification.toastButton @@ -92,7 +90,6 @@ import gg.essential.gui.notification.warning import gg.essential.gui.overlay.Layer import gg.essential.gui.overlay.LayerPriority import gg.essential.gui.overlay.ModalManager -import gg.essential.gui.overlay.launchModalFlow import gg.essential.gui.sps.InviteFriendsModal import gg.essential.gui.sps.WorldSelectionModal import gg.essential.gui.util.addTag @@ -103,7 +100,6 @@ import gg.essential.util.findButtonByLabel import gg.essential.gui.util.pollingState import gg.essential.network.connectionmanager.serverdiscovery.NewServerDiscoveryManager import gg.essential.network.connectionmanager.sps.SPSSessionSource -import gg.essential.network.connectionmanager.suspension.suspensionModal import gg.essential.sps.SpsAddress import gg.essential.universal.USound import gg.essential.util.FirewallUtil @@ -302,15 +298,6 @@ class PauseMenuDisplay { AutoUpdate.seenUpdateToast = true } - // Suspension modal - Essential.getInstance().connectionManager.suspensionManager.activeSuspension.getUntracked()?.let { suspension -> - if (suspension.unseen) { - GuiUtil.launchModalFlow { - suspensionModal(suspension) - } - } - } - // Update Notification Modal if (VersionData.getMajorComponents(VersionData.essentialVersion) != VersionData.getMajorComponents(VersionData.getLastSeenModal()) && EssentialConfig.updateModal @@ -407,43 +394,21 @@ class PauseMenuDisplay { source: SPSSessionSource, prepopulatedInvites: Set = emptySet(), worldSummary: WorldSummary? = null, - showIPWarning: Boolean = true, - callback: () -> Unit = {}, - ) { - GuiUtil.launchModalFlow { - ensurePrerequisites(social = true, rules = false) - - awaitModal { - object : Modal(modalManager) { - override fun onOpen() { - super.onOpen() - showInviteOrHostModalInternal(source, prepopulatedInvites, worldSummary, this, showIPWarning, callback) - } - - override val modalName: String? get() = null - override fun LayoutScope.layoutModal() {} - override fun handleEscapeKeyPress() {} - } - } - } - } - - fun showInviteOrHostModalInternal( - source: SPSSessionSource, - prepopulatedInvites: Set = emptySet(), - worldSummary: WorldSummary? = null, - previousModal: Modal, + previousModal: Modal? = null, showIPWarning: Boolean = true, callback: () -> Unit = {}, ) { val connectionManager = Essential.getInstance().connectionManager - val currentServerData = UMinecraft.getMinecraft().currentServerData val spsManager = connectionManager.spsManager // Attempts to replace the previously opened modal, or, push a new modal if one is not open. fun pushModal(builder: (ModalManager) -> Modal) { - previousModal.replaceWith(builder(previousModal.modalManager)) + if (previousModal != null) { + previousModal.replaceWith(builder(previousModal.modalManager)) + } else { + GuiUtil.pushModal(builder) + } } // Attempts to show the user various warnings (TOS, Connection Manager, Firewall, etc.) before pushing @@ -453,7 +418,7 @@ class PauseMenuDisplay { builder: (ModalManager) -> Modal ) { fun Modal.retryModal(showIPWarningOverride: Boolean = showIPWarning) { - showInviteOrHostModalInternal( + showInviteOrHostModal( source, prepopulatedInvites, worldSummary, @@ -564,7 +529,6 @@ class PauseMenuDisplay { } else { // Realms, ReplayMod, etc. Notifications.error("Can't invite to this world", "") - previousModal.close() } } diff --git a/src/main/kotlin/gg/essential/network/connectionmanager/ConnectionManagerKt.kt b/src/main/kotlin/gg/essential/network/connectionmanager/ConnectionManagerKt.kt index 05bb34c8..7bf633f0 100644 --- a/src/main/kotlin/gg/essential/network/connectionmanager/ConnectionManagerKt.kt +++ b/src/main/kotlin/gg/essential/network/connectionmanager/ConnectionManagerKt.kt @@ -117,8 +117,8 @@ abstract class ConnectionManagerKt : CMConnection { val connectBackoff = ExponentialBackoff(2.seconds, 1.minutes, 2.0) val unexpectedCloseBackoff = ExponentialBackoff(10.seconds, 2.minutes, 2.0) - var awaitSessionChange = false - while (true) { + var exit = false + while (!exit) { updateStatus(null) if (!OnboardingData.hasAcceptedTos()) { @@ -211,12 +211,6 @@ abstract class ConnectionManagerKt : CMConnection { delay(delay.inWholeMilliseconds) continue } - is ConnectResult.Suspended -> { - handleSuspension(result.info) - // If the account in use changes, we can try to connect again - USession.active.await { it != session } - continue - } ConnectResult.Connected -> {} } connectBackoff.reset() @@ -239,14 +233,9 @@ abstract class ConnectionManagerKt : CMConnection { select { wrapper.onClose { info -> - if (info.knownReason == KnownCloseReason.SUSPENDED) { - handleSuspension(info) - awaitSessionChange = true - } else { - val duration = JavaDuration.between(connectedAt, Instant.now()).toKotlinDuration() - fastUnexpectedClose = duration < 2.minutes - LOGGER.warn("Connection closed unexpectedly ({}) after {}", info, duration) - } + val duration = JavaDuration.between(connectedAt, Instant.now()).toKotlinDuration() + fastUnexpectedClose = duration < 2.minutes + LOGGER.warn("Connection closed unexpectedly ({}) after {}", info, duration) } async { USession.active.await { it.uuid != session.uuid } }.onAwait { newSession -> val duration = JavaDuration.between(connectedAt, Instant.now()).toKotlinDuration() @@ -283,11 +272,6 @@ abstract class ConnectionManagerKt : CMConnection { } else { unexpectedCloseBackoff.reset() } - - if (awaitSessionChange) { - USession.active.await { it != session } - awaitSessionChange = false - } } } @@ -297,19 +281,10 @@ abstract class ConnectionManagerKt : CMConnection { } } - private suspend fun handleSuspension(result: CloseInfo) { - LOGGER.error("User is permanently suspended. Will no longer try to connect for this session.") - updateStatus(Status.USER_SUSPENDED) - withContext(Dispatchers.Client) { - java.suspensionManager.setPermanentlySuspended(result.reason) - } - } - private sealed interface ConnectResult { object Connected : ConnectResult object Outdated : ConnectResult data class Failed(val info: CloseInfo) : ConnectResult - data class Suspended(val info: CloseInfo) : ConnectResult } private class ConnectionWrapper : Connection.Callbacks, Closeable { @@ -344,7 +319,6 @@ abstract class ConnectionManagerKt : CMConnection { closeChannel.onReceive { info -> when (info.knownReason) { KnownCloseReason.OUTDATED -> ConnectResult.Outdated - KnownCloseReason.SUSPENDED -> ConnectResult.Suspended(info) null -> ConnectResult.Failed(info) else -> throw AssertionError() // FIXME: Workaround for compiler bug fixed in Kotlin 2.0 } diff --git a/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/notices/SocialMenuNewFriendRequestNoticeManager.kt b/src/main/kotlin/gg/essential/network/connectionmanager/notices/SocialMenuNewFriendRequestNoticeManager.kt similarity index 100% rename from gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/notices/SocialMenuNewFriendRequestNoticeManager.kt rename to src/main/kotlin/gg/essential/network/connectionmanager/notices/SocialMenuNewFriendRequestNoticeManager.kt diff --git a/src/main/kotlin/gg/essential/network/connectionmanager/profile/SuspensionDisconnectHandler.kt b/src/main/kotlin/gg/essential/network/connectionmanager/profile/SuspensionDisconnectHandler.kt deleted file mode 100644 index e538a76b..00000000 --- a/src/main/kotlin/gg/essential/network/connectionmanager/profile/SuspensionDisconnectHandler.kt +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.network.connectionmanager.profile - -import gg.essential.Essential -import gg.essential.event.gui.GuiOpenEvent -import gg.essential.gui.elementa.state.v2.ReferenceHolderImpl -import gg.essential.gui.elementa.state.v2.effect -import gg.essential.gui.modals.DisconnectModal -import gg.essential.mixins.ext.server.dispatcher -import gg.essential.mixins.transformers.client.gui.GuiDisconnectedAccessor -import gg.essential.network.connectionmanager.ConnectionManager -import gg.essential.universal.UMinecraft -import gg.essential.util.GuiUtil.pushModal -import gg.essential.util.ServerType -import gg.essential.util.UUIDUtil -import gg.essential.util.textLiteral -import me.kbrewster.eventbus.Subscribe -import net.minecraft.client.gui.GuiDisconnected -import net.minecraft.client.gui.GuiMainMenu -import net.minecraft.entity.player.EntityPlayerMP -import java.util.* -import kotlin.coroutines.EmptyCoroutineContext - -object SuspensionDisconnectHandler { - - private val referenceHolder = ReferenceHolderImpl() - - private const val SELF_SUSPENSION_STRING = "essential:self_suspension" - private val selfSuspensionComponent = textLiteral(SELF_SUSPENSION_STRING) - - private const val HOST_SUSPENSION_STRING = "essential:host_suspension" - private val hostSuspensionComponent = textLiteral(HOST_SUSPENSION_STRING) - - fun setupEffects(connectionManager: ConnectionManager) { - Essential.EVENT_BUS.register(this) - - effect(referenceHolder) { - val suspension = connectionManager.suspensionManager.activeSuspension() - if (suspension != null && suspension.isActiveNow()) { - handleSelfSuspension() - } - } - - effect(referenceHolder) { - val suspensions = connectionManager.profileManager.suspensions() - for (suspension in suspensions) { - if (suspension.isActiveNow()) { - handleSuspension(suspension.user) - } - } - } - } - - @Subscribe fun onGuiOpened(event: GuiOpenEvent) { - if (event.gui is GuiDisconnected) { - val reason: String = getDisconnectReason(event.gui as GuiDisconnectedAccessor) - val hostSuspension = HOST_SUSPENSION_STRING == reason - val selfSuspension = SELF_SUSPENSION_STRING == reason - - if (hostSuspension || selfSuspension) { - event.isCancelled = true - UMinecraft.getMinecraft().displayGuiScreen(GuiMainMenu()) - - if (hostSuspension) { - pushModal { - DisconnectModal(it, DisconnectModal.HOST_SUSPENDED) - } - } - } - } - } - - private fun handleSelfSuspension() { - handleSuspension(UUIDUtil.getClientUUID()) - } - - private fun handleSuspension(uuid: UUID) { - val self = UUIDUtil.getClientUUID() == uuid - when (val serverType = ServerType.current()) { - is ServerType.SPS.Host -> { - val server = UMinecraft.getMinecraft().integratedServer - server?.dispatcher?.dispatch(EmptyCoroutineContext) { - if (self) { - var selfPlayer: EntityPlayerMP? = null - server.playerList.players.toList().forEach { player -> - if (player.uniqueID == uuid) { - selfPlayer = player - } else { - kickPlayer(player, false) - } - } - selfPlayer?.let { kickPlayer(it, true) } - } else { - server.playerList.players.find { it.uniqueID == uuid }?.let { kickPlayer(it, true) } - } - } - } - is ServerType.SPS.Guest -> { - val localPlayer = UMinecraft.getPlayer() - if (localPlayer != null) { - if (self) { - localPlayer.connection.networkManager.closeChannel(selfSuspensionComponent) - } else if (serverType.hostUuid == uuid) { - localPlayer.connection.networkManager.closeChannel(hostSuspensionComponent) - } - } - } - else -> {} - } - } - - private fun kickPlayer(player: EntityPlayerMP, self: Boolean) { - val connection = player.connection - //#if MC<=10809 - //$$ connection.kickPlayerFromServer(if (self) SELF_SUSPENSION_STRING else HOST_SUSPENSION_STRING) - //#else - connection.disconnect(if (self) selfSuspensionComponent else hostSuspensionComponent) - //#endif - } - - private fun getDisconnectReason(accessor: GuiDisconnectedAccessor): String = - //#if MC>=12100 - //$$ accessor.info.reason.string - //#elseif MC>=11600 - //$$ accessor.message.string - //#else - accessor.message.unformattedText - //#endif -} \ No newline at end of file diff --git a/src/main/kotlin/gg/essential/network/connectionmanager/profile/profileManager.kt b/src/main/kotlin/gg/essential/network/connectionmanager/profile/profileManager.kt deleted file mode 100644 index f8517cba..00000000 --- a/src/main/kotlin/gg/essential/network/connectionmanager/profile/profileManager.kt +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.network.connectionmanager.profile - -import gg.essential.gui.elementa.state.v2.ListState -import gg.essential.gui.elementa.state.v2.MutableState -import gg.essential.gui.elementa.state.v2.collections.MutableTrackedList -import gg.essential.gui.elementa.state.v2.mapList -import gg.essential.gui.elementa.state.v2.withSystemTime -import gg.essential.network.connectionmanager.social.ProfileSuspension - -fun filterActiveSuspensions(suspensions: MutableState>): ListState = - suspensions.mapList { list -> withSystemTime { now -> list.filter { suspension -> suspension.isActive(now) } } } \ No newline at end of file diff --git a/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/relationship/relationshipErrorResponse.kt b/src/main/kotlin/gg/essential/network/connectionmanager/relationship/relationshipErrorResponse.kt similarity index 88% rename from gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/relationship/relationshipErrorResponse.kt rename to src/main/kotlin/gg/essential/network/connectionmanager/relationship/relationshipErrorResponse.kt index 13d25865..e2adcd86 100644 --- a/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/relationship/relationshipErrorResponse.kt +++ b/src/main/kotlin/gg/essential/network/connectionmanager/relationship/relationshipErrorResponse.kt @@ -17,12 +17,12 @@ import gg.essential.gui.notification.error import gg.essential.gui.notification.markdownBody import gg.essential.gui.notification.warning import gg.essential.network.connectionmanager.relationship.RelationshipErrorResponse.* -import gg.essential.universal.UI18n import gg.essential.util.colored +import net.minecraft.client.resources.I18n import java.util.UUID val RelationshipErrorResponse.message: String - get() = UI18n.i18n("connectionmanager.friends." + name) + get() = I18n.format("connectionmanager.friends." + name) fun RelationshipErrorResponse.showToast(uuid: UUID, name: String) = when (this) { EXISTING_REQUEST_IS_PENDING -> @@ -78,13 +78,5 @@ fun RelationshipErrorResponse.showToast(uuid: UUID, name: String) = when (this) Notifications.error("Error", "") { markdownBody("${name.colored(EssentialPalette.TEXT_HIGHLIGHT)} does not exist.") } - USER_IS_PERMANENTLY_SUSPENDED -> - Notifications.error("Friend request failed", "") { - markdownBody("${name.colored(EssentialPalette.TEXT_HIGHLIGHT)} is suspended.") - } - USER_IS_TEMPORARILY_SUSPENDED -> - Notifications.error("Friend request failed", "") { - markdownBody("${name.colored(EssentialPalette.TEXT_HIGHLIGHT)} is temporarily suspended.") - } else -> throw AssertionError() // FIXME: Workaround for compiler bug fixed in Kotlin 2.0 } diff --git a/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/relationship/relationshipResponse.kt b/src/main/kotlin/gg/essential/network/connectionmanager/relationship/relationshipResponse.kt similarity index 89% rename from gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/relationship/relationshipResponse.kt rename to src/main/kotlin/gg/essential/network/connectionmanager/relationship/relationshipResponse.kt index 756859cf..923a202f 100644 --- a/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/relationship/relationshipResponse.kt +++ b/src/main/kotlin/gg/essential/network/connectionmanager/relationship/relationshipResponse.kt @@ -11,14 +11,14 @@ */ package gg.essential.network.connectionmanager.relationship -import gg.essential.util.UuidNameLookup +import gg.essential.util.UUIDUtil import java.util.* val RelationshipResponse.message: String? get() = rawMessage ?: relationshipErrorResponse?.message fun RelationshipResponse.displayToast(uuid: UUID) { - UuidNameLookup.getName(uuid).thenAccept { username -> + UUIDUtil.getName(uuid).thenAccept { username -> this.relationshipErrorResponse?.showToast( uuid, username diff --git a/src/main/kotlin/gg/essential/network/connectionmanager/suspension/McSuspensionManager.kt b/src/main/kotlin/gg/essential/network/connectionmanager/suspension/McSuspensionManager.kt deleted file mode 100644 index 75a07d42..00000000 --- a/src/main/kotlin/gg/essential/network/connectionmanager/suspension/McSuspensionManager.kt +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.network.connectionmanager.suspension - -import gg.essential.Essential -import gg.essential.gui.InternalEssentialGUI -import gg.essential.gui.modals.SuspensionModal -import gg.essential.gui.overlay.ModalFlow -import gg.essential.network.connectionmanager.ConnectionManager -import gg.essential.network.connectionmanager.social.Suspension -import gg.essential.universal.UMinecraft -import gg.essential.util.GuiUtil -import gg.essential.util.isMainMenu -import net.minecraft.client.gui.GuiIngameMenu - -class McSuspensionManager(connectionManager: ConnectionManager) : SuspensionManager(connectionManager) { - - init { - Essential.EVENT_BUS.register(this) - } - - override fun isSuspensionShowable(): Boolean { - val openedScreen = GuiUtil.openedScreen() - return openedScreen is InternalEssentialGUI || openedScreen.isMainMenu || openedScreen is GuiIngameMenu - } - - override fun showSuspension(suspension: Suspension) { - GuiUtil.launchModalFlow { - suspensionModal(suspension) - } - } -} - -suspend fun ModalFlow.suspensionModal(suspension: Suspension) { - val connectionManager = Essential.getInstance().connectionManager - val suspensionManager = connectionManager.suspensionManager - awaitModal { continuation -> - suspensionManager.markSeen() - SuspensionModal( - modalManager, - suspension, - connectionManager.chatManager.getReportReasons(UMinecraft.getSettings().language), - continuation - ) - } -} \ No newline at end of file diff --git a/src/main/kotlin/gg/essential/network/connectionmanager/telemetry/FPSSessionMonitor.kt b/src/main/kotlin/gg/essential/network/connectionmanager/telemetry/FPSSessionMonitor.kt deleted file mode 100644 index 1a402bba..00000000 --- a/src/main/kotlin/gg/essential/network/connectionmanager/telemetry/FPSSessionMonitor.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.network.connectionmanager.telemetry - -class FPSSessionMonitor { - private var startTime = 0L - private var frameCounter = 0L - - fun frame() { - if (startTime == 0L) { - startTime = System.currentTimeMillis() - } else { - frameCounter++ - } - } - - fun getAverageFPS(): Float { - val endTime = System.currentTimeMillis() - return (frameCounter.toDouble() / ((endTime - startTime).toDouble() / 1000.0)).toFloat() - } -} \ No newline at end of file diff --git a/src/main/kotlin/gg/essential/network/connectionmanager/telemetry/FPSTelemetryManager.kt b/src/main/kotlin/gg/essential/network/connectionmanager/telemetry/FPSTelemetryManager.kt deleted file mode 100644 index b1c489f5..00000000 --- a/src/main/kotlin/gg/essential/network/connectionmanager/telemetry/FPSTelemetryManager.kt +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.network.connectionmanager.telemetry - -import gg.essential.Essential -import gg.essential.connectionmanager.common.packet.telemetry.ClientTelemetryPacket -import gg.essential.event.network.server.ServerJoinEvent -import gg.essential.event.network.server.ServerLeaveEvent -import gg.essential.event.network.server.SingleplayerJoinEvent -import gg.essential.event.render.RenderTickEvent -import me.kbrewster.eventbus.Subscribe - -object FPSTelemetryManager { - private val telemetryManager = Essential.getInstance().connectionManager.telemetryManager - private var fpsSessionMonitor: FPSSessionMonitor? = null - - fun initialize() { - Essential.EVENT_BUS.register(this) - } - - private fun startSession() { - fpsSessionMonitor = FPSSessionMonitor() - } - - private fun endSession() { - val fps = fpsSessionMonitor?.getAverageFPS() - if (fps != null) { - telemetryManager.enqueue(ClientTelemetryPacket("SESSION_FPS", mapOf("averageFPS" to fps))) - } - fpsSessionMonitor = null - } - - @Subscribe - fun onServerJoinEvent(event: ServerJoinEvent) { - startSession() - } - - @Subscribe - fun onSingleplayerJoinEvent(event: SingleplayerJoinEvent) { - startSession() - } - - @Subscribe - fun onServerLeaveEvent(event: ServerLeaveEvent) { - endSession() - } - - @Subscribe - fun onRenderTickEvent(event: RenderTickEvent) { - if (event.isPre) { - return - } - fpsSessionMonitor?.frame() - } - -} \ No newline at end of file diff --git a/src/main/kotlin/gg/essential/network/cosmetics/cape/CapeCosmeticsManager.kt b/src/main/kotlin/gg/essential/network/cosmetics/cape/CapeCosmeticsManager.kt index 02eb2047..241c8d77 100644 --- a/src/main/kotlin/gg/essential/network/cosmetics/cape/CapeCosmeticsManager.kt +++ b/src/main/kotlin/gg/essential/network/cosmetics/cape/CapeCosmeticsManager.kt @@ -16,9 +16,8 @@ import gg.essential.connectionmanager.common.packet.cosmetic.ClientCosmeticCheck import gg.essential.connectionmanager.common.packet.cosmetic.ServerCosmeticsUserUnlockedPacket import gg.essential.connectionmanager.common.packet.cosmetic.capes.ClientCosmeticCapesUnlockedPacket import gg.essential.connectionmanager.common.packet.response.ResponseActionPacket -import gg.essential.handlers.MinecraftGameProfileTexturesRefresher -import gg.essential.mod.cosmetics.CAPE_DISABLED_COSMETIC_ID import gg.essential.mod.cosmetics.CosmeticSlot +import gg.essential.mod.cosmetics.CAPE_DISABLED_COSMETIC_ID import gg.essential.network.connectionmanager.ConnectionManager import gg.essential.network.connectionmanager.cosmetics.CosmeticsManager import gg.essential.util.Multithreading @@ -26,7 +25,6 @@ import net.minecraft.client.Minecraft import net.minecraft.entity.player.EnumPlayerModelParts import java.util.concurrent.Semaphore - class CapeCosmeticsManager( private val connectionManager: ConnectionManager, private val cosmeticsManager: CosmeticsManager, @@ -78,7 +76,6 @@ class CapeCosmeticsManager( MojangCapeApi.putCape(cape?.id) Essential.logger.info("Updated Mojang cape to \"${cape?.name ?: ""}\"") - MinecraftGameProfileTexturesRefresher.updateTextures(activeCape, "CAPE") } catch (e: Throwable) { Essential.logger.error("Error enabling cape $cape at Mojang:", e) } finally { diff --git a/src/main/kotlin/gg/essential/network/pingproxy/SPSUtils.kt b/src/main/kotlin/gg/essential/network/pingproxy/SPSUtils.kt new file mode 100644 index 00000000..40e717b0 --- /dev/null +++ b/src/main/kotlin/gg/essential/network/pingproxy/SPSUtils.kt @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.network.pingproxy + +import com.google.gson.JsonParser +import gg.essential.Essential +import gg.essential.connectionmanager.common.packet.Packet +import gg.essential.connectionmanager.common.packet.pingproxy.ClientPingProxyPacket +import gg.essential.connectionmanager.common.packet.pingproxy.ServerPingProxyResponsePacket +import gg.essential.gui.elementa.state.v2.State +import gg.essential.gui.elementa.state.v2.mutableStateOf +import gg.essential.sps.SpsAddress +import gg.essential.util.MinecraftUtils +import java.util.* + +fun fetchWorldNameFromSPSHost(uuid: UUID): State { + val connectionManager = Essential.getInstance().connectionManager + val address = SpsAddress(uuid).toString() + + val worldName = mutableStateOf(null) + connectionManager.send(ClientPingProxyPacket(address, 25565, MinecraftUtils.currentProtocolVersion)) { maybePacket: Optional -> + val packet = maybePacket.orElse(null) as? ServerPingProxyResponsePacket ?: return@send + + try { + val json = JsonParser().parse(packet.rawJson).asJsonObject + worldName.set(json.get("description").asJsonObject.get("text").asString.split(" - ").drop(1).joinToString(" - ")) + } catch (exception: Exception) { + Essential.logger.warn("Failed to parse server info", exception) + } + } + + return worldName +} diff --git a/gui/essential/src/main/kotlin/gg/essential/util/AddressUtil.kt b/src/main/kotlin/gg/essential/util/AddressUtil.kt similarity index 77% rename from gui/essential/src/main/kotlin/gg/essential/util/AddressUtil.kt rename to src/main/kotlin/gg/essential/util/AddressUtil.kt index 22a9a23e..88835d18 100644 --- a/gui/essential/src/main/kotlin/gg/essential/util/AddressUtil.kt +++ b/src/main/kotlin/gg/essential/util/AddressUtil.kt @@ -11,7 +11,8 @@ */ package gg.essential.util -import gg.essential.util.GuiEssentialPlatform.Companion.platform +import com.google.common.net.HostAndPort +import com.google.common.net.InetAddresses object AddressUtil { @JvmStatic @@ -21,19 +22,17 @@ object AddressUtil { @JvmStatic fun isLanOrLocalAddress(address: String): Boolean { - val host = platform.splitHostAndPort(address).first + val host = HostAndPort.fromString(address).host if (host.equals("localhost", ignoreCase = true)) { return true } - - // Note: Not using INetAddress.getByName because we want to avoid DNS lookups for non-IPs - platform.parseIpAddress(host)?.let { inetAddress -> - if (inetAddress.isSiteLocalAddress || inetAddress.isLoopbackAddress) { - return true + try { + // Guava INetAddresses avoids DNS lookups + InetAddresses.forString(host).let { inetAddress -> + return inetAddress.isSiteLocalAddress || inetAddress.isLoopbackAddress } - } - + } catch (ignored: IllegalArgumentException) { } return false } diff --git a/src/main/kotlin/gg/essential/util/GuiEssentialPlatformImpl.kt b/src/main/kotlin/gg/essential/util/GuiEssentialPlatformImpl.kt index 5299c3b6..b5a3fac9 100644 --- a/src/main/kotlin/gg/essential/util/GuiEssentialPlatformImpl.kt +++ b/src/main/kotlin/gg/essential/util/GuiEssentialPlatformImpl.kt @@ -11,8 +11,6 @@ */ package gg.essential.util -import com.google.common.net.HostAndPort -import com.google.common.net.InetAddresses import com.mojang.authlib.GameProfile import gg.essential.Essential import gg.essential.api.profile.wrapped @@ -26,7 +24,6 @@ import gg.essential.gui.common.EmulatedUI3DPlayer import gg.essential.gui.common.UIPlayer import gg.essential.gui.common.modal.Modal import gg.essential.gui.elementa.essentialmarkdown.EssentialMarkdown -import gg.essential.gui.elementa.state.v2.ListState import gg.essential.gui.elementa.state.v2.MutableState import gg.essential.gui.elementa.state.v2.ReferenceHolderImpl import gg.essential.gui.elementa.state.v2.State @@ -44,7 +41,6 @@ import gg.essential.gui.friends.state.MessengerStateManagerImpl import gg.essential.gui.friends.state.RelationshipStateManagerImpl import gg.essential.gui.friends.state.SocialStates import gg.essential.gui.friends.state.StatusStateManagerImpl -import gg.essential.gui.modals.McModalPrerequisites import gg.essential.gui.notification.NotificationsImpl import gg.essential.gui.notification.NotificationsManager import gg.essential.gui.overlay.ModalManager @@ -52,7 +48,6 @@ import gg.essential.gui.overlay.ModalManagerImpl import gg.essential.gui.overlay.OverlayManager import gg.essential.gui.overlay.OverlayManagerImpl import gg.essential.gui.screenshot.bytebuf.LimitedAllocator -import gg.essential.gui.screenshot.components.ScreenshotBrowser import gg.essential.gui.screenshot.providers.MinecraftWindowedTextureProvider import gg.essential.gui.screenshot.providers.WindowedImageProvider import gg.essential.gui.screenshot.providers.WindowedTextureProvider @@ -72,20 +67,15 @@ import gg.essential.model.backend.minecraft.MinecraftRenderBackend import gg.essential.model.util.Color import gg.essential.network.CMConnection import gg.essential.network.connectionmanager.cosmetics.AssetLoader -import gg.essential.network.connectionmanager.cosmetics.ICosmeticsManager import gg.essential.network.connectionmanager.cosmetics.ModelLoader -import gg.essential.network.connectionmanager.cosmetics.WardrobeSettings import gg.essential.network.connectionmanager.features.DisabledFeaturesManager import gg.essential.network.connectionmanager.media.IScreenshotManager import gg.essential.network.connectionmanager.notices.INoticesManager import gg.essential.network.connectionmanager.skins.SkinsManager -import gg.essential.network.connectionmanager.social.ProfileSuspension import gg.essential.sps.SpsAddress import gg.essential.universal.UGraphics import gg.essential.universal.UImage -import gg.essential.universal.UMinecraft import gg.essential.universal.UScreen -import gg.essential.universal.USound import gg.essential.universal.utils.ReleasedDynamicTexture import gg.essential.util.image.GpuTexture import gg.essential.util.image.bitmap.Bitmap @@ -99,14 +89,10 @@ import net.minecraft.client.Minecraft import org.lwjgl.opengl.GL11.GL_ONE_MINUS_SRC_ALPHA import org.lwjgl.opengl.GL11.GL_SRC_ALPHA import org.lwjgl.opengl.GL13.GL_TEXTURE0 -import java.awt.image.BufferedImage import java.io.IOException import java.io.InputStream -import java.net.InetAddress import java.nio.file.Path import java.util.* -import javax.imageio.ImageIO -import kotlin.io.path.isRegularFile import kotlin.jvm.Throws //#if MC>=12106 @@ -118,12 +104,6 @@ import kotlin.jvm.Throws //$$ import com.mojang.blaze3d.opengl.GlStateManager //#endif -//#if MC>=11200 -import net.minecraft.init.SoundEvents -//#else -//$$ import net.minecraft.util.ResourceLocation -//#endif - @AccessedViaReflection("GuiEssentialPlatform") class GuiEssentialPlatformImpl : GuiEssentialPlatform { override val mcVersion: Int @@ -216,14 +196,6 @@ class GuiEssentialPlatformImpl : GuiEssentialPlatform { EssentialSoundManager.playSound(identifier.toMC()) } - override fun playNoteHatSound(volume: Float, pitch: Float) { - //#if MC>=11200 - USound.playSoundStatic(SoundEvents.BLOCK_NOTE_HAT, .25f, 0.75f) - //#else - //$$ USound.playSoundStatic(ResourceLocation("note.hat"), .25f, 0.75f) - //#endif - } - override fun registerCosmeticTexture(name: String, texture: ReleasedDynamicTexture): UIdentifier { return MinecraftRenderBackend.CosmeticTexture(name, texture).identifier.toU() } @@ -242,14 +214,6 @@ class GuiEssentialPlatformImpl : GuiEssentialPlatform { } } - override fun registerEventBusListener(cls: Class, listener: (T) -> Unit, priority: Int) { - Essential.EVENT_BUS.register(cls, listener, priority) - } - - override fun unregisterEventBusListener(cls: Class, listener: (T) -> Unit) { - Essential.EVENT_BUS.unregister(cls, listener) - } - override val essentialBaseDir: Path get() = Essential.getInstance().baseDir.toPath() @@ -319,8 +283,6 @@ class GuiEssentialPlatformImpl : GuiEssentialPlatform { override val relationships: IRelationshipStates by lazy { RelationshipStateManagerImpl(cm.relationshipManager) } override val messages: IMessengerStates by lazy { MessengerStateManagerImpl(cm.chatManager) } override val activity: IStatusStates by lazy { StatusStateManagerImpl(cm.profileManager, cm.spsManager) } - override val suspensions: ListState - get() = cm.profileManager.suspensions } } @@ -328,11 +290,6 @@ class GuiEssentialPlatformImpl : GuiEssentialPlatform { Essential.getInstance().connectionManager.chatManager.retrieveChannelHistoryUntil(messageRef) } - override fun haveActiveRemoteSpsSession(address: String): Boolean { - val spsAddr = SpsAddress.parse(address) ?: return false - return Essential.getInstance().connectionManager.spsManager.getRemoteSession(spsAddr.host) != null - } - override val essentialUriListener: EssentialMarkdown.(EssentialMarkdown.LinkClickEvent) -> Unit get() = gg.essential.util.essentialUriListener @@ -342,12 +299,6 @@ class GuiEssentialPlatformImpl : GuiEssentialPlatform { override val skinsManager: SkinsManager get() = Essential.getInstance().connectionManager.skinsManager - override val cosmeticsManager: ICosmeticsManager - get() = Essential.getInstance().connectionManager.cosmeticsManager - - override val wardrobeSettings: WardrobeSettings - get() = Essential.getInstance().connectionManager.cosmeticsManager.wardrobeSettings - override val mojangSkinManager: MojangSkinManager get() = Essential.getInstance().skinManager @@ -357,22 +308,12 @@ class GuiEssentialPlatformImpl : GuiEssentialPlatform { override val disabledFeaturesManager: DisabledFeaturesManager get() = Essential.getInstance().connectionManager.disabledFeaturesManager - override val screenshotFolder: Path - get() = gg.essential.util.screenshotFolder.toPath() - override val isOptiFineInstalled: Boolean get() = OptiFineUtil.isLoaded() override val trustedHosts: Set get() = TrustedHostsUtil.getTrustedHosts().flatMapTo(mutableSetOf()) { it.domains } - override val reportReasons: Map - get() = Essential.getInstance().connectionManager.chatManager.getReportReasons(UMinecraft.getSettings().language) - - override fun fileReport(modalManager: ModalManager, channelId: Long, messageId: Long, sender: UUID, reason: String) { - Essential.getInstance().connectionManager.chatManager.fileReport(modalManager, channelId, messageId, sender, reason) - } - override fun enqueueTelemetry(packet: ClientTelemetryPacket) { Essential.getInstance().connectionManager.telemetryManager.enqueue(packet) } @@ -484,10 +425,6 @@ class GuiEssentialPlatformImpl : GuiEssentialPlatform { GuiUtil.openScreen(SocialMenu::class.java) { SocialMenu(channelId) } } - override fun openScreenshotBrowser() { - GuiUtil.openScreen { ScreenshotBrowser() } - } - override fun connectToServer(name: String, address: String) { MinecraftUtils.connectToServer(name, address) } @@ -516,33 +453,4 @@ class GuiEssentialPlatformImpl : GuiEssentialPlatform { //$$ net.minecraft.client.render.BufferRenderer.unbindAll() //#endif } - - override fun splitHostAndPort(address: String, defaultPort: Int): Pair { - val hostAndPort = HostAndPort.fromString(address) - val host = hostAndPort.host - val port = hostAndPort.getPortOrDefault(25565) - return Pair(host, port) - } - - override fun parseIpAddress(address: String): InetAddress? { - return try { - InetAddresses.forString(address) - } catch (e: IllegalArgumentException) { - null - } - } - - override fun loadIntegratedServerIcon(): BufferedImage? { - val integratedServer = UMinecraft.getMinecraft().integratedServer - val icon = integratedServer?.worldDirectory?.resolve("icon.png") - return if (icon != null && icon.isRegularFile()) { - ImageIO.read(icon.toFile()) - } else { - null - } - } - - // TODO: Eventually move to :gui:essential project - override val modalPrerequisites - get() = McModalPrerequisites } diff --git a/src/main/kotlin/gg/essential/util/GuiUtil.kt b/src/main/kotlin/gg/essential/util/GuiUtil.kt index 509689f9..aeeaa8e0 100644 --- a/src/main/kotlin/gg/essential/util/GuiUtil.kt +++ b/src/main/kotlin/gg/essential/util/GuiUtil.kt @@ -95,30 +95,38 @@ object GuiUtil : GuiUtil, OverlayManager by OverlayManagerImpl, ModalManager by val screenRequiresTOS = GuiRequiresTOS::class.java.isAssignableFrom(type) val screenRequiresCosmetics = type == Wardrobe::class.java val screenRequiresAuth = screenRequiresCosmetics || type == SocialMenu::class.java - val screenChecksSocialSuspension = type == SocialMenu::class.java + val screenRequiresRules = type == SocialMenu::class.java if (screenRequiresTOS && !OnboardingData.hasAcceptedTos()) { + fun showTOS() = pushModal { + TOSModal(it, unprompted = false, requiresAuth = screenRequiresAuth, { openScreen(type, screen) }) + } if (openedScreen() == null) { // Show a notification when we're not in any menu, so it's less intrusive - sendTosNotification { - pushModal { - TOSModal(it, unprompted = false, requiresAuth = screenRequiresAuth, { openScreen(type, screen) }) - } - } - return + sendTosNotification { showTOS() } + } else { + showTOS() } + return } - launchModalFlow { - if (screenRequiresTOS) { - ensurePrerequisites( - cosmetics = screenRequiresCosmetics, - social = screenChecksSocialSuspension, - rules = false - ) - } - doOpenScreen(screen) + if (screenRequiresAuth && AutoUpdate.requiresUpdate()) { + // Essential outdated, require update first + pushModal { AutoUpdate.createUpdateModal(it) } + return + } + + if (screenRequiresAuth && !connectionManager.isAuthenticated) { + pushModal { NotAuthenticatedModal(it) { openScreen(type, screen) } } + return + } + + if (screenRequiresCosmetics && !connectionManager.cosmeticsManager.cosmeticsLoaded.getUntracked()) { + pushModal { CosmeticsLoadingModal(it) { openScreen(type, screen) } } + return } + + doOpenScreen(screen) } @Deprecated("For API users only. Does not check for TOS or similar, use the generic overload instead.", level = DeprecationLevel.ERROR) diff --git a/src/main/kotlin/gg/essential/util/extensions.kt b/src/main/kotlin/gg/essential/util/extensions.kt index 73fb82a9..720dcebe 100644 --- a/src/main/kotlin/gg/essential/util/extensions.kt +++ b/src/main/kotlin/gg/essential/util/extensions.kt @@ -11,10 +11,16 @@ */ package gg.essential.util +import com.sparkuniverse.toolbox.chat.enums.ChannelType +import com.sparkuniverse.toolbox.chat.model.Channel import dev.folomeev.kotgl.matrix.matrices.mat3 import dev.folomeev.kotgl.matrix.matrices.mat4 import gg.essential.Essential +import gg.essential.api.gui.EssentialGUI import gg.essential.connectionmanager.common.model.knownserver.KnownServer +import gg.essential.elementa.UIComponent +import gg.essential.elementa.components.UIContainer +import gg.essential.elementa.state.State import gg.essential.elementa.utils.elementaDebug import gg.essential.lib.gson.JsonElement import gg.essential.lib.gson.JsonPrimitive @@ -83,6 +89,13 @@ operator fun Color.component3() = this.blue operator fun Color.component4() = this.alpha +fun Channel.getOtherUser(): UUID? = + if (type == ChannelType.DIRECT_MESSAGE) members.firstOrNull { it != UUIDUtil.getClientUUID() } else null + +private val BOT_UUID = UUID.fromString("cd899a14-de78-4de8-8d31-9d42fff31d7a") // EssentialBot +fun Channel.isAnnouncement(): Boolean = + this.type == ChannelType.ANNOUNCEMENT || BOT_UUID in members + fun ServerDiscovery.toServerData(knownServers: Map = emptyMap()) = knownServers[addresses[0]] ?: ServerData( @@ -204,6 +217,17 @@ fun UImage.Companion.read(stream: InputStream): UImage { fun UImage.Companion.read(bytes: ByteArray) = read(bytes.inputStream()) +/** + * Creates and returns a scrollbar bound within the right divider of an EssentialGUI + */ +fun EssentialGUI.createRightDividerScroller( + display: State, + xPositionAndWidth: UIComponent = rightDivider, + parent: UIComponent = window, + yPositionAndHeight: UIComponent = content, + initializeToBottom: Boolean = false, +): Pair Unit> = createScrollbarRelativeTo(display, xPositionAndWidth, parent, yPositionAndHeight, initializeToBottom) + fun UMatrixStack.toCommon() = gg.essential.model.util.UMatrixStack( //#if MC>=11400 diff --git a/src/main/kotlin/gg/essential/util/helpers.kt b/src/main/kotlin/gg/essential/util/helpers.kt index d12b665d..3a21f06d 100644 --- a/src/main/kotlin/gg/essential/util/helpers.kt +++ b/src/main/kotlin/gg/essential/util/helpers.kt @@ -15,6 +15,9 @@ import com.sparkuniverse.toolbox.util.DateTime import gg.essential.Essential import gg.essential.config.LoadsResources import gg.essential.config.McEssentialConfig +import gg.essential.elementa.components.UIImage +import gg.essential.elementa.components.Window +import gg.essential.gui.common.ImageLoadCallback import gg.essential.gui.elementa.essentialmarkdown.EssentialMarkdown import gg.essential.gui.elementa.state.v2.ListState import gg.essential.gui.elementa.state.v2.State @@ -40,6 +43,7 @@ import net.minecraft.client.resources.IResourcePack import net.minecraft.util.ResourceLocation import net.minecraft.util.Session import org.apache.logging.log4j.LogManager +import java.awt.image.BufferedImage import java.io.File import java.io.IOException import java.net.URI @@ -49,6 +53,7 @@ import java.nio.file.Files import java.nio.file.Path import java.nio.file.StandardCopyOption import java.util.* +import java.util.concurrent.CompletableFuture import java.util.concurrent.ConcurrentHashMap import java.util.function.Consumer import kotlin.io.path.exists @@ -469,6 +474,32 @@ fun combinedOrderedScreenshotsState( }.toListState() } +/** + * Loads [image] as a [UIImage], calling [whenReady] on the UI thread once it is fully loaded. + * May be called from any thread. + * If [image] is `null`, no [UIImage] is created and [whenReady] will be called with `null`. + */ +fun maybeLoadUIImage(image: BufferedImage?, whenReady: (UIImage?) -> Unit) { + if (image == null) { + Window.enqueueRenderOperation { whenReady(null) } + } else { + loadUIImage(image, whenReady) + } +} + +/** + * Loads [image] as a [UIImage], calling [whenReady] on the UI thread once it is fully loaded. + * May be called from any thread. + */ +fun loadUIImage(image: BufferedImage, whenReady: (UIImage) -> Unit) { + val uiImage = UIImage(CompletableFuture.completedFuture(image)) + Window.enqueueRenderOperation { + uiImage.supply(ImageLoadCallback { + whenReady(uiImage) + }) + } +} + /** Listens for, and parses, any links pointing to custom essential protocol scheme and open associated GuiScreen */ val essentialUriListener: EssentialMarkdown.(EssentialMarkdown.LinkClickEvent) -> Unit = { event -> val prefix = "essential://" diff --git a/src/main/resources/assets/essential/commit.txt b/src/main/resources/assets/essential/commit.txt index 74c3bfb9..4c88fc72 100644 --- a/src/main/resources/assets/essential/commit.txt +++ b/src/main/resources/assets/essential/commit.txt @@ -1 +1 @@ -f0f0be8ff4 \ No newline at end of file +a263b344a3 \ No newline at end of file diff --git a/src/main/resources/mixins.essential.json b/src/main/resources/mixins.essential.json index c668aad4..eb3c1158 100644 --- a/src/main/resources/mixins.essential.json +++ b/src/main/resources/mixins.essential.json @@ -26,7 +26,6 @@ "client.gui.ResourcePackListEntryAccessor", "client.gui.PackLoadingManagerAccessor", "client.gui.PackScreenAccessor", - "client.gui.GuiDisconnectedAccessor", "client.gui.GuiMultiplayerAccessor", "client.gui.GuiScreenAccessor", "client.gui.Mixin_BypassModCompat_Gui", @@ -86,7 +85,6 @@ "client.model.geom.Mixin_ExtraTransform_ModelPart", "client.multiplayer.MixinGuiConnecting_SetMostRecentlyConnectedMultiplayerServer", "client.multiplayer.MixinServerData", - "client.multiplayer.Mixin_RealmsScreenOpenedTelemetry", "client.network.CPacketChatMessageAccessor", "client.network.Mixin_IngameEquippedOutfitsManager", "client.network.Mixin_IngameEquippedOutfitsManager_NetworkPlayerInfo", @@ -94,7 +92,7 @@ "client.network.Mixin_LogExceptions", "client.network.Mixin_OverridePing", "client.network.Mixin_RedirectToLocalConnection", - "client.network.Mixin_ApplyGameProfileOverrides", + "client.network.Mixin_RefreshSkinOnChange", "client.network.Mixin_RegisterEssentialChannel", "client.network.Mixin_SendChatEvents", "client.network.Mixin_TabListNameIdCache", @@ -220,9 +218,7 @@ "feature.particles.Mixin_RenderParticleSystemOfClientWorld", "feature.per_server_privacy.Mixin_AddPerServerPrivacyButton", "feature.per_server_settings.Mixin_PreserveCustomServerData", - "feature.skin_overwrites.MinecraftAccessor", "feature.skin_overwrites.Mixin_InstallTrustingServicesKeyInfo", - "feature.skin_overwrites.PlayerEntityAccessor", "feature.sound.Mixin_FixPaulscodeChannelAllocation", "feature.sound.Mixin_ISoundExt_createAccessor", "feature.sound.Mixin_ISoundExt_isRelativeToListener_apply", @@ -259,7 +255,6 @@ "server.GameRulesMixin_RegisterCustomGameRules", "server.MinecraftServerAccessor", "server.MinecraftServerMixin_PvPGameRule", - "server.Mixin_MinecraftServer_FixStatusEquality", "server.Mixin_ControlAreCommandsAllowed", "server.Mixin_InvertPlayerDataPriority", "server.Mixin_ServerCoroutineScope", @@ -268,7 +263,6 @@ "server.integrated.LanConnectionsAccessor", "server.integrated.Mixin_FixDefaultOpPermissionLevel", "server.integrated.Mixin_IntegratedServerManager", - "server.integrated.Mixin_IntegratedServerOnTick", "server.integrated.ServerWorldAccessor", "server.integrated.ServerWorldInfoAccessor", "server.integrated.Mixin_SetDifficulty", diff --git a/subprojects/feature-flags/src/main/kotlin/gg/essential/config/HideIfDisabled.kt b/subprojects/feature-flags/src/main/kotlin/gg/essential/config/HideIfDisabled.kt index f34df852..8cf32b5c 100644 --- a/subprojects/feature-flags/src/main/kotlin/gg/essential/config/HideIfDisabled.kt +++ b/subprojects/feature-flags/src/main/kotlin/gg/essential/config/HideIfDisabled.kt @@ -19,22 +19,7 @@ package gg.essential.config * Note that this cannot (and MUST NOT) be used to control behavior because it unconditionally removes the element * at build time only. It has no effect at runtime (it is in fact removed at build time) and the element will not be * removed if the feature flag is set to any value other than `false` (e.g. A/B testing) at build time. - * The primary purpose of this annotation is to hide code which should not yet be visible to the general public. - * - * The secondary purpose of this annotation is to ensure that some new code is never by accident accessed without - * checking the corresponding feature flag. It is common practice to apply it to some of your new data types / methods, - * even if they do not need to be hidden explicitly, just to make sure the build fails if they are unexpectedly - * retained. - * Note that for this purpose it is not necessary to put this annotation on absolutely everything you add, especially - * if that thing already inherently refers to another class/method/property which is already annotated; only the core - * classes/methods are usually sufficient. - * - * Of special note is also usage of this annotation in combination with changes to packets: - * It is highly recommended to apply this annotation to any new fields in existing packets (and to new packets). - * This is because GSON will look at all fields of a Packet at runtime and try to deserialize them even if the feature - * flag isn't enabled yet (it doesn't know about feature flags), so if the fields are not explicitly hidden from - * production, one would need to already consider backwards compatibility with production clients for protocol changes - * even before the proper release of the feature. + * The only purpose of this annotation is to hide code which should not yet be visible to the general public. */ @Target( AnnotationTarget.FILE, diff --git a/subprojects/infra/src/main/java/com/sparkuniverse/toolbox/chat/model/Channel.java b/subprojects/infra/src/main/java/com/sparkuniverse/toolbox/chat/model/Channel.java index 8ae3ee06..443f01b7 100644 --- a/subprojects/infra/src/main/java/com/sparkuniverse/toolbox/chat/model/Channel.java +++ b/subprojects/infra/src/main/java/com/sparkuniverse/toolbox/chat/model/Channel.java @@ -58,12 +58,6 @@ public final class Channel { @SerializedName("joined_at") public final long joinedAt; - @SerializedName("last_read_message_id") - private @Nullable Long lastReadMessageId; - - @SerializedName("unread_messages") - private int unreadMessages; - public Channel( final long id, @NotNull final ChannelType type, @NotNull final String name, @Nullable final String topic, @Nullable final ChannelSettings settings, @NotNull final Set members, @@ -80,8 +74,6 @@ public Channel( this.closedInfo = closedInfo; this.muted = muted; this.joinedAt = joinedAt; - this.lastReadMessageId = lastReadMessageId; - this.unreadMessages = unreadMessages; } public long getId() { @@ -127,11 +119,11 @@ public long getJoinedAt() { } public @Nullable Long getLastReadMessageId() { - return lastReadMessageId; + return null; } public int getUnreadMessages() { - return unreadMessages; + return -1; } public void setName(@NotNull final String name) { @@ -147,10 +139,8 @@ public void setMuted(boolean muted) { } public void setLastReadMessageId(@Nullable Long lastReadMessageId) { - this.lastReadMessageId = lastReadMessageId; } public void setUnreadMessages(int unreadMessages) { - this.unreadMessages = unreadMessages; } } diff --git a/subprojects/infra/src/main/java/gg/essential/connectionmanager/common/model/profile/ProfilePunishmentStatus.java b/subprojects/infra/src/main/java/gg/essential/connectionmanager/common/model/profile/ProfilePunishmentStatus.java deleted file mode 100644 index 351f1981..00000000 --- a/subprojects/infra/src/main/java/gg/essential/connectionmanager/common/model/profile/ProfilePunishmentStatus.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.connectionmanager.common.model.profile; - -import gg.essential.lib.gson.annotations.SerializedName; -import gg.essential.network.connectionmanager.common.model.profile.PunishmentType; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public class ProfilePunishmentStatus { - - @SerializedName("punishment_type") - private final PunishmentType punishmentType; - - @SerializedName("expires_at") - private final @Nullable Long expiresAt; - - public ProfilePunishmentStatus(final @NotNull PunishmentType punishmentType, final @Nullable Long expiresAt) { - this.punishmentType = punishmentType; - this.expiresAt = expiresAt; - } - - public PunishmentType getPunishmentType() { - return this.punishmentType; - } - - public @Nullable Long getExpiresAt() { - return this.expiresAt; - } -} diff --git a/subprojects/infra/src/main/java/gg/essential/connectionmanager/common/packet/chat/ClientChatChannelMessagesRetrievePacket.java b/subprojects/infra/src/main/java/gg/essential/connectionmanager/common/packet/chat/ClientChatChannelMessagesRetrievePacket.java index 141d862a..c3cf04ff 100644 --- a/subprojects/infra/src/main/java/gg/essential/connectionmanager/common/packet/chat/ClientChatChannelMessagesRetrievePacket.java +++ b/subprojects/infra/src/main/java/gg/essential/connectionmanager/common/packet/chat/ClientChatChannelMessagesRetrievePacket.java @@ -42,11 +42,6 @@ public long getChannelId() { return this.channelId; } - @Nullable - public Long getBefore() { - return this.before; - } - @Nullable public Long getAfter() { return this.after; diff --git a/subprojects/infra/src/main/java/gg/essential/connectionmanager/common/packet/chat/ServerChatChannelMessageRejectedPacket.java b/subprojects/infra/src/main/java/gg/essential/connectionmanager/common/packet/chat/ServerChatChannelMessageRejectedPacket.java deleted file mode 100644 index a7da156b..00000000 --- a/subprojects/infra/src/main/java/gg/essential/connectionmanager/common/packet/chat/ServerChatChannelMessageRejectedPacket.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.connectionmanager.common.packet.chat; - -import gg.essential.connectionmanager.common.packet.Packet; -import gg.essential.lib.gson.annotations.SerializedName; - -public class ServerChatChannelMessageRejectedPacket extends Packet { - - // Identifier for the reason for this message being rejected (e.g. "CONTAINS_NON_WHITELISTED_DOMAIN"), to then be processed by the mod. - private final String reason; - - // The index in the sent message that the offending content starts at. - @SerializedName("start_index") - private final int startIndex; - - // The index in the sent message that the offending content ends at (exclusive). - @SerializedName("end_index") - private final int endIndex; - - public ServerChatChannelMessageRejectedPacket(String reason, int startIndex, int endIndex) { - this.reason = reason; - this.startIndex = startIndex; - this.endIndex = endIndex; - } - - public String getReason() { - return this.reason; - } - - public int getStartIndex() { - return this.startIndex; - } - - public int getEndIndex() { - return this.endIndex; - } -} diff --git a/subprojects/infra/src/main/java/gg/essential/connectionmanager/common/packet/profile/ServerProfileStatusPacket.java b/subprojects/infra/src/main/java/gg/essential/connectionmanager/common/packet/profile/ServerProfileStatusPacket.java index 1eee19ff..3b72ff62 100644 --- a/subprojects/infra/src/main/java/gg/essential/connectionmanager/common/packet/profile/ServerProfileStatusPacket.java +++ b/subprojects/infra/src/main/java/gg/essential/connectionmanager/common/packet/profile/ServerProfileStatusPacket.java @@ -11,7 +11,6 @@ */ package gg.essential.connectionmanager.common.packet.profile; -import gg.essential.connectionmanager.common.model.profile.ProfilePunishmentStatus; import gg.essential.lib.gson.annotations.SerializedName; import gg.essential.connectionmanager.common.enums.ProfileStatus; import gg.essential.connectionmanager.common.packet.Packet; @@ -32,9 +31,6 @@ public class ServerProfileStatusPacket extends Packet { private final @Nullable Long lastOnlineTimestamp; - @SerializedName("punishment_status") - private @Nullable ProfilePunishmentStatus punishmentStatus = null; - public ServerProfileStatusPacket(final @NotNull UUID uuid, final @NotNull ProfileStatus profileStatus) { this(uuid, profileStatus, (Long) null); } @@ -45,17 +41,6 @@ public ServerProfileStatusPacket(@NotNull final UUID uuid, @NotNull final Profil this.lastOnlineTimestamp = lastOnlineTimestamp; } - public ServerProfileStatusPacket(final @NotNull UUID uuid, final @NotNull ProfileStatus profileStatus, final @Nullable ProfilePunishmentStatus punishmentStatus) { - this(uuid, profileStatus, null, punishmentStatus); - } - - public ServerProfileStatusPacket(@NotNull final UUID uuid, @NotNull final ProfileStatus status, final @Nullable Long lastOnlineTimestamp, final @Nullable ProfilePunishmentStatus punishmentStatus) { - this.uuid = uuid; - this.status = status; - this.lastOnlineTimestamp = lastOnlineTimestamp; - this.punishmentStatus = punishmentStatus; - } - @NotNull public UUID getUUID() { return this.uuid; @@ -70,8 +55,4 @@ public ProfileStatus getStatus() { return this.lastOnlineTimestamp; } - @Nullable - public ProfilePunishmentStatus getPunishmentStatus() { - return this.punishmentStatus; - } } diff --git a/subprojects/infra/src/main/java/gg/essential/connectionmanager/common/packet/social/ClientCommunityRulesAgreedPacket.java b/subprojects/infra/src/main/java/gg/essential/connectionmanager/common/packet/social/ClientCommunityRulesAgreedPacket.java deleted file mode 100644 index 47f6711d..00000000 --- a/subprojects/infra/src/main/java/gg/essential/connectionmanager/common/packet/social/ClientCommunityRulesAgreedPacket.java +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.connectionmanager.common.packet.social; - -import gg.essential.connectionmanager.common.packet.Packet; - -public class ClientCommunityRulesAgreedPacket extends Packet { - -} diff --git a/subprojects/infra/src/main/java/gg/essential/connectionmanager/common/packet/social/ServerCommunityRulesStatePacket.java b/subprojects/infra/src/main/java/gg/essential/connectionmanager/common/packet/social/ServerCommunityRulesStatePacket.java deleted file mode 100644 index 04fef28b..00000000 --- a/subprojects/infra/src/main/java/gg/essential/connectionmanager/common/packet/social/ServerCommunityRulesStatePacket.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.connectionmanager.common.packet.social; - -import com.google.common.collect.ImmutableList; -import gg.essential.connectionmanager.common.packet.Packet; - -import java.util.List; -import java.util.Map; - -public class ServerCommunityRulesStatePacket extends Packet { - - private final List> rules; // - private final boolean accepted; - - public ServerCommunityRulesStatePacket( - final List> rules, - final boolean accepted - ) { - this.rules = rules; - this.accepted = accepted; - } - - public List> getRules() { - return ImmutableList.copyOf(this.rules); - } - - public boolean getAccepted() { - return this.accepted; - } -} diff --git a/subprojects/infra/src/main/java/gg/essential/connectionmanager/common/packet/social/ServerSocialAllowedDomainsPacket.java b/subprojects/infra/src/main/java/gg/essential/connectionmanager/common/packet/social/ServerSocialAllowedDomainsPacket.java deleted file mode 100644 index 35eb3395..00000000 --- a/subprojects/infra/src/main/java/gg/essential/connectionmanager/common/packet/social/ServerSocialAllowedDomainsPacket.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.connectionmanager.common.packet.social; - -import gg.essential.connectionmanager.common.packet.Packet; - -import java.util.Collections; -import java.util.List; - -public class ServerSocialAllowedDomainsPacket extends Packet { - - private final List domains; - - public ServerSocialAllowedDomainsPacket(final List domains) { - this.domains = domains; - } - - public List getDomains() { - return Collections.unmodifiableList(this.domains); - } -} diff --git a/subprojects/infra/src/main/java/gg/essential/connectionmanager/common/packet/social/ServerSocialSuspensionStatePacket.java b/subprojects/infra/src/main/java/gg/essential/connectionmanager/common/packet/social/ServerSocialSuspensionStatePacket.java deleted file mode 100644 index 7252816a..00000000 --- a/subprojects/infra/src/main/java/gg/essential/connectionmanager/common/packet/social/ServerSocialSuspensionStatePacket.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.connectionmanager.common.packet.social; - -import gg.essential.connectionmanager.common.packet.Packet; -import gg.essential.lib.gson.annotations.SerializedName; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.time.Instant; - -public class ServerSocialSuspensionStatePacket extends Packet { - - private final boolean suspended; - - // Only present when the user has been suspended. - private final @Nullable String reason; - - @SerializedName("expires_at") - private final @Nullable Long expiresAt; - - // Only present when the user has been suspended. - @SerializedName("recently_started") - private final @Nullable Boolean recentlyStarted; - - public ServerSocialSuspensionStatePacket( - final boolean suspended, - final @Nullable String reason, - final @Nullable Long expiresAt, - final @Nullable Boolean recentlyStarted - ) { - this.suspended = suspended; - this.reason = reason; - this.expiresAt = expiresAt; - this.recentlyStarted = recentlyStarted; - } - - public boolean isSuspended() { - return this.suspended; - } - - public @NotNull String getReason() { - return this.reason == null ? "" : this.reason; - } - - public @Nullable Instant getExpiresAt() { - return this.expiresAt == null ? null : Instant.ofEpochMilli(this.expiresAt); - } - - public boolean isRecentlyStarted() { - return this.recentlyStarted != null && this.recentlyStarted; - } -} diff --git a/subprojects/infra/src/main/kotlin/gg/essential/network/CMConnection.kt b/subprojects/infra/src/main/kotlin/gg/essential/network/CMConnection.kt index b0ef7514..5550a3df 100644 --- a/subprojects/infra/src/main/kotlin/gg/essential/network/CMConnection.kt +++ b/subprojects/infra/src/main/kotlin/gg/essential/network/CMConnection.kt @@ -35,7 +35,6 @@ interface CMConnection { fun send(packet: Packet, callback: Consumer>?, timeoutUnit: TimeUnit?, timeoutValue: Long?) @Deprecated("Use `call` instead.", ReplaceWith("call(packet).await()")) fun send(packet: Packet, callback: Consumer>?) = send(packet, callback, TimeUnit.SECONDS, 10) - } inline fun CMConnection.registerPacketHandler(noinline handler: (T) -> Unit) = diff --git a/subprojects/infra/src/main/kotlin/gg/essential/network/connectionmanager/common/model/profile/PunishmentType.kt b/subprojects/infra/src/main/kotlin/gg/essential/network/connectionmanager/common/model/profile/PunishmentType.kt deleted file mode 100644 index c6c43201..00000000 --- a/subprojects/infra/src/main/kotlin/gg/essential/network/connectionmanager/common/model/profile/PunishmentType.kt +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.network.connectionmanager.common.model.profile - -enum class PunishmentType { - - PERMANENT_BAN, - SOCIAL_BAN, -} diff --git a/subprojects/infra/src/main/kotlin/gg/essential/network/connectionmanager/relationship/RelationshipErrorResponse.kt b/subprojects/infra/src/main/kotlin/gg/essential/network/connectionmanager/relationship/RelationshipErrorResponse.kt index fc797b3b..5f87902d 100644 --- a/subprojects/infra/src/main/kotlin/gg/essential/network/connectionmanager/relationship/RelationshipErrorResponse.kt +++ b/subprojects/infra/src/main/kotlin/gg/essential/network/connectionmanager/relationship/RelationshipErrorResponse.kt @@ -26,8 +26,6 @@ enum class RelationshipErrorResponse { TARGET_PRIVACY_SETTING_FRIEND_OF_FRIENDS, TARGET_PRIVACY_SETTING_NO_ONE, TARGET_NOT_EXIST, - USER_IS_TEMPORARILY_SUSPENDED, - USER_IS_PERMANENTLY_SUSPENDED, ; companion object { diff --git a/versions/1.21.9-fabric/src/main/java/gg/essential/mixins/transformers/feature/skin_overwrites/PlayerEntityAccessor.java b/versions/1.21.9-fabric/src/main/java/gg/essential/mixins/transformers/feature/skin_overwrites/PlayerEntityAccessor.java deleted file mode 100644 index b82c93e9..00000000 --- a/versions/1.21.9-fabric/src/main/java/gg/essential/mixins/transformers/feature/skin_overwrites/PlayerEntityAccessor.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.mixins.transformers.feature.skin_overwrites; - -import com.mojang.authlib.GameProfile; -import net.minecraft.entity.player.PlayerEntity; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Mutable; -import org.spongepowered.asm.mixin.gen.Accessor; - -@Mixin(PlayerEntity.class) -public interface PlayerEntityAccessor { - @Accessor - @Mutable - void setGameProfile(GameProfile gameProfile); -} diff --git a/versions/aliases.txt b/versions/aliases.txt index fa39bc58..09bfa1da 100644 --- a/versions/aliases.txt +++ b/versions/aliases.txt @@ -1,4 +1,3 @@ -1.21.10-fabric 1.21.9-fabric 1.21.8-neoforge 1.21.7-neoforge 1.21.8-forge 1.21.7-forge 1.21.8-fabric 1.21.7-fabric