From 1ffa879f17868fed35d850752ffacb1e2a089afe Mon Sep 17 00:00:00 2001 From: hypherionmc Date: Tue, 11 Jun 2024 19:51:28 +0200 Subject: [PATCH] [DEV] Fix up porting patches and configs --- .gitignore | 4 +- .gitattributes => 1.18.2/.gitattributes | 0 1.18.2/.gitignore | 29 ++ 1.18.2/.jenkins/Jenkinsfile.deploy | 55 +++ 1.18.2/.jenkins/Jenkinsfile.snapshot | 65 +++ 1.18.2/Common/build.gradle | 68 +++ .../craterlib/CraterConstants.java | 10 + .../craterlib/api/commands/CraterCommand.java | 53 +++ .../events/client/CraterClientTickEvent.java | 14 + .../client/CraterSinglePlayerEvent.java | 21 + .../api/events/client/LateInitEvent.java | 16 + .../events/client/PlayerJoinRealmEvent.java | 14 + .../api/events/client/ScreenEvent.java | 27 ++ .../events/common/CraterPlayerDeathEvent.java | 21 + .../events/server/CraterAdvancementEvent.java | 34 ++ .../api/events/server/CraterCommandEvent.java | 58 +++ .../api/events/server/CraterPlayerEvent.java | 30 ++ .../server/CraterRegisterCommandEvent.java | 15 + .../events/server/CraterServerChatEvent.java | 25 ++ .../server/CraterServerLifecycleEvent.java | 34 ++ .../events/server/MessageBroadcastEvent.java | 20 + .../events/server/PlayerPreLoginEvent.java | 20 + .../api/networking/CraterNetworkHandler.java | 28 ++ .../client/gui/config/CraterConfigScreen.java | 394 ++++++++++++++++++ .../config/widgets/AbstractConfigWidget.java | 27 ++ .../client/gui/config/widgets/BaseWidget.java | 61 +++ .../config/widgets/InternalConfigButton.java | 53 +++ .../client/gui/config/widgets/Option.java | 71 ++++ .../gui/config/widgets/SubConfigWidget.java | 40 ++ .../gui/config/widgets/TextConfigOption.java | 45 ++ .../gui/config/widgets/ToggleButton.java | 34 ++ .../gui/config/widgets/WrappedEditBox.java | 29 ++ .../client/mentions/MentionCondition.java | 13 + .../client/mentions/MentionsController.java | 47 +++ .../core/config/ConfigController.java | 49 +++ .../craterlib/core/config/ModuleConfig.java | 184 ++++++++ .../config/annotations/HideFromScreen.java | 8 + .../config/annotations/NoConfigScreen.java | 12 + .../core/config/annotations/SubConfig.java | 16 + .../core/config/annotations/Syncable.java | 15 + .../core/config/annotations/Tooltip.java | 16 + .../craterlib/core/event/CraterEvent.java | 30 ++ .../craterlib/core/event/CraterEventBus.java | 241 +++++++++++ .../core/event/CraterEventPriority.java | 13 + .../core/event/annot/Cancellable.java | 8 + .../core/event/annot/CraterEventListener.java | 11 + .../CraterEventCancellationException.java | 9 + .../craterlib/core/event/package-info.java | 5 + .../core/networking/CraterPacketNetwork.java | 43 ++ .../networking/DeferredPacketRegistrar.java | 41 ++ .../core/networking/PacketRegistrar.java | 21 + .../core/networking/PacketRegistry.java | 41 ++ .../core/networking/data/PacketContext.java | 15 + .../core/networking/data/PacketHolder.java | 18 + .../core/networking/data/PacketSide.java | 13 + .../core/platform/ClientPlatform.java | 23 + .../core/platform/CommonPlatform.java | 15 + .../craterlib/core/platform/CompatUtils.java | 13 + .../craterlib/core/platform/Environment.java | 18 + .../core/platform/ModloaderEnvironment.java | 32 ++ .../core/rpcsdk/DiscordEventHandlers.java | 90 ++++ .../craterlib/core/rpcsdk/DiscordRPC.java | 99 +++++ .../core/rpcsdk/DiscordRichPresence.java | 248 +++++++++++ .../craterlib/core/rpcsdk/DiscordUser.java | 39 ++ .../callbacks/DisconnectedCallback.java | 18 + .../rpcsdk/callbacks/ErroredCallback.java | 18 + .../rpcsdk/callbacks/JoinGameCallback.java | 17 + .../rpcsdk/callbacks/JoinRequestCallback.java | 19 + .../core/rpcsdk/callbacks/ReadyCallback.java | 19 + .../callbacks/SpectateGameCallback.java | 17 + .../core/rpcsdk/helpers/RPCButton.java | 55 +++ .../mixin/ChatInputSuggestorMixin.java | 84 ++++ .../craterlib/mixin/events/CommandMixin.java | 57 +++ .../mixin/events/PlayerAdvancementsMixin.java | 29 ++ .../mixin/events/PlayerListMixin.java | 53 +++ .../craterlib/mixin/events/PlayerMixin.java | 21 + .../mixin/events/ServerPlayerMixin.java | 21 + .../mixin/events/client/ClientLevelMixin.java | 25 ++ .../mixin/events/client/MinecraftMixin.java | 31 ++ .../events/client/RealmsMainScreenMixin.java | 23 + .../advancements/BridgedAdvancement.java | 21 + .../advancements/BridgedDisplayInfo.java | 29 ++ .../nojang/authlib/BridgedGameProfile.java | 29 ++ .../nojang/client/BridgedMinecraft.java | 84 ++++ .../nojang/client/BridgedOptions.java | 15 + .../nojang/client/gui/BridgedScreen.java | 36 ++ .../multiplayer/BridgedClientLevel.java | 59 +++ .../client/multiplayer/BridgedServerData.java | 45 ++ .../server/BridgedIntegratedServer.java | 19 + .../commands/BridgedCommandSourceStack.java | 22 + .../nojang/commands/BridgedFakePlayer.java | 56 +++ .../nojang/commands/CommandsRegistry.java | 85 ++++ .../nojang/core/BridgedBlockPos.java | 27 ++ .../nojang/nbt/BridgedCompoundTag.java | 49 +++ .../network/BridgedFriendlyByteBuf.java | 34 ++ .../craterlib/nojang/package-info.java | 9 + .../realmsclient/dto/BridgedRealmsServer.java | 40 ++ .../nojang/resources/ResourceIdentifier.java | 36 ++ .../nojang/server/BridgedMinecraftServer.java | 93 +++++ .../world/entity/player/BridgedPlayer.java | 63 +++ .../craterlib/utils/ChatUtils.java | 98 +++++ .../craterlib/utils/InternalServiceUtil.java | 27 ++ .../craterlib/utils/OptifineUtils.java | 46 ++ .../craterlib/utils/TriConsumer.java | 5 + .../assets/craterlib/lang/en_us.json | 8 + .../src/main/resources/craterlib.mixins.json | 24 ++ 1.18.2/Common/src/main/resources/pack.mcmeta | 6 + 1.18.2/Fabric/build.gradle | 126 ++++++ .../craterlib/CraterLibInitializer.java | 42 ++ .../CraterLibModMenuIntegration.java | 30 ++ .../client/CraterLibClientInitializer.java | 27 ++ .../client/FabricClientPlatform.java | 34 ++ .../common/FabricCommonPlatform.java | 18 + .../craterlib/common/FabricCompatHelper.java | 23 + .../craterlib/common/FabricLoaderHelper.java | 69 +++ .../craterlib/compat/FabricTailor.java | 23 + .../hypherionmc/craterlib/compat/Vanish.java | 25 ++ .../ServerGamePacketListenerImplMixin.java | 40 ++ .../craterlib/mixin/TutorialMixin.java | 24 ++ .../network/CraterFabricNetworkHandler.java | 81 ++++ ...nmc.craterlib.core.platform.ClientPlatform | 1 + ...nmc.craterlib.core.platform.CommonPlatform | 1 + ...rionmc.craterlib.core.platform.CompatUtils | 1 + ...aterlib.core.platform.ModloaderEnvironment | 1 + .../assets/craterlib/craterlib_logo.png | Bin 0 -> 49343 bytes .../resources/craterlib.fabric.mixins.json | 17 + .../Fabric/src/main/resources/fabric.mod.json | 39 ++ 1.18.2/Forge/build.gradle | 112 +++++ .../com/hypherionmc/craterlib/CraterLib.java | 41 ++ .../craterlib/client/ForgeClientEvents.java | 25 ++ .../craterlib/client/ForgeClientHelper.java | 41 ++ .../craterlib/common/ForgeCommonHelper.java | 26 ++ .../craterlib/common/ForgeCompatHelper.java | 17 + .../craterlib/common/ForgeLoaderHelper.java | 73 ++++ .../craterlib/common/ForgeServerEvents.java | 43 ++ .../hypherionmc/craterlib/compat/Vanish.java | 27 ++ .../mixin/ConfigScreenHandlerMixin.java | 43 ++ .../ServerGamePacketListenerImplMixin.java | 40 ++ .../network/CraterForgeNetworkHandler.java | 100 +++++ .../src/main/resources/META-INF/mods.toml | 31 ++ ...nmc.craterlib.core.platform.ClientPlatform | 1 + ...nmc.craterlib.core.platform.CommonPlatform | 1 + ...rionmc.craterlib.core.platform.CompatUtils | 1 + ...aterlib.core.platform.ModloaderEnvironment | 1 + .../resources/craterlib.forge.mixins.json | 17 + .../src/main/resources/craterlib_logo.png | Bin 0 -> 49343 bytes 1.18.2/LICENSE | 21 + .../src/main/resources/craterlib_logo.png | Bin 0 -> 49343 bytes 1.18.2/README.md | 55 +++ 1.18.2/build.gradle | 110 +++++ 1.18.2/gradle.properties | 42 ++ 1.18.2/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59536 bytes .../gradle/wrapper/gradle-wrapper.properties | 5 + 1.18.2/gradlew | 183 ++++++++ 1.18.2/gradlew.bat | 89 ++++ 1.18.2/settings.gradle | 14 + 1.19.2/.gitattributes | 15 + 1.19.2/.gitignore | 29 ++ 1.19.2/.jenkins/Jenkinsfile.deploy | 55 +++ 1.19.2/.jenkins/Jenkinsfile.snapshot | 65 +++ 1.19.2/Common/build.gradle | 68 +++ .../craterlib/CraterConstants.java | 10 + .../craterlib/api/commands/CraterCommand.java | 53 +++ .../events/client/CraterClientTickEvent.java | 14 + .../client/CraterSinglePlayerEvent.java | 21 + .../api/events/client/LateInitEvent.java | 16 + .../events/client/PlayerJoinRealmEvent.java | 14 + .../api/events/client/ScreenEvent.java | 27 ++ .../events/common/CraterPlayerDeathEvent.java | 21 + .../events/server/CraterAdvancementEvent.java | 34 ++ .../api/events/server/CraterCommandEvent.java | 58 +++ .../api/events/server/CraterPlayerEvent.java | 30 ++ .../server/CraterRegisterCommandEvent.java | 15 + .../events/server/CraterServerChatEvent.java | 25 ++ .../server/CraterServerLifecycleEvent.java | 34 ++ .../events/server/MessageBroadcastEvent.java | 20 + .../events/server/PlayerPreLoginEvent.java | 20 + .../api/networking/CraterNetworkHandler.java | 28 ++ .../client/gui/config/CraterConfigScreen.java | 392 +++++++++++++++++ .../config/widgets/AbstractConfigWidget.java | 27 ++ .../client/gui/config/widgets/BaseWidget.java | 61 +++ .../config/widgets/InternalConfigButton.java | 52 +++ .../client/gui/config/widgets/Option.java | 71 ++++ .../gui/config/widgets/SubConfigWidget.java | 40 ++ .../gui/config/widgets/TextConfigOption.java | 45 ++ .../gui/config/widgets/ToggleButton.java | 33 ++ .../gui/config/widgets/WrappedEditBox.java | 29 ++ .../client/mentions/MentionCondition.java | 13 + .../client/mentions/MentionsController.java | 47 +++ .../core/config/ConfigController.java | 49 +++ .../craterlib/core/config/ModuleConfig.java | 184 ++++++++ .../config/annotations/HideFromScreen.java | 8 + .../config/annotations/NoConfigScreen.java | 12 + .../core/config/annotations/SubConfig.java | 16 + .../core/config/annotations/Syncable.java | 15 + .../core/config/annotations/Tooltip.java | 16 + .../craterlib/core/event/CraterEvent.java | 30 ++ .../craterlib/core/event/CraterEventBus.java | 241 +++++++++++ .../core/event/CraterEventPriority.java | 13 + .../core/event/annot/Cancellable.java | 8 + .../core/event/annot/CraterEventListener.java | 11 + .../CraterEventCancellationException.java | 9 + .../craterlib/core/event/package-info.java | 5 + .../core/networking/CraterPacketNetwork.java | 43 ++ .../networking/DeferredPacketRegistrar.java | 41 ++ .../core/networking/PacketRegistrar.java | 21 + .../core/networking/PacketRegistry.java | 41 ++ .../core/networking/data/PacketContext.java | 15 + .../core/networking/data/PacketHolder.java | 18 + .../core/networking/data/PacketSide.java | 13 + .../core/platform/ClientPlatform.java | 23 + .../core/platform/CommonPlatform.java | 15 + .../craterlib/core/platform/CompatUtils.java | 13 + .../craterlib/core/platform/Environment.java | 18 + .../core/platform/ModloaderEnvironment.java | 32 ++ .../core/rpcsdk/DiscordEventHandlers.java | 90 ++++ .../craterlib/core/rpcsdk/DiscordRPC.java | 99 +++++ .../core/rpcsdk/DiscordRichPresence.java | 248 +++++++++++ .../craterlib/core/rpcsdk/DiscordUser.java | 39 ++ .../callbacks/DisconnectedCallback.java | 18 + .../rpcsdk/callbacks/ErroredCallback.java | 18 + .../rpcsdk/callbacks/JoinGameCallback.java | 17 + .../rpcsdk/callbacks/JoinRequestCallback.java | 19 + .../core/rpcsdk/callbacks/ReadyCallback.java | 19 + .../callbacks/SpectateGameCallback.java | 17 + .../core/rpcsdk/helpers/RPCButton.java | 55 +++ .../mixin/ChatInputSuggestorMixin.java | 84 ++++ .../craterlib/mixin/events/CommandMixin.java | 37 ++ .../mixin/events/PlayerAdvancementsMixin.java | 29 ++ .../mixin/events/PlayerListMixin.java | 52 +++ .../craterlib/mixin/events/PlayerMixin.java | 21 + .../mixin/events/ServerPlayerMixin.java | 21 + .../mixin/events/client/ClientLevelMixin.java | 25 ++ .../mixin/events/client/MinecraftMixin.java | 31 ++ .../events/client/RealmsMainScreenMixin.java | 23 + .../advancements/BridgedAdvancement.java | 21 + .../advancements/BridgedDisplayInfo.java | 29 ++ .../nojang/authlib/BridgedGameProfile.java | 29 ++ .../nojang/client/BridgedMinecraft.java | 84 ++++ .../nojang/client/BridgedOptions.java | 15 + .../nojang/client/gui/BridgedScreen.java | 36 ++ .../multiplayer/BridgedClientLevel.java | 59 +++ .../client/multiplayer/BridgedServerData.java | 45 ++ .../server/BridgedIntegratedServer.java | 19 + .../commands/BridgedCommandSourceStack.java | 22 + .../nojang/commands/BridgedFakePlayer.java | 55 +++ .../nojang/commands/CommandsRegistry.java | 85 ++++ .../nojang/core/BridgedBlockPos.java | 27 ++ .../nojang/nbt/BridgedCompoundTag.java | 49 +++ .../network/BridgedFriendlyByteBuf.java | 34 ++ .../craterlib/nojang/package-info.java | 9 + .../realmsclient/dto/BridgedRealmsServer.java | 40 ++ .../nojang/resources/ResourceIdentifier.java | 36 ++ .../nojang/server/BridgedMinecraftServer.java | 91 ++++ .../world/entity/player/BridgedPlayer.java | 63 +++ .../craterlib/utils/ChatUtils.java | 96 +++++ .../craterlib/utils/InternalServiceUtil.java | 27 ++ .../craterlib/utils/OptifineUtils.java | 46 ++ .../craterlib/utils/TriConsumer.java | 5 + .../assets/craterlib/lang/en_us.json | 8 + .../src/main/resources/craterlib.mixins.json | 24 ++ 1.19.2/Common/src/main/resources/pack.mcmeta | 6 + 1.19.2/Fabric/build.gradle | 126 ++++++ .../craterlib/CraterLibInitializer.java | 42 ++ .../CraterLibModMenuIntegration.java | 30 ++ .../client/CraterLibClientInitializer.java | 27 ++ .../client/FabricClientPlatform.java | 34 ++ .../common/FabricCommonPlatform.java | 18 + .../craterlib/common/FabricCompatHelper.java | 23 + .../craterlib/common/FabricLoaderHelper.java | 69 +++ .../craterlib/compat/FabricTailor.java | 23 + .../hypherionmc/craterlib/compat/Vanish.java | 25 ++ .../ServerGamePacketListenerImplMixin.java | 36 ++ .../craterlib/mixin/TutorialMixin.java | 24 ++ .../network/CraterFabricNetworkHandler.java | 81 ++++ ...nmc.craterlib.core.platform.ClientPlatform | 1 + ...nmc.craterlib.core.platform.CommonPlatform | 1 + ...rionmc.craterlib.core.platform.CompatUtils | 1 + ...aterlib.core.platform.ModloaderEnvironment | 1 + .../assets/craterlib/craterlib_logo.png | Bin 0 -> 49343 bytes .../resources/craterlib.fabric.mixins.json | 17 + .../Fabric/src/main/resources/fabric.mod.json | 39 ++ 1.19.2/Forge/build.gradle | 112 +++++ .../com/hypherionmc/craterlib/CraterLib.java | 41 ++ .../craterlib/client/ForgeClientEvents.java | 25 ++ .../craterlib/client/ForgeClientHelper.java | 41 ++ .../craterlib/common/ForgeCommonHelper.java | 26 ++ .../craterlib/common/ForgeCompatHelper.java | 17 + .../craterlib/common/ForgeLoaderHelper.java | 73 ++++ .../craterlib/common/ForgeServerEvents.java | 43 ++ .../hypherionmc/craterlib/compat/Vanish.java | 24 ++ .../mixin/ConfigScreenHandlerMixin.java | 43 ++ .../ServerGamePacketListenerImplMixin.java | 36 ++ .../network/CraterForgeNetworkHandler.java | 100 +++++ .../src/main/resources/META-INF/mods.toml | 31 ++ ...nmc.craterlib.core.platform.ClientPlatform | 1 + ...nmc.craterlib.core.platform.CommonPlatform | 1 + ...rionmc.craterlib.core.platform.CompatUtils | 1 + ...aterlib.core.platform.ModloaderEnvironment | 1 + .../resources/craterlib.forge.mixins.json | 17 + .../src/main/resources/craterlib_logo.png | Bin 0 -> 49343 bytes 1.19.2/LICENSE | 21 + .../src/main/resources/craterlib_logo.png | Bin 0 -> 49343 bytes 1.19.2/README.md | 55 +++ 1.19.2/build.gradle | 110 +++++ 1.19.2/gradle.properties | 42 ++ 1.19.2/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59536 bytes .../gradle/wrapper/gradle-wrapper.properties | 5 + 1.19.2/gradlew | 183 ++++++++ 1.19.2/gradlew.bat | 89 ++++ 1.19.2/settings.gradle | 14 + 1.19.3/.gitattributes | 15 + 1.19.3/.gitignore | 29 ++ 1.19.3/.jenkins/Jenkinsfile.deploy | 55 +++ 1.19.3/.jenkins/Jenkinsfile.snapshot | 65 +++ 1.19.3/Common/build.gradle | 68 +++ .../craterlib/CraterConstants.java | 10 + .../craterlib/api/commands/CraterCommand.java | 53 +++ .../events/client/CraterClientTickEvent.java | 14 + .../client/CraterSinglePlayerEvent.java | 21 + .../api/events/client/LateInitEvent.java | 16 + .../events/client/PlayerJoinRealmEvent.java | 14 + .../api/events/client/ScreenEvent.java | 27 ++ .../events/common/CraterPlayerDeathEvent.java | 21 + .../events/server/CraterAdvancementEvent.java | 34 ++ .../api/events/server/CraterCommandEvent.java | 58 +++ .../api/events/server/CraterPlayerEvent.java | 30 ++ .../server/CraterRegisterCommandEvent.java | 15 + .../events/server/CraterServerChatEvent.java | 25 ++ .../server/CraterServerLifecycleEvent.java | 34 ++ .../events/server/MessageBroadcastEvent.java | 20 + .../events/server/PlayerPreLoginEvent.java | 20 + .../api/networking/CraterNetworkHandler.java | 28 ++ .../client/gui/config/CraterConfigScreen.java | 392 +++++++++++++++++ .../config/widgets/AbstractConfigWidget.java | 27 ++ .../client/gui/config/widgets/BaseWidget.java | 61 +++ .../config/widgets/InternalConfigButton.java | 52 +++ .../client/gui/config/widgets/Option.java | 71 ++++ .../gui/config/widgets/SubConfigWidget.java | 40 ++ .../gui/config/widgets/TextConfigOption.java | 45 ++ .../gui/config/widgets/ToggleButton.java | 33 ++ .../gui/config/widgets/WrappedEditBox.java | 29 ++ .../client/mentions/MentionCondition.java | 13 + .../client/mentions/MentionsController.java | 47 +++ .../core/config/ConfigController.java | 49 +++ .../craterlib/core/config/ModuleConfig.java | 184 ++++++++ .../config/annotations/HideFromScreen.java | 8 + .../config/annotations/NoConfigScreen.java | 12 + .../core/config/annotations/SubConfig.java | 16 + .../core/config/annotations/Syncable.java | 15 + .../core/config/annotations/Tooltip.java | 16 + .../craterlib/core/event/CraterEvent.java | 30 ++ .../craterlib/core/event/CraterEventBus.java | 241 +++++++++++ .../core/event/CraterEventPriority.java | 13 + .../core/event/annot/Cancellable.java | 8 + .../core/event/annot/CraterEventListener.java | 11 + .../CraterEventCancellationException.java | 9 + .../craterlib/core/event/package-info.java | 5 + .../core/networking/CraterPacketNetwork.java | 43 ++ .../networking/DeferredPacketRegistrar.java | 41 ++ .../core/networking/PacketRegistrar.java | 21 + .../core/networking/PacketRegistry.java | 41 ++ .../core/networking/data/PacketContext.java | 15 + .../core/networking/data/PacketHolder.java | 18 + .../core/networking/data/PacketSide.java | 13 + .../core/platform/ClientPlatform.java | 23 + .../core/platform/CommonPlatform.java | 15 + .../craterlib/core/platform/CompatUtils.java | 13 + .../craterlib/core/platform/Environment.java | 18 + .../core/platform/ModloaderEnvironment.java | 32 ++ .../core/rpcsdk/DiscordEventHandlers.java | 90 ++++ .../craterlib/core/rpcsdk/DiscordRPC.java | 99 +++++ .../core/rpcsdk/DiscordRichPresence.java | 248 +++++++++++ .../craterlib/core/rpcsdk/DiscordUser.java | 39 ++ .../callbacks/DisconnectedCallback.java | 18 + .../rpcsdk/callbacks/ErroredCallback.java | 18 + .../rpcsdk/callbacks/JoinGameCallback.java | 17 + .../rpcsdk/callbacks/JoinRequestCallback.java | 19 + .../core/rpcsdk/callbacks/ReadyCallback.java | 19 + .../callbacks/SpectateGameCallback.java | 17 + .../core/rpcsdk/helpers/RPCButton.java | 55 +++ .../mixin/ChatInputSuggestorMixin.java | 84 ++++ .../craterlib/mixin/events/CommandMixin.java | 37 ++ .../mixin/events/PlayerAdvancementsMixin.java | 29 ++ .../mixin/events/PlayerListMixin.java | 52 +++ .../craterlib/mixin/events/PlayerMixin.java | 21 + .../mixin/events/ServerPlayerMixin.java | 21 + .../mixin/events/client/ClientLevelMixin.java | 25 ++ .../mixin/events/client/MinecraftMixin.java | 31 ++ .../events/client/RealmsMainScreenMixin.java | 23 + .../advancements/BridgedAdvancement.java | 21 + .../advancements/BridgedDisplayInfo.java | 29 ++ .../nojang/authlib/BridgedGameProfile.java | 29 ++ .../nojang/client/BridgedMinecraft.java | 84 ++++ .../nojang/client/BridgedOptions.java | 15 + .../nojang/client/gui/BridgedScreen.java | 36 ++ .../multiplayer/BridgedClientLevel.java | 59 +++ .../client/multiplayer/BridgedServerData.java | 45 ++ .../server/BridgedIntegratedServer.java | 19 + .../commands/BridgedCommandSourceStack.java | 22 + .../nojang/commands/BridgedFakePlayer.java | 55 +++ .../nojang/commands/CommandsRegistry.java | 85 ++++ .../nojang/core/BridgedBlockPos.java | 27 ++ .../nojang/nbt/BridgedCompoundTag.java | 49 +++ .../network/BridgedFriendlyByteBuf.java | 34 ++ .../craterlib/nojang/package-info.java | 9 + .../realmsclient/dto/BridgedRealmsServer.java | 40 ++ .../nojang/resources/ResourceIdentifier.java | 36 ++ .../nojang/server/BridgedMinecraftServer.java | 91 ++++ .../world/entity/player/BridgedPlayer.java | 63 +++ .../craterlib/utils/ChatUtils.java | 96 +++++ .../craterlib/utils/InternalServiceUtil.java | 27 ++ .../craterlib/utils/OptifineUtils.java | 46 ++ .../craterlib/utils/TriConsumer.java | 5 + .../assets/craterlib/lang/en_us.json | 8 + .../src/main/resources/craterlib.mixins.json | 24 ++ 1.19.3/Common/src/main/resources/pack.mcmeta | 6 + 1.19.3/Fabric/build.gradle | 126 ++++++ .../craterlib/CraterLibInitializer.java | 42 ++ .../CraterLibModMenuIntegration.java | 30 ++ .../client/CraterLibClientInitializer.java | 27 ++ .../client/FabricClientPlatform.java | 34 ++ .../common/FabricCommonPlatform.java | 18 + .../craterlib/common/FabricCompatHelper.java | 23 + .../craterlib/common/FabricLoaderHelper.java | 69 +++ .../craterlib/compat/FabricTailor.java | 23 + .../hypherionmc/craterlib/compat/Vanish.java | 25 ++ .../ServerGamePacketListenerImplMixin.java | 36 ++ .../craterlib/mixin/TutorialMixin.java | 24 ++ .../network/CraterFabricNetworkHandler.java | 81 ++++ ...nmc.craterlib.core.platform.ClientPlatform | 1 + ...nmc.craterlib.core.platform.CommonPlatform | 1 + ...rionmc.craterlib.core.platform.CompatUtils | 1 + ...aterlib.core.platform.ModloaderEnvironment | 1 + .../assets/craterlib/craterlib_logo.png | Bin 0 -> 49343 bytes .../resources/craterlib.fabric.mixins.json | 17 + .../Fabric/src/main/resources/fabric.mod.json | 39 ++ 1.19.3/Forge/build.gradle | 112 +++++ .../com/hypherionmc/craterlib/CraterLib.java | 41 ++ .../craterlib/client/ForgeClientEvents.java | 25 ++ .../craterlib/client/ForgeClientHelper.java | 41 ++ .../craterlib/common/ForgeCommonHelper.java | 26 ++ .../craterlib/common/ForgeCompatHelper.java | 17 + .../craterlib/common/ForgeLoaderHelper.java | 73 ++++ .../craterlib/common/ForgeServerEvents.java | 43 ++ .../hypherionmc/craterlib/compat/Vanish.java | 24 ++ .../mixin/ConfigScreenHandlerMixin.java | 43 ++ .../ServerGamePacketListenerImplMixin.java | 36 ++ .../network/CraterForgeNetworkHandler.java | 100 +++++ .../src/main/resources/META-INF/mods.toml | 31 ++ ...nmc.craterlib.core.platform.ClientPlatform | 1 + ...nmc.craterlib.core.platform.CommonPlatform | 1 + ...rionmc.craterlib.core.platform.CompatUtils | 1 + ...aterlib.core.platform.ModloaderEnvironment | 1 + .../resources/craterlib.forge.mixins.json | 17 + .../src/main/resources/craterlib_logo.png | Bin 0 -> 49343 bytes 1.19.3/LICENSE | 21 + .../src/main/resources/craterlib_logo.png | Bin 0 -> 49343 bytes 1.19.3/README.md | 56 +++ 1.19.3/build.gradle | 110 +++++ 1.19.3/gradle.properties | 42 ++ 1.19.3/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59536 bytes .../gradle/wrapper/gradle-wrapper.properties | 5 + 1.19.3/gradlew | 183 ++++++++ 1.19.3/gradlew.bat | 89 ++++ 1.19.3/settings.gradle | 14 + 1.20.2/.gitattributes | 15 + 1.20.2/.gitignore | 29 ++ 1.20.2/.jenkins/Jenkinsfile.deploy | 55 +++ 1.20.2/.jenkins/Jenkinsfile.snapshot | 65 +++ 1.20.2/Common/build.gradle | 68 +++ .../craterlib/CraterConstants.java | 10 + .../craterlib/api/commands/CraterCommand.java | 53 +++ .../events/client/CraterClientTickEvent.java | 14 + .../client/CraterSinglePlayerEvent.java | 21 + .../api/events/client/LateInitEvent.java | 16 + .../events/client/PlayerJoinRealmEvent.java | 14 + .../api/events/client/ScreenEvent.java | 27 ++ .../events/common/CraterPlayerDeathEvent.java | 21 + .../events/server/CraterAdvancementEvent.java | 34 ++ .../api/events/server/CraterCommandEvent.java | 58 +++ .../api/events/server/CraterPlayerEvent.java | 30 ++ .../server/CraterRegisterCommandEvent.java | 15 + .../events/server/CraterServerChatEvent.java | 25 ++ .../server/CraterServerLifecycleEvent.java | 34 ++ .../events/server/MessageBroadcastEvent.java | 20 + .../events/server/PlayerPreLoginEvent.java | 20 + .../api/networking/CraterNetworkHandler.java | 28 ++ .../client/gui/config/CraterConfigScreen.java | 393 +++++++++++++++++ .../config/widgets/AbstractConfigWidget.java | 27 ++ .../client/gui/config/widgets/BaseWidget.java | 61 +++ .../config/widgets/InternalConfigButton.java | 51 +++ .../client/gui/config/widgets/Option.java | 71 ++++ .../gui/config/widgets/SubConfigWidget.java | 40 ++ .../gui/config/widgets/TextConfigOption.java | 45 ++ .../gui/config/widgets/ToggleButton.java | 33 ++ .../gui/config/widgets/WrappedEditBox.java | 29 ++ .../client/mentions/MentionCondition.java | 13 + .../client/mentions/MentionsController.java | 47 +++ .../core/config/ConfigController.java | 49 +++ .../craterlib/core/config/ModuleConfig.java | 184 ++++++++ .../config/annotations/HideFromScreen.java | 8 + .../config/annotations/NoConfigScreen.java | 12 + .../core/config/annotations/SubConfig.java | 16 + .../core/config/annotations/Syncable.java | 15 + .../core/config/annotations/Tooltip.java | 16 + .../craterlib/core/event/CraterEvent.java | 30 ++ .../craterlib/core/event/CraterEventBus.java | 241 +++++++++++ .../core/event/CraterEventPriority.java | 13 + .../core/event/annot/Cancellable.java | 8 + .../core/event/annot/CraterEventListener.java | 11 + .../CraterEventCancellationException.java | 9 + .../craterlib/core/event/package-info.java | 5 + .../core/networking/CraterPacketNetwork.java | 43 ++ .../networking/DeferredPacketRegistrar.java | 41 ++ .../core/networking/PacketRegistrar.java | 21 + .../core/networking/PacketRegistry.java | 41 ++ .../core/networking/data/PacketContext.java | 15 + .../core/networking/data/PacketHolder.java | 18 + .../core/networking/data/PacketSide.java | 13 + .../core/platform/ClientPlatform.java | 23 + .../core/platform/CommonPlatform.java | 15 + .../craterlib/core/platform/CompatUtils.java | 13 + .../craterlib/core/platform/Environment.java | 18 + .../core/platform/ModloaderEnvironment.java | 32 ++ .../core/rpcsdk/DiscordEventHandlers.java | 90 ++++ .../craterlib/core/rpcsdk/DiscordRPC.java | 99 +++++ .../core/rpcsdk/DiscordRichPresence.java | 248 +++++++++++ .../craterlib/core/rpcsdk/DiscordUser.java | 39 ++ .../callbacks/DisconnectedCallback.java | 18 + .../rpcsdk/callbacks/ErroredCallback.java | 18 + .../rpcsdk/callbacks/JoinGameCallback.java | 17 + .../rpcsdk/callbacks/JoinRequestCallback.java | 19 + .../core/rpcsdk/callbacks/ReadyCallback.java | 19 + .../callbacks/SpectateGameCallback.java | 17 + .../core/rpcsdk/helpers/RPCButton.java | 55 +++ .../mixin/ChatInputSuggestorMixin.java | 84 ++++ .../craterlib/mixin/events/CommandMixin.java | 37 ++ .../mixin/events/PlayerAdvancementsMixin.java | 31 ++ .../mixin/events/PlayerListMixin.java | 53 +++ .../craterlib/mixin/events/PlayerMixin.java | 21 + .../mixin/events/ServerPlayerMixin.java | 21 + .../mixin/events/client/ClientLevelMixin.java | 25 ++ .../mixin/events/client/MinecraftMixin.java | 31 ++ .../events/client/RealmsMainScreenMixin.java | 23 + .../advancements/BridgedAdvancement.java | 21 + .../advancements/BridgedDisplayInfo.java | 29 ++ .../nojang/authlib/BridgedGameProfile.java | 29 ++ .../nojang/client/BridgedMinecraft.java | 84 ++++ .../nojang/client/BridgedOptions.java | 15 + .../nojang/client/gui/BridgedScreen.java | 36 ++ .../multiplayer/BridgedClientLevel.java | 59 +++ .../client/multiplayer/BridgedServerData.java | 40 ++ .../server/BridgedIntegratedServer.java | 19 + .../commands/BridgedCommandSourceStack.java | 22 + .../nojang/commands/BridgedFakePlayer.java | 55 +++ .../nojang/commands/CommandsRegistry.java | 85 ++++ .../nojang/core/BridgedBlockPos.java | 27 ++ .../nojang/nbt/BridgedCompoundTag.java | 49 +++ .../network/BridgedFriendlyByteBuf.java | 34 ++ .../craterlib/nojang/package-info.java | 9 + .../realmsclient/dto/BridgedRealmsServer.java | 40 ++ .../nojang/resources/ResourceIdentifier.java | 36 ++ .../nojang/server/BridgedMinecraftServer.java | 91 ++++ .../world/entity/player/BridgedPlayer.java | 63 +++ .../craterlib/utils/ChatUtils.java | 96 +++++ .../craterlib/utils/InternalServiceUtil.java | 27 ++ .../craterlib/utils/OptifineUtils.java | 46 ++ .../craterlib/utils/TriConsumer.java | 5 + .../assets/craterlib/lang/en_us.json | 8 + .../src/main/resources/craterlib.mixins.json | 24 ++ 1.20.2/Common/src/main/resources/pack.mcmeta | 6 + 1.20.2/Fabric/build.gradle | 126 ++++++ .../craterlib/CraterLibInitializer.java | 42 ++ .../CraterLibModMenuIntegration.java | 30 ++ .../client/CraterLibClientInitializer.java | 27 ++ .../client/FabricClientPlatform.java | 34 ++ .../common/FabricCommonPlatform.java | 18 + .../craterlib/common/FabricCompatHelper.java | 23 + .../craterlib/common/FabricLoaderHelper.java | 69 +++ .../craterlib/compat/FabricTailor.java | 23 + .../hypherionmc/craterlib/compat/Vanish.java | 25 ++ .../ServerGamePacketListenerImplMixin.java | 36 ++ .../craterlib/mixin/TutorialMixin.java | 24 ++ .../network/CraterFabricNetworkHandler.java | 81 ++++ ...nmc.craterlib.core.platform.ClientPlatform | 1 + ...nmc.craterlib.core.platform.CommonPlatform | 1 + ...rionmc.craterlib.core.platform.CompatUtils | 1 + ...aterlib.core.platform.ModloaderEnvironment | 1 + .../assets/craterlib/craterlib_logo.png | Bin 0 -> 49343 bytes .../resources/craterlib.fabric.mixins.json | 17 + .../Fabric/src/main/resources/fabric.mod.json | 39 ++ 1.20.2/Forge/build.gradle | 112 +++++ .../com/hypherionmc/craterlib/CraterLib.java | 41 ++ .../craterlib/client/ForgeClientEvents.java | 25 ++ .../craterlib/client/ForgeClientHelper.java | 41 ++ .../craterlib/common/ForgeCommonHelper.java | 26 ++ .../craterlib/common/ForgeCompatHelper.java | 17 + .../craterlib/common/ForgeLoaderHelper.java | 73 ++++ .../craterlib/common/ForgeServerEvents.java | 43 ++ .../hypherionmc/craterlib/compat/Vanish.java | 24 ++ .../mixin/ConfigScreenHandlerMixin.java | 43 ++ .../ServerGamePacketListenerImplMixin.java | 36 ++ .../network/CraterForgeNetworkHandler.java | 98 +++++ .../src/main/resources/META-INF/mods.toml | 31 ++ ...nmc.craterlib.core.platform.ClientPlatform | 1 + ...nmc.craterlib.core.platform.CommonPlatform | 1 + ...rionmc.craterlib.core.platform.CompatUtils | 1 + ...aterlib.core.platform.ModloaderEnvironment | 1 + .../resources/craterlib.forge.mixins.json | 17 + .../src/main/resources/craterlib_logo.png | Bin 0 -> 49343 bytes 1.20.2/LICENSE | 21 + .../src/main/resources/craterlib_logo.png | Bin 0 -> 49343 bytes 1.20.2/README.md | 55 +++ 1.20.2/build.gradle | 110 +++++ 1.20.2/gradle.properties | 42 ++ 1.20.2/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59536 bytes .../gradle/wrapper/gradle-wrapper.properties | 5 + 1.20.2/gradlew | 183 ++++++++ 1.20.2/gradlew.bat | 89 ++++ 1.20.2/settings.gradle | 14 + 1.20.4/.gitattributes | 15 + 1.20.4/.gitignore | 29 ++ 1.20.4/.jenkins/Jenkinsfile.deploy | 55 +++ 1.20.4/.jenkins/Jenkinsfile.snapshot | 65 +++ 1.20.4/Common/build.gradle | 68 +++ .../craterlib/CraterConstants.java | 10 + .../craterlib/api/commands/CraterCommand.java | 53 +++ .../events/client/CraterClientTickEvent.java | 14 + .../client/CraterSinglePlayerEvent.java | 21 + .../api/events/client/LateInitEvent.java | 16 + .../events/client/PlayerJoinRealmEvent.java | 14 + .../api/events/client/ScreenEvent.java | 27 ++ .../events/common/CraterPlayerDeathEvent.java | 21 + .../events/server/CraterAdvancementEvent.java | 34 ++ .../api/events/server/CraterCommandEvent.java | 58 +++ .../api/events/server/CraterPlayerEvent.java | 30 ++ .../server/CraterRegisterCommandEvent.java | 15 + .../events/server/CraterServerChatEvent.java | 25 ++ .../server/CraterServerLifecycleEvent.java | 34 ++ .../events/server/MessageBroadcastEvent.java | 20 + .../events/server/PlayerPreLoginEvent.java | 20 + .../api/networking/CraterNetworkHandler.java | 28 ++ .../client/gui/config/CraterConfigScreen.java | 393 +++++++++++++++++ .../config/widgets/AbstractConfigWidget.java | 27 ++ .../client/gui/config/widgets/BaseWidget.java | 61 +++ .../config/widgets/InternalConfigButton.java | 51 +++ .../client/gui/config/widgets/Option.java | 71 ++++ .../gui/config/widgets/SubConfigWidget.java | 40 ++ .../gui/config/widgets/TextConfigOption.java | 45 ++ .../gui/config/widgets/ToggleButton.java | 33 ++ .../gui/config/widgets/WrappedEditBox.java | 29 ++ .../client/mentions/MentionCondition.java | 13 + .../client/mentions/MentionsController.java | 47 +++ .../core/config/ConfigController.java | 49 +++ .../craterlib/core/config/ModuleConfig.java | 184 ++++++++ .../config/annotations/HideFromScreen.java | 8 + .../config/annotations/NoConfigScreen.java | 12 + .../core/config/annotations/SubConfig.java | 16 + .../core/config/annotations/Syncable.java | 15 + .../core/config/annotations/Tooltip.java | 16 + .../craterlib/core/event/CraterEvent.java | 30 ++ .../craterlib/core/event/CraterEventBus.java | 241 +++++++++++ .../core/event/CraterEventPriority.java | 13 + .../core/event/annot/Cancellable.java | 8 + .../core/event/annot/CraterEventListener.java | 11 + .../CraterEventCancellationException.java | 9 + .../craterlib/core/event/package-info.java | 5 + .../core/networking/CraterPacketNetwork.java | 43 ++ .../networking/DeferredPacketRegistrar.java | 41 ++ .../core/networking/PacketRegistrar.java | 21 + .../core/networking/PacketRegistry.java | 41 ++ .../core/networking/data/PacketContext.java | 15 + .../core/networking/data/PacketHolder.java | 18 + .../core/networking/data/PacketSide.java | 13 + .../core/platform/ClientPlatform.java | 23 + .../core/platform/CommonPlatform.java | 15 + .../craterlib/core/platform/CompatUtils.java | 13 + .../craterlib/core/platform/Environment.java | 18 + .../core/platform/ModloaderEnvironment.java | 32 ++ .../core/rpcsdk/DiscordEventHandlers.java | 90 ++++ .../craterlib/core/rpcsdk/DiscordRPC.java | 99 +++++ .../core/rpcsdk/DiscordRichPresence.java | 248 +++++++++++ .../craterlib/core/rpcsdk/DiscordUser.java | 39 ++ .../callbacks/DisconnectedCallback.java | 18 + .../rpcsdk/callbacks/ErroredCallback.java | 18 + .../rpcsdk/callbacks/JoinGameCallback.java | 17 + .../rpcsdk/callbacks/JoinRequestCallback.java | 19 + .../core/rpcsdk/callbacks/ReadyCallback.java | 19 + .../callbacks/SpectateGameCallback.java | 17 + .../core/rpcsdk/helpers/RPCButton.java | 55 +++ .../mixin/ChatInputSuggestorMixin.java | 84 ++++ .../craterlib/mixin/events/CommandMixin.java | 37 ++ .../mixin/events/PlayerAdvancementsMixin.java | 31 ++ .../mixin/events/PlayerListMixin.java | 53 +++ .../craterlib/mixin/events/PlayerMixin.java | 21 + .../mixin/events/ServerPlayerMixin.java | 21 + .../mixin/events/client/ClientLevelMixin.java | 25 ++ .../mixin/events/client/MinecraftMixin.java | 31 ++ .../events/client/RealmsMainScreenMixin.java | 23 + .../advancements/BridgedAdvancement.java | 21 + .../advancements/BridgedDisplayInfo.java | 29 ++ .../nojang/authlib/BridgedGameProfile.java | 29 ++ .../nojang/client/BridgedMinecraft.java | 84 ++++ .../nojang/client/BridgedOptions.java | 15 + .../nojang/client/gui/BridgedScreen.java | 36 ++ .../multiplayer/BridgedClientLevel.java | 59 +++ .../client/multiplayer/BridgedServerData.java | 40 ++ .../server/BridgedIntegratedServer.java | 19 + .../commands/BridgedCommandSourceStack.java | 22 + .../nojang/commands/BridgedFakePlayer.java | 55 +++ .../nojang/commands/CommandsRegistry.java | 85 ++++ .../nojang/core/BridgedBlockPos.java | 27 ++ .../nojang/nbt/BridgedCompoundTag.java | 49 +++ .../network/BridgedFriendlyByteBuf.java | 34 ++ .../craterlib/nojang/package-info.java | 9 + .../realmsclient/dto/BridgedRealmsServer.java | 40 ++ .../nojang/resources/ResourceIdentifier.java | 36 ++ .../nojang/server/BridgedMinecraftServer.java | 91 ++++ .../world/entity/player/BridgedPlayer.java | 63 +++ .../craterlib/utils/ChatUtils.java | 96 +++++ .../craterlib/utils/InternalServiceUtil.java | 27 ++ .../craterlib/utils/OptifineUtils.java | 46 ++ .../craterlib/utils/TriConsumer.java | 5 + .../assets/craterlib/lang/en_us.json | 8 + .../src/main/resources/craterlib.mixins.json | 24 ++ 1.20.4/Common/src/main/resources/pack.mcmeta | 6 + 1.20.4/Fabric/build.gradle | 126 ++++++ .../craterlib/CraterLibInitializer.java | 42 ++ .../CraterLibModMenuIntegration.java | 30 ++ .../client/CraterLibClientInitializer.java | 27 ++ .../client/FabricClientPlatform.java | 34 ++ .../common/FabricCommonPlatform.java | 18 + .../craterlib/common/FabricCompatHelper.java | 23 + .../craterlib/common/FabricLoaderHelper.java | 69 +++ .../craterlib/compat/FabricTailor.java | 23 + .../hypherionmc/craterlib/compat/Vanish.java | 25 ++ .../ServerGamePacketListenerImplMixin.java | 36 ++ .../craterlib/mixin/TutorialMixin.java | 24 ++ .../network/CraterFabricNetworkHandler.java | 81 ++++ ...nmc.craterlib.core.platform.ClientPlatform | 1 + ...nmc.craterlib.core.platform.CommonPlatform | 1 + ...rionmc.craterlib.core.platform.CompatUtils | 1 + ...aterlib.core.platform.ModloaderEnvironment | 1 + .../assets/craterlib/craterlib_logo.png | Bin 0 -> 49343 bytes .../resources/craterlib.fabric.mixins.json | 17 + .../Fabric/src/main/resources/fabric.mod.json | 39 ++ 1.20.4/Forge/build.gradle | 112 +++++ .../com/hypherionmc/craterlib/CraterLib.java | 41 ++ .../craterlib/client/ForgeClientEvents.java | 25 ++ .../craterlib/client/ForgeClientHelper.java | 41 ++ .../craterlib/common/ForgeCommonHelper.java | 26 ++ .../craterlib/common/ForgeCompatHelper.java | 17 + .../craterlib/common/ForgeLoaderHelper.java | 73 ++++ .../craterlib/common/ForgeServerEvents.java | 43 ++ .../hypherionmc/craterlib/compat/Vanish.java | 24 ++ .../mixin/ConfigScreenHandlerMixin.java | 43 ++ .../ServerGamePacketListenerImplMixin.java | 36 ++ .../network/CraterForgeNetworkHandler.java | 98 +++++ .../src/main/resources/META-INF/mods.toml | 31 ++ ...nmc.craterlib.core.platform.ClientPlatform | 1 + ...nmc.craterlib.core.platform.CommonPlatform | 1 + ...rionmc.craterlib.core.platform.CompatUtils | 1 + ...aterlib.core.platform.ModloaderEnvironment | 1 + .../resources/craterlib.forge.mixins.json | 17 + .../src/main/resources/craterlib_logo.png | Bin 0 -> 49343 bytes 1.20.4/LICENSE | 21 + 1.20.4/NeoForge/build.gradle | 111 +++++ .../com/hypherionmc/craterlib/CraterLib.java | 44 ++ .../client/NeoForgeClientEvents.java | 25 ++ .../client/NeoForgeClientHelper.java | 40 ++ .../common/NeoForgeCommonHelper.java | 19 + .../common/NeoForgeCompatHelper.java | 22 + .../common/NeoForgeLoaderHelper.java | 73 ++++ .../common/NeoForgeServerEvents.java | 43 ++ .../hypherionmc/craterlib/compat/Vanish.java | 24 ++ .../mixin/ConfigScreenHandlerMixin.java | 43 ++ .../ServerGamePacketListenerImplMixin.java | 36 ++ .../network/CraterNeoForgeNetworkHandler.java | 116 ++++++ .../craterlib/network/NeoForgePacket.java | 21 + .../network/NeoForgePacketContainer.java | 16 + .../src/main/resources/META-INF/mods.toml | 33 ++ ...nmc.craterlib.core.platform.ClientPlatform | 1 + ...nmc.craterlib.core.platform.CommonPlatform | 1 + ...rionmc.craterlib.core.platform.CompatUtils | 1 + ...aterlib.core.platform.ModloaderEnvironment | 1 + .../resources/craterlib.neoforge.mixins.json | 17 + .../src/main/resources/craterlib_logo.png | Bin 0 -> 49343 bytes 1.20.4/README.md | 55 +++ 1.20.4/build.gradle | 110 +++++ 1.20.4/gradle.properties | 46 ++ 1.20.4/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59536 bytes .../gradle/wrapper/gradle-wrapper.properties | 5 + 1.20.4/gradlew | 183 ++++++++ 1.20.4/gradlew.bat | 89 ++++ 1.20.4/settings.gradle | 14 + 1.20/.gitattributes | 15 + 1.20/.gitignore | 29 ++ 1.20/.jenkins/Jenkinsfile.deploy | 55 +++ 1.20/.jenkins/Jenkinsfile.snapshot | 65 +++ 1.20/Common/build.gradle | 68 +++ .../craterlib/CraterConstants.java | 10 + .../craterlib/api/commands/CraterCommand.java | 53 +++ .../events/client/CraterClientTickEvent.java | 14 + .../client/CraterSinglePlayerEvent.java | 21 + .../api/events/client/LateInitEvent.java | 16 + .../events/client/PlayerJoinRealmEvent.java | 14 + .../api/events/client/ScreenEvent.java | 27 ++ .../events/common/CraterPlayerDeathEvent.java | 21 + .../events/server/CraterAdvancementEvent.java | 34 ++ .../api/events/server/CraterCommandEvent.java | 58 +++ .../api/events/server/CraterPlayerEvent.java | 30 ++ .../server/CraterRegisterCommandEvent.java | 15 + .../events/server/CraterServerChatEvent.java | 25 ++ .../server/CraterServerLifecycleEvent.java | 34 ++ .../events/server/MessageBroadcastEvent.java | 20 + .../events/server/PlayerPreLoginEvent.java | 20 + .../api/networking/CraterNetworkHandler.java | 28 ++ .../client/gui/config/CraterConfigScreen.java | 393 +++++++++++++++++ .../config/widgets/AbstractConfigWidget.java | 27 ++ .../client/gui/config/widgets/BaseWidget.java | 61 +++ .../config/widgets/InternalConfigButton.java | 52 +++ .../client/gui/config/widgets/Option.java | 71 ++++ .../gui/config/widgets/SubConfigWidget.java | 40 ++ .../gui/config/widgets/TextConfigOption.java | 45 ++ .../gui/config/widgets/ToggleButton.java | 33 ++ .../gui/config/widgets/WrappedEditBox.java | 29 ++ .../client/mentions/MentionCondition.java | 13 + .../client/mentions/MentionsController.java | 47 +++ .../core/config/ConfigController.java | 49 +++ .../craterlib/core/config/ModuleConfig.java | 184 ++++++++ .../config/annotations/HideFromScreen.java | 8 + .../config/annotations/NoConfigScreen.java | 12 + .../core/config/annotations/SubConfig.java | 16 + .../core/config/annotations/Syncable.java | 15 + .../core/config/annotations/Tooltip.java | 16 + .../craterlib/core/event/CraterEvent.java | 30 ++ .../craterlib/core/event/CraterEventBus.java | 241 +++++++++++ .../core/event/CraterEventPriority.java | 13 + .../core/event/annot/Cancellable.java | 8 + .../core/event/annot/CraterEventListener.java | 11 + .../CraterEventCancellationException.java | 9 + .../craterlib/core/event/package-info.java | 5 + .../core/networking/CraterPacketNetwork.java | 43 ++ .../networking/DeferredPacketRegistrar.java | 41 ++ .../core/networking/PacketRegistrar.java | 21 + .../core/networking/PacketRegistry.java | 41 ++ .../core/networking/data/PacketContext.java | 15 + .../core/networking/data/PacketHolder.java | 18 + .../core/networking/data/PacketSide.java | 13 + .../core/platform/ClientPlatform.java | 23 + .../core/platform/CommonPlatform.java | 15 + .../craterlib/core/platform/CompatUtils.java | 13 + .../craterlib/core/platform/Environment.java | 18 + .../core/platform/ModloaderEnvironment.java | 32 ++ .../core/rpcsdk/DiscordEventHandlers.java | 90 ++++ .../craterlib/core/rpcsdk/DiscordRPC.java | 99 +++++ .../core/rpcsdk/DiscordRichPresence.java | 248 +++++++++++ .../craterlib/core/rpcsdk/DiscordUser.java | 39 ++ .../callbacks/DisconnectedCallback.java | 18 + .../rpcsdk/callbacks/ErroredCallback.java | 18 + .../rpcsdk/callbacks/JoinGameCallback.java | 17 + .../rpcsdk/callbacks/JoinRequestCallback.java | 19 + .../core/rpcsdk/callbacks/ReadyCallback.java | 19 + .../callbacks/SpectateGameCallback.java | 17 + .../core/rpcsdk/helpers/RPCButton.java | 55 +++ .../mixin/ChatInputSuggestorMixin.java | 84 ++++ .../craterlib/mixin/events/CommandMixin.java | 37 ++ .../mixin/events/PlayerAdvancementsMixin.java | 29 ++ .../mixin/events/PlayerListMixin.java | 52 +++ .../craterlib/mixin/events/PlayerMixin.java | 21 + .../mixin/events/ServerPlayerMixin.java | 21 + .../mixin/events/client/ClientLevelMixin.java | 25 ++ .../mixin/events/client/MinecraftMixin.java | 31 ++ .../events/client/RealmsMainScreenMixin.java | 23 + .../advancements/BridgedAdvancement.java | 21 + .../advancements/BridgedDisplayInfo.java | 29 ++ .../nojang/authlib/BridgedGameProfile.java | 29 ++ .../nojang/client/BridgedMinecraft.java | 84 ++++ .../nojang/client/BridgedOptions.java | 15 + .../nojang/client/gui/BridgedScreen.java | 36 ++ .../multiplayer/BridgedClientLevel.java | 59 +++ .../client/multiplayer/BridgedServerData.java | 40 ++ .../server/BridgedIntegratedServer.java | 19 + .../commands/BridgedCommandSourceStack.java | 22 + .../nojang/commands/BridgedFakePlayer.java | 55 +++ .../nojang/commands/CommandsRegistry.java | 85 ++++ .../nojang/core/BridgedBlockPos.java | 27 ++ .../nojang/nbt/BridgedCompoundTag.java | 49 +++ .../network/BridgedFriendlyByteBuf.java | 34 ++ .../craterlib/nojang/package-info.java | 9 + .../realmsclient/dto/BridgedRealmsServer.java | 40 ++ .../nojang/resources/ResourceIdentifier.java | 36 ++ .../nojang/server/BridgedMinecraftServer.java | 91 ++++ .../world/entity/player/BridgedPlayer.java | 63 +++ .../craterlib/utils/ChatUtils.java | 96 +++++ .../craterlib/utils/InternalServiceUtil.java | 27 ++ .../craterlib/utils/OptifineUtils.java | 46 ++ .../craterlib/utils/TriConsumer.java | 5 + .../assets/craterlib/lang/en_us.json | 8 + .../src/main/resources/craterlib.mixins.json | 24 ++ 1.20/Common/src/main/resources/pack.mcmeta | 6 + 1.20/Fabric/build.gradle | 126 ++++++ .../craterlib/CraterLibInitializer.java | 42 ++ .../CraterLibModMenuIntegration.java | 30 ++ .../client/CraterLibClientInitializer.java | 27 ++ .../client/FabricClientPlatform.java | 34 ++ .../common/FabricCommonPlatform.java | 18 + .../craterlib/common/FabricCompatHelper.java | 23 + .../craterlib/common/FabricLoaderHelper.java | 69 +++ .../craterlib/compat/FabricTailor.java | 23 + .../hypherionmc/craterlib/compat/Vanish.java | 25 ++ .../ServerGamePacketListenerImplMixin.java | 36 ++ .../craterlib/mixin/TutorialMixin.java | 24 ++ .../network/CraterFabricNetworkHandler.java | 81 ++++ ...nmc.craterlib.core.platform.ClientPlatform | 1 + ...nmc.craterlib.core.platform.CommonPlatform | 1 + ...rionmc.craterlib.core.platform.CompatUtils | 1 + ...aterlib.core.platform.ModloaderEnvironment | 1 + .../assets/craterlib/craterlib_logo.png | Bin 0 -> 49343 bytes .../resources/craterlib.fabric.mixins.json | 17 + .../Fabric/src/main/resources/fabric.mod.json | 39 ++ 1.20/Forge/build.gradle | 112 +++++ .../com/hypherionmc/craterlib/CraterLib.java | 41 ++ .../craterlib/client/ForgeClientEvents.java | 25 ++ .../craterlib/client/ForgeClientHelper.java | 41 ++ .../craterlib/common/ForgeCommonHelper.java | 26 ++ .../craterlib/common/ForgeCompatHelper.java | 17 + .../craterlib/common/ForgeLoaderHelper.java | 73 ++++ .../craterlib/common/ForgeServerEvents.java | 43 ++ .../hypherionmc/craterlib/compat/Vanish.java | 24 ++ .../mixin/ConfigScreenHandlerMixin.java | 43 ++ .../ServerGamePacketListenerImplMixin.java | 36 ++ .../network/CraterForgeNetworkHandler.java | 100 +++++ .../src/main/resources/META-INF/mods.toml | 31 ++ ...nmc.craterlib.core.platform.ClientPlatform | 1 + ...nmc.craterlib.core.platform.CommonPlatform | 1 + ...rionmc.craterlib.core.platform.CompatUtils | 1 + ...aterlib.core.platform.ModloaderEnvironment | 1 + .../resources/craterlib.forge.mixins.json | 17 + .../src/main/resources/craterlib_logo.png | Bin 0 -> 49343 bytes 1.20/LICENSE | 21 + .../src/main/resources/craterlib_logo.png | Bin 0 -> 49343 bytes 1.20/README.md | 55 +++ 1.20/build.gradle | 109 +++++ 1.20/gradle.properties | 42 ++ 1.20/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59536 bytes 1.20/gradle/wrapper/gradle-wrapper.properties | 5 + 1.20/gradlew | 183 ++++++++ 1.20/gradlew.bat | 89 ++++ 1.20/settings.gradle | 14 + README.md | 2 +- build.gradle | 6 +- commit.sha | 2 +- gradle.properties | 3 - orbit.json | 4 - .../1.18.2/.jenkins/Jenkinsfile.deploy.patch | 22 +- .../.jenkins/Jenkinsfile.snapshot.patch | 56 +-- .../mixin/events/PlayerListMixin.java.patch | 4 +- .../craterlib/utils/ChatUtils.java.patch | 32 +- .../CraterFabricNetworkHandler.java.patch | 6 +- .../craterlib/CraterLib.java.patch | 6 +- patches/1.18.2/README.md.patch | 5 +- patches/1.18.2/build.gradle.patch | 30 ++ patches/1.18.2/orbit.json.patch | 7 - patches/1.18.2/settings.gradle.patch | 6 +- .../1.19.2/.jenkins/Jenkinsfile.deploy.patch | 22 +- .../.jenkins/Jenkinsfile.snapshot.patch | 56 +-- .../craterlib/utils/ChatUtils.java.patch | 26 +- ...rverGamePacketListenerImplMixin.java.patch | 16 +- .../CraterFabricNetworkHandler.java.patch | 6 +- .../craterlib/CraterLib.java.patch | 6 +- .../craterlib/compat/Vanish.java.patch | 3 +- ...rverGamePacketListenerImplMixin.java.patch | 16 +- patches/1.19.2/README.md.patch | 5 +- patches/1.19.2/build.gradle.patch | 30 ++ patches/1.19.2/orbit.json.patch | 7 - patches/1.19.2/settings.gradle.patch | 6 +- .../1.19.3/.jenkins/Jenkinsfile.deploy.patch | 22 +- .../.jenkins/Jenkinsfile.snapshot.patch | 50 +-- .../craterlib/utils/ChatUtils.java.patch | 26 +- ...rverGamePacketListenerImplMixin.java.patch | 15 +- .../CraterFabricNetworkHandler.java.patch | 6 +- .../craterlib/CraterLib.java.patch | 6 +- .../craterlib/compat/Vanish.java.patch | 3 +- ...rverGamePacketListenerImplMixin.java.patch | 15 +- patches/1.19.3/README.md.patch | 5 +- patches/1.19.3/build.gradle.patch | 30 ++ patches/1.19.3/orbit.json.patch | 7 - patches/1.19.3/settings.gradle.patch | 6 +- .../1.20.2/.jenkins/Jenkinsfile.deploy.patch | 22 +- .../.jenkins/Jenkinsfile.snapshot.patch | 56 +-- .../craterlib/utils/ChatUtils.java.patch | 26 +- .../CraterFabricNetworkHandler.java.patch | 6 +- .../craterlib/CraterLib.java.patch | 6 +- .../craterlib/compat/Vanish.java.patch | 3 +- patches/1.20.2/README.md.patch | 5 +- patches/1.20.2/build.gradle.patch | 30 ++ patches/1.20.2/orbit.json.patch | 7 - patches/1.20.2/settings.gradle.patch | 6 +- .../1.20.4/.jenkins/Jenkinsfile.deploy.patch | 22 +- .../.jenkins/Jenkinsfile.snapshot.patch | 45 +- .../craterlib/utils/ChatUtils.java.patch | 15 +- .../CraterFabricNetworkHandler.java.patch | 6 +- .../craterlib/CraterLib.java.patch | 6 +- .../craterlib/compat/Vanish.java.patch | 3 +- .../CraterNeoForgeNetworkHandler.java.patch | 5 +- patches/1.20.4/README.md.patch | 5 +- patches/1.20.4/build.gradle.patch | 21 + patches/1.20.4/orbit.json.patch | 7 - patches/1.20.4/settings.gradle.patch | 6 +- .../1.20/.jenkins/Jenkinsfile.deploy.patch | 22 +- .../1.20/.jenkins/Jenkinsfile.snapshot.patch | 56 +-- .../craterlib/utils/ChatUtils.java.patch | 26 +- ...rverGamePacketListenerImplMixin.java.patch | 15 +- .../CraterFabricNetworkHandler.java.patch | 6 +- .../craterlib/CraterLib.java.patch | 6 +- .../craterlib/compat/Vanish.java.patch | 3 +- ...rverGamePacketListenerImplMixin.java.patch | 15 +- patches/1.20/README.md.patch | 5 +- patches/1.20/build.gradle.patch | 31 ++ patches/1.20/orbit.json.patch | 7 - patches/1.20/settings.gradle.patch | 6 +- 1023 files changed, 38304 insertions(+), 596 deletions(-) rename .gitattributes => 1.18.2/.gitattributes (100%) create mode 100644 1.18.2/.gitignore create mode 100644 1.18.2/.jenkins/Jenkinsfile.deploy create mode 100644 1.18.2/.jenkins/Jenkinsfile.snapshot create mode 100644 1.18.2/Common/build.gradle create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/CraterConstants.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/commands/CraterCommand.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/CraterClientTickEvent.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/CraterSinglePlayerEvent.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/LateInitEvent.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/PlayerJoinRealmEvent.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/ScreenEvent.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/common/CraterPlayerDeathEvent.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterAdvancementEvent.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterCommandEvent.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterPlayerEvent.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterRegisterCommandEvent.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterServerChatEvent.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterServerLifecycleEvent.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/MessageBroadcastEvent.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/PlayerPreLoginEvent.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/networking/CraterNetworkHandler.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/CraterConfigScreen.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/AbstractConfigWidget.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/BaseWidget.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/InternalConfigButton.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/Option.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/SubConfigWidget.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/TextConfigOption.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/ToggleButton.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/WrappedEditBox.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/client/mentions/MentionCondition.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/client/mentions/MentionsController.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/ConfigController.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/ModuleConfig.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/HideFromScreen.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/NoConfigScreen.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/SubConfig.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/Syncable.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/Tooltip.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEvent.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEventBus.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEventPriority.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/annot/Cancellable.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/annot/CraterEventListener.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/exception/CraterEventCancellationException.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/package-info.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/CraterPacketNetwork.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/DeferredPacketRegistrar.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/PacketRegistrar.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/PacketRegistry.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketContext.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketHolder.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketSide.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/ClientPlatform.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/CommonPlatform.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/CompatUtils.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/Environment.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/ModloaderEnvironment.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordEventHandlers.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordRPC.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordRichPresence.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordUser.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/DisconnectedCallback.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/ErroredCallback.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/JoinGameCallback.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/JoinRequestCallback.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/ReadyCallback.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/SpectateGameCallback.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/helpers/RPCButton.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/ChatInputSuggestorMixin.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/CommandMixin.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerAdvancementsMixin.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerListMixin.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerMixin.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/ServerPlayerMixin.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/ClientLevelMixin.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/MinecraftMixin.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/RealmsMainScreenMixin.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/advancements/BridgedAdvancement.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/advancements/BridgedDisplayInfo.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/authlib/BridgedGameProfile.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/BridgedMinecraft.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/BridgedOptions.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/gui/BridgedScreen.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/multiplayer/BridgedClientLevel.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/multiplayer/BridgedServerData.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/server/BridgedIntegratedServer.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/BridgedCommandSourceStack.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/BridgedFakePlayer.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/CommandsRegistry.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/core/BridgedBlockPos.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/nbt/BridgedCompoundTag.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/network/BridgedFriendlyByteBuf.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/package-info.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/realmsclient/dto/BridgedRealmsServer.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/resources/ResourceIdentifier.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/server/BridgedMinecraftServer.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/world/entity/player/BridgedPlayer.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/utils/InternalServiceUtil.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/utils/OptifineUtils.java create mode 100644 1.18.2/Common/src/main/java/com/hypherionmc/craterlib/utils/TriConsumer.java create mode 100644 1.18.2/Common/src/main/resources/assets/craterlib/lang/en_us.json create mode 100644 1.18.2/Common/src/main/resources/craterlib.mixins.json create mode 100644 1.18.2/Common/src/main/resources/pack.mcmeta create mode 100644 1.18.2/Fabric/build.gradle create mode 100644 1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/CraterLibInitializer.java create mode 100644 1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/CraterLibModMenuIntegration.java create mode 100644 1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/client/CraterLibClientInitializer.java create mode 100644 1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/client/FabricClientPlatform.java create mode 100644 1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricCommonPlatform.java create mode 100644 1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricCompatHelper.java create mode 100644 1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricLoaderHelper.java create mode 100644 1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/compat/FabricTailor.java create mode 100644 1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java create mode 100644 1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java create mode 100644 1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/TutorialMixin.java create mode 100644 1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java create mode 100644 1.18.2/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform create mode 100644 1.18.2/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform create mode 100644 1.18.2/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils create mode 100644 1.18.2/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment create mode 100644 1.18.2/Fabric/src/main/resources/assets/craterlib/craterlib_logo.png create mode 100644 1.18.2/Fabric/src/main/resources/craterlib.fabric.mixins.json create mode 100644 1.18.2/Fabric/src/main/resources/fabric.mod.json create mode 100644 1.18.2/Forge/build.gradle create mode 100644 1.18.2/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java create mode 100644 1.18.2/Forge/src/main/java/com/hypherionmc/craterlib/client/ForgeClientEvents.java create mode 100644 1.18.2/Forge/src/main/java/com/hypherionmc/craterlib/client/ForgeClientHelper.java create mode 100644 1.18.2/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeCommonHelper.java create mode 100644 1.18.2/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeCompatHelper.java create mode 100644 1.18.2/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeLoaderHelper.java create mode 100644 1.18.2/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeServerEvents.java create mode 100644 1.18.2/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java create mode 100644 1.18.2/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ConfigScreenHandlerMixin.java create mode 100644 1.18.2/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java create mode 100644 1.18.2/Forge/src/main/java/com/hypherionmc/craterlib/network/CraterForgeNetworkHandler.java create mode 100644 1.18.2/Forge/src/main/resources/META-INF/mods.toml create mode 100644 1.18.2/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform create mode 100644 1.18.2/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform create mode 100644 1.18.2/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils create mode 100644 1.18.2/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment create mode 100644 1.18.2/Forge/src/main/resources/craterlib.forge.mixins.json create mode 100644 1.18.2/Forge/src/main/resources/craterlib_logo.png create mode 100644 1.18.2/LICENSE create mode 100644 1.18.2/NeoForge/src/main/resources/craterlib_logo.png create mode 100644 1.18.2/README.md create mode 100644 1.18.2/build.gradle create mode 100644 1.18.2/gradle.properties create mode 100644 1.18.2/gradle/wrapper/gradle-wrapper.jar create mode 100644 1.18.2/gradle/wrapper/gradle-wrapper.properties create mode 100644 1.18.2/gradlew create mode 100644 1.18.2/gradlew.bat create mode 100644 1.18.2/settings.gradle create mode 100644 1.19.2/.gitattributes create mode 100644 1.19.2/.gitignore create mode 100644 1.19.2/.jenkins/Jenkinsfile.deploy create mode 100644 1.19.2/.jenkins/Jenkinsfile.snapshot create mode 100644 1.19.2/Common/build.gradle create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/CraterConstants.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/commands/CraterCommand.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/CraterClientTickEvent.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/CraterSinglePlayerEvent.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/LateInitEvent.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/PlayerJoinRealmEvent.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/ScreenEvent.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/common/CraterPlayerDeathEvent.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterAdvancementEvent.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterCommandEvent.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterPlayerEvent.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterRegisterCommandEvent.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterServerChatEvent.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterServerLifecycleEvent.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/MessageBroadcastEvent.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/PlayerPreLoginEvent.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/networking/CraterNetworkHandler.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/CraterConfigScreen.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/AbstractConfigWidget.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/BaseWidget.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/InternalConfigButton.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/Option.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/SubConfigWidget.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/TextConfigOption.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/ToggleButton.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/WrappedEditBox.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/client/mentions/MentionCondition.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/client/mentions/MentionsController.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/ConfigController.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/ModuleConfig.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/HideFromScreen.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/NoConfigScreen.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/SubConfig.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/Syncable.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/Tooltip.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEvent.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEventBus.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEventPriority.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/annot/Cancellable.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/annot/CraterEventListener.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/exception/CraterEventCancellationException.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/package-info.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/CraterPacketNetwork.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/DeferredPacketRegistrar.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/PacketRegistrar.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/PacketRegistry.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketContext.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketHolder.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketSide.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/ClientPlatform.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/CommonPlatform.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/CompatUtils.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/Environment.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/ModloaderEnvironment.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordEventHandlers.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordRPC.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordRichPresence.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordUser.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/DisconnectedCallback.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/ErroredCallback.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/JoinGameCallback.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/JoinRequestCallback.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/ReadyCallback.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/SpectateGameCallback.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/helpers/RPCButton.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/ChatInputSuggestorMixin.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/CommandMixin.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerAdvancementsMixin.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerListMixin.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerMixin.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/ServerPlayerMixin.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/ClientLevelMixin.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/MinecraftMixin.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/RealmsMainScreenMixin.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/advancements/BridgedAdvancement.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/advancements/BridgedDisplayInfo.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/authlib/BridgedGameProfile.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/BridgedMinecraft.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/BridgedOptions.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/gui/BridgedScreen.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/multiplayer/BridgedClientLevel.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/multiplayer/BridgedServerData.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/server/BridgedIntegratedServer.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/BridgedCommandSourceStack.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/BridgedFakePlayer.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/CommandsRegistry.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/core/BridgedBlockPos.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/nbt/BridgedCompoundTag.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/network/BridgedFriendlyByteBuf.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/package-info.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/realmsclient/dto/BridgedRealmsServer.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/resources/ResourceIdentifier.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/server/BridgedMinecraftServer.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/world/entity/player/BridgedPlayer.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/utils/InternalServiceUtil.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/utils/OptifineUtils.java create mode 100644 1.19.2/Common/src/main/java/com/hypherionmc/craterlib/utils/TriConsumer.java create mode 100644 1.19.2/Common/src/main/resources/assets/craterlib/lang/en_us.json create mode 100644 1.19.2/Common/src/main/resources/craterlib.mixins.json create mode 100644 1.19.2/Common/src/main/resources/pack.mcmeta create mode 100644 1.19.2/Fabric/build.gradle create mode 100644 1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/CraterLibInitializer.java create mode 100644 1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/CraterLibModMenuIntegration.java create mode 100644 1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/client/CraterLibClientInitializer.java create mode 100644 1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/client/FabricClientPlatform.java create mode 100644 1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricCommonPlatform.java create mode 100644 1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricCompatHelper.java create mode 100644 1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricLoaderHelper.java create mode 100644 1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/compat/FabricTailor.java create mode 100644 1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java create mode 100644 1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java create mode 100644 1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/TutorialMixin.java create mode 100644 1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java create mode 100644 1.19.2/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform create mode 100644 1.19.2/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform create mode 100644 1.19.2/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils create mode 100644 1.19.2/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment create mode 100644 1.19.2/Fabric/src/main/resources/assets/craterlib/craterlib_logo.png create mode 100644 1.19.2/Fabric/src/main/resources/craterlib.fabric.mixins.json create mode 100644 1.19.2/Fabric/src/main/resources/fabric.mod.json create mode 100644 1.19.2/Forge/build.gradle create mode 100644 1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java create mode 100644 1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/client/ForgeClientEvents.java create mode 100644 1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/client/ForgeClientHelper.java create mode 100644 1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeCommonHelper.java create mode 100644 1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeCompatHelper.java create mode 100644 1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeLoaderHelper.java create mode 100644 1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeServerEvents.java create mode 100644 1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java create mode 100644 1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ConfigScreenHandlerMixin.java create mode 100644 1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java create mode 100644 1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/network/CraterForgeNetworkHandler.java create mode 100644 1.19.2/Forge/src/main/resources/META-INF/mods.toml create mode 100644 1.19.2/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform create mode 100644 1.19.2/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform create mode 100644 1.19.2/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils create mode 100644 1.19.2/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment create mode 100644 1.19.2/Forge/src/main/resources/craterlib.forge.mixins.json create mode 100644 1.19.2/Forge/src/main/resources/craterlib_logo.png create mode 100644 1.19.2/LICENSE create mode 100644 1.19.2/NeoForge/src/main/resources/craterlib_logo.png create mode 100644 1.19.2/README.md create mode 100644 1.19.2/build.gradle create mode 100644 1.19.2/gradle.properties create mode 100644 1.19.2/gradle/wrapper/gradle-wrapper.jar create mode 100644 1.19.2/gradle/wrapper/gradle-wrapper.properties create mode 100644 1.19.2/gradlew create mode 100644 1.19.2/gradlew.bat create mode 100644 1.19.2/settings.gradle create mode 100644 1.19.3/.gitattributes create mode 100644 1.19.3/.gitignore create mode 100644 1.19.3/.jenkins/Jenkinsfile.deploy create mode 100644 1.19.3/.jenkins/Jenkinsfile.snapshot create mode 100644 1.19.3/Common/build.gradle create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/CraterConstants.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/commands/CraterCommand.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/CraterClientTickEvent.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/CraterSinglePlayerEvent.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/LateInitEvent.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/PlayerJoinRealmEvent.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/ScreenEvent.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/common/CraterPlayerDeathEvent.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterAdvancementEvent.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterCommandEvent.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterPlayerEvent.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterRegisterCommandEvent.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterServerChatEvent.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterServerLifecycleEvent.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/MessageBroadcastEvent.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/PlayerPreLoginEvent.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/networking/CraterNetworkHandler.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/CraterConfigScreen.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/AbstractConfigWidget.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/BaseWidget.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/InternalConfigButton.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/Option.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/SubConfigWidget.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/TextConfigOption.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/ToggleButton.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/WrappedEditBox.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/client/mentions/MentionCondition.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/client/mentions/MentionsController.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/config/ConfigController.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/config/ModuleConfig.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/HideFromScreen.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/NoConfigScreen.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/SubConfig.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/Syncable.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/Tooltip.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEvent.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEventBus.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEventPriority.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/event/annot/Cancellable.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/event/annot/CraterEventListener.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/event/exception/CraterEventCancellationException.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/event/package-info.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/networking/CraterPacketNetwork.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/networking/DeferredPacketRegistrar.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/networking/PacketRegistrar.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/networking/PacketRegistry.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketContext.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketHolder.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketSide.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/platform/ClientPlatform.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/platform/CommonPlatform.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/platform/CompatUtils.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/platform/Environment.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/platform/ModloaderEnvironment.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordEventHandlers.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordRPC.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordRichPresence.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordUser.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/DisconnectedCallback.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/ErroredCallback.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/JoinGameCallback.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/JoinRequestCallback.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/ReadyCallback.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/SpectateGameCallback.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/helpers/RPCButton.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/mixin/ChatInputSuggestorMixin.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/CommandMixin.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerAdvancementsMixin.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerListMixin.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerMixin.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/ServerPlayerMixin.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/ClientLevelMixin.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/MinecraftMixin.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/RealmsMainScreenMixin.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/advancements/BridgedAdvancement.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/advancements/BridgedDisplayInfo.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/authlib/BridgedGameProfile.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/BridgedMinecraft.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/BridgedOptions.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/gui/BridgedScreen.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/multiplayer/BridgedClientLevel.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/multiplayer/BridgedServerData.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/server/BridgedIntegratedServer.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/BridgedCommandSourceStack.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/BridgedFakePlayer.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/CommandsRegistry.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/core/BridgedBlockPos.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/nbt/BridgedCompoundTag.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/network/BridgedFriendlyByteBuf.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/package-info.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/realmsclient/dto/BridgedRealmsServer.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/resources/ResourceIdentifier.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/server/BridgedMinecraftServer.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/world/entity/player/BridgedPlayer.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/utils/InternalServiceUtil.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/utils/OptifineUtils.java create mode 100644 1.19.3/Common/src/main/java/com/hypherionmc/craterlib/utils/TriConsumer.java create mode 100644 1.19.3/Common/src/main/resources/assets/craterlib/lang/en_us.json create mode 100644 1.19.3/Common/src/main/resources/craterlib.mixins.json create mode 100644 1.19.3/Common/src/main/resources/pack.mcmeta create mode 100644 1.19.3/Fabric/build.gradle create mode 100644 1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/CraterLibInitializer.java create mode 100644 1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/CraterLibModMenuIntegration.java create mode 100644 1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/client/CraterLibClientInitializer.java create mode 100644 1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/client/FabricClientPlatform.java create mode 100644 1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricCommonPlatform.java create mode 100644 1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricCompatHelper.java create mode 100644 1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricLoaderHelper.java create mode 100644 1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/compat/FabricTailor.java create mode 100644 1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java create mode 100644 1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java create mode 100644 1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/TutorialMixin.java create mode 100644 1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java create mode 100644 1.19.3/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform create mode 100644 1.19.3/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform create mode 100644 1.19.3/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils create mode 100644 1.19.3/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment create mode 100644 1.19.3/Fabric/src/main/resources/assets/craterlib/craterlib_logo.png create mode 100644 1.19.3/Fabric/src/main/resources/craterlib.fabric.mixins.json create mode 100644 1.19.3/Fabric/src/main/resources/fabric.mod.json create mode 100644 1.19.3/Forge/build.gradle create mode 100644 1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java create mode 100644 1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/client/ForgeClientEvents.java create mode 100644 1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/client/ForgeClientHelper.java create mode 100644 1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeCommonHelper.java create mode 100644 1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeCompatHelper.java create mode 100644 1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeLoaderHelper.java create mode 100644 1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeServerEvents.java create mode 100644 1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java create mode 100644 1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ConfigScreenHandlerMixin.java create mode 100644 1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java create mode 100644 1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/network/CraterForgeNetworkHandler.java create mode 100644 1.19.3/Forge/src/main/resources/META-INF/mods.toml create mode 100644 1.19.3/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform create mode 100644 1.19.3/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform create mode 100644 1.19.3/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils create mode 100644 1.19.3/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment create mode 100644 1.19.3/Forge/src/main/resources/craterlib.forge.mixins.json create mode 100644 1.19.3/Forge/src/main/resources/craterlib_logo.png create mode 100644 1.19.3/LICENSE create mode 100644 1.19.3/NeoForge/src/main/resources/craterlib_logo.png create mode 100644 1.19.3/README.md create mode 100644 1.19.3/build.gradle create mode 100644 1.19.3/gradle.properties create mode 100644 1.19.3/gradle/wrapper/gradle-wrapper.jar create mode 100644 1.19.3/gradle/wrapper/gradle-wrapper.properties create mode 100644 1.19.3/gradlew create mode 100644 1.19.3/gradlew.bat create mode 100644 1.19.3/settings.gradle create mode 100644 1.20.2/.gitattributes create mode 100644 1.20.2/.gitignore create mode 100644 1.20.2/.jenkins/Jenkinsfile.deploy create mode 100644 1.20.2/.jenkins/Jenkinsfile.snapshot create mode 100644 1.20.2/Common/build.gradle create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/CraterConstants.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/commands/CraterCommand.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/CraterClientTickEvent.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/CraterSinglePlayerEvent.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/LateInitEvent.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/PlayerJoinRealmEvent.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/ScreenEvent.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/common/CraterPlayerDeathEvent.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterAdvancementEvent.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterCommandEvent.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterPlayerEvent.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterRegisterCommandEvent.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterServerChatEvent.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterServerLifecycleEvent.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/MessageBroadcastEvent.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/PlayerPreLoginEvent.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/networking/CraterNetworkHandler.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/CraterConfigScreen.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/AbstractConfigWidget.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/BaseWidget.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/InternalConfigButton.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/Option.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/SubConfigWidget.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/TextConfigOption.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/ToggleButton.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/WrappedEditBox.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/client/mentions/MentionCondition.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/client/mentions/MentionsController.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/ConfigController.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/ModuleConfig.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/HideFromScreen.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/NoConfigScreen.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/SubConfig.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/Syncable.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/Tooltip.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEvent.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEventBus.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEventPriority.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/annot/Cancellable.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/annot/CraterEventListener.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/exception/CraterEventCancellationException.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/package-info.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/CraterPacketNetwork.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/DeferredPacketRegistrar.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/PacketRegistrar.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/PacketRegistry.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketContext.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketHolder.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketSide.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/ClientPlatform.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/CommonPlatform.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/CompatUtils.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/Environment.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/ModloaderEnvironment.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordEventHandlers.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordRPC.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordRichPresence.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordUser.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/DisconnectedCallback.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/ErroredCallback.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/JoinGameCallback.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/JoinRequestCallback.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/ReadyCallback.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/SpectateGameCallback.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/helpers/RPCButton.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/ChatInputSuggestorMixin.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/CommandMixin.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerAdvancementsMixin.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerListMixin.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerMixin.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/ServerPlayerMixin.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/ClientLevelMixin.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/MinecraftMixin.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/RealmsMainScreenMixin.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/advancements/BridgedAdvancement.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/advancements/BridgedDisplayInfo.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/authlib/BridgedGameProfile.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/BridgedMinecraft.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/BridgedOptions.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/gui/BridgedScreen.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/multiplayer/BridgedClientLevel.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/multiplayer/BridgedServerData.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/server/BridgedIntegratedServer.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/BridgedCommandSourceStack.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/BridgedFakePlayer.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/CommandsRegistry.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/core/BridgedBlockPos.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/nbt/BridgedCompoundTag.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/network/BridgedFriendlyByteBuf.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/package-info.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/realmsclient/dto/BridgedRealmsServer.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/resources/ResourceIdentifier.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/server/BridgedMinecraftServer.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/world/entity/player/BridgedPlayer.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/utils/InternalServiceUtil.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/utils/OptifineUtils.java create mode 100644 1.20.2/Common/src/main/java/com/hypherionmc/craterlib/utils/TriConsumer.java create mode 100644 1.20.2/Common/src/main/resources/assets/craterlib/lang/en_us.json create mode 100644 1.20.2/Common/src/main/resources/craterlib.mixins.json create mode 100644 1.20.2/Common/src/main/resources/pack.mcmeta create mode 100644 1.20.2/Fabric/build.gradle create mode 100644 1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/CraterLibInitializer.java create mode 100644 1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/CraterLibModMenuIntegration.java create mode 100644 1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/client/CraterLibClientInitializer.java create mode 100644 1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/client/FabricClientPlatform.java create mode 100644 1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricCommonPlatform.java create mode 100644 1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricCompatHelper.java create mode 100644 1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricLoaderHelper.java create mode 100644 1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/compat/FabricTailor.java create mode 100644 1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java create mode 100644 1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java create mode 100644 1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/TutorialMixin.java create mode 100644 1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java create mode 100644 1.20.2/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform create mode 100644 1.20.2/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform create mode 100644 1.20.2/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils create mode 100644 1.20.2/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment create mode 100644 1.20.2/Fabric/src/main/resources/assets/craterlib/craterlib_logo.png create mode 100644 1.20.2/Fabric/src/main/resources/craterlib.fabric.mixins.json create mode 100644 1.20.2/Fabric/src/main/resources/fabric.mod.json create mode 100644 1.20.2/Forge/build.gradle create mode 100644 1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java create mode 100644 1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/client/ForgeClientEvents.java create mode 100644 1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/client/ForgeClientHelper.java create mode 100644 1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeCommonHelper.java create mode 100644 1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeCompatHelper.java create mode 100644 1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeLoaderHelper.java create mode 100644 1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeServerEvents.java create mode 100644 1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java create mode 100644 1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ConfigScreenHandlerMixin.java create mode 100644 1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java create mode 100644 1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/network/CraterForgeNetworkHandler.java create mode 100644 1.20.2/Forge/src/main/resources/META-INF/mods.toml create mode 100644 1.20.2/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform create mode 100644 1.20.2/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform create mode 100644 1.20.2/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils create mode 100644 1.20.2/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment create mode 100644 1.20.2/Forge/src/main/resources/craterlib.forge.mixins.json create mode 100644 1.20.2/Forge/src/main/resources/craterlib_logo.png create mode 100644 1.20.2/LICENSE create mode 100644 1.20.2/NeoForge/src/main/resources/craterlib_logo.png create mode 100644 1.20.2/README.md create mode 100644 1.20.2/build.gradle create mode 100644 1.20.2/gradle.properties create mode 100644 1.20.2/gradle/wrapper/gradle-wrapper.jar create mode 100644 1.20.2/gradle/wrapper/gradle-wrapper.properties create mode 100644 1.20.2/gradlew create mode 100644 1.20.2/gradlew.bat create mode 100644 1.20.2/settings.gradle create mode 100644 1.20.4/.gitattributes create mode 100644 1.20.4/.gitignore create mode 100644 1.20.4/.jenkins/Jenkinsfile.deploy create mode 100644 1.20.4/.jenkins/Jenkinsfile.snapshot create mode 100644 1.20.4/Common/build.gradle create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/CraterConstants.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/commands/CraterCommand.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/CraterClientTickEvent.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/CraterSinglePlayerEvent.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/LateInitEvent.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/PlayerJoinRealmEvent.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/ScreenEvent.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/common/CraterPlayerDeathEvent.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterAdvancementEvent.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterCommandEvent.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterPlayerEvent.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterRegisterCommandEvent.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterServerChatEvent.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterServerLifecycleEvent.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/MessageBroadcastEvent.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/PlayerPreLoginEvent.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/networking/CraterNetworkHandler.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/CraterConfigScreen.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/AbstractConfigWidget.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/BaseWidget.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/InternalConfigButton.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/Option.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/SubConfigWidget.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/TextConfigOption.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/ToggleButton.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/WrappedEditBox.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/client/mentions/MentionCondition.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/client/mentions/MentionsController.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/config/ConfigController.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/config/ModuleConfig.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/HideFromScreen.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/NoConfigScreen.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/SubConfig.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/Syncable.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/Tooltip.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEvent.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEventBus.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEventPriority.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/event/annot/Cancellable.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/event/annot/CraterEventListener.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/event/exception/CraterEventCancellationException.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/event/package-info.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/networking/CraterPacketNetwork.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/networking/DeferredPacketRegistrar.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/networking/PacketRegistrar.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/networking/PacketRegistry.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketContext.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketHolder.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketSide.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/platform/ClientPlatform.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/platform/CommonPlatform.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/platform/CompatUtils.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/platform/Environment.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/platform/ModloaderEnvironment.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordEventHandlers.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordRPC.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordRichPresence.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordUser.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/DisconnectedCallback.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/ErroredCallback.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/JoinGameCallback.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/JoinRequestCallback.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/ReadyCallback.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/SpectateGameCallback.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/helpers/RPCButton.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/mixin/ChatInputSuggestorMixin.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/CommandMixin.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerAdvancementsMixin.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerListMixin.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerMixin.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/ServerPlayerMixin.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/ClientLevelMixin.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/MinecraftMixin.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/RealmsMainScreenMixin.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/advancements/BridgedAdvancement.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/advancements/BridgedDisplayInfo.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/authlib/BridgedGameProfile.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/BridgedMinecraft.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/BridgedOptions.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/gui/BridgedScreen.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/multiplayer/BridgedClientLevel.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/multiplayer/BridgedServerData.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/server/BridgedIntegratedServer.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/BridgedCommandSourceStack.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/BridgedFakePlayer.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/CommandsRegistry.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/core/BridgedBlockPos.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/nbt/BridgedCompoundTag.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/network/BridgedFriendlyByteBuf.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/package-info.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/realmsclient/dto/BridgedRealmsServer.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/resources/ResourceIdentifier.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/server/BridgedMinecraftServer.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/world/entity/player/BridgedPlayer.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/utils/InternalServiceUtil.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/utils/OptifineUtils.java create mode 100644 1.20.4/Common/src/main/java/com/hypherionmc/craterlib/utils/TriConsumer.java create mode 100644 1.20.4/Common/src/main/resources/assets/craterlib/lang/en_us.json create mode 100644 1.20.4/Common/src/main/resources/craterlib.mixins.json create mode 100644 1.20.4/Common/src/main/resources/pack.mcmeta create mode 100644 1.20.4/Fabric/build.gradle create mode 100644 1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/CraterLibInitializer.java create mode 100644 1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/CraterLibModMenuIntegration.java create mode 100644 1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/client/CraterLibClientInitializer.java create mode 100644 1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/client/FabricClientPlatform.java create mode 100644 1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricCommonPlatform.java create mode 100644 1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricCompatHelper.java create mode 100644 1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricLoaderHelper.java create mode 100644 1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/compat/FabricTailor.java create mode 100644 1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java create mode 100644 1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java create mode 100644 1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/TutorialMixin.java create mode 100644 1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java create mode 100644 1.20.4/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform create mode 100644 1.20.4/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform create mode 100644 1.20.4/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils create mode 100644 1.20.4/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment create mode 100644 1.20.4/Fabric/src/main/resources/assets/craterlib/craterlib_logo.png create mode 100644 1.20.4/Fabric/src/main/resources/craterlib.fabric.mixins.json create mode 100644 1.20.4/Fabric/src/main/resources/fabric.mod.json create mode 100644 1.20.4/Forge/build.gradle create mode 100644 1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java create mode 100644 1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/client/ForgeClientEvents.java create mode 100644 1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/client/ForgeClientHelper.java create mode 100644 1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeCommonHelper.java create mode 100644 1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeCompatHelper.java create mode 100644 1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeLoaderHelper.java create mode 100644 1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeServerEvents.java create mode 100644 1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java create mode 100644 1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ConfigScreenHandlerMixin.java create mode 100644 1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java create mode 100644 1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/network/CraterForgeNetworkHandler.java create mode 100644 1.20.4/Forge/src/main/resources/META-INF/mods.toml create mode 100644 1.20.4/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform create mode 100644 1.20.4/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform create mode 100644 1.20.4/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils create mode 100644 1.20.4/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment create mode 100644 1.20.4/Forge/src/main/resources/craterlib.forge.mixins.json create mode 100644 1.20.4/Forge/src/main/resources/craterlib_logo.png create mode 100644 1.20.4/LICENSE create mode 100644 1.20.4/NeoForge/build.gradle create mode 100644 1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/CraterLib.java create mode 100644 1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/client/NeoForgeClientEvents.java create mode 100644 1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/client/NeoForgeClientHelper.java create mode 100644 1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/common/NeoForgeCommonHelper.java create mode 100644 1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/common/NeoForgeCompatHelper.java create mode 100644 1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/common/NeoForgeLoaderHelper.java create mode 100644 1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/common/NeoForgeServerEvents.java create mode 100644 1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java create mode 100644 1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/mixin/ConfigScreenHandlerMixin.java create mode 100644 1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java create mode 100644 1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/network/CraterNeoForgeNetworkHandler.java create mode 100644 1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/network/NeoForgePacket.java create mode 100644 1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/network/NeoForgePacketContainer.java create mode 100644 1.20.4/NeoForge/src/main/resources/META-INF/mods.toml create mode 100644 1.20.4/NeoForge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform create mode 100644 1.20.4/NeoForge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform create mode 100644 1.20.4/NeoForge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils create mode 100644 1.20.4/NeoForge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment create mode 100644 1.20.4/NeoForge/src/main/resources/craterlib.neoforge.mixins.json create mode 100644 1.20.4/NeoForge/src/main/resources/craterlib_logo.png create mode 100644 1.20.4/README.md create mode 100644 1.20.4/build.gradle create mode 100644 1.20.4/gradle.properties create mode 100644 1.20.4/gradle/wrapper/gradle-wrapper.jar create mode 100644 1.20.4/gradle/wrapper/gradle-wrapper.properties create mode 100644 1.20.4/gradlew create mode 100644 1.20.4/gradlew.bat create mode 100644 1.20.4/settings.gradle create mode 100644 1.20/.gitattributes create mode 100644 1.20/.gitignore create mode 100644 1.20/.jenkins/Jenkinsfile.deploy create mode 100644 1.20/.jenkins/Jenkinsfile.snapshot create mode 100644 1.20/Common/build.gradle create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/CraterConstants.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/api/commands/CraterCommand.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/CraterClientTickEvent.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/CraterSinglePlayerEvent.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/LateInitEvent.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/PlayerJoinRealmEvent.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/ScreenEvent.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/common/CraterPlayerDeathEvent.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterAdvancementEvent.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterCommandEvent.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterPlayerEvent.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterRegisterCommandEvent.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterServerChatEvent.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterServerLifecycleEvent.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/MessageBroadcastEvent.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/PlayerPreLoginEvent.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/api/networking/CraterNetworkHandler.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/CraterConfigScreen.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/AbstractConfigWidget.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/BaseWidget.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/InternalConfigButton.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/Option.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/SubConfigWidget.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/TextConfigOption.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/ToggleButton.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/WrappedEditBox.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/client/mentions/MentionCondition.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/client/mentions/MentionsController.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/core/config/ConfigController.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/core/config/ModuleConfig.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/HideFromScreen.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/NoConfigScreen.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/SubConfig.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/Syncable.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/Tooltip.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEvent.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEventBus.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEventPriority.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/core/event/annot/Cancellable.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/core/event/annot/CraterEventListener.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/core/event/exception/CraterEventCancellationException.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/core/event/package-info.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/core/networking/CraterPacketNetwork.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/core/networking/DeferredPacketRegistrar.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/core/networking/PacketRegistrar.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/core/networking/PacketRegistry.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketContext.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketHolder.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketSide.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/core/platform/ClientPlatform.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/core/platform/CommonPlatform.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/core/platform/CompatUtils.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/core/platform/Environment.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/core/platform/ModloaderEnvironment.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordEventHandlers.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordRPC.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordRichPresence.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordUser.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/DisconnectedCallback.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/ErroredCallback.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/JoinGameCallback.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/JoinRequestCallback.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/ReadyCallback.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/SpectateGameCallback.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/helpers/RPCButton.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/mixin/ChatInputSuggestorMixin.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/CommandMixin.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerAdvancementsMixin.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerListMixin.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerMixin.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/ServerPlayerMixin.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/ClientLevelMixin.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/MinecraftMixin.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/RealmsMainScreenMixin.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/advancements/BridgedAdvancement.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/advancements/BridgedDisplayInfo.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/authlib/BridgedGameProfile.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/BridgedMinecraft.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/BridgedOptions.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/gui/BridgedScreen.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/multiplayer/BridgedClientLevel.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/multiplayer/BridgedServerData.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/server/BridgedIntegratedServer.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/BridgedCommandSourceStack.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/BridgedFakePlayer.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/CommandsRegistry.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/core/BridgedBlockPos.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/nbt/BridgedCompoundTag.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/network/BridgedFriendlyByteBuf.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/package-info.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/realmsclient/dto/BridgedRealmsServer.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/resources/ResourceIdentifier.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/server/BridgedMinecraftServer.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/world/entity/player/BridgedPlayer.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/utils/InternalServiceUtil.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/utils/OptifineUtils.java create mode 100644 1.20/Common/src/main/java/com/hypherionmc/craterlib/utils/TriConsumer.java create mode 100644 1.20/Common/src/main/resources/assets/craterlib/lang/en_us.json create mode 100644 1.20/Common/src/main/resources/craterlib.mixins.json create mode 100644 1.20/Common/src/main/resources/pack.mcmeta create mode 100644 1.20/Fabric/build.gradle create mode 100644 1.20/Fabric/src/main/java/com/hypherionmc/craterlib/CraterLibInitializer.java create mode 100644 1.20/Fabric/src/main/java/com/hypherionmc/craterlib/CraterLibModMenuIntegration.java create mode 100644 1.20/Fabric/src/main/java/com/hypherionmc/craterlib/client/CraterLibClientInitializer.java create mode 100644 1.20/Fabric/src/main/java/com/hypherionmc/craterlib/client/FabricClientPlatform.java create mode 100644 1.20/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricCommonPlatform.java create mode 100644 1.20/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricCompatHelper.java create mode 100644 1.20/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricLoaderHelper.java create mode 100644 1.20/Fabric/src/main/java/com/hypherionmc/craterlib/compat/FabricTailor.java create mode 100644 1.20/Fabric/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java create mode 100644 1.20/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java create mode 100644 1.20/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/TutorialMixin.java create mode 100644 1.20/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java create mode 100644 1.20/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform create mode 100644 1.20/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform create mode 100644 1.20/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils create mode 100644 1.20/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment create mode 100644 1.20/Fabric/src/main/resources/assets/craterlib/craterlib_logo.png create mode 100644 1.20/Fabric/src/main/resources/craterlib.fabric.mixins.json create mode 100644 1.20/Fabric/src/main/resources/fabric.mod.json create mode 100644 1.20/Forge/build.gradle create mode 100644 1.20/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java create mode 100644 1.20/Forge/src/main/java/com/hypherionmc/craterlib/client/ForgeClientEvents.java create mode 100644 1.20/Forge/src/main/java/com/hypherionmc/craterlib/client/ForgeClientHelper.java create mode 100644 1.20/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeCommonHelper.java create mode 100644 1.20/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeCompatHelper.java create mode 100644 1.20/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeLoaderHelper.java create mode 100644 1.20/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeServerEvents.java create mode 100644 1.20/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java create mode 100644 1.20/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ConfigScreenHandlerMixin.java create mode 100644 1.20/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java create mode 100644 1.20/Forge/src/main/java/com/hypherionmc/craterlib/network/CraterForgeNetworkHandler.java create mode 100644 1.20/Forge/src/main/resources/META-INF/mods.toml create mode 100644 1.20/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform create mode 100644 1.20/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform create mode 100644 1.20/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils create mode 100644 1.20/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment create mode 100644 1.20/Forge/src/main/resources/craterlib.forge.mixins.json create mode 100644 1.20/Forge/src/main/resources/craterlib_logo.png create mode 100644 1.20/LICENSE create mode 100644 1.20/NeoForge/src/main/resources/craterlib_logo.png create mode 100644 1.20/README.md create mode 100644 1.20/build.gradle create mode 100644 1.20/gradle.properties create mode 100644 1.20/gradle/wrapper/gradle-wrapper.jar create mode 100644 1.20/gradle/wrapper/gradle-wrapper.properties create mode 100644 1.20/gradlew create mode 100644 1.20/gradlew.bat create mode 100644 1.20/settings.gradle delete mode 100644 gradle.properties delete mode 100644 orbit.json create mode 100644 patches/1.18.2/build.gradle.patch delete mode 100644 patches/1.18.2/orbit.json.patch create mode 100644 patches/1.19.2/build.gradle.patch delete mode 100644 patches/1.19.2/orbit.json.patch create mode 100644 patches/1.19.3/build.gradle.patch delete mode 100644 patches/1.19.3/orbit.json.patch create mode 100644 patches/1.20.2/build.gradle.patch delete mode 100644 patches/1.20.2/orbit.json.patch create mode 100644 patches/1.20.4/build.gradle.patch delete mode 100644 patches/1.20.4/orbit.json.patch create mode 100644 patches/1.20/build.gradle.patch delete mode 100644 patches/1.20/orbit.json.patch diff --git a/.gitignore b/.gitignore index dcfc706..91ad8e7 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,6 @@ run artifacts src/test/** dev -upstream \ No newline at end of file +upstream +rejects +workspace \ No newline at end of file diff --git a/.gitattributes b/1.18.2/.gitattributes similarity index 100% rename from .gitattributes rename to 1.18.2/.gitattributes diff --git a/1.18.2/.gitignore b/1.18.2/.gitignore new file mode 100644 index 0000000..966dad4 --- /dev/null +++ b/1.18.2/.gitignore @@ -0,0 +1,29 @@ +# eclipse +bin +*.launch +.settings +.metadata +.classpath +.project + +# idea +out +*.ipr +*.iws +*.iml +.idea + +# gradle +build +.gradle + +# other +eclipse +run + +artifacts +src/test/** + +workspace +upstream +rejects \ No newline at end of file diff --git a/1.18.2/.jenkins/Jenkinsfile.deploy b/1.18.2/.jenkins/Jenkinsfile.deploy new file mode 100644 index 0000000..ef25403 --- /dev/null +++ b/1.18.2/.jenkins/Jenkinsfile.deploy @@ -0,0 +1,55 @@ +def JDK = "17" + +pipeline { + agent { + docker { + image "registry.firstdark.dev/java${JDK}:latest" + alwaysPull true + args '-v gradle-cache:/home/gradle/.gradle' + } + } + + environment { + GRADLE_USER_HOME = '/home/gradle/.gradle' + } + + stages { + stage("Notify Discord") { + steps { + discordSend webhookURL: env.FDD_WH_ADMIN, + title: "Deploy Started: CraterLib 1.18.2 Deploy #${BUILD_NUMBER}", + link: env.BUILD_URL, + result: 'SUCCESS', + description: "Build: [${BUILD_NUMBER}](${env.BUILD_URL})" + } + } + stage("Prepare") { + steps { + sh "chmod +x ./gradlew" + sh "./gradlew clean" + } + } + stage("Publish to Modrinth/Curseforge") { + steps { + sh "./gradlew publishMod -Prelease=true" + } + } + stage("Publish to Maven") { + steps { + sh "./gradlew publish -Prelease=true" + } + } + } + post { + always { + sh "./gradlew --stop" + deleteDir() + + discordSend webhookURL: env.FDD_WH_ADMIN, + title: "CraterLib Port Deploy #${BUILD_NUMBER}", + link: env.BUILD_URL, + result: currentBuild.currentResult, + description: "Build: [${BUILD_NUMBER}](${env.BUILD_URL})\nStatus: ${currentBuild.currentResult}" + } + } +} diff --git a/1.18.2/.jenkins/Jenkinsfile.snapshot b/1.18.2/.jenkins/Jenkinsfile.snapshot new file mode 100644 index 0000000..61a5aee --- /dev/null +++ b/1.18.2/.jenkins/Jenkinsfile.snapshot @@ -0,0 +1,65 @@ +def projectName = "CraterLib"; +def projectIcon = "https://cdn.modrinth.com/data/Nn8Wasaq/a172c634683a11a2e9ae593e56eba7885743bb44.png"; +def JDK = "17"; +def majorMc = "1.18.2"; +def modLoaders = "forge|fabric|quilt"; +def supportedMc = "1.18.2"; +def reltype = "snapshot"; + +pipeline { + agent { + docker { + image "registry.firstdark.dev/java${JDK}:latest" + alwaysPull true + args '-v gradle-cache:/home/gradle/.gradle' + } + } + + environment { + GRADLE_USER_HOME = '/home/gradle/.gradle' + } + + stages { + stage("Notify Discord") { + steps { + discordSend webhookURL: env.SSS_WEBHOOK, + title: "Deploy Started: ${projectName} ${majorMc} Deploy #${BUILD_NUMBER}", + link: env.BUILD_URL, + result: 'SUCCESS', + description: "Build: [${BUILD_NUMBER}](${env.BUILD_URL})" + } + } + + stage("Prepare") { + steps { + sh "chmod +x ./gradlew" + sh "./gradlew build -PreleaseType=port" + } + } + + stage("Publish to Maven") { + steps { + catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') { + sh "./gradlew publish -PreleaseType=${reltype}" + } + } + } + } + + post { + always { + sh "./gradlew --stop" + + fddsnapshotter apiKey: env.PLATFORM_KEY, + projectSlug: "craterlib", + projectName: "${projectName}", + projectIcon: "${projectIcon}", + modLoaders: "${modLoaders}", + minecraftVersions: "${supportedMc}", + failWebhook: env.SSS_WEBHOOK, + publishWebhooks: "${env.SSS_WEBHOOK}|${env.FDD_WH}" + + deleteDir() + } + } +} diff --git a/1.18.2/Common/build.gradle b/1.18.2/Common/build.gradle new file mode 100644 index 0000000..843d62a --- /dev/null +++ b/1.18.2/Common/build.gradle @@ -0,0 +1,68 @@ +archivesBaseName = "${mod_name.replace(" ", "")}-Common-${minecraft_version}" + +dependencies { + +} + +shadowJar { + from sourceSets.main.output + configurations = [project.configurations.shade] + + dependencies { + exclude(dependency('com.google.code.gson:.*')) + + relocate 'me.hypherionmc.moonconfig', 'shadow.hypherionmc.moonconfig' + relocate 'me.hypherionmc.mcdiscordformatter', 'shadow.hypherionmc.mcdiscordformatter' + relocate 'net.kyori', 'shadow.kyori' + } + setArchiveClassifier("dev") +} + +/** + * =============================================================================== + * = DO NOT EDIT BELOW THIS LINE UNLESS YOU KNOW WHAT YOU ARE DOING = + * =============================================================================== + */ + +unimined.minecraft { + fabric { + loader fabric_loader + } + + defaultRemapJar = false +} + +processResources { + def buildProps = project.properties.clone() + + filesMatching(['pack.mcmeta']) { + expand buildProps + } +} + +/** + * Publishing Config + */ +publishing { + publications { + mavenCommon(MavenPublication) { + artifactId project.archivesBaseName + from components.java + + pom.withXml { + Node pomNode = asNode() + pomNode.dependencies.'*'.findAll() { + it.artifactId.text() == 'regutils-joined-fabric' || + it.artifactId.text() == 'core' || + it.artifactId.text() == 'toml' + }.each() { + it.parent().remove(it) + } + } + } + } + + repositories { + maven rootProject.orion.getPublishingMaven() + } +} \ No newline at end of file diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/CraterConstants.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/CraterConstants.java new file mode 100644 index 0000000..93edffc --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/CraterConstants.java @@ -0,0 +1,10 @@ +package com.hypherionmc.craterlib; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CraterConstants { + public static final String MOD_ID = "craterlib"; + public static final String MOD_NAME = "CraterLib"; + public static final Logger LOG = LoggerFactory.getLogger(MOD_NAME); +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/commands/CraterCommand.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/commands/CraterCommand.java new file mode 100644 index 0000000..cc4969e --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/commands/CraterCommand.java @@ -0,0 +1,53 @@ +package com.hypherionmc.craterlib.api.commands; + +import com.hypherionmc.craterlib.nojang.authlib.BridgedGameProfile; +import com.hypherionmc.craterlib.nojang.commands.BridgedCommandSourceStack; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.TriConsumer; +import com.mojang.brigadier.arguments.ArgumentType; +import lombok.Getter; +import net.minecraft.commands.arguments.GameProfileArgument; +import org.apache.commons.lang3.tuple.Pair; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.function.Consumer; + +@Getter +public class CraterCommand { + + private final HashMap, TriConsumer>> arguments = new LinkedHashMap<>(); + private Consumer executor; + + private final String commandName; + private int permissionLevel = 4; + + CraterCommand(String commandName) { + this.commandName = commandName; + } + + public static CraterCommand literal(String commandName) { + return new CraterCommand(commandName); + } + + public CraterCommand requiresPermission(int perm) { + this.permissionLevel = perm; + return this; + } + + public CraterCommand withGameProfileArgument(String key, TriConsumer, BridgedCommandSourceStack> executor) { + arguments.put(key, Pair.of(GameProfileArgument.gameProfile(), executor)); + return this; + } + + public CraterCommand executes(Consumer ctx) { + executor = ctx; + return this; + } + + public boolean hasArguments() { + return !arguments.isEmpty(); + } + +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/CraterClientTickEvent.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/CraterClientTickEvent.java new file mode 100644 index 0000000..c68c596 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/CraterClientTickEvent.java @@ -0,0 +1,14 @@ +package com.hypherionmc.craterlib.api.events.client; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedClientLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public class CraterClientTickEvent extends CraterEvent { + + private final BridgedClientLevel level; + +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/CraterSinglePlayerEvent.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/CraterSinglePlayerEvent.java new file mode 100644 index 0000000..ab671f0 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/CraterSinglePlayerEvent.java @@ -0,0 +1,21 @@ +package com.hypherionmc.craterlib.api.events.client; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public class CraterSinglePlayerEvent extends CraterEvent { + + private final BridgedPlayer player; + + public static class PlayerLogin extends CraterSinglePlayerEvent { + + public PlayerLogin(BridgedPlayer player) { + super(player); + } + + } +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/LateInitEvent.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/LateInitEvent.java new file mode 100644 index 0000000..33c06fb --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/LateInitEvent.java @@ -0,0 +1,16 @@ +package com.hypherionmc.craterlib.api.events.client; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.client.BridgedMinecraft; +import com.hypherionmc.craterlib.nojang.client.BridgedOptions; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public class LateInitEvent extends CraterEvent { + + private final BridgedMinecraft minecraft; + private final BridgedOptions options; + +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/PlayerJoinRealmEvent.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/PlayerJoinRealmEvent.java new file mode 100644 index 0000000..1ef95de --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/PlayerJoinRealmEvent.java @@ -0,0 +1,14 @@ +package com.hypherionmc.craterlib.api.events.client; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.realmsclient.dto.BridgedRealmsServer; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public class PlayerJoinRealmEvent extends CraterEvent { + + private final BridgedRealmsServer server; + +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/ScreenEvent.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/ScreenEvent.java new file mode 100644 index 0000000..034ca4a --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/ScreenEvent.java @@ -0,0 +1,27 @@ +package com.hypherionmc.craterlib.api.events.client; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.client.gui.BridgedScreen; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; + +@Getter +@RequiredArgsConstructor +public class ScreenEvent extends CraterEvent { + + private final BridgedScreen screen; + + @Getter + public static class Opening extends ScreenEvent { + + private final BridgedScreen currentScreen; + @Setter private BridgedScreen newScreen; + + public Opening(BridgedScreen currentScreen, BridgedScreen newScreen) { + super(newScreen); + this.currentScreen = currentScreen; + this.newScreen = newScreen; + } + } +} \ No newline at end of file diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/common/CraterPlayerDeathEvent.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/common/CraterPlayerDeathEvent.java new file mode 100644 index 0000000..02cd63c --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/common/CraterPlayerDeathEvent.java @@ -0,0 +1,21 @@ +package com.hypherionmc.craterlib.api.events.common; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.ChatUtils; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; +import net.minecraft.world.damagesource.DamageSource; + +@RequiredArgsConstructor +@Getter +public class CraterPlayerDeathEvent extends CraterEvent { + + private final BridgedPlayer player; + private final DamageSource damageSource; + + public Component getDeathMessage() { + return ChatUtils.mojangToAdventure(damageSource.getLocalizedDeathMessage(player.toMojang())); + } +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterAdvancementEvent.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterAdvancementEvent.java new file mode 100644 index 0000000..5cd659d --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterAdvancementEvent.java @@ -0,0 +1,34 @@ +package com.hypherionmc.craterlib.api.events.server; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.advancements.BridgedAdvancement; +import com.hypherionmc.craterlib.nojang.advancements.BridgedDisplayInfo; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import lombok.Getter; +import net.kyori.adventure.text.Component; + +import java.util.Optional; + +@Getter +public class CraterAdvancementEvent extends CraterEvent { + + private final BridgedAdvancement advancement; + private final BridgedPlayer player; + private final Component title; + private final Component description; + + public CraterAdvancementEvent(BridgedPlayer player, BridgedAdvancement advancement) { + this.advancement = advancement; + this.player = player; + + Optional displayInfo = advancement.displayInfo(); + + if (displayInfo.isPresent()) { + this.title = displayInfo.get().displayName(); + this.description = displayInfo.get().description(); + } else { + this.title = Component.text("Unknown"); + this.description = Component.text("Unknown"); + } + } +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterCommandEvent.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterCommandEvent.java new file mode 100644 index 0000000..07327ad --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterCommandEvent.java @@ -0,0 +1,58 @@ +package com.hypherionmc.craterlib.api.events.server; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.ChatUtils; +import com.mojang.brigadier.ParseResults; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.context.StringRange; +import lombok.Getter; +import lombok.Setter; +import net.kyori.adventure.text.Component; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.arguments.ComponentArgument; +import net.minecraft.world.entity.player.Player; +import org.jetbrains.annotations.Nullable; + +@Getter +public class CraterCommandEvent extends CraterEvent { + + private final ParseResults parseResults; + @Setter private Throwable exception; + private final String command; + + private CraterCommandEvent(ParseResults parseResults, String command) { + this.parseResults = parseResults; + this.command = command; + } + + public static CraterCommandEvent of(ParseResults stack, String command) { + return new CraterCommandEvent(stack, command); + } + + public String getCommandString() { + return parseResults.getReader().getString(); + } + + @Nullable + public BridgedPlayer getPlayer() { + try { + Player p = parseResults.getContext().getLastChild().getSource().getPlayerOrException(); + + if (p != null) + return BridgedPlayer.of(p); + } catch (Exception ignored) {} + + return null; + } + + public String getTarget() { + CommandContext context = parseResults.getContext().build(parseResults.getReader().getString()); + StringRange selector_range = parseResults.getContext().getArguments().get("targets").getRange(); + return context.getInput().substring(selector_range.getStart(), selector_range.getEnd()); + } + + public Component getMessage() { + return ChatUtils.mojangToAdventure(ComponentArgument.getComponent(parseResults.getContext().build(parseResults.getReader().getString()), "message")); + } +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterPlayerEvent.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterPlayerEvent.java new file mode 100644 index 0000000..7eb3701 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterPlayerEvent.java @@ -0,0 +1,30 @@ +package com.hypherionmc.craterlib.api.events.server; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public class CraterPlayerEvent extends CraterEvent { + + private final BridgedPlayer player; + + public static class PlayerLoggedIn extends CraterPlayerEvent { + + public PlayerLoggedIn(BridgedPlayer player) { + super(player); + } + + } + + public static class PlayerLoggedOut extends CraterPlayerEvent { + + public PlayerLoggedOut(BridgedPlayer player) { + super(player); + } + + } + +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterRegisterCommandEvent.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterRegisterCommandEvent.java new file mode 100644 index 0000000..269065a --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterRegisterCommandEvent.java @@ -0,0 +1,15 @@ +package com.hypherionmc.craterlib.api.events.server; + +import com.hypherionmc.craterlib.api.commands.CraterCommand; +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.commands.CommandsRegistry; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +public class CraterRegisterCommandEvent extends CraterEvent { + + public void registerCommand(CraterCommand cmd) { + CommandsRegistry.INSTANCE.registerCommand(cmd); + } + +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterServerChatEvent.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterServerChatEvent.java new file mode 100644 index 0000000..b25170a --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterServerChatEvent.java @@ -0,0 +1,25 @@ +package com.hypherionmc.craterlib.api.events.server; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.core.event.annot.Cancellable; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import lombok.Getter; +import lombok.Setter; +import net.kyori.adventure.text.Component; + +@Cancellable +@Getter +public class CraterServerChatEvent extends CraterEvent { + + public final String message, username; + public final BridgedPlayer player; + @Setter private Component component; + + public CraterServerChatEvent(BridgedPlayer player, String message, Component component) { + this.message = message; + this.player = player; + this.username = player.getGameProfile().getName(); + this.component = component; + } + +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterServerLifecycleEvent.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterServerLifecycleEvent.java new file mode 100644 index 0000000..3f87d04 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterServerLifecycleEvent.java @@ -0,0 +1,34 @@ +package com.hypherionmc.craterlib.api.events.server; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +public class CraterServerLifecycleEvent extends CraterEvent { + + @RequiredArgsConstructor + @Getter + public static class Starting extends CraterServerLifecycleEvent { + private final BridgedMinecraftServer server; + } + + @RequiredArgsConstructor + @Getter + public static class Started extends CraterServerLifecycleEvent { + private final BridgedMinecraftServer server; + } + + @RequiredArgsConstructor + @Getter + public static class Stopping extends CraterServerLifecycleEvent { + private final BridgedMinecraftServer server; + } + + @RequiredArgsConstructor + @Getter + public static class Stopped extends CraterServerLifecycleEvent { + private final BridgedMinecraftServer server; + } + +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/MessageBroadcastEvent.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/MessageBroadcastEvent.java new file mode 100644 index 0000000..6b11404 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/MessageBroadcastEvent.java @@ -0,0 +1,20 @@ +package com.hypherionmc.craterlib.api.events.server; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; + +import java.util.function.Function; + +@RequiredArgsConstructor +@Getter +public class MessageBroadcastEvent extends CraterEvent { + + private final Component component; + private final Function function; + private final boolean bl; + private final String threadName; + +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/PlayerPreLoginEvent.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/PlayerPreLoginEvent.java new file mode 100644 index 0000000..eaabffd --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/PlayerPreLoginEvent.java @@ -0,0 +1,20 @@ +package com.hypherionmc.craterlib.api.events.server; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.authlib.BridgedGameProfile; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import net.kyori.adventure.text.Component; + +import java.net.SocketAddress; + +@RequiredArgsConstructor +@Getter +public class PlayerPreLoginEvent extends CraterEvent { + + private final SocketAddress address; + private final BridgedGameProfile gameProfile; + @Setter private Component message; + +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/networking/CraterNetworkHandler.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/networking/CraterNetworkHandler.java new file mode 100644 index 0000000..6b57e96 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/api/networking/CraterNetworkHandler.java @@ -0,0 +1,28 @@ +package com.hypherionmc.craterlib.api.networking; + +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; + +import java.util.List; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public interface CraterNetworkHandler { + + void sendToServer(T packet); + + void sendToServer(T packet, boolean ignoreCheck); + + void sendToClient(T packet, BridgedPlayer player); + + default void sendToClients(T packet, List players) { + for (BridgedPlayer player : players) { + sendToClient(packet, player); + } + } + + default void sendToAllClients(T packet, BridgedMinecraftServer server) { + sendToClients(packet, server.getPlayers()); + } +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/CraterConfigScreen.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/CraterConfigScreen.java new file mode 100644 index 0000000..fbabb4b --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/CraterConfigScreen.java @@ -0,0 +1,394 @@ +package com.hypherionmc.craterlib.client.gui.config; + +import com.hypherionmc.craterlib.CraterConstants; +import com.hypherionmc.craterlib.client.gui.config.widgets.*; +import com.hypherionmc.craterlib.core.config.ModuleConfig; +import com.hypherionmc.craterlib.core.config.annotations.HideFromScreen; +import com.hypherionmc.craterlib.core.config.annotations.SubConfig; +import com.hypherionmc.craterlib.core.config.annotations.Tooltip; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.*; +import com.mojang.math.Matrix4f; +import me.hypherionmc.moonconfig.core.conversion.SpecComment; +import net.minecraft.ChatFormatting; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.screens.ConfirmScreen; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.TextComponent; +import net.minecraft.network.chat.TranslatableComponent; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Mth; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * @author HypherionSA + */ +public class CraterConfigScreen extends Screen { + public static final float SCROLLBAR_BOTTOM_COLOR = .5f; + public static final float SCROLLBAR_TOP_COLOR = .67f; + private static final int TOP = 26; + private static final int BOTTOM = 24; + private final Screen parent; + private final List> options = new ArrayList<>(); + private final ModuleConfig config; + public double scrollerAmount; + private boolean dragging; + + public CraterConfigScreen(ModuleConfig config, Screen parent, Object subConfig) { + super(new TranslatableComponent("cl." + config.getClass().getSimpleName().toLowerCase() + ".title")); + this.parent = parent; + this.config = config; + if (subConfig != null) { + setupScreenFromConfig(subConfig, subConfig.getClass()); + } else { + setupScreenFromConfig(config, config.getClass()); + } + } + + public CraterConfigScreen(ModuleConfig config, Screen parent) { + this(config, parent, null); + } + + private static Component toText(Enum val) { + return new TranslatableComponent(val.toString()); + } + + private static Component toText(Boolean bool) { + return new TranslatableComponent(bool.toString()); + } + + private void setupScreenFromConfig(Object object, Class clazz) { + while (clazz != Object.class) { + for (Field field : clazz.getDeclaredFields()) { + final int fieldModifiers = field.getModifiers(); + if (object == null || Modifier.isStatic(fieldModifiers) || Modifier.isTransient(fieldModifiers)) { + continue; + } + + try { + if (!field.isAccessible()) { + field.setAccessible(true); + } + if (field.isAnnotationPresent(HideFromScreen.class)) + return; + Object val = field.get(object); + + /* Lang Stuff */ + String baseLangKey = "cl." + clazz.getSimpleName().toLowerCase() + "." + field.getName().toLowerCase(); + String[] tooltipLang = field.isAnnotationPresent(SpecComment.class) ? new String[]{field.getAnnotation(SpecComment.class).value()} : new String[0]; + if (field.isAnnotationPresent(Tooltip.class)) { + tooltipLang = field.getAnnotation(Tooltip.class).value(); + } + + add(new TranslatableComponent(baseLangKey), + val, + () -> val, + (ret) -> { + try { + field.set(object, ret); + config.saveConfig(config); + } catch (IllegalAccessException e) { + CraterConstants.LOG.error("Failed to update value for field {} in config {}", field.getName(), config.getConfigName(), e); + } + }, + field.isAnnotationPresent(SubConfig.class), + tooltipLang + ); + } catch (IllegalAccessException e) { + CraterConstants.LOG.error("Failed to access value for field {} in config {}", field.getName(), config.getConfigName(), e); + } + } + clazz = clazz.getSuperclass(); + } + } + + public void add(Component text, T value, @Nullable Supplier defaultValue, Consumer savingConsumer, boolean isSubConfig, String... langKeys) { + Option option = (Option) createOption(value, isSubConfig); + option.text = text; + option.defaultValue = defaultValue; + option.savingConsumer = savingConsumer; + option.originalValue = value; + option.value = value; + option.setLangKeys(List.of(langKeys)); + options.add(option); + option.onAdd(); + } + + private Option createOption(T value, boolean isSubConfig) { + if (value instanceof Enum) { + Object[] objects = value.getClass().getEnumConstants(); + return new ToggleButton>((List) Arrays.asList(objects), CraterConfigScreen::toText); + } + if (value instanceof Boolean) { + return new ToggleButton<>(Arrays.asList(Boolean.TRUE, Boolean.FALSE), CraterConfigScreen::toText); + } + if (value instanceof String) { + return new TextConfigOption<>(Function.identity(), Function.identity()); + } + if (value instanceof Integer) { + return new TextConfigOption<>(Objects::toString, Integer::valueOf); + } + if (value instanceof Long) { + return new TextConfigOption<>(Objects::toString, Long::valueOf); + } + if (value instanceof Double) { + return new TextConfigOption<>(Objects::toString, Double::valueOf); + } + if (value instanceof Float) { + return new TextConfigOption<>(Objects::toString, Float::valueOf); + } + if (value instanceof BigInteger) { + return new TextConfigOption<>(Objects::toString, BigInteger::new); + } + if (value instanceof BigDecimal) { + return new TextConfigOption<>(Objects::toString, BigDecimal::new); + } + if (value instanceof ResourceLocation) { + return new TextConfigOption<>(Objects::toString, ResourceLocation::new); + } + if (isSubConfig) { + return new SubConfigWidget<>(config, this, value); + } + throw new IllegalArgumentException(String.valueOf(value)); + } + + @Override + protected void init() { + super.init(); + ((List) children()).addAll(options); + + int buttonWidths = Math.min(200, (width - 50 - 12) / 3); + addRenderableWidget(new InternalConfigButton(this, width / 2 - buttonWidths - 3, height - 22, buttonWidths, 20, new TextComponent(""), true)); + addRenderableWidget(new InternalConfigButton(this, width / 2 + 3, height - 22, buttonWidths, 20, new TextComponent(""), false)); + } + + @Override + public void render(@NotNull PoseStack matrices, int mouseX, int mouseY, float delta) { + overlayBackground(matrices, TOP, height - BOTTOM, 32); + + renderScrollBar(); + + matrices.pushPose(); + matrices.translate(0, 0, 500.0); + overlayBackground(matrices, 0, TOP, 64); + overlayBackground(matrices, height - BOTTOM, height, 64); + renderShadow(matrices); + drawCenteredString(matrices, font, getTitle(), width / 2, 9, 0xFFFFFF); + super.render(matrices, mouseX, mouseY, delta); + matrices.popPose(); + + int y = (int) (TOP + 4 - Math.round(scrollerAmount)); + for (Option option : options) { + int height1 = option.height(); + option.render(minecraft, font, 40, y, width - 80, height1, matrices, mouseX, mouseY, delta); + renderConfigTooltip(matrices, font, mouseX, mouseY, 40, y, font.width(option.text), height1, option.text.getString(), option.getLangKeys().toArray(new String[0])); + y += height1; + } + } + + private void renderScrollBar() { + int listHeight = height - BOTTOM - TOP; + int totalHeight = totalHeight(); + if (totalHeight > listHeight) { + int maxScroll = Math.max(0, totalHeight - listHeight); + int height = listHeight * listHeight / totalHeight; + height = Mth.clamp(height, 32, listHeight); + height = Math.max(10, height); + int minY = Math.min(Math.max((int) scrollerAmount * (listHeight - height) / maxScroll + TOP, TOP), this.height - BOTTOM - height); + + int scrollbarPositionMaxX = width; + int scrollbarPositionMinX = scrollbarPositionMaxX - 6; + + int maxY = this.height - BOTTOM; + //RenderSystem.disableTexture(); + Tesselator tesselator = Tesselator.getInstance(); + BufferBuilder buffer = tesselator.getBuilder(); + RenderSystem.setShader(GameRenderer::getPositionColorShader); + buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR); + + buffer.vertex(scrollbarPositionMinX, maxY, 0.0D).color(0, 0, 0, 255).endVertex(); + buffer.vertex(scrollbarPositionMaxX, maxY, 0.0D).color(0, 0, 0, 255).endVertex(); + buffer.vertex(scrollbarPositionMaxX, TOP, 0.0D).color(0, 0, 0, 255).endVertex(); + buffer.vertex(scrollbarPositionMinX, TOP, 0.0D).color(0, 0, 0, 255).endVertex(); + + buffer.vertex(scrollbarPositionMinX, minY + height, 0.0D).color(SCROLLBAR_BOTTOM_COLOR, SCROLLBAR_BOTTOM_COLOR, SCROLLBAR_BOTTOM_COLOR, 1).endVertex(); + buffer.vertex(scrollbarPositionMaxX, minY + height, 0.0D).color(SCROLLBAR_BOTTOM_COLOR, SCROLLBAR_BOTTOM_COLOR, SCROLLBAR_BOTTOM_COLOR, 1).endVertex(); + buffer.vertex(scrollbarPositionMaxX, minY, 0.0D).color(SCROLLBAR_BOTTOM_COLOR, SCROLLBAR_BOTTOM_COLOR, SCROLLBAR_BOTTOM_COLOR, 1).endVertex(); + buffer.vertex(scrollbarPositionMinX, minY, 0.0D).color(SCROLLBAR_BOTTOM_COLOR, SCROLLBAR_BOTTOM_COLOR, SCROLLBAR_BOTTOM_COLOR, 1).endVertex(); + buffer.vertex(scrollbarPositionMinX, (minY + height - 1), 0.0D).color(SCROLLBAR_TOP_COLOR, SCROLLBAR_TOP_COLOR, SCROLLBAR_TOP_COLOR, 1).endVertex(); + buffer.vertex((scrollbarPositionMaxX - 1), (minY + height - 1), 0.0D).color(SCROLLBAR_TOP_COLOR, SCROLLBAR_TOP_COLOR, SCROLLBAR_TOP_COLOR, 1).endVertex(); + buffer.vertex((scrollbarPositionMaxX - 1), minY, 0.0D).color(SCROLLBAR_TOP_COLOR, SCROLLBAR_TOP_COLOR, SCROLLBAR_TOP_COLOR, 1).endVertex(); + buffer.vertex(scrollbarPositionMinX, minY, 0.0D).color(SCROLLBAR_TOP_COLOR, SCROLLBAR_TOP_COLOR, SCROLLBAR_TOP_COLOR, 1).endVertex(); + tesselator.end(); + RenderSystem.disableBlend(); + //RenderSystem.enableTexture(); + } + } + + private void renderShadow(PoseStack matrices) { + Tesselator tesselator = Tesselator.getInstance(); + BufferBuilder buffer = tesselator.getBuilder(); + RenderSystem.enableBlend(); + RenderSystem.blendFuncSeparate(770, 771, 0, 1); + //RenderSystem.disableTexture(); + RenderSystem.setShader(GameRenderer::getPositionTexColorShader); + Matrix4f matrix = matrices.last().pose(); + buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX_COLOR); + buffer.vertex(matrix, 0, TOP + 4, 0.0F).uv(0, 1).color(0, 0, 0, 0).endVertex(); + buffer.vertex(matrix, width, TOP + 4, 0.0F).uv(1, 1).color(0, 0, 0, 0).endVertex(); + buffer.vertex(matrix, width, TOP, 0.0F).uv(1, 0).color(0, 0, 0, 185).endVertex(); + buffer.vertex(matrix, 0, TOP, 0.0F).uv(0, 0).color(0, 0, 0, 185).endVertex(); + buffer.vertex(matrix, 0, height - BOTTOM, 0.0F).uv(0, 1).color(0, 0, 0, 185).endVertex(); + buffer.vertex(matrix, width, height - BOTTOM, 0.0F).uv(1, 1).color(0, 0, 0, 185).endVertex(); + buffer.vertex(matrix, width, height - BOTTOM - 4, 0.0F).uv(1, 0).color(0, 0, 0, 0).endVertex(); + buffer.vertex(matrix, 0, height - BOTTOM - 4, 0.0F).uv(0, 0).color(0, 0, 0, 0).endVertex(); + tesselator.end(); + //RenderSystem.enableTexture(); + RenderSystem.disableBlend(); + } + + protected void overlayBackground(PoseStack matrices, int h1, int h2, int color) { + overlayBackground(matrices.last().pose(), 0, h1, width, h2, color, color, color, 255, 255); + } + + protected void overlayBackground(Matrix4f matrix, int minX, int minY, int maxX, int maxY, int red, int green, int blue, int startAlpha, int endAlpha) { + Tesselator tesselator = Tesselator.getInstance(); + BufferBuilder buffer = tesselator.getBuilder(); + RenderSystem.setShaderTexture(0, Screen.BACKGROUND_LOCATION); + RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f); + RenderSystem.setShader(GameRenderer::getPositionTexColorShader); + buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX_COLOR); + buffer.vertex(matrix, minX, maxY, 0.0F).uv(minX / 32.0F, maxY / 32.0F).color(red, green, blue, endAlpha).endVertex(); + buffer.vertex(matrix, maxX, maxY, 0.0F).uv(maxX / 32.0F, maxY / 32.0F).color(red, green, blue, endAlpha).endVertex(); + buffer.vertex(matrix, maxX, minY, 0.0F).uv(maxX / 32.0F, minY / 32.0F).color(red, green, blue, startAlpha).endVertex(); + buffer.vertex(matrix, minX, minY, 0.0F).uv(minX / 32.0F, minY / 32.0F).color(red, green, blue, startAlpha).endVertex(); + tesselator.end(); + } + + public int scrollHeight() { + int totalHeight = totalHeight(); + int listHeight = height - BOTTOM - TOP; + if (totalHeight <= listHeight) { + return 0; + } + return totalHeight - listHeight; + } + + public int totalHeight() { + int i = 8; + for (Option option : options) { + i += option.height(); + } + return i; + } + + public boolean hasErrors() { + for (Option option : options) { + if (option.hasErrors) { + return true; + } + } + return false; + } + + public boolean isEdited() { + for (Option option : options) { + if (option.isEdited()) { + return true; + } + } + return false; + } + + public void save() { + for (Option option : options) { + option.save(); + option.originalValue = option.value; + } + } + + @Override + public void onClose() { + if (isEdited()) { + minecraft.setScreen(new ConfirmScreen(this::acceptConfirm, new TranslatableComponent("t.clc.quit_config"), + new TranslatableComponent("t.clc.quit_config_sure"), + new TranslatableComponent("t.clc.quit_discard"), + new TranslatableComponent("gui.cancel"))); + } else { + minecraft.setScreen(parent); + } + } + + @Override + public boolean mouseScrolled(double d, double e, double f) { + if (e >= TOP && e <= height - BOTTOM) { + scrollerAmount = Mth.clamp(scrollerAmount - f * 16.0D, 0, scrollHeight()); + return true; + } + return super.mouseScrolled(d, e, f); + } + + @Override + public boolean mouseClicked(double d, double e, int i) { + this.dragging = i == 0 && d >= width - 6 && d < width; + return super.mouseClicked(d, e, i) || dragging; + } + + @Override + public boolean mouseDragged(double d, double e, int i, double f, double g) { + if (super.mouseDragged(d, e, i, f, g)) { + return true; + } + if (i != 0 || !this.dragging) { + return false; + } + if (e < TOP) { + scrollerAmount = 0; + } else if (e > height - BOTTOM) { + scrollerAmount = scrollHeight(); + } else { + double h = Math.max(1, this.scrollHeight()); + int j = height - BOTTOM - TOP; + int k = Mth.clamp((int) ((float) (j * j) / (float) this.scrollHeight()), 32, j - 8); + double l = Math.max(1.0, h / (double) (j - k)); + scrollerAmount = Mth.clamp(scrollerAmount + g * l, 0, scrollHeight()); + } + return true; + } + + private void acceptConfirm(boolean t) { + if (!t) { + minecraft.setScreen(this); + } else { + minecraft.setScreen(parent); + } + } + + private void renderConfigTooltip(PoseStack stack, Font font, int mouseX, int mouseY, int startX, int startY, int sizeX, int sizeY, String title, String... description) { + if (mouseX > startX && mouseX < startX + sizeX) { + if (mouseY > startY && mouseY < startY + sizeY) { + List list = new ArrayList<>(); + list.add(new TranslatableComponent(ChatFormatting.BOLD + "" + ChatFormatting.YELLOW + title)); + for (String desc : description) { + list.add(new TranslatableComponent(desc)); + } + renderComponentTooltip(stack, list, mouseX, mouseY); + } + } + } +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/AbstractConfigWidget.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/AbstractConfigWidget.java new file mode 100644 index 0000000..408c515 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/AbstractConfigWidget.java @@ -0,0 +1,27 @@ +package com.hypherionmc.craterlib.client.gui.config.widgets; + +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.client.gui.components.EditBox; + +/** + * Copied from Cloth Config Lite + * ... + */ +public class AbstractConfigWidget extends BaseWidget { + + public static final int buttonWidth = 200; + public static final int buttonHeight = 20; + public W widget; + + @Override + public void render(Minecraft minecraft, Font font, int x, int y, int width, int height, PoseStack matrices, int mouseX, int mouseY, float delta) { + super.render(minecraft, font, x, y, width, height, matrices, mouseX, mouseY, delta); + int i = (widget instanceof EditBox ? 1 : 0); + widget.x = (x + width - 200 - resetButtonOffset + i); + widget.y = (y + i + 1); + widget.render(matrices, mouseX, mouseY, delta); + } +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/BaseWidget.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/BaseWidget.java new file mode 100644 index 0000000..ab45fb1 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/BaseWidget.java @@ -0,0 +1,61 @@ +package com.hypherionmc.craterlib.client.gui.config.widgets; + +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.ChatFormatting; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.components.Button; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.network.chat.TextColor; +import net.minecraft.network.chat.TextComponent; + +/** + * Copied from Cloth Config Lite + * ... + */ +public class BaseWidget extends Option { + + public static final int resetButtonOffset = 48; + private final Button resetButton = addChild(new Button(0, 0, 46, 20, new TextComponent("Reset"), this::onResetPressed)); + private boolean hideReset = false; + + private boolean isSubConfig = false; + + private void onResetPressed(Button button) { + value = defaultValue.get(); + reset(); + } + + public void hideReset() { + this.hideReset = true; + } + + public boolean isSubConfig() { + return isSubConfig; + } + + public void setSubConfig(boolean subConfig) { + isSubConfig = subConfig; + } + + @Override + public void render(Minecraft minecraft, Font font, int x, int y, int width, int height, PoseStack matrices, int mouseX, int mouseY, float delta) { + MutableComponent text = new TextComponent(this.text.getString()); + boolean edited = isEdited() || hasErrors; + if (edited) { + text.withStyle(ChatFormatting.ITALIC); + if (hasErrors) { + text.withStyle(style -> style.withColor(TextColor.fromRgb(16733525))); + } + } else { + text.withStyle(ChatFormatting.GRAY); + } + font.draw(matrices, text, x, y, 0xFFFFFF); + resetButton.x = (x + width - 46); + resetButton.y = (y + 1); + resetButton.active = isNotDefault(); + if (!hideReset) { + resetButton.render(matrices, mouseX, mouseY, delta); + } + } +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/InternalConfigButton.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/InternalConfigButton.java new file mode 100644 index 0000000..fc5d271 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/InternalConfigButton.java @@ -0,0 +1,53 @@ +package com.hypherionmc.craterlib.client.gui.config.widgets; + +import com.hypherionmc.craterlib.client.gui.config.CraterConfigScreen; +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.gui.components.AbstractButton; +import net.minecraft.client.gui.narration.NarratedElementType; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.TranslatableComponent; +import org.jetbrains.annotations.NotNull; + +/** + * @author HypherionSA + */ +public class InternalConfigButton extends AbstractButton { + + CraterConfigScreen screen; + boolean cancel; + + public InternalConfigButton(CraterConfigScreen screen, int i, int j, int k, int l, Component component, boolean cancel) { + super(i, j, k, l, component); + this.screen = screen; + this.cancel = cancel; + } + + @Override + public void render(@NotNull PoseStack poseStack, int i, int j, float f) { + if (cancel) { + setMessage(new TranslatableComponent(screen.isEdited() ? "t.clc.cancel_discard" : "gui.cancel")); + } else { + boolean hasErrors = screen.hasErrors(); + active = screen.isEdited() && !hasErrors; + setMessage(new TranslatableComponent(hasErrors ? "t.clc.error" : "t.clc.save")); + } + super.render(poseStack, i, j, f); + } + + @Override + public void updateNarration(NarrationElementOutput narrationElementOutput) { + narrationElementOutput.add(NarratedElementType.USAGE, getMessage()); + } + + @Override + public void onPress() { + if (cancel) { + screen.onClose(); + } else { + screen.save(); + } + } + + +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/Option.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/Option.java new file mode 100644 index 0000000..9573bd0 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/Option.java @@ -0,0 +1,71 @@ +package com.hypherionmc.craterlib.client.gui.config.widgets; + +import com.mojang.blaze3d.vertex.PoseStack; +import lombok.Getter; +import lombok.Setter; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.components.events.AbstractContainerEventHandler; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Supplier; + +/** + * Copied from Cloth Config Lite + * ... + */ +public abstract class Option extends AbstractContainerEventHandler { + + public Component text; + @Nullable + public Supplier defaultValue; + public Consumer savingConsumer; + public T originalValue; + public T value; + public boolean hasErrors; + public List children = new ArrayList<>(); + @Setter + @Getter + private List langKeys = new ArrayList<>(); + + public abstract void render(Minecraft minecraft, Font font, int x, int y, int width, int height, PoseStack matrices, int mouseX, int mouseY, float delta); + + public int height() { + return 22; + } + + @Override + public List children() { + return children; + } + + protected R addChild(R listener) { + ((List) children).add(listener); + return listener; + } + + public void onAdd() { + } + + protected void reset() { + onAdd(); + } + + public boolean isEdited() { + return !Objects.equals(originalValue, value); + } + + protected boolean isNotDefault() { + return defaultValue != null && !Objects.equals(defaultValue.get(), value); + } + + public void save() { + savingConsumer.accept(value); + } +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/SubConfigWidget.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/SubConfigWidget.java new file mode 100644 index 0000000..b4fa4f9 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/SubConfigWidget.java @@ -0,0 +1,40 @@ +package com.hypherionmc.craterlib.client.gui.config.widgets; + +import com.hypherionmc.craterlib.client.gui.config.CraterConfigScreen; +import com.hypherionmc.craterlib.core.config.ModuleConfig; +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.TranslatableComponent; + +/** + * @author HypherionSA + */ +public class SubConfigWidget extends AbstractConfigWidget { + + private final Object subConfig; + private final ModuleConfig config; + private final Screen screen; + + public SubConfigWidget(ModuleConfig config, Screen screen, Object subConfig) { + this.config = config; + this.subConfig = subConfig; + this.screen = screen; + + this.widget = addChild(new Button(0, 0, 200, buttonHeight, new TranslatableComponent("t.clc.opensubconfig"), this::openSubConfig)); + } + + @Override + public void render(Minecraft minecraft, Font font, int x, int y, int width, int height, PoseStack matrices, int mouseX, int mouseY, float delta) { + this.text = new TranslatableComponent(subConfig.getClass().getSimpleName().toLowerCase()); + this.hideReset(); + super.render(minecraft, font, x, y, width, height, matrices, mouseX, mouseY, delta); + } + + private void openSubConfig(Button button) { + Minecraft.getInstance().setScreen(new CraterConfigScreen(config, screen, subConfig)); + } + +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/TextConfigOption.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/TextConfigOption.java new file mode 100644 index 0000000..192da97 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/TextConfigOption.java @@ -0,0 +1,45 @@ +package com.hypherionmc.craterlib.client.gui.config.widgets; + +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; + +import java.util.function.Function; + +/** + * Copied from Cloth Config Lite + * ... + */ +public class TextConfigOption extends AbstractConfigWidget { + + private final Function toString; + private final Function fromString; + + public TextConfigOption(Function toString, Function fromString) { + this.toString = toString; + this.fromString = fromString; + this.widget = addChild(new WrappedEditBox(Minecraft.getInstance().font, 0, 0, 198, 18, null)); + } + + @Override + public void onAdd() { + widget.setMaxLength(1000000); + widget.setValue(toString.apply(value)); + widget.setResponder(this::update); + } + + @Override + public void render(Minecraft minecraft, Font font, int x, int y, int width, int height, PoseStack matrices, int mouseX, int mouseY, float delta) { + widget.setTextColor(hasErrors ? 16733525 : 14737632); + super.render(minecraft, font, x, y, width, height, matrices, mouseX, mouseY, delta); + } + + private void update(String s) { + try { + this.value = fromString.apply(s); + this.hasErrors = false; + } catch (Exception e) { + this.hasErrors = true; + } + } +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/ToggleButton.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/ToggleButton.java new file mode 100644 index 0000000..0efe2d1 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/ToggleButton.java @@ -0,0 +1,34 @@ +package com.hypherionmc.craterlib.client.gui.config.widgets; + +import net.minecraft.client.gui.components.Button; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.TextComponent; + +import java.util.List; +import java.util.function.Function; + +/** + * Copied from Cloth Config Lite + * ... + */ +public class ToggleButton extends AbstractConfigWidget { + + private final List options; + private final Function toComponent; + + public ToggleButton(List options, Function toComponent) { + this.options = options; + this.toComponent = toComponent; + this.widget = addChild(new Button(0, 0, buttonWidth, buttonHeight, new TextComponent(""), this::switchNext)); + } + + @Override + public void onAdd() { + widget.setMessage(toComponent.apply(value)); + } + + private void switchNext(Button button) { + value = options.get((options.indexOf(value) + 1) % options.size()); + onAdd(); + } +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/WrappedEditBox.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/WrappedEditBox.java new file mode 100644 index 0000000..42c0c0c --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/WrappedEditBox.java @@ -0,0 +1,29 @@ +package com.hypherionmc.craterlib.client.gui.config.widgets; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.components.EditBox; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.NotNull; + +/** + * @author HypherionSA + */ +public class WrappedEditBox extends EditBox { + + public WrappedEditBox(Font font, int i, int j, int k, int l, @NotNull Component component) { + super(font, i, j, k, l, component); + } + + @Override + public void setFocused(boolean bl) { + for (GuiEventListener child : Minecraft.getInstance().screen.children()) { + if (child instanceof TextConfigOption option) { + WrappedEditBox box = option.widget; + super.setFocused(box == this); + } + } + super.setFocused(bl); + } +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/client/mentions/MentionCondition.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/client/mentions/MentionCondition.java new file mode 100644 index 0000000..e8bffde --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/client/mentions/MentionCondition.java @@ -0,0 +1,13 @@ +package com.hypherionmc.craterlib.client.mentions; + +/** + * Based on ... + */ +@FunctionalInterface +public interface MentionCondition { + + boolean shouldAddMention(String currentWord); + + MentionCondition ALWAYS = currentWord -> true; + +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/client/mentions/MentionsController.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/client/mentions/MentionsController.java new file mode 100644 index 0000000..bad3b43 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/client/mentions/MentionsController.java @@ -0,0 +1,47 @@ +package com.hypherionmc.craterlib.client.mentions; + +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; +import lombok.Getter; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Based on ... + */ +public class MentionsController { + + private static final Map> mentions = new LinkedHashMap<>(); + private static final Map mentionConditions = new LinkedHashMap<>(); + @Getter + private static boolean lastMentionConditional = true; + + public static void registerMention(ResourceIdentifier mentionClass, Collection suggestions, MentionCondition condition) { + mentions.put(mentionClass, suggestions); + mentionConditions.put(mentionClass, condition); + } + + public static Collection getMentions(String currentWord) { + ArrayList applicableMentions = new ArrayList<>(); + lastMentionConditional = false; + + mentionConditions.forEach((mention, condition) -> { + boolean shouldSuggest = condition.shouldAddMention(currentWord); + if (!shouldSuggest) return; + + if (!lastMentionConditional && condition != MentionCondition.ALWAYS) { + lastMentionConditional = true; + } + + applicableMentions.addAll(mentions.get(mention)); + }); + + return applicableMentions; + } + + public static boolean hasMentions() { + return !mentions.isEmpty(); + } +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/ConfigController.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/ConfigController.java new file mode 100644 index 0000000..41c9471 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/ConfigController.java @@ -0,0 +1,49 @@ +package com.hypherionmc.craterlib.core.config; + +import com.hypherionmc.craterlib.CraterConstants; +import lombok.Getter; +import me.hypherionmc.moonconfig.core.file.FileWatcher; +import org.jetbrains.annotations.ApiStatus; + +import java.io.Serializable; +import java.util.HashMap; + +/** + * @author HypherionSA + * Controls Config File Reloads and Events + */ +public final class ConfigController implements Serializable { + + /** + * Cache of registered configs + */ + @Getter + private static final HashMap monitoredConfigs = new HashMap<>(); + + /** + * INTERNAL METHOD - Register and watch the config + * + * @param config - The config class to register and watch + */ + @ApiStatus.Internal + public static void register_config(ModuleConfig config) { + if (monitoredConfigs.containsKey(config)) { + CraterConstants.LOG.error("Failed to register " + config.getConfigPath().getName() + ". Config already registered"); + } else { + FileWatcher configWatcher = new FileWatcher(); + try { + configWatcher.setWatch(config.getConfigPath(), () -> { + if (!config.isSaveCalled()) { + CraterConstants.LOG.info("Sending Reload Event for: " + config.getConfigPath().getName()); + config.configReloaded(); + } + }); + } catch (Exception e) { + CraterConstants.LOG.error("Failed to register " + config.getConfigPath().getName() + " for auto reloading. " + e.getMessage()); + } + monitoredConfigs.put(config, configWatcher); + CraterConstants.LOG.info("Registered " + config.getConfigPath().getName() + " successfully!"); + } + } + +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/ModuleConfig.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/ModuleConfig.java new file mode 100644 index 0000000..181efdc --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/ModuleConfig.java @@ -0,0 +1,184 @@ +package com.hypherionmc.craterlib.core.config; + +import me.hypherionmc.moonconfig.core.CommentedConfig; +import me.hypherionmc.moonconfig.core.Config; +import me.hypherionmc.moonconfig.core.conversion.ObjectConverter; +import me.hypherionmc.moonconfig.core.file.CommentedFileConfig; + +import java.io.File; + +/** + * @author HypherionSA + * Base Config class containing the save, upgrading and loading logic. + * All config classes must extend this class + */ +public class ModuleConfig { + + /* Final Variables */ + private final transient File configPath; + private final transient String networkID; + + private final transient String configName; + + private final transient String modId; + + private transient boolean isSaveCalled = false; + + /** + * Set up the config + * + * @param modId - The ID of the Mod/Module the config belongs to + * @param configName - The name of the config file, excluding extension + */ + public ModuleConfig(String modId, String configName) { + this(modId, "", configName); + } + + public ModuleConfig(String modId, String subFolder, String configName) { + /* Preserve the order of the config values */ + Config.setInsertionOrderPreserved(true); + + /* Configure Paths and Network SYNC ID */ + File configDir = new File("config" + (subFolder.isEmpty() ? "" : File.separator + subFolder)); + configPath = new File(configDir + File.separator + configName + ".toml"); + networkID = modId + ":conf_" + configName.replace("-", "_"); + this.modId = modId; + this.configName = configName; + + /* Check if the required directories exists, otherwise we create them */ + if (!configDir.exists()) { + configDir.mkdirs(); + } + } + + /** + * This method has to be called in the config constructor. This creates or upgrades the config file as needed + * + * @param config - The config class to use + */ + public void registerAndSetup(ModuleConfig config) { + if (!configPath.exists() || configPath.length() < 2) { + saveConfig(config); + } else { + migrateConfig(config); + } + /* Register the Config for Watching and events */ + ConfigController.register_config(this); + this.configReloaded(); + } + + /** + * Save the config to the disk + * + * @param conf - The config class to serialize and save + */ + public void saveConfig(ModuleConfig conf) { + this.isSaveCalled = true; + /* Set up the Serializer and Config Object */ + ObjectConverter converter = new ObjectConverter(); + CommentedFileConfig config = CommentedFileConfig.builder(configPath).build(); + + /* Save the config and fire the reload events */ + converter.toConfig(conf, config); + config.save(); + configReloaded(); + this.isSaveCalled = false; + } + + /** + * Load the config from the file into a Class + * + * @param conf - The config Class to load + * @return - Returns the loaded version of the class + */ + public T loadConfig(Object conf) { + /* Set up the Serializer and Config Object */ + ObjectConverter converter = new ObjectConverter(); + CommentedFileConfig config = CommentedFileConfig.builder(configPath).build(); + config.load(); + + /* Load the config and return the loaded config */ + converter.toObject(config, conf); + return (T) conf; + } + + /** + * INTERNAL METHOD - Upgrades the config files in the events the config structure changes + * + * @param conf - The config class to load + */ + public void migrateConfig(ModuleConfig conf) { + /* Set up the Serializer and Config Objects */ + CommentedFileConfig config = CommentedFileConfig.builder(configPath).build(); + CommentedFileConfig newConfig = CommentedFileConfig.builder(configPath).build(); + config.load(); + + /* Upgrade the config */ + new ObjectConverter().toConfig(conf, newConfig); + updateConfigValues(config, newConfig, newConfig, ""); + newConfig.save(); + + config.close(); + newConfig.close(); + } + + public void updateConfigValues(CommentedConfig oldConfig, CommentedConfig newConfig, CommentedConfig outputConfig, String subKey) { + /* Loop over the config keys and check what has changed */ + newConfig.valueMap().forEach((key, value) -> { + String finalKey = subKey + (subKey.isEmpty() ? "" : ".") + key; + if (value instanceof CommentedConfig commentedConfig) { + updateConfigValues(oldConfig, commentedConfig, outputConfig, finalKey); + } else { + outputConfig.set(finalKey, + oldConfig.contains(finalKey) ? oldConfig.get(finalKey) : value); + } + }); + } + + /** + * Get the location of the config file + * + * @return - The FILE object containing the config file + */ + public File getConfigPath() { + return configPath; + } + + /** + * Get the NETWORK SYNC ID + * + * @return - Returns the Sync ID in format modid:config_name + */ + public String getNetworkID() { + return networkID; + } + + /** + * Fired whenever changes to the config are detected + */ + public void configReloaded() { + + } + + /** + * Get the name of the Config File + * + * @return + */ + public String getConfigName() { + return configName; + } + + /** + * Get the MODID of the Module the config is registered to + * + * @return + */ + public String getModId() { + return modId; + } + + public boolean isSaveCalled() { + return isSaveCalled; + } +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/HideFromScreen.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/HideFromScreen.java new file mode 100644 index 0000000..512a025 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/HideFromScreen.java @@ -0,0 +1,8 @@ +package com.hypherionmc.craterlib.core.config.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface HideFromScreen { +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/NoConfigScreen.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/NoConfigScreen.java new file mode 100644 index 0000000..4288ee0 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/NoConfigScreen.java @@ -0,0 +1,12 @@ +package com.hypherionmc.craterlib.core.config.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * @author HypherionSA + * Allows Modules to disable Automatic Config Screens + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface NoConfigScreen { +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/SubConfig.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/SubConfig.java new file mode 100644 index 0000000..e3ec808 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/SubConfig.java @@ -0,0 +1,16 @@ +package com.hypherionmc.craterlib.core.config.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author HypherionSA + * Used to determine if a Config section should be rendered as a separate screen + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface SubConfig { +} + diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/Syncable.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/Syncable.java new file mode 100644 index 0000000..838b35b --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/Syncable.java @@ -0,0 +1,15 @@ +package com.hypherionmc.craterlib.core.config.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author HypherionSA + * //TODO Currently unused, but to be used with Config Syncing in the future + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Syncable { +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/Tooltip.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/Tooltip.java new file mode 100644 index 0000000..538471c --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/Tooltip.java @@ -0,0 +1,16 @@ +package com.hypherionmc.craterlib.core.config.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author HypherionSA + * Provides tooltips to the config GUI + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Tooltip { + String[] value(); +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEvent.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEvent.java new file mode 100644 index 0000000..89ff00a --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEvent.java @@ -0,0 +1,30 @@ +package com.hypherionmc.craterlib.core.event; + +import com.hypherionmc.craterlib.core.event.annot.Cancellable; +import com.hypherionmc.craterlib.core.event.exception.CraterEventCancellationException; + +public class CraterEvent { + + private boolean canceled = false; + + private boolean canCancel() { + return this.getClass().isAnnotationPresent(Cancellable.class); + } + + public void cancelEvent() { + try { + if (!this.canCancel()) { + throw new CraterEventCancellationException("Tried to cancel non-cancelable event: " + this.getClass().getName()); + } + + this.canceled = true; + } catch (Exception e) { + e.printStackTrace(); + } + } + + public boolean wasCancelled() { + return this.canceled; + } + +} \ No newline at end of file diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEventBus.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEventBus.java new file mode 100644 index 0000000..74eeecc --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEventBus.java @@ -0,0 +1,241 @@ +package com.hypherionmc.craterlib.core.event; + +import com.hypherionmc.craterlib.CraterConstants; +import com.hypherionmc.craterlib.core.event.annot.CraterEventListener; +import org.slf4j.Logger; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.*; +import java.util.function.Consumer; + +public final class CraterEventBus { + + public static final CraterEventBus INSTANCE = new CraterEventBus(); + private static final Logger LOGGER = CraterConstants.LOG; + private final Map, List> events = new HashMap<>(); + + public void postEvent(CraterEvent event) { + if (eventsRegisteredForType(event.getClass())) { + List l = new ArrayList<>(events.get(event.getClass())); + l.sort((o1, o2) -> Integer.compare(o2.priority, o1.priority)); + + for (ListenerContainer c : l) { + c.notifyListener(event); + } + } + } + + public void registerEventListener(Class clazz) { + this.registerListenerMethods(this.getEventMethodsOf(clazz)); + } + + public void registerEventListener(Object object) { + this.registerListenerMethods(this.getEventMethodsOf(object)); + } + + private void registerListenerMethods(List methods) { + for (EventMethod m : methods) { + Consumer listener = (event) -> { + try { + m.method.invoke(m.parentObject, event); + } catch (Exception e) { + throw new RuntimeException(e); + } + }; + + ListenerContainer container = new ListenerContainer(m.eventType, listener, m.priority); + container.listenerParentClassName = m.parentClass.getName(); + container.listenerMethodName = m.method.getName(); + this.registerListener(container); + } + } + + private List getEventMethodsOf(Object objectOrClass) { + List l = new ArrayList<>(); + try { + if (objectOrClass != null) { + boolean isClass = (objectOrClass instanceof Class); + Class c = isClass ? (Class) objectOrClass : objectOrClass.getClass(); + for (Method m : c.getMethods()) { + if (isClass && Modifier.isStatic(m.getModifiers())) { + EventMethod em = EventMethod.tryCreateFrom(new AnalyzedMethod(m, c)); + if ((em != null) && this.hasEventAnnotation(em)) l.add(em); + } + if (!isClass && !Modifier.isStatic(m.getModifiers())) { + EventMethod em = EventMethod.tryCreateFrom(new AnalyzedMethod(m, objectOrClass)); + if ((em != null) && this.hasEventAnnotation(em)) l.add(em); + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return l; + } + + private boolean hasEventAnnotation(EventMethod m) { + for (Annotation a : m.annotations) { + if (a instanceof CraterEventListener) return true; + } + return false; + } + + public void registerListener(Consumer listener, Class eventType) { + this.registerListener(listener, eventType, 0); + } + + public void registerListener(Consumer listener, Class eventType, int priority) { + this.registerListener(new ListenerContainer(eventType, listener, priority)); + } + + private void registerListener(ListenerContainer listenerContainer) { + try { + if (!eventsRegisteredForType(listenerContainer.eventType)) { + events.put(listenerContainer.eventType, new ArrayList<>()); + } + events.get(listenerContainer.eventType).add(listenerContainer); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public boolean eventsRegisteredForType(Class eventType) { + if (eventType == null) { + return false; + } + return this.events.containsKey(eventType); + } + + protected final static class ListenerContainer { + + private final Consumer listener; + private final Class eventType; + private final int priority; + private String listenerParentClassName = "[unknown]"; + private String listenerMethodName = "[unknown]"; + + private ListenerContainer(Class eventType, Consumer listener, int priority) { + this.eventType = eventType; + this.listener = listener; + this.priority = priority; + } + + private void notifyListener(CraterEvent event) { + try { + this.listener.accept(event); + } catch (Exception e) { + LOGGER.error("##################################"); + LOGGER.error("Failed to notify event listener!"); + LOGGER.error("Event Type: " + this.eventType.getName()); + LOGGER.error("Listener Parent Class Name: " + this.listenerParentClassName); + LOGGER.error("Listener Method Name In Parent Class: " + this.listenerMethodName); + LOGGER.error("##################################"); + e.printStackTrace(); + } + } + } + + protected static class AnalyzedMethod { + + protected Method method; + protected Object parentObject; + protected Class parentClass; + protected boolean isStatic; + protected List annotations = new ArrayList<>(); + + protected AnalyzedMethod() { + } + + protected AnalyzedMethod(Method method, Object parentObjectOrClass) { + this.method = method; + this.parentObject = parentObjectOrClass; + this.parentClass = this.tryGetParentClass(); + this.isStatic = Modifier.isStatic(method.getModifiers()); + collectMethodAnnotations(this.isStatic ? null : this.parentObject.getClass(), this.method, this.annotations); + } + + protected static void collectMethodAnnotations(Class c, Method m, List addToList) { + try { + addToList.addAll(Arrays.asList(m.getAnnotations())); + if (!Modifier.isStatic(m.getModifiers()) && (c != null)) { + Class sc = c.getSuperclass(); + if (sc != null) { + try { + Method sm = sc.getMethod(m.getName(), m.getParameterTypes()); + collectMethodAnnotations(sc, sm, addToList); + } catch (Exception ignored) { + } + } + } + } catch (Exception ignored) { + } + } + + protected Class tryGetParentClass() { + if (this.parentObject instanceof Class) { + return (Class) this.parentObject; + } + return this.parentObject.getClass(); + } + + } + + protected static class EventMethod extends AnalyzedMethod { + + protected final int priority; + protected final Class eventType; + + protected EventMethod(AnalyzedMethod method) { + + super(); + this.method = method.method; + this.parentObject = method.parentObject; + this.parentClass = method.parentClass; + this.isStatic = method.isStatic; + this.annotations = method.annotations; + + this.priority = this.tryGetPriority(); + this.eventType = this.tryGetEventType(); + + } + + protected static EventMethod tryCreateFrom(AnalyzedMethod method) { + EventMethod em = new EventMethod(method); + return (em.eventType != null) ? em : null; + } + + protected Class tryGetEventType() { + try { + if (this.method != null) { + Class[] params = this.method.getParameterTypes(); + if (params.length > 0) { + Class firstParam = params[0]; + if (CraterEvent.class.isAssignableFrom(firstParam)) { + return (Class) firstParam; + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + protected int tryGetPriority() { + try { + for (Annotation a : this.annotations) { + if (a instanceof CraterEventListener craterEventListener) { + return craterEventListener.priority(); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return 0; + } + + } + +} \ No newline at end of file diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEventPriority.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEventPriority.java new file mode 100644 index 0000000..0c97134 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEventPriority.java @@ -0,0 +1,13 @@ +package com.hypherionmc.craterlib.core.event; + +public class CraterEventPriority { + + public static final int LOWEST = -3; + public static final int LOWER = -2; + public static final int LOW = -1; + public static final int NORMAL = 0; + public static final int HIGH = 1; + public static final int HIGHER = 2; + public static final int HIGHEST = 3; + +} \ No newline at end of file diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/annot/Cancellable.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/annot/Cancellable.java new file mode 100644 index 0000000..ee3c2fc --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/annot/Cancellable.java @@ -0,0 +1,8 @@ +package com.hypherionmc.craterlib.core.event.annot; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface Cancellable { +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/annot/CraterEventListener.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/annot/CraterEventListener.java new file mode 100644 index 0000000..7b42490 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/annot/CraterEventListener.java @@ -0,0 +1,11 @@ +package com.hypherionmc.craterlib.core.event.annot; + +import com.hypherionmc.craterlib.core.event.CraterEventPriority; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface CraterEventListener { + int priority() default CraterEventPriority.NORMAL; +} \ No newline at end of file diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/exception/CraterEventCancellationException.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/exception/CraterEventCancellationException.java new file mode 100644 index 0000000..fe2a8a3 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/exception/CraterEventCancellationException.java @@ -0,0 +1,9 @@ +package com.hypherionmc.craterlib.core.event.exception; + +public class CraterEventCancellationException extends Exception { + + public CraterEventCancellationException(String msg) { + super(msg); + } + +} \ No newline at end of file diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/package-info.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/package-info.java new file mode 100644 index 0000000..5ec1749 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/package-info.java @@ -0,0 +1,5 @@ +/** + * The event system code in this package is based on, and adapted from Acara (https://github.com/Keksuccino/acara/) + * and is licensed under MIT by Keksuccino + */ +package com.hypherionmc.craterlib.core.event; \ No newline at end of file diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/CraterPacketNetwork.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/CraterPacketNetwork.java new file mode 100644 index 0000000..081c8fb --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/CraterPacketNetwork.java @@ -0,0 +1,43 @@ +package com.hypherionmc.craterlib.core.networking; + +import com.hypherionmc.craterlib.core.networking.data.PacketContext; +import com.hypherionmc.craterlib.nojang.network.BridgedFriendlyByteBuf; +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; +import lombok.Getter; + +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +@Getter +public class CraterPacketNetwork { + + private final PacketRegistry packetRegistry; + public static CraterPacketNetwork INSTANCE; + private static DeferredPacketRegistrar delayedHandler; + + public CraterPacketNetwork(PacketRegistry registry) { + INSTANCE = this; + this.packetRegistry = registry; + getDelayedHandler().registerQueuedPackets(registry); + } + + private static DeferredPacketRegistrar getDelayedHandler() { + if (delayedHandler == null) { + delayedHandler = new DeferredPacketRegistrar(); + } + return delayedHandler; + } + + public static PacketRegistrar registerPacket(ResourceIdentifier id, Class messageType, BiConsumer encoder, Function decoder, Consumer> handler) { + if (INSTANCE != null) { + return INSTANCE.packetRegistry.registerPacket(id, messageType, encoder, decoder, handler); + } else { + return getDelayedHandler().registerPacket(id, messageType, encoder, decoder, handler); + } + } + +} \ No newline at end of file diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/DeferredPacketRegistrar.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/DeferredPacketRegistrar.java new file mode 100644 index 0000000..25556f4 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/DeferredPacketRegistrar.java @@ -0,0 +1,41 @@ +package com.hypherionmc.craterlib.core.networking; + +import com.hypherionmc.craterlib.core.networking.data.PacketContext; +import com.hypherionmc.craterlib.core.networking.data.PacketHolder; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.nojang.network.BridgedFriendlyByteBuf; +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public class DeferredPacketRegistrar implements PacketRegistrar { + + private static final Map, PacketHolder> QUEUED_PACKET_MAP = new HashMap<>(); + + @Override + public PacketSide side() { + return PacketSide.CLIENT; + } + + @Override + public PacketRegistrar registerPacket(ResourceIdentifier packetIdentifier, Class messageType, BiConsumer encoder, Function decoder, Consumer> handler) { + PacketHolder container = new PacketHolder<>(packetIdentifier, messageType, encoder, decoder, handler); + QUEUED_PACKET_MAP.put(messageType, container); + return this; + } + + + public void registerQueuedPackets(PacketRegistry packetRegistration) { + if (!QUEUED_PACKET_MAP.isEmpty()) { + packetRegistration.PACKET_MAP.putAll(QUEUED_PACKET_MAP); + QUEUED_PACKET_MAP.forEach((aClass, container) -> packetRegistration.registerPacket(container)); + } + } +} \ No newline at end of file diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/PacketRegistrar.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/PacketRegistrar.java new file mode 100644 index 0000000..3ea55fa --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/PacketRegistrar.java @@ -0,0 +1,21 @@ +package com.hypherionmc.craterlib.core.networking; + +import com.hypherionmc.craterlib.core.networking.data.PacketContext; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.nojang.network.BridgedFriendlyByteBuf; +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; + +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public interface PacketRegistrar { + + PacketSide side(); + + PacketRegistrar registerPacket(ResourceIdentifier id, Class messageType, BiConsumer encoder, Function decoder, Consumer> handler); + +} \ No newline at end of file diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/PacketRegistry.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/PacketRegistry.java new file mode 100644 index 0000000..d1ec06c --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/PacketRegistry.java @@ -0,0 +1,41 @@ +package com.hypherionmc.craterlib.core.networking; + +import com.hypherionmc.craterlib.api.networking.CraterNetworkHandler; +import com.hypherionmc.craterlib.core.networking.data.PacketContext; +import com.hypherionmc.craterlib.core.networking.data.PacketHolder; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.nojang.network.BridgedFriendlyByteBuf; +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public abstract class PacketRegistry implements CraterNetworkHandler, PacketRegistrar { + + protected final Map, PacketHolder> PACKET_MAP = new HashMap<>(); + + protected final PacketSide side; + + public PacketRegistry(PacketSide side) { + this.side = side; + } + + public PacketRegistrar registerPacket(ResourceIdentifier id, Class messageType, BiConsumer encoder, Function decoder, Consumer> handler) { + PacketHolder holder = new PacketHolder<>(id, messageType, encoder, decoder, handler); + PACKET_MAP.put(messageType, holder); + registerPacket(holder); + return this; + } + + public PacketSide side() { + return side; + } + + protected abstract void registerPacket(PacketHolder packetHolder); +} \ No newline at end of file diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketContext.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketContext.java new file mode 100644 index 0000000..fc432dc --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketContext.java @@ -0,0 +1,15 @@ +package com.hypherionmc.craterlib.core.networking.data; + +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import org.jetbrains.annotations.Nullable; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public record PacketContext(@Nullable BridgedPlayer sender, T message, PacketSide side) { + + public PacketContext(T message, PacketSide side) { + this(null, message, side); + } + +} \ No newline at end of file diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketHolder.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketHolder.java new file mode 100644 index 0000000..14b6947 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketHolder.java @@ -0,0 +1,18 @@ +package com.hypherionmc.craterlib.core.networking.data; + +import com.hypherionmc.craterlib.nojang.network.BridgedFriendlyByteBuf; +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; + +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public record PacketHolder(ResourceIdentifier type, + Class messageType, + BiConsumer encoder, + Function decoder, + Consumer> handler) { +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketSide.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketSide.java new file mode 100644 index 0000000..ad6f8ec --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketSide.java @@ -0,0 +1,13 @@ +package com.hypherionmc.craterlib.core.networking.data; + +public enum PacketSide { + CLIENT, + SERVER; + + public PacketSide flipped() { + if (CLIENT.equals(this)) + return SERVER; + + return CLIENT; + } +} \ No newline at end of file diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/ClientPlatform.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/ClientPlatform.java new file mode 100644 index 0000000..089fb36 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/ClientPlatform.java @@ -0,0 +1,23 @@ +package com.hypherionmc.craterlib.core.platform; + +import com.hypherionmc.craterlib.nojang.client.BridgedMinecraft; +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedClientLevel; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.InternalServiceUtil; +import net.minecraft.network.Connection; + +/** + * @author HypherionSA + */ +public interface ClientPlatform { + + public final ClientPlatform INSTANCE = InternalServiceUtil.load(ClientPlatform.class); + + BridgedMinecraft getClientInstance(); + + BridgedPlayer getClientPlayer(); + + BridgedClientLevel getClientLevel(); + + Connection getClientConnection(); +} \ No newline at end of file diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/CommonPlatform.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/CommonPlatform.java new file mode 100644 index 0000000..ee7f944 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/CommonPlatform.java @@ -0,0 +1,15 @@ +package com.hypherionmc.craterlib.core.platform; + +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import com.hypherionmc.craterlib.utils.InternalServiceUtil; + +/** + * @author HypherionSA + */ +public interface CommonPlatform { + + public CommonPlatform INSTANCE = InternalServiceUtil.load(CommonPlatform.class); + + BridgedMinecraftServer getMCServer(); + +} \ No newline at end of file diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/CompatUtils.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/CompatUtils.java new file mode 100644 index 0000000..fb745fc --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/CompatUtils.java @@ -0,0 +1,13 @@ +package com.hypherionmc.craterlib.core.platform; + +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.InternalServiceUtil; + +public interface CompatUtils { + + public static final CompatUtils INSTANCE = InternalServiceUtil.load(CompatUtils.class); + + boolean isPlayerActive(BridgedPlayer player); + String getSkinUUID(BridgedPlayer player); + +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/Environment.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/Environment.java new file mode 100644 index 0000000..2ef219e --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/Environment.java @@ -0,0 +1,18 @@ +package com.hypherionmc.craterlib.core.platform; + +/** + * @author HypherionSA + */ +public enum Environment { + CLIENT, + SERVER, + UNKNOWN; + + public boolean isClient() { + return this == CLIENT; + } + + public boolean isServer() { + return this == SERVER; + } +} \ No newline at end of file diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/ModloaderEnvironment.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/ModloaderEnvironment.java new file mode 100644 index 0000000..c1bc176 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/ModloaderEnvironment.java @@ -0,0 +1,32 @@ +package com.hypherionmc.craterlib.core.platform; + +import com.hypherionmc.craterlib.utils.InternalServiceUtil; + +import java.io.File; + +/** + * @author HypherionSA + * Helper class to provide information about the ModLoader + */ +public interface ModloaderEnvironment { + + public final ModloaderEnvironment INSTANCE = InternalServiceUtil.load(ModloaderEnvironment.class); + + boolean isFabric(); + + String getGameVersion(); + + File getGameFolder(); + + File getConfigFolder(); + + File getModsFolder(); + + Environment getEnvironment(); + + boolean isModLoaded(String modid); + + boolean isDevEnv(); + + int getModCount(); +} \ No newline at end of file diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordEventHandlers.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordEventHandlers.java new file mode 100644 index 0000000..3f1ffcb --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordEventHandlers.java @@ -0,0 +1,90 @@ +package com.hypherionmc.craterlib.core.rpcsdk; + +import com.hypherionmc.craterlib.core.rpcsdk.callbacks.*; +import com.sun.jna.Structure; + +import java.util.Arrays; +import java.util.List; + +/** + * @author HypherionSA + * Class containing references to all available discord event handles. + * Registering a handler is optional, and non-assigned handlers will be ignored + */ +public class DiscordEventHandlers extends Structure { + + // Callback for when the RPC was initialized successfully + public ReadyCallback ready; + + // Callback for when the Discord connection was ended + public DisconnectedCallback disconnected; + + // Callback for when a Discord Error occurs + public ErroredCallback errored; + + // Callback for when a player joins the game + public JoinGameCallback joinGame; + + // Callback for when a player spectates the game + public SpectateGameCallback spectateGame; + + // Callback for when a players request to join your game + public JoinRequestCallback joinRequest; + + /** + * DO NOT TOUCH THIS... EVER! + */ + @Override + protected List getFieldOrder() { + return Arrays.asList( + "ready", + "disconnected", + "errored", + "joinGame", + "spectateGame", + "joinRequest" + ); + } + + public static class Builder { + private final DiscordEventHandlers handlers; + + public Builder() { + this.handlers = new DiscordEventHandlers(); + } + + public Builder ready(ReadyCallback readyCallback) { + handlers.ready = readyCallback; + return this; + } + + public Builder disconnected(DisconnectedCallback disconnectedCallback) { + handlers.disconnected = disconnectedCallback; + return this; + } + + public Builder errored(ErroredCallback erroredCallback) { + handlers.errored = erroredCallback; + return this; + } + + public Builder joinGame(JoinGameCallback joinGameCallback) { + handlers.joinGame = joinGameCallback; + return this; + } + + public Builder spectateGame(SpectateGameCallback spectateGameCallback) { + handlers.spectateGame = spectateGameCallback; + return this; + } + + public Builder joinRequest(JoinRequestCallback joinRequestCallback) { + handlers.joinRequest = joinRequestCallback; + return this; + } + + public DiscordEventHandlers build() { + return handlers; + } + } +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordRPC.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordRPC.java new file mode 100644 index 0000000..7cf53ff --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordRPC.java @@ -0,0 +1,99 @@ +package com.hypherionmc.craterlib.core.rpcsdk; + +import com.sun.jna.Library; +import com.sun.jna.Native; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author HypherionSA + * Java Wrapper of the Discord-RPC Library + */ +public interface DiscordRPC extends Library { + + DiscordRPC INSTANCE = Native.load("discord-rpc", DiscordRPC.class); + + /** + * Open a New RPC Connection + * + * @param applicationId The ID of the Application the RPC is tied to + * @param handlers Optional Event Callback Handlers + * @param autoRegister Auto Register the running game + * @param steamId Steam ID of the game + */ + void Discord_Initialize(@NotNull String applicationId, @Nullable DiscordEventHandlers handlers, boolean autoRegister, @Nullable String steamId); + + /** + * Shutdown the RPC instance and disconnect from discord + */ + void Discord_Shutdown(); + + /** + * Need to be called manually at least every 2 seconds, to allow RPC updates + * and callback handlers to fire + */ + void Discord_RunCallbacks(); + + /** + * Not sure about this. Believe it needs to be called manually in some circumstances + */ + void Discord_UpdateConnection(); + + /** + * Update the Rich Presence + * + * @param struct Constructed {@link DiscordRichPresence} + */ + void Discord_UpdatePresence(@Nullable DiscordRichPresence struct); + + /** + * Clear the current Rich Presence + */ + void Discord_ClearPresence(); + + /** + * Respond to Join/Spectate callback + * + * @param userid The Discord User ID of the user that initiated the request + * @param reply Reply to the request. See {@link DiscordReply} + */ + void Discord_Respond(@NotNull String userid, int reply); + + /** + * Replace the already registered {@link DiscordEventHandlers} + * + * @param handlers The new handlers to apply + */ + void Discord_UpdateHandlers(@Nullable DiscordEventHandlers handlers); + + /** + * Register the executable of the application/game + * Only applicable when autoRegister is set to false + * + * @param applicationId The Application ID + * @param command The Launch command of the game + *

+ * NB: THIS DOES NOT WORK WITH MINECRAFT + */ + void Discord_Register(String applicationId, String command); + + /** + * Register the Steam executable of the application/game + * + * @param applicationId The Application ID + * @param steamId The Steam ID of the application/game + */ + void Discord_RegisterSteamGame(String applicationId, String steamId); + + public enum DiscordReply { + NO(0), + YES(1), + IGNORE(2); + + public final int reply; + + DiscordReply(int reply) { + this.reply = reply; + } + } +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordRichPresence.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordRichPresence.java new file mode 100644 index 0000000..51fd720 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordRichPresence.java @@ -0,0 +1,248 @@ +package com.hypherionmc.craterlib.core.rpcsdk; + +import com.hypherionmc.craterlib.core.rpcsdk.helpers.RPCButton; +import com.sun.jna.Structure; +import org.jetbrains.annotations.NotNull; + +import java.time.OffsetDateTime; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * @author HypherionSA + * Class reprenting a Discord RPC activity + */ +public class DiscordRichPresence extends Structure { + + // First line of text on the RPC + public String state; + + // Second line of text on the RPC + public String details; + + // Time the activity started in UNIX-Timestamp format + public long startTimestamp; + + // Time the activity will end in UNIX-Timestamp format + public long endTimestamp; + + // URL or Asset key of the Large Image + public String largeImageKey; + + // Hover text to display when hovering the Large Image + public String largeImageText; + + // URL or Asset key of the Small Image + public String smallImageKey; + + // Hover text to display when hovering the Small Image + public String smallImageText; + + // Id of the player's party, lobby, or group. + public String partyId; + + // Current size of the player's party, lobby, or group. + public int partySize; + + // Maximum size of the player's party, lobby, or group. + public int partyMax; + + // Unused + public String partyPrivacy; + + // Unused. + public String matchSecret; + + // Unique hashed string for chat invitations and Ask to Join. + public String joinSecret; + + // Unique hashed string for Spectate button. + public String spectateSecret; + + // Label of the First RPC Button + public String button_label_1; + + // URL of the First RPC Button + public String button_url_1; + + // Label of the Second RPC Button + public String button_label_2; + + // URL of the Second RPC Button + public String button_url_2; + + // Unused + public int instance; + + public DiscordRichPresence() { + setStringEncoding("UTF-8"); + } + + /** + * DO NOT TOUCH THIS... EVER! + */ + @Override + protected List getFieldOrder() { + return Arrays.asList( + "state", + "details", + "startTimestamp", + "endTimestamp", + "largeImageKey", + "largeImageText", + "smallImageKey", + "smallImageText", + "partyId", + "partySize", + "partyMax", + "partyPrivacy", + "matchSecret", + "joinSecret", + "spectateSecret", + "button_label_1", + "button_url_1", + "button_label_2", + "button_url_2", + "instance" + ); + } + + public static class Builder { + private final DiscordRichPresence rpc; + + public Builder(String state) { + rpc = new DiscordRichPresence(); + + if (state != null && !state.isEmpty()) { + rpc.state = state.substring(0, Math.min(state.length(), 128)); + } + } + + public Builder setDetails(String details) { + if (details != null && !details.isEmpty()) { + rpc.details = details.substring(0, Math.min(details.length(), 128)); + } + return this; + } + + public Builder setStartTimestamp(long timestamp) { + rpc.startTimestamp = timestamp; + return this; + } + + public Builder setStartTimestamp(OffsetDateTime timestamp) { + rpc.startTimestamp = timestamp.toEpochSecond(); + return this; + } + + public Builder setEndTimestamp(long timestamp) { + rpc.endTimestamp = timestamp; + return this; + } + + public Builder setEndTimestamp(OffsetDateTime timestamp) { + rpc.endTimestamp = timestamp.toEpochSecond(); + return this; + } + + public Builder setLargeImage(String key) { + return this.setLargeImage(key, ""); + } + + public Builder setLargeImage(@NotNull String key, String text) { + // Null check used for users blatantly ignoring the NotNull marker + if ((text != null && !text.isEmpty()) && key != null) { + throw new IllegalArgumentException("Image key cannot be null when assigning a hover text"); + } + + rpc.largeImageKey = key; + rpc.largeImageText = text; + return this; + } + + public Builder setSmallImage(String key) { + return this.setSmallImage(key, ""); + } + + public Builder setSmallImage(@NotNull String key, String text) { + // Null check used for users blatantly ignoring the NotNull marker + if ((text != null && !text.isEmpty()) && key != null) { + throw new IllegalArgumentException("Image key cannot be null when assigning a hover text"); + } + + rpc.smallImageKey = key; + rpc.smallImageText = text; + return this; + } + + public Builder setParty(String party, int size, int max) { + // Buttons are present, ignore + if ((rpc.button_label_1 != null && rpc.button_label_1.isEmpty()) || (rpc.button_label_2 != null && rpc.button_label_2.isEmpty())) + return this; + + rpc.partyId = party; + rpc.partySize = size; + rpc.partyMax = max; + return this; + } + + public Builder setSecrets(String match, String join, String spectate) { + // Buttons are present, ignore + if ((rpc.button_label_1 != null && rpc.button_label_1.isEmpty()) || (rpc.button_label_2 != null && rpc.button_label_2.isEmpty())) + return this; + + rpc.matchSecret = match; + rpc.joinSecret = join; + rpc.spectateSecret = spectate; + return this; + } + + public Builder setSecrets(String join, String spectate) { + // Buttons are present, ignore + if ((rpc.button_label_1 != null && rpc.button_label_1.isEmpty()) || (rpc.button_label_2 != null && rpc.button_label_2.isEmpty())) + return this; + + rpc.joinSecret = join; + rpc.spectateSecret = spectate; + return this; + } + + public Builder setInstance(boolean i) { + // Buttons are present, ignore + if ((rpc.button_label_1 != null && rpc.button_label_1.isEmpty()) || (rpc.button_label_2 != null && rpc.button_label_2.isEmpty())) + return this; + + rpc.instance = i ? 1 : 0; + return this; + } + + public Builder setButtons(RPCButton button) { + return this.setButtons(Collections.singletonList(button)); + } + + public Builder setButtons(RPCButton button1, RPCButton button2) { + return this.setButtons(Arrays.asList(button1, button2)); + } + + public Builder setButtons(List rpcButtons) { + // Limit to 2 Buttons. Discord Limitation + if (rpcButtons != null && !rpcButtons.isEmpty()) { + int length = Math.min(rpcButtons.size(), 2); + rpc.button_label_1 = rpcButtons.get(0).getLabel(); + rpc.button_url_1 = rpcButtons.get(0).getUrl(); + + if (length == 2) { + rpc.button_label_2 = rpcButtons.get(1).getLabel(); + rpc.button_url_2 = rpcButtons.get(1).getUrl(); + } + } + + return this; + } + + public DiscordRichPresence build() { + return rpc; + } + } +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordUser.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordUser.java new file mode 100644 index 0000000..e8bc085 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordUser.java @@ -0,0 +1,39 @@ +package com.hypherionmc.craterlib.core.rpcsdk; + +import com.sun.jna.Structure; + +import java.util.Arrays; +import java.util.List; + +/** + * @author HypherionSA + * Class representing the Discord User + */ +public class DiscordUser extends Structure { + + // The User ID of the User + public String userId; + + // The Username of the User + public String username; + + // The unique identifier of the user. Discontinued by Discord + @Deprecated + public String discriminator; + + // The avatar has of the user + public String avatar; + + /** + * DO NOT TOUCH THIS... EVER! + */ + @Override + protected List getFieldOrder() { + return Arrays.asList( + "userId", + "username", + "discriminator", + "avatar" + ); + } +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/DisconnectedCallback.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/DisconnectedCallback.java new file mode 100644 index 0000000..7ea59e1 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/DisconnectedCallback.java @@ -0,0 +1,18 @@ +package com.hypherionmc.craterlib.core.rpcsdk.callbacks; + +import com.sun.jna.Callback; + +/** + * @author HypherionSA + * Callback for when the Discord RPC disconnects + */ +public interface DisconnectedCallback extends Callback { + + /** + * Called when RPC disconnected + * + * @param errorCode Error code if any + * @param message Details about the disconnection + */ + void apply(int errorCode, String message); +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/ErroredCallback.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/ErroredCallback.java new file mode 100644 index 0000000..1f86c90 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/ErroredCallback.java @@ -0,0 +1,18 @@ +package com.hypherionmc.craterlib.core.rpcsdk.callbacks; + +import com.sun.jna.Callback; + +/** + * @author HypherionSA + * Callback for when the RPC ran into an error + */ +public interface ErroredCallback extends Callback { + + /** + * Called when an RPC error occurs + * + * @param errorCode Error code if any + * @param message Details about the error + */ + void apply(int errorCode, String message); +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/JoinGameCallback.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/JoinGameCallback.java new file mode 100644 index 0000000..cc752af --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/JoinGameCallback.java @@ -0,0 +1,17 @@ +package com.hypherionmc.craterlib.core.rpcsdk.callbacks; + +import com.sun.jna.Callback; + +/** + * @author HypherionSA + * Callback for when someone was approved to join your game + */ +public interface JoinGameCallback extends Callback { + + /** + * Called when someone joins a game from {@link JoinRequestCallback} + * + * @param joinSecret Secret or Password required to let the player join the game + */ + void apply(String joinSecret); +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/JoinRequestCallback.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/JoinRequestCallback.java new file mode 100644 index 0000000..115fd4f --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/JoinRequestCallback.java @@ -0,0 +1,19 @@ +package com.hypherionmc.craterlib.core.rpcsdk.callbacks; + +import com.hypherionmc.craterlib.core.rpcsdk.DiscordUser; +import com.sun.jna.Callback; + +/** + * @author HypherionSA + * Callback for when someone requests to join your game + */ +public interface JoinRequestCallback extends Callback { + + /** + * Called when someone clicks on the Join Game button + * + * @param user The Discord User trying to join your game + * @see DiscordUser + */ + void apply(DiscordUser user); +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/ReadyCallback.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/ReadyCallback.java new file mode 100644 index 0000000..66f3b59 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/ReadyCallback.java @@ -0,0 +1,19 @@ +package com.hypherionmc.craterlib.core.rpcsdk.callbacks; + +import com.hypherionmc.craterlib.core.rpcsdk.DiscordUser; +import com.sun.jna.Callback; + +/** + * @author HypherionSA + * Callback for when the RPC has connected successfully + */ +public interface ReadyCallback extends Callback { + + /** + * Called when the RPC is connected and ready to be used + * + * @param user The user the RPC is displayed on + * @see DiscordUser + */ + void apply(DiscordUser user); +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/SpectateGameCallback.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/SpectateGameCallback.java new file mode 100644 index 0000000..979e53d --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/SpectateGameCallback.java @@ -0,0 +1,17 @@ +package com.hypherionmc.craterlib.core.rpcsdk.callbacks; + +import com.sun.jna.Callback; + +/** + * @author HypherionSA + * Callback for when someone is requesting to spectate your game + */ +public interface SpectateGameCallback extends Callback { + + /** + * Called when joining the game + * + * @param spectateSecret Secret or Password required to let the player spectate + */ + void apply(String spectateSecret); +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/helpers/RPCButton.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/helpers/RPCButton.java new file mode 100644 index 0000000..ab2bdc8 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/helpers/RPCButton.java @@ -0,0 +1,55 @@ +package com.hypherionmc.craterlib.core.rpcsdk.helpers; + +import org.jetbrains.annotations.NotNull; + +import java.io.Serializable; + +/** + * @author HypherionSA + * Helper class to add Buttons to Discord Rich Presence + * This can not be used with Join/Spectate + */ +public class RPCButton implements Serializable { + + // The label of the button + private final String label; + + // The URL the button will open when clicked + private final String url; + + protected RPCButton(String label, String url) { + this.label = label; + this.url = url; + } + + /** + * Create a new RPC Button + * + * @param label The label of the button + * @param url The URL the button will open when clicked + * @return The constructed button + */ + public static RPCButton create(@NotNull String label, @NotNull String url) { + // Null check used here for users blatantly ignoring the NotNull marker + if (label == null || label.isEmpty() || url == null || url.isEmpty()) { + throw new IllegalArgumentException("RPC Buttons require both a label and url"); + } + + label = label.substring(0, Math.min(label.length(), 31)); + return new RPCButton(label, url); + } + + /** + * @return The label assigned to the button + */ + public String getLabel() { + return label; + } + + /** + * @return The URL of the button + */ + public String getUrl() { + return url; + } +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/ChatInputSuggestorMixin.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/ChatInputSuggestorMixin.java new file mode 100644 index 0000000..c48dfbc --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/ChatInputSuggestorMixin.java @@ -0,0 +1,84 @@ +package com.hypherionmc.craterlib.mixin; + +import com.hypherionmc.craterlib.client.mentions.MentionsController; +import net.minecraft.client.gui.components.CommandSuggestions; +import net.minecraft.client.gui.components.EditBox; +import org.objectweb.asm.Opcodes; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.ModifyVariable; +import org.spongepowered.asm.mixin.injection.Slice; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * @author HypherionSA + * Allow Users, Roles and Channels to be pingable from MC chat (Client Side) + */ +@Mixin(CommandSuggestions.class) +public abstract class ChatInputSuggestorMixin { + + @Shadow + public abstract void showSuggestions(boolean p_93931_); + + @Shadow @Final + EditBox input; + + @Shadow + private static int getLastWordIndex(String p_93913_) { + return 0; + } + + @Inject( + method = "updateCommandInfo", + at = @At( + value = "FIELD", + target = "Lnet/minecraft/client/gui/components/CommandSuggestions;pendingSuggestions:Ljava/util/concurrent/CompletableFuture;", + opcode = Opcodes.PUTFIELD, + shift = At.Shift.AFTER, + ordinal = 0 + ), + slice = @Slice( + from = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/gui/components/CommandSuggestions;getLastWordIndex(Ljava/lang/String;)I" + ) + ) + ) + private void injectSuggestions(CallbackInfo ci) { + if (MentionsController.hasMentions() && MentionsController.isLastMentionConditional()) { + this.showSuggestions(true); + } + } + + @SuppressWarnings("InvalidInjectorMethodSignature") + @ModifyVariable(method = "updateCommandInfo", at = @At(value = "STORE"), ordinal = 0, name = "collection") + private Collection injectMentions(Collection vanilla) { + if (!MentionsController.hasMentions()) + return vanilla; + + ArrayList newSuggest = new ArrayList<>(vanilla); + + String currentInput = this.input.getValue(); + int currentCursorPosition = this.input.getCursorPosition(); + + String textBeforeCursor = currentInput.substring(0, currentCursorPosition); + int startOfCurrentWord = getLastWordIndex(textBeforeCursor); + + String currentWord = textBeforeCursor.substring(startOfCurrentWord); + String finalWord = currentWord.replace("[", "").replace("]", ""); + + Collection mentions = MentionsController.getMentions(finalWord); + + if (!mentions.isEmpty()) { + mentions.forEach(m -> newSuggest.add("[" + m + "]")); + } + + return newSuggest; + } +} \ No newline at end of file diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/CommandMixin.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/CommandMixin.java new file mode 100644 index 0000000..3cf7801 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/CommandMixin.java @@ -0,0 +1,57 @@ +package com.hypherionmc.craterlib.mixin.events; + +import com.google.common.base.Throwables; +import com.hypherionmc.craterlib.api.events.server.CraterCommandEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.ParseResults; +import com.mojang.brigadier.StringReader; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(Commands.class) +public class CommandMixin { + + @Shadow + @Final + private CommandDispatcher dispatcher; + + @Inject( + method = "performCommand", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/util/profiling/ProfilerFiller;push(Ljava/lang/String;)V", + shift = At.Shift.AFTER + ), + cancellable = true + ) + private void injectCommandEvent(CommandSourceStack stack, String command, CallbackInfoReturnable cir) { + StringReader stringreader = new StringReader(command); + if (stringreader.canRead() && stringreader.peek() == '/') { + stringreader.skip(); + } + + try { + ParseResults parse = dispatcher.parse(stringreader, stack); + + CraterCommandEvent commandEvent = CraterCommandEvent.of(parse, command); + CraterEventBus.INSTANCE.postEvent(commandEvent); + if (commandEvent.wasCancelled()) { + cir.setReturnValue(1); + return; + } + + if (commandEvent.getException() != null) { + Throwables.throwIfUnchecked(commandEvent.getException()); + cir.setReturnValue(1); + } + } catch (Exception ignored) {} + } + +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerAdvancementsMixin.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerAdvancementsMixin.java new file mode 100644 index 0000000..2457176 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerAdvancementsMixin.java @@ -0,0 +1,29 @@ +package com.hypherionmc.craterlib.mixin.events; + +import com.hypherionmc.craterlib.api.events.server.CraterAdvancementEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.advancements.BridgedAdvancement; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraft.advancements.Advancement; +import net.minecraft.server.PlayerAdvancements; +import net.minecraft.server.level.ServerPlayer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(PlayerAdvancements.class) +public class PlayerAdvancementsMixin { + + @Shadow + private ServerPlayer player; + + @Inject(method = "award", at = @At(value = "INVOKE", target = "Lnet/minecraft/advancements/AdvancementRewards;grant(Lnet/minecraft/server/level/ServerPlayer;)V", shift = At.Shift.AFTER)) + private void injectAdvancementEvent(Advancement advancement, String string, CallbackInfoReturnable cir) { + if (advancement.getDisplay() == null || !advancement.getDisplay().shouldAnnounceChat()) + return; + + CraterEventBus.INSTANCE.postEvent(new CraterAdvancementEvent(BridgedPlayer.of(this.player), BridgedAdvancement.of(advancement))); + } +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerListMixin.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerListMixin.java new file mode 100644 index 0000000..805a014 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerListMixin.java @@ -0,0 +1,53 @@ +package com.hypherionmc.craterlib.mixin.events; + +import com.hypherionmc.craterlib.api.events.server.CraterPlayerEvent; +import com.hypherionmc.craterlib.api.events.server.MessageBroadcastEvent; +import com.hypherionmc.craterlib.api.events.server.PlayerPreLoginEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.authlib.BridgedGameProfile; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.ChatUtils; +import com.mojang.authlib.GameProfile; +import net.minecraft.network.Connection; +import net.minecraft.network.chat.ChatType; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.players.PlayerList; +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; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.net.SocketAddress; +import java.util.UUID; + +@Mixin(PlayerList.class) +public class PlayerListMixin { + + @Inject(method = "broadcastMessage(Lnet/minecraft/network/chat/Component;Lnet/minecraft/network/chat/ChatType;Ljava/util/UUID;)V", at = @At("HEAD")) + private void injectBroadcast(Component component, ChatType chatType, UUID uUID, CallbackInfo ci) { + String thread = Thread.currentThread().getStackTrace()[3].getClassName(); + MessageBroadcastEvent event = new MessageBroadcastEvent(ChatUtils.mojangToAdventure(component), (s) -> null, false, thread); + CraterEventBus.INSTANCE.postEvent(event); + } + + @Inject(method = "placeNewPlayer", at = @At("TAIL")) + private void injectPlayerLoginEvent(Connection arg, ServerPlayer serverPlayer, CallbackInfo ci) { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerEvent.PlayerLoggedIn(BridgedPlayer.of(serverPlayer))); + } + + @Inject(method = "remove", at = @At("HEAD")) + private void injectPlayerLogoutEvent(ServerPlayer player, CallbackInfo ci) { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerEvent.PlayerLoggedOut(BridgedPlayer.of(player))); + } + + @Inject(method = "canPlayerLogin", at = @At("HEAD"), cancellable = true) + private void injectPreLoginEvent(SocketAddress address, GameProfile gameProfile, CallbackInfoReturnable cir) { + PlayerPreLoginEvent event = new PlayerPreLoginEvent(address, BridgedGameProfile.of(gameProfile)); + CraterEventBus.INSTANCE.postEvent(event); + if (event.getMessage() != null) { + cir.setReturnValue(ChatUtils.adventureToMojang(event.getMessage())); + } + } +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerMixin.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerMixin.java new file mode 100644 index 0000000..5fd03bd --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerMixin.java @@ -0,0 +1,21 @@ +package com.hypherionmc.craterlib.mixin.events; + +import com.hypherionmc.craterlib.api.events.common.CraterPlayerDeathEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.player.Player; +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(Player.class) +public class PlayerMixin { + + @Inject(method = "die", at = @At("HEAD")) + private void injectPlayerDeathEvent(DamageSource damageSource, CallbackInfo ci) { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerDeathEvent(BridgedPlayer.of(((Player) (Object) this)), damageSource)); + } + +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/ServerPlayerMixin.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/ServerPlayerMixin.java new file mode 100644 index 0000000..ae028e7 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/ServerPlayerMixin.java @@ -0,0 +1,21 @@ +package com.hypherionmc.craterlib.mixin.events; + +import com.hypherionmc.craterlib.api.events.common.CraterPlayerDeathEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.damagesource.DamageSource; +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(ServerPlayer.class) +public class ServerPlayerMixin { + + @Inject(method = "die", at = @At("HEAD")) + private void injectPlayerDeathEvent(DamageSource damageSource, CallbackInfo ci) { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerDeathEvent(BridgedPlayer.of(((ServerPlayer) (Object) this)), damageSource)); + } + +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/ClientLevelMixin.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/ClientLevelMixin.java new file mode 100644 index 0000000..3535d6f --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/ClientLevelMixin.java @@ -0,0 +1,25 @@ +package com.hypherionmc.craterlib.mixin.events.client; + +import com.hypherionmc.craterlib.api.events.client.CraterSinglePlayerEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; +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(ClientLevel.class) +public class ClientLevelMixin { + + @Inject(method = "addEntity", at = @At("HEAD")) + private void injectSinglePlayerJoinEvent(int i, Entity entity, CallbackInfo ci) { + if (entity instanceof Player player) { + CraterSinglePlayerEvent.PlayerLogin playerLogin = new CraterSinglePlayerEvent.PlayerLogin(BridgedPlayer.of(player)); + CraterEventBus.INSTANCE.postEvent(playerLogin); + } + } + +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/MinecraftMixin.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/MinecraftMixin.java new file mode 100644 index 0000000..2903131 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/MinecraftMixin.java @@ -0,0 +1,31 @@ +package com.hypherionmc.craterlib.mixin.events.client; + +import com.hypherionmc.craterlib.api.events.client.ScreenEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.client.gui.BridgedScreen; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.Screen; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(Minecraft.class) +public class MinecraftMixin { + + @Shadow + @Nullable + public Screen screen; + + @Inject(method = "setScreen", at = @At(value = "TAIL")) + private void injectScreenOpeningEvent(Screen screen, CallbackInfo ci) { + Screen old = this.screen; + if (screen != null) { + ScreenEvent.Opening opening = new ScreenEvent.Opening(BridgedScreen.of(old), BridgedScreen.of(screen)); + CraterEventBus.INSTANCE.postEvent(opening); + } + } + +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/RealmsMainScreenMixin.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/RealmsMainScreenMixin.java new file mode 100644 index 0000000..d2445c3 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/RealmsMainScreenMixin.java @@ -0,0 +1,23 @@ +package com.hypherionmc.craterlib.mixin.events.client; + +import com.hypherionmc.craterlib.api.events.client.PlayerJoinRealmEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.realmsclient.dto.BridgedRealmsServer; +import com.mojang.realmsclient.RealmsMainScreen; +import com.mojang.realmsclient.dto.RealmsServer; +import net.minecraft.client.gui.screens.Screen; +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 RealmsMainScreenMixin { + + @Inject(at = @At("HEAD"), method = "play") + private void play(RealmsServer serverData, Screen arg2, CallbackInfo ci) { + PlayerJoinRealmEvent playerJoinRealm = new PlayerJoinRealmEvent(BridgedRealmsServer.of(serverData)); + CraterEventBus.INSTANCE.postEvent(playerJoinRealm); + } + +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/advancements/BridgedAdvancement.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/advancements/BridgedAdvancement.java new file mode 100644 index 0000000..6b8e376 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/advancements/BridgedAdvancement.java @@ -0,0 +1,21 @@ +package com.hypherionmc.craterlib.nojang.advancements; + +import lombok.RequiredArgsConstructor; +import net.minecraft.advancements.Advancement; + +import java.util.Optional; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedAdvancement { + + private final Advancement internal; + + public Optional displayInfo() { + if (internal.getDisplay() != null) { + return Optional.of(BridgedDisplayInfo.of(internal.getDisplay())); + } + + return Optional.empty(); + } + +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/advancements/BridgedDisplayInfo.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/advancements/BridgedDisplayInfo.java new file mode 100644 index 0000000..5a0c155 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/advancements/BridgedDisplayInfo.java @@ -0,0 +1,29 @@ +package com.hypherionmc.craterlib.nojang.advancements; + +import com.hypherionmc.craterlib.utils.ChatUtils; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; +import net.minecraft.advancements.DisplayInfo; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedDisplayInfo { + + private final DisplayInfo internal; + + public boolean shouldDisplay() { + return internal.shouldAnnounceChat(); + } + + public boolean isHidden() { + return internal.isHidden(); + } + + public Component displayName() { + return ChatUtils.mojangToAdventure(internal.getTitle()); + } + + public Component description() { + return ChatUtils.mojangToAdventure(internal.getDescription()); + } + +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/authlib/BridgedGameProfile.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/authlib/BridgedGameProfile.java new file mode 100644 index 0000000..c83f3c4 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/authlib/BridgedGameProfile.java @@ -0,0 +1,29 @@ +package com.hypherionmc.craterlib.nojang.authlib; + +import com.mojang.authlib.GameProfile; +import lombok.RequiredArgsConstructor; + +import java.util.UUID; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedGameProfile { + + private final GameProfile internal; + + public static BridgedGameProfile mojang(UUID id, String name) { + return new BridgedGameProfile(new GameProfile(id, name)); + } + + public String getName() { + return internal.getName(); + } + + public UUID getId() { + return internal.getId(); + } + + public GameProfile toMojang() { + return internal; + } + +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/BridgedMinecraft.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/BridgedMinecraft.java new file mode 100644 index 0000000..35df5c1 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/BridgedMinecraft.java @@ -0,0 +1,84 @@ +package com.hypherionmc.craterlib.nojang.client; + +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedClientLevel; +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedServerData; +import com.hypherionmc.craterlib.nojang.client.server.BridgedIntegratedServer; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import lombok.Getter; +import net.minecraft.SharedConstants; +import net.minecraft.client.Minecraft; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.util.UUID; + +public class BridgedMinecraft { + + @Getter + private static final BridgedMinecraft instance = new BridgedMinecraft(); + private final Minecraft internal = Minecraft.getInstance(); + + public File getGameDirectory() { + return internal.gameDirectory; + } + + public BridgedOptions getOptions() { + return BridgedOptions.of(internal.options); + } + + @Nullable + public BridgedClientLevel getLevel() { + if (internal.level == null) + return null; + + return BridgedClientLevel.of(internal.level); + } + + public boolean isRealmServer() { + return internal.getCurrentServer() != null && internal.isConnectedToRealms(); + } + + public boolean isSinglePlayer() { + return internal.hasSingleplayerServer(); + } + + @Nullable + public BridgedPlayer getPlayer() { + if (internal.player == null) + return null; + + return BridgedPlayer.of(internal.player); + } + + public String getGameVersion() { + return SharedConstants.getCurrentVersion().getName(); + } + + public String getUserName() { + return internal.getUser().getName(); + } + + public UUID getPlayerId() { + return internal.getUser().getGameProfile().getId(); + } + + @Nullable + public BridgedServerData getCurrentServer() { + if (internal.getCurrentServer() == null) + return null; + + return BridgedServerData.of(internal.getCurrentServer()); + } + + @Nullable + public BridgedIntegratedServer getSinglePlayerServer() { + return BridgedIntegratedServer.of(internal.getSingleplayerServer()); + } + + public int getServerPlayerCount () { + if (internal.getConnection() == null) + return 0; + + return internal.getConnection().getOnlinePlayers().size(); + } +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/BridgedOptions.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/BridgedOptions.java new file mode 100644 index 0000000..7063feb --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/BridgedOptions.java @@ -0,0 +1,15 @@ +package com.hypherionmc.craterlib.nojang.client; + +import lombok.RequiredArgsConstructor; +import net.minecraft.client.Options; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedOptions { + + private final Options internal; + + public String getLanguage() { + return internal.languageCode; + } + +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/gui/BridgedScreen.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/gui/BridgedScreen.java new file mode 100644 index 0000000..c4bcc23 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/gui/BridgedScreen.java @@ -0,0 +1,36 @@ +package com.hypherionmc.craterlib.nojang.client.gui; + +import lombok.RequiredArgsConstructor; +import net.minecraft.client.gui.screens.LevelLoadingScreen; +import net.minecraft.client.gui.screens.ReceivingLevelScreen; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.gui.screens.TitleScreen; +import net.minecraft.client.gui.screens.multiplayer.JoinMultiplayerScreen; +import net.minecraft.realms.RealmsScreen; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedScreen { + + private final Screen internal; + + public boolean isTitleScreen() { + return internal instanceof TitleScreen; + } + + public boolean isRealmsScreen() { + return internal instanceof RealmsScreen; + } + + public boolean isServerBrowserScreen() { + return internal instanceof JoinMultiplayerScreen; + } + + public boolean isLoadingScreen() { + return internal instanceof LevelLoadingScreen || internal instanceof ReceivingLevelScreen; + } + + public Screen toMojang() { + return internal; + } + +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/multiplayer/BridgedClientLevel.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/multiplayer/BridgedClientLevel.java new file mode 100644 index 0000000..4e7b60b --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/multiplayer/BridgedClientLevel.java @@ -0,0 +1,59 @@ +package com.hypherionmc.craterlib.nojang.client.multiplayer; + +import com.hypherionmc.craterlib.nojang.core.BridgedBlockPos; +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; +import com.hypherionmc.craterlib.utils.ChatUtils; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; +import net.minecraft.client.multiplayer.ClientLevel; +import org.jetbrains.annotations.Nullable; + +import java.util.concurrent.atomic.AtomicReference; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedClientLevel { + + private final ClientLevel internal; + + public boolean isClientSide() { + return internal.isClientSide(); + } + + public long getGameTime() { + return internal.getGameTime(); + } + + public long getDayTime() { + return internal.getDayTime(); + } + + public long dayTime() { + return internal.dayTime(); + } + + public boolean isRaining() { + return internal.isRaining(); + } + + public boolean isThundering() { + return internal.isThundering(); + } + + @Nullable + public ResourceIdentifier getDimensionKey() { + return ResourceIdentifier.fromMojang(internal.dimension().location()); + } + + @Nullable + public ResourceIdentifier getBiomeIdentifier(BridgedBlockPos onPos) { + AtomicReference identifier = new AtomicReference<>(null); + internal.getBiome(onPos.toMojang()).unwrap().ifLeft(b -> identifier.set(ResourceIdentifier.fromMojang(b.location()))); + return identifier.get(); + } + + @Nullable + public Component getDifficulty() { + return ChatUtils.mojangToAdventure(internal.getDifficulty().getDisplayName()); + } + +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/multiplayer/BridgedServerData.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/multiplayer/BridgedServerData.java new file mode 100644 index 0000000..710d2c6 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/multiplayer/BridgedServerData.java @@ -0,0 +1,45 @@ +package com.hypherionmc.craterlib.nojang.client.multiplayer; + +import com.hypherionmc.craterlib.utils.ChatUtils; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; +import net.minecraft.ChatFormatting; +import net.minecraft.client.multiplayer.ServerData; +import net.minecraft.client.multiplayer.ServerStatusPinger; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedServerData { + + private final ServerData internal; + + public String name() { + return internal.name; + } + + public String ip() { + return internal.ip; + } + + public Component motd() { + return ChatUtils.mojangToAdventure(internal.motd); + } + + public int getMaxPlayers() { + if (!internal.pinged || internal.status.getString() == null) { + try { + new ServerStatusPinger().pingServer(internal, () -> {}); + } catch (Exception ignored) {} + } + + try { + return Integer.parseInt(ChatFormatting.stripFormatting(internal.status.getString()).split("/")[1]); + } catch (Exception ignored) {} + + return 0; + } + + public ServerData toMojang() { + return internal; + } + +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/server/BridgedIntegratedServer.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/server/BridgedIntegratedServer.java new file mode 100644 index 0000000..0ecacaf --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/server/BridgedIntegratedServer.java @@ -0,0 +1,19 @@ +package com.hypherionmc.craterlib.nojang.client.server; + +import lombok.RequiredArgsConstructor; +import net.minecraft.client.server.IntegratedServer; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedIntegratedServer { + + private final IntegratedServer internal; + + public String getLevelName() { + return internal.getWorldData().getLevelName(); + } + + public IntegratedServer toMojang() { + return internal; + } + +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/BridgedCommandSourceStack.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/BridgedCommandSourceStack.java new file mode 100644 index 0000000..b14e484 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/BridgedCommandSourceStack.java @@ -0,0 +1,22 @@ +package com.hypherionmc.craterlib.nojang.commands; + +import com.hypherionmc.craterlib.utils.ChatUtils; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; +import net.minecraft.commands.CommandSourceStack; + +import java.util.function.Supplier; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedCommandSourceStack { + + private final CommandSourceStack internal; + + public void sendSuccess(Supplier supplier, boolean bl) { + internal.sendSuccess(ChatUtils.adventureToMojang(supplier.get()), bl); + } + + public CommandSourceStack toMojang() { + return internal; + } +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/BridgedFakePlayer.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/BridgedFakePlayer.java new file mode 100644 index 0000000..28b8ce9 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/BridgedFakePlayer.java @@ -0,0 +1,56 @@ +package com.hypherionmc.craterlib.nojang.commands; + +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import com.hypherionmc.craterlib.utils.ChatUtils; +import net.minecraft.commands.CommandSource; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.TextComponent; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.phys.Vec2; +import net.minecraft.world.phys.Vec3; + +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Supplier; + +public abstract class BridgedFakePlayer { + + final MojangBridge internal; + + public BridgedFakePlayer(BridgedMinecraftServer server, int perm, String name) { + internal = new MojangBridge(server.toMojang(), perm, name, this::onSuccess, this::onError); + } + + public abstract void onSuccess(Supplier supplier, Boolean aBoolean); + + public void onError(net.kyori.adventure.text.Component component) { + this.onSuccess(() -> component, false); + } + + public CommandSourceStack toMojang() { + return internal; + } + + static class MojangBridge extends CommandSourceStack { + + private final BiConsumer, Boolean> successCallback; + public final Consumer errorCallback; + + MojangBridge(MinecraftServer server, int perm, String name, BiConsumer, Boolean> successCallback, Consumer errorCallback) { + super(CommandSource.NULL, Vec3.ZERO, Vec2.ZERO, server.overworld(), perm, name, new TextComponent(name), server, null); + this.successCallback = successCallback; + this.errorCallback = errorCallback; + } + + @Override + public void sendSuccess(Component supplier, boolean bl) { + successCallback.accept(() -> ChatUtils.mojangToAdventure(supplier), bl); + } + + @Override + public void sendFailure(Component arg) { + errorCallback.accept(ChatUtils.mojangToAdventure(arg)); + } + } +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/CommandsRegistry.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/CommandsRegistry.java new file mode 100644 index 0000000..7e777f7 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/CommandsRegistry.java @@ -0,0 +1,85 @@ +package com.hypherionmc.craterlib.nojang.commands; + +import com.hypherionmc.craterlib.api.commands.CraterCommand; +import com.hypherionmc.craterlib.nojang.authlib.BridgedGameProfile; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.TriConsumer; +import com.mojang.authlib.GameProfile; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.commands.arguments.GameProfileArgument; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class CommandsRegistry { + + public static final CommandsRegistry INSTANCE = new CommandsRegistry(); + + private final List commands = new ArrayList<>(); + + public void registerCommand(CraterCommand cmd) { + commands.add(cmd); + } + + public void registerCommands(CommandDispatcher stack) { + commands.forEach(cmd -> { + if (cmd.hasArguments()) { + CommandWithArguments.register(cmd, stack); + } else { + CommandWithoutArguments.register(cmd, stack); + } + }); + } + + static class CommandWithoutArguments { + + public static void register(CraterCommand cmd, CommandDispatcher dispatcher) { + LiteralArgumentBuilder command = Commands.literal(cmd.getCommandName()) + .requires(source -> source.hasPermission(cmd.getPermissionLevel())) + .executes(context -> { + cmd.getExecutor().accept(BridgedCommandSourceStack.of(context.getSource())); + return 1; + }); + + dispatcher.register(command); + } + + } + + @SuppressWarnings("unchecked") + static class CommandWithArguments { + + public static void register(CraterCommand cmd, CommandDispatcher dispatcher) { + LiteralArgumentBuilder command = Commands.literal(cmd.getCommandName()) + .requires(source -> source.hasPermission(cmd.getPermissionLevel())); + + cmd.getArguments().forEach((key, pair) -> command.then(Commands.argument(key, pair.getLeft()).executes(context -> { + + // This is FUCKING UGLY.... Need to improve this in the future + if (pair.getLeft() instanceof GameProfileArgument) { + Collection profiles = GameProfileArgument.getGameProfiles(context, key); + List bridgedGameProfiles = new ArrayList<>(); + + profiles.forEach(p -> bridgedGameProfiles.add(BridgedGameProfile.of(p))); + + ((TriConsumer, BridgedCommandSourceStack>) pair.getRight()) + .accept(BridgedPlayer.of(context.getSource().getPlayerOrException()), bridgedGameProfiles, BridgedCommandSourceStack.of(context.getSource())); + return 1; + } + + return 1; + }))); + + dispatcher.register(command); + } + + } + +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/core/BridgedBlockPos.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/core/BridgedBlockPos.java new file mode 100644 index 0000000..bd49e2c --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/core/BridgedBlockPos.java @@ -0,0 +1,27 @@ +package com.hypherionmc.craterlib.nojang.core; + +import lombok.RequiredArgsConstructor; +import net.minecraft.core.BlockPos; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedBlockPos { + + private final BlockPos internal; + + public int getX() { + return internal.getX(); + } + + public int getY() { + return internal.getY(); + } + + public int getZ() { + return internal.getZ(); + } + + public BlockPos toMojang() { + return internal; + } + +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/nbt/BridgedCompoundTag.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/nbt/BridgedCompoundTag.java new file mode 100644 index 0000000..17a2d04 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/nbt/BridgedCompoundTag.java @@ -0,0 +1,49 @@ +package com.hypherionmc.craterlib.nojang.nbt; + +import lombok.RequiredArgsConstructor; +import net.minecraft.nbt.CompoundTag; + +import java.util.Set; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedCompoundTag { + + private final CompoundTag internal; + + public static BridgedCompoundTag empty() { + return new BridgedCompoundTag(new CompoundTag()); + } + + public BridgedCompoundTag getCompound(String key) { + return BridgedCompoundTag.of(internal.getCompound(key)); + } + + public Set getAllKeys() { + return internal.getAllKeys(); + } + + public String getString(String key) { + return internal.getString(key); + } + + public boolean getBoolean(String key) { + return internal.getBoolean(key); + } + + public void putString(String key, String value) { + internal.putString(key, value); + } + + public void put(String key, BridgedCompoundTag value) { + internal.put(key, value.toMojang()); + } + + public void putBoolean(String key, boolean value) { + internal.putBoolean(key, value); + } + + public CompoundTag toMojang() { + return internal; + } + +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/network/BridgedFriendlyByteBuf.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/network/BridgedFriendlyByteBuf.java new file mode 100644 index 0000000..a7bafd0 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/network/BridgedFriendlyByteBuf.java @@ -0,0 +1,34 @@ +package com.hypherionmc.craterlib.nojang.network; + +import com.hypherionmc.craterlib.nojang.nbt.BridgedCompoundTag; +import lombok.RequiredArgsConstructor; +import net.minecraft.network.FriendlyByteBuf; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedFriendlyByteBuf { + + private final FriendlyByteBuf internal; + + public BridgedCompoundTag readNbt() { + return BridgedCompoundTag.of(internal.readNbt()); + } + + public BridgedFriendlyByteBuf writeNbt(BridgedCompoundTag tag) { + internal.writeNbt(tag.toMojang()); + return BridgedFriendlyByteBuf.of(internal); + } + + public BridgedFriendlyByteBuf writeUtf(String value) { + internal.writeUtf(value); + return BridgedFriendlyByteBuf.of(internal); + } + + public String readUtf() { + return internal.readUtf(); + } + + public FriendlyByteBuf toMojang() { + return internal; + } + +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/package-info.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/package-info.java new file mode 100644 index 0000000..4c177a5 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/package-info.java @@ -0,0 +1,9 @@ +/** + * @author HypherionSA + * This package, called NoJang, exposes various wrapped API's. + * Using this api, a mod can essentially run on ANY minecraft version this library + * supports, from one code base. + * IMPORTANT NOTE: THESE API'S MUST NEVER EXPOSE ANY MINECRAFT CLASSES OR CODE!!!! + * THEY MUST ALWAYS BE HANDLED INTERNALLY AND ONLY RETURN WRAPPED VARIANTS + */ +package com.hypherionmc.craterlib.nojang; \ No newline at end of file diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/realmsclient/dto/BridgedRealmsServer.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/realmsclient/dto/BridgedRealmsServer.java new file mode 100644 index 0000000..dddf746 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/realmsclient/dto/BridgedRealmsServer.java @@ -0,0 +1,40 @@ +package com.hypherionmc.craterlib.nojang.realmsclient.dto; + +import com.mojang.realmsclient.dto.PlayerInfo; +import com.mojang.realmsclient.dto.RealmsServer; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedRealmsServer { + + private final RealmsServer internal; + + public String getName() { + return internal.getName(); + } + + public String getDescription() { + return internal.getDescription(); + } + + public String getWorldType() { + return internal.worldType.name(); + } + + public String getMinigameName() { + return internal.getMinigameName(); + } + + public String getMinigameImage() { + return internal.minigameImage; + } + + public long getPlayerCount() { + return internal.players.stream().filter(PlayerInfo::getOnline).count(); + } + + public RealmsServer toMojang() { + return internal; + } + +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/resources/ResourceIdentifier.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/resources/ResourceIdentifier.java new file mode 100644 index 0000000..9b848f4 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/resources/ResourceIdentifier.java @@ -0,0 +1,36 @@ +package com.hypherionmc.craterlib.nojang.resources; + +import net.minecraft.resources.ResourceLocation; + +public class ResourceIdentifier { + + private final ResourceLocation internal; + + public ResourceIdentifier(String namespace, String path) { + this.internal = new ResourceLocation(namespace, path); + } + + public ResourceIdentifier(String path) { + this.internal = new ResourceLocation(path); + } + + public String getNamespace() { + return internal.getNamespace(); + } + + public String getPath() { + return internal.getPath(); + } + + public String getString() { + return internal.toString(); + } + + public static ResourceIdentifier fromMojang(ResourceLocation location) { + return new ResourceIdentifier(location.getNamespace(), location.getPath()); + } + + public ResourceLocation toMojang() { + return internal; + } +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/server/BridgedMinecraftServer.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/server/BridgedMinecraftServer.java new file mode 100644 index 0000000..dd851aa --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/server/BridgedMinecraftServer.java @@ -0,0 +1,93 @@ +package com.hypherionmc.craterlib.nojang.server; + +import com.hypherionmc.craterlib.nojang.authlib.BridgedGameProfile; +import com.hypherionmc.craterlib.nojang.commands.BridgedFakePlayer; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.ChatUtils; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; +import net.minecraft.SharedConstants; +import net.minecraft.Util; +import net.minecraft.network.chat.ChatType; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.players.UserBanListEntry; +import net.minecraft.server.players.UserWhiteListEntry; + +import java.util.ArrayList; +import java.util.List; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedMinecraftServer { + + private final MinecraftServer internal; + + public boolean isUsingWhitelist() { + return internal.getPlayerList().isUsingWhitelist(); + } + + public int getPlayerCount() { + return internal.getPlayerList().getPlayerCount(); + } + + public int getMaxPlayers() { + return internal.getPlayerList().getMaxPlayers(); + } + + public String getServerModName() { + return internal.getServerModName(); + } + + public String getName() { + return SharedConstants.getCurrentVersion().getName(); + } + + public boolean usesAuthentication() { + return internal.usesAuthentication(); + } + + public void broadcastSystemMessage(Component text, boolean bl) { + internal.getPlayerList().broadcastMessage(ChatUtils.adventureToMojang(text), ChatType.SYSTEM, Util.NIL_UUID); + } + + public boolean isPlayerBanned(BridgedGameProfile profile) { + return internal.getPlayerList().getBans().isBanned(profile.toMojang()); + } + + public void whitelistPlayer(BridgedGameProfile gameProfile) { + if (!internal.getPlayerList().isUsingWhitelist()) + return; + + internal.getPlayerList().getWhiteList().add(new UserWhiteListEntry(gameProfile.toMojang())); + } + + public void unWhitelistPlayer(BridgedGameProfile gameProfile) { + if (!internal.getPlayerList().isUsingWhitelist()) + return; + + internal.getPlayerList().getWhiteList().remove(new UserWhiteListEntry(gameProfile.toMojang())); + } + + public List getPlayers() { + List profiles = new ArrayList<>(); + + if (internal.getPlayerList() == null) + return profiles; + + internal.getPlayerList().getPlayers().forEach(p -> profiles.add(BridgedPlayer.of(p))); + + return profiles; + } + + public void banPlayer(BridgedGameProfile profile) { + internal.getPlayerList().getBans().add(new UserBanListEntry(profile.toMojang())); + } + + public void executeCommand(BridgedMinecraftServer server, BridgedFakePlayer player, String command) { + internal.getCommands().performCommand(player.toMojang(), command); + } + + public MinecraftServer toMojang() { + return internal; + } + +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/world/entity/player/BridgedPlayer.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/world/entity/player/BridgedPlayer.java new file mode 100644 index 0000000..8aa6d49 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/world/entity/player/BridgedPlayer.java @@ -0,0 +1,63 @@ +package com.hypherionmc.craterlib.nojang.world.entity.player; + +import com.hypherionmc.craterlib.nojang.authlib.BridgedGameProfile; +import com.hypherionmc.craterlib.nojang.core.BridgedBlockPos; +import com.hypherionmc.craterlib.utils.ChatUtils; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraft.world.entity.player.Player; +import org.jetbrains.annotations.Nullable; + +import java.util.UUID; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedPlayer { + + private final Player internal; + + public Component getDisplayName() { + return ChatUtils.mojangToAdventure(internal.getDisplayName()); + } + + public Component getName() { + return ChatUtils.mojangToAdventure(internal.getName()); + } + + public UUID getUUID() { + return internal.getUUID(); + } + + public String getStringUUID() { + return internal.getStringUUID(); + } + + public BridgedGameProfile getGameProfile() { + return BridgedGameProfile.of(internal.getGameProfile()); + } + + public boolean isServerPlayer() { + return internal instanceof ServerPlayer; + } + + public Player toMojang() { + return internal; + } + + public BridgedBlockPos getOnPos() { + return BridgedBlockPos.of(internal.getOnPos()); + } + + @Nullable + public ServerGamePacketListenerImpl getConnection() { + if (isServerPlayer()) { + return ((ServerPlayer) internal).connection; + } + return null; + } + + public ServerPlayer toMojangServerPlayer() { + return (ServerPlayer) internal; + } +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java new file mode 100644 index 0000000..bba620c --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java @@ -0,0 +1,98 @@ +package com.hypherionmc.craterlib.utils; + +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; +import me.hypherionmc.mcdiscordformatter.discord.DiscordSerializer; +import me.hypherionmc.mcdiscordformatter.minecraft.MinecraftSerializer; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import net.kyori.adventure.text.serializer.json.JSONOptions; +import net.minecraft.ChatFormatting; +import net.minecraft.SharedConstants; +import net.minecraft.Util; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.Style; +import net.minecraft.network.chat.TextComponent; +import net.minecraft.network.chat.TranslatableComponent; + +public class ChatUtils { + + private static final GsonComponentSerializer adventureSerializer = GsonComponentSerializer.builder().options( + JSONOptions.byDataVersion().at(SharedConstants.getCurrentVersion().getDataVersion().getVersion()) + ).build(); + + public static Component adventureToMojang(net.kyori.adventure.text.Component inComponent) { + final String serialised = adventureSerializer.serialize(inComponent); + return Component.Serializer.fromJson(serialised); + } + + public static net.kyori.adventure.text.Component mojangToAdventure(Component inComponent) { + final String serialised = Component.Serializer.toJson(inComponent); + return adventureSerializer.deserialize(serialised); + } + + // Some text components contain duplicate text, resulting in duplicate messages + // sent back to discord. This should help fix those issues + public static Component safeCopy(Component inComponent) { + String value = inComponent.getString(); + Style style = inComponent.getStyle(); + return new TextComponent(value).withStyle(style); + } + + public static String strip(String inString, String... toStrip) { + String finalString = inString; + + for (String strip : toStrip) { + if (finalString.startsWith(strip)) + finalString = finalString.replaceFirst(strip, ""); + + if (finalString.startsWith(" ")) + finalString = finalString.replaceFirst(" ", ""); + } + + return finalString; + } + + public static String resolve(net.kyori.adventure.text.Component component, boolean formatted) { + Component c = adventureToMojang(component); + String returnVal = ChatFormatting.stripFormatting(c.getString()); + + if (formatted) { + returnVal = DiscordSerializer.INSTANCE.serialize(safeCopy(c).copy()); + } + + return returnVal; + } + + public static net.kyori.adventure.text.Component resolve(String component, boolean formatted) { + Component returnVal = new TextComponent(component); + if (formatted) { + returnVal = MinecraftSerializer.INSTANCE.serialize(component); + } + + return mojangToAdventure(returnVal); + } + + public static net.kyori.adventure.text.Component getTooltipTitle(String key) { + return net.kyori.adventure.text.Component.text(NamedTextColor.YELLOW + net.kyori.adventure.text.Component.translatable(key).key()); + } + + public static String resolveTranslation(String key) { + return net.kyori.adventure.text.Component.translatable(key).key(); + } + + public static net.kyori.adventure.text.Component getTranslation(String key) { + return net.kyori.adventure.text.Component.translatable(key); + } + + public static net.kyori.adventure.text.Component makeComponent(String text) { + return net.kyori.adventure.text.Component.translatable(text); + } + + public static net.kyori.adventure.text.Component getBiomeName(ResourceIdentifier identifier) { + if (identifier == null) + return net.kyori.adventure.text.Component.text("Unknown"); + + return mojangToAdventure(new TranslatableComponent(Util.makeDescriptionId("biome", identifier.toMojang()))); + } + +} diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/utils/InternalServiceUtil.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/utils/InternalServiceUtil.java new file mode 100644 index 0000000..2305d14 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/utils/InternalServiceUtil.java @@ -0,0 +1,27 @@ +package com.hypherionmc.craterlib.utils; + +import com.hypherionmc.craterlib.CraterConstants; + +import java.util.ServiceLoader; + +/** + * @author HypherionSA + * Utility class to handle SPI loading + */ +public class InternalServiceUtil { + + /** + * Try to load a service + * + * @param clazz The service class type to load + * @return The loaded class + */ + public static T load(Class clazz) { + final T loadedService = ServiceLoader.load(clazz) + .findFirst() + .orElseThrow(() -> new NullPointerException("Failed to load service for " + clazz.getName())); + CraterConstants.LOG.debug("Loaded {} for service {}", loadedService, clazz); + return loadedService; + } + +} \ No newline at end of file diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/utils/OptifineUtils.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/utils/OptifineUtils.java new file mode 100644 index 0000000..8a06da2 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/utils/OptifineUtils.java @@ -0,0 +1,46 @@ +package com.hypherionmc.craterlib.utils; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * @author HypherionSA + * Utility class for Optifine compatibility + */ +public class OptifineUtils { + + private static final boolean hasOptifine = checkOptifine(); + + private static boolean checkOptifine() { + try { + Class ofConfigClass = Class.forName("net.optifine.Config"); + return true; + } catch (ClassNotFoundException e) { + // Optifine is probably not present. Ignore the error + return false; + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } + + public static boolean isRenderRegions() { + try { + Class ofConfigClass = Class.forName("net.optifine.Config"); + Method rrField = ofConfigClass.getMethod("isRenderRegions"); + return (boolean) rrField.invoke(null); + } catch (ClassNotFoundException | InvocationTargetException | NoSuchMethodException | + IllegalAccessException e) { + // Optifine is probably not present. Ignore the error + return false; + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } + + public static boolean hasOptifine() { + return hasOptifine; + } + +} \ No newline at end of file diff --git a/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/utils/TriConsumer.java b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/utils/TriConsumer.java new file mode 100644 index 0000000..77f7309 --- /dev/null +++ b/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/utils/TriConsumer.java @@ -0,0 +1,5 @@ +package com.hypherionmc.craterlib.utils; + +public interface TriConsumer { + void accept(T t, U u, V v); +} diff --git a/1.18.2/Common/src/main/resources/assets/craterlib/lang/en_us.json b/1.18.2/Common/src/main/resources/assets/craterlib/lang/en_us.json new file mode 100644 index 0000000..03115d9 --- /dev/null +++ b/1.18.2/Common/src/main/resources/assets/craterlib/lang/en_us.json @@ -0,0 +1,8 @@ +{ + "t.clc.opensubconfig": "Open Config", + "t.clc.save": "Save", + "t.clc.cancel_discard": "Discard", + "t.clc.quit_config": "Unsaved Changes", + "t.clc.quit_config_sure": "You have unsaved config changes. Are you sure you want to discard them?", + "t.clc.quit_discard": "Quit & Discard" +} diff --git a/1.18.2/Common/src/main/resources/craterlib.mixins.json b/1.18.2/Common/src/main/resources/craterlib.mixins.json new file mode 100644 index 0000000..1a739db --- /dev/null +++ b/1.18.2/Common/src/main/resources/craterlib.mixins.json @@ -0,0 +1,24 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "com.hypherionmc.craterlib.mixin", + "compatibilityLevel": "JAVA_17", + "mixins": [ + ], + "client": [ + "ChatInputSuggestorMixin", + "events.PlayerMixin", + "events.client.ClientLevelMixin", + "events.client.MinecraftMixin", + "events.client.RealmsMainScreenMixin" + ], + "server": [ + "events.CommandMixin", + "events.PlayerAdvancementsMixin", + "events.PlayerListMixin", + "events.ServerPlayerMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/1.18.2/Common/src/main/resources/pack.mcmeta b/1.18.2/Common/src/main/resources/pack.mcmeta new file mode 100644 index 0000000..263d366 --- /dev/null +++ b/1.18.2/Common/src/main/resources/pack.mcmeta @@ -0,0 +1,6 @@ +{ + "pack": { + "description": "${mod_name}", + "pack_format": 18 + } +} diff --git a/1.18.2/Fabric/build.gradle b/1.18.2/Fabric/build.gradle new file mode 100644 index 0000000..0255dd0 --- /dev/null +++ b/1.18.2/Fabric/build.gradle @@ -0,0 +1,126 @@ +archivesBaseName = "${mod_name.replace(" ", "")}-Fabric-${minecraft_version}" + +dependencies { + // Core + modImplementation "net.fabricmc.fabric-api:fabric-api:${fabric_api}" + + // Compat + modImplementation("com.terraformersmc:modmenu:${mod_menu_version}") { + exclude(group: "net.fabricmc.fabric-api") + } + + modImplementation "maven.modrinth:fabrictailor:${fabrictailor}" + modImplementation "maven.modrinth:vanish:${vanish}" + + // Do not edit or remove + implementation project(":Common") +} + +shadowJar { + from sourceSets.main.output + configurations = [project.configurations.shade] + + dependencies { + exclude(dependency('com.google.code.gson:.*')) + + relocate 'me.hypherionmc.moonconfig', 'shadow.hypherionmc.moonconfig' + relocate 'me.hypherionmc.mcdiscordformatter', 'shadow.hypherionmc.mcdiscordformatter' + relocate 'net.kyori', 'shadow.kyori' + } + + setArchiveClassifier('dev-shadow') +} + +/** + * =============================================================================== + * = DO NOT EDIT BELOW THIS LINE UNLESS YOU KNOW WHAT YOU ARE DOING = + * =============================================================================== + */ + +unimined.minecraft { + fabric { + loader fabric_loader + } +} + +remapJar { + inputFile.set shadowJar.archiveFile + dependsOn shadowJar + archiveClassifier.set null +} + +jar { + archiveClassifier.set "dev" +} + +processResources { + from project(":Common").sourceSets.main.resources + def buildProps = project.properties.clone() + + filesMatching(['fabric.mod.json']) { + expand buildProps + } +} + +compileTestJava.enabled = false + +tasks.withType(JavaCompile).configureEach { + source(project(":Common").sourceSets.main.allSource) +} + +/** + * Publishing Config + */ +publishing { + publications { + mavenJava(MavenPublication) { + artifactId project.archivesBaseName + from components.java + + artifact(remapJar) { + builtBy remapJar + } + + pom.withXml { + Node pomNode = asNode() + pomNode.dependencies.'*'.findAll() { + it.artifactId.text() == 'regutils-joined-fabric' || + it.artifactId.text() == 'core' || + it.artifactId.text() == 'toml' + }.each() { + it.parent().remove(it) + } + } + } + } + + repositories { + maven rootProject.orion.getPublishingMaven() + } +} + +publisher { + apiKeys { + modrinth(System.getenv("MODRINTH_TOKEN")) + curseforge(System.getenv("CURSE_TOKEN")) + } + + setCurseID(curse_id) + setModrinthID(modrinth_id) + setVersionType("release") + setChangelog("https://raw.githubusercontent.com/hypherionmc/changelogs/main/craterlib/changelog-fabric.md") + setProjectVersion("${minecraft_version}-${project.version}") + setDisplayName("[FABRIC/QUILT 1.18.2] CraterLib - ${project.version}") + setGameVersions("1.18.2") + setLoaders("fabric", "quilt") + setArtifact(remapJar) + setCurseEnvironment("both") + + modrinthDepends { + required("fabric-api") + } + + curseDepends { + required("fabric-api") + } +} diff --git a/1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/CraterLibInitializer.java b/1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/CraterLibInitializer.java new file mode 100644 index 0000000..f951a1c --- /dev/null +++ b/1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/CraterLibInitializer.java @@ -0,0 +1,42 @@ +package com.hypherionmc.craterlib; + +import com.hypherionmc.craterlib.api.events.server.CraterRegisterCommandEvent; +import com.hypherionmc.craterlib.api.events.server.CraterServerLifecycleEvent; +import com.hypherionmc.craterlib.common.FabricCommonPlatform; +import com.hypherionmc.craterlib.compat.Vanish; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.core.networking.CraterPacketNetwork; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.core.platform.ModloaderEnvironment; +import com.hypherionmc.craterlib.network.CraterFabricNetworkHandler; +import com.hypherionmc.craterlib.nojang.commands.CommandsRegistry; +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; + +public class CraterLibInitializer implements ModInitializer { + + @Override + public void onInitialize() { + new CraterPacketNetwork(new CraterFabricNetworkHandler(PacketSide.SERVER)); + CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> { + CraterEventBus.INSTANCE.postEvent(new CraterRegisterCommandEvent()); + CommandsRegistry.INSTANCE.registerCommands(dispatcher); + }); + + + ServerLifecycleEvents.SERVER_STARTING.register(server -> { + FabricCommonPlatform.server = server; + CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Starting(BridgedMinecraftServer.of(server))); + }); + + ServerLifecycleEvents.SERVER_STARTED.register(li -> CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Started(BridgedMinecraftServer.of(li)))); + ServerLifecycleEvents.SERVER_STOPPING.register(server -> CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Stopping(BridgedMinecraftServer.of(server)))); + ServerLifecycleEvents.SERVER_STOPPED.register(server -> CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Stopped(BridgedMinecraftServer.of(server)))); + + if (ModloaderEnvironment.INSTANCE.isModLoaded("melius-vanish")) { + Vanish.register(); + } + } +} diff --git a/1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/CraterLibModMenuIntegration.java b/1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/CraterLibModMenuIntegration.java new file mode 100644 index 0000000..6615352 --- /dev/null +++ b/1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/CraterLibModMenuIntegration.java @@ -0,0 +1,30 @@ +package com.hypherionmc.craterlib; + +import com.hypherionmc.craterlib.client.gui.config.CraterConfigScreen; +import com.hypherionmc.craterlib.core.config.ConfigController; +import com.hypherionmc.craterlib.core.config.ModuleConfig; +import com.hypherionmc.craterlib.core.config.annotations.NoConfigScreen; +import com.terraformersmc.modmenu.api.ConfigScreenFactory; +import com.terraformersmc.modmenu.api.ModMenuApi; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author HypherionSA + */ +public class CraterLibModMenuIntegration implements ModMenuApi { + + @Override + public Map> getProvidedConfigScreenFactories() { + Map> configScreens = new HashMap<>(); + + ConfigController.getMonitoredConfigs().forEach((conf, watcher) -> { + if (!conf.getClass().isAnnotationPresent(NoConfigScreen.class)) { + configScreens.put(((ModuleConfig) conf).getModId(), screen -> new CraterConfigScreen((ModuleConfig) conf, screen)); + } + }); + + return configScreens; + } +} diff --git a/1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/client/CraterLibClientInitializer.java b/1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/client/CraterLibClientInitializer.java new file mode 100644 index 0000000..b5ec5b1 --- /dev/null +++ b/1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/client/CraterLibClientInitializer.java @@ -0,0 +1,27 @@ +package com.hypherionmc.craterlib.client; + +import com.hypherionmc.craterlib.api.events.client.CraterClientTickEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.core.networking.CraterPacketNetwork; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.network.CraterFabricNetworkHandler; +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedClientLevel; +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; + +public class CraterLibClientInitializer implements ClientModInitializer { + + @Override + public void onInitializeClient() { + new CraterPacketNetwork(new CraterFabricNetworkHandler(PacketSide.CLIENT)); + ClientTickEvents.START_CLIENT_TICK.register((listener) -> { + if (listener.level == null) + return; + + CraterClientTickEvent event = new CraterClientTickEvent(BridgedClientLevel.of(listener.level)); + CraterEventBus.INSTANCE.postEvent(event); + }); + + CraterEventBus.INSTANCE.registerEventListener(CraterLibClientInitializer.class); + } +} diff --git a/1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/client/FabricClientPlatform.java b/1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/client/FabricClientPlatform.java new file mode 100644 index 0000000..7852f12 --- /dev/null +++ b/1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/client/FabricClientPlatform.java @@ -0,0 +1,34 @@ +package com.hypherionmc.craterlib.client; + +import com.hypherionmc.craterlib.core.platform.ClientPlatform; +import com.hypherionmc.craterlib.nojang.client.BridgedMinecraft; +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedClientLevel; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraft.client.Minecraft; +import net.minecraft.network.Connection; + +/** + * @author HypherionSA + */ +public class FabricClientPlatform implements ClientPlatform { + + @Override + public BridgedMinecraft getClientInstance() { + return new BridgedMinecraft(); + } + + @Override + public BridgedPlayer getClientPlayer() { + return BridgedPlayer.of(Minecraft.getInstance().player); + } + + @Override + public BridgedClientLevel getClientLevel() { + return BridgedClientLevel.of(Minecraft.getInstance().level); + } + + @Override + public Connection getClientConnection() { + return Minecraft.getInstance().getConnection().getConnection(); + } +} diff --git a/1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricCommonPlatform.java b/1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricCommonPlatform.java new file mode 100644 index 0000000..c1a30c7 --- /dev/null +++ b/1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricCommonPlatform.java @@ -0,0 +1,18 @@ +package com.hypherionmc.craterlib.common; + +import com.hypherionmc.craterlib.core.platform.CommonPlatform; +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import net.minecraft.server.MinecraftServer; + +/** + * @author HypherionSA + */ +public class FabricCommonPlatform implements CommonPlatform { + + public static MinecraftServer server; + + @Override + public BridgedMinecraftServer getMCServer() { + return BridgedMinecraftServer.of(server); + } +} diff --git a/1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricCompatHelper.java b/1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricCompatHelper.java new file mode 100644 index 0000000..dbcaf63 --- /dev/null +++ b/1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricCompatHelper.java @@ -0,0 +1,23 @@ +package com.hypherionmc.craterlib.common; + +import com.hypherionmc.craterlib.compat.FabricTailor; +import com.hypherionmc.craterlib.compat.Vanish; +import com.hypherionmc.craterlib.core.platform.CompatUtils; +import com.hypherionmc.craterlib.core.platform.ModloaderEnvironment; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; + +public class FabricCompatHelper implements CompatUtils { + + @Override + public boolean isPlayerActive(BridgedPlayer player) { + if (!ModloaderEnvironment.INSTANCE.isModLoaded("melius-vanish")) + return true; + + return Vanish.isPlayerVanished(player.toMojangServerPlayer()); + } + + @Override + public String getSkinUUID(BridgedPlayer player) { + return FabricTailor.getTailorSkin(player.toMojangServerPlayer()); + } +} diff --git a/1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricLoaderHelper.java b/1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricLoaderHelper.java new file mode 100644 index 0000000..95f5c42 --- /dev/null +++ b/1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricLoaderHelper.java @@ -0,0 +1,69 @@ +package com.hypherionmc.craterlib.common; + +import com.hypherionmc.craterlib.core.platform.Environment; +import com.hypherionmc.craterlib.core.platform.ModloaderEnvironment; +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.SharedConstants; +import net.minecraft.client.Minecraft; + +import java.io.File; + +/** + * @author HypherionSA + * @date 07/08/2022 + */ +public class FabricLoaderHelper implements ModloaderEnvironment { + + @Override + public boolean isFabric() { + return true; + } + + @Override + public String getGameVersion() { + return SharedConstants.VERSION_STRING; + } + + @Override + public File getGameFolder() { + return Minecraft.getInstance().gameDirectory; + } + + @Override + public File getConfigFolder() { + return FabricLoader.getInstance().getConfigDir().toFile(); + } + + @Override + public File getModsFolder() { + return new File(FabricLoader.getInstance().getGameDir().toString() + File.separator + "mods"); + } + + @Override + public Environment getEnvironment() { + switch (FabricLoader.getInstance().getEnvironmentType()) { + case SERVER -> { + return Environment.SERVER; + } + case CLIENT -> { + return Environment.CLIENT; + } + } + return Environment.UNKNOWN; + } + + @Override + public boolean isModLoaded(String modid) { + return FabricLoader.getInstance().isModLoaded(modid); + } + + @Override + public boolean isDevEnv() { + return FabricLoader.getInstance().isDevelopmentEnvironment(); + } + + @Override + public int getModCount() { + return FabricLoader.getInstance().getAllMods().size(); + } +} diff --git a/1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/compat/FabricTailor.java b/1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/compat/FabricTailor.java new file mode 100644 index 0000000..b95a7d7 --- /dev/null +++ b/1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/compat/FabricTailor.java @@ -0,0 +1,23 @@ +package com.hypherionmc.craterlib.compat; + +import com.hypherionmc.craterlib.core.platform.ModloaderEnvironment; +import net.minecraft.server.level.ServerPlayer; +import org.samo_lego.fabrictailor.casts.TailoredPlayer; +public class FabricTailor { + + public static String getTailorSkin(ServerPlayer player) { + if (!ModloaderEnvironment.INSTANCE.isModLoaded("fabrictailor")) + return player.getStringUUID(); + + try { + if (player instanceof TailoredPlayer tp) { + return tp.getSkinId(); + } + } catch (Exception e) { + e.printStackTrace(); + } + + return player.getStringUUID(); + } + +} \ No newline at end of file diff --git a/1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java b/1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java new file mode 100644 index 0000000..cc6c1e0 --- /dev/null +++ b/1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java @@ -0,0 +1,25 @@ +package com.hypherionmc.craterlib.compat; + +import com.hypherionmc.craterlib.api.events.server.CraterPlayerEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import me.drex.vanish.api.VanishAPI; +import me.drex.vanish.api.VanishEvents; +import net.minecraft.server.level.ServerPlayer; + +public class Vanish { + + public static void register() { + VanishEvents.VANISH_EVENT.register((serverPlayer, b) -> { + if (b) { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerEvent.PlayerLoggedOut(BridgedPlayer.of(serverPlayer))); + } else { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerEvent.PlayerLoggedIn(BridgedPlayer.of(serverPlayer))); + } + }); + } + + public static boolean isPlayerVanished(ServerPlayer player) { + return VanishAPI.isVanished(player); + } +} diff --git a/1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java b/1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java new file mode 100644 index 0000000..31832af --- /dev/null +++ b/1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java @@ -0,0 +1,40 @@ +package com.hypherionmc.craterlib.mixin; + +import com.hypherionmc.craterlib.api.events.server.CraterServerChatEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.ChatUtils; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.TextComponent; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraft.server.network.TextFilter; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(value = ServerGamePacketListenerImpl.class, priority = Integer.MIN_VALUE) +public class ServerGamePacketListenerImplMixin { + + @Shadow + public ServerPlayer player; + + @Inject( + method = "handleChat(Lnet/minecraft/server/network/TextFilter$FilteredText;)V", + at = @At(value = "INVOKE", target = "Lnet/minecraft/server/network/TextFilter$FilteredText;getFiltered()Ljava/lang/String;"), + cancellable = true + ) + private void injectChatEvent(TextFilter.FilteredText arg, CallbackInfo ci) { + Component message = new TextComponent(arg.getRaw()); + if (message.getString().startsWith("/")) + return; + + CraterServerChatEvent event = new CraterServerChatEvent(BridgedPlayer.of(this.player), arg.getFiltered(), ChatUtils.mojangToAdventure(message)); + CraterEventBus.INSTANCE.postEvent(event); + if (event.wasCancelled()) + ci.cancel(); + } + +} diff --git a/1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/TutorialMixin.java b/1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/TutorialMixin.java new file mode 100644 index 0000000..f9eaa50 --- /dev/null +++ b/1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/TutorialMixin.java @@ -0,0 +1,24 @@ +package com.hypherionmc.craterlib.mixin; + +import com.hypherionmc.craterlib.api.events.client.LateInitEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.client.BridgedMinecraft; +import com.hypherionmc.craterlib.nojang.client.BridgedOptions; +import net.minecraft.client.Minecraft; +import net.minecraft.client.Options; +import net.minecraft.client.tutorial.Tutorial; +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(Tutorial.class) +public class TutorialMixin { + + @Inject(method = "", at = @At("RETURN")) + private void injectEarlyInitEvent(Minecraft minecraft, Options options, CallbackInfo ci) { + LateInitEvent event = new LateInitEvent(new BridgedMinecraft(), BridgedOptions.of(options)); + CraterEventBus.INSTANCE.postEvent(event); + } + +} diff --git a/1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java b/1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java new file mode 100644 index 0000000..b3014a3 --- /dev/null +++ b/1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java @@ -0,0 +1,81 @@ +package com.hypherionmc.craterlib.network; + +import com.hypherionmc.craterlib.CraterConstants; +import com.hypherionmc.craterlib.core.networking.PacketRegistry; +import com.hypherionmc.craterlib.core.networking.data.PacketContext; +import com.hypherionmc.craterlib.core.networking.data.PacketHolder; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.nojang.network.BridgedFriendlyByteBuf; +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.minecraft.network.FriendlyByteBuf; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public class CraterFabricNetworkHandler extends PacketRegistry { + + private final Map, Message> CHANNELS = new HashMap(); + + public CraterFabricNetworkHandler(PacketSide side) { + super(side); + } + + protected void registerPacket(PacketHolder holder) { + if (CHANNELS.get(holder.messageType()) == null) { + CHANNELS.put(holder.messageType(), new Message<>(holder.type(), holder.encoder())); + + if (PacketSide.CLIENT.equals(this.side)) { + ClientPlayNetworking.registerGlobalReceiver(holder.type().toMojang(), ((client, listener, buf, responseSender) -> { + buf.readByte(); + T message = holder.decoder().apply(BridgedFriendlyByteBuf.of(buf)); + client.execute(() -> holder.handler().accept(new PacketContext<>(message, PacketSide.CLIENT))); + })); + } else { + + ServerPlayNetworking.registerGlobalReceiver(holder.type().toMojang(), ((server, player, listener, buf, responseSender) -> { + buf.readByte(); + T message = holder.decoder().apply(BridgedFriendlyByteBuf.of(buf)); + server.execute(() -> holder.handler().accept(new PacketContext<>(BridgedPlayer.of(player), message, PacketSide.SERVER))); + })); + } + + } else { + CraterConstants.LOG.error("Trying to register duplicate packet for type {}", holder.messageType()); + } + } + + public void sendToServer(T packet) { + this.sendToServer(packet, false); + } + + public void sendToServer(T packet, boolean ignoreCheck) { + Message message = (Message) CHANNELS.get(packet.getClass()); + + if (ClientPlayNetworking.canSend(message.id().toMojang()) || ignoreCheck) { + FriendlyByteBuf buf = PacketByteBufs.create(); + buf.writeByte(0); + message.encoder().accept(packet, BridgedFriendlyByteBuf.of(buf)); + ClientPlayNetworking.send(message.id().toMojang(), buf); + } + } + + public void sendToClient(T packet, BridgedPlayer player) { + Message message = (Message) CHANNELS.get(packet.getClass()); + if (ServerPlayNetworking.canSend(player.toMojangServerPlayer(), message.id().toMojang())) { + FriendlyByteBuf buf = PacketByteBufs.create(); + buf.writeByte(0); + message.encoder().accept(packet, BridgedFriendlyByteBuf.of(buf)); + ServerPlayNetworking.send(player.toMojangServerPlayer(), message.id().toMojang(), buf); + } + } + + public record Message(ResourceIdentifier id, BiConsumer encoder) { } +} diff --git a/1.18.2/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform b/1.18.2/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform new file mode 100644 index 0000000..a78d9e5 --- /dev/null +++ b/1.18.2/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform @@ -0,0 +1 @@ +com.hypherionmc.craterlib.client.FabricClientPlatform diff --git a/1.18.2/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform b/1.18.2/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform new file mode 100644 index 0000000..9a2fdb0 --- /dev/null +++ b/1.18.2/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform @@ -0,0 +1 @@ +com.hypherionmc.craterlib.common.FabricCommonPlatform diff --git a/1.18.2/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils b/1.18.2/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils new file mode 100644 index 0000000..62f79a8 --- /dev/null +++ b/1.18.2/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils @@ -0,0 +1 @@ +com.hypherionmc.craterlib.common.FabricCompatHelper \ No newline at end of file diff --git a/1.18.2/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment b/1.18.2/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment new file mode 100644 index 0000000..9a1fb33 --- /dev/null +++ b/1.18.2/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment @@ -0,0 +1 @@ +com.hypherionmc.craterlib.common.FabricLoaderHelper diff --git a/1.18.2/Fabric/src/main/resources/assets/craterlib/craterlib_logo.png b/1.18.2/Fabric/src/main/resources/assets/craterlib/craterlib_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..ce0159cc4aa4d823ea354042e531b4522d6dfead GIT binary patch literal 49343 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 z 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 De~rmR literal 0 HcmV?d00001 diff --git a/1.18.2/Fabric/src/main/resources/craterlib.fabric.mixins.json b/1.18.2/Fabric/src/main/resources/craterlib.fabric.mixins.json new file mode 100644 index 0000000..7c59043 --- /dev/null +++ b/1.18.2/Fabric/src/main/resources/craterlib.fabric.mixins.json @@ -0,0 +1,17 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "com.hypherionmc.craterlib.mixin", + "compatibilityLevel": "JAVA_17", + "mixins": [ + ], + "client": [ + "TutorialMixin" + ], + "server": [ + "ServerGamePacketListenerImplMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/1.18.2/Fabric/src/main/resources/fabric.mod.json b/1.18.2/Fabric/src/main/resources/fabric.mod.json new file mode 100644 index 0000000..1e963e6 --- /dev/null +++ b/1.18.2/Fabric/src/main/resources/fabric.mod.json @@ -0,0 +1,39 @@ +{ + "schemaVersion": 1, + "id": "${mod_id}", + "version": "${version}", + "name": "${mod_name}", + "description": "A library mod used by First Dark Development and HypherionSA Mods", + "authors": [ + "${mod_author}", + "Misha" + ], + "contact": { + "homepage": "https://modrinth.com/mod/craterlib", + "sources": "https://github.com/firstdarkdev/craterLib/" + }, + "license": "MIT", + "icon": "assets/craterlib/craterlib_logo.png", + "environment": "*", + "entrypoints": { + "main": [ + "com.hypherionmc.craterlib.CraterLibInitializer" + ], + "client": [ + "com.hypherionmc.craterlib.client.CraterLibClientInitializer" + ], + "modmenu": [ + "com.hypherionmc.craterlib.CraterLibModMenuIntegration" + ] + }, + "mixins": [ + "${mod_id}.mixins.json", + "${mod_id}.fabric.mixins.json" + ], + "depends": { + "fabricloader": ">=0.15.0", + "fabric-api": "*", + "minecraft": "1.18.2", + "java": ">=17" + } +} diff --git a/1.18.2/Forge/build.gradle b/1.18.2/Forge/build.gradle new file mode 100644 index 0000000..e877178 --- /dev/null +++ b/1.18.2/Forge/build.gradle @@ -0,0 +1,112 @@ +// Adjust the output jar name here +archivesBaseName = "${mod_name.replace(" ", "")}-Forge-${minecraft_version}" + +dependencies { + // Compat + modImplementation("maven.modrinth:vanishmod:${vanishmod}") + + // Do not edit or remove + implementation project(":Common") +} + +shadowJar { + from sourceSets.main.output + configurations = [project.configurations.shade] + + dependencies { + exclude(dependency('com.google.code.gson:.*')) + + relocate 'me.hypherionmc.moonconfig', 'shadow.hypherionmc.moonconfig' + relocate 'me.hypherionmc.mcdiscordformatter', 'shadow.hypherionmc.mcdiscordformatter' + relocate 'net.kyori', 'shadow.kyori' + } + + setArchiveClassifier('dev-shadow') +} + +/** + * =============================================================================== + * = DO NOT EDIT BELOW THIS LINE UNLESS YOU KNOW WHAT YOU ARE DOING = + * =============================================================================== + */ + +unimined.minecraft { + minecraftForge { + loader forge_version + mixinConfig("${mod_id}.mixins.json", "${mod_id}.forge.mixins.json") + } +} + +remapJar { + inputFile.set shadowJar.archiveFile + dependsOn shadowJar + archiveClassifier.set null +} + +jar { + archiveClassifier.set "dev" +} + +processResources { + from project(":Common").sourceSets.main.resources + def buildProps = project.properties.clone() + + filesMatching("META-INF/mods.toml") { + expand buildProps + } +} + +compileTestJava.enabled = false + +tasks.withType(JavaCompile).configureEach { + source(project(":Common").sourceSets.main.allSource) +} + +/** + * Publishing Config + */ +publishing { + publications { + mavenJava(MavenPublication) { + artifactId project.archivesBaseName + from components.java + + artifact(remapJar) { + builtBy remapJar + } + + pom.withXml { + Node pomNode = asNode() + pomNode.dependencies.'*'.findAll() { + it.artifactId.text() == 'regutils-joined-fabric' || + it.artifactId.text() == 'core' || + it.artifactId.text() == 'toml' + }.each() { + it.parent().remove(it) + } + } + } + } + + repositories { + maven rootProject.orion.getPublishingMaven() + } +} + +publisher { + apiKeys { + modrinth(System.getenv("MODRINTH_TOKEN")) + curseforge(System.getenv("CURSE_TOKEN")) + } + + setCurseID(curse_id) + setModrinthID(modrinth_id) + setVersionType("release") + setChangelog("https://raw.githubusercontent.com/hypherionmc/changelogs/main/craterlib/changelog-forge.md") + setProjectVersion("${minecraft_version}-${project.version}") + setDisplayName("[Forge 1.18.2] CraterLib - ${project.version}") + setGameVersions("1.18.2") + setLoaders("forge") + setArtifact(remapJar) + setCurseEnvironment("both") +} diff --git a/1.18.2/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java b/1.18.2/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java new file mode 100644 index 0000000..968da05 --- /dev/null +++ b/1.18.2/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java @@ -0,0 +1,41 @@ +package com.hypherionmc.craterlib; + +import com.hypherionmc.craterlib.api.events.client.LateInitEvent; +import com.hypherionmc.craterlib.common.ForgeServerEvents; +import com.hypherionmc.craterlib.compat.Vanish; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.core.networking.CraterPacketNetwork; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.core.platform.ModloaderEnvironment; +import com.hypherionmc.craterlib.network.CraterForgeNetworkHandler; +import com.hypherionmc.craterlib.nojang.client.BridgedMinecraft; +import com.hypherionmc.craterlib.nojang.client.BridgedOptions; +import net.minecraft.client.Minecraft; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.DistExecutor; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import net.minecraftforge.fml.loading.FMLLoader; + +@Mod(CraterConstants.MOD_ID) +public class CraterLib { + + public CraterLib() { + MinecraftForge.EVENT_BUS.register(new ForgeServerEvents()); + FMLJavaModLoadingContext.get().getModEventBus().addListener(this::commonSetup); + } + + public void commonSetup(FMLCommonSetupEvent evt) { + new CraterPacketNetwork(new CraterForgeNetworkHandler(FMLLoader.getDist().isClient() ? PacketSide.CLIENT : PacketSide.SERVER)); + DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> { + LateInitEvent event = new LateInitEvent(new BridgedMinecraft(), BridgedOptions.of(Minecraft.getInstance().options)); + CraterEventBus.INSTANCE.postEvent(event); + }); + + if (ModloaderEnvironment.INSTANCE.isModLoaded("vmod")) { + MinecraftForge.EVENT_BUS.register(new Vanish()); + } + } +} diff --git a/1.18.2/Forge/src/main/java/com/hypherionmc/craterlib/client/ForgeClientEvents.java b/1.18.2/Forge/src/main/java/com/hypherionmc/craterlib/client/ForgeClientEvents.java new file mode 100644 index 0000000..5037e88 --- /dev/null +++ b/1.18.2/Forge/src/main/java/com/hypherionmc/craterlib/client/ForgeClientEvents.java @@ -0,0 +1,25 @@ +package com.hypherionmc.craterlib.client; + +import com.hypherionmc.craterlib.CraterConstants; +import com.hypherionmc.craterlib.api.events.client.CraterClientTickEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedClientLevel; +import net.minecraft.client.Minecraft; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.event.TickEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; + +@Mod.EventBusSubscriber(modid = CraterConstants.MOD_ID, bus = Mod.EventBusSubscriber.Bus.FORGE, value = Dist.CLIENT) +public class ForgeClientEvents { + + @SubscribeEvent + public static void clientTick(TickEvent.WorldTickEvent event) { + if (Minecraft.getInstance().level == null) + return; + + CraterClientTickEvent craterClientTickEvent = new CraterClientTickEvent(BridgedClientLevel.of(Minecraft.getInstance().level)); + CraterEventBus.INSTANCE.postEvent(craterClientTickEvent); + } + +} diff --git a/1.18.2/Forge/src/main/java/com/hypherionmc/craterlib/client/ForgeClientHelper.java b/1.18.2/Forge/src/main/java/com/hypherionmc/craterlib/client/ForgeClientHelper.java new file mode 100644 index 0000000..6776622 --- /dev/null +++ b/1.18.2/Forge/src/main/java/com/hypherionmc/craterlib/client/ForgeClientHelper.java @@ -0,0 +1,41 @@ +package com.hypherionmc.craterlib.client; + +import com.hypherionmc.craterlib.core.platform.ClientPlatform; +import com.hypherionmc.craterlib.nojang.client.BridgedMinecraft; +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedClientLevel; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraft.client.Minecraft; +import net.minecraft.network.Connection; + +import java.util.Objects; + +/** + * @author HypherionSA + * @date 16/06/2022 + */ +public class ForgeClientHelper implements ClientPlatform { + + public ForgeClientHelper() { + } + + @Override + public BridgedMinecraft getClientInstance() { + return new BridgedMinecraft(); + } + + @Override + public BridgedPlayer getClientPlayer() { + return BridgedPlayer.of(Minecraft.getInstance().player); + } + + @Override + public BridgedClientLevel getClientLevel() { + return BridgedClientLevel.of(Minecraft.getInstance().level); + } + + @Override + public Connection getClientConnection() { + Objects.requireNonNull(Minecraft.getInstance().getConnection(), "Cannot send packets when not in game!"); + return Minecraft.getInstance().getConnection().getConnection(); + } +} diff --git a/1.18.2/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeCommonHelper.java b/1.18.2/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeCommonHelper.java new file mode 100644 index 0000000..281ddd7 --- /dev/null +++ b/1.18.2/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeCommonHelper.java @@ -0,0 +1,26 @@ +package com.hypherionmc.craterlib.common; + +import com.hypherionmc.craterlib.core.platform.CommonPlatform; +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.CreativeModeTab; +import net.minecraftforge.server.ServerLifecycleHooks; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author HypherionSA + */ +public class ForgeCommonHelper implements CommonPlatform { + + public static Map TABS = new HashMap<>(); + + public ForgeCommonHelper() { + } + + @Override + public BridgedMinecraftServer getMCServer() { + return BridgedMinecraftServer.of(ServerLifecycleHooks.getCurrentServer()); + } +} diff --git a/1.18.2/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeCompatHelper.java b/1.18.2/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeCompatHelper.java new file mode 100644 index 0000000..f325837 --- /dev/null +++ b/1.18.2/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeCompatHelper.java @@ -0,0 +1,17 @@ +package com.hypherionmc.craterlib.common; + +import com.hypherionmc.craterlib.core.platform.CompatUtils; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; + +public class ForgeCompatHelper implements CompatUtils { + + @Override + public boolean isPlayerActive(BridgedPlayer player) { + return true; + } + + @Override + public String getSkinUUID(BridgedPlayer player) { + return player.getStringUUID(); + } +} diff --git a/1.18.2/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeLoaderHelper.java b/1.18.2/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeLoaderHelper.java new file mode 100644 index 0000000..2be70fc --- /dev/null +++ b/1.18.2/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeLoaderHelper.java @@ -0,0 +1,73 @@ +package com.hypherionmc.craterlib.common; + +import com.hypherionmc.craterlib.core.platform.Environment; +import com.hypherionmc.craterlib.core.platform.ModloaderEnvironment; +import net.minecraft.SharedConstants; +import net.minecraft.client.Minecraft; +import net.minecraftforge.fml.ModList; +import net.minecraftforge.fml.loading.FMLLoader; +import net.minecraftforge.fml.loading.FMLPaths; + +import java.io.File; + +/** + * @author HypherionSA + */ +public class ForgeLoaderHelper implements ModloaderEnvironment { + + public ForgeLoaderHelper() { + } + + @Override + public boolean isFabric() { + return false; + } + + @Override + public String getGameVersion() { + return SharedConstants.VERSION_STRING; + } + + @Override + public File getGameFolder() { + return Minecraft.getInstance().gameDirectory; + } + + @Override + public File getConfigFolder() { + return FMLPaths.CONFIGDIR.get().toFile(); + } + + @Override + public File getModsFolder() { + return FMLPaths.MODSDIR.get().toFile(); + } + + @Override + public Environment getEnvironment() { + switch (FMLLoader.getDist()) { + case CLIENT -> { + return Environment.CLIENT; + } + case DEDICATED_SERVER -> { + return Environment.SERVER; + } + } + return Environment.UNKNOWN; + } + + @Override + public boolean isModLoaded(String modid) { + return ModList.get().isLoaded(modid); + } + + @Override + public boolean isDevEnv() { + return !FMLLoader.isProduction(); + } + + @Override + public int getModCount() { + return ModList.get().size(); + } +} diff --git a/1.18.2/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeServerEvents.java b/1.18.2/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeServerEvents.java new file mode 100644 index 0000000..f354ddc --- /dev/null +++ b/1.18.2/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeServerEvents.java @@ -0,0 +1,43 @@ +package com.hypherionmc.craterlib.common; + +import com.hypherionmc.craterlib.api.events.server.CraterRegisterCommandEvent; +import com.hypherionmc.craterlib.api.events.server.CraterServerLifecycleEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.commands.CommandsRegistry; +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import net.minecraftforge.event.RegisterCommandsEvent; +import net.minecraftforge.event.server.ServerStartedEvent; +import net.minecraftforge.event.server.ServerStartingEvent; +import net.minecraftforge.event.server.ServerStoppedEvent; +import net.minecraftforge.event.server.ServerStoppingEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; + +public class ForgeServerEvents { + + @SubscribeEvent + public void serverStarting(ServerStartingEvent event) { + CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Starting(BridgedMinecraftServer.of(event.getServer()))); + } + + @SubscribeEvent + public void serverStarted(ServerStartedEvent event) { + CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Started(BridgedMinecraftServer.of(event.getServer()))); + } + + @SubscribeEvent + public void serverStopping(ServerStoppingEvent event) { + CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Stopping(BridgedMinecraftServer.of(event.getServer()))); + } + + @SubscribeEvent + public void serverStopped(ServerStoppedEvent event) { + CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Stopped(BridgedMinecraftServer.of(event.getServer()))); + } + + @SubscribeEvent + public void onCommandRegister(RegisterCommandsEvent event) { + CraterEventBus.INSTANCE.postEvent(new CraterRegisterCommandEvent()); + CommandsRegistry.INSTANCE.registerCommands(event.getDispatcher()); + } + +} diff --git a/1.18.2/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java b/1.18.2/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java new file mode 100644 index 0000000..cbcd1d2 --- /dev/null +++ b/1.18.2/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java @@ -0,0 +1,27 @@ +package com.hypherionmc.craterlib.compat; + +import com.hypherionmc.craterlib.api.events.server.CraterPlayerEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraft.world.entity.player.Player; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import redstonedubstep.mods.vanishmod.api.PlayerVanishEvent; + +public class Vanish { + + public Vanish() { + + } + + @SubscribeEvent + public void vanishevent(PlayerVanishEvent event) { + if (event.getEntity() instanceof Player p) { + if (event.isVanished()) { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerEvent.PlayerLoggedOut(BridgedPlayer.of(p))); + } else { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerEvent.PlayerLoggedIn(BridgedPlayer.of(p))); + } + } + } + +} \ No newline at end of file diff --git a/1.18.2/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ConfigScreenHandlerMixin.java b/1.18.2/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ConfigScreenHandlerMixin.java new file mode 100644 index 0000000..c61993f --- /dev/null +++ b/1.18.2/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ConfigScreenHandlerMixin.java @@ -0,0 +1,43 @@ +package com.hypherionmc.craterlib.mixin; + +import com.hypherionmc.craterlib.client.gui.config.CraterConfigScreen; +import com.hypherionmc.craterlib.core.config.ConfigController; +import com.hypherionmc.craterlib.core.config.ModuleConfig; +import com.hypherionmc.craterlib.core.config.annotations.NoConfigScreen; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.Screen; +import net.minecraftforge.client.ConfigGuiHandler; +import net.minecraftforge.forgespi.language.IModInfo; +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.CallbackInfoReturnable; + +import java.util.Optional; +import java.util.function.BiFunction; + +/** + * @author HypherionSA + */ +@Mixin(ConfigGuiHandler.class) +public class ConfigScreenHandlerMixin { + + /** + * Inject Auto Generated config Screens into forge + * + */ + @Inject(at = @At("RETURN"), method = "getGuiFactoryFor", cancellable = true, remap = false) + private static void injectConfigScreen(IModInfo selectedMod, CallbackInfoReturnable>> cir) { + ConfigController.getMonitoredConfigs().forEach((conf, watcher) -> { + if (!conf.getClass().isAnnotationPresent(NoConfigScreen.class)) { + ModuleConfig config = (ModuleConfig) conf; + if (config.getModId().equals(selectedMod.getModId())) { + cir.setReturnValue( + Optional.of((minecraft, screen) -> new CraterConfigScreen(config, screen)) + ); + } + } + }); + } + +} diff --git a/1.18.2/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java b/1.18.2/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java new file mode 100644 index 0000000..31832af --- /dev/null +++ b/1.18.2/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java @@ -0,0 +1,40 @@ +package com.hypherionmc.craterlib.mixin; + +import com.hypherionmc.craterlib.api.events.server.CraterServerChatEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.ChatUtils; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.TextComponent; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraft.server.network.TextFilter; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(value = ServerGamePacketListenerImpl.class, priority = Integer.MIN_VALUE) +public class ServerGamePacketListenerImplMixin { + + @Shadow + public ServerPlayer player; + + @Inject( + method = "handleChat(Lnet/minecraft/server/network/TextFilter$FilteredText;)V", + at = @At(value = "INVOKE", target = "Lnet/minecraft/server/network/TextFilter$FilteredText;getFiltered()Ljava/lang/String;"), + cancellable = true + ) + private void injectChatEvent(TextFilter.FilteredText arg, CallbackInfo ci) { + Component message = new TextComponent(arg.getRaw()); + if (message.getString().startsWith("/")) + return; + + CraterServerChatEvent event = new CraterServerChatEvent(BridgedPlayer.of(this.player), arg.getFiltered(), ChatUtils.mojangToAdventure(message)); + CraterEventBus.INSTANCE.postEvent(event); + if (event.wasCancelled()) + ci.cancel(); + } + +} diff --git a/1.18.2/Forge/src/main/java/com/hypherionmc/craterlib/network/CraterForgeNetworkHandler.java b/1.18.2/Forge/src/main/java/com/hypherionmc/craterlib/network/CraterForgeNetworkHandler.java new file mode 100644 index 0000000..5b54d5d --- /dev/null +++ b/1.18.2/Forge/src/main/java/com/hypherionmc/craterlib/network/CraterForgeNetworkHandler.java @@ -0,0 +1,100 @@ +package com.hypherionmc.craterlib.network; + +import com.hypherionmc.craterlib.CraterConstants; +import com.hypherionmc.craterlib.core.networking.PacketRegistry; +import com.hypherionmc.craterlib.core.networking.data.PacketContext; +import com.hypherionmc.craterlib.core.networking.data.PacketHolder; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.nojang.network.BridgedFriendlyByteBuf; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraft.client.Minecraft; +import net.minecraft.network.Connection; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraftforge.network.NetworkDirection; +import net.minecraftforge.network.NetworkEvent; +import net.minecraftforge.network.NetworkRegistry; +import net.minecraftforge.network.simple.SimpleChannel; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public class CraterForgeNetworkHandler extends PacketRegistry { + private final Map, SimpleChannel> CHANNELS = new HashMap<>(); + + public CraterForgeNetworkHandler(PacketSide side) { + super(side); + } + + protected void registerPacket(PacketHolder holder) { + if (CHANNELS.get(holder.messageType()) == null) { + SimpleChannel channel = NetworkRegistry.ChannelBuilder + .named(holder.type().toMojang()) + .clientAcceptedVersions((a) -> true) + .serverAcceptedVersions((a) -> true) + .networkProtocolVersion(() -> "1") + .simpleChannel(); + + channel.registerMessage( + 0, + holder.messageType(), + mojangEncoder(holder.encoder()), + mojangDecoder(holder.decoder()), + buildHandler(holder.handler())); + + CHANNELS.put(holder.messageType(), channel); + } else { + CraterConstants.LOG.error("Trying to register duplicate packet for type {}", holder.messageType()); + } + } + + public void sendToServer(T packet) { + this.sendToServer(packet, false); + } + + public void sendToServer(T packet, boolean ignoreCheck) { + SimpleChannel channel = CHANNELS.get(packet.getClass()); + Connection connection = Minecraft.getInstance().getConnection().getConnection(); + if (channel.isRemotePresent(connection) || ignoreCheck) { + channel.sendToServer(packet); + } + } + + public void sendToClient(T packet, BridgedPlayer player) { + SimpleChannel channel = CHANNELS.get(packet.getClass()); + ServerGamePacketListenerImpl connection = player.getConnection(); + if (connection == null) + return; + + if (channel.isRemotePresent(connection.connection)) { + channel.sendTo(packet, player.getConnection().connection, NetworkDirection.PLAY_TO_CLIENT); + } + } + + private Function mojangDecoder(Function handler) { + return byteBuf -> handler.apply(BridgedFriendlyByteBuf.of(byteBuf)); + } + + private BiConsumer mojangEncoder(BiConsumer handler) { + return ((t, byteBuf) -> handler.accept(t, BridgedFriendlyByteBuf.of(byteBuf))); + } + + private BiConsumer> buildHandler(Consumer> handler) { + return (message, ctx) -> { + ctx.get().enqueueWork(() -> { + PacketSide side = ctx.get().getDirection().getReceptionSide().isServer() ? PacketSide.SERVER : PacketSide.CLIENT; + ServerPlayer player = ctx.get().getSender(); + handler.accept(new PacketContext<>(BridgedPlayer.of(player), message, side)); + }); + ctx.get().setPacketHandled(true); + }; + } +} diff --git a/1.18.2/Forge/src/main/resources/META-INF/mods.toml b/1.18.2/Forge/src/main/resources/META-INF/mods.toml new file mode 100644 index 0000000..a9dcec0 --- /dev/null +++ b/1.18.2/Forge/src/main/resources/META-INF/mods.toml @@ -0,0 +1,31 @@ +modLoader = "javafml" +loaderVersion = "[40,)" +license = "MIT" +issueTrackerURL = "https://github.com/firstdarkdev/craterLib/issues" + +[[mods]] + modId = "${mod_id}" + version = "${version}" + displayName = "${mod_name}" + displayURL = "https://modrinth.com/mod/craterlib" + logoFile = "craterlib_logo.png" + #credits="Thanks for this example mod goes to Java" + authors = "${mod_author}, Zenith" + description = ''' + A library mod used by First Dark Development and HypherionSA Mods + ''' + displayTest = "NONE" + +[[dependencies.${ mod_id }]] + modId = "forge" + mandatory = true + versionRange = "[40,)" + ordering = "NONE" + side = "BOTH" + +[[dependencies.${ mod_id }]] + modId = "minecraft" + mandatory = true + versionRange = "[1.18.2,1.19)" + ordering = "NONE" + side = "BOTH" diff --git a/1.18.2/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform b/1.18.2/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform new file mode 100644 index 0000000..a12ad8c --- /dev/null +++ b/1.18.2/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform @@ -0,0 +1 @@ +com.hypherionmc.craterlib.client.ForgeClientHelper \ No newline at end of file diff --git a/1.18.2/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform b/1.18.2/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform new file mode 100644 index 0000000..09e119f --- /dev/null +++ b/1.18.2/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform @@ -0,0 +1 @@ +com.hypherionmc.craterlib.common.ForgeCommonHelper \ No newline at end of file diff --git a/1.18.2/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils b/1.18.2/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils new file mode 100644 index 0000000..a9f823d --- /dev/null +++ b/1.18.2/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils @@ -0,0 +1 @@ +com.hypherionmc.craterlib.common.ForgeCompatHelper \ No newline at end of file diff --git a/1.18.2/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment b/1.18.2/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment new file mode 100644 index 0000000..02b4e07 --- /dev/null +++ b/1.18.2/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment @@ -0,0 +1 @@ +com.hypherionmc.craterlib.common.ForgeLoaderHelper \ No newline at end of file diff --git a/1.18.2/Forge/src/main/resources/craterlib.forge.mixins.json b/1.18.2/Forge/src/main/resources/craterlib.forge.mixins.json new file mode 100644 index 0000000..aa072d1 --- /dev/null +++ b/1.18.2/Forge/src/main/resources/craterlib.forge.mixins.json @@ -0,0 +1,17 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "com.hypherionmc.craterlib.mixin", + "compatibilityLevel": "JAVA_17", + "mixins": [ + ], + "client": [ + "ConfigScreenHandlerMixin" + ], + "server": [ + "ServerGamePacketListenerImplMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/1.18.2/Forge/src/main/resources/craterlib_logo.png b/1.18.2/Forge/src/main/resources/craterlib_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..ce0159cc4aa4d823ea354042e531b4522d6dfead GIT binary patch literal 49343 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 z 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 De~rmR literal 0 HcmV?d00001 diff --git a/1.18.2/LICENSE b/1.18.2/LICENSE new file mode 100644 index 0000000..f4a1e2d --- /dev/null +++ b/1.18.2/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) HypherionSA and Contributors 2024 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/1.18.2/NeoForge/src/main/resources/craterlib_logo.png b/1.18.2/NeoForge/src/main/resources/craterlib_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..ce0159cc4aa4d823ea354042e531b4522d6dfead GIT binary patch literal 49343 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 z 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 De~rmR literal 0 HcmV?d00001 diff --git a/1.18.2/README.md b/1.18.2/README.md new file mode 100644 index 0000000..49c0699 --- /dev/null +++ b/1.18.2/README.md @@ -0,0 +1,55 @@ +# CraterLib + +![badge-snapshot](https://maven.firstdarkdev.xyz/api/badge/latest/snapshots/me/hypherionmc/craterlib/CraterLib-common-1.20-pre6?color=40c14a&name=CraterLib-Snapshot) + +*** + +A Library mod and modding api for easier multi-version minecraft and mod loader development + +*** + +### Supported Minecraft Versions + +| Minecraft Version | Support Status | +|-------------------| -------------- | +| < 1.18.2 | ❌ | +| 1.18.2-1.20.2 | ✳️ | +| 1.20.4 | ✳️ | +| 1.21 | 🚧 | + +- ❌ - Not Supported; no bug fixes or new features. +- 🚧 - Work in Progress; not ready for release. +- ✳️ - Long Term Support; receives changes through backports only. +- ✅ - In Support; the active version, receiving all bugfixes and features directly. + +*** + +## Library Features + +* Universal Config System (TOML Based) +* Built in Helper Classes for Various minecraft features +* Built in Optifine-Compat utilities +* Various utilities for Blockstates, LANG, Math and Rendering +* Cross Mod-Loader Events - Based on [Acara](https://github.com/Keksuccino/acara) +* Cross Mod-Loader Config Screens (Based on [Cloth Config Lite](https://github.com/shedaniel/cloth-config-lite)) +* Automatic ModMenu and Forge Config screen registration +* Built in Cross Mod-Loader Network system +* Nojang Modding API + +*** + +## Setup Instructions + +There's a **wiki coming soon**, but for now, here's some basic instructions for building the project: + +1. `git clone` the project to a safe spot. +2. Install Java's JDK 17. Make sure you have the development version explicitly: + * Fedora: `sudo dnf install java-17-openjdk-devel` + * Ubuntu: `sudo apt install openjdk-17-jdk` + * macOS: `brew install openjdk@17` +3. Set it accordingly: + * Windows/macOS: Set the `JAVA_HOME` environment variable or use system settings + * Linux: `sudo update-alternatives --config java` +4. Navigate to the CraterLib folder, then run a `gradlew` file depending on your operating system: + * Windows: `.\gradlew.bat build` + * macOS/Linux/BSD: `chmod +x gradlew` and `./gradlew` diff --git a/1.18.2/build.gradle b/1.18.2/build.gradle new file mode 100644 index 0000000..90bdb38 --- /dev/null +++ b/1.18.2/build.gradle @@ -0,0 +1,110 @@ +plugins { + id 'java' + id 'com.github.johnrengelman.shadow' version '8.1.1' apply false + id "xyz.wagyourtail.unimined" version "1.2.4" apply false + id "com.hypherionmc.modutils.modpublisher" version "2.1.+" + id "com.hypherionmc.modutils.orion" version "1.0.+" + id 'maven-publish' +} + +orion.setup { + multiProject = true + enableMirrorMaven = true + enableReleasesMaven = true + + dopplerToken = System.getenv("DOPPLER_KEY") + + versioning { + var relType = project.properties["releaseType"] ?: "${release_type}" + identifier("${relType}") + } +} + +group = project_group + +subprojects { + apply plugin: "xyz.wagyourtail.unimined" + apply plugin: "java" + apply plugin: 'maven-publish' + apply plugin: 'com.github.johnrengelman.shadow' + apply plugin: 'com.hypherionmc.modutils.modpublisher' + + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + + group = rootProject.group + + repositories { + mavenCentral() + + maven { + name = "Modrinth" + url = "https://api.modrinth.com/maven" + content { + includeGroup "maven.modrinth" + } + } + } + + configurations { + shade + modCompileOnly + implementation.extendsFrom shade + compileOnly.extendsFrom modCompileOnly + } + + dependencies { + // All Projects + shade "me.hypherionmc.moon-config:core:${moon_config}" + shade "me.hypherionmc.moon-config:toml:${moon_config}" + shade "com.hypherionmc:rpcsdk:${rpc_sdk}" + shade "me.hypherionmc.sdlink:mcdiscordformatter-1.18.1:${discord_formatter}" + shade "net.kyori:adventure-api:${adventure}" + shade "net.kyori:adventure-text-serializer-gson:${adventure}" + + compileOnly("org.projectlombok:lombok:${lombok}") + annotationProcessor("org.projectlombok:lombok:${lombok}") + } + + jar { + manifest { + attributes([ + 'Specification-Title' : mod_name, + 'Specification-Vendor' : mod_author, + 'Specification-Version' : project.jar.archiveVersion, + 'Implementation-Title' : project.name, + 'Implementation-Version' : project.jar.archiveVersion, + 'Implementation-Vendor' : mod_author, + 'Implementation-Timestamp': new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"), + 'Timestamp' : System.currentTimeMillis(), + 'Built-On-Java' : "${System.getProperty('java.vm.version')} (${System.getProperty('java.vm.vendor')})", + 'Built-On-Minecraft' : minecraft_version + ]) + } + } + +/** + * =============================================================================== + * = DO NOT EDIT BELOW THIS LINE UNLESS YOU KNOW WHAT YOU ARE DOING = + * =============================================================================== + */ + unimined.minecraft(sourceSets.main, true) { + version minecraft_version + + mappings { + mojmap() + devNamespace "mojmap" + } + } + + tasks.withType(JavaCompile).configureEach { + it.options.encoding = 'UTF-8' + it.options.release = 17 + } + + tasks.withType(GenerateModuleMetadata).configureEach { + enabled = false + } +} + +// TODO MODULE JARS diff --git a/1.18.2/gradle.properties b/1.18.2/gradle.properties new file mode 100644 index 0000000..09390a0 --- /dev/null +++ b/1.18.2/gradle.properties @@ -0,0 +1,42 @@ +#Project +version_major=2 +version_minor=0 +version_patch=0 + +#Mod +mod_author=HypherionSA +mod_id=craterlib +mod_name=CraterLib + +# Shared +minecraft_version=1.18.2 +project_group=com.hypherionmc.craterlib + +# Fabric +fabric_loader=0.15.11 +fabric_api=0.76.0+1.18.2 + +# Forge +forge_version=40.2.0 + +# Dependencies +moon_config=1.0.9 +lombok=1.18.32 +adventure=4.16.0 +rpc_sdk=1.0 +discord_formatter=2.0.0 + +# Mod Dependencies +fabrictailor=1.9.0+1.18.2 +vanish=1.1.0 +mod_menu_version=3.2.5 +vanishmod=1.1.14 + +# Publishing +curse_id=867099 +modrinth_id=Nn8Wasaq +release_type=release + +# Gradle +org.gradle.jvmargs=-Xmx3G +org.gradle.daemon=false diff --git a/1.18.2/gradle/wrapper/gradle-wrapper.jar b/1.18.2/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..7454180f2ae8848c63b8b4dea2cb829da983f2fa GIT binary patch literal 59536 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 QuEJmfE|#`Dqx|px0L@K;Y5)KL literal 0 HcmV?d00001 diff --git a/1.18.2/gradle/wrapper/gradle-wrapper.properties b/1.18.2/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..48c0a02 --- /dev/null +++ b/1.18.2/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/1.18.2/gradlew b/1.18.2/gradlew new file mode 100644 index 0000000..b4f908a --- /dev/null +++ b/1.18.2/gradlew @@ -0,0 +1,183 @@ +#!/usr/bin/env bash + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MSYS* | MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +ARGV=("$@") +eval set -- $DEFAULT_JVM_OPTS + +IFS=$' +' read -rd '' -a JAVA_OPTS_ARR <<< "$(echo $JAVA_OPTS | xargs -n1)" +IFS=$' +' read -rd '' -a GRADLE_OPTS_ARR <<< "$(echo $GRADLE_OPTS | xargs -n1)" + +exec "$JAVACMD" "$@" "${JAVA_OPTS_ARR[@]}" "${GRADLE_OPTS_ARR[@]}" "-Dorg.gradle.appname=$APP_BASE_NAME" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "${ARGV[@]}" diff --git a/1.18.2/gradlew.bat b/1.18.2/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/1.18.2/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/1.18.2/settings.gradle b/1.18.2/settings.gradle new file mode 100644 index 0000000..81b001e --- /dev/null +++ b/1.18.2/settings.gradle @@ -0,0 +1,14 @@ +pluginManagement { + repositories { + gradlePluginPortal() + maven { + url "https://mcentral.firstdark.dev/releases" + } + maven { + url "https://maven.firstdark.dev/releases" + } + } +} + +rootProject.name = 'CraterLib-1.18.2' +include("Common", "Fabric", "Forge") diff --git a/1.19.2/.gitattributes b/1.19.2/.gitattributes new file mode 100644 index 0000000..20fc528 --- /dev/null +++ b/1.19.2/.gitattributes @@ -0,0 +1,15 @@ +* text eol=lf +*.bat text eol=crlf +*.patch text eol=lf +*.java text eol=lf +*.gradle text eol=crlf +*.png binary +*.gif binary +*.exe binary +*.dll binary +*.jar binary +*.lzma binary +*.zip binary +*.pyd binary +*.cfg text eol=lf +*.jks binary \ No newline at end of file diff --git a/1.19.2/.gitignore b/1.19.2/.gitignore new file mode 100644 index 0000000..966dad4 --- /dev/null +++ b/1.19.2/.gitignore @@ -0,0 +1,29 @@ +# eclipse +bin +*.launch +.settings +.metadata +.classpath +.project + +# idea +out +*.ipr +*.iws +*.iml +.idea + +# gradle +build +.gradle + +# other +eclipse +run + +artifacts +src/test/** + +workspace +upstream +rejects \ No newline at end of file diff --git a/1.19.2/.jenkins/Jenkinsfile.deploy b/1.19.2/.jenkins/Jenkinsfile.deploy new file mode 100644 index 0000000..625878e --- /dev/null +++ b/1.19.2/.jenkins/Jenkinsfile.deploy @@ -0,0 +1,55 @@ +def JDK = "17" + +pipeline { + agent { + docker { + image "registry.firstdark.dev/java${JDK}:latest" + alwaysPull true + args '-v gradle-cache:/home/gradle/.gradle' + } + } + + environment { + GRADLE_USER_HOME = '/home/gradle/.gradle' + } + + stages { + stage("Notify Discord") { + steps { + discordSend webhookURL: env.FDD_WH_ADMIN, + title: "Deploy Started: CraterLib 1.19.2 Deploy #${BUILD_NUMBER}", + link: env.BUILD_URL, + result: 'SUCCESS', + description: "Build: [${BUILD_NUMBER}](${env.BUILD_URL})" + } + } + stage("Prepare") { + steps { + sh "chmod +x ./gradlew" + sh "./gradlew clean" + } + } + stage("Publish to Modrinth/Curseforge") { + steps { + sh "./gradlew publishMod -Prelease=true" + } + } + stage("Publish to Maven") { + steps { + sh "./gradlew publish -Prelease=true" + } + } + } + post { + always { + sh "./gradlew --stop" + deleteDir() + + discordSend webhookURL: env.FDD_WH_ADMIN, + title: "CraterLib Port Deploy #${BUILD_NUMBER}", + link: env.BUILD_URL, + result: currentBuild.currentResult, + description: "Build: [${BUILD_NUMBER}](${env.BUILD_URL})\nStatus: ${currentBuild.currentResult}" + } + } +} diff --git a/1.19.2/.jenkins/Jenkinsfile.snapshot b/1.19.2/.jenkins/Jenkinsfile.snapshot new file mode 100644 index 0000000..a54c1ba --- /dev/null +++ b/1.19.2/.jenkins/Jenkinsfile.snapshot @@ -0,0 +1,65 @@ +def projectName = "CraterLib"; +def projectIcon = "https://cdn.modrinth.com/data/Nn8Wasaq/a172c634683a11a2e9ae593e56eba7885743bb44.png"; +def JDK = "17"; +def majorMc = "1.19.2"; +def modLoaders = "forge|fabric|quilt"; +def supportedMc = "1.19.2"; +def reltype = "snapshot"; + +pipeline { + agent { + docker { + image "registry.firstdark.dev/java${JDK}:latest" + alwaysPull true + args '-v gradle-cache:/home/gradle/.gradle' + } + } + + environment { + GRADLE_USER_HOME = '/home/gradle/.gradle' + } + + stages { + stage("Notify Discord") { + steps { + discordSend webhookURL: env.SSS_WEBHOOK, + title: "Deploy Started: ${projectName} ${majorMc} Deploy #${BUILD_NUMBER}", + link: env.BUILD_URL, + result: 'SUCCESS', + description: "Build: [${BUILD_NUMBER}](${env.BUILD_URL})" + } + } + + stage("Prepare") { + steps { + sh "chmod +x ./gradlew" + sh "./gradlew build -PreleaseType=port" + } + } + + stage("Publish to Maven") { + steps { + catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') { + sh "./gradlew publish -PreleaseType=${reltype}" + } + } + } + } + + post { + always { + sh "./gradlew --stop" + + fddsnapshotter apiKey: env.PLATFORM_KEY, + projectSlug: "craterlib", + projectName: "${projectName}", + projectIcon: "${projectIcon}", + modLoaders: "${modLoaders}", + minecraftVersions: "${supportedMc}", + failWebhook: env.SSS_WEBHOOK, + publishWebhooks: "${env.SSS_WEBHOOK}|${env.FDD_WH}" + + deleteDir() + } + } +} diff --git a/1.19.2/Common/build.gradle b/1.19.2/Common/build.gradle new file mode 100644 index 0000000..843d62a --- /dev/null +++ b/1.19.2/Common/build.gradle @@ -0,0 +1,68 @@ +archivesBaseName = "${mod_name.replace(" ", "")}-Common-${minecraft_version}" + +dependencies { + +} + +shadowJar { + from sourceSets.main.output + configurations = [project.configurations.shade] + + dependencies { + exclude(dependency('com.google.code.gson:.*')) + + relocate 'me.hypherionmc.moonconfig', 'shadow.hypherionmc.moonconfig' + relocate 'me.hypherionmc.mcdiscordformatter', 'shadow.hypherionmc.mcdiscordformatter' + relocate 'net.kyori', 'shadow.kyori' + } + setArchiveClassifier("dev") +} + +/** + * =============================================================================== + * = DO NOT EDIT BELOW THIS LINE UNLESS YOU KNOW WHAT YOU ARE DOING = + * =============================================================================== + */ + +unimined.minecraft { + fabric { + loader fabric_loader + } + + defaultRemapJar = false +} + +processResources { + def buildProps = project.properties.clone() + + filesMatching(['pack.mcmeta']) { + expand buildProps + } +} + +/** + * Publishing Config + */ +publishing { + publications { + mavenCommon(MavenPublication) { + artifactId project.archivesBaseName + from components.java + + pom.withXml { + Node pomNode = asNode() + pomNode.dependencies.'*'.findAll() { + it.artifactId.text() == 'regutils-joined-fabric' || + it.artifactId.text() == 'core' || + it.artifactId.text() == 'toml' + }.each() { + it.parent().remove(it) + } + } + } + } + + repositories { + maven rootProject.orion.getPublishingMaven() + } +} \ No newline at end of file diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/CraterConstants.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/CraterConstants.java new file mode 100644 index 0000000..93edffc --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/CraterConstants.java @@ -0,0 +1,10 @@ +package com.hypherionmc.craterlib; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CraterConstants { + public static final String MOD_ID = "craterlib"; + public static final String MOD_NAME = "CraterLib"; + public static final Logger LOG = LoggerFactory.getLogger(MOD_NAME); +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/commands/CraterCommand.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/commands/CraterCommand.java new file mode 100644 index 0000000..cc4969e --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/commands/CraterCommand.java @@ -0,0 +1,53 @@ +package com.hypherionmc.craterlib.api.commands; + +import com.hypherionmc.craterlib.nojang.authlib.BridgedGameProfile; +import com.hypherionmc.craterlib.nojang.commands.BridgedCommandSourceStack; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.TriConsumer; +import com.mojang.brigadier.arguments.ArgumentType; +import lombok.Getter; +import net.minecraft.commands.arguments.GameProfileArgument; +import org.apache.commons.lang3.tuple.Pair; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.function.Consumer; + +@Getter +public class CraterCommand { + + private final HashMap, TriConsumer>> arguments = new LinkedHashMap<>(); + private Consumer executor; + + private final String commandName; + private int permissionLevel = 4; + + CraterCommand(String commandName) { + this.commandName = commandName; + } + + public static CraterCommand literal(String commandName) { + return new CraterCommand(commandName); + } + + public CraterCommand requiresPermission(int perm) { + this.permissionLevel = perm; + return this; + } + + public CraterCommand withGameProfileArgument(String key, TriConsumer, BridgedCommandSourceStack> executor) { + arguments.put(key, Pair.of(GameProfileArgument.gameProfile(), executor)); + return this; + } + + public CraterCommand executes(Consumer ctx) { + executor = ctx; + return this; + } + + public boolean hasArguments() { + return !arguments.isEmpty(); + } + +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/CraterClientTickEvent.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/CraterClientTickEvent.java new file mode 100644 index 0000000..c68c596 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/CraterClientTickEvent.java @@ -0,0 +1,14 @@ +package com.hypherionmc.craterlib.api.events.client; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedClientLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public class CraterClientTickEvent extends CraterEvent { + + private final BridgedClientLevel level; + +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/CraterSinglePlayerEvent.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/CraterSinglePlayerEvent.java new file mode 100644 index 0000000..ab671f0 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/CraterSinglePlayerEvent.java @@ -0,0 +1,21 @@ +package com.hypherionmc.craterlib.api.events.client; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public class CraterSinglePlayerEvent extends CraterEvent { + + private final BridgedPlayer player; + + public static class PlayerLogin extends CraterSinglePlayerEvent { + + public PlayerLogin(BridgedPlayer player) { + super(player); + } + + } +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/LateInitEvent.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/LateInitEvent.java new file mode 100644 index 0000000..33c06fb --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/LateInitEvent.java @@ -0,0 +1,16 @@ +package com.hypherionmc.craterlib.api.events.client; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.client.BridgedMinecraft; +import com.hypherionmc.craterlib.nojang.client.BridgedOptions; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public class LateInitEvent extends CraterEvent { + + private final BridgedMinecraft minecraft; + private final BridgedOptions options; + +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/PlayerJoinRealmEvent.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/PlayerJoinRealmEvent.java new file mode 100644 index 0000000..1ef95de --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/PlayerJoinRealmEvent.java @@ -0,0 +1,14 @@ +package com.hypherionmc.craterlib.api.events.client; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.realmsclient.dto.BridgedRealmsServer; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public class PlayerJoinRealmEvent extends CraterEvent { + + private final BridgedRealmsServer server; + +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/ScreenEvent.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/ScreenEvent.java new file mode 100644 index 0000000..034ca4a --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/ScreenEvent.java @@ -0,0 +1,27 @@ +package com.hypherionmc.craterlib.api.events.client; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.client.gui.BridgedScreen; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; + +@Getter +@RequiredArgsConstructor +public class ScreenEvent extends CraterEvent { + + private final BridgedScreen screen; + + @Getter + public static class Opening extends ScreenEvent { + + private final BridgedScreen currentScreen; + @Setter private BridgedScreen newScreen; + + public Opening(BridgedScreen currentScreen, BridgedScreen newScreen) { + super(newScreen); + this.currentScreen = currentScreen; + this.newScreen = newScreen; + } + } +} \ No newline at end of file diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/common/CraterPlayerDeathEvent.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/common/CraterPlayerDeathEvent.java new file mode 100644 index 0000000..02cd63c --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/common/CraterPlayerDeathEvent.java @@ -0,0 +1,21 @@ +package com.hypherionmc.craterlib.api.events.common; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.ChatUtils; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; +import net.minecraft.world.damagesource.DamageSource; + +@RequiredArgsConstructor +@Getter +public class CraterPlayerDeathEvent extends CraterEvent { + + private final BridgedPlayer player; + private final DamageSource damageSource; + + public Component getDeathMessage() { + return ChatUtils.mojangToAdventure(damageSource.getLocalizedDeathMessage(player.toMojang())); + } +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterAdvancementEvent.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterAdvancementEvent.java new file mode 100644 index 0000000..5cd659d --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterAdvancementEvent.java @@ -0,0 +1,34 @@ +package com.hypherionmc.craterlib.api.events.server; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.advancements.BridgedAdvancement; +import com.hypherionmc.craterlib.nojang.advancements.BridgedDisplayInfo; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import lombok.Getter; +import net.kyori.adventure.text.Component; + +import java.util.Optional; + +@Getter +public class CraterAdvancementEvent extends CraterEvent { + + private final BridgedAdvancement advancement; + private final BridgedPlayer player; + private final Component title; + private final Component description; + + public CraterAdvancementEvent(BridgedPlayer player, BridgedAdvancement advancement) { + this.advancement = advancement; + this.player = player; + + Optional displayInfo = advancement.displayInfo(); + + if (displayInfo.isPresent()) { + this.title = displayInfo.get().displayName(); + this.description = displayInfo.get().description(); + } else { + this.title = Component.text("Unknown"); + this.description = Component.text("Unknown"); + } + } +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterCommandEvent.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterCommandEvent.java new file mode 100644 index 0000000..364cc22 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterCommandEvent.java @@ -0,0 +1,58 @@ +package com.hypherionmc.craterlib.api.events.server; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.ChatUtils; +import com.mojang.brigadier.ParseResults; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.context.StringRange; +import lombok.Getter; +import lombok.Setter; +import net.kyori.adventure.text.Component; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.arguments.ComponentArgument; +import net.minecraft.world.entity.player.Player; +import org.jetbrains.annotations.Nullable; + +@Getter +public class CraterCommandEvent extends CraterEvent { + + private final ParseResults parseResults; + @Setter private Throwable exception; + private final String command; + + private CraterCommandEvent(ParseResults parseResults, String command) { + this.parseResults = parseResults; + this.command = command; + } + + public static CraterCommandEvent of(ParseResults stack, String command) { + return new CraterCommandEvent(stack, command); + } + + public String getCommandString() { + return parseResults.getReader().getString(); + } + + @Nullable + public BridgedPlayer getPlayer() { + try { + Player p = parseResults.getContext().getLastChild().getSource().getPlayer(); + + if (p != null) + return BridgedPlayer.of(p); + } catch (Exception ignored) {} + + return null; + } + + public String getTarget() { + CommandContext context = parseResults.getContext().build(parseResults.getReader().getString()); + StringRange selector_range = parseResults.getContext().getArguments().get("targets").getRange(); + return context.getInput().substring(selector_range.getStart(), selector_range.getEnd()); + } + + public Component getMessage() { + return ChatUtils.mojangToAdventure(ComponentArgument.getComponent(parseResults.getContext().build(parseResults.getReader().getString()), "message")); + } +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterPlayerEvent.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterPlayerEvent.java new file mode 100644 index 0000000..7eb3701 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterPlayerEvent.java @@ -0,0 +1,30 @@ +package com.hypherionmc.craterlib.api.events.server; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public class CraterPlayerEvent extends CraterEvent { + + private final BridgedPlayer player; + + public static class PlayerLoggedIn extends CraterPlayerEvent { + + public PlayerLoggedIn(BridgedPlayer player) { + super(player); + } + + } + + public static class PlayerLoggedOut extends CraterPlayerEvent { + + public PlayerLoggedOut(BridgedPlayer player) { + super(player); + } + + } + +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterRegisterCommandEvent.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterRegisterCommandEvent.java new file mode 100644 index 0000000..269065a --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterRegisterCommandEvent.java @@ -0,0 +1,15 @@ +package com.hypherionmc.craterlib.api.events.server; + +import com.hypherionmc.craterlib.api.commands.CraterCommand; +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.commands.CommandsRegistry; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +public class CraterRegisterCommandEvent extends CraterEvent { + + public void registerCommand(CraterCommand cmd) { + CommandsRegistry.INSTANCE.registerCommand(cmd); + } + +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterServerChatEvent.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterServerChatEvent.java new file mode 100644 index 0000000..b25170a --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterServerChatEvent.java @@ -0,0 +1,25 @@ +package com.hypherionmc.craterlib.api.events.server; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.core.event.annot.Cancellable; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import lombok.Getter; +import lombok.Setter; +import net.kyori.adventure.text.Component; + +@Cancellable +@Getter +public class CraterServerChatEvent extends CraterEvent { + + public final String message, username; + public final BridgedPlayer player; + @Setter private Component component; + + public CraterServerChatEvent(BridgedPlayer player, String message, Component component) { + this.message = message; + this.player = player; + this.username = player.getGameProfile().getName(); + this.component = component; + } + +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterServerLifecycleEvent.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterServerLifecycleEvent.java new file mode 100644 index 0000000..3f87d04 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterServerLifecycleEvent.java @@ -0,0 +1,34 @@ +package com.hypherionmc.craterlib.api.events.server; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +public class CraterServerLifecycleEvent extends CraterEvent { + + @RequiredArgsConstructor + @Getter + public static class Starting extends CraterServerLifecycleEvent { + private final BridgedMinecraftServer server; + } + + @RequiredArgsConstructor + @Getter + public static class Started extends CraterServerLifecycleEvent { + private final BridgedMinecraftServer server; + } + + @RequiredArgsConstructor + @Getter + public static class Stopping extends CraterServerLifecycleEvent { + private final BridgedMinecraftServer server; + } + + @RequiredArgsConstructor + @Getter + public static class Stopped extends CraterServerLifecycleEvent { + private final BridgedMinecraftServer server; + } + +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/MessageBroadcastEvent.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/MessageBroadcastEvent.java new file mode 100644 index 0000000..6b11404 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/MessageBroadcastEvent.java @@ -0,0 +1,20 @@ +package com.hypherionmc.craterlib.api.events.server; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; + +import java.util.function.Function; + +@RequiredArgsConstructor +@Getter +public class MessageBroadcastEvent extends CraterEvent { + + private final Component component; + private final Function function; + private final boolean bl; + private final String threadName; + +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/PlayerPreLoginEvent.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/PlayerPreLoginEvent.java new file mode 100644 index 0000000..eaabffd --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/PlayerPreLoginEvent.java @@ -0,0 +1,20 @@ +package com.hypherionmc.craterlib.api.events.server; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.authlib.BridgedGameProfile; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import net.kyori.adventure.text.Component; + +import java.net.SocketAddress; + +@RequiredArgsConstructor +@Getter +public class PlayerPreLoginEvent extends CraterEvent { + + private final SocketAddress address; + private final BridgedGameProfile gameProfile; + @Setter private Component message; + +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/networking/CraterNetworkHandler.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/networking/CraterNetworkHandler.java new file mode 100644 index 0000000..6b57e96 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/api/networking/CraterNetworkHandler.java @@ -0,0 +1,28 @@ +package com.hypherionmc.craterlib.api.networking; + +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; + +import java.util.List; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public interface CraterNetworkHandler { + + void sendToServer(T packet); + + void sendToServer(T packet, boolean ignoreCheck); + + void sendToClient(T packet, BridgedPlayer player); + + default void sendToClients(T packet, List players) { + for (BridgedPlayer player : players) { + sendToClient(packet, player); + } + } + + default void sendToAllClients(T packet, BridgedMinecraftServer server) { + sendToClients(packet, server.getPlayers()); + } +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/CraterConfigScreen.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/CraterConfigScreen.java new file mode 100644 index 0000000..823f2f5 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/CraterConfigScreen.java @@ -0,0 +1,392 @@ +package com.hypherionmc.craterlib.client.gui.config; + +import com.hypherionmc.craterlib.CraterConstants; +import com.hypherionmc.craterlib.client.gui.config.widgets.*; +import com.hypherionmc.craterlib.core.config.ModuleConfig; +import com.hypherionmc.craterlib.core.config.annotations.HideFromScreen; +import com.hypherionmc.craterlib.core.config.annotations.SubConfig; +import com.hypherionmc.craterlib.core.config.annotations.Tooltip; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.*; +import com.mojang.math.Matrix4f; +import me.hypherionmc.moonconfig.core.conversion.SpecComment; +import net.minecraft.ChatFormatting; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.screens.ConfirmScreen; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Mth; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * @author HypherionSA + */ +public class CraterConfigScreen extends Screen { + public static final float SCROLLBAR_BOTTOM_COLOR = .5f; + public static final float SCROLLBAR_TOP_COLOR = .67f; + private static final int TOP = 26; + private static final int BOTTOM = 24; + private final Screen parent; + private final List> options = new ArrayList<>(); + private final ModuleConfig config; + public double scrollerAmount; + private boolean dragging; + + public CraterConfigScreen(ModuleConfig config, Screen parent, Object subConfig) { + super(Component.translatable("cl." + config.getClass().getSimpleName().toLowerCase() + ".title")); + this.parent = parent; + this.config = config; + if (subConfig != null) { + setupScreenFromConfig(subConfig, subConfig.getClass()); + } else { + setupScreenFromConfig(config, config.getClass()); + } + } + + public CraterConfigScreen(ModuleConfig config, Screen parent) { + this(config, parent, null); + } + + private static Component toText(Enum val) { + return Component.translatable(val.toString()); + } + + private static Component toText(Boolean bool) { + return Component.translatable(bool.toString()); + } + + private void setupScreenFromConfig(Object object, Class clazz) { + while (clazz != Object.class) { + for (Field field : clazz.getDeclaredFields()) { + final int fieldModifiers = field.getModifiers(); + if (object == null || Modifier.isStatic(fieldModifiers) || Modifier.isTransient(fieldModifiers)) { + continue; + } + + try { + if (!field.isAccessible()) { + field.setAccessible(true); + } + if (field.isAnnotationPresent(HideFromScreen.class)) + return; + Object val = field.get(object); + + /* Lang Stuff */ + String baseLangKey = "cl." + clazz.getSimpleName().toLowerCase() + "." + field.getName().toLowerCase(); + String[] tooltipLang = field.isAnnotationPresent(SpecComment.class) ? new String[]{field.getAnnotation(SpecComment.class).value()} : new String[0]; + if (field.isAnnotationPresent(Tooltip.class)) { + tooltipLang = field.getAnnotation(Tooltip.class).value(); + } + + add(Component.translatable(baseLangKey), + val, + () -> val, + (ret) -> { + try { + field.set(object, ret); + config.saveConfig(config); + } catch (IllegalAccessException e) { + CraterConstants.LOG.error("Failed to update value for field {} in config {}", field.getName(), config.getConfigName(), e); + } + }, + field.isAnnotationPresent(SubConfig.class), + tooltipLang + ); + } catch (IllegalAccessException e) { + CraterConstants.LOG.error("Failed to access value for field {} in config {}", field.getName(), config.getConfigName(), e); + } + } + clazz = clazz.getSuperclass(); + } + } + + public void add(Component text, T value, @Nullable Supplier defaultValue, Consumer savingConsumer, boolean isSubConfig, String... langKeys) { + Option option = (Option) createOption(value, isSubConfig); + option.text = text; + option.defaultValue = defaultValue; + option.savingConsumer = savingConsumer; + option.originalValue = value; + option.value = value; + option.setLangKeys(List.of(langKeys)); + options.add(option); + option.onAdd(); + } + + private Option createOption(T value, boolean isSubConfig) { + if (value instanceof Enum) { + Object[] objects = value.getClass().getEnumConstants(); + return new ToggleButton>((List) Arrays.asList(objects), CraterConfigScreen::toText); + } + if (value instanceof Boolean) { + return new ToggleButton<>(Arrays.asList(Boolean.TRUE, Boolean.FALSE), CraterConfigScreen::toText); + } + if (value instanceof String) { + return new TextConfigOption<>(Function.identity(), Function.identity()); + } + if (value instanceof Integer) { + return new TextConfigOption<>(Objects::toString, Integer::valueOf); + } + if (value instanceof Long) { + return new TextConfigOption<>(Objects::toString, Long::valueOf); + } + if (value instanceof Double) { + return new TextConfigOption<>(Objects::toString, Double::valueOf); + } + if (value instanceof Float) { + return new TextConfigOption<>(Objects::toString, Float::valueOf); + } + if (value instanceof BigInteger) { + return new TextConfigOption<>(Objects::toString, BigInteger::new); + } + if (value instanceof BigDecimal) { + return new TextConfigOption<>(Objects::toString, BigDecimal::new); + } + if (value instanceof ResourceLocation) { + return new TextConfigOption<>(Objects::toString, ResourceLocation::new); + } + if (isSubConfig) { + return new SubConfigWidget<>(config, this, value); + } + throw new IllegalArgumentException(String.valueOf(value)); + } + + @Override + protected void init() { + super.init(); + ((List) children()).addAll(options); + + int buttonWidths = Math.min(200, (width - 50 - 12) / 3); + addRenderableWidget(new InternalConfigButton(this, width / 2 - buttonWidths - 3, height - 22, buttonWidths, 20, Component.empty(), true)); + addRenderableWidget(new InternalConfigButton(this, width / 2 + 3, height - 22, buttonWidths, 20, Component.empty(), false)); + } + + @Override + public void render(@NotNull PoseStack matrices, int mouseX, int mouseY, float delta) { + overlayBackground(matrices, TOP, height - BOTTOM, 32); + + renderScrollBar(); + + matrices.pushPose(); + matrices.translate(0, 0, 500.0); + overlayBackground(matrices, 0, TOP, 64); + overlayBackground(matrices, height - BOTTOM, height, 64); + renderShadow(matrices); + drawCenteredString(matrices, font, getTitle(), width / 2, 9, 0xFFFFFF); + super.render(matrices, mouseX, mouseY, delta); + matrices.popPose(); + + int y = (int) (TOP + 4 - Math.round(scrollerAmount)); + for (Option option : options) { + int height1 = option.height(); + option.render(minecraft, font, 40, y, width - 80, height1, matrices, mouseX, mouseY, delta); + renderConfigTooltip(matrices, font, mouseX, mouseY, 40, y, font.width(option.text), height1, option.text.getString(), option.getLangKeys().toArray(new String[0])); + y += height1; + } + } + + private void renderScrollBar() { + int listHeight = height - BOTTOM - TOP; + int totalHeight = totalHeight(); + if (totalHeight > listHeight) { + int maxScroll = Math.max(0, totalHeight - listHeight); + int height = listHeight * listHeight / totalHeight; + height = Mth.clamp(height, 32, listHeight); + height = Math.max(10, height); + int minY = Math.min(Math.max((int) scrollerAmount * (listHeight - height) / maxScroll + TOP, TOP), this.height - BOTTOM - height); + + int scrollbarPositionMaxX = width; + int scrollbarPositionMinX = scrollbarPositionMaxX - 6; + + int maxY = this.height - BOTTOM; + //RenderSystem.disableTexture(); + Tesselator tesselator = Tesselator.getInstance(); + BufferBuilder buffer = tesselator.getBuilder(); + RenderSystem.setShader(GameRenderer::getPositionColorShader); + buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR); + + buffer.vertex(scrollbarPositionMinX, maxY, 0.0D).color(0, 0, 0, 255).endVertex(); + buffer.vertex(scrollbarPositionMaxX, maxY, 0.0D).color(0, 0, 0, 255).endVertex(); + buffer.vertex(scrollbarPositionMaxX, TOP, 0.0D).color(0, 0, 0, 255).endVertex(); + buffer.vertex(scrollbarPositionMinX, TOP, 0.0D).color(0, 0, 0, 255).endVertex(); + + buffer.vertex(scrollbarPositionMinX, minY + height, 0.0D).color(SCROLLBAR_BOTTOM_COLOR, SCROLLBAR_BOTTOM_COLOR, SCROLLBAR_BOTTOM_COLOR, 1).endVertex(); + buffer.vertex(scrollbarPositionMaxX, minY + height, 0.0D).color(SCROLLBAR_BOTTOM_COLOR, SCROLLBAR_BOTTOM_COLOR, SCROLLBAR_BOTTOM_COLOR, 1).endVertex(); + buffer.vertex(scrollbarPositionMaxX, minY, 0.0D).color(SCROLLBAR_BOTTOM_COLOR, SCROLLBAR_BOTTOM_COLOR, SCROLLBAR_BOTTOM_COLOR, 1).endVertex(); + buffer.vertex(scrollbarPositionMinX, minY, 0.0D).color(SCROLLBAR_BOTTOM_COLOR, SCROLLBAR_BOTTOM_COLOR, SCROLLBAR_BOTTOM_COLOR, 1).endVertex(); + buffer.vertex(scrollbarPositionMinX, (minY + height - 1), 0.0D).color(SCROLLBAR_TOP_COLOR, SCROLLBAR_TOP_COLOR, SCROLLBAR_TOP_COLOR, 1).endVertex(); + buffer.vertex((scrollbarPositionMaxX - 1), (minY + height - 1), 0.0D).color(SCROLLBAR_TOP_COLOR, SCROLLBAR_TOP_COLOR, SCROLLBAR_TOP_COLOR, 1).endVertex(); + buffer.vertex((scrollbarPositionMaxX - 1), minY, 0.0D).color(SCROLLBAR_TOP_COLOR, SCROLLBAR_TOP_COLOR, SCROLLBAR_TOP_COLOR, 1).endVertex(); + buffer.vertex(scrollbarPositionMinX, minY, 0.0D).color(SCROLLBAR_TOP_COLOR, SCROLLBAR_TOP_COLOR, SCROLLBAR_TOP_COLOR, 1).endVertex(); + tesselator.end(); + RenderSystem.disableBlend(); + //RenderSystem.enableTexture(); + } + } + + private void renderShadow(PoseStack matrices) { + Tesselator tesselator = Tesselator.getInstance(); + BufferBuilder buffer = tesselator.getBuilder(); + RenderSystem.enableBlend(); + RenderSystem.blendFuncSeparate(770, 771, 0, 1); + //RenderSystem.disableTexture(); + RenderSystem.setShader(GameRenderer::getPositionTexColorShader); + Matrix4f matrix = matrices.last().pose(); + buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX_COLOR); + buffer.vertex(matrix, 0, TOP + 4, 0.0F).uv(0, 1).color(0, 0, 0, 0).endVertex(); + buffer.vertex(matrix, width, TOP + 4, 0.0F).uv(1, 1).color(0, 0, 0, 0).endVertex(); + buffer.vertex(matrix, width, TOP, 0.0F).uv(1, 0).color(0, 0, 0, 185).endVertex(); + buffer.vertex(matrix, 0, TOP, 0.0F).uv(0, 0).color(0, 0, 0, 185).endVertex(); + buffer.vertex(matrix, 0, height - BOTTOM, 0.0F).uv(0, 1).color(0, 0, 0, 185).endVertex(); + buffer.vertex(matrix, width, height - BOTTOM, 0.0F).uv(1, 1).color(0, 0, 0, 185).endVertex(); + buffer.vertex(matrix, width, height - BOTTOM - 4, 0.0F).uv(1, 0).color(0, 0, 0, 0).endVertex(); + buffer.vertex(matrix, 0, height - BOTTOM - 4, 0.0F).uv(0, 0).color(0, 0, 0, 0).endVertex(); + tesselator.end(); + //RenderSystem.enableTexture(); + RenderSystem.disableBlend(); + } + + protected void overlayBackground(PoseStack matrices, int h1, int h2, int color) { + overlayBackground(matrices.last().pose(), 0, h1, width, h2, color, color, color, 255, 255); + } + + protected void overlayBackground(Matrix4f matrix, int minX, int minY, int maxX, int maxY, int red, int green, int blue, int startAlpha, int endAlpha) { + Tesselator tesselator = Tesselator.getInstance(); + BufferBuilder buffer = tesselator.getBuilder(); + RenderSystem.setShaderTexture(0, Screen.BACKGROUND_LOCATION); + RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f); + RenderSystem.setShader(GameRenderer::getPositionTexColorShader); + buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX_COLOR); + buffer.vertex(matrix, minX, maxY, 0.0F).uv(minX / 32.0F, maxY / 32.0F).color(red, green, blue, endAlpha).endVertex(); + buffer.vertex(matrix, maxX, maxY, 0.0F).uv(maxX / 32.0F, maxY / 32.0F).color(red, green, blue, endAlpha).endVertex(); + buffer.vertex(matrix, maxX, minY, 0.0F).uv(maxX / 32.0F, minY / 32.0F).color(red, green, blue, startAlpha).endVertex(); + buffer.vertex(matrix, minX, minY, 0.0F).uv(minX / 32.0F, minY / 32.0F).color(red, green, blue, startAlpha).endVertex(); + tesselator.end(); + } + + public int scrollHeight() { + int totalHeight = totalHeight(); + int listHeight = height - BOTTOM - TOP; + if (totalHeight <= listHeight) { + return 0; + } + return totalHeight - listHeight; + } + + public int totalHeight() { + int i = 8; + for (Option option : options) { + i += option.height(); + } + return i; + } + + public boolean hasErrors() { + for (Option option : options) { + if (option.hasErrors) { + return true; + } + } + return false; + } + + public boolean isEdited() { + for (Option option : options) { + if (option.isEdited()) { + return true; + } + } + return false; + } + + public void save() { + for (Option option : options) { + option.save(); + option.originalValue = option.value; + } + } + + @Override + public void onClose() { + if (isEdited()) { + minecraft.setScreen(new ConfirmScreen(this::acceptConfirm, Component.translatable("t.clc.quit_config"), + Component.translatable("t.clc.quit_config_sure"), + Component.translatable("t.clc.quit_discard"), + Component.translatable("gui.cancel"))); + } else { + minecraft.setScreen(parent); + } + } + + @Override + public boolean mouseScrolled(double d, double e, double f) { + if (e >= TOP && e <= height - BOTTOM) { + scrollerAmount = Mth.clamp(scrollerAmount - f * 16.0D, 0, scrollHeight()); + return true; + } + return super.mouseScrolled(d, e, f); + } + + @Override + public boolean mouseClicked(double d, double e, int i) { + this.dragging = i == 0 && d >= width - 6 && d < width; + return super.mouseClicked(d, e, i) || dragging; + } + + @Override + public boolean mouseDragged(double d, double e, int i, double f, double g) { + if (super.mouseDragged(d, e, i, f, g)) { + return true; + } + if (i != 0 || !this.dragging) { + return false; + } + if (e < TOP) { + scrollerAmount = 0; + } else if (e > height - BOTTOM) { + scrollerAmount = scrollHeight(); + } else { + double h = Math.max(1, this.scrollHeight()); + int j = height - BOTTOM - TOP; + int k = Mth.clamp((int) ((float) (j * j) / (float) this.scrollHeight()), 32, j - 8); + double l = Math.max(1.0, h / (double) (j - k)); + scrollerAmount = Mth.clamp(scrollerAmount + g * l, 0, scrollHeight()); + } + return true; + } + + private void acceptConfirm(boolean t) { + if (!t) { + minecraft.setScreen(this); + } else { + minecraft.setScreen(parent); + } + } + + private void renderConfigTooltip(PoseStack stack, Font font, int mouseX, int mouseY, int startX, int startY, int sizeX, int sizeY, String title, String... description) { + if (mouseX > startX && mouseX < startX + sizeX) { + if (mouseY > startY && mouseY < startY + sizeY) { + List list = new ArrayList<>(); + list.add(Component.translatable(ChatFormatting.BOLD + "" + ChatFormatting.YELLOW + title)); + for (String desc : description) { + list.add(Component.translatable(desc)); + } + renderComponentTooltip(stack, list, mouseX, mouseY); + } + } + } +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/AbstractConfigWidget.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/AbstractConfigWidget.java new file mode 100644 index 0000000..408c515 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/AbstractConfigWidget.java @@ -0,0 +1,27 @@ +package com.hypherionmc.craterlib.client.gui.config.widgets; + +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.client.gui.components.EditBox; + +/** + * Copied from Cloth Config Lite + * ... + */ +public class AbstractConfigWidget extends BaseWidget { + + public static final int buttonWidth = 200; + public static final int buttonHeight = 20; + public W widget; + + @Override + public void render(Minecraft minecraft, Font font, int x, int y, int width, int height, PoseStack matrices, int mouseX, int mouseY, float delta) { + super.render(minecraft, font, x, y, width, height, matrices, mouseX, mouseY, delta); + int i = (widget instanceof EditBox ? 1 : 0); + widget.x = (x + width - 200 - resetButtonOffset + i); + widget.y = (y + i + 1); + widget.render(matrices, mouseX, mouseY, delta); + } +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/BaseWidget.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/BaseWidget.java new file mode 100644 index 0000000..07cf984 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/BaseWidget.java @@ -0,0 +1,61 @@ +package com.hypherionmc.craterlib.client.gui.config.widgets; + +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.ChatFormatting; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.components.Button; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.network.chat.TextColor; + +/** + * Copied from Cloth Config Lite + * ... + */ +public class BaseWidget extends Option { + + public static final int resetButtonOffset = 48; + private final Button resetButton = addChild(new Button(0, 0, 46, 20, Component.literal("Reset"), this::onResetPressed)); + private boolean hideReset = false; + + private boolean isSubConfig = false; + + private void onResetPressed(Button button) { + value = defaultValue.get(); + reset(); + } + + public void hideReset() { + this.hideReset = true; + } + + public boolean isSubConfig() { + return isSubConfig; + } + + public void setSubConfig(boolean subConfig) { + isSubConfig = subConfig; + } + + @Override + public void render(Minecraft minecraft, Font font, int x, int y, int width, int height, PoseStack matrices, int mouseX, int mouseY, float delta) { + MutableComponent text = Component.literal(this.text.getString()); + boolean edited = isEdited() || hasErrors; + if (edited) { + text.withStyle(ChatFormatting.ITALIC); + if (hasErrors) { + text.withStyle(style -> style.withColor(TextColor.fromRgb(16733525))); + } + } else { + text.withStyle(ChatFormatting.GRAY); + } + font.draw(matrices, text, x, y, 0xFFFFFF); + resetButton.x = (x + width - 46); + resetButton.y = (y + 1); + resetButton.active = isNotDefault(); + if (!hideReset) { + resetButton.render(matrices, mouseX, mouseY, delta); + } + } +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/InternalConfigButton.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/InternalConfigButton.java new file mode 100644 index 0000000..8d22fdf --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/InternalConfigButton.java @@ -0,0 +1,52 @@ +package com.hypherionmc.craterlib.client.gui.config.widgets; + +import com.hypherionmc.craterlib.client.gui.config.CraterConfigScreen; +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.gui.components.AbstractButton; +import net.minecraft.client.gui.narration.NarratedElementType; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.NotNull; + +/** + * @author HypherionSA + */ +public class InternalConfigButton extends AbstractButton { + + CraterConfigScreen screen; + boolean cancel; + + public InternalConfigButton(CraterConfigScreen screen, int i, int j, int k, int l, Component component, boolean cancel) { + super(i, j, k, l, component); + this.screen = screen; + this.cancel = cancel; + } + + @Override + public void render(@NotNull PoseStack poseStack, int i, int j, float f) { + if (cancel) { + setMessage(Component.translatable(screen.isEdited() ? "t.clc.cancel_discard" : "gui.cancel")); + } else { + boolean hasErrors = screen.hasErrors(); + active = screen.isEdited() && !hasErrors; + setMessage(Component.translatable(hasErrors ? "t.clc.error" : "t.clc.save")); + } + super.render(poseStack, i, j, f); + } + + @Override + public void updateNarration(NarrationElementOutput narrationElementOutput) { + narrationElementOutput.add(NarratedElementType.USAGE, getMessage()); + } + + @Override + public void onPress() { + if (cancel) { + screen.onClose(); + } else { + screen.save(); + } + } + + +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/Option.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/Option.java new file mode 100644 index 0000000..9573bd0 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/Option.java @@ -0,0 +1,71 @@ +package com.hypherionmc.craterlib.client.gui.config.widgets; + +import com.mojang.blaze3d.vertex.PoseStack; +import lombok.Getter; +import lombok.Setter; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.components.events.AbstractContainerEventHandler; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Supplier; + +/** + * Copied from Cloth Config Lite + * ... + */ +public abstract class Option extends AbstractContainerEventHandler { + + public Component text; + @Nullable + public Supplier defaultValue; + public Consumer savingConsumer; + public T originalValue; + public T value; + public boolean hasErrors; + public List children = new ArrayList<>(); + @Setter + @Getter + private List langKeys = new ArrayList<>(); + + public abstract void render(Minecraft minecraft, Font font, int x, int y, int width, int height, PoseStack matrices, int mouseX, int mouseY, float delta); + + public int height() { + return 22; + } + + @Override + public List children() { + return children; + } + + protected R addChild(R listener) { + ((List) children).add(listener); + return listener; + } + + public void onAdd() { + } + + protected void reset() { + onAdd(); + } + + public boolean isEdited() { + return !Objects.equals(originalValue, value); + } + + protected boolean isNotDefault() { + return defaultValue != null && !Objects.equals(defaultValue.get(), value); + } + + public void save() { + savingConsumer.accept(value); + } +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/SubConfigWidget.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/SubConfigWidget.java new file mode 100644 index 0000000..a4d229d --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/SubConfigWidget.java @@ -0,0 +1,40 @@ +package com.hypherionmc.craterlib.client.gui.config.widgets; + +import com.hypherionmc.craterlib.client.gui.config.CraterConfigScreen; +import com.hypherionmc.craterlib.core.config.ModuleConfig; +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; + +/** + * @author HypherionSA + */ +public class SubConfigWidget extends AbstractConfigWidget { + + private final Object subConfig; + private final ModuleConfig config; + private final Screen screen; + + public SubConfigWidget(ModuleConfig config, Screen screen, Object subConfig) { + this.config = config; + this.subConfig = subConfig; + this.screen = screen; + + this.widget = addChild(new Button(0, 0, 200, buttonHeight, Component.translatable("t.clc.opensubconfig"), this::openSubConfig)); + } + + @Override + public void render(Minecraft minecraft, Font font, int x, int y, int width, int height, PoseStack matrices, int mouseX, int mouseY, float delta) { + this.text = Component.literal(subConfig.getClass().getSimpleName().toLowerCase()); + this.hideReset(); + super.render(minecraft, font, x, y, width, height, matrices, mouseX, mouseY, delta); + } + + private void openSubConfig(Button button) { + Minecraft.getInstance().setScreen(new CraterConfigScreen(config, screen, subConfig)); + } + +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/TextConfigOption.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/TextConfigOption.java new file mode 100644 index 0000000..192da97 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/TextConfigOption.java @@ -0,0 +1,45 @@ +package com.hypherionmc.craterlib.client.gui.config.widgets; + +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; + +import java.util.function.Function; + +/** + * Copied from Cloth Config Lite + * ... + */ +public class TextConfigOption extends AbstractConfigWidget { + + private final Function toString; + private final Function fromString; + + public TextConfigOption(Function toString, Function fromString) { + this.toString = toString; + this.fromString = fromString; + this.widget = addChild(new WrappedEditBox(Minecraft.getInstance().font, 0, 0, 198, 18, null)); + } + + @Override + public void onAdd() { + widget.setMaxLength(1000000); + widget.setValue(toString.apply(value)); + widget.setResponder(this::update); + } + + @Override + public void render(Minecraft minecraft, Font font, int x, int y, int width, int height, PoseStack matrices, int mouseX, int mouseY, float delta) { + widget.setTextColor(hasErrors ? 16733525 : 14737632); + super.render(minecraft, font, x, y, width, height, matrices, mouseX, mouseY, delta); + } + + private void update(String s) { + try { + this.value = fromString.apply(s); + this.hasErrors = false; + } catch (Exception e) { + this.hasErrors = true; + } + } +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/ToggleButton.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/ToggleButton.java new file mode 100644 index 0000000..f7c1bdd --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/ToggleButton.java @@ -0,0 +1,33 @@ +package com.hypherionmc.craterlib.client.gui.config.widgets; + +import net.minecraft.client.gui.components.Button; +import net.minecraft.network.chat.Component; + +import java.util.List; +import java.util.function.Function; + +/** + * Copied from Cloth Config Lite + * ... + */ +public class ToggleButton extends AbstractConfigWidget { + + private final List options; + private final Function toComponent; + + public ToggleButton(List options, Function toComponent) { + this.options = options; + this.toComponent = toComponent; + this.widget = addChild(new Button(0, 0, buttonWidth, buttonHeight, Component.empty(), this::switchNext)); + } + + @Override + public void onAdd() { + widget.setMessage(toComponent.apply(value)); + } + + private void switchNext(Button button) { + value = options.get((options.indexOf(value) + 1) % options.size()); + onAdd(); + } +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/WrappedEditBox.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/WrappedEditBox.java new file mode 100644 index 0000000..42c0c0c --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/WrappedEditBox.java @@ -0,0 +1,29 @@ +package com.hypherionmc.craterlib.client.gui.config.widgets; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.components.EditBox; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.NotNull; + +/** + * @author HypherionSA + */ +public class WrappedEditBox extends EditBox { + + public WrappedEditBox(Font font, int i, int j, int k, int l, @NotNull Component component) { + super(font, i, j, k, l, component); + } + + @Override + public void setFocused(boolean bl) { + for (GuiEventListener child : Minecraft.getInstance().screen.children()) { + if (child instanceof TextConfigOption option) { + WrappedEditBox box = option.widget; + super.setFocused(box == this); + } + } + super.setFocused(bl); + } +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/client/mentions/MentionCondition.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/client/mentions/MentionCondition.java new file mode 100644 index 0000000..e8bffde --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/client/mentions/MentionCondition.java @@ -0,0 +1,13 @@ +package com.hypherionmc.craterlib.client.mentions; + +/** + * Based on ... + */ +@FunctionalInterface +public interface MentionCondition { + + boolean shouldAddMention(String currentWord); + + MentionCondition ALWAYS = currentWord -> true; + +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/client/mentions/MentionsController.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/client/mentions/MentionsController.java new file mode 100644 index 0000000..bad3b43 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/client/mentions/MentionsController.java @@ -0,0 +1,47 @@ +package com.hypherionmc.craterlib.client.mentions; + +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; +import lombok.Getter; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Based on ... + */ +public class MentionsController { + + private static final Map> mentions = new LinkedHashMap<>(); + private static final Map mentionConditions = new LinkedHashMap<>(); + @Getter + private static boolean lastMentionConditional = true; + + public static void registerMention(ResourceIdentifier mentionClass, Collection suggestions, MentionCondition condition) { + mentions.put(mentionClass, suggestions); + mentionConditions.put(mentionClass, condition); + } + + public static Collection getMentions(String currentWord) { + ArrayList applicableMentions = new ArrayList<>(); + lastMentionConditional = false; + + mentionConditions.forEach((mention, condition) -> { + boolean shouldSuggest = condition.shouldAddMention(currentWord); + if (!shouldSuggest) return; + + if (!lastMentionConditional && condition != MentionCondition.ALWAYS) { + lastMentionConditional = true; + } + + applicableMentions.addAll(mentions.get(mention)); + }); + + return applicableMentions; + } + + public static boolean hasMentions() { + return !mentions.isEmpty(); + } +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/ConfigController.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/ConfigController.java new file mode 100644 index 0000000..41c9471 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/ConfigController.java @@ -0,0 +1,49 @@ +package com.hypherionmc.craterlib.core.config; + +import com.hypherionmc.craterlib.CraterConstants; +import lombok.Getter; +import me.hypherionmc.moonconfig.core.file.FileWatcher; +import org.jetbrains.annotations.ApiStatus; + +import java.io.Serializable; +import java.util.HashMap; + +/** + * @author HypherionSA + * Controls Config File Reloads and Events + */ +public final class ConfigController implements Serializable { + + /** + * Cache of registered configs + */ + @Getter + private static final HashMap monitoredConfigs = new HashMap<>(); + + /** + * INTERNAL METHOD - Register and watch the config + * + * @param config - The config class to register and watch + */ + @ApiStatus.Internal + public static void register_config(ModuleConfig config) { + if (monitoredConfigs.containsKey(config)) { + CraterConstants.LOG.error("Failed to register " + config.getConfigPath().getName() + ". Config already registered"); + } else { + FileWatcher configWatcher = new FileWatcher(); + try { + configWatcher.setWatch(config.getConfigPath(), () -> { + if (!config.isSaveCalled()) { + CraterConstants.LOG.info("Sending Reload Event for: " + config.getConfigPath().getName()); + config.configReloaded(); + } + }); + } catch (Exception e) { + CraterConstants.LOG.error("Failed to register " + config.getConfigPath().getName() + " for auto reloading. " + e.getMessage()); + } + monitoredConfigs.put(config, configWatcher); + CraterConstants.LOG.info("Registered " + config.getConfigPath().getName() + " successfully!"); + } + } + +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/ModuleConfig.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/ModuleConfig.java new file mode 100644 index 0000000..181efdc --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/ModuleConfig.java @@ -0,0 +1,184 @@ +package com.hypherionmc.craterlib.core.config; + +import me.hypherionmc.moonconfig.core.CommentedConfig; +import me.hypherionmc.moonconfig.core.Config; +import me.hypherionmc.moonconfig.core.conversion.ObjectConverter; +import me.hypherionmc.moonconfig.core.file.CommentedFileConfig; + +import java.io.File; + +/** + * @author HypherionSA + * Base Config class containing the save, upgrading and loading logic. + * All config classes must extend this class + */ +public class ModuleConfig { + + /* Final Variables */ + private final transient File configPath; + private final transient String networkID; + + private final transient String configName; + + private final transient String modId; + + private transient boolean isSaveCalled = false; + + /** + * Set up the config + * + * @param modId - The ID of the Mod/Module the config belongs to + * @param configName - The name of the config file, excluding extension + */ + public ModuleConfig(String modId, String configName) { + this(modId, "", configName); + } + + public ModuleConfig(String modId, String subFolder, String configName) { + /* Preserve the order of the config values */ + Config.setInsertionOrderPreserved(true); + + /* Configure Paths and Network SYNC ID */ + File configDir = new File("config" + (subFolder.isEmpty() ? "" : File.separator + subFolder)); + configPath = new File(configDir + File.separator + configName + ".toml"); + networkID = modId + ":conf_" + configName.replace("-", "_"); + this.modId = modId; + this.configName = configName; + + /* Check if the required directories exists, otherwise we create them */ + if (!configDir.exists()) { + configDir.mkdirs(); + } + } + + /** + * This method has to be called in the config constructor. This creates or upgrades the config file as needed + * + * @param config - The config class to use + */ + public void registerAndSetup(ModuleConfig config) { + if (!configPath.exists() || configPath.length() < 2) { + saveConfig(config); + } else { + migrateConfig(config); + } + /* Register the Config for Watching and events */ + ConfigController.register_config(this); + this.configReloaded(); + } + + /** + * Save the config to the disk + * + * @param conf - The config class to serialize and save + */ + public void saveConfig(ModuleConfig conf) { + this.isSaveCalled = true; + /* Set up the Serializer and Config Object */ + ObjectConverter converter = new ObjectConverter(); + CommentedFileConfig config = CommentedFileConfig.builder(configPath).build(); + + /* Save the config and fire the reload events */ + converter.toConfig(conf, config); + config.save(); + configReloaded(); + this.isSaveCalled = false; + } + + /** + * Load the config from the file into a Class + * + * @param conf - The config Class to load + * @return - Returns the loaded version of the class + */ + public T loadConfig(Object conf) { + /* Set up the Serializer and Config Object */ + ObjectConverter converter = new ObjectConverter(); + CommentedFileConfig config = CommentedFileConfig.builder(configPath).build(); + config.load(); + + /* Load the config and return the loaded config */ + converter.toObject(config, conf); + return (T) conf; + } + + /** + * INTERNAL METHOD - Upgrades the config files in the events the config structure changes + * + * @param conf - The config class to load + */ + public void migrateConfig(ModuleConfig conf) { + /* Set up the Serializer and Config Objects */ + CommentedFileConfig config = CommentedFileConfig.builder(configPath).build(); + CommentedFileConfig newConfig = CommentedFileConfig.builder(configPath).build(); + config.load(); + + /* Upgrade the config */ + new ObjectConverter().toConfig(conf, newConfig); + updateConfigValues(config, newConfig, newConfig, ""); + newConfig.save(); + + config.close(); + newConfig.close(); + } + + public void updateConfigValues(CommentedConfig oldConfig, CommentedConfig newConfig, CommentedConfig outputConfig, String subKey) { + /* Loop over the config keys and check what has changed */ + newConfig.valueMap().forEach((key, value) -> { + String finalKey = subKey + (subKey.isEmpty() ? "" : ".") + key; + if (value instanceof CommentedConfig commentedConfig) { + updateConfigValues(oldConfig, commentedConfig, outputConfig, finalKey); + } else { + outputConfig.set(finalKey, + oldConfig.contains(finalKey) ? oldConfig.get(finalKey) : value); + } + }); + } + + /** + * Get the location of the config file + * + * @return - The FILE object containing the config file + */ + public File getConfigPath() { + return configPath; + } + + /** + * Get the NETWORK SYNC ID + * + * @return - Returns the Sync ID in format modid:config_name + */ + public String getNetworkID() { + return networkID; + } + + /** + * Fired whenever changes to the config are detected + */ + public void configReloaded() { + + } + + /** + * Get the name of the Config File + * + * @return + */ + public String getConfigName() { + return configName; + } + + /** + * Get the MODID of the Module the config is registered to + * + * @return + */ + public String getModId() { + return modId; + } + + public boolean isSaveCalled() { + return isSaveCalled; + } +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/HideFromScreen.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/HideFromScreen.java new file mode 100644 index 0000000..512a025 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/HideFromScreen.java @@ -0,0 +1,8 @@ +package com.hypherionmc.craterlib.core.config.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface HideFromScreen { +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/NoConfigScreen.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/NoConfigScreen.java new file mode 100644 index 0000000..4288ee0 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/NoConfigScreen.java @@ -0,0 +1,12 @@ +package com.hypherionmc.craterlib.core.config.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * @author HypherionSA + * Allows Modules to disable Automatic Config Screens + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface NoConfigScreen { +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/SubConfig.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/SubConfig.java new file mode 100644 index 0000000..e3ec808 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/SubConfig.java @@ -0,0 +1,16 @@ +package com.hypherionmc.craterlib.core.config.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author HypherionSA + * Used to determine if a Config section should be rendered as a separate screen + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface SubConfig { +} + diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/Syncable.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/Syncable.java new file mode 100644 index 0000000..838b35b --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/Syncable.java @@ -0,0 +1,15 @@ +package com.hypherionmc.craterlib.core.config.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author HypherionSA + * //TODO Currently unused, but to be used with Config Syncing in the future + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Syncable { +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/Tooltip.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/Tooltip.java new file mode 100644 index 0000000..538471c --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/Tooltip.java @@ -0,0 +1,16 @@ +package com.hypherionmc.craterlib.core.config.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author HypherionSA + * Provides tooltips to the config GUI + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Tooltip { + String[] value(); +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEvent.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEvent.java new file mode 100644 index 0000000..89ff00a --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEvent.java @@ -0,0 +1,30 @@ +package com.hypherionmc.craterlib.core.event; + +import com.hypherionmc.craterlib.core.event.annot.Cancellable; +import com.hypherionmc.craterlib.core.event.exception.CraterEventCancellationException; + +public class CraterEvent { + + private boolean canceled = false; + + private boolean canCancel() { + return this.getClass().isAnnotationPresent(Cancellable.class); + } + + public void cancelEvent() { + try { + if (!this.canCancel()) { + throw new CraterEventCancellationException("Tried to cancel non-cancelable event: " + this.getClass().getName()); + } + + this.canceled = true; + } catch (Exception e) { + e.printStackTrace(); + } + } + + public boolean wasCancelled() { + return this.canceled; + } + +} \ No newline at end of file diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEventBus.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEventBus.java new file mode 100644 index 0000000..74eeecc --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEventBus.java @@ -0,0 +1,241 @@ +package com.hypherionmc.craterlib.core.event; + +import com.hypherionmc.craterlib.CraterConstants; +import com.hypherionmc.craterlib.core.event.annot.CraterEventListener; +import org.slf4j.Logger; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.*; +import java.util.function.Consumer; + +public final class CraterEventBus { + + public static final CraterEventBus INSTANCE = new CraterEventBus(); + private static final Logger LOGGER = CraterConstants.LOG; + private final Map, List> events = new HashMap<>(); + + public void postEvent(CraterEvent event) { + if (eventsRegisteredForType(event.getClass())) { + List l = new ArrayList<>(events.get(event.getClass())); + l.sort((o1, o2) -> Integer.compare(o2.priority, o1.priority)); + + for (ListenerContainer c : l) { + c.notifyListener(event); + } + } + } + + public void registerEventListener(Class clazz) { + this.registerListenerMethods(this.getEventMethodsOf(clazz)); + } + + public void registerEventListener(Object object) { + this.registerListenerMethods(this.getEventMethodsOf(object)); + } + + private void registerListenerMethods(List methods) { + for (EventMethod m : methods) { + Consumer listener = (event) -> { + try { + m.method.invoke(m.parentObject, event); + } catch (Exception e) { + throw new RuntimeException(e); + } + }; + + ListenerContainer container = new ListenerContainer(m.eventType, listener, m.priority); + container.listenerParentClassName = m.parentClass.getName(); + container.listenerMethodName = m.method.getName(); + this.registerListener(container); + } + } + + private List getEventMethodsOf(Object objectOrClass) { + List l = new ArrayList<>(); + try { + if (objectOrClass != null) { + boolean isClass = (objectOrClass instanceof Class); + Class c = isClass ? (Class) objectOrClass : objectOrClass.getClass(); + for (Method m : c.getMethods()) { + if (isClass && Modifier.isStatic(m.getModifiers())) { + EventMethod em = EventMethod.tryCreateFrom(new AnalyzedMethod(m, c)); + if ((em != null) && this.hasEventAnnotation(em)) l.add(em); + } + if (!isClass && !Modifier.isStatic(m.getModifiers())) { + EventMethod em = EventMethod.tryCreateFrom(new AnalyzedMethod(m, objectOrClass)); + if ((em != null) && this.hasEventAnnotation(em)) l.add(em); + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return l; + } + + private boolean hasEventAnnotation(EventMethod m) { + for (Annotation a : m.annotations) { + if (a instanceof CraterEventListener) return true; + } + return false; + } + + public void registerListener(Consumer listener, Class eventType) { + this.registerListener(listener, eventType, 0); + } + + public void registerListener(Consumer listener, Class eventType, int priority) { + this.registerListener(new ListenerContainer(eventType, listener, priority)); + } + + private void registerListener(ListenerContainer listenerContainer) { + try { + if (!eventsRegisteredForType(listenerContainer.eventType)) { + events.put(listenerContainer.eventType, new ArrayList<>()); + } + events.get(listenerContainer.eventType).add(listenerContainer); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public boolean eventsRegisteredForType(Class eventType) { + if (eventType == null) { + return false; + } + return this.events.containsKey(eventType); + } + + protected final static class ListenerContainer { + + private final Consumer listener; + private final Class eventType; + private final int priority; + private String listenerParentClassName = "[unknown]"; + private String listenerMethodName = "[unknown]"; + + private ListenerContainer(Class eventType, Consumer listener, int priority) { + this.eventType = eventType; + this.listener = listener; + this.priority = priority; + } + + private void notifyListener(CraterEvent event) { + try { + this.listener.accept(event); + } catch (Exception e) { + LOGGER.error("##################################"); + LOGGER.error("Failed to notify event listener!"); + LOGGER.error("Event Type: " + this.eventType.getName()); + LOGGER.error("Listener Parent Class Name: " + this.listenerParentClassName); + LOGGER.error("Listener Method Name In Parent Class: " + this.listenerMethodName); + LOGGER.error("##################################"); + e.printStackTrace(); + } + } + } + + protected static class AnalyzedMethod { + + protected Method method; + protected Object parentObject; + protected Class parentClass; + protected boolean isStatic; + protected List annotations = new ArrayList<>(); + + protected AnalyzedMethod() { + } + + protected AnalyzedMethod(Method method, Object parentObjectOrClass) { + this.method = method; + this.parentObject = parentObjectOrClass; + this.parentClass = this.tryGetParentClass(); + this.isStatic = Modifier.isStatic(method.getModifiers()); + collectMethodAnnotations(this.isStatic ? null : this.parentObject.getClass(), this.method, this.annotations); + } + + protected static void collectMethodAnnotations(Class c, Method m, List addToList) { + try { + addToList.addAll(Arrays.asList(m.getAnnotations())); + if (!Modifier.isStatic(m.getModifiers()) && (c != null)) { + Class sc = c.getSuperclass(); + if (sc != null) { + try { + Method sm = sc.getMethod(m.getName(), m.getParameterTypes()); + collectMethodAnnotations(sc, sm, addToList); + } catch (Exception ignored) { + } + } + } + } catch (Exception ignored) { + } + } + + protected Class tryGetParentClass() { + if (this.parentObject instanceof Class) { + return (Class) this.parentObject; + } + return this.parentObject.getClass(); + } + + } + + protected static class EventMethod extends AnalyzedMethod { + + protected final int priority; + protected final Class eventType; + + protected EventMethod(AnalyzedMethod method) { + + super(); + this.method = method.method; + this.parentObject = method.parentObject; + this.parentClass = method.parentClass; + this.isStatic = method.isStatic; + this.annotations = method.annotations; + + this.priority = this.tryGetPriority(); + this.eventType = this.tryGetEventType(); + + } + + protected static EventMethod tryCreateFrom(AnalyzedMethod method) { + EventMethod em = new EventMethod(method); + return (em.eventType != null) ? em : null; + } + + protected Class tryGetEventType() { + try { + if (this.method != null) { + Class[] params = this.method.getParameterTypes(); + if (params.length > 0) { + Class firstParam = params[0]; + if (CraterEvent.class.isAssignableFrom(firstParam)) { + return (Class) firstParam; + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + protected int tryGetPriority() { + try { + for (Annotation a : this.annotations) { + if (a instanceof CraterEventListener craterEventListener) { + return craterEventListener.priority(); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return 0; + } + + } + +} \ No newline at end of file diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEventPriority.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEventPriority.java new file mode 100644 index 0000000..0c97134 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEventPriority.java @@ -0,0 +1,13 @@ +package com.hypherionmc.craterlib.core.event; + +public class CraterEventPriority { + + public static final int LOWEST = -3; + public static final int LOWER = -2; + public static final int LOW = -1; + public static final int NORMAL = 0; + public static final int HIGH = 1; + public static final int HIGHER = 2; + public static final int HIGHEST = 3; + +} \ No newline at end of file diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/annot/Cancellable.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/annot/Cancellable.java new file mode 100644 index 0000000..ee3c2fc --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/annot/Cancellable.java @@ -0,0 +1,8 @@ +package com.hypherionmc.craterlib.core.event.annot; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface Cancellable { +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/annot/CraterEventListener.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/annot/CraterEventListener.java new file mode 100644 index 0000000..7b42490 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/annot/CraterEventListener.java @@ -0,0 +1,11 @@ +package com.hypherionmc.craterlib.core.event.annot; + +import com.hypherionmc.craterlib.core.event.CraterEventPriority; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface CraterEventListener { + int priority() default CraterEventPriority.NORMAL; +} \ No newline at end of file diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/exception/CraterEventCancellationException.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/exception/CraterEventCancellationException.java new file mode 100644 index 0000000..fe2a8a3 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/exception/CraterEventCancellationException.java @@ -0,0 +1,9 @@ +package com.hypherionmc.craterlib.core.event.exception; + +public class CraterEventCancellationException extends Exception { + + public CraterEventCancellationException(String msg) { + super(msg); + } + +} \ No newline at end of file diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/package-info.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/package-info.java new file mode 100644 index 0000000..5ec1749 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/package-info.java @@ -0,0 +1,5 @@ +/** + * The event system code in this package is based on, and adapted from Acara (https://github.com/Keksuccino/acara/) + * and is licensed under MIT by Keksuccino + */ +package com.hypherionmc.craterlib.core.event; \ No newline at end of file diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/CraterPacketNetwork.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/CraterPacketNetwork.java new file mode 100644 index 0000000..081c8fb --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/CraterPacketNetwork.java @@ -0,0 +1,43 @@ +package com.hypherionmc.craterlib.core.networking; + +import com.hypherionmc.craterlib.core.networking.data.PacketContext; +import com.hypherionmc.craterlib.nojang.network.BridgedFriendlyByteBuf; +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; +import lombok.Getter; + +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +@Getter +public class CraterPacketNetwork { + + private final PacketRegistry packetRegistry; + public static CraterPacketNetwork INSTANCE; + private static DeferredPacketRegistrar delayedHandler; + + public CraterPacketNetwork(PacketRegistry registry) { + INSTANCE = this; + this.packetRegistry = registry; + getDelayedHandler().registerQueuedPackets(registry); + } + + private static DeferredPacketRegistrar getDelayedHandler() { + if (delayedHandler == null) { + delayedHandler = new DeferredPacketRegistrar(); + } + return delayedHandler; + } + + public static PacketRegistrar registerPacket(ResourceIdentifier id, Class messageType, BiConsumer encoder, Function decoder, Consumer> handler) { + if (INSTANCE != null) { + return INSTANCE.packetRegistry.registerPacket(id, messageType, encoder, decoder, handler); + } else { + return getDelayedHandler().registerPacket(id, messageType, encoder, decoder, handler); + } + } + +} \ No newline at end of file diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/DeferredPacketRegistrar.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/DeferredPacketRegistrar.java new file mode 100644 index 0000000..25556f4 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/DeferredPacketRegistrar.java @@ -0,0 +1,41 @@ +package com.hypherionmc.craterlib.core.networking; + +import com.hypherionmc.craterlib.core.networking.data.PacketContext; +import com.hypherionmc.craterlib.core.networking.data.PacketHolder; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.nojang.network.BridgedFriendlyByteBuf; +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public class DeferredPacketRegistrar implements PacketRegistrar { + + private static final Map, PacketHolder> QUEUED_PACKET_MAP = new HashMap<>(); + + @Override + public PacketSide side() { + return PacketSide.CLIENT; + } + + @Override + public PacketRegistrar registerPacket(ResourceIdentifier packetIdentifier, Class messageType, BiConsumer encoder, Function decoder, Consumer> handler) { + PacketHolder container = new PacketHolder<>(packetIdentifier, messageType, encoder, decoder, handler); + QUEUED_PACKET_MAP.put(messageType, container); + return this; + } + + + public void registerQueuedPackets(PacketRegistry packetRegistration) { + if (!QUEUED_PACKET_MAP.isEmpty()) { + packetRegistration.PACKET_MAP.putAll(QUEUED_PACKET_MAP); + QUEUED_PACKET_MAP.forEach((aClass, container) -> packetRegistration.registerPacket(container)); + } + } +} \ No newline at end of file diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/PacketRegistrar.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/PacketRegistrar.java new file mode 100644 index 0000000..3ea55fa --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/PacketRegistrar.java @@ -0,0 +1,21 @@ +package com.hypherionmc.craterlib.core.networking; + +import com.hypherionmc.craterlib.core.networking.data.PacketContext; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.nojang.network.BridgedFriendlyByteBuf; +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; + +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public interface PacketRegistrar { + + PacketSide side(); + + PacketRegistrar registerPacket(ResourceIdentifier id, Class messageType, BiConsumer encoder, Function decoder, Consumer> handler); + +} \ No newline at end of file diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/PacketRegistry.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/PacketRegistry.java new file mode 100644 index 0000000..d1ec06c --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/PacketRegistry.java @@ -0,0 +1,41 @@ +package com.hypherionmc.craterlib.core.networking; + +import com.hypherionmc.craterlib.api.networking.CraterNetworkHandler; +import com.hypherionmc.craterlib.core.networking.data.PacketContext; +import com.hypherionmc.craterlib.core.networking.data.PacketHolder; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.nojang.network.BridgedFriendlyByteBuf; +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public abstract class PacketRegistry implements CraterNetworkHandler, PacketRegistrar { + + protected final Map, PacketHolder> PACKET_MAP = new HashMap<>(); + + protected final PacketSide side; + + public PacketRegistry(PacketSide side) { + this.side = side; + } + + public PacketRegistrar registerPacket(ResourceIdentifier id, Class messageType, BiConsumer encoder, Function decoder, Consumer> handler) { + PacketHolder holder = new PacketHolder<>(id, messageType, encoder, decoder, handler); + PACKET_MAP.put(messageType, holder); + registerPacket(holder); + return this; + } + + public PacketSide side() { + return side; + } + + protected abstract void registerPacket(PacketHolder packetHolder); +} \ No newline at end of file diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketContext.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketContext.java new file mode 100644 index 0000000..fc432dc --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketContext.java @@ -0,0 +1,15 @@ +package com.hypherionmc.craterlib.core.networking.data; + +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import org.jetbrains.annotations.Nullable; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public record PacketContext(@Nullable BridgedPlayer sender, T message, PacketSide side) { + + public PacketContext(T message, PacketSide side) { + this(null, message, side); + } + +} \ No newline at end of file diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketHolder.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketHolder.java new file mode 100644 index 0000000..14b6947 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketHolder.java @@ -0,0 +1,18 @@ +package com.hypherionmc.craterlib.core.networking.data; + +import com.hypherionmc.craterlib.nojang.network.BridgedFriendlyByteBuf; +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; + +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public record PacketHolder(ResourceIdentifier type, + Class messageType, + BiConsumer encoder, + Function decoder, + Consumer> handler) { +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketSide.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketSide.java new file mode 100644 index 0000000..ad6f8ec --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketSide.java @@ -0,0 +1,13 @@ +package com.hypherionmc.craterlib.core.networking.data; + +public enum PacketSide { + CLIENT, + SERVER; + + public PacketSide flipped() { + if (CLIENT.equals(this)) + return SERVER; + + return CLIENT; + } +} \ No newline at end of file diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/ClientPlatform.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/ClientPlatform.java new file mode 100644 index 0000000..089fb36 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/ClientPlatform.java @@ -0,0 +1,23 @@ +package com.hypherionmc.craterlib.core.platform; + +import com.hypherionmc.craterlib.nojang.client.BridgedMinecraft; +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedClientLevel; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.InternalServiceUtil; +import net.minecraft.network.Connection; + +/** + * @author HypherionSA + */ +public interface ClientPlatform { + + public final ClientPlatform INSTANCE = InternalServiceUtil.load(ClientPlatform.class); + + BridgedMinecraft getClientInstance(); + + BridgedPlayer getClientPlayer(); + + BridgedClientLevel getClientLevel(); + + Connection getClientConnection(); +} \ No newline at end of file diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/CommonPlatform.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/CommonPlatform.java new file mode 100644 index 0000000..ee7f944 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/CommonPlatform.java @@ -0,0 +1,15 @@ +package com.hypherionmc.craterlib.core.platform; + +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import com.hypherionmc.craterlib.utils.InternalServiceUtil; + +/** + * @author HypherionSA + */ +public interface CommonPlatform { + + public CommonPlatform INSTANCE = InternalServiceUtil.load(CommonPlatform.class); + + BridgedMinecraftServer getMCServer(); + +} \ No newline at end of file diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/CompatUtils.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/CompatUtils.java new file mode 100644 index 0000000..fb745fc --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/CompatUtils.java @@ -0,0 +1,13 @@ +package com.hypherionmc.craterlib.core.platform; + +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.InternalServiceUtil; + +public interface CompatUtils { + + public static final CompatUtils INSTANCE = InternalServiceUtil.load(CompatUtils.class); + + boolean isPlayerActive(BridgedPlayer player); + String getSkinUUID(BridgedPlayer player); + +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/Environment.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/Environment.java new file mode 100644 index 0000000..2ef219e --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/Environment.java @@ -0,0 +1,18 @@ +package com.hypherionmc.craterlib.core.platform; + +/** + * @author HypherionSA + */ +public enum Environment { + CLIENT, + SERVER, + UNKNOWN; + + public boolean isClient() { + return this == CLIENT; + } + + public boolean isServer() { + return this == SERVER; + } +} \ No newline at end of file diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/ModloaderEnvironment.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/ModloaderEnvironment.java new file mode 100644 index 0000000..c1bc176 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/ModloaderEnvironment.java @@ -0,0 +1,32 @@ +package com.hypherionmc.craterlib.core.platform; + +import com.hypherionmc.craterlib.utils.InternalServiceUtil; + +import java.io.File; + +/** + * @author HypherionSA + * Helper class to provide information about the ModLoader + */ +public interface ModloaderEnvironment { + + public final ModloaderEnvironment INSTANCE = InternalServiceUtil.load(ModloaderEnvironment.class); + + boolean isFabric(); + + String getGameVersion(); + + File getGameFolder(); + + File getConfigFolder(); + + File getModsFolder(); + + Environment getEnvironment(); + + boolean isModLoaded(String modid); + + boolean isDevEnv(); + + int getModCount(); +} \ No newline at end of file diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordEventHandlers.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordEventHandlers.java new file mode 100644 index 0000000..3f1ffcb --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordEventHandlers.java @@ -0,0 +1,90 @@ +package com.hypherionmc.craterlib.core.rpcsdk; + +import com.hypherionmc.craterlib.core.rpcsdk.callbacks.*; +import com.sun.jna.Structure; + +import java.util.Arrays; +import java.util.List; + +/** + * @author HypherionSA + * Class containing references to all available discord event handles. + * Registering a handler is optional, and non-assigned handlers will be ignored + */ +public class DiscordEventHandlers extends Structure { + + // Callback for when the RPC was initialized successfully + public ReadyCallback ready; + + // Callback for when the Discord connection was ended + public DisconnectedCallback disconnected; + + // Callback for when a Discord Error occurs + public ErroredCallback errored; + + // Callback for when a player joins the game + public JoinGameCallback joinGame; + + // Callback for when a player spectates the game + public SpectateGameCallback spectateGame; + + // Callback for when a players request to join your game + public JoinRequestCallback joinRequest; + + /** + * DO NOT TOUCH THIS... EVER! + */ + @Override + protected List getFieldOrder() { + return Arrays.asList( + "ready", + "disconnected", + "errored", + "joinGame", + "spectateGame", + "joinRequest" + ); + } + + public static class Builder { + private final DiscordEventHandlers handlers; + + public Builder() { + this.handlers = new DiscordEventHandlers(); + } + + public Builder ready(ReadyCallback readyCallback) { + handlers.ready = readyCallback; + return this; + } + + public Builder disconnected(DisconnectedCallback disconnectedCallback) { + handlers.disconnected = disconnectedCallback; + return this; + } + + public Builder errored(ErroredCallback erroredCallback) { + handlers.errored = erroredCallback; + return this; + } + + public Builder joinGame(JoinGameCallback joinGameCallback) { + handlers.joinGame = joinGameCallback; + return this; + } + + public Builder spectateGame(SpectateGameCallback spectateGameCallback) { + handlers.spectateGame = spectateGameCallback; + return this; + } + + public Builder joinRequest(JoinRequestCallback joinRequestCallback) { + handlers.joinRequest = joinRequestCallback; + return this; + } + + public DiscordEventHandlers build() { + return handlers; + } + } +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordRPC.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordRPC.java new file mode 100644 index 0000000..7cf53ff --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordRPC.java @@ -0,0 +1,99 @@ +package com.hypherionmc.craterlib.core.rpcsdk; + +import com.sun.jna.Library; +import com.sun.jna.Native; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author HypherionSA + * Java Wrapper of the Discord-RPC Library + */ +public interface DiscordRPC extends Library { + + DiscordRPC INSTANCE = Native.load("discord-rpc", DiscordRPC.class); + + /** + * Open a New RPC Connection + * + * @param applicationId The ID of the Application the RPC is tied to + * @param handlers Optional Event Callback Handlers + * @param autoRegister Auto Register the running game + * @param steamId Steam ID of the game + */ + void Discord_Initialize(@NotNull String applicationId, @Nullable DiscordEventHandlers handlers, boolean autoRegister, @Nullable String steamId); + + /** + * Shutdown the RPC instance and disconnect from discord + */ + void Discord_Shutdown(); + + /** + * Need to be called manually at least every 2 seconds, to allow RPC updates + * and callback handlers to fire + */ + void Discord_RunCallbacks(); + + /** + * Not sure about this. Believe it needs to be called manually in some circumstances + */ + void Discord_UpdateConnection(); + + /** + * Update the Rich Presence + * + * @param struct Constructed {@link DiscordRichPresence} + */ + void Discord_UpdatePresence(@Nullable DiscordRichPresence struct); + + /** + * Clear the current Rich Presence + */ + void Discord_ClearPresence(); + + /** + * Respond to Join/Spectate callback + * + * @param userid The Discord User ID of the user that initiated the request + * @param reply Reply to the request. See {@link DiscordReply} + */ + void Discord_Respond(@NotNull String userid, int reply); + + /** + * Replace the already registered {@link DiscordEventHandlers} + * + * @param handlers The new handlers to apply + */ + void Discord_UpdateHandlers(@Nullable DiscordEventHandlers handlers); + + /** + * Register the executable of the application/game + * Only applicable when autoRegister is set to false + * + * @param applicationId The Application ID + * @param command The Launch command of the game + *

+ * NB: THIS DOES NOT WORK WITH MINECRAFT + */ + void Discord_Register(String applicationId, String command); + + /** + * Register the Steam executable of the application/game + * + * @param applicationId The Application ID + * @param steamId The Steam ID of the application/game + */ + void Discord_RegisterSteamGame(String applicationId, String steamId); + + public enum DiscordReply { + NO(0), + YES(1), + IGNORE(2); + + public final int reply; + + DiscordReply(int reply) { + this.reply = reply; + } + } +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordRichPresence.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordRichPresence.java new file mode 100644 index 0000000..51fd720 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordRichPresence.java @@ -0,0 +1,248 @@ +package com.hypherionmc.craterlib.core.rpcsdk; + +import com.hypherionmc.craterlib.core.rpcsdk.helpers.RPCButton; +import com.sun.jna.Structure; +import org.jetbrains.annotations.NotNull; + +import java.time.OffsetDateTime; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * @author HypherionSA + * Class reprenting a Discord RPC activity + */ +public class DiscordRichPresence extends Structure { + + // First line of text on the RPC + public String state; + + // Second line of text on the RPC + public String details; + + // Time the activity started in UNIX-Timestamp format + public long startTimestamp; + + // Time the activity will end in UNIX-Timestamp format + public long endTimestamp; + + // URL or Asset key of the Large Image + public String largeImageKey; + + // Hover text to display when hovering the Large Image + public String largeImageText; + + // URL or Asset key of the Small Image + public String smallImageKey; + + // Hover text to display when hovering the Small Image + public String smallImageText; + + // Id of the player's party, lobby, or group. + public String partyId; + + // Current size of the player's party, lobby, or group. + public int partySize; + + // Maximum size of the player's party, lobby, or group. + public int partyMax; + + // Unused + public String partyPrivacy; + + // Unused. + public String matchSecret; + + // Unique hashed string for chat invitations and Ask to Join. + public String joinSecret; + + // Unique hashed string for Spectate button. + public String spectateSecret; + + // Label of the First RPC Button + public String button_label_1; + + // URL of the First RPC Button + public String button_url_1; + + // Label of the Second RPC Button + public String button_label_2; + + // URL of the Second RPC Button + public String button_url_2; + + // Unused + public int instance; + + public DiscordRichPresence() { + setStringEncoding("UTF-8"); + } + + /** + * DO NOT TOUCH THIS... EVER! + */ + @Override + protected List getFieldOrder() { + return Arrays.asList( + "state", + "details", + "startTimestamp", + "endTimestamp", + "largeImageKey", + "largeImageText", + "smallImageKey", + "smallImageText", + "partyId", + "partySize", + "partyMax", + "partyPrivacy", + "matchSecret", + "joinSecret", + "spectateSecret", + "button_label_1", + "button_url_1", + "button_label_2", + "button_url_2", + "instance" + ); + } + + public static class Builder { + private final DiscordRichPresence rpc; + + public Builder(String state) { + rpc = new DiscordRichPresence(); + + if (state != null && !state.isEmpty()) { + rpc.state = state.substring(0, Math.min(state.length(), 128)); + } + } + + public Builder setDetails(String details) { + if (details != null && !details.isEmpty()) { + rpc.details = details.substring(0, Math.min(details.length(), 128)); + } + return this; + } + + public Builder setStartTimestamp(long timestamp) { + rpc.startTimestamp = timestamp; + return this; + } + + public Builder setStartTimestamp(OffsetDateTime timestamp) { + rpc.startTimestamp = timestamp.toEpochSecond(); + return this; + } + + public Builder setEndTimestamp(long timestamp) { + rpc.endTimestamp = timestamp; + return this; + } + + public Builder setEndTimestamp(OffsetDateTime timestamp) { + rpc.endTimestamp = timestamp.toEpochSecond(); + return this; + } + + public Builder setLargeImage(String key) { + return this.setLargeImage(key, ""); + } + + public Builder setLargeImage(@NotNull String key, String text) { + // Null check used for users blatantly ignoring the NotNull marker + if ((text != null && !text.isEmpty()) && key != null) { + throw new IllegalArgumentException("Image key cannot be null when assigning a hover text"); + } + + rpc.largeImageKey = key; + rpc.largeImageText = text; + return this; + } + + public Builder setSmallImage(String key) { + return this.setSmallImage(key, ""); + } + + public Builder setSmallImage(@NotNull String key, String text) { + // Null check used for users blatantly ignoring the NotNull marker + if ((text != null && !text.isEmpty()) && key != null) { + throw new IllegalArgumentException("Image key cannot be null when assigning a hover text"); + } + + rpc.smallImageKey = key; + rpc.smallImageText = text; + return this; + } + + public Builder setParty(String party, int size, int max) { + // Buttons are present, ignore + if ((rpc.button_label_1 != null && rpc.button_label_1.isEmpty()) || (rpc.button_label_2 != null && rpc.button_label_2.isEmpty())) + return this; + + rpc.partyId = party; + rpc.partySize = size; + rpc.partyMax = max; + return this; + } + + public Builder setSecrets(String match, String join, String spectate) { + // Buttons are present, ignore + if ((rpc.button_label_1 != null && rpc.button_label_1.isEmpty()) || (rpc.button_label_2 != null && rpc.button_label_2.isEmpty())) + return this; + + rpc.matchSecret = match; + rpc.joinSecret = join; + rpc.spectateSecret = spectate; + return this; + } + + public Builder setSecrets(String join, String spectate) { + // Buttons are present, ignore + if ((rpc.button_label_1 != null && rpc.button_label_1.isEmpty()) || (rpc.button_label_2 != null && rpc.button_label_2.isEmpty())) + return this; + + rpc.joinSecret = join; + rpc.spectateSecret = spectate; + return this; + } + + public Builder setInstance(boolean i) { + // Buttons are present, ignore + if ((rpc.button_label_1 != null && rpc.button_label_1.isEmpty()) || (rpc.button_label_2 != null && rpc.button_label_2.isEmpty())) + return this; + + rpc.instance = i ? 1 : 0; + return this; + } + + public Builder setButtons(RPCButton button) { + return this.setButtons(Collections.singletonList(button)); + } + + public Builder setButtons(RPCButton button1, RPCButton button2) { + return this.setButtons(Arrays.asList(button1, button2)); + } + + public Builder setButtons(List rpcButtons) { + // Limit to 2 Buttons. Discord Limitation + if (rpcButtons != null && !rpcButtons.isEmpty()) { + int length = Math.min(rpcButtons.size(), 2); + rpc.button_label_1 = rpcButtons.get(0).getLabel(); + rpc.button_url_1 = rpcButtons.get(0).getUrl(); + + if (length == 2) { + rpc.button_label_2 = rpcButtons.get(1).getLabel(); + rpc.button_url_2 = rpcButtons.get(1).getUrl(); + } + } + + return this; + } + + public DiscordRichPresence build() { + return rpc; + } + } +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordUser.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordUser.java new file mode 100644 index 0000000..e8bc085 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordUser.java @@ -0,0 +1,39 @@ +package com.hypherionmc.craterlib.core.rpcsdk; + +import com.sun.jna.Structure; + +import java.util.Arrays; +import java.util.List; + +/** + * @author HypherionSA + * Class representing the Discord User + */ +public class DiscordUser extends Structure { + + // The User ID of the User + public String userId; + + // The Username of the User + public String username; + + // The unique identifier of the user. Discontinued by Discord + @Deprecated + public String discriminator; + + // The avatar has of the user + public String avatar; + + /** + * DO NOT TOUCH THIS... EVER! + */ + @Override + protected List getFieldOrder() { + return Arrays.asList( + "userId", + "username", + "discriminator", + "avatar" + ); + } +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/DisconnectedCallback.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/DisconnectedCallback.java new file mode 100644 index 0000000..7ea59e1 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/DisconnectedCallback.java @@ -0,0 +1,18 @@ +package com.hypherionmc.craterlib.core.rpcsdk.callbacks; + +import com.sun.jna.Callback; + +/** + * @author HypherionSA + * Callback for when the Discord RPC disconnects + */ +public interface DisconnectedCallback extends Callback { + + /** + * Called when RPC disconnected + * + * @param errorCode Error code if any + * @param message Details about the disconnection + */ + void apply(int errorCode, String message); +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/ErroredCallback.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/ErroredCallback.java new file mode 100644 index 0000000..1f86c90 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/ErroredCallback.java @@ -0,0 +1,18 @@ +package com.hypherionmc.craterlib.core.rpcsdk.callbacks; + +import com.sun.jna.Callback; + +/** + * @author HypherionSA + * Callback for when the RPC ran into an error + */ +public interface ErroredCallback extends Callback { + + /** + * Called when an RPC error occurs + * + * @param errorCode Error code if any + * @param message Details about the error + */ + void apply(int errorCode, String message); +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/JoinGameCallback.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/JoinGameCallback.java new file mode 100644 index 0000000..cc752af --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/JoinGameCallback.java @@ -0,0 +1,17 @@ +package com.hypherionmc.craterlib.core.rpcsdk.callbacks; + +import com.sun.jna.Callback; + +/** + * @author HypherionSA + * Callback for when someone was approved to join your game + */ +public interface JoinGameCallback extends Callback { + + /** + * Called when someone joins a game from {@link JoinRequestCallback} + * + * @param joinSecret Secret or Password required to let the player join the game + */ + void apply(String joinSecret); +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/JoinRequestCallback.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/JoinRequestCallback.java new file mode 100644 index 0000000..115fd4f --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/JoinRequestCallback.java @@ -0,0 +1,19 @@ +package com.hypherionmc.craterlib.core.rpcsdk.callbacks; + +import com.hypherionmc.craterlib.core.rpcsdk.DiscordUser; +import com.sun.jna.Callback; + +/** + * @author HypherionSA + * Callback for when someone requests to join your game + */ +public interface JoinRequestCallback extends Callback { + + /** + * Called when someone clicks on the Join Game button + * + * @param user The Discord User trying to join your game + * @see DiscordUser + */ + void apply(DiscordUser user); +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/ReadyCallback.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/ReadyCallback.java new file mode 100644 index 0000000..66f3b59 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/ReadyCallback.java @@ -0,0 +1,19 @@ +package com.hypherionmc.craterlib.core.rpcsdk.callbacks; + +import com.hypherionmc.craterlib.core.rpcsdk.DiscordUser; +import com.sun.jna.Callback; + +/** + * @author HypherionSA + * Callback for when the RPC has connected successfully + */ +public interface ReadyCallback extends Callback { + + /** + * Called when the RPC is connected and ready to be used + * + * @param user The user the RPC is displayed on + * @see DiscordUser + */ + void apply(DiscordUser user); +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/SpectateGameCallback.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/SpectateGameCallback.java new file mode 100644 index 0000000..979e53d --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/SpectateGameCallback.java @@ -0,0 +1,17 @@ +package com.hypherionmc.craterlib.core.rpcsdk.callbacks; + +import com.sun.jna.Callback; + +/** + * @author HypherionSA + * Callback for when someone is requesting to spectate your game + */ +public interface SpectateGameCallback extends Callback { + + /** + * Called when joining the game + * + * @param spectateSecret Secret or Password required to let the player spectate + */ + void apply(String spectateSecret); +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/helpers/RPCButton.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/helpers/RPCButton.java new file mode 100644 index 0000000..ab2bdc8 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/helpers/RPCButton.java @@ -0,0 +1,55 @@ +package com.hypherionmc.craterlib.core.rpcsdk.helpers; + +import org.jetbrains.annotations.NotNull; + +import java.io.Serializable; + +/** + * @author HypherionSA + * Helper class to add Buttons to Discord Rich Presence + * This can not be used with Join/Spectate + */ +public class RPCButton implements Serializable { + + // The label of the button + private final String label; + + // The URL the button will open when clicked + private final String url; + + protected RPCButton(String label, String url) { + this.label = label; + this.url = url; + } + + /** + * Create a new RPC Button + * + * @param label The label of the button + * @param url The URL the button will open when clicked + * @return The constructed button + */ + public static RPCButton create(@NotNull String label, @NotNull String url) { + // Null check used here for users blatantly ignoring the NotNull marker + if (label == null || label.isEmpty() || url == null || url.isEmpty()) { + throw new IllegalArgumentException("RPC Buttons require both a label and url"); + } + + label = label.substring(0, Math.min(label.length(), 31)); + return new RPCButton(label, url); + } + + /** + * @return The label assigned to the button + */ + public String getLabel() { + return label; + } + + /** + * @return The URL of the button + */ + public String getUrl() { + return url; + } +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/ChatInputSuggestorMixin.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/ChatInputSuggestorMixin.java new file mode 100644 index 0000000..c48dfbc --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/ChatInputSuggestorMixin.java @@ -0,0 +1,84 @@ +package com.hypherionmc.craterlib.mixin; + +import com.hypherionmc.craterlib.client.mentions.MentionsController; +import net.minecraft.client.gui.components.CommandSuggestions; +import net.minecraft.client.gui.components.EditBox; +import org.objectweb.asm.Opcodes; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.ModifyVariable; +import org.spongepowered.asm.mixin.injection.Slice; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * @author HypherionSA + * Allow Users, Roles and Channels to be pingable from MC chat (Client Side) + */ +@Mixin(CommandSuggestions.class) +public abstract class ChatInputSuggestorMixin { + + @Shadow + public abstract void showSuggestions(boolean p_93931_); + + @Shadow @Final + EditBox input; + + @Shadow + private static int getLastWordIndex(String p_93913_) { + return 0; + } + + @Inject( + method = "updateCommandInfo", + at = @At( + value = "FIELD", + target = "Lnet/minecraft/client/gui/components/CommandSuggestions;pendingSuggestions:Ljava/util/concurrent/CompletableFuture;", + opcode = Opcodes.PUTFIELD, + shift = At.Shift.AFTER, + ordinal = 0 + ), + slice = @Slice( + from = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/gui/components/CommandSuggestions;getLastWordIndex(Ljava/lang/String;)I" + ) + ) + ) + private void injectSuggestions(CallbackInfo ci) { + if (MentionsController.hasMentions() && MentionsController.isLastMentionConditional()) { + this.showSuggestions(true); + } + } + + @SuppressWarnings("InvalidInjectorMethodSignature") + @ModifyVariable(method = "updateCommandInfo", at = @At(value = "STORE"), ordinal = 0, name = "collection") + private Collection injectMentions(Collection vanilla) { + if (!MentionsController.hasMentions()) + return vanilla; + + ArrayList newSuggest = new ArrayList<>(vanilla); + + String currentInput = this.input.getValue(); + int currentCursorPosition = this.input.getCursorPosition(); + + String textBeforeCursor = currentInput.substring(0, currentCursorPosition); + int startOfCurrentWord = getLastWordIndex(textBeforeCursor); + + String currentWord = textBeforeCursor.substring(startOfCurrentWord); + String finalWord = currentWord.replace("[", "").replace("]", ""); + + Collection mentions = MentionsController.getMentions(finalWord); + + if (!mentions.isEmpty()) { + mentions.forEach(m -> newSuggest.add("[" + m + "]")); + } + + return newSuggest; + } +} \ No newline at end of file diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/CommandMixin.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/CommandMixin.java new file mode 100644 index 0000000..aeb2e22 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/CommandMixin.java @@ -0,0 +1,37 @@ +package com.hypherionmc.craterlib.mixin.events; + +import com.google.common.base.Throwables; +import com.hypherionmc.craterlib.api.events.server.CraterCommandEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.mojang.brigadier.ParseResults; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +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.CallbackInfoReturnable; + +@Mixin(Commands.class) +public class CommandMixin { + + @Inject(method = "performCommand", + at = @At(value = "INVOKE", + target = "Lcom/mojang/brigadier/CommandDispatcher;execute(Lcom/mojang/brigadier/ParseResults;)I", + shift = At.Shift.BEFORE + ), cancellable = true + ) + private void injectCommandEvent(ParseResults stackParseResults, String command, CallbackInfoReturnable cir) { + CraterCommandEvent commandEvent = CraterCommandEvent.of(stackParseResults, command); + CraterEventBus.INSTANCE.postEvent(commandEvent); + if (commandEvent.wasCancelled()) { + cir.setReturnValue(1); + return; + } + + if (commandEvent.getException() != null) { + Throwables.throwIfUnchecked(commandEvent.getException()); + cir.setReturnValue(1); + } + } + +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerAdvancementsMixin.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerAdvancementsMixin.java new file mode 100644 index 0000000..2457176 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerAdvancementsMixin.java @@ -0,0 +1,29 @@ +package com.hypherionmc.craterlib.mixin.events; + +import com.hypherionmc.craterlib.api.events.server.CraterAdvancementEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.advancements.BridgedAdvancement; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraft.advancements.Advancement; +import net.minecraft.server.PlayerAdvancements; +import net.minecraft.server.level.ServerPlayer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(PlayerAdvancements.class) +public class PlayerAdvancementsMixin { + + @Shadow + private ServerPlayer player; + + @Inject(method = "award", at = @At(value = "INVOKE", target = "Lnet/minecraft/advancements/AdvancementRewards;grant(Lnet/minecraft/server/level/ServerPlayer;)V", shift = At.Shift.AFTER)) + private void injectAdvancementEvent(Advancement advancement, String string, CallbackInfoReturnable cir) { + if (advancement.getDisplay() == null || !advancement.getDisplay().shouldAnnounceChat()) + return; + + CraterEventBus.INSTANCE.postEvent(new CraterAdvancementEvent(BridgedPlayer.of(this.player), BridgedAdvancement.of(advancement))); + } +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerListMixin.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerListMixin.java new file mode 100644 index 0000000..0aaa866 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerListMixin.java @@ -0,0 +1,52 @@ +package com.hypherionmc.craterlib.mixin.events; + +import com.hypherionmc.craterlib.api.events.server.CraterPlayerEvent; +import com.hypherionmc.craterlib.api.events.server.MessageBroadcastEvent; +import com.hypherionmc.craterlib.api.events.server.PlayerPreLoginEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.authlib.BridgedGameProfile; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.ChatUtils; +import com.mojang.authlib.GameProfile; +import net.minecraft.network.Connection; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.players.PlayerList; +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; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.net.SocketAddress; +import java.util.function.Function; + +@Mixin(PlayerList.class) +public class PlayerListMixin { + + @Inject(method = "broadcastSystemMessage(Lnet/minecraft/network/chat/Component;Ljava/util/function/Function;Z)V", at = @At("HEAD")) + private void injectBroadcastEvent(Component component, Function function, boolean bl, CallbackInfo ci) { + String thread = Thread.currentThread().getStackTrace()[3].getClassName(); + MessageBroadcastEvent event = new MessageBroadcastEvent(ChatUtils.mojangToAdventure(component), (f) -> ChatUtils.mojangToAdventure(component), bl, thread); + CraterEventBus.INSTANCE.postEvent(event); + } + + @Inject(method = "placeNewPlayer", at = @At("TAIL")) + private void injectPlayerLoginEvent(Connection arg, ServerPlayer serverPlayer, CallbackInfo ci) { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerEvent.PlayerLoggedIn(BridgedPlayer.of(serverPlayer))); + } + + @Inject(method = "remove", at = @At("HEAD")) + private void injectPlayerLogoutEvent(ServerPlayer player, CallbackInfo ci) { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerEvent.PlayerLoggedOut(BridgedPlayer.of(player))); + } + + @Inject(method = "canPlayerLogin", at = @At("HEAD"), cancellable = true) + private void injectPreLoginEvent(SocketAddress address, GameProfile gameProfile, CallbackInfoReturnable cir) { + PlayerPreLoginEvent event = new PlayerPreLoginEvent(address, BridgedGameProfile.of(gameProfile)); + CraterEventBus.INSTANCE.postEvent(event); + if (event.getMessage() != null) { + cir.setReturnValue(ChatUtils.adventureToMojang(event.getMessage())); + } + } +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerMixin.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerMixin.java new file mode 100644 index 0000000..5fd03bd --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerMixin.java @@ -0,0 +1,21 @@ +package com.hypherionmc.craterlib.mixin.events; + +import com.hypherionmc.craterlib.api.events.common.CraterPlayerDeathEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.player.Player; +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(Player.class) +public class PlayerMixin { + + @Inject(method = "die", at = @At("HEAD")) + private void injectPlayerDeathEvent(DamageSource damageSource, CallbackInfo ci) { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerDeathEvent(BridgedPlayer.of(((Player) (Object) this)), damageSource)); + } + +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/ServerPlayerMixin.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/ServerPlayerMixin.java new file mode 100644 index 0000000..ae028e7 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/ServerPlayerMixin.java @@ -0,0 +1,21 @@ +package com.hypherionmc.craterlib.mixin.events; + +import com.hypherionmc.craterlib.api.events.common.CraterPlayerDeathEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.damagesource.DamageSource; +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(ServerPlayer.class) +public class ServerPlayerMixin { + + @Inject(method = "die", at = @At("HEAD")) + private void injectPlayerDeathEvent(DamageSource damageSource, CallbackInfo ci) { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerDeathEvent(BridgedPlayer.of(((ServerPlayer) (Object) this)), damageSource)); + } + +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/ClientLevelMixin.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/ClientLevelMixin.java new file mode 100644 index 0000000..3535d6f --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/ClientLevelMixin.java @@ -0,0 +1,25 @@ +package com.hypherionmc.craterlib.mixin.events.client; + +import com.hypherionmc.craterlib.api.events.client.CraterSinglePlayerEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; +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(ClientLevel.class) +public class ClientLevelMixin { + + @Inject(method = "addEntity", at = @At("HEAD")) + private void injectSinglePlayerJoinEvent(int i, Entity entity, CallbackInfo ci) { + if (entity instanceof Player player) { + CraterSinglePlayerEvent.PlayerLogin playerLogin = new CraterSinglePlayerEvent.PlayerLogin(BridgedPlayer.of(player)); + CraterEventBus.INSTANCE.postEvent(playerLogin); + } + } + +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/MinecraftMixin.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/MinecraftMixin.java new file mode 100644 index 0000000..2903131 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/MinecraftMixin.java @@ -0,0 +1,31 @@ +package com.hypherionmc.craterlib.mixin.events.client; + +import com.hypherionmc.craterlib.api.events.client.ScreenEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.client.gui.BridgedScreen; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.Screen; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(Minecraft.class) +public class MinecraftMixin { + + @Shadow + @Nullable + public Screen screen; + + @Inject(method = "setScreen", at = @At(value = "TAIL")) + private void injectScreenOpeningEvent(Screen screen, CallbackInfo ci) { + Screen old = this.screen; + if (screen != null) { + ScreenEvent.Opening opening = new ScreenEvent.Opening(BridgedScreen.of(old), BridgedScreen.of(screen)); + CraterEventBus.INSTANCE.postEvent(opening); + } + } + +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/RealmsMainScreenMixin.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/RealmsMainScreenMixin.java new file mode 100644 index 0000000..d2445c3 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/RealmsMainScreenMixin.java @@ -0,0 +1,23 @@ +package com.hypherionmc.craterlib.mixin.events.client; + +import com.hypherionmc.craterlib.api.events.client.PlayerJoinRealmEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.realmsclient.dto.BridgedRealmsServer; +import com.mojang.realmsclient.RealmsMainScreen; +import com.mojang.realmsclient.dto.RealmsServer; +import net.minecraft.client.gui.screens.Screen; +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 RealmsMainScreenMixin { + + @Inject(at = @At("HEAD"), method = "play") + private void play(RealmsServer serverData, Screen arg2, CallbackInfo ci) { + PlayerJoinRealmEvent playerJoinRealm = new PlayerJoinRealmEvent(BridgedRealmsServer.of(serverData)); + CraterEventBus.INSTANCE.postEvent(playerJoinRealm); + } + +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/advancements/BridgedAdvancement.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/advancements/BridgedAdvancement.java new file mode 100644 index 0000000..6b8e376 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/advancements/BridgedAdvancement.java @@ -0,0 +1,21 @@ +package com.hypherionmc.craterlib.nojang.advancements; + +import lombok.RequiredArgsConstructor; +import net.minecraft.advancements.Advancement; + +import java.util.Optional; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedAdvancement { + + private final Advancement internal; + + public Optional displayInfo() { + if (internal.getDisplay() != null) { + return Optional.of(BridgedDisplayInfo.of(internal.getDisplay())); + } + + return Optional.empty(); + } + +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/advancements/BridgedDisplayInfo.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/advancements/BridgedDisplayInfo.java new file mode 100644 index 0000000..5a0c155 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/advancements/BridgedDisplayInfo.java @@ -0,0 +1,29 @@ +package com.hypherionmc.craterlib.nojang.advancements; + +import com.hypherionmc.craterlib.utils.ChatUtils; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; +import net.minecraft.advancements.DisplayInfo; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedDisplayInfo { + + private final DisplayInfo internal; + + public boolean shouldDisplay() { + return internal.shouldAnnounceChat(); + } + + public boolean isHidden() { + return internal.isHidden(); + } + + public Component displayName() { + return ChatUtils.mojangToAdventure(internal.getTitle()); + } + + public Component description() { + return ChatUtils.mojangToAdventure(internal.getDescription()); + } + +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/authlib/BridgedGameProfile.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/authlib/BridgedGameProfile.java new file mode 100644 index 0000000..c83f3c4 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/authlib/BridgedGameProfile.java @@ -0,0 +1,29 @@ +package com.hypherionmc.craterlib.nojang.authlib; + +import com.mojang.authlib.GameProfile; +import lombok.RequiredArgsConstructor; + +import java.util.UUID; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedGameProfile { + + private final GameProfile internal; + + public static BridgedGameProfile mojang(UUID id, String name) { + return new BridgedGameProfile(new GameProfile(id, name)); + } + + public String getName() { + return internal.getName(); + } + + public UUID getId() { + return internal.getId(); + } + + public GameProfile toMojang() { + return internal; + } + +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/BridgedMinecraft.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/BridgedMinecraft.java new file mode 100644 index 0000000..2d6242b --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/BridgedMinecraft.java @@ -0,0 +1,84 @@ +package com.hypherionmc.craterlib.nojang.client; + +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedClientLevel; +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedServerData; +import com.hypherionmc.craterlib.nojang.client.server.BridgedIntegratedServer; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import lombok.Getter; +import net.minecraft.SharedConstants; +import net.minecraft.client.Minecraft; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.util.UUID; + +public class BridgedMinecraft { + + @Getter + private static final BridgedMinecraft instance = new BridgedMinecraft(); + private final Minecraft internal = Minecraft.getInstance(); + + public File getGameDirectory() { + return internal.gameDirectory; + } + + public BridgedOptions getOptions() { + return BridgedOptions.of(internal.options); + } + + @Nullable + public BridgedClientLevel getLevel() { + if (internal.level == null) + return null; + + return BridgedClientLevel.of(internal.level); + } + + public boolean isRealmServer() { + return internal.getCurrentServer() != null && internal.isConnectedToRealms(); + } + + public boolean isSinglePlayer() { + return internal.hasSingleplayerServer(); + } + + @Nullable + public BridgedPlayer getPlayer() { + if (internal.player == null) + return null; + + return BridgedPlayer.of(internal.player); + } + + public String getGameVersion() { + return SharedConstants.getCurrentVersion().getName(); + } + + public String getUserName() { + return internal.getUser().getName(); + } + + public UUID getPlayerId() { + return internal.getUser().getProfileId(); + } + + @Nullable + public BridgedServerData getCurrentServer() { + if (internal.getCurrentServer() == null) + return null; + + return BridgedServerData.of(internal.getCurrentServer()); + } + + @Nullable + public BridgedIntegratedServer getSinglePlayerServer() { + return BridgedIntegratedServer.of(internal.getSingleplayerServer()); + } + + public int getServerPlayerCount () { + if (internal.getConnection() == null) + return 0; + + return internal.getConnection().getOnlinePlayers().size(); + } +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/BridgedOptions.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/BridgedOptions.java new file mode 100644 index 0000000..7063feb --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/BridgedOptions.java @@ -0,0 +1,15 @@ +package com.hypherionmc.craterlib.nojang.client; + +import lombok.RequiredArgsConstructor; +import net.minecraft.client.Options; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedOptions { + + private final Options internal; + + public String getLanguage() { + return internal.languageCode; + } + +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/gui/BridgedScreen.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/gui/BridgedScreen.java new file mode 100644 index 0000000..c4bcc23 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/gui/BridgedScreen.java @@ -0,0 +1,36 @@ +package com.hypherionmc.craterlib.nojang.client.gui; + +import lombok.RequiredArgsConstructor; +import net.minecraft.client.gui.screens.LevelLoadingScreen; +import net.minecraft.client.gui.screens.ReceivingLevelScreen; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.gui.screens.TitleScreen; +import net.minecraft.client.gui.screens.multiplayer.JoinMultiplayerScreen; +import net.minecraft.realms.RealmsScreen; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedScreen { + + private final Screen internal; + + public boolean isTitleScreen() { + return internal instanceof TitleScreen; + } + + public boolean isRealmsScreen() { + return internal instanceof RealmsScreen; + } + + public boolean isServerBrowserScreen() { + return internal instanceof JoinMultiplayerScreen; + } + + public boolean isLoadingScreen() { + return internal instanceof LevelLoadingScreen || internal instanceof ReceivingLevelScreen; + } + + public Screen toMojang() { + return internal; + } + +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/multiplayer/BridgedClientLevel.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/multiplayer/BridgedClientLevel.java new file mode 100644 index 0000000..4e7b60b --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/multiplayer/BridgedClientLevel.java @@ -0,0 +1,59 @@ +package com.hypherionmc.craterlib.nojang.client.multiplayer; + +import com.hypherionmc.craterlib.nojang.core.BridgedBlockPos; +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; +import com.hypherionmc.craterlib.utils.ChatUtils; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; +import net.minecraft.client.multiplayer.ClientLevel; +import org.jetbrains.annotations.Nullable; + +import java.util.concurrent.atomic.AtomicReference; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedClientLevel { + + private final ClientLevel internal; + + public boolean isClientSide() { + return internal.isClientSide(); + } + + public long getGameTime() { + return internal.getGameTime(); + } + + public long getDayTime() { + return internal.getDayTime(); + } + + public long dayTime() { + return internal.dayTime(); + } + + public boolean isRaining() { + return internal.isRaining(); + } + + public boolean isThundering() { + return internal.isThundering(); + } + + @Nullable + public ResourceIdentifier getDimensionKey() { + return ResourceIdentifier.fromMojang(internal.dimension().location()); + } + + @Nullable + public ResourceIdentifier getBiomeIdentifier(BridgedBlockPos onPos) { + AtomicReference identifier = new AtomicReference<>(null); + internal.getBiome(onPos.toMojang()).unwrap().ifLeft(b -> identifier.set(ResourceIdentifier.fromMojang(b.location()))); + return identifier.get(); + } + + @Nullable + public Component getDifficulty() { + return ChatUtils.mojangToAdventure(internal.getDifficulty().getDisplayName()); + } + +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/multiplayer/BridgedServerData.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/multiplayer/BridgedServerData.java new file mode 100644 index 0000000..710d2c6 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/multiplayer/BridgedServerData.java @@ -0,0 +1,45 @@ +package com.hypherionmc.craterlib.nojang.client.multiplayer; + +import com.hypherionmc.craterlib.utils.ChatUtils; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; +import net.minecraft.ChatFormatting; +import net.minecraft.client.multiplayer.ServerData; +import net.minecraft.client.multiplayer.ServerStatusPinger; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedServerData { + + private final ServerData internal; + + public String name() { + return internal.name; + } + + public String ip() { + return internal.ip; + } + + public Component motd() { + return ChatUtils.mojangToAdventure(internal.motd); + } + + public int getMaxPlayers() { + if (!internal.pinged || internal.status.getString() == null) { + try { + new ServerStatusPinger().pingServer(internal, () -> {}); + } catch (Exception ignored) {} + } + + try { + return Integer.parseInt(ChatFormatting.stripFormatting(internal.status.getString()).split("/")[1]); + } catch (Exception ignored) {} + + return 0; + } + + public ServerData toMojang() { + return internal; + } + +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/server/BridgedIntegratedServer.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/server/BridgedIntegratedServer.java new file mode 100644 index 0000000..0ecacaf --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/server/BridgedIntegratedServer.java @@ -0,0 +1,19 @@ +package com.hypherionmc.craterlib.nojang.client.server; + +import lombok.RequiredArgsConstructor; +import net.minecraft.client.server.IntegratedServer; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedIntegratedServer { + + private final IntegratedServer internal; + + public String getLevelName() { + return internal.getWorldData().getLevelName(); + } + + public IntegratedServer toMojang() { + return internal; + } + +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/BridgedCommandSourceStack.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/BridgedCommandSourceStack.java new file mode 100644 index 0000000..b14e484 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/BridgedCommandSourceStack.java @@ -0,0 +1,22 @@ +package com.hypherionmc.craterlib.nojang.commands; + +import com.hypherionmc.craterlib.utils.ChatUtils; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; +import net.minecraft.commands.CommandSourceStack; + +import java.util.function.Supplier; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedCommandSourceStack { + + private final CommandSourceStack internal; + + public void sendSuccess(Supplier supplier, boolean bl) { + internal.sendSuccess(ChatUtils.adventureToMojang(supplier.get()), bl); + } + + public CommandSourceStack toMojang() { + return internal; + } +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/BridgedFakePlayer.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/BridgedFakePlayer.java new file mode 100644 index 0000000..1c5eaa0 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/BridgedFakePlayer.java @@ -0,0 +1,55 @@ +package com.hypherionmc.craterlib.nojang.commands; + +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import com.hypherionmc.craterlib.utils.ChatUtils; +import net.minecraft.commands.CommandSource; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.network.chat.Component; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.phys.Vec2; +import net.minecraft.world.phys.Vec3; + +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Supplier; + +public abstract class BridgedFakePlayer { + + final MojangBridge internal; + + public BridgedFakePlayer(BridgedMinecraftServer server, int perm, String name) { + internal = new MojangBridge(server.toMojang(), perm, name, this::onSuccess, this::onError); + } + + public abstract void onSuccess(Supplier supplier, Boolean aBoolean); + + public void onError(net.kyori.adventure.text.Component component) { + this.onSuccess(() -> component, false); + } + + public CommandSourceStack toMojang() { + return internal; + } + + static class MojangBridge extends CommandSourceStack { + + private final BiConsumer, Boolean> successCallback; + public final Consumer errorCallback; + + MojangBridge(MinecraftServer server, int perm, String name, BiConsumer, Boolean> successCallback, Consumer errorCallback) { + super(CommandSource.NULL, Vec3.ZERO, Vec2.ZERO, server.overworld(), perm, name, Component.literal(name), server, null); + this.successCallback = successCallback; + this.errorCallback = errorCallback; + } + + @Override + public void sendSuccess(Component supplier, boolean bl) { + successCallback.accept(() -> ChatUtils.mojangToAdventure(supplier), bl); + } + + @Override + public void sendFailure(Component arg) { + errorCallback.accept(ChatUtils.mojangToAdventure(arg)); + } + } +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/CommandsRegistry.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/CommandsRegistry.java new file mode 100644 index 0000000..37ad15b --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/CommandsRegistry.java @@ -0,0 +1,85 @@ +package com.hypherionmc.craterlib.nojang.commands; + +import com.hypherionmc.craterlib.api.commands.CraterCommand; +import com.hypherionmc.craterlib.nojang.authlib.BridgedGameProfile; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.TriConsumer; +import com.mojang.authlib.GameProfile; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.commands.arguments.GameProfileArgument; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class CommandsRegistry { + + public static final CommandsRegistry INSTANCE = new CommandsRegistry(); + + private final List commands = new ArrayList<>(); + + public void registerCommand(CraterCommand cmd) { + commands.add(cmd); + } + + public void registerCommands(CommandDispatcher stack) { + commands.forEach(cmd -> { + if (cmd.hasArguments()) { + CommandWithArguments.register(cmd, stack); + } else { + CommandWithoutArguments.register(cmd, stack); + } + }); + } + + static class CommandWithoutArguments { + + public static void register(CraterCommand cmd, CommandDispatcher dispatcher) { + LiteralArgumentBuilder command = Commands.literal(cmd.getCommandName()) + .requires(source -> source.hasPermission(cmd.getPermissionLevel())) + .executes(context -> { + cmd.getExecutor().accept(BridgedCommandSourceStack.of(context.getSource())); + return 1; + }); + + dispatcher.register(command); + } + + } + + @SuppressWarnings("unchecked") + static class CommandWithArguments { + + public static void register(CraterCommand cmd, CommandDispatcher dispatcher) { + LiteralArgumentBuilder command = Commands.literal(cmd.getCommandName()) + .requires(source -> source.hasPermission(cmd.getPermissionLevel())); + + cmd.getArguments().forEach((key, pair) -> command.then(Commands.argument(key, pair.getLeft()).executes(context -> { + + // This is FUCKING UGLY.... Need to improve this in the future + if (pair.getLeft() instanceof GameProfileArgument) { + Collection profiles = GameProfileArgument.getGameProfiles(context, key); + List bridgedGameProfiles = new ArrayList<>(); + + profiles.forEach(p -> bridgedGameProfiles.add(BridgedGameProfile.of(p))); + + ((TriConsumer, BridgedCommandSourceStack>) pair.getRight()) + .accept(BridgedPlayer.of(context.getSource().getPlayer()), bridgedGameProfiles, BridgedCommandSourceStack.of(context.getSource())); + return 1; + } + + return 1; + }))); + + dispatcher.register(command); + } + + } + +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/core/BridgedBlockPos.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/core/BridgedBlockPos.java new file mode 100644 index 0000000..bd49e2c --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/core/BridgedBlockPos.java @@ -0,0 +1,27 @@ +package com.hypherionmc.craterlib.nojang.core; + +import lombok.RequiredArgsConstructor; +import net.minecraft.core.BlockPos; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedBlockPos { + + private final BlockPos internal; + + public int getX() { + return internal.getX(); + } + + public int getY() { + return internal.getY(); + } + + public int getZ() { + return internal.getZ(); + } + + public BlockPos toMojang() { + return internal; + } + +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/nbt/BridgedCompoundTag.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/nbt/BridgedCompoundTag.java new file mode 100644 index 0000000..17a2d04 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/nbt/BridgedCompoundTag.java @@ -0,0 +1,49 @@ +package com.hypherionmc.craterlib.nojang.nbt; + +import lombok.RequiredArgsConstructor; +import net.minecraft.nbt.CompoundTag; + +import java.util.Set; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedCompoundTag { + + private final CompoundTag internal; + + public static BridgedCompoundTag empty() { + return new BridgedCompoundTag(new CompoundTag()); + } + + public BridgedCompoundTag getCompound(String key) { + return BridgedCompoundTag.of(internal.getCompound(key)); + } + + public Set getAllKeys() { + return internal.getAllKeys(); + } + + public String getString(String key) { + return internal.getString(key); + } + + public boolean getBoolean(String key) { + return internal.getBoolean(key); + } + + public void putString(String key, String value) { + internal.putString(key, value); + } + + public void put(String key, BridgedCompoundTag value) { + internal.put(key, value.toMojang()); + } + + public void putBoolean(String key, boolean value) { + internal.putBoolean(key, value); + } + + public CompoundTag toMojang() { + return internal; + } + +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/network/BridgedFriendlyByteBuf.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/network/BridgedFriendlyByteBuf.java new file mode 100644 index 0000000..a7bafd0 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/network/BridgedFriendlyByteBuf.java @@ -0,0 +1,34 @@ +package com.hypherionmc.craterlib.nojang.network; + +import com.hypherionmc.craterlib.nojang.nbt.BridgedCompoundTag; +import lombok.RequiredArgsConstructor; +import net.minecraft.network.FriendlyByteBuf; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedFriendlyByteBuf { + + private final FriendlyByteBuf internal; + + public BridgedCompoundTag readNbt() { + return BridgedCompoundTag.of(internal.readNbt()); + } + + public BridgedFriendlyByteBuf writeNbt(BridgedCompoundTag tag) { + internal.writeNbt(tag.toMojang()); + return BridgedFriendlyByteBuf.of(internal); + } + + public BridgedFriendlyByteBuf writeUtf(String value) { + internal.writeUtf(value); + return BridgedFriendlyByteBuf.of(internal); + } + + public String readUtf() { + return internal.readUtf(); + } + + public FriendlyByteBuf toMojang() { + return internal; + } + +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/package-info.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/package-info.java new file mode 100644 index 0000000..4c177a5 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/package-info.java @@ -0,0 +1,9 @@ +/** + * @author HypherionSA + * This package, called NoJang, exposes various wrapped API's. + * Using this api, a mod can essentially run on ANY minecraft version this library + * supports, from one code base. + * IMPORTANT NOTE: THESE API'S MUST NEVER EXPOSE ANY MINECRAFT CLASSES OR CODE!!!! + * THEY MUST ALWAYS BE HANDLED INTERNALLY AND ONLY RETURN WRAPPED VARIANTS + */ +package com.hypherionmc.craterlib.nojang; \ No newline at end of file diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/realmsclient/dto/BridgedRealmsServer.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/realmsclient/dto/BridgedRealmsServer.java new file mode 100644 index 0000000..dddf746 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/realmsclient/dto/BridgedRealmsServer.java @@ -0,0 +1,40 @@ +package com.hypherionmc.craterlib.nojang.realmsclient.dto; + +import com.mojang.realmsclient.dto.PlayerInfo; +import com.mojang.realmsclient.dto.RealmsServer; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedRealmsServer { + + private final RealmsServer internal; + + public String getName() { + return internal.getName(); + } + + public String getDescription() { + return internal.getDescription(); + } + + public String getWorldType() { + return internal.worldType.name(); + } + + public String getMinigameName() { + return internal.getMinigameName(); + } + + public String getMinigameImage() { + return internal.minigameImage; + } + + public long getPlayerCount() { + return internal.players.stream().filter(PlayerInfo::getOnline).count(); + } + + public RealmsServer toMojang() { + return internal; + } + +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/resources/ResourceIdentifier.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/resources/ResourceIdentifier.java new file mode 100644 index 0000000..9b848f4 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/resources/ResourceIdentifier.java @@ -0,0 +1,36 @@ +package com.hypherionmc.craterlib.nojang.resources; + +import net.minecraft.resources.ResourceLocation; + +public class ResourceIdentifier { + + private final ResourceLocation internal; + + public ResourceIdentifier(String namespace, String path) { + this.internal = new ResourceLocation(namespace, path); + } + + public ResourceIdentifier(String path) { + this.internal = new ResourceLocation(path); + } + + public String getNamespace() { + return internal.getNamespace(); + } + + public String getPath() { + return internal.getPath(); + } + + public String getString() { + return internal.toString(); + } + + public static ResourceIdentifier fromMojang(ResourceLocation location) { + return new ResourceIdentifier(location.getNamespace(), location.getPath()); + } + + public ResourceLocation toMojang() { + return internal; + } +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/server/BridgedMinecraftServer.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/server/BridgedMinecraftServer.java new file mode 100644 index 0000000..cb818b7 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/server/BridgedMinecraftServer.java @@ -0,0 +1,91 @@ +package com.hypherionmc.craterlib.nojang.server; + +import com.hypherionmc.craterlib.nojang.authlib.BridgedGameProfile; +import com.hypherionmc.craterlib.nojang.commands.BridgedFakePlayer; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.ChatUtils; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; +import net.minecraft.SharedConstants; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.players.UserBanListEntry; +import net.minecraft.server.players.UserWhiteListEntry; + +import java.util.ArrayList; +import java.util.List; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedMinecraftServer { + + private final MinecraftServer internal; + + public boolean isUsingWhitelist() { + return internal.getPlayerList().isUsingWhitelist(); + } + + public int getPlayerCount() { + return internal.getPlayerList().getPlayerCount(); + } + + public int getMaxPlayers() { + return internal.getPlayerList().getMaxPlayers(); + } + + public String getServerModName() { + return internal.getServerModName(); + } + + public String getName() { + return SharedConstants.getCurrentVersion().getName(); + } + + public boolean usesAuthentication() { + return internal.usesAuthentication(); + } + + public void broadcastSystemMessage(Component text, boolean bl) { + internal.getPlayerList().broadcastSystemMessage(ChatUtils.adventureToMojang(text), bl); + } + + public boolean isPlayerBanned(BridgedGameProfile profile) { + return internal.getPlayerList().getBans().isBanned(profile.toMojang()); + } + + public void whitelistPlayer(BridgedGameProfile gameProfile) { + if (!internal.getPlayerList().isUsingWhitelist()) + return; + + internal.getPlayerList().getWhiteList().add(new UserWhiteListEntry(gameProfile.toMojang())); + } + + public void unWhitelistPlayer(BridgedGameProfile gameProfile) { + if (!internal.getPlayerList().isUsingWhitelist()) + return; + + internal.getPlayerList().getWhiteList().remove(new UserWhiteListEntry(gameProfile.toMojang())); + } + + public List getPlayers() { + List profiles = new ArrayList<>(); + + if (internal.getPlayerList() == null) + return profiles; + + internal.getPlayerList().getPlayers().forEach(p -> profiles.add(BridgedPlayer.of(p))); + + return profiles; + } + + public void banPlayer(BridgedGameProfile profile) { + internal.getPlayerList().getBans().add(new UserBanListEntry(profile.toMojang())); + } + + public void executeCommand(BridgedMinecraftServer server, BridgedFakePlayer player, String command) { + internal.getCommands().performPrefixedCommand(player.toMojang(), command); + } + + public MinecraftServer toMojang() { + return internal; + } + +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/world/entity/player/BridgedPlayer.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/world/entity/player/BridgedPlayer.java new file mode 100644 index 0000000..8aa6d49 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/world/entity/player/BridgedPlayer.java @@ -0,0 +1,63 @@ +package com.hypherionmc.craterlib.nojang.world.entity.player; + +import com.hypherionmc.craterlib.nojang.authlib.BridgedGameProfile; +import com.hypherionmc.craterlib.nojang.core.BridgedBlockPos; +import com.hypherionmc.craterlib.utils.ChatUtils; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraft.world.entity.player.Player; +import org.jetbrains.annotations.Nullable; + +import java.util.UUID; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedPlayer { + + private final Player internal; + + public Component getDisplayName() { + return ChatUtils.mojangToAdventure(internal.getDisplayName()); + } + + public Component getName() { + return ChatUtils.mojangToAdventure(internal.getName()); + } + + public UUID getUUID() { + return internal.getUUID(); + } + + public String getStringUUID() { + return internal.getStringUUID(); + } + + public BridgedGameProfile getGameProfile() { + return BridgedGameProfile.of(internal.getGameProfile()); + } + + public boolean isServerPlayer() { + return internal instanceof ServerPlayer; + } + + public Player toMojang() { + return internal; + } + + public BridgedBlockPos getOnPos() { + return BridgedBlockPos.of(internal.getOnPos()); + } + + @Nullable + public ServerGamePacketListenerImpl getConnection() { + if (isServerPlayer()) { + return ((ServerPlayer) internal).connection; + } + return null; + } + + public ServerPlayer toMojangServerPlayer() { + return (ServerPlayer) internal; + } +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java new file mode 100644 index 0000000..7bbcfd1 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java @@ -0,0 +1,96 @@ +package com.hypherionmc.craterlib.utils; + +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; +import me.hypherionmc.mcdiscordformatter.discord.DiscordSerializer; +import me.hypherionmc.mcdiscordformatter.minecraft.MinecraftSerializer; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import net.kyori.adventure.text.serializer.json.JSONOptions; +import net.minecraft.ChatFormatting; +import net.minecraft.SharedConstants; +import net.minecraft.Util; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.Style; + +public class ChatUtils { + + private static final GsonComponentSerializer adventureSerializer = GsonComponentSerializer.builder().options( + JSONOptions.byDataVersion().at(SharedConstants.getCurrentVersion().getDataVersion().getVersion()) + ).build(); + + public static Component adventureToMojang(net.kyori.adventure.text.Component inComponent) { + final String serialised = adventureSerializer.serialize(inComponent); + return Component.Serializer.fromJson(serialised); + } + + public static net.kyori.adventure.text.Component mojangToAdventure(Component inComponent) { + final String serialised = Component.Serializer.toJson(inComponent); + return adventureSerializer.deserialize(serialised); + } + + // Some text components contain duplicate text, resulting in duplicate messages + // sent back to discord. This should help fix those issues + public static Component safeCopy(Component inComponent) { + String value = inComponent.getString(); + Style style = inComponent.getStyle(); + return Component.literal(value).withStyle(style); + } + + public static String strip(String inString, String... toStrip) { + String finalString = inString; + + for (String strip : toStrip) { + if (finalString.startsWith(strip)) + finalString = finalString.replaceFirst(strip, ""); + + if (finalString.startsWith(" ")) + finalString = finalString.replaceFirst(" ", ""); + } + + return finalString; + } + + public static String resolve(net.kyori.adventure.text.Component component, boolean formatted) { + Component c = adventureToMojang(component); + String returnVal = ChatFormatting.stripFormatting(c.getString()); + + if (formatted) { + returnVal = DiscordSerializer.INSTANCE.serialize(safeCopy(c).copy()); + } + + return returnVal; + } + + public static net.kyori.adventure.text.Component resolve(String component, boolean formatted) { + Component returnVal = Component.literal(component); + if (formatted) { + returnVal = MinecraftSerializer.INSTANCE.serialize(component); + } + + return mojangToAdventure(returnVal); + } + + public static net.kyori.adventure.text.Component getTooltipTitle(String key) { + return net.kyori.adventure.text.Component.text(NamedTextColor.YELLOW + net.kyori.adventure.text.Component.translatable(key).key()); + } + + public static String resolveTranslation(String key) { + return net.kyori.adventure.text.Component.translatable(key).key(); + } + + public static net.kyori.adventure.text.Component getTranslation(String key) { + return net.kyori.adventure.text.Component.translatable(key); + } + + public static net.kyori.adventure.text.Component makeComponent(String text) { + return net.kyori.adventure.text.Component.translatable(text); + } + + public static net.kyori.adventure.text.Component getBiomeName(ResourceIdentifier identifier) { + if (identifier == null) + return net.kyori.adventure.text.Component.text("Unknown"); + + return mojangToAdventure(Component.translatable(Util.makeDescriptionId("biome", identifier.toMojang()))); + } + +} diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/utils/InternalServiceUtil.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/utils/InternalServiceUtil.java new file mode 100644 index 0000000..2305d14 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/utils/InternalServiceUtil.java @@ -0,0 +1,27 @@ +package com.hypherionmc.craterlib.utils; + +import com.hypherionmc.craterlib.CraterConstants; + +import java.util.ServiceLoader; + +/** + * @author HypherionSA + * Utility class to handle SPI loading + */ +public class InternalServiceUtil { + + /** + * Try to load a service + * + * @param clazz The service class type to load + * @return The loaded class + */ + public static T load(Class clazz) { + final T loadedService = ServiceLoader.load(clazz) + .findFirst() + .orElseThrow(() -> new NullPointerException("Failed to load service for " + clazz.getName())); + CraterConstants.LOG.debug("Loaded {} for service {}", loadedService, clazz); + return loadedService; + } + +} \ No newline at end of file diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/utils/OptifineUtils.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/utils/OptifineUtils.java new file mode 100644 index 0000000..8a06da2 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/utils/OptifineUtils.java @@ -0,0 +1,46 @@ +package com.hypherionmc.craterlib.utils; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * @author HypherionSA + * Utility class for Optifine compatibility + */ +public class OptifineUtils { + + private static final boolean hasOptifine = checkOptifine(); + + private static boolean checkOptifine() { + try { + Class ofConfigClass = Class.forName("net.optifine.Config"); + return true; + } catch (ClassNotFoundException e) { + // Optifine is probably not present. Ignore the error + return false; + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } + + public static boolean isRenderRegions() { + try { + Class ofConfigClass = Class.forName("net.optifine.Config"); + Method rrField = ofConfigClass.getMethod("isRenderRegions"); + return (boolean) rrField.invoke(null); + } catch (ClassNotFoundException | InvocationTargetException | NoSuchMethodException | + IllegalAccessException e) { + // Optifine is probably not present. Ignore the error + return false; + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } + + public static boolean hasOptifine() { + return hasOptifine; + } + +} \ No newline at end of file diff --git a/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/utils/TriConsumer.java b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/utils/TriConsumer.java new file mode 100644 index 0000000..77f7309 --- /dev/null +++ b/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/utils/TriConsumer.java @@ -0,0 +1,5 @@ +package com.hypherionmc.craterlib.utils; + +public interface TriConsumer { + void accept(T t, U u, V v); +} diff --git a/1.19.2/Common/src/main/resources/assets/craterlib/lang/en_us.json b/1.19.2/Common/src/main/resources/assets/craterlib/lang/en_us.json new file mode 100644 index 0000000..03115d9 --- /dev/null +++ b/1.19.2/Common/src/main/resources/assets/craterlib/lang/en_us.json @@ -0,0 +1,8 @@ +{ + "t.clc.opensubconfig": "Open Config", + "t.clc.save": "Save", + "t.clc.cancel_discard": "Discard", + "t.clc.quit_config": "Unsaved Changes", + "t.clc.quit_config_sure": "You have unsaved config changes. Are you sure you want to discard them?", + "t.clc.quit_discard": "Quit & Discard" +} diff --git a/1.19.2/Common/src/main/resources/craterlib.mixins.json b/1.19.2/Common/src/main/resources/craterlib.mixins.json new file mode 100644 index 0000000..1a739db --- /dev/null +++ b/1.19.2/Common/src/main/resources/craterlib.mixins.json @@ -0,0 +1,24 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "com.hypherionmc.craterlib.mixin", + "compatibilityLevel": "JAVA_17", + "mixins": [ + ], + "client": [ + "ChatInputSuggestorMixin", + "events.PlayerMixin", + "events.client.ClientLevelMixin", + "events.client.MinecraftMixin", + "events.client.RealmsMainScreenMixin" + ], + "server": [ + "events.CommandMixin", + "events.PlayerAdvancementsMixin", + "events.PlayerListMixin", + "events.ServerPlayerMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/1.19.2/Common/src/main/resources/pack.mcmeta b/1.19.2/Common/src/main/resources/pack.mcmeta new file mode 100644 index 0000000..263d366 --- /dev/null +++ b/1.19.2/Common/src/main/resources/pack.mcmeta @@ -0,0 +1,6 @@ +{ + "pack": { + "description": "${mod_name}", + "pack_format": 18 + } +} diff --git a/1.19.2/Fabric/build.gradle b/1.19.2/Fabric/build.gradle new file mode 100644 index 0000000..fffa339 --- /dev/null +++ b/1.19.2/Fabric/build.gradle @@ -0,0 +1,126 @@ +archivesBaseName = "${mod_name.replace(" ", "")}-Fabric-${minecraft_version}" + +dependencies { + // Core + modImplementation "net.fabricmc.fabric-api:fabric-api:${fabric_api}" + + // Compat + modImplementation("com.terraformersmc:modmenu:${mod_menu_version}") { + exclude(group: "net.fabricmc.fabric-api") + } + + modImplementation "maven.modrinth:fabrictailor:${fabrictailor}" + modImplementation "maven.modrinth:vanish:${vanish}" + + // Do not edit or remove + implementation project(":Common") +} + +shadowJar { + from sourceSets.main.output + configurations = [project.configurations.shade] + + dependencies { + exclude(dependency('com.google.code.gson:.*')) + + relocate 'me.hypherionmc.moonconfig', 'shadow.hypherionmc.moonconfig' + relocate 'me.hypherionmc.mcdiscordformatter', 'shadow.hypherionmc.mcdiscordformatter' + relocate 'net.kyori', 'shadow.kyori' + } + + setArchiveClassifier('dev-shadow') +} + +/** + * =============================================================================== + * = DO NOT EDIT BELOW THIS LINE UNLESS YOU KNOW WHAT YOU ARE DOING = + * =============================================================================== + */ + +unimined.minecraft { + fabric { + loader fabric_loader + } +} + +remapJar { + inputFile.set shadowJar.archiveFile + dependsOn shadowJar + archiveClassifier.set null +} + +jar { + archiveClassifier.set "dev" +} + +processResources { + from project(":Common").sourceSets.main.resources + def buildProps = project.properties.clone() + + filesMatching(['fabric.mod.json']) { + expand buildProps + } +} + +compileTestJava.enabled = false + +tasks.withType(JavaCompile).configureEach { + source(project(":Common").sourceSets.main.allSource) +} + +/** + * Publishing Config + */ +publishing { + publications { + mavenJava(MavenPublication) { + artifactId project.archivesBaseName + from components.java + + artifact(remapJar) { + builtBy remapJar + } + + pom.withXml { + Node pomNode = asNode() + pomNode.dependencies.'*'.findAll() { + it.artifactId.text() == 'regutils-joined-fabric' || + it.artifactId.text() == 'core' || + it.artifactId.text() == 'toml' + }.each() { + it.parent().remove(it) + } + } + } + } + + repositories { + maven rootProject.orion.getPublishingMaven() + } +} + +publisher { + apiKeys { + modrinth(System.getenv("MODRINTH_TOKEN")) + curseforge(System.getenv("CURSE_TOKEN")) + } + + setCurseID(curse_id) + setModrinthID(modrinth_id) + setVersionType("release") + setChangelog("https://raw.githubusercontent.com/hypherionmc/changelogs/main/craterlib/changelog-fabric.md") + setProjectVersion("${minecraft_version}-${project.version}") + setDisplayName("[FABRIC/QUILT 1.19.2] CraterLib - ${project.version}") + setGameVersions("1.19.2") + setLoaders("fabric", "quilt") + setArtifact(remapJar) + setCurseEnvironment("both") + + modrinthDepends { + required("fabric-api") + } + + curseDepends { + required("fabric-api") + } +} diff --git a/1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/CraterLibInitializer.java b/1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/CraterLibInitializer.java new file mode 100644 index 0000000..c66c210 --- /dev/null +++ b/1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/CraterLibInitializer.java @@ -0,0 +1,42 @@ +package com.hypherionmc.craterlib; + +import com.hypherionmc.craterlib.api.events.server.CraterRegisterCommandEvent; +import com.hypherionmc.craterlib.api.events.server.CraterServerLifecycleEvent; +import com.hypherionmc.craterlib.common.FabricCommonPlatform; +import com.hypherionmc.craterlib.compat.Vanish; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.core.networking.CraterPacketNetwork; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.core.platform.ModloaderEnvironment; +import com.hypherionmc.craterlib.network.CraterFabricNetworkHandler; +import com.hypherionmc.craterlib.nojang.commands.CommandsRegistry; +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; + +public class CraterLibInitializer implements ModInitializer { + + @Override + public void onInitialize() { + new CraterPacketNetwork(new CraterFabricNetworkHandler(PacketSide.SERVER)); + CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> { + CraterEventBus.INSTANCE.postEvent(new CraterRegisterCommandEvent()); + CommandsRegistry.INSTANCE.registerCommands(dispatcher); + }); + + + ServerLifecycleEvents.SERVER_STARTING.register(server -> { + FabricCommonPlatform.server = server; + CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Starting(BridgedMinecraftServer.of(server))); + }); + + ServerLifecycleEvents.SERVER_STARTED.register(li -> CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Started(BridgedMinecraftServer.of(li)))); + ServerLifecycleEvents.SERVER_STOPPING.register(server -> CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Stopping(BridgedMinecraftServer.of(server)))); + ServerLifecycleEvents.SERVER_STOPPED.register(server -> CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Stopped(BridgedMinecraftServer.of(server)))); + + if (ModloaderEnvironment.INSTANCE.isModLoaded("melius-vanish")) { + Vanish.register(); + } + } +} diff --git a/1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/CraterLibModMenuIntegration.java b/1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/CraterLibModMenuIntegration.java new file mode 100644 index 0000000..6615352 --- /dev/null +++ b/1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/CraterLibModMenuIntegration.java @@ -0,0 +1,30 @@ +package com.hypherionmc.craterlib; + +import com.hypherionmc.craterlib.client.gui.config.CraterConfigScreen; +import com.hypherionmc.craterlib.core.config.ConfigController; +import com.hypherionmc.craterlib.core.config.ModuleConfig; +import com.hypherionmc.craterlib.core.config.annotations.NoConfigScreen; +import com.terraformersmc.modmenu.api.ConfigScreenFactory; +import com.terraformersmc.modmenu.api.ModMenuApi; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author HypherionSA + */ +public class CraterLibModMenuIntegration implements ModMenuApi { + + @Override + public Map> getProvidedConfigScreenFactories() { + Map> configScreens = new HashMap<>(); + + ConfigController.getMonitoredConfigs().forEach((conf, watcher) -> { + if (!conf.getClass().isAnnotationPresent(NoConfigScreen.class)) { + configScreens.put(((ModuleConfig) conf).getModId(), screen -> new CraterConfigScreen((ModuleConfig) conf, screen)); + } + }); + + return configScreens; + } +} diff --git a/1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/client/CraterLibClientInitializer.java b/1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/client/CraterLibClientInitializer.java new file mode 100644 index 0000000..b5ec5b1 --- /dev/null +++ b/1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/client/CraterLibClientInitializer.java @@ -0,0 +1,27 @@ +package com.hypherionmc.craterlib.client; + +import com.hypherionmc.craterlib.api.events.client.CraterClientTickEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.core.networking.CraterPacketNetwork; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.network.CraterFabricNetworkHandler; +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedClientLevel; +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; + +public class CraterLibClientInitializer implements ClientModInitializer { + + @Override + public void onInitializeClient() { + new CraterPacketNetwork(new CraterFabricNetworkHandler(PacketSide.CLIENT)); + ClientTickEvents.START_CLIENT_TICK.register((listener) -> { + if (listener.level == null) + return; + + CraterClientTickEvent event = new CraterClientTickEvent(BridgedClientLevel.of(listener.level)); + CraterEventBus.INSTANCE.postEvent(event); + }); + + CraterEventBus.INSTANCE.registerEventListener(CraterLibClientInitializer.class); + } +} diff --git a/1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/client/FabricClientPlatform.java b/1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/client/FabricClientPlatform.java new file mode 100644 index 0000000..7852f12 --- /dev/null +++ b/1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/client/FabricClientPlatform.java @@ -0,0 +1,34 @@ +package com.hypherionmc.craterlib.client; + +import com.hypherionmc.craterlib.core.platform.ClientPlatform; +import com.hypherionmc.craterlib.nojang.client.BridgedMinecraft; +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedClientLevel; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraft.client.Minecraft; +import net.minecraft.network.Connection; + +/** + * @author HypherionSA + */ +public class FabricClientPlatform implements ClientPlatform { + + @Override + public BridgedMinecraft getClientInstance() { + return new BridgedMinecraft(); + } + + @Override + public BridgedPlayer getClientPlayer() { + return BridgedPlayer.of(Minecraft.getInstance().player); + } + + @Override + public BridgedClientLevel getClientLevel() { + return BridgedClientLevel.of(Minecraft.getInstance().level); + } + + @Override + public Connection getClientConnection() { + return Minecraft.getInstance().getConnection().getConnection(); + } +} diff --git a/1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricCommonPlatform.java b/1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricCommonPlatform.java new file mode 100644 index 0000000..c1a30c7 --- /dev/null +++ b/1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricCommonPlatform.java @@ -0,0 +1,18 @@ +package com.hypherionmc.craterlib.common; + +import com.hypherionmc.craterlib.core.platform.CommonPlatform; +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import net.minecraft.server.MinecraftServer; + +/** + * @author HypherionSA + */ +public class FabricCommonPlatform implements CommonPlatform { + + public static MinecraftServer server; + + @Override + public BridgedMinecraftServer getMCServer() { + return BridgedMinecraftServer.of(server); + } +} diff --git a/1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricCompatHelper.java b/1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricCompatHelper.java new file mode 100644 index 0000000..dbcaf63 --- /dev/null +++ b/1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricCompatHelper.java @@ -0,0 +1,23 @@ +package com.hypherionmc.craterlib.common; + +import com.hypherionmc.craterlib.compat.FabricTailor; +import com.hypherionmc.craterlib.compat.Vanish; +import com.hypherionmc.craterlib.core.platform.CompatUtils; +import com.hypherionmc.craterlib.core.platform.ModloaderEnvironment; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; + +public class FabricCompatHelper implements CompatUtils { + + @Override + public boolean isPlayerActive(BridgedPlayer player) { + if (!ModloaderEnvironment.INSTANCE.isModLoaded("melius-vanish")) + return true; + + return Vanish.isPlayerVanished(player.toMojangServerPlayer()); + } + + @Override + public String getSkinUUID(BridgedPlayer player) { + return FabricTailor.getTailorSkin(player.toMojangServerPlayer()); + } +} diff --git a/1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricLoaderHelper.java b/1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricLoaderHelper.java new file mode 100644 index 0000000..95f5c42 --- /dev/null +++ b/1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricLoaderHelper.java @@ -0,0 +1,69 @@ +package com.hypherionmc.craterlib.common; + +import com.hypherionmc.craterlib.core.platform.Environment; +import com.hypherionmc.craterlib.core.platform.ModloaderEnvironment; +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.SharedConstants; +import net.minecraft.client.Minecraft; + +import java.io.File; + +/** + * @author HypherionSA + * @date 07/08/2022 + */ +public class FabricLoaderHelper implements ModloaderEnvironment { + + @Override + public boolean isFabric() { + return true; + } + + @Override + public String getGameVersion() { + return SharedConstants.VERSION_STRING; + } + + @Override + public File getGameFolder() { + return Minecraft.getInstance().gameDirectory; + } + + @Override + public File getConfigFolder() { + return FabricLoader.getInstance().getConfigDir().toFile(); + } + + @Override + public File getModsFolder() { + return new File(FabricLoader.getInstance().getGameDir().toString() + File.separator + "mods"); + } + + @Override + public Environment getEnvironment() { + switch (FabricLoader.getInstance().getEnvironmentType()) { + case SERVER -> { + return Environment.SERVER; + } + case CLIENT -> { + return Environment.CLIENT; + } + } + return Environment.UNKNOWN; + } + + @Override + public boolean isModLoaded(String modid) { + return FabricLoader.getInstance().isModLoaded(modid); + } + + @Override + public boolean isDevEnv() { + return FabricLoader.getInstance().isDevelopmentEnvironment(); + } + + @Override + public int getModCount() { + return FabricLoader.getInstance().getAllMods().size(); + } +} diff --git a/1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/compat/FabricTailor.java b/1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/compat/FabricTailor.java new file mode 100644 index 0000000..b95a7d7 --- /dev/null +++ b/1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/compat/FabricTailor.java @@ -0,0 +1,23 @@ +package com.hypherionmc.craterlib.compat; + +import com.hypherionmc.craterlib.core.platform.ModloaderEnvironment; +import net.minecraft.server.level.ServerPlayer; +import org.samo_lego.fabrictailor.casts.TailoredPlayer; +public class FabricTailor { + + public static String getTailorSkin(ServerPlayer player) { + if (!ModloaderEnvironment.INSTANCE.isModLoaded("fabrictailor")) + return player.getStringUUID(); + + try { + if (player instanceof TailoredPlayer tp) { + return tp.getSkinId(); + } + } catch (Exception e) { + e.printStackTrace(); + } + + return player.getStringUUID(); + } + +} \ No newline at end of file diff --git a/1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java b/1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java new file mode 100644 index 0000000..cc6c1e0 --- /dev/null +++ b/1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java @@ -0,0 +1,25 @@ +package com.hypherionmc.craterlib.compat; + +import com.hypherionmc.craterlib.api.events.server.CraterPlayerEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import me.drex.vanish.api.VanishAPI; +import me.drex.vanish.api.VanishEvents; +import net.minecraft.server.level.ServerPlayer; + +public class Vanish { + + public static void register() { + VanishEvents.VANISH_EVENT.register((serverPlayer, b) -> { + if (b) { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerEvent.PlayerLoggedOut(BridgedPlayer.of(serverPlayer))); + } else { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerEvent.PlayerLoggedIn(BridgedPlayer.of(serverPlayer))); + } + }); + } + + public static boolean isPlayerVanished(ServerPlayer player) { + return VanishAPI.isVanished(player); + } +} diff --git a/1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java b/1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java new file mode 100644 index 0000000..d5f6671 --- /dev/null +++ b/1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java @@ -0,0 +1,36 @@ +package com.hypherionmc.craterlib.mixin; + +import com.hypherionmc.craterlib.api.events.server.CraterServerChatEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.ChatUtils; +import net.minecraft.network.chat.PlayerChatMessage; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.concurrent.CompletableFuture; + +@Mixin(value = ServerGamePacketListenerImpl.class, priority = Integer.MIN_VALUE) +public class ServerGamePacketListenerImplMixin { + + @Shadow + public ServerPlayer player; + + @Inject( + method = "lambda$handleChat$9", + at = @At("HEAD"), + cancellable = true + ) + private void injectChatEvent(PlayerChatMessage arg, CallbackInfoReturnable ci) { + CraterServerChatEvent event = new CraterServerChatEvent(BridgedPlayer.of(this.player), arg.serverContent().getString(), ChatUtils.mojangToAdventure(arg.serverContent())); + CraterEventBus.INSTANCE.postEvent(event); + if (event.wasCancelled()) + ci.cancel(); + } + +} diff --git a/1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/TutorialMixin.java b/1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/TutorialMixin.java new file mode 100644 index 0000000..f9eaa50 --- /dev/null +++ b/1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/TutorialMixin.java @@ -0,0 +1,24 @@ +package com.hypherionmc.craterlib.mixin; + +import com.hypherionmc.craterlib.api.events.client.LateInitEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.client.BridgedMinecraft; +import com.hypherionmc.craterlib.nojang.client.BridgedOptions; +import net.minecraft.client.Minecraft; +import net.minecraft.client.Options; +import net.minecraft.client.tutorial.Tutorial; +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(Tutorial.class) +public class TutorialMixin { + + @Inject(method = "", at = @At("RETURN")) + private void injectEarlyInitEvent(Minecraft minecraft, Options options, CallbackInfo ci) { + LateInitEvent event = new LateInitEvent(new BridgedMinecraft(), BridgedOptions.of(options)); + CraterEventBus.INSTANCE.postEvent(event); + } + +} diff --git a/1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java b/1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java new file mode 100644 index 0000000..b3014a3 --- /dev/null +++ b/1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java @@ -0,0 +1,81 @@ +package com.hypherionmc.craterlib.network; + +import com.hypherionmc.craterlib.CraterConstants; +import com.hypherionmc.craterlib.core.networking.PacketRegistry; +import com.hypherionmc.craterlib.core.networking.data.PacketContext; +import com.hypherionmc.craterlib.core.networking.data.PacketHolder; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.nojang.network.BridgedFriendlyByteBuf; +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.minecraft.network.FriendlyByteBuf; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public class CraterFabricNetworkHandler extends PacketRegistry { + + private final Map, Message> CHANNELS = new HashMap(); + + public CraterFabricNetworkHandler(PacketSide side) { + super(side); + } + + protected void registerPacket(PacketHolder holder) { + if (CHANNELS.get(holder.messageType()) == null) { + CHANNELS.put(holder.messageType(), new Message<>(holder.type(), holder.encoder())); + + if (PacketSide.CLIENT.equals(this.side)) { + ClientPlayNetworking.registerGlobalReceiver(holder.type().toMojang(), ((client, listener, buf, responseSender) -> { + buf.readByte(); + T message = holder.decoder().apply(BridgedFriendlyByteBuf.of(buf)); + client.execute(() -> holder.handler().accept(new PacketContext<>(message, PacketSide.CLIENT))); + })); + } else { + + ServerPlayNetworking.registerGlobalReceiver(holder.type().toMojang(), ((server, player, listener, buf, responseSender) -> { + buf.readByte(); + T message = holder.decoder().apply(BridgedFriendlyByteBuf.of(buf)); + server.execute(() -> holder.handler().accept(new PacketContext<>(BridgedPlayer.of(player), message, PacketSide.SERVER))); + })); + } + + } else { + CraterConstants.LOG.error("Trying to register duplicate packet for type {}", holder.messageType()); + } + } + + public void sendToServer(T packet) { + this.sendToServer(packet, false); + } + + public void sendToServer(T packet, boolean ignoreCheck) { + Message message = (Message) CHANNELS.get(packet.getClass()); + + if (ClientPlayNetworking.canSend(message.id().toMojang()) || ignoreCheck) { + FriendlyByteBuf buf = PacketByteBufs.create(); + buf.writeByte(0); + message.encoder().accept(packet, BridgedFriendlyByteBuf.of(buf)); + ClientPlayNetworking.send(message.id().toMojang(), buf); + } + } + + public void sendToClient(T packet, BridgedPlayer player) { + Message message = (Message) CHANNELS.get(packet.getClass()); + if (ServerPlayNetworking.canSend(player.toMojangServerPlayer(), message.id().toMojang())) { + FriendlyByteBuf buf = PacketByteBufs.create(); + buf.writeByte(0); + message.encoder().accept(packet, BridgedFriendlyByteBuf.of(buf)); + ServerPlayNetworking.send(player.toMojangServerPlayer(), message.id().toMojang(), buf); + } + } + + public record Message(ResourceIdentifier id, BiConsumer encoder) { } +} diff --git a/1.19.2/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform b/1.19.2/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform new file mode 100644 index 0000000..a78d9e5 --- /dev/null +++ b/1.19.2/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform @@ -0,0 +1 @@ +com.hypherionmc.craterlib.client.FabricClientPlatform diff --git a/1.19.2/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform b/1.19.2/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform new file mode 100644 index 0000000..9a2fdb0 --- /dev/null +++ b/1.19.2/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform @@ -0,0 +1 @@ +com.hypherionmc.craterlib.common.FabricCommonPlatform diff --git a/1.19.2/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils b/1.19.2/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils new file mode 100644 index 0000000..62f79a8 --- /dev/null +++ b/1.19.2/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils @@ -0,0 +1 @@ +com.hypherionmc.craterlib.common.FabricCompatHelper \ No newline at end of file diff --git a/1.19.2/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment b/1.19.2/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment new file mode 100644 index 0000000..9a1fb33 --- /dev/null +++ b/1.19.2/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment @@ -0,0 +1 @@ +com.hypherionmc.craterlib.common.FabricLoaderHelper diff --git a/1.19.2/Fabric/src/main/resources/assets/craterlib/craterlib_logo.png b/1.19.2/Fabric/src/main/resources/assets/craterlib/craterlib_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..ce0159cc4aa4d823ea354042e531b4522d6dfead GIT binary patch literal 49343 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 z z0_4I}B3{&0Z2$_UK|l6$_F5GbB1!e;?<`^ga;fFGMz-Se^MAW>Keh~s)aWsZza-Wa zh8FX{!yiI)Ljp=gIiG3cbs*3I7#lyPS*q6+XL$;krfa|T{j!X7NO%9Jw8jjQ^oGyb zuB>(Ay2y58m-xwl&;PxSaJT2vTCB#G6Yc?K6DubK#C89Og8@02Yf7+4Vmz5*NEo{& z&>W3XcH_uv{$Z45Oo+-NT$_6P>JUFKFv(gRqcz%^lx6572_M)nTWIO~h}&HFE{_}5 z8R|z4_}lCMQBU{Gd3?a4|9JCe_ew+2=R30OVOp1-?E`3=QwNM{jwLdE9Frn+%Q*$o zY-BRKU036=wxizwcph`BWI3Q^ms~Qk*&UewhP~N@df3sHJJT~7s=j`y^C&-a*BQz{ zUfNXagXU($%NqlFZDuykU1X#WZ_ow}eCTYw!3sFi-uAbIrzSa$1$QWY3^E|wM}@Ol z4BW+h^o;(}F`L>JY_v@ev&zsG194dotf7rHJTNeAQ~pNjVns}f!B?0szcS39HePIg z_wxP*(=Xeb$9Y5fV_b73G_il zXrSUx5E;tj(;!?eRdU+o*>h&72`G1&OU4{v4 zBM69zj5>>~lqkyBnHz9%3h2rC>P0E7M#!;M%}dCZ3{P zesubLp#ul3fqk?ZFeI+q%=?jY2glsYA>B6M-r!HqjRTSV;N0e=ulsLzKQi3rop&z3 zH;OHYJqan7<>>8y!_AGE$IU@Ct?|tdVpjnV&HVtf9S8T{lr1>M%n&8bazr#QxHe;< z+0=m4Q#3_|K%w|k&#HOT3hb1$#>HuRPo&*ju2UQcx&%6aH7J-;5;RG{r)uU|2ur7R z(9>i-B`X<0L0r?4>o&#P zAUra7SR&%Gp~?Ft&+{F)fCUjY&8x$@3;BPe^LC^=tG9csyL&Aa9?S$4uT%ye+iIRG zmNV!U42y%)FSuyl>HuNdKC#D;*w?j32J-D4$sK;bGQ_{n;$Fc3>RZO30i&ui2pxGX zWkF_hWr~P{+z+q`$HJnXo|lHlIC%I%qQ&U2@8ndyUDf(v9?@e&A!yQ(7_6Go$Q)CRNo z9}@w|%_mrl+}ioP`BSJ;eDly8`El>9JJbC6Oal#U0*u;%KWdR%aLwqIspxB2oF^oY z#<*A`D{k!|yW)obE;hb%qmIC^-Dk{*)?Cv2*^%OwFfD01CKw_Ee{_r9cIXAJ{p4oSiu;rX`J@;Ts29_--I|12eLb5ozFTzJf7R(FQjkK+59+`Nyxt6XV$p zS+d9cnNUyQ2jY6rjJQ~!HTUrn)&T}(-OnA^@3h@jP8_D{6MOm!f8b#LzJSUAKT4)ha9-WL$F&}@MME_!hG1YG)Ju~6e<7)tbYYw$a}uw3feLj zHx_9d#XnnHdHyK@3==#-i3G3?7`F&F6jg!m%^~WgRIMGJz+9SqhCy{7^V?Zr5lrqe zE*E2krwZF1&g7r>TV#?~ynBjY*@dP1OJ24(I#3nde2E^Mf%S~Ot|VwY0TP)j#oS7j zJ+LfR;dC>Z-J!ngPS)g53T^@_Ggh}qvR-&jy(V^V1`b@pJwKryM}3{+h3=_~c}w-s zmrqf)%trzjXpQ5odUbjqhSM?c7sX1S+g|kKjAvxwXnt!vu_25Ll$(I_R&0kiUK{sw zYkr`_S;cu^>MG=3rMC5aD#ocTL)YtiF6f6-DAu!`P4ub4xi}K95yQJB+#^=6CELF} ze9n~5+AX3lb{@H9HcrSnT=7O@5qQoV^BU}f=7Rso;Tf6G-plzq<9#hMqe1%4hm^i> zVpMoQ^_-5vyM`IDof|_87+jQos8s?(c{fI+|D@|s!e0V!R$cEn@aOXb^?;k;Up-_0j z%+y{j(M4Fvxxa1?rq>gMJ0lo zZnLCaDlCc)wV-hO*vb)z z{Aw?4m?<_im&T4DuQQ{yl(LQr;7FI;F+?)cr@tNK5!uHuIS#OwJs!X&AOamCp!T^2 zwMGCc^c<0U3Ak%R`5E2&)DL^W^dCT1G7Laq0>FM7aIJI_MrRVh2$CsG0o|WQChE5s z>=MUftW>@RRD6e#X1N}_BMO(5ZvRQ6OL_#W04zkzODy==r&0E(3?1sjUueloD8S1- zEgm|ZRWJ@!$e@gCCe${7>Y|-=NEBmizm979@!9Ad*s1B#LL46QzEXvLo6!uSqjmfC zOy~kNtQDqafvAWRo1CKk>`?6$S=qeFKpJuj#dJ_6C?_LhA-ynR_$GRnfPjkd^-B)8QTHjsGseoBoPtJi@JAII=+z1cLU5~kLwm4O(A@$K=lt|$zg*|K@{&AJBfwIZufXlAxV`NbN?8G()+jo^HH;bmZ8&+Y zpJ#+AQ2_9UcSpiH&+I?leumlSZf|B(KkK(?x?AomuAN9E^j;K}wo7KI4imBGRfEIh zaReC3hk_>9cL>SyO_2w2_ky3QrUgS4!1z%)-)f}NN43xA#Vh2fTYq8hTEIVLfbHSbf5$-m-ZJ_eqw>#~Wu z35oW!EjG6()?RI7vKVp z5Yz(N2`~JU%M?_8ge74Q3b{FNgqWYQ)PK|E`X`Timke_g`^Q$up|U7xVOrfkoFj!& z1DK>s^3`b%`k3>e-|{W|%@edE3^Siq?f-r251ZFBFvZ0SpB4FI zgxx#|VN}}j-XHH*he9>ps#>*sx!X!`U$z?VW@_G6PQA zu_BxYUmKTq;!A+57$;K#6&udHYnG%ozi>!3Uk#J-YNq5W2xJ7Y*r^SJ6r zNM#6vzkB(7`H(|)B`Bsr+TmUnj2$z$_JCtD>ou+9exFOER_$34Yu4cp^y%RaeEOT0 z=KUqZH?F1V;ioU(qV1c1lK1!b#Rp*f(mT>MT2ud4msbpL0#DHNhCjjli@Xxjb2fyJ zH;AV-lFai-<;aLb;)wUT{|+Zincr~E<3L-|z2Kr%0pV5p+_BJ$eCFnLYbsAK#GASQ zDf;&|uLRABEj?0;&tEOv!l$~^F8-DtZ%&7|QbZ-2_otcNHj<$2OiM~d*p;r!4wa!I zl*1>R|GkT6$A{u>kTwL%G4gH{!voK=8mTu=Lnt)m9Yn85sJGF|=CIx;gRiOIOQ_4I zQ&v2tZxVx#`9wR=`_OOy@iE$=FL9_U&RvCxm1yW;&x`xL_cT*Fx@DDMsMa|*oRW4<`TU7L}1 zd87?L{p)sZ`1{=FExUhpSML>rymDP!5c_ZVa(HQvz`m@lzjc`fLGzg4x>m#A_k(NS z1+jYqs2AL{&sB*r;UttXfU2@q{5p>SEIi@m=oS}V>y_O;qs}Qh>XV2^*ABoF9}+Wc z=|NSe9)9$WoURbZX`x@X|K9aOtQ}sUraok~lp0o0a-HT~Pw2Q$U;L>k;C-HsK(1Hjg#g}mYiI6?|ht{qKC6VnHn zf4BKoz8Gk{bbuzk(i9M>S-|ZfOx>eV>aD05kSy67-t0uhyTed4$kNo%uu8r))?6vW;lWz@S%Ih#S|?0?;b^qdqdP}t0NI=@b<07 zVUB~e{wkMSPdU6@Mjh`J=(4*H^kIobA0g~Fpw2t^cF}DLm!e2%N?DzF*u;WJ^+Er! zegF>;Cdaiu4yxYROZ%$?U;hGws-mhoyc?d<*po)F&j=CSw13Fq5O$P#AQ5?Cduj__ zgC*q}hbrFvkU@D{Ma(c{s?1itmC45RW6p4`NYNgC%}kuhIlJShXnjjeS5wUl$2%NA zTJn{xl?hEdf%=9Y^O(GO(;k8Y9D+iZ*GI_F5fM~ydu*%bm7~)Jya?!O8EBTPJB0rq De~rmR literal 0 HcmV?d00001 diff --git a/1.19.2/Fabric/src/main/resources/craterlib.fabric.mixins.json b/1.19.2/Fabric/src/main/resources/craterlib.fabric.mixins.json new file mode 100644 index 0000000..7c59043 --- /dev/null +++ b/1.19.2/Fabric/src/main/resources/craterlib.fabric.mixins.json @@ -0,0 +1,17 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "com.hypherionmc.craterlib.mixin", + "compatibilityLevel": "JAVA_17", + "mixins": [ + ], + "client": [ + "TutorialMixin" + ], + "server": [ + "ServerGamePacketListenerImplMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/1.19.2/Fabric/src/main/resources/fabric.mod.json b/1.19.2/Fabric/src/main/resources/fabric.mod.json new file mode 100644 index 0000000..5c4c727 --- /dev/null +++ b/1.19.2/Fabric/src/main/resources/fabric.mod.json @@ -0,0 +1,39 @@ +{ + "schemaVersion": 1, + "id": "${mod_id}", + "version": "${version}", + "name": "${mod_name}", + "description": "A library mod used by First Dark Development and HypherionSA Mods", + "authors": [ + "${mod_author}", + "Misha" + ], + "contact": { + "homepage": "https://modrinth.com/mod/craterlib", + "sources": "https://github.com/firstdarkdev/craterLib/" + }, + "license": "MIT", + "icon": "assets/craterlib/craterlib_logo.png", + "environment": "*", + "entrypoints": { + "main": [ + "com.hypherionmc.craterlib.CraterLibInitializer" + ], + "client": [ + "com.hypherionmc.craterlib.client.CraterLibClientInitializer" + ], + "modmenu": [ + "com.hypherionmc.craterlib.CraterLibModMenuIntegration" + ] + }, + "mixins": [ + "${mod_id}.mixins.json", + "${mod_id}.fabric.mixins.json" + ], + "depends": { + "fabricloader": ">=0.15.0", + "fabric-api": "*", + "minecraft": "1.19.2", + "java": ">=17" + } +} diff --git a/1.19.2/Forge/build.gradle b/1.19.2/Forge/build.gradle new file mode 100644 index 0000000..c072955 --- /dev/null +++ b/1.19.2/Forge/build.gradle @@ -0,0 +1,112 @@ +// Adjust the output jar name here +archivesBaseName = "${mod_name.replace(" ", "")}-Forge-${minecraft_version}" + +dependencies { + // Compat + modImplementation("maven.modrinth:vanishmod:${vanishmod}") + + // Do not edit or remove + implementation project(":Common") +} + +shadowJar { + from sourceSets.main.output + configurations = [project.configurations.shade] + + dependencies { + exclude(dependency('com.google.code.gson:.*')) + + relocate 'me.hypherionmc.moonconfig', 'shadow.hypherionmc.moonconfig' + relocate 'me.hypherionmc.mcdiscordformatter', 'shadow.hypherionmc.mcdiscordformatter' + relocate 'net.kyori', 'shadow.kyori' + } + + setArchiveClassifier('dev-shadow') +} + +/** + * =============================================================================== + * = DO NOT EDIT BELOW THIS LINE UNLESS YOU KNOW WHAT YOU ARE DOING = + * =============================================================================== + */ + +unimined.minecraft { + minecraftForge { + loader forge_version + mixinConfig("${mod_id}.mixins.json", "${mod_id}.forge.mixins.json") + } +} + +remapJar { + inputFile.set shadowJar.archiveFile + dependsOn shadowJar + archiveClassifier.set null +} + +jar { + archiveClassifier.set "dev" +} + +processResources { + from project(":Common").sourceSets.main.resources + def buildProps = project.properties.clone() + + filesMatching("META-INF/mods.toml") { + expand buildProps + } +} + +compileTestJava.enabled = false + +tasks.withType(JavaCompile).configureEach { + source(project(":Common").sourceSets.main.allSource) +} + +/** + * Publishing Config + */ +publishing { + publications { + mavenJava(MavenPublication) { + artifactId project.archivesBaseName + from components.java + + artifact(remapJar) { + builtBy remapJar + } + + pom.withXml { + Node pomNode = asNode() + pomNode.dependencies.'*'.findAll() { + it.artifactId.text() == 'regutils-joined-fabric' || + it.artifactId.text() == 'core' || + it.artifactId.text() == 'toml' + }.each() { + it.parent().remove(it) + } + } + } + } + + repositories { + maven rootProject.orion.getPublishingMaven() + } +} + +publisher { + apiKeys { + modrinth(System.getenv("MODRINTH_TOKEN")) + curseforge(System.getenv("CURSE_TOKEN")) + } + + setCurseID(curse_id) + setModrinthID(modrinth_id) + setVersionType("release") + setChangelog("https://raw.githubusercontent.com/hypherionmc/changelogs/main/craterlib/changelog-forge.md") + setProjectVersion("${minecraft_version}-${project.version}") + setDisplayName("[Forge 1.19.2] CraterLib - ${project.version}") + setGameVersions("1.19.2") + setLoaders("forge") + setArtifact(remapJar) + setCurseEnvironment("both") +} diff --git a/1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java b/1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java new file mode 100644 index 0000000..968da05 --- /dev/null +++ b/1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java @@ -0,0 +1,41 @@ +package com.hypherionmc.craterlib; + +import com.hypherionmc.craterlib.api.events.client.LateInitEvent; +import com.hypherionmc.craterlib.common.ForgeServerEvents; +import com.hypherionmc.craterlib.compat.Vanish; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.core.networking.CraterPacketNetwork; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.core.platform.ModloaderEnvironment; +import com.hypherionmc.craterlib.network.CraterForgeNetworkHandler; +import com.hypherionmc.craterlib.nojang.client.BridgedMinecraft; +import com.hypherionmc.craterlib.nojang.client.BridgedOptions; +import net.minecraft.client.Minecraft; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.DistExecutor; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import net.minecraftforge.fml.loading.FMLLoader; + +@Mod(CraterConstants.MOD_ID) +public class CraterLib { + + public CraterLib() { + MinecraftForge.EVENT_BUS.register(new ForgeServerEvents()); + FMLJavaModLoadingContext.get().getModEventBus().addListener(this::commonSetup); + } + + public void commonSetup(FMLCommonSetupEvent evt) { + new CraterPacketNetwork(new CraterForgeNetworkHandler(FMLLoader.getDist().isClient() ? PacketSide.CLIENT : PacketSide.SERVER)); + DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> { + LateInitEvent event = new LateInitEvent(new BridgedMinecraft(), BridgedOptions.of(Minecraft.getInstance().options)); + CraterEventBus.INSTANCE.postEvent(event); + }); + + if (ModloaderEnvironment.INSTANCE.isModLoaded("vmod")) { + MinecraftForge.EVENT_BUS.register(new Vanish()); + } + } +} diff --git a/1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/client/ForgeClientEvents.java b/1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/client/ForgeClientEvents.java new file mode 100644 index 0000000..cb096d5 --- /dev/null +++ b/1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/client/ForgeClientEvents.java @@ -0,0 +1,25 @@ +package com.hypherionmc.craterlib.client; + +import com.hypherionmc.craterlib.CraterConstants; +import com.hypherionmc.craterlib.api.events.client.CraterClientTickEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedClientLevel; +import net.minecraft.client.Minecraft; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.event.TickEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; + +@Mod.EventBusSubscriber(modid = CraterConstants.MOD_ID, bus = Mod.EventBusSubscriber.Bus.FORGE, value = Dist.CLIENT) +public class ForgeClientEvents { + + @SubscribeEvent + public static void clientTick(TickEvent.LevelTickEvent event) { + if (Minecraft.getInstance().level == null) + return; + + CraterClientTickEvent craterClientTickEvent = new CraterClientTickEvent(BridgedClientLevel.of(Minecraft.getInstance().level)); + CraterEventBus.INSTANCE.postEvent(craterClientTickEvent); + } + +} diff --git a/1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/client/ForgeClientHelper.java b/1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/client/ForgeClientHelper.java new file mode 100644 index 0000000..6776622 --- /dev/null +++ b/1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/client/ForgeClientHelper.java @@ -0,0 +1,41 @@ +package com.hypherionmc.craterlib.client; + +import com.hypherionmc.craterlib.core.platform.ClientPlatform; +import com.hypherionmc.craterlib.nojang.client.BridgedMinecraft; +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedClientLevel; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraft.client.Minecraft; +import net.minecraft.network.Connection; + +import java.util.Objects; + +/** + * @author HypherionSA + * @date 16/06/2022 + */ +public class ForgeClientHelper implements ClientPlatform { + + public ForgeClientHelper() { + } + + @Override + public BridgedMinecraft getClientInstance() { + return new BridgedMinecraft(); + } + + @Override + public BridgedPlayer getClientPlayer() { + return BridgedPlayer.of(Minecraft.getInstance().player); + } + + @Override + public BridgedClientLevel getClientLevel() { + return BridgedClientLevel.of(Minecraft.getInstance().level); + } + + @Override + public Connection getClientConnection() { + Objects.requireNonNull(Minecraft.getInstance().getConnection(), "Cannot send packets when not in game!"); + return Minecraft.getInstance().getConnection().getConnection(); + } +} diff --git a/1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeCommonHelper.java b/1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeCommonHelper.java new file mode 100644 index 0000000..281ddd7 --- /dev/null +++ b/1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeCommonHelper.java @@ -0,0 +1,26 @@ +package com.hypherionmc.craterlib.common; + +import com.hypherionmc.craterlib.core.platform.CommonPlatform; +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.CreativeModeTab; +import net.minecraftforge.server.ServerLifecycleHooks; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author HypherionSA + */ +public class ForgeCommonHelper implements CommonPlatform { + + public static Map TABS = new HashMap<>(); + + public ForgeCommonHelper() { + } + + @Override + public BridgedMinecraftServer getMCServer() { + return BridgedMinecraftServer.of(ServerLifecycleHooks.getCurrentServer()); + } +} diff --git a/1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeCompatHelper.java b/1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeCompatHelper.java new file mode 100644 index 0000000..f325837 --- /dev/null +++ b/1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeCompatHelper.java @@ -0,0 +1,17 @@ +package com.hypherionmc.craterlib.common; + +import com.hypherionmc.craterlib.core.platform.CompatUtils; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; + +public class ForgeCompatHelper implements CompatUtils { + + @Override + public boolean isPlayerActive(BridgedPlayer player) { + return true; + } + + @Override + public String getSkinUUID(BridgedPlayer player) { + return player.getStringUUID(); + } +} diff --git a/1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeLoaderHelper.java b/1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeLoaderHelper.java new file mode 100644 index 0000000..2be70fc --- /dev/null +++ b/1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeLoaderHelper.java @@ -0,0 +1,73 @@ +package com.hypherionmc.craterlib.common; + +import com.hypherionmc.craterlib.core.platform.Environment; +import com.hypherionmc.craterlib.core.platform.ModloaderEnvironment; +import net.minecraft.SharedConstants; +import net.minecraft.client.Minecraft; +import net.minecraftforge.fml.ModList; +import net.minecraftforge.fml.loading.FMLLoader; +import net.minecraftforge.fml.loading.FMLPaths; + +import java.io.File; + +/** + * @author HypherionSA + */ +public class ForgeLoaderHelper implements ModloaderEnvironment { + + public ForgeLoaderHelper() { + } + + @Override + public boolean isFabric() { + return false; + } + + @Override + public String getGameVersion() { + return SharedConstants.VERSION_STRING; + } + + @Override + public File getGameFolder() { + return Minecraft.getInstance().gameDirectory; + } + + @Override + public File getConfigFolder() { + return FMLPaths.CONFIGDIR.get().toFile(); + } + + @Override + public File getModsFolder() { + return FMLPaths.MODSDIR.get().toFile(); + } + + @Override + public Environment getEnvironment() { + switch (FMLLoader.getDist()) { + case CLIENT -> { + return Environment.CLIENT; + } + case DEDICATED_SERVER -> { + return Environment.SERVER; + } + } + return Environment.UNKNOWN; + } + + @Override + public boolean isModLoaded(String modid) { + return ModList.get().isLoaded(modid); + } + + @Override + public boolean isDevEnv() { + return !FMLLoader.isProduction(); + } + + @Override + public int getModCount() { + return ModList.get().size(); + } +} diff --git a/1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeServerEvents.java b/1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeServerEvents.java new file mode 100644 index 0000000..f354ddc --- /dev/null +++ b/1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeServerEvents.java @@ -0,0 +1,43 @@ +package com.hypherionmc.craterlib.common; + +import com.hypherionmc.craterlib.api.events.server.CraterRegisterCommandEvent; +import com.hypherionmc.craterlib.api.events.server.CraterServerLifecycleEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.commands.CommandsRegistry; +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import net.minecraftforge.event.RegisterCommandsEvent; +import net.minecraftforge.event.server.ServerStartedEvent; +import net.minecraftforge.event.server.ServerStartingEvent; +import net.minecraftforge.event.server.ServerStoppedEvent; +import net.minecraftforge.event.server.ServerStoppingEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; + +public class ForgeServerEvents { + + @SubscribeEvent + public void serverStarting(ServerStartingEvent event) { + CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Starting(BridgedMinecraftServer.of(event.getServer()))); + } + + @SubscribeEvent + public void serverStarted(ServerStartedEvent event) { + CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Started(BridgedMinecraftServer.of(event.getServer()))); + } + + @SubscribeEvent + public void serverStopping(ServerStoppingEvent event) { + CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Stopping(BridgedMinecraftServer.of(event.getServer()))); + } + + @SubscribeEvent + public void serverStopped(ServerStoppedEvent event) { + CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Stopped(BridgedMinecraftServer.of(event.getServer()))); + } + + @SubscribeEvent + public void onCommandRegister(RegisterCommandsEvent event) { + CraterEventBus.INSTANCE.postEvent(new CraterRegisterCommandEvent()); + CommandsRegistry.INSTANCE.registerCommands(event.getDispatcher()); + } + +} diff --git a/1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java b/1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java new file mode 100644 index 0000000..09bb181 --- /dev/null +++ b/1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java @@ -0,0 +1,24 @@ +package com.hypherionmc.craterlib.compat; + +import com.hypherionmc.craterlib.api.events.server.CraterPlayerEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import redstonedubstep.mods.vanishmod.api.PlayerVanishEvent; + +public class Vanish { + + public Vanish() { + + } + + @SubscribeEvent + public void vanishevent(PlayerVanishEvent event) { + if (event.isVanished()) { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerEvent.PlayerLoggedOut(BridgedPlayer.of(event.getEntity()))); + } else { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerEvent.PlayerLoggedIn(BridgedPlayer.of(event.getEntity()))); + } + } + +} \ No newline at end of file diff --git a/1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ConfigScreenHandlerMixin.java b/1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ConfigScreenHandlerMixin.java new file mode 100644 index 0000000..927bd23 --- /dev/null +++ b/1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ConfigScreenHandlerMixin.java @@ -0,0 +1,43 @@ +package com.hypherionmc.craterlib.mixin; + +import com.hypherionmc.craterlib.client.gui.config.CraterConfigScreen; +import com.hypherionmc.craterlib.core.config.ConfigController; +import com.hypherionmc.craterlib.core.config.ModuleConfig; +import com.hypherionmc.craterlib.core.config.annotations.NoConfigScreen; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.Screen; +import net.minecraftforge.client.ConfigScreenHandler; +import net.minecraftforge.forgespi.language.IModInfo; +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.CallbackInfoReturnable; + +import java.util.Optional; +import java.util.function.BiFunction; + +/** + * @author HypherionSA + */ +@Mixin(ConfigScreenHandler.class) +public class ConfigScreenHandlerMixin { + + /** + * Inject Auto Generated config Screens into forge + * + */ + @Inject(at = @At("RETURN"), method = "getScreenFactoryFor", cancellable = true, remap = false) + private static void injectConfigScreen(IModInfo selectedMod, CallbackInfoReturnable>> cir) { + ConfigController.getMonitoredConfigs().forEach((conf, watcher) -> { + if (!conf.getClass().isAnnotationPresent(NoConfigScreen.class)) { + ModuleConfig config = (ModuleConfig) conf; + if (config.getModId().equals(selectedMod.getModId())) { + cir.setReturnValue( + Optional.of((minecraft, screen) -> new CraterConfigScreen(config, screen)) + ); + } + } + }); + } + +} diff --git a/1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java b/1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java new file mode 100644 index 0000000..7bfc659 --- /dev/null +++ b/1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java @@ -0,0 +1,36 @@ +package com.hypherionmc.craterlib.mixin; + +import com.hypherionmc.craterlib.api.events.server.CraterServerChatEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.ChatUtils; +import net.minecraft.network.chat.PlayerChatMessage; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.concurrent.CompletableFuture; + +@Mixin(value = ServerGamePacketListenerImpl.class, priority = Integer.MIN_VALUE) +public class ServerGamePacketListenerImplMixin { + + @Shadow + public ServerPlayer player; + + @Inject( + method = "lambda$handleChat$10", + at = @At("HEAD"), + cancellable = true + ) + private void injectChatEvent(PlayerChatMessage arg, CallbackInfoReturnable ci) { + CraterServerChatEvent event = new CraterServerChatEvent(BridgedPlayer.of(this.player), arg.serverContent().getString(), ChatUtils.mojangToAdventure(arg.serverContent())); + CraterEventBus.INSTANCE.postEvent(event); + if (event.wasCancelled()) + ci.cancel(); + } + +} diff --git a/1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/network/CraterForgeNetworkHandler.java b/1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/network/CraterForgeNetworkHandler.java new file mode 100644 index 0000000..5b54d5d --- /dev/null +++ b/1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/network/CraterForgeNetworkHandler.java @@ -0,0 +1,100 @@ +package com.hypherionmc.craterlib.network; + +import com.hypherionmc.craterlib.CraterConstants; +import com.hypherionmc.craterlib.core.networking.PacketRegistry; +import com.hypherionmc.craterlib.core.networking.data.PacketContext; +import com.hypherionmc.craterlib.core.networking.data.PacketHolder; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.nojang.network.BridgedFriendlyByteBuf; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraft.client.Minecraft; +import net.minecraft.network.Connection; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraftforge.network.NetworkDirection; +import net.minecraftforge.network.NetworkEvent; +import net.minecraftforge.network.NetworkRegistry; +import net.minecraftforge.network.simple.SimpleChannel; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public class CraterForgeNetworkHandler extends PacketRegistry { + private final Map, SimpleChannel> CHANNELS = new HashMap<>(); + + public CraterForgeNetworkHandler(PacketSide side) { + super(side); + } + + protected void registerPacket(PacketHolder holder) { + if (CHANNELS.get(holder.messageType()) == null) { + SimpleChannel channel = NetworkRegistry.ChannelBuilder + .named(holder.type().toMojang()) + .clientAcceptedVersions((a) -> true) + .serverAcceptedVersions((a) -> true) + .networkProtocolVersion(() -> "1") + .simpleChannel(); + + channel.registerMessage( + 0, + holder.messageType(), + mojangEncoder(holder.encoder()), + mojangDecoder(holder.decoder()), + buildHandler(holder.handler())); + + CHANNELS.put(holder.messageType(), channel); + } else { + CraterConstants.LOG.error("Trying to register duplicate packet for type {}", holder.messageType()); + } + } + + public void sendToServer(T packet) { + this.sendToServer(packet, false); + } + + public void sendToServer(T packet, boolean ignoreCheck) { + SimpleChannel channel = CHANNELS.get(packet.getClass()); + Connection connection = Minecraft.getInstance().getConnection().getConnection(); + if (channel.isRemotePresent(connection) || ignoreCheck) { + channel.sendToServer(packet); + } + } + + public void sendToClient(T packet, BridgedPlayer player) { + SimpleChannel channel = CHANNELS.get(packet.getClass()); + ServerGamePacketListenerImpl connection = player.getConnection(); + if (connection == null) + return; + + if (channel.isRemotePresent(connection.connection)) { + channel.sendTo(packet, player.getConnection().connection, NetworkDirection.PLAY_TO_CLIENT); + } + } + + private Function mojangDecoder(Function handler) { + return byteBuf -> handler.apply(BridgedFriendlyByteBuf.of(byteBuf)); + } + + private BiConsumer mojangEncoder(BiConsumer handler) { + return ((t, byteBuf) -> handler.accept(t, BridgedFriendlyByteBuf.of(byteBuf))); + } + + private BiConsumer> buildHandler(Consumer> handler) { + return (message, ctx) -> { + ctx.get().enqueueWork(() -> { + PacketSide side = ctx.get().getDirection().getReceptionSide().isServer() ? PacketSide.SERVER : PacketSide.CLIENT; + ServerPlayer player = ctx.get().getSender(); + handler.accept(new PacketContext<>(BridgedPlayer.of(player), message, side)); + }); + ctx.get().setPacketHandled(true); + }; + } +} diff --git a/1.19.2/Forge/src/main/resources/META-INF/mods.toml b/1.19.2/Forge/src/main/resources/META-INF/mods.toml new file mode 100644 index 0000000..c21548f --- /dev/null +++ b/1.19.2/Forge/src/main/resources/META-INF/mods.toml @@ -0,0 +1,31 @@ +modLoader = "javafml" +loaderVersion = "[43,)" +license = "MIT" +issueTrackerURL = "https://github.com/firstdarkdev/craterLib/issues" + +[[mods]] + modId = "${mod_id}" + version = "${version}" + displayName = "${mod_name}" + displayURL = "https://modrinth.com/mod/craterlib" + logoFile = "craterlib_logo.png" + #credits="Thanks for this example mod goes to Java" + authors = "${mod_author}, Zenith" + description = ''' + A library mod used by First Dark Development and HypherionSA Mods + ''' + displayTest = "NONE" + +[[dependencies.${ mod_id }]] + modId = "forge" + mandatory = true + versionRange = "[43,)" + ordering = "NONE" + side = "BOTH" + +[[dependencies.${ mod_id }]] + modId = "minecraft" + mandatory = true + versionRange = "[1.19.2,1.19.3)" + ordering = "NONE" + side = "BOTH" diff --git a/1.19.2/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform b/1.19.2/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform new file mode 100644 index 0000000..a12ad8c --- /dev/null +++ b/1.19.2/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform @@ -0,0 +1 @@ +com.hypherionmc.craterlib.client.ForgeClientHelper \ No newline at end of file diff --git a/1.19.2/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform b/1.19.2/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform new file mode 100644 index 0000000..09e119f --- /dev/null +++ b/1.19.2/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform @@ -0,0 +1 @@ +com.hypherionmc.craterlib.common.ForgeCommonHelper \ No newline at end of file diff --git a/1.19.2/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils b/1.19.2/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils new file mode 100644 index 0000000..a9f823d --- /dev/null +++ b/1.19.2/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils @@ -0,0 +1 @@ +com.hypherionmc.craterlib.common.ForgeCompatHelper \ No newline at end of file diff --git a/1.19.2/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment b/1.19.2/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment new file mode 100644 index 0000000..02b4e07 --- /dev/null +++ b/1.19.2/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment @@ -0,0 +1 @@ +com.hypherionmc.craterlib.common.ForgeLoaderHelper \ No newline at end of file diff --git a/1.19.2/Forge/src/main/resources/craterlib.forge.mixins.json b/1.19.2/Forge/src/main/resources/craterlib.forge.mixins.json new file mode 100644 index 0000000..aa072d1 --- /dev/null +++ b/1.19.2/Forge/src/main/resources/craterlib.forge.mixins.json @@ -0,0 +1,17 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "com.hypherionmc.craterlib.mixin", + "compatibilityLevel": "JAVA_17", + "mixins": [ + ], + "client": [ + "ConfigScreenHandlerMixin" + ], + "server": [ + "ServerGamePacketListenerImplMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/1.19.2/Forge/src/main/resources/craterlib_logo.png b/1.19.2/Forge/src/main/resources/craterlib_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..ce0159cc4aa4d823ea354042e531b4522d6dfead GIT binary patch literal 49343 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 z z0_4I}B3{&0Z2$_UK|l6$_F5GbB1!e;?<`^ga;fFGMz-Se^MAW>Keh~s)aWsZza-Wa zh8FX{!yiI)Ljp=gIiG3cbs*3I7#lyPS*q6+XL$;krfa|T{j!X7NO%9Jw8jjQ^oGyb zuB>(Ay2y58m-xwl&;PxSaJT2vTCB#G6Yc?K6DubK#C89Og8@02Yf7+4Vmz5*NEo{& z&>W3XcH_uv{$Z45Oo+-NT$_6P>JUFKFv(gRqcz%^lx6572_M)nTWIO~h}&HFE{_}5 z8R|z4_}lCMQBU{Gd3?a4|9JCe_ew+2=R30OVOp1-?E`3=QwNM{jwLdE9Frn+%Q*$o zY-BRKU036=wxizwcph`BWI3Q^ms~Qk*&UewhP~N@df3sHJJT~7s=j`y^C&-a*BQz{ zUfNXagXU($%NqlFZDuykU1X#WZ_ow}eCTYw!3sFi-uAbIrzSa$1$QWY3^E|wM}@Ol z4BW+h^o;(}F`L>JY_v@ev&zsG194dotf7rHJTNeAQ~pNjVns}f!B?0szcS39HePIg z_wxP*(=Xeb$9Y5fV_b73G_il zXrSUx5E;tj(;!?eRdU+o*>h&72`G1&OU4{v4 zBM69zj5>>~lqkyBnHz9%3h2rC>P0E7M#!;M%}dCZ3{P zesubLp#ul3fqk?ZFeI+q%=?jY2glsYA>B6M-r!HqjRTSV;N0e=ulsLzKQi3rop&z3 zH;OHYJqan7<>>8y!_AGE$IU@Ct?|tdVpjnV&HVtf9S8T{lr1>M%n&8bazr#QxHe;< z+0=m4Q#3_|K%w|k&#HOT3hb1$#>HuRPo&*ju2UQcx&%6aH7J-;5;RG{r)uU|2ur7R z(9>i-B`X<0L0r?4>o&#P zAUra7SR&%Gp~?Ft&+{F)fCUjY&8x$@3;BPe^LC^=tG9csyL&Aa9?S$4uT%ye+iIRG zmNV!U42y%)FSuyl>HuNdKC#D;*w?j32J-D4$sK;bGQ_{n;$Fc3>RZO30i&ui2pxGX zWkF_hWr~P{+z+q`$HJnXo|lHlIC%I%qQ&U2@8ndyUDf(v9?@e&A!yQ(7_6Go$Q)CRNo z9}@w|%_mrl+}ioP`BSJ;eDly8`El>9JJbC6Oal#U0*u;%KWdR%aLwqIspxB2oF^oY z#<*A`D{k!|yW)obE;hb%qmIC^-Dk{*)?Cv2*^%OwFfD01CKw_Ee{_r9cIXAJ{p4oSiu;rX`J@;Ts29_--I|12eLb5ozFTzJf7R(FQjkK+59+`Nyxt6XV$p zS+d9cnNUyQ2jY6rjJQ~!HTUrn)&T}(-OnA^@3h@jP8_D{6MOm!f8b#LzJSUAKT4)ha9-WL$F&}@MME_!hG1YG)Ju~6e<7)tbYYw$a}uw3feLj zHx_9d#XnnHdHyK@3==#-i3G3?7`F&F6jg!m%^~WgRIMGJz+9SqhCy{7^V?Zr5lrqe zE*E2krwZF1&g7r>TV#?~ynBjY*@dP1OJ24(I#3nde2E^Mf%S~Ot|VwY0TP)j#oS7j zJ+LfR;dC>Z-J!ngPS)g53T^@_Ggh}qvR-&jy(V^V1`b@pJwKryM}3{+h3=_~c}w-s zmrqf)%trzjXpQ5odUbjqhSM?c7sX1S+g|kKjAvxwXnt!vu_25Ll$(I_R&0kiUK{sw zYkr`_S;cu^>MG=3rMC5aD#ocTL)YtiF6f6-DAu!`P4ub4xi}K95yQJB+#^=6CELF} ze9n~5+AX3lb{@H9HcrSnT=7O@5qQoV^BU}f=7Rso;Tf6G-plzq<9#hMqe1%4hm^i> zVpMoQ^_-5vyM`IDof|_87+jQos8s?(c{fI+|D@|s!e0V!R$cEn@aOXb^?;k;Up-_0j z%+y{j(M4Fvxxa1?rq>gMJ0lo zZnLCaDlCc)wV-hO*vb)z z{Aw?4m?<_im&T4DuQQ{yl(LQr;7FI;F+?)cr@tNK5!uHuIS#OwJs!X&AOamCp!T^2 zwMGCc^c<0U3Ak%R`5E2&)DL^W^dCT1G7Laq0>FM7aIJI_MrRVh2$CsG0o|WQChE5s z>=MUftW>@RRD6e#X1N}_BMO(5ZvRQ6OL_#W04zkzODy==r&0E(3?1sjUueloD8S1- zEgm|ZRWJ@!$e@gCCe${7>Y|-=NEBmizm979@!9Ad*s1B#LL46QzEXvLo6!uSqjmfC zOy~kNtQDqafvAWRo1CKk>`?6$S=qeFKpJuj#dJ_6C?_LhA-ynR_$GRnfPjkd^-B)8QTHjsGseoBoPtJi@JAII=+z1cLU5~kLwm4O(A@$K=lt|$zg*|K@{&AJBfwIZufXlAxV`NbN?8G()+jo^HH;bmZ8&+Y zpJ#+AQ2_9UcSpiH&+I?leumlSZf|B(KkK(?x?AomuAN9E^j;K}wo7KI4imBGRfEIh zaReC3hk_>9cL>SyO_2w2_ky3QrUgS4!1z%)-)f}NN43xA#Vh2fTYq8hTEIVLfbHSbf5$-m-ZJ_eqw>#~Wu z35oW!EjG6()?RI7vKVp z5Yz(N2`~JU%M?_8ge74Q3b{FNgqWYQ)PK|E`X`Timke_g`^Q$up|U7xVOrfkoFj!& z1DK>s^3`b%`k3>e-|{W|%@edE3^Siq?f-r251ZFBFvZ0SpB4FI zgxx#|VN}}j-XHH*he9>ps#>*sx!X!`U$z?VW@_G6PQA zu_BxYUmKTq;!A+57$;K#6&udHYnG%ozi>!3Uk#J-YNq5W2xJ7Y*r^SJ6r zNM#6vzkB(7`H(|)B`Bsr+TmUnj2$z$_JCtD>ou+9exFOER_$34Yu4cp^y%RaeEOT0 z=KUqZH?F1V;ioU(qV1c1lK1!b#Rp*f(mT>MT2ud4msbpL0#DHNhCjjli@Xxjb2fyJ zH;AV-lFai-<;aLb;)wUT{|+Zincr~E<3L-|z2Kr%0pV5p+_BJ$eCFnLYbsAK#GASQ zDf;&|uLRABEj?0;&tEOv!l$~^F8-DtZ%&7|QbZ-2_otcNHj<$2OiM~d*p;r!4wa!I zl*1>R|GkT6$A{u>kTwL%G4gH{!voK=8mTu=Lnt)m9Yn85sJGF|=CIx;gRiOIOQ_4I zQ&v2tZxVx#`9wR=`_OOy@iE$=FL9_U&RvCxm1yW;&x`xL_cT*Fx@DDMsMa|*oRW4<`TU7L}1 zd87?L{p)sZ`1{=FExUhpSML>rymDP!5c_ZVa(HQvz`m@lzjc`fLGzg4x>m#A_k(NS z1+jYqs2AL{&sB*r;UttXfU2@q{5p>SEIi@m=oS}V>y_O;qs}Qh>XV2^*ABoF9}+Wc z=|NSe9)9$WoURbZX`x@X|K9aOtQ}sUraok~lp0o0a-HT~Pw2Q$U;L>k;C-HsK(1Hjg#g}mYiI6?|ht{qKC6VnHn zf4BKoz8Gk{bbuzk(i9M>S-|ZfOx>eV>aD05kSy67-t0uhyTed4$kNo%uu8r))?6vW;lWz@S%Ih#S|?0?;b^qdqdP}t0NI=@b<07 zVUB~e{wkMSPdU6@Mjh`J=(4*H^kIobA0g~Fpw2t^cF}DLm!e2%N?DzF*u;WJ^+Er! zegF>;Cdaiu4yxYROZ%$?U;hGws-mhoyc?d<*po)F&j=CSw13Fq5O$P#AQ5?Cduj__ zgC*q}hbrFvkU@D{Ma(c{s?1itmC45RW6p4`NYNgC%}kuhIlJShXnjjeS5wUl$2%NA zTJn{xl?hEdf%=9Y^O(GO(;k8Y9D+iZ*GI_F5fM~ydu*%bm7~)Jya?!O8EBTPJB0rq De~rmR literal 0 HcmV?d00001 diff --git a/1.19.2/LICENSE b/1.19.2/LICENSE new file mode 100644 index 0000000..f4a1e2d --- /dev/null +++ b/1.19.2/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) HypherionSA and Contributors 2024 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/1.19.2/NeoForge/src/main/resources/craterlib_logo.png b/1.19.2/NeoForge/src/main/resources/craterlib_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..ce0159cc4aa4d823ea354042e531b4522d6dfead GIT binary patch literal 49343 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 z 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 De~rmR literal 0 HcmV?d00001 diff --git a/1.19.2/README.md b/1.19.2/README.md new file mode 100644 index 0000000..49c0699 --- /dev/null +++ b/1.19.2/README.md @@ -0,0 +1,55 @@ +# CraterLib + +![badge-snapshot](https://maven.firstdarkdev.xyz/api/badge/latest/snapshots/me/hypherionmc/craterlib/CraterLib-common-1.20-pre6?color=40c14a&name=CraterLib-Snapshot) + +*** + +A Library mod and modding api for easier multi-version minecraft and mod loader development + +*** + +### Supported Minecraft Versions + +| Minecraft Version | Support Status | +|-------------------| -------------- | +| < 1.18.2 | ❌ | +| 1.18.2-1.20.2 | ✳️ | +| 1.20.4 | ✳️ | +| 1.21 | 🚧 | + +- ❌ - Not Supported; no bug fixes or new features. +- 🚧 - Work in Progress; not ready for release. +- ✳️ - Long Term Support; receives changes through backports only. +- ✅ - In Support; the active version, receiving all bugfixes and features directly. + +*** + +## Library Features + +* Universal Config System (TOML Based) +* Built in Helper Classes for Various minecraft features +* Built in Optifine-Compat utilities +* Various utilities for Blockstates, LANG, Math and Rendering +* Cross Mod-Loader Events - Based on [Acara](https://github.com/Keksuccino/acara) +* Cross Mod-Loader Config Screens (Based on [Cloth Config Lite](https://github.com/shedaniel/cloth-config-lite)) +* Automatic ModMenu and Forge Config screen registration +* Built in Cross Mod-Loader Network system +* Nojang Modding API + +*** + +## Setup Instructions + +There's a **wiki coming soon**, but for now, here's some basic instructions for building the project: + +1. `git clone` the project to a safe spot. +2. Install Java's JDK 17. Make sure you have the development version explicitly: + * Fedora: `sudo dnf install java-17-openjdk-devel` + * Ubuntu: `sudo apt install openjdk-17-jdk` + * macOS: `brew install openjdk@17` +3. Set it accordingly: + * Windows/macOS: Set the `JAVA_HOME` environment variable or use system settings + * Linux: `sudo update-alternatives --config java` +4. Navigate to the CraterLib folder, then run a `gradlew` file depending on your operating system: + * Windows: `.\gradlew.bat build` + * macOS/Linux/BSD: `chmod +x gradlew` and `./gradlew` diff --git a/1.19.2/build.gradle b/1.19.2/build.gradle new file mode 100644 index 0000000..5bdbcfc --- /dev/null +++ b/1.19.2/build.gradle @@ -0,0 +1,110 @@ +plugins { + id 'java' + id 'com.github.johnrengelman.shadow' version '8.1.1' apply false + id "xyz.wagyourtail.unimined" version "1.2.4" apply false + id "com.hypherionmc.modutils.modpublisher" version "2.1.+" + id "com.hypherionmc.modutils.orion" version "1.0.+" + id 'maven-publish' +} + +orion.setup { + multiProject = true + enableMirrorMaven = true + enableReleasesMaven = true + + dopplerToken = System.getenv("DOPPLER_KEY") + + versioning { + var relType = project.properties["releaseType"] ?: "${release_type}" + identifier("${relType}") + } +} + +group = project_group + +subprojects { + apply plugin: "xyz.wagyourtail.unimined" + apply plugin: "java" + apply plugin: 'maven-publish' + apply plugin: 'com.github.johnrengelman.shadow' + apply plugin: 'com.hypherionmc.modutils.modpublisher' + + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + + group = rootProject.group + + repositories { + mavenCentral() + + maven { + name = "Modrinth" + url = "https://api.modrinth.com/maven" + content { + includeGroup "maven.modrinth" + } + } + } + + configurations { + shade + modCompileOnly + implementation.extendsFrom shade + compileOnly.extendsFrom modCompileOnly + } + + dependencies { + // All Projects + shade "me.hypherionmc.moon-config:core:${moon_config}" + shade "me.hypherionmc.moon-config:toml:${moon_config}" + shade "com.hypherionmc:rpcsdk:${rpc_sdk}" + shade "me.hypherionmc.sdlink:mcdiscordformatter-1.19.1:${discord_formatter}" + shade "net.kyori:adventure-api:${adventure}" + shade "net.kyori:adventure-text-serializer-gson:${adventure}" + + compileOnly("org.projectlombok:lombok:${lombok}") + annotationProcessor("org.projectlombok:lombok:${lombok}") + } + + jar { + manifest { + attributes([ + 'Specification-Title' : mod_name, + 'Specification-Vendor' : mod_author, + 'Specification-Version' : project.jar.archiveVersion, + 'Implementation-Title' : project.name, + 'Implementation-Version' : project.jar.archiveVersion, + 'Implementation-Vendor' : mod_author, + 'Implementation-Timestamp': new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"), + 'Timestamp' : System.currentTimeMillis(), + 'Built-On-Java' : "${System.getProperty('java.vm.version')} (${System.getProperty('java.vm.vendor')})", + 'Built-On-Minecraft' : minecraft_version + ]) + } + } + +/** + * =============================================================================== + * = DO NOT EDIT BELOW THIS LINE UNLESS YOU KNOW WHAT YOU ARE DOING = + * =============================================================================== + */ + unimined.minecraft(sourceSets.main, true) { + version minecraft_version + + mappings { + mojmap() + devNamespace "mojmap" + } + } + + tasks.withType(JavaCompile).configureEach { + it.options.encoding = 'UTF-8' + it.options.release = 17 + } + + tasks.withType(GenerateModuleMetadata).configureEach { + enabled = false + } +} + +// TODO MODULE JARS diff --git a/1.19.2/gradle.properties b/1.19.2/gradle.properties new file mode 100644 index 0000000..cb2e0a9 --- /dev/null +++ b/1.19.2/gradle.properties @@ -0,0 +1,42 @@ +#Project +version_major=2 +version_minor=0 +version_patch=0 + +#Mod +mod_author=HypherionSA +mod_id=craterlib +mod_name=CraterLib + +# Shared +minecraft_version=1.19.2 +project_group=com.hypherionmc.craterlib + +# Fabric +fabric_loader=0.15.11 +fabric_api=0.76.0+1.19.2 + +# Forge +forge_version=43.2.0 + +# Dependencies +moon_config=1.0.9 +lombok=1.18.32 +adventure=4.16.0 +rpc_sdk=1.0 +discord_formatter=2.0.0 + +# Mod Dependencies +fabrictailor=2.0.1 +vanish=1.3.2 +mod_menu_version=4.2.0-beta.2 +vanishmod=1.1.15 + +# Publishing +curse_id=867099 +modrinth_id=Nn8Wasaq +release_type=release + +# Gradle +org.gradle.jvmargs=-Xmx3G +org.gradle.daemon=false diff --git a/1.19.2/gradle/wrapper/gradle-wrapper.jar b/1.19.2/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..7454180f2ae8848c63b8b4dea2cb829da983f2fa GIT binary patch literal 59536 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 QuEJmfE|#`Dqx|px0L@K;Y5)KL literal 0 HcmV?d00001 diff --git a/1.19.2/gradle/wrapper/gradle-wrapper.properties b/1.19.2/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..48c0a02 --- /dev/null +++ b/1.19.2/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/1.19.2/gradlew b/1.19.2/gradlew new file mode 100644 index 0000000..b4f908a --- /dev/null +++ b/1.19.2/gradlew @@ -0,0 +1,183 @@ +#!/usr/bin/env bash + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MSYS* | MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +ARGV=("$@") +eval set -- $DEFAULT_JVM_OPTS + +IFS=$' +' read -rd '' -a JAVA_OPTS_ARR <<< "$(echo $JAVA_OPTS | xargs -n1)" +IFS=$' +' read -rd '' -a GRADLE_OPTS_ARR <<< "$(echo $GRADLE_OPTS | xargs -n1)" + +exec "$JAVACMD" "$@" "${JAVA_OPTS_ARR[@]}" "${GRADLE_OPTS_ARR[@]}" "-Dorg.gradle.appname=$APP_BASE_NAME" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "${ARGV[@]}" diff --git a/1.19.2/gradlew.bat b/1.19.2/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/1.19.2/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/1.19.2/settings.gradle b/1.19.2/settings.gradle new file mode 100644 index 0000000..60140f2 --- /dev/null +++ b/1.19.2/settings.gradle @@ -0,0 +1,14 @@ +pluginManagement { + repositories { + gradlePluginPortal() + maven { + url "https://mcentral.firstdark.dev/releases" + } + maven { + url "https://maven.firstdark.dev/releases" + } + } +} + +rootProject.name = 'CraterLib-1.19.2' +include("Common", "Fabric", "Forge") diff --git a/1.19.3/.gitattributes b/1.19.3/.gitattributes new file mode 100644 index 0000000..20fc528 --- /dev/null +++ b/1.19.3/.gitattributes @@ -0,0 +1,15 @@ +* text eol=lf +*.bat text eol=crlf +*.patch text eol=lf +*.java text eol=lf +*.gradle text eol=crlf +*.png binary +*.gif binary +*.exe binary +*.dll binary +*.jar binary +*.lzma binary +*.zip binary +*.pyd binary +*.cfg text eol=lf +*.jks binary \ No newline at end of file diff --git a/1.19.3/.gitignore b/1.19.3/.gitignore new file mode 100644 index 0000000..966dad4 --- /dev/null +++ b/1.19.3/.gitignore @@ -0,0 +1,29 @@ +# eclipse +bin +*.launch +.settings +.metadata +.classpath +.project + +# idea +out +*.ipr +*.iws +*.iml +.idea + +# gradle +build +.gradle + +# other +eclipse +run + +artifacts +src/test/** + +workspace +upstream +rejects \ No newline at end of file diff --git a/1.19.3/.jenkins/Jenkinsfile.deploy b/1.19.3/.jenkins/Jenkinsfile.deploy new file mode 100644 index 0000000..5b5b728 --- /dev/null +++ b/1.19.3/.jenkins/Jenkinsfile.deploy @@ -0,0 +1,55 @@ +def JDK = "17" + +pipeline { + agent { + docker { + image "registry.firstdark.dev/java${JDK}:latest" + alwaysPull true + args '-v gradle-cache:/home/gradle/.gradle' + } + } + + environment { + GRADLE_USER_HOME = '/home/gradle/.gradle' + } + + stages { + stage("Notify Discord") { + steps { + discordSend webhookURL: env.FDD_WH_ADMIN, + title: "Deploy Started: CraterLib 1.19.3/4 Deploy #${BUILD_NUMBER}", + link: env.BUILD_URL, + result: 'SUCCESS', + description: "Build: [${BUILD_NUMBER}](${env.BUILD_URL})" + } + } + stage("Prepare") { + steps { + sh "chmod +x ./gradlew" + sh "./gradlew clean" + } + } + stage("Publish to Modrinth/Curseforge") { + steps { + sh "./gradlew publishMod -Prelease=true" + } + } + stage("Publish to Maven") { + steps { + sh "./gradlew publish -Prelease=true" + } + } + } + post { + always { + sh "./gradlew --stop" + deleteDir() + + discordSend webhookURL: env.FDD_WH_ADMIN, + title: "CraterLib Port Deploy #${BUILD_NUMBER}", + link: env.BUILD_URL, + result: currentBuild.currentResult, + description: "Build: [${BUILD_NUMBER}](${env.BUILD_URL})\nStatus: ${currentBuild.currentResult}" + } + } +} diff --git a/1.19.3/.jenkins/Jenkinsfile.snapshot b/1.19.3/.jenkins/Jenkinsfile.snapshot new file mode 100644 index 0000000..9661b3e --- /dev/null +++ b/1.19.3/.jenkins/Jenkinsfile.snapshot @@ -0,0 +1,65 @@ +def projectName = "CraterLib"; +def projectIcon = "https://cdn.modrinth.com/data/Nn8Wasaq/a172c634683a11a2e9ae593e56eba7885743bb44.png"; +def JDK = "17"; +def majorMc = "1.19.3/4"; +def modLoaders = "forge|fabric|quilt"; +def supportedMc = "1.19.3|1.19.4"; +def reltype = "snapshot"; + +pipeline { + agent { + docker { + image "registry.firstdark.dev/java${JDK}:latest" + alwaysPull true + args '-v gradle-cache:/home/gradle/.gradle' + } + } + + environment { + GRADLE_USER_HOME = '/home/gradle/.gradle' + } + + stages { + stage("Notify Discord") { + steps { + discordSend webhookURL: env.SSS_WEBHOOK, + title: "Deploy Started: ${projectName} ${majorMc} Deploy #${BUILD_NUMBER}", + link: env.BUILD_URL, + result: 'SUCCESS', + description: "Build: [${BUILD_NUMBER}](${env.BUILD_URL})" + } + } + + stage("Prepare") { + steps { + sh "chmod +x ./gradlew" + sh "./gradlew build -PreleaseType=port" + } + } + + stage("Publish to Maven") { + steps { + catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') { + sh "./gradlew publish -PreleaseType=${reltype}" + } + } + } + } + + post { + always { + sh "./gradlew --stop" + + fddsnapshotter apiKey: env.PLATFORM_KEY, + projectSlug: "craterlib", + projectName: "${projectName}", + projectIcon: "${projectIcon}", + modLoaders: "${modLoaders}", + minecraftVersions: "${supportedMc}", + failWebhook: env.SSS_WEBHOOK, + publishWebhooks: "${env.SSS_WEBHOOK}|${env.FDD_WH}" + + deleteDir() + } + } +} diff --git a/1.19.3/Common/build.gradle b/1.19.3/Common/build.gradle new file mode 100644 index 0000000..843d62a --- /dev/null +++ b/1.19.3/Common/build.gradle @@ -0,0 +1,68 @@ +archivesBaseName = "${mod_name.replace(" ", "")}-Common-${minecraft_version}" + +dependencies { + +} + +shadowJar { + from sourceSets.main.output + configurations = [project.configurations.shade] + + dependencies { + exclude(dependency('com.google.code.gson:.*')) + + relocate 'me.hypherionmc.moonconfig', 'shadow.hypherionmc.moonconfig' + relocate 'me.hypherionmc.mcdiscordformatter', 'shadow.hypherionmc.mcdiscordformatter' + relocate 'net.kyori', 'shadow.kyori' + } + setArchiveClassifier("dev") +} + +/** + * =============================================================================== + * = DO NOT EDIT BELOW THIS LINE UNLESS YOU KNOW WHAT YOU ARE DOING = + * =============================================================================== + */ + +unimined.minecraft { + fabric { + loader fabric_loader + } + + defaultRemapJar = false +} + +processResources { + def buildProps = project.properties.clone() + + filesMatching(['pack.mcmeta']) { + expand buildProps + } +} + +/** + * Publishing Config + */ +publishing { + publications { + mavenCommon(MavenPublication) { + artifactId project.archivesBaseName + from components.java + + pom.withXml { + Node pomNode = asNode() + pomNode.dependencies.'*'.findAll() { + it.artifactId.text() == 'regutils-joined-fabric' || + it.artifactId.text() == 'core' || + it.artifactId.text() == 'toml' + }.each() { + it.parent().remove(it) + } + } + } + } + + repositories { + maven rootProject.orion.getPublishingMaven() + } +} \ No newline at end of file diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/CraterConstants.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/CraterConstants.java new file mode 100644 index 0000000..93edffc --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/CraterConstants.java @@ -0,0 +1,10 @@ +package com.hypherionmc.craterlib; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CraterConstants { + public static final String MOD_ID = "craterlib"; + public static final String MOD_NAME = "CraterLib"; + public static final Logger LOG = LoggerFactory.getLogger(MOD_NAME); +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/commands/CraterCommand.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/commands/CraterCommand.java new file mode 100644 index 0000000..cc4969e --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/commands/CraterCommand.java @@ -0,0 +1,53 @@ +package com.hypherionmc.craterlib.api.commands; + +import com.hypherionmc.craterlib.nojang.authlib.BridgedGameProfile; +import com.hypherionmc.craterlib.nojang.commands.BridgedCommandSourceStack; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.TriConsumer; +import com.mojang.brigadier.arguments.ArgumentType; +import lombok.Getter; +import net.minecraft.commands.arguments.GameProfileArgument; +import org.apache.commons.lang3.tuple.Pair; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.function.Consumer; + +@Getter +public class CraterCommand { + + private final HashMap, TriConsumer>> arguments = new LinkedHashMap<>(); + private Consumer executor; + + private final String commandName; + private int permissionLevel = 4; + + CraterCommand(String commandName) { + this.commandName = commandName; + } + + public static CraterCommand literal(String commandName) { + return new CraterCommand(commandName); + } + + public CraterCommand requiresPermission(int perm) { + this.permissionLevel = perm; + return this; + } + + public CraterCommand withGameProfileArgument(String key, TriConsumer, BridgedCommandSourceStack> executor) { + arguments.put(key, Pair.of(GameProfileArgument.gameProfile(), executor)); + return this; + } + + public CraterCommand executes(Consumer ctx) { + executor = ctx; + return this; + } + + public boolean hasArguments() { + return !arguments.isEmpty(); + } + +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/CraterClientTickEvent.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/CraterClientTickEvent.java new file mode 100644 index 0000000..c68c596 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/CraterClientTickEvent.java @@ -0,0 +1,14 @@ +package com.hypherionmc.craterlib.api.events.client; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedClientLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public class CraterClientTickEvent extends CraterEvent { + + private final BridgedClientLevel level; + +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/CraterSinglePlayerEvent.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/CraterSinglePlayerEvent.java new file mode 100644 index 0000000..ab671f0 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/CraterSinglePlayerEvent.java @@ -0,0 +1,21 @@ +package com.hypherionmc.craterlib.api.events.client; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public class CraterSinglePlayerEvent extends CraterEvent { + + private final BridgedPlayer player; + + public static class PlayerLogin extends CraterSinglePlayerEvent { + + public PlayerLogin(BridgedPlayer player) { + super(player); + } + + } +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/LateInitEvent.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/LateInitEvent.java new file mode 100644 index 0000000..33c06fb --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/LateInitEvent.java @@ -0,0 +1,16 @@ +package com.hypherionmc.craterlib.api.events.client; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.client.BridgedMinecraft; +import com.hypherionmc.craterlib.nojang.client.BridgedOptions; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public class LateInitEvent extends CraterEvent { + + private final BridgedMinecraft minecraft; + private final BridgedOptions options; + +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/PlayerJoinRealmEvent.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/PlayerJoinRealmEvent.java new file mode 100644 index 0000000..1ef95de --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/PlayerJoinRealmEvent.java @@ -0,0 +1,14 @@ +package com.hypherionmc.craterlib.api.events.client; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.realmsclient.dto.BridgedRealmsServer; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public class PlayerJoinRealmEvent extends CraterEvent { + + private final BridgedRealmsServer server; + +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/ScreenEvent.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/ScreenEvent.java new file mode 100644 index 0000000..034ca4a --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/ScreenEvent.java @@ -0,0 +1,27 @@ +package com.hypherionmc.craterlib.api.events.client; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.client.gui.BridgedScreen; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; + +@Getter +@RequiredArgsConstructor +public class ScreenEvent extends CraterEvent { + + private final BridgedScreen screen; + + @Getter + public static class Opening extends ScreenEvent { + + private final BridgedScreen currentScreen; + @Setter private BridgedScreen newScreen; + + public Opening(BridgedScreen currentScreen, BridgedScreen newScreen) { + super(newScreen); + this.currentScreen = currentScreen; + this.newScreen = newScreen; + } + } +} \ No newline at end of file diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/common/CraterPlayerDeathEvent.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/common/CraterPlayerDeathEvent.java new file mode 100644 index 0000000..02cd63c --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/common/CraterPlayerDeathEvent.java @@ -0,0 +1,21 @@ +package com.hypherionmc.craterlib.api.events.common; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.ChatUtils; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; +import net.minecraft.world.damagesource.DamageSource; + +@RequiredArgsConstructor +@Getter +public class CraterPlayerDeathEvent extends CraterEvent { + + private final BridgedPlayer player; + private final DamageSource damageSource; + + public Component getDeathMessage() { + return ChatUtils.mojangToAdventure(damageSource.getLocalizedDeathMessage(player.toMojang())); + } +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterAdvancementEvent.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterAdvancementEvent.java new file mode 100644 index 0000000..5cd659d --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterAdvancementEvent.java @@ -0,0 +1,34 @@ +package com.hypherionmc.craterlib.api.events.server; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.advancements.BridgedAdvancement; +import com.hypherionmc.craterlib.nojang.advancements.BridgedDisplayInfo; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import lombok.Getter; +import net.kyori.adventure.text.Component; + +import java.util.Optional; + +@Getter +public class CraterAdvancementEvent extends CraterEvent { + + private final BridgedAdvancement advancement; + private final BridgedPlayer player; + private final Component title; + private final Component description; + + public CraterAdvancementEvent(BridgedPlayer player, BridgedAdvancement advancement) { + this.advancement = advancement; + this.player = player; + + Optional displayInfo = advancement.displayInfo(); + + if (displayInfo.isPresent()) { + this.title = displayInfo.get().displayName(); + this.description = displayInfo.get().description(); + } else { + this.title = Component.text("Unknown"); + this.description = Component.text("Unknown"); + } + } +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterCommandEvent.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterCommandEvent.java new file mode 100644 index 0000000..364cc22 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterCommandEvent.java @@ -0,0 +1,58 @@ +package com.hypherionmc.craterlib.api.events.server; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.ChatUtils; +import com.mojang.brigadier.ParseResults; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.context.StringRange; +import lombok.Getter; +import lombok.Setter; +import net.kyori.adventure.text.Component; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.arguments.ComponentArgument; +import net.minecraft.world.entity.player.Player; +import org.jetbrains.annotations.Nullable; + +@Getter +public class CraterCommandEvent extends CraterEvent { + + private final ParseResults parseResults; + @Setter private Throwable exception; + private final String command; + + private CraterCommandEvent(ParseResults parseResults, String command) { + this.parseResults = parseResults; + this.command = command; + } + + public static CraterCommandEvent of(ParseResults stack, String command) { + return new CraterCommandEvent(stack, command); + } + + public String getCommandString() { + return parseResults.getReader().getString(); + } + + @Nullable + public BridgedPlayer getPlayer() { + try { + Player p = parseResults.getContext().getLastChild().getSource().getPlayer(); + + if (p != null) + return BridgedPlayer.of(p); + } catch (Exception ignored) {} + + return null; + } + + public String getTarget() { + CommandContext context = parseResults.getContext().build(parseResults.getReader().getString()); + StringRange selector_range = parseResults.getContext().getArguments().get("targets").getRange(); + return context.getInput().substring(selector_range.getStart(), selector_range.getEnd()); + } + + public Component getMessage() { + return ChatUtils.mojangToAdventure(ComponentArgument.getComponent(parseResults.getContext().build(parseResults.getReader().getString()), "message")); + } +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterPlayerEvent.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterPlayerEvent.java new file mode 100644 index 0000000..7eb3701 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterPlayerEvent.java @@ -0,0 +1,30 @@ +package com.hypherionmc.craterlib.api.events.server; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public class CraterPlayerEvent extends CraterEvent { + + private final BridgedPlayer player; + + public static class PlayerLoggedIn extends CraterPlayerEvent { + + public PlayerLoggedIn(BridgedPlayer player) { + super(player); + } + + } + + public static class PlayerLoggedOut extends CraterPlayerEvent { + + public PlayerLoggedOut(BridgedPlayer player) { + super(player); + } + + } + +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterRegisterCommandEvent.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterRegisterCommandEvent.java new file mode 100644 index 0000000..269065a --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterRegisterCommandEvent.java @@ -0,0 +1,15 @@ +package com.hypherionmc.craterlib.api.events.server; + +import com.hypherionmc.craterlib.api.commands.CraterCommand; +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.commands.CommandsRegistry; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +public class CraterRegisterCommandEvent extends CraterEvent { + + public void registerCommand(CraterCommand cmd) { + CommandsRegistry.INSTANCE.registerCommand(cmd); + } + +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterServerChatEvent.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterServerChatEvent.java new file mode 100644 index 0000000..b25170a --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterServerChatEvent.java @@ -0,0 +1,25 @@ +package com.hypherionmc.craterlib.api.events.server; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.core.event.annot.Cancellable; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import lombok.Getter; +import lombok.Setter; +import net.kyori.adventure.text.Component; + +@Cancellable +@Getter +public class CraterServerChatEvent extends CraterEvent { + + public final String message, username; + public final BridgedPlayer player; + @Setter private Component component; + + public CraterServerChatEvent(BridgedPlayer player, String message, Component component) { + this.message = message; + this.player = player; + this.username = player.getGameProfile().getName(); + this.component = component; + } + +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterServerLifecycleEvent.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterServerLifecycleEvent.java new file mode 100644 index 0000000..3f87d04 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterServerLifecycleEvent.java @@ -0,0 +1,34 @@ +package com.hypherionmc.craterlib.api.events.server; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +public class CraterServerLifecycleEvent extends CraterEvent { + + @RequiredArgsConstructor + @Getter + public static class Starting extends CraterServerLifecycleEvent { + private final BridgedMinecraftServer server; + } + + @RequiredArgsConstructor + @Getter + public static class Started extends CraterServerLifecycleEvent { + private final BridgedMinecraftServer server; + } + + @RequiredArgsConstructor + @Getter + public static class Stopping extends CraterServerLifecycleEvent { + private final BridgedMinecraftServer server; + } + + @RequiredArgsConstructor + @Getter + public static class Stopped extends CraterServerLifecycleEvent { + private final BridgedMinecraftServer server; + } + +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/MessageBroadcastEvent.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/MessageBroadcastEvent.java new file mode 100644 index 0000000..6b11404 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/MessageBroadcastEvent.java @@ -0,0 +1,20 @@ +package com.hypherionmc.craterlib.api.events.server; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; + +import java.util.function.Function; + +@RequiredArgsConstructor +@Getter +public class MessageBroadcastEvent extends CraterEvent { + + private final Component component; + private final Function function; + private final boolean bl; + private final String threadName; + +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/PlayerPreLoginEvent.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/PlayerPreLoginEvent.java new file mode 100644 index 0000000..eaabffd --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/PlayerPreLoginEvent.java @@ -0,0 +1,20 @@ +package com.hypherionmc.craterlib.api.events.server; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.authlib.BridgedGameProfile; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import net.kyori.adventure.text.Component; + +import java.net.SocketAddress; + +@RequiredArgsConstructor +@Getter +public class PlayerPreLoginEvent extends CraterEvent { + + private final SocketAddress address; + private final BridgedGameProfile gameProfile; + @Setter private Component message; + +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/networking/CraterNetworkHandler.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/networking/CraterNetworkHandler.java new file mode 100644 index 0000000..6b57e96 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/api/networking/CraterNetworkHandler.java @@ -0,0 +1,28 @@ +package com.hypherionmc.craterlib.api.networking; + +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; + +import java.util.List; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public interface CraterNetworkHandler { + + void sendToServer(T packet); + + void sendToServer(T packet, boolean ignoreCheck); + + void sendToClient(T packet, BridgedPlayer player); + + default void sendToClients(T packet, List players) { + for (BridgedPlayer player : players) { + sendToClient(packet, player); + } + } + + default void sendToAllClients(T packet, BridgedMinecraftServer server) { + sendToClients(packet, server.getPlayers()); + } +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/CraterConfigScreen.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/CraterConfigScreen.java new file mode 100644 index 0000000..d0e31a6 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/CraterConfigScreen.java @@ -0,0 +1,392 @@ +package com.hypherionmc.craterlib.client.gui.config; + +import com.hypherionmc.craterlib.CraterConstants; +import com.hypherionmc.craterlib.client.gui.config.widgets.*; +import com.hypherionmc.craterlib.core.config.ModuleConfig; +import com.hypherionmc.craterlib.core.config.annotations.HideFromScreen; +import com.hypherionmc.craterlib.core.config.annotations.SubConfig; +import com.hypherionmc.craterlib.core.config.annotations.Tooltip; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.*; +import me.hypherionmc.moonconfig.core.conversion.SpecComment; +import net.minecraft.ChatFormatting; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.screens.ConfirmScreen; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Mth; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.joml.Matrix4f; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * @author HypherionSA + */ +public class CraterConfigScreen extends Screen { + public static final float SCROLLBAR_BOTTOM_COLOR = .5f; + public static final float SCROLLBAR_TOP_COLOR = .67f; + private static final int TOP = 26; + private static final int BOTTOM = 24; + private final Screen parent; + private final List> options = new ArrayList<>(); + private final ModuleConfig config; + public double scrollerAmount; + private boolean dragging; + + public CraterConfigScreen(ModuleConfig config, Screen parent, Object subConfig) { + super(Component.translatable("cl." + config.getClass().getSimpleName().toLowerCase() + ".title")); + this.parent = parent; + this.config = config; + if (subConfig != null) { + setupScreenFromConfig(subConfig, subConfig.getClass()); + } else { + setupScreenFromConfig(config, config.getClass()); + } + } + + public CraterConfigScreen(ModuleConfig config, Screen parent) { + this(config, parent, null); + } + + private static Component toText(Enum val) { + return Component.translatable(val.toString()); + } + + private static Component toText(Boolean bool) { + return Component.translatable(bool.toString()); + } + + private void setupScreenFromConfig(Object object, Class clazz) { + while (clazz != Object.class) { + for (Field field : clazz.getDeclaredFields()) { + final int fieldModifiers = field.getModifiers(); + if (object == null || Modifier.isStatic(fieldModifiers) || Modifier.isTransient(fieldModifiers)) { + continue; + } + + try { + if (!field.isAccessible()) { + field.setAccessible(true); + } + if (field.isAnnotationPresent(HideFromScreen.class)) + return; + Object val = field.get(object); + + /* Lang Stuff */ + String baseLangKey = "cl." + clazz.getSimpleName().toLowerCase() + "." + field.getName().toLowerCase(); + String[] tooltipLang = field.isAnnotationPresent(SpecComment.class) ? new String[]{field.getAnnotation(SpecComment.class).value()} : new String[0]; + if (field.isAnnotationPresent(Tooltip.class)) { + tooltipLang = field.getAnnotation(Tooltip.class).value(); + } + + add(Component.translatable(baseLangKey), + val, + () -> val, + (ret) -> { + try { + field.set(object, ret); + config.saveConfig(config); + } catch (IllegalAccessException e) { + CraterConstants.LOG.error("Failed to update value for field {} in config {}", field.getName(), config.getConfigName(), e); + } + }, + field.isAnnotationPresent(SubConfig.class), + tooltipLang + ); + } catch (IllegalAccessException e) { + CraterConstants.LOG.error("Failed to access value for field {} in config {}", field.getName(), config.getConfigName(), e); + } + } + clazz = clazz.getSuperclass(); + } + } + + public void add(Component text, T value, @Nullable Supplier defaultValue, Consumer savingConsumer, boolean isSubConfig, String... langKeys) { + Option option = (Option) createOption(value, isSubConfig); + option.text = text; + option.defaultValue = defaultValue; + option.savingConsumer = savingConsumer; + option.originalValue = value; + option.value = value; + option.setLangKeys(List.of(langKeys)); + options.add(option); + option.onAdd(); + } + + private Option createOption(T value, boolean isSubConfig) { + if (value instanceof Enum) { + Object[] objects = value.getClass().getEnumConstants(); + return new ToggleButton>((List) Arrays.asList(objects), CraterConfigScreen::toText); + } + if (value instanceof Boolean) { + return new ToggleButton<>(Arrays.asList(Boolean.TRUE, Boolean.FALSE), CraterConfigScreen::toText); + } + if (value instanceof String) { + return new TextConfigOption<>(Function.identity(), Function.identity()); + } + if (value instanceof Integer) { + return new TextConfigOption<>(Objects::toString, Integer::valueOf); + } + if (value instanceof Long) { + return new TextConfigOption<>(Objects::toString, Long::valueOf); + } + if (value instanceof Double) { + return new TextConfigOption<>(Objects::toString, Double::valueOf); + } + if (value instanceof Float) { + return new TextConfigOption<>(Objects::toString, Float::valueOf); + } + if (value instanceof BigInteger) { + return new TextConfigOption<>(Objects::toString, BigInteger::new); + } + if (value instanceof BigDecimal) { + return new TextConfigOption<>(Objects::toString, BigDecimal::new); + } + if (value instanceof ResourceLocation) { + return new TextConfigOption<>(Objects::toString, ResourceLocation::new); + } + if (isSubConfig) { + return new SubConfigWidget<>(config, this, value); + } + throw new IllegalArgumentException(String.valueOf(value)); + } + + @Override + protected void init() { + super.init(); + ((List) children()).addAll(options); + + int buttonWidths = Math.min(200, (width - 50 - 12) / 3); + addRenderableWidget(new InternalConfigButton(this, width / 2 - buttonWidths - 3, height - 22, buttonWidths, 20, Component.empty(), true)); + addRenderableWidget(new InternalConfigButton(this, width / 2 + 3, height - 22, buttonWidths, 20, Component.empty(), false)); + } + + @Override + public void render(@NotNull PoseStack matrices, int mouseX, int mouseY, float delta) { + overlayBackground(matrices, TOP, height - BOTTOM, 32); + + renderScrollBar(); + + matrices.pushPose(); + matrices.translate(0, 0, 500.0); + overlayBackground(matrices, 0, TOP, 64); + overlayBackground(matrices, height - BOTTOM, height, 64); + renderShadow(matrices); + drawCenteredString(matrices, font, getTitle(), width / 2, 9, 0xFFFFFF); + super.render(matrices, mouseX, mouseY, delta); + matrices.popPose(); + + int y = (int) (TOP + 4 - Math.round(scrollerAmount)); + for (Option option : options) { + int height1 = option.height(); + option.render(minecraft, font, 40, y, width - 80, height1, matrices, mouseX, mouseY, delta); + renderConfigTooltip(matrices, font, mouseX, mouseY, 40, y, font.width(option.text), height1, option.text.getString(), option.getLangKeys().toArray(new String[0])); + y += height1; + } + } + + private void renderScrollBar() { + int listHeight = height - BOTTOM - TOP; + int totalHeight = totalHeight(); + if (totalHeight > listHeight) { + int maxScroll = Math.max(0, totalHeight - listHeight); + int height = listHeight * listHeight / totalHeight; + height = Mth.clamp(height, 32, listHeight); + height = Math.max(10, height); + int minY = Math.min(Math.max((int) scrollerAmount * (listHeight - height) / maxScroll + TOP, TOP), this.height - BOTTOM - height); + + int scrollbarPositionMaxX = width; + int scrollbarPositionMinX = scrollbarPositionMaxX - 6; + + int maxY = this.height - BOTTOM; + //RenderSystem.disableTexture(); + Tesselator tesselator = Tesselator.getInstance(); + BufferBuilder buffer = tesselator.getBuilder(); + RenderSystem.setShader(GameRenderer::getPositionColorShader); + buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR); + + buffer.vertex(scrollbarPositionMinX, maxY, 0.0D).color(0, 0, 0, 255).endVertex(); + buffer.vertex(scrollbarPositionMaxX, maxY, 0.0D).color(0, 0, 0, 255).endVertex(); + buffer.vertex(scrollbarPositionMaxX, TOP, 0.0D).color(0, 0, 0, 255).endVertex(); + buffer.vertex(scrollbarPositionMinX, TOP, 0.0D).color(0, 0, 0, 255).endVertex(); + + buffer.vertex(scrollbarPositionMinX, minY + height, 0.0D).color(SCROLLBAR_BOTTOM_COLOR, SCROLLBAR_BOTTOM_COLOR, SCROLLBAR_BOTTOM_COLOR, 1).endVertex(); + buffer.vertex(scrollbarPositionMaxX, minY + height, 0.0D).color(SCROLLBAR_BOTTOM_COLOR, SCROLLBAR_BOTTOM_COLOR, SCROLLBAR_BOTTOM_COLOR, 1).endVertex(); + buffer.vertex(scrollbarPositionMaxX, minY, 0.0D).color(SCROLLBAR_BOTTOM_COLOR, SCROLLBAR_BOTTOM_COLOR, SCROLLBAR_BOTTOM_COLOR, 1).endVertex(); + buffer.vertex(scrollbarPositionMinX, minY, 0.0D).color(SCROLLBAR_BOTTOM_COLOR, SCROLLBAR_BOTTOM_COLOR, SCROLLBAR_BOTTOM_COLOR, 1).endVertex(); + buffer.vertex(scrollbarPositionMinX, (minY + height - 1), 0.0D).color(SCROLLBAR_TOP_COLOR, SCROLLBAR_TOP_COLOR, SCROLLBAR_TOP_COLOR, 1).endVertex(); + buffer.vertex((scrollbarPositionMaxX - 1), (minY + height - 1), 0.0D).color(SCROLLBAR_TOP_COLOR, SCROLLBAR_TOP_COLOR, SCROLLBAR_TOP_COLOR, 1).endVertex(); + buffer.vertex((scrollbarPositionMaxX - 1), minY, 0.0D).color(SCROLLBAR_TOP_COLOR, SCROLLBAR_TOP_COLOR, SCROLLBAR_TOP_COLOR, 1).endVertex(); + buffer.vertex(scrollbarPositionMinX, minY, 0.0D).color(SCROLLBAR_TOP_COLOR, SCROLLBAR_TOP_COLOR, SCROLLBAR_TOP_COLOR, 1).endVertex(); + tesselator.end(); + RenderSystem.disableBlend(); + //RenderSystem.enableTexture(); + } + } + + private void renderShadow(PoseStack matrices) { + Tesselator tesselator = Tesselator.getInstance(); + BufferBuilder buffer = tesselator.getBuilder(); + RenderSystem.enableBlend(); + RenderSystem.blendFuncSeparate(770, 771, 0, 1); + //RenderSystem.disableTexture(); + RenderSystem.setShader(GameRenderer::getPositionTexColorShader); + Matrix4f matrix = matrices.last().pose(); + buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX_COLOR); + buffer.vertex(matrix, 0, TOP + 4, 0.0F).uv(0, 1).color(0, 0, 0, 0).endVertex(); + buffer.vertex(matrix, width, TOP + 4, 0.0F).uv(1, 1).color(0, 0, 0, 0).endVertex(); + buffer.vertex(matrix, width, TOP, 0.0F).uv(1, 0).color(0, 0, 0, 185).endVertex(); + buffer.vertex(matrix, 0, TOP, 0.0F).uv(0, 0).color(0, 0, 0, 185).endVertex(); + buffer.vertex(matrix, 0, height - BOTTOM, 0.0F).uv(0, 1).color(0, 0, 0, 185).endVertex(); + buffer.vertex(matrix, width, height - BOTTOM, 0.0F).uv(1, 1).color(0, 0, 0, 185).endVertex(); + buffer.vertex(matrix, width, height - BOTTOM - 4, 0.0F).uv(1, 0).color(0, 0, 0, 0).endVertex(); + buffer.vertex(matrix, 0, height - BOTTOM - 4, 0.0F).uv(0, 0).color(0, 0, 0, 0).endVertex(); + tesselator.end(); + //RenderSystem.enableTexture(); + RenderSystem.disableBlend(); + } + + protected void overlayBackground(PoseStack matrices, int h1, int h2, int color) { + overlayBackground(matrices.last().pose(), 0, h1, width, h2, color, color, color, 255, 255); + } + + protected void overlayBackground(Matrix4f matrix, int minX, int minY, int maxX, int maxY, int red, int green, int blue, int startAlpha, int endAlpha) { + Tesselator tesselator = Tesselator.getInstance(); + BufferBuilder buffer = tesselator.getBuilder(); + RenderSystem.setShaderTexture(0, Screen.BACKGROUND_LOCATION); + RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f); + RenderSystem.setShader(GameRenderer::getPositionTexColorShader); + buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX_COLOR); + buffer.vertex(matrix, minX, maxY, 0.0F).uv(minX / 32.0F, maxY / 32.0F).color(red, green, blue, endAlpha).endVertex(); + buffer.vertex(matrix, maxX, maxY, 0.0F).uv(maxX / 32.0F, maxY / 32.0F).color(red, green, blue, endAlpha).endVertex(); + buffer.vertex(matrix, maxX, minY, 0.0F).uv(maxX / 32.0F, minY / 32.0F).color(red, green, blue, startAlpha).endVertex(); + buffer.vertex(matrix, minX, minY, 0.0F).uv(minX / 32.0F, minY / 32.0F).color(red, green, blue, startAlpha).endVertex(); + tesselator.end(); + } + + public int scrollHeight() { + int totalHeight = totalHeight(); + int listHeight = height - BOTTOM - TOP; + if (totalHeight <= listHeight) { + return 0; + } + return totalHeight - listHeight; + } + + public int totalHeight() { + int i = 8; + for (Option option : options) { + i += option.height(); + } + return i; + } + + public boolean hasErrors() { + for (Option option : options) { + if (option.hasErrors) { + return true; + } + } + return false; + } + + public boolean isEdited() { + for (Option option : options) { + if (option.isEdited()) { + return true; + } + } + return false; + } + + public void save() { + for (Option option : options) { + option.save(); + option.originalValue = option.value; + } + } + + @Override + public void onClose() { + if (isEdited()) { + minecraft.setScreen(new ConfirmScreen(this::acceptConfirm, Component.translatable("t.clc.quit_config"), + Component.translatable("t.clc.quit_config_sure"), + Component.translatable("t.clc.quit_discard"), + Component.translatable("gui.cancel"))); + } else { + minecraft.setScreen(parent); + } + } + + @Override + public boolean mouseScrolled(double d, double e, double f) { + if (e >= TOP && e <= height - BOTTOM) { + scrollerAmount = Mth.clamp(scrollerAmount - f * 16.0D, 0, scrollHeight()); + return true; + } + return super.mouseScrolled(d, e, f); + } + + @Override + public boolean mouseClicked(double d, double e, int i) { + this.dragging = i == 0 && d >= width - 6 && d < width; + return super.mouseClicked(d, e, i) || dragging; + } + + @Override + public boolean mouseDragged(double d, double e, int i, double f, double g) { + if (super.mouseDragged(d, e, i, f, g)) { + return true; + } + if (i != 0 || !this.dragging) { + return false; + } + if (e < TOP) { + scrollerAmount = 0; + } else if (e > height - BOTTOM) { + scrollerAmount = scrollHeight(); + } else { + double h = Math.max(1, this.scrollHeight()); + int j = height - BOTTOM - TOP; + int k = Mth.clamp((int) ((float) (j * j) / (float) this.scrollHeight()), 32, j - 8); + double l = Math.max(1.0, h / (double) (j - k)); + scrollerAmount = Mth.clamp(scrollerAmount + g * l, 0, scrollHeight()); + } + return true; + } + + private void acceptConfirm(boolean t) { + if (!t) { + minecraft.setScreen(this); + } else { + minecraft.setScreen(parent); + } + } + + private void renderConfigTooltip(PoseStack stack, Font font, int mouseX, int mouseY, int startX, int startY, int sizeX, int sizeY, String title, String... description) { + if (mouseX > startX && mouseX < startX + sizeX) { + if (mouseY > startY && mouseY < startY + sizeY) { + List list = new ArrayList<>(); + list.add(Component.translatable(ChatFormatting.BOLD + "" + ChatFormatting.YELLOW + title)); + for (String desc : description) { + list.add(Component.translatable(desc)); + } + renderComponentTooltip(stack, list, mouseX, mouseY); + } + } + } +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/AbstractConfigWidget.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/AbstractConfigWidget.java new file mode 100644 index 0000000..dc7ac98 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/AbstractConfigWidget.java @@ -0,0 +1,27 @@ +package com.hypherionmc.craterlib.client.gui.config.widgets; + +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.client.gui.components.EditBox; + +/** + * Copied from Cloth Config Lite + * ... + */ +public class AbstractConfigWidget extends BaseWidget { + + public static final int buttonWidth = 200; + public static final int buttonHeight = 20; + public W widget; + + @Override + public void render(Minecraft minecraft, Font font, int x, int y, int width, int height, PoseStack matrices, int mouseX, int mouseY, float delta) { + super.render(minecraft, font, x, y, width, height, matrices, mouseX, mouseY, delta); + int i = (widget instanceof EditBox ? 1 : 0); + widget.setX(x + width - 200 - resetButtonOffset + i); + widget.setY(y + i + 1); + widget.render(matrices, mouseX, mouseY, delta); + } +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/BaseWidget.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/BaseWidget.java new file mode 100644 index 0000000..14a0695 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/BaseWidget.java @@ -0,0 +1,61 @@ +package com.hypherionmc.craterlib.client.gui.config.widgets; + +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.ChatFormatting; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.components.Button; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.network.chat.TextColor; + +/** + * Copied from Cloth Config Lite + * ... + */ +public class BaseWidget extends Option { + + public static final int resetButtonOffset = 48; + private final Button resetButton = addChild(Button.builder(Component.literal("Reset"), this::onResetPressed).size(46, 20).build()); + private boolean hideReset = false; + + private boolean isSubConfig = false; + + private void onResetPressed(Button button) { + value = defaultValue.get(); + reset(); + } + + public void hideReset() { + this.hideReset = true; + } + + public boolean isSubConfig() { + return isSubConfig; + } + + public void setSubConfig(boolean subConfig) { + isSubConfig = subConfig; + } + + @Override + public void render(Minecraft minecraft, Font font, int x, int y, int width, int height, PoseStack matrices, int mouseX, int mouseY, float delta) { + MutableComponent text = Component.literal(this.text.getString()); + boolean edited = isEdited() || hasErrors; + if (edited) { + text.withStyle(ChatFormatting.ITALIC); + if (hasErrors) { + text.withStyle(style -> style.withColor(TextColor.fromRgb(16733525))); + } + } else { + text.withStyle(ChatFormatting.GRAY); + } + font.draw(matrices, text, x, y, 0xFFFFFF); + resetButton.setX(x + width - 46); + resetButton.setY(y + 1); + resetButton.active = isNotDefault(); + if (!hideReset) { + resetButton.render(matrices, mouseX, mouseY, delta); + } + } +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/InternalConfigButton.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/InternalConfigButton.java new file mode 100644 index 0000000..c4cf1e8 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/InternalConfigButton.java @@ -0,0 +1,52 @@ +package com.hypherionmc.craterlib.client.gui.config.widgets; + +import com.hypherionmc.craterlib.client.gui.config.CraterConfigScreen; +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.gui.components.AbstractButton; +import net.minecraft.client.gui.narration.NarratedElementType; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.NotNull; + +/** + * @author HypherionSA + */ +public class InternalConfigButton extends AbstractButton { + + CraterConfigScreen screen; + boolean cancel; + + public InternalConfigButton(CraterConfigScreen screen, int i, int j, int k, int l, Component component, boolean cancel) { + super(i, j, k, l, component); + this.screen = screen; + this.cancel = cancel; + } + + @Override + public void render(@NotNull PoseStack poseStack, int i, int j, float f) { + if (cancel) { + setMessage(Component.translatable(screen.isEdited() ? "t.clc.cancel_discard" : "gui.cancel")); + } else { + boolean hasErrors = screen.hasErrors(); + active = screen.isEdited() && !hasErrors; + setMessage(Component.translatable(hasErrors ? "t.clc.error" : "t.clc.save")); + } + super.render(poseStack, i, j, f); + } + + @Override + protected void updateWidgetNarration(NarrationElementOutput narrationElementOutput) { + narrationElementOutput.add(NarratedElementType.USAGE, getMessage()); + } + + @Override + public void onPress() { + if (cancel) { + screen.onClose(); + } else { + screen.save(); + } + } + + +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/Option.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/Option.java new file mode 100644 index 0000000..9573bd0 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/Option.java @@ -0,0 +1,71 @@ +package com.hypherionmc.craterlib.client.gui.config.widgets; + +import com.mojang.blaze3d.vertex.PoseStack; +import lombok.Getter; +import lombok.Setter; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.components.events.AbstractContainerEventHandler; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Supplier; + +/** + * Copied from Cloth Config Lite + * ... + */ +public abstract class Option extends AbstractContainerEventHandler { + + public Component text; + @Nullable + public Supplier defaultValue; + public Consumer savingConsumer; + public T originalValue; + public T value; + public boolean hasErrors; + public List children = new ArrayList<>(); + @Setter + @Getter + private List langKeys = new ArrayList<>(); + + public abstract void render(Minecraft minecraft, Font font, int x, int y, int width, int height, PoseStack matrices, int mouseX, int mouseY, float delta); + + public int height() { + return 22; + } + + @Override + public List children() { + return children; + } + + protected R addChild(R listener) { + ((List) children).add(listener); + return listener; + } + + public void onAdd() { + } + + protected void reset() { + onAdd(); + } + + public boolean isEdited() { + return !Objects.equals(originalValue, value); + } + + protected boolean isNotDefault() { + return defaultValue != null && !Objects.equals(defaultValue.get(), value); + } + + public void save() { + savingConsumer.accept(value); + } +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/SubConfigWidget.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/SubConfigWidget.java new file mode 100644 index 0000000..c284781 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/SubConfigWidget.java @@ -0,0 +1,40 @@ +package com.hypherionmc.craterlib.client.gui.config.widgets; + +import com.hypherionmc.craterlib.client.gui.config.CraterConfigScreen; +import com.hypherionmc.craterlib.core.config.ModuleConfig; +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; + +/** + * @author HypherionSA + */ +public class SubConfigWidget extends AbstractConfigWidget { + + private final Object subConfig; + private final ModuleConfig config; + private final Screen screen; + + public SubConfigWidget(ModuleConfig config, Screen screen, Object subConfig) { + this.config = config; + this.subConfig = subConfig; + this.screen = screen; + + this.widget = addChild(Button.builder(Component.translatable("t.clc.opensubconfig"), this::openSubConfig).size(200, buttonHeight).build()); + } + + @Override + public void render(Minecraft minecraft, Font font, int x, int y, int width, int height, PoseStack matrices, int mouseX, int mouseY, float delta) { + this.text = Component.literal(subConfig.getClass().getSimpleName().toLowerCase()); + this.hideReset(); + super.render(minecraft, font, x, y, width, height, matrices, mouseX, mouseY, delta); + } + + private void openSubConfig(Button button) { + Minecraft.getInstance().setScreen(new CraterConfigScreen(config, screen, subConfig)); + } + +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/TextConfigOption.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/TextConfigOption.java new file mode 100644 index 0000000..192da97 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/TextConfigOption.java @@ -0,0 +1,45 @@ +package com.hypherionmc.craterlib.client.gui.config.widgets; + +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; + +import java.util.function.Function; + +/** + * Copied from Cloth Config Lite + * ... + */ +public class TextConfigOption extends AbstractConfigWidget { + + private final Function toString; + private final Function fromString; + + public TextConfigOption(Function toString, Function fromString) { + this.toString = toString; + this.fromString = fromString; + this.widget = addChild(new WrappedEditBox(Minecraft.getInstance().font, 0, 0, 198, 18, null)); + } + + @Override + public void onAdd() { + widget.setMaxLength(1000000); + widget.setValue(toString.apply(value)); + widget.setResponder(this::update); + } + + @Override + public void render(Minecraft minecraft, Font font, int x, int y, int width, int height, PoseStack matrices, int mouseX, int mouseY, float delta) { + widget.setTextColor(hasErrors ? 16733525 : 14737632); + super.render(minecraft, font, x, y, width, height, matrices, mouseX, mouseY, delta); + } + + private void update(String s) { + try { + this.value = fromString.apply(s); + this.hasErrors = false; + } catch (Exception e) { + this.hasErrors = true; + } + } +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/ToggleButton.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/ToggleButton.java new file mode 100644 index 0000000..03ea11d --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/ToggleButton.java @@ -0,0 +1,33 @@ +package com.hypherionmc.craterlib.client.gui.config.widgets; + +import net.minecraft.client.gui.components.Button; +import net.minecraft.network.chat.Component; + +import java.util.List; +import java.util.function.Function; + +/** + * Copied from Cloth Config Lite + * ... + */ +public class ToggleButton extends AbstractConfigWidget { + + private final List options; + private final Function toComponent; + + public ToggleButton(List options, Function toComponent) { + this.options = options; + this.toComponent = toComponent; + this.widget = addChild(Button.builder(Component.empty(), this::switchNext).size(buttonWidth, buttonHeight).build()); + } + + @Override + public void onAdd() { + widget.setMessage(toComponent.apply(value)); + } + + private void switchNext(Button button) { + value = options.get((options.indexOf(value) + 1) % options.size()); + onAdd(); + } +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/WrappedEditBox.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/WrappedEditBox.java new file mode 100644 index 0000000..42c0c0c --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/WrappedEditBox.java @@ -0,0 +1,29 @@ +package com.hypherionmc.craterlib.client.gui.config.widgets; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.components.EditBox; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.NotNull; + +/** + * @author HypherionSA + */ +public class WrappedEditBox extends EditBox { + + public WrappedEditBox(Font font, int i, int j, int k, int l, @NotNull Component component) { + super(font, i, j, k, l, component); + } + + @Override + public void setFocused(boolean bl) { + for (GuiEventListener child : Minecraft.getInstance().screen.children()) { + if (child instanceof TextConfigOption option) { + WrappedEditBox box = option.widget; + super.setFocused(box == this); + } + } + super.setFocused(bl); + } +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/client/mentions/MentionCondition.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/client/mentions/MentionCondition.java new file mode 100644 index 0000000..e8bffde --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/client/mentions/MentionCondition.java @@ -0,0 +1,13 @@ +package com.hypherionmc.craterlib.client.mentions; + +/** + * Based on ... + */ +@FunctionalInterface +public interface MentionCondition { + + boolean shouldAddMention(String currentWord); + + MentionCondition ALWAYS = currentWord -> true; + +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/client/mentions/MentionsController.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/client/mentions/MentionsController.java new file mode 100644 index 0000000..bad3b43 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/client/mentions/MentionsController.java @@ -0,0 +1,47 @@ +package com.hypherionmc.craterlib.client.mentions; + +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; +import lombok.Getter; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Based on ... + */ +public class MentionsController { + + private static final Map> mentions = new LinkedHashMap<>(); + private static final Map mentionConditions = new LinkedHashMap<>(); + @Getter + private static boolean lastMentionConditional = true; + + public static void registerMention(ResourceIdentifier mentionClass, Collection suggestions, MentionCondition condition) { + mentions.put(mentionClass, suggestions); + mentionConditions.put(mentionClass, condition); + } + + public static Collection getMentions(String currentWord) { + ArrayList applicableMentions = new ArrayList<>(); + lastMentionConditional = false; + + mentionConditions.forEach((mention, condition) -> { + boolean shouldSuggest = condition.shouldAddMention(currentWord); + if (!shouldSuggest) return; + + if (!lastMentionConditional && condition != MentionCondition.ALWAYS) { + lastMentionConditional = true; + } + + applicableMentions.addAll(mentions.get(mention)); + }); + + return applicableMentions; + } + + public static boolean hasMentions() { + return !mentions.isEmpty(); + } +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/config/ConfigController.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/config/ConfigController.java new file mode 100644 index 0000000..41c9471 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/config/ConfigController.java @@ -0,0 +1,49 @@ +package com.hypherionmc.craterlib.core.config; + +import com.hypherionmc.craterlib.CraterConstants; +import lombok.Getter; +import me.hypherionmc.moonconfig.core.file.FileWatcher; +import org.jetbrains.annotations.ApiStatus; + +import java.io.Serializable; +import java.util.HashMap; + +/** + * @author HypherionSA + * Controls Config File Reloads and Events + */ +public final class ConfigController implements Serializable { + + /** + * Cache of registered configs + */ + @Getter + private static final HashMap monitoredConfigs = new HashMap<>(); + + /** + * INTERNAL METHOD - Register and watch the config + * + * @param config - The config class to register and watch + */ + @ApiStatus.Internal + public static void register_config(ModuleConfig config) { + if (monitoredConfigs.containsKey(config)) { + CraterConstants.LOG.error("Failed to register " + config.getConfigPath().getName() + ". Config already registered"); + } else { + FileWatcher configWatcher = new FileWatcher(); + try { + configWatcher.setWatch(config.getConfigPath(), () -> { + if (!config.isSaveCalled()) { + CraterConstants.LOG.info("Sending Reload Event for: " + config.getConfigPath().getName()); + config.configReloaded(); + } + }); + } catch (Exception e) { + CraterConstants.LOG.error("Failed to register " + config.getConfigPath().getName() + " for auto reloading. " + e.getMessage()); + } + monitoredConfigs.put(config, configWatcher); + CraterConstants.LOG.info("Registered " + config.getConfigPath().getName() + " successfully!"); + } + } + +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/config/ModuleConfig.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/config/ModuleConfig.java new file mode 100644 index 0000000..181efdc --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/config/ModuleConfig.java @@ -0,0 +1,184 @@ +package com.hypherionmc.craterlib.core.config; + +import me.hypherionmc.moonconfig.core.CommentedConfig; +import me.hypherionmc.moonconfig.core.Config; +import me.hypherionmc.moonconfig.core.conversion.ObjectConverter; +import me.hypherionmc.moonconfig.core.file.CommentedFileConfig; + +import java.io.File; + +/** + * @author HypherionSA + * Base Config class containing the save, upgrading and loading logic. + * All config classes must extend this class + */ +public class ModuleConfig { + + /* Final Variables */ + private final transient File configPath; + private final transient String networkID; + + private final transient String configName; + + private final transient String modId; + + private transient boolean isSaveCalled = false; + + /** + * Set up the config + * + * @param modId - The ID of the Mod/Module the config belongs to + * @param configName - The name of the config file, excluding extension + */ + public ModuleConfig(String modId, String configName) { + this(modId, "", configName); + } + + public ModuleConfig(String modId, String subFolder, String configName) { + /* Preserve the order of the config values */ + Config.setInsertionOrderPreserved(true); + + /* Configure Paths and Network SYNC ID */ + File configDir = new File("config" + (subFolder.isEmpty() ? "" : File.separator + subFolder)); + configPath = new File(configDir + File.separator + configName + ".toml"); + networkID = modId + ":conf_" + configName.replace("-", "_"); + this.modId = modId; + this.configName = configName; + + /* Check if the required directories exists, otherwise we create them */ + if (!configDir.exists()) { + configDir.mkdirs(); + } + } + + /** + * This method has to be called in the config constructor. This creates or upgrades the config file as needed + * + * @param config - The config class to use + */ + public void registerAndSetup(ModuleConfig config) { + if (!configPath.exists() || configPath.length() < 2) { + saveConfig(config); + } else { + migrateConfig(config); + } + /* Register the Config for Watching and events */ + ConfigController.register_config(this); + this.configReloaded(); + } + + /** + * Save the config to the disk + * + * @param conf - The config class to serialize and save + */ + public void saveConfig(ModuleConfig conf) { + this.isSaveCalled = true; + /* Set up the Serializer and Config Object */ + ObjectConverter converter = new ObjectConverter(); + CommentedFileConfig config = CommentedFileConfig.builder(configPath).build(); + + /* Save the config and fire the reload events */ + converter.toConfig(conf, config); + config.save(); + configReloaded(); + this.isSaveCalled = false; + } + + /** + * Load the config from the file into a Class + * + * @param conf - The config Class to load + * @return - Returns the loaded version of the class + */ + public T loadConfig(Object conf) { + /* Set up the Serializer and Config Object */ + ObjectConverter converter = new ObjectConverter(); + CommentedFileConfig config = CommentedFileConfig.builder(configPath).build(); + config.load(); + + /* Load the config and return the loaded config */ + converter.toObject(config, conf); + return (T) conf; + } + + /** + * INTERNAL METHOD - Upgrades the config files in the events the config structure changes + * + * @param conf - The config class to load + */ + public void migrateConfig(ModuleConfig conf) { + /* Set up the Serializer and Config Objects */ + CommentedFileConfig config = CommentedFileConfig.builder(configPath).build(); + CommentedFileConfig newConfig = CommentedFileConfig.builder(configPath).build(); + config.load(); + + /* Upgrade the config */ + new ObjectConverter().toConfig(conf, newConfig); + updateConfigValues(config, newConfig, newConfig, ""); + newConfig.save(); + + config.close(); + newConfig.close(); + } + + public void updateConfigValues(CommentedConfig oldConfig, CommentedConfig newConfig, CommentedConfig outputConfig, String subKey) { + /* Loop over the config keys and check what has changed */ + newConfig.valueMap().forEach((key, value) -> { + String finalKey = subKey + (subKey.isEmpty() ? "" : ".") + key; + if (value instanceof CommentedConfig commentedConfig) { + updateConfigValues(oldConfig, commentedConfig, outputConfig, finalKey); + } else { + outputConfig.set(finalKey, + oldConfig.contains(finalKey) ? oldConfig.get(finalKey) : value); + } + }); + } + + /** + * Get the location of the config file + * + * @return - The FILE object containing the config file + */ + public File getConfigPath() { + return configPath; + } + + /** + * Get the NETWORK SYNC ID + * + * @return - Returns the Sync ID in format modid:config_name + */ + public String getNetworkID() { + return networkID; + } + + /** + * Fired whenever changes to the config are detected + */ + public void configReloaded() { + + } + + /** + * Get the name of the Config File + * + * @return + */ + public String getConfigName() { + return configName; + } + + /** + * Get the MODID of the Module the config is registered to + * + * @return + */ + public String getModId() { + return modId; + } + + public boolean isSaveCalled() { + return isSaveCalled; + } +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/HideFromScreen.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/HideFromScreen.java new file mode 100644 index 0000000..512a025 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/HideFromScreen.java @@ -0,0 +1,8 @@ +package com.hypherionmc.craterlib.core.config.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface HideFromScreen { +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/NoConfigScreen.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/NoConfigScreen.java new file mode 100644 index 0000000..4288ee0 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/NoConfigScreen.java @@ -0,0 +1,12 @@ +package com.hypherionmc.craterlib.core.config.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * @author HypherionSA + * Allows Modules to disable Automatic Config Screens + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface NoConfigScreen { +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/SubConfig.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/SubConfig.java new file mode 100644 index 0000000..e3ec808 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/SubConfig.java @@ -0,0 +1,16 @@ +package com.hypherionmc.craterlib.core.config.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author HypherionSA + * Used to determine if a Config section should be rendered as a separate screen + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface SubConfig { +} + diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/Syncable.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/Syncable.java new file mode 100644 index 0000000..838b35b --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/Syncable.java @@ -0,0 +1,15 @@ +package com.hypherionmc.craterlib.core.config.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author HypherionSA + * //TODO Currently unused, but to be used with Config Syncing in the future + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Syncable { +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/Tooltip.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/Tooltip.java new file mode 100644 index 0000000..538471c --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/Tooltip.java @@ -0,0 +1,16 @@ +package com.hypherionmc.craterlib.core.config.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author HypherionSA + * Provides tooltips to the config GUI + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Tooltip { + String[] value(); +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEvent.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEvent.java new file mode 100644 index 0000000..89ff00a --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEvent.java @@ -0,0 +1,30 @@ +package com.hypherionmc.craterlib.core.event; + +import com.hypherionmc.craterlib.core.event.annot.Cancellable; +import com.hypherionmc.craterlib.core.event.exception.CraterEventCancellationException; + +public class CraterEvent { + + private boolean canceled = false; + + private boolean canCancel() { + return this.getClass().isAnnotationPresent(Cancellable.class); + } + + public void cancelEvent() { + try { + if (!this.canCancel()) { + throw new CraterEventCancellationException("Tried to cancel non-cancelable event: " + this.getClass().getName()); + } + + this.canceled = true; + } catch (Exception e) { + e.printStackTrace(); + } + } + + public boolean wasCancelled() { + return this.canceled; + } + +} \ No newline at end of file diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEventBus.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEventBus.java new file mode 100644 index 0000000..74eeecc --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEventBus.java @@ -0,0 +1,241 @@ +package com.hypherionmc.craterlib.core.event; + +import com.hypherionmc.craterlib.CraterConstants; +import com.hypherionmc.craterlib.core.event.annot.CraterEventListener; +import org.slf4j.Logger; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.*; +import java.util.function.Consumer; + +public final class CraterEventBus { + + public static final CraterEventBus INSTANCE = new CraterEventBus(); + private static final Logger LOGGER = CraterConstants.LOG; + private final Map, List> events = new HashMap<>(); + + public void postEvent(CraterEvent event) { + if (eventsRegisteredForType(event.getClass())) { + List l = new ArrayList<>(events.get(event.getClass())); + l.sort((o1, o2) -> Integer.compare(o2.priority, o1.priority)); + + for (ListenerContainer c : l) { + c.notifyListener(event); + } + } + } + + public void registerEventListener(Class clazz) { + this.registerListenerMethods(this.getEventMethodsOf(clazz)); + } + + public void registerEventListener(Object object) { + this.registerListenerMethods(this.getEventMethodsOf(object)); + } + + private void registerListenerMethods(List methods) { + for (EventMethod m : methods) { + Consumer listener = (event) -> { + try { + m.method.invoke(m.parentObject, event); + } catch (Exception e) { + throw new RuntimeException(e); + } + }; + + ListenerContainer container = new ListenerContainer(m.eventType, listener, m.priority); + container.listenerParentClassName = m.parentClass.getName(); + container.listenerMethodName = m.method.getName(); + this.registerListener(container); + } + } + + private List getEventMethodsOf(Object objectOrClass) { + List l = new ArrayList<>(); + try { + if (objectOrClass != null) { + boolean isClass = (objectOrClass instanceof Class); + Class c = isClass ? (Class) objectOrClass : objectOrClass.getClass(); + for (Method m : c.getMethods()) { + if (isClass && Modifier.isStatic(m.getModifiers())) { + EventMethod em = EventMethod.tryCreateFrom(new AnalyzedMethod(m, c)); + if ((em != null) && this.hasEventAnnotation(em)) l.add(em); + } + if (!isClass && !Modifier.isStatic(m.getModifiers())) { + EventMethod em = EventMethod.tryCreateFrom(new AnalyzedMethod(m, objectOrClass)); + if ((em != null) && this.hasEventAnnotation(em)) l.add(em); + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return l; + } + + private boolean hasEventAnnotation(EventMethod m) { + for (Annotation a : m.annotations) { + if (a instanceof CraterEventListener) return true; + } + return false; + } + + public void registerListener(Consumer listener, Class eventType) { + this.registerListener(listener, eventType, 0); + } + + public void registerListener(Consumer listener, Class eventType, int priority) { + this.registerListener(new ListenerContainer(eventType, listener, priority)); + } + + private void registerListener(ListenerContainer listenerContainer) { + try { + if (!eventsRegisteredForType(listenerContainer.eventType)) { + events.put(listenerContainer.eventType, new ArrayList<>()); + } + events.get(listenerContainer.eventType).add(listenerContainer); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public boolean eventsRegisteredForType(Class eventType) { + if (eventType == null) { + return false; + } + return this.events.containsKey(eventType); + } + + protected final static class ListenerContainer { + + private final Consumer listener; + private final Class eventType; + private final int priority; + private String listenerParentClassName = "[unknown]"; + private String listenerMethodName = "[unknown]"; + + private ListenerContainer(Class eventType, Consumer listener, int priority) { + this.eventType = eventType; + this.listener = listener; + this.priority = priority; + } + + private void notifyListener(CraterEvent event) { + try { + this.listener.accept(event); + } catch (Exception e) { + LOGGER.error("##################################"); + LOGGER.error("Failed to notify event listener!"); + LOGGER.error("Event Type: " + this.eventType.getName()); + LOGGER.error("Listener Parent Class Name: " + this.listenerParentClassName); + LOGGER.error("Listener Method Name In Parent Class: " + this.listenerMethodName); + LOGGER.error("##################################"); + e.printStackTrace(); + } + } + } + + protected static class AnalyzedMethod { + + protected Method method; + protected Object parentObject; + protected Class parentClass; + protected boolean isStatic; + protected List annotations = new ArrayList<>(); + + protected AnalyzedMethod() { + } + + protected AnalyzedMethod(Method method, Object parentObjectOrClass) { + this.method = method; + this.parentObject = parentObjectOrClass; + this.parentClass = this.tryGetParentClass(); + this.isStatic = Modifier.isStatic(method.getModifiers()); + collectMethodAnnotations(this.isStatic ? null : this.parentObject.getClass(), this.method, this.annotations); + } + + protected static void collectMethodAnnotations(Class c, Method m, List addToList) { + try { + addToList.addAll(Arrays.asList(m.getAnnotations())); + if (!Modifier.isStatic(m.getModifiers()) && (c != null)) { + Class sc = c.getSuperclass(); + if (sc != null) { + try { + Method sm = sc.getMethod(m.getName(), m.getParameterTypes()); + collectMethodAnnotations(sc, sm, addToList); + } catch (Exception ignored) { + } + } + } + } catch (Exception ignored) { + } + } + + protected Class tryGetParentClass() { + if (this.parentObject instanceof Class) { + return (Class) this.parentObject; + } + return this.parentObject.getClass(); + } + + } + + protected static class EventMethod extends AnalyzedMethod { + + protected final int priority; + protected final Class eventType; + + protected EventMethod(AnalyzedMethod method) { + + super(); + this.method = method.method; + this.parentObject = method.parentObject; + this.parentClass = method.parentClass; + this.isStatic = method.isStatic; + this.annotations = method.annotations; + + this.priority = this.tryGetPriority(); + this.eventType = this.tryGetEventType(); + + } + + protected static EventMethod tryCreateFrom(AnalyzedMethod method) { + EventMethod em = new EventMethod(method); + return (em.eventType != null) ? em : null; + } + + protected Class tryGetEventType() { + try { + if (this.method != null) { + Class[] params = this.method.getParameterTypes(); + if (params.length > 0) { + Class firstParam = params[0]; + if (CraterEvent.class.isAssignableFrom(firstParam)) { + return (Class) firstParam; + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + protected int tryGetPriority() { + try { + for (Annotation a : this.annotations) { + if (a instanceof CraterEventListener craterEventListener) { + return craterEventListener.priority(); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return 0; + } + + } + +} \ No newline at end of file diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEventPriority.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEventPriority.java new file mode 100644 index 0000000..0c97134 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEventPriority.java @@ -0,0 +1,13 @@ +package com.hypherionmc.craterlib.core.event; + +public class CraterEventPriority { + + public static final int LOWEST = -3; + public static final int LOWER = -2; + public static final int LOW = -1; + public static final int NORMAL = 0; + public static final int HIGH = 1; + public static final int HIGHER = 2; + public static final int HIGHEST = 3; + +} \ No newline at end of file diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/event/annot/Cancellable.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/event/annot/Cancellable.java new file mode 100644 index 0000000..ee3c2fc --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/event/annot/Cancellable.java @@ -0,0 +1,8 @@ +package com.hypherionmc.craterlib.core.event.annot; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface Cancellable { +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/event/annot/CraterEventListener.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/event/annot/CraterEventListener.java new file mode 100644 index 0000000..7b42490 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/event/annot/CraterEventListener.java @@ -0,0 +1,11 @@ +package com.hypherionmc.craterlib.core.event.annot; + +import com.hypherionmc.craterlib.core.event.CraterEventPriority; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface CraterEventListener { + int priority() default CraterEventPriority.NORMAL; +} \ No newline at end of file diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/event/exception/CraterEventCancellationException.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/event/exception/CraterEventCancellationException.java new file mode 100644 index 0000000..fe2a8a3 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/event/exception/CraterEventCancellationException.java @@ -0,0 +1,9 @@ +package com.hypherionmc.craterlib.core.event.exception; + +public class CraterEventCancellationException extends Exception { + + public CraterEventCancellationException(String msg) { + super(msg); + } + +} \ No newline at end of file diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/event/package-info.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/event/package-info.java new file mode 100644 index 0000000..5ec1749 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/event/package-info.java @@ -0,0 +1,5 @@ +/** + * The event system code in this package is based on, and adapted from Acara (https://github.com/Keksuccino/acara/) + * and is licensed under MIT by Keksuccino + */ +package com.hypherionmc.craterlib.core.event; \ No newline at end of file diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/networking/CraterPacketNetwork.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/networking/CraterPacketNetwork.java new file mode 100644 index 0000000..081c8fb --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/networking/CraterPacketNetwork.java @@ -0,0 +1,43 @@ +package com.hypherionmc.craterlib.core.networking; + +import com.hypherionmc.craterlib.core.networking.data.PacketContext; +import com.hypherionmc.craterlib.nojang.network.BridgedFriendlyByteBuf; +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; +import lombok.Getter; + +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +@Getter +public class CraterPacketNetwork { + + private final PacketRegistry packetRegistry; + public static CraterPacketNetwork INSTANCE; + private static DeferredPacketRegistrar delayedHandler; + + public CraterPacketNetwork(PacketRegistry registry) { + INSTANCE = this; + this.packetRegistry = registry; + getDelayedHandler().registerQueuedPackets(registry); + } + + private static DeferredPacketRegistrar getDelayedHandler() { + if (delayedHandler == null) { + delayedHandler = new DeferredPacketRegistrar(); + } + return delayedHandler; + } + + public static PacketRegistrar registerPacket(ResourceIdentifier id, Class messageType, BiConsumer encoder, Function decoder, Consumer> handler) { + if (INSTANCE != null) { + return INSTANCE.packetRegistry.registerPacket(id, messageType, encoder, decoder, handler); + } else { + return getDelayedHandler().registerPacket(id, messageType, encoder, decoder, handler); + } + } + +} \ No newline at end of file diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/networking/DeferredPacketRegistrar.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/networking/DeferredPacketRegistrar.java new file mode 100644 index 0000000..25556f4 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/networking/DeferredPacketRegistrar.java @@ -0,0 +1,41 @@ +package com.hypherionmc.craterlib.core.networking; + +import com.hypherionmc.craterlib.core.networking.data.PacketContext; +import com.hypherionmc.craterlib.core.networking.data.PacketHolder; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.nojang.network.BridgedFriendlyByteBuf; +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public class DeferredPacketRegistrar implements PacketRegistrar { + + private static final Map, PacketHolder> QUEUED_PACKET_MAP = new HashMap<>(); + + @Override + public PacketSide side() { + return PacketSide.CLIENT; + } + + @Override + public PacketRegistrar registerPacket(ResourceIdentifier packetIdentifier, Class messageType, BiConsumer encoder, Function decoder, Consumer> handler) { + PacketHolder container = new PacketHolder<>(packetIdentifier, messageType, encoder, decoder, handler); + QUEUED_PACKET_MAP.put(messageType, container); + return this; + } + + + public void registerQueuedPackets(PacketRegistry packetRegistration) { + if (!QUEUED_PACKET_MAP.isEmpty()) { + packetRegistration.PACKET_MAP.putAll(QUEUED_PACKET_MAP); + QUEUED_PACKET_MAP.forEach((aClass, container) -> packetRegistration.registerPacket(container)); + } + } +} \ No newline at end of file diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/networking/PacketRegistrar.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/networking/PacketRegistrar.java new file mode 100644 index 0000000..3ea55fa --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/networking/PacketRegistrar.java @@ -0,0 +1,21 @@ +package com.hypherionmc.craterlib.core.networking; + +import com.hypherionmc.craterlib.core.networking.data.PacketContext; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.nojang.network.BridgedFriendlyByteBuf; +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; + +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public interface PacketRegistrar { + + PacketSide side(); + + PacketRegistrar registerPacket(ResourceIdentifier id, Class messageType, BiConsumer encoder, Function decoder, Consumer> handler); + +} \ No newline at end of file diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/networking/PacketRegistry.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/networking/PacketRegistry.java new file mode 100644 index 0000000..d1ec06c --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/networking/PacketRegistry.java @@ -0,0 +1,41 @@ +package com.hypherionmc.craterlib.core.networking; + +import com.hypherionmc.craterlib.api.networking.CraterNetworkHandler; +import com.hypherionmc.craterlib.core.networking.data.PacketContext; +import com.hypherionmc.craterlib.core.networking.data.PacketHolder; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.nojang.network.BridgedFriendlyByteBuf; +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public abstract class PacketRegistry implements CraterNetworkHandler, PacketRegistrar { + + protected final Map, PacketHolder> PACKET_MAP = new HashMap<>(); + + protected final PacketSide side; + + public PacketRegistry(PacketSide side) { + this.side = side; + } + + public PacketRegistrar registerPacket(ResourceIdentifier id, Class messageType, BiConsumer encoder, Function decoder, Consumer> handler) { + PacketHolder holder = new PacketHolder<>(id, messageType, encoder, decoder, handler); + PACKET_MAP.put(messageType, holder); + registerPacket(holder); + return this; + } + + public PacketSide side() { + return side; + } + + protected abstract void registerPacket(PacketHolder packetHolder); +} \ No newline at end of file diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketContext.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketContext.java new file mode 100644 index 0000000..fc432dc --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketContext.java @@ -0,0 +1,15 @@ +package com.hypherionmc.craterlib.core.networking.data; + +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import org.jetbrains.annotations.Nullable; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public record PacketContext(@Nullable BridgedPlayer sender, T message, PacketSide side) { + + public PacketContext(T message, PacketSide side) { + this(null, message, side); + } + +} \ No newline at end of file diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketHolder.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketHolder.java new file mode 100644 index 0000000..14b6947 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketHolder.java @@ -0,0 +1,18 @@ +package com.hypherionmc.craterlib.core.networking.data; + +import com.hypherionmc.craterlib.nojang.network.BridgedFriendlyByteBuf; +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; + +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public record PacketHolder(ResourceIdentifier type, + Class messageType, + BiConsumer encoder, + Function decoder, + Consumer> handler) { +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketSide.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketSide.java new file mode 100644 index 0000000..ad6f8ec --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketSide.java @@ -0,0 +1,13 @@ +package com.hypherionmc.craterlib.core.networking.data; + +public enum PacketSide { + CLIENT, + SERVER; + + public PacketSide flipped() { + if (CLIENT.equals(this)) + return SERVER; + + return CLIENT; + } +} \ No newline at end of file diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/platform/ClientPlatform.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/platform/ClientPlatform.java new file mode 100644 index 0000000..089fb36 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/platform/ClientPlatform.java @@ -0,0 +1,23 @@ +package com.hypherionmc.craterlib.core.platform; + +import com.hypherionmc.craterlib.nojang.client.BridgedMinecraft; +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedClientLevel; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.InternalServiceUtil; +import net.minecraft.network.Connection; + +/** + * @author HypherionSA + */ +public interface ClientPlatform { + + public final ClientPlatform INSTANCE = InternalServiceUtil.load(ClientPlatform.class); + + BridgedMinecraft getClientInstance(); + + BridgedPlayer getClientPlayer(); + + BridgedClientLevel getClientLevel(); + + Connection getClientConnection(); +} \ No newline at end of file diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/platform/CommonPlatform.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/platform/CommonPlatform.java new file mode 100644 index 0000000..ee7f944 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/platform/CommonPlatform.java @@ -0,0 +1,15 @@ +package com.hypherionmc.craterlib.core.platform; + +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import com.hypherionmc.craterlib.utils.InternalServiceUtil; + +/** + * @author HypherionSA + */ +public interface CommonPlatform { + + public CommonPlatform INSTANCE = InternalServiceUtil.load(CommonPlatform.class); + + BridgedMinecraftServer getMCServer(); + +} \ No newline at end of file diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/platform/CompatUtils.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/platform/CompatUtils.java new file mode 100644 index 0000000..fb745fc --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/platform/CompatUtils.java @@ -0,0 +1,13 @@ +package com.hypherionmc.craterlib.core.platform; + +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.InternalServiceUtil; + +public interface CompatUtils { + + public static final CompatUtils INSTANCE = InternalServiceUtil.load(CompatUtils.class); + + boolean isPlayerActive(BridgedPlayer player); + String getSkinUUID(BridgedPlayer player); + +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/platform/Environment.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/platform/Environment.java new file mode 100644 index 0000000..2ef219e --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/platform/Environment.java @@ -0,0 +1,18 @@ +package com.hypherionmc.craterlib.core.platform; + +/** + * @author HypherionSA + */ +public enum Environment { + CLIENT, + SERVER, + UNKNOWN; + + public boolean isClient() { + return this == CLIENT; + } + + public boolean isServer() { + return this == SERVER; + } +} \ No newline at end of file diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/platform/ModloaderEnvironment.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/platform/ModloaderEnvironment.java new file mode 100644 index 0000000..c1bc176 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/platform/ModloaderEnvironment.java @@ -0,0 +1,32 @@ +package com.hypherionmc.craterlib.core.platform; + +import com.hypherionmc.craterlib.utils.InternalServiceUtil; + +import java.io.File; + +/** + * @author HypherionSA + * Helper class to provide information about the ModLoader + */ +public interface ModloaderEnvironment { + + public final ModloaderEnvironment INSTANCE = InternalServiceUtil.load(ModloaderEnvironment.class); + + boolean isFabric(); + + String getGameVersion(); + + File getGameFolder(); + + File getConfigFolder(); + + File getModsFolder(); + + Environment getEnvironment(); + + boolean isModLoaded(String modid); + + boolean isDevEnv(); + + int getModCount(); +} \ No newline at end of file diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordEventHandlers.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordEventHandlers.java new file mode 100644 index 0000000..3f1ffcb --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordEventHandlers.java @@ -0,0 +1,90 @@ +package com.hypherionmc.craterlib.core.rpcsdk; + +import com.hypherionmc.craterlib.core.rpcsdk.callbacks.*; +import com.sun.jna.Structure; + +import java.util.Arrays; +import java.util.List; + +/** + * @author HypherionSA + * Class containing references to all available discord event handles. + * Registering a handler is optional, and non-assigned handlers will be ignored + */ +public class DiscordEventHandlers extends Structure { + + // Callback for when the RPC was initialized successfully + public ReadyCallback ready; + + // Callback for when the Discord connection was ended + public DisconnectedCallback disconnected; + + // Callback for when a Discord Error occurs + public ErroredCallback errored; + + // Callback for when a player joins the game + public JoinGameCallback joinGame; + + // Callback for when a player spectates the game + public SpectateGameCallback spectateGame; + + // Callback for when a players request to join your game + public JoinRequestCallback joinRequest; + + /** + * DO NOT TOUCH THIS... EVER! + */ + @Override + protected List getFieldOrder() { + return Arrays.asList( + "ready", + "disconnected", + "errored", + "joinGame", + "spectateGame", + "joinRequest" + ); + } + + public static class Builder { + private final DiscordEventHandlers handlers; + + public Builder() { + this.handlers = new DiscordEventHandlers(); + } + + public Builder ready(ReadyCallback readyCallback) { + handlers.ready = readyCallback; + return this; + } + + public Builder disconnected(DisconnectedCallback disconnectedCallback) { + handlers.disconnected = disconnectedCallback; + return this; + } + + public Builder errored(ErroredCallback erroredCallback) { + handlers.errored = erroredCallback; + return this; + } + + public Builder joinGame(JoinGameCallback joinGameCallback) { + handlers.joinGame = joinGameCallback; + return this; + } + + public Builder spectateGame(SpectateGameCallback spectateGameCallback) { + handlers.spectateGame = spectateGameCallback; + return this; + } + + public Builder joinRequest(JoinRequestCallback joinRequestCallback) { + handlers.joinRequest = joinRequestCallback; + return this; + } + + public DiscordEventHandlers build() { + return handlers; + } + } +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordRPC.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordRPC.java new file mode 100644 index 0000000..7cf53ff --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordRPC.java @@ -0,0 +1,99 @@ +package com.hypherionmc.craterlib.core.rpcsdk; + +import com.sun.jna.Library; +import com.sun.jna.Native; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author HypherionSA + * Java Wrapper of the Discord-RPC Library + */ +public interface DiscordRPC extends Library { + + DiscordRPC INSTANCE = Native.load("discord-rpc", DiscordRPC.class); + + /** + * Open a New RPC Connection + * + * @param applicationId The ID of the Application the RPC is tied to + * @param handlers Optional Event Callback Handlers + * @param autoRegister Auto Register the running game + * @param steamId Steam ID of the game + */ + void Discord_Initialize(@NotNull String applicationId, @Nullable DiscordEventHandlers handlers, boolean autoRegister, @Nullable String steamId); + + /** + * Shutdown the RPC instance and disconnect from discord + */ + void Discord_Shutdown(); + + /** + * Need to be called manually at least every 2 seconds, to allow RPC updates + * and callback handlers to fire + */ + void Discord_RunCallbacks(); + + /** + * Not sure about this. Believe it needs to be called manually in some circumstances + */ + void Discord_UpdateConnection(); + + /** + * Update the Rich Presence + * + * @param struct Constructed {@link DiscordRichPresence} + */ + void Discord_UpdatePresence(@Nullable DiscordRichPresence struct); + + /** + * Clear the current Rich Presence + */ + void Discord_ClearPresence(); + + /** + * Respond to Join/Spectate callback + * + * @param userid The Discord User ID of the user that initiated the request + * @param reply Reply to the request. See {@link DiscordReply} + */ + void Discord_Respond(@NotNull String userid, int reply); + + /** + * Replace the already registered {@link DiscordEventHandlers} + * + * @param handlers The new handlers to apply + */ + void Discord_UpdateHandlers(@Nullable DiscordEventHandlers handlers); + + /** + * Register the executable of the application/game + * Only applicable when autoRegister is set to false + * + * @param applicationId The Application ID + * @param command The Launch command of the game + *

+ * NB: THIS DOES NOT WORK WITH MINECRAFT + */ + void Discord_Register(String applicationId, String command); + + /** + * Register the Steam executable of the application/game + * + * @param applicationId The Application ID + * @param steamId The Steam ID of the application/game + */ + void Discord_RegisterSteamGame(String applicationId, String steamId); + + public enum DiscordReply { + NO(0), + YES(1), + IGNORE(2); + + public final int reply; + + DiscordReply(int reply) { + this.reply = reply; + } + } +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordRichPresence.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordRichPresence.java new file mode 100644 index 0000000..51fd720 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordRichPresence.java @@ -0,0 +1,248 @@ +package com.hypherionmc.craterlib.core.rpcsdk; + +import com.hypherionmc.craterlib.core.rpcsdk.helpers.RPCButton; +import com.sun.jna.Structure; +import org.jetbrains.annotations.NotNull; + +import java.time.OffsetDateTime; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * @author HypherionSA + * Class reprenting a Discord RPC activity + */ +public class DiscordRichPresence extends Structure { + + // First line of text on the RPC + public String state; + + // Second line of text on the RPC + public String details; + + // Time the activity started in UNIX-Timestamp format + public long startTimestamp; + + // Time the activity will end in UNIX-Timestamp format + public long endTimestamp; + + // URL or Asset key of the Large Image + public String largeImageKey; + + // Hover text to display when hovering the Large Image + public String largeImageText; + + // URL or Asset key of the Small Image + public String smallImageKey; + + // Hover text to display when hovering the Small Image + public String smallImageText; + + // Id of the player's party, lobby, or group. + public String partyId; + + // Current size of the player's party, lobby, or group. + public int partySize; + + // Maximum size of the player's party, lobby, or group. + public int partyMax; + + // Unused + public String partyPrivacy; + + // Unused. + public String matchSecret; + + // Unique hashed string for chat invitations and Ask to Join. + public String joinSecret; + + // Unique hashed string for Spectate button. + public String spectateSecret; + + // Label of the First RPC Button + public String button_label_1; + + // URL of the First RPC Button + public String button_url_1; + + // Label of the Second RPC Button + public String button_label_2; + + // URL of the Second RPC Button + public String button_url_2; + + // Unused + public int instance; + + public DiscordRichPresence() { + setStringEncoding("UTF-8"); + } + + /** + * DO NOT TOUCH THIS... EVER! + */ + @Override + protected List getFieldOrder() { + return Arrays.asList( + "state", + "details", + "startTimestamp", + "endTimestamp", + "largeImageKey", + "largeImageText", + "smallImageKey", + "smallImageText", + "partyId", + "partySize", + "partyMax", + "partyPrivacy", + "matchSecret", + "joinSecret", + "spectateSecret", + "button_label_1", + "button_url_1", + "button_label_2", + "button_url_2", + "instance" + ); + } + + public static class Builder { + private final DiscordRichPresence rpc; + + public Builder(String state) { + rpc = new DiscordRichPresence(); + + if (state != null && !state.isEmpty()) { + rpc.state = state.substring(0, Math.min(state.length(), 128)); + } + } + + public Builder setDetails(String details) { + if (details != null && !details.isEmpty()) { + rpc.details = details.substring(0, Math.min(details.length(), 128)); + } + return this; + } + + public Builder setStartTimestamp(long timestamp) { + rpc.startTimestamp = timestamp; + return this; + } + + public Builder setStartTimestamp(OffsetDateTime timestamp) { + rpc.startTimestamp = timestamp.toEpochSecond(); + return this; + } + + public Builder setEndTimestamp(long timestamp) { + rpc.endTimestamp = timestamp; + return this; + } + + public Builder setEndTimestamp(OffsetDateTime timestamp) { + rpc.endTimestamp = timestamp.toEpochSecond(); + return this; + } + + public Builder setLargeImage(String key) { + return this.setLargeImage(key, ""); + } + + public Builder setLargeImage(@NotNull String key, String text) { + // Null check used for users blatantly ignoring the NotNull marker + if ((text != null && !text.isEmpty()) && key != null) { + throw new IllegalArgumentException("Image key cannot be null when assigning a hover text"); + } + + rpc.largeImageKey = key; + rpc.largeImageText = text; + return this; + } + + public Builder setSmallImage(String key) { + return this.setSmallImage(key, ""); + } + + public Builder setSmallImage(@NotNull String key, String text) { + // Null check used for users blatantly ignoring the NotNull marker + if ((text != null && !text.isEmpty()) && key != null) { + throw new IllegalArgumentException("Image key cannot be null when assigning a hover text"); + } + + rpc.smallImageKey = key; + rpc.smallImageText = text; + return this; + } + + public Builder setParty(String party, int size, int max) { + // Buttons are present, ignore + if ((rpc.button_label_1 != null && rpc.button_label_1.isEmpty()) || (rpc.button_label_2 != null && rpc.button_label_2.isEmpty())) + return this; + + rpc.partyId = party; + rpc.partySize = size; + rpc.partyMax = max; + return this; + } + + public Builder setSecrets(String match, String join, String spectate) { + // Buttons are present, ignore + if ((rpc.button_label_1 != null && rpc.button_label_1.isEmpty()) || (rpc.button_label_2 != null && rpc.button_label_2.isEmpty())) + return this; + + rpc.matchSecret = match; + rpc.joinSecret = join; + rpc.spectateSecret = spectate; + return this; + } + + public Builder setSecrets(String join, String spectate) { + // Buttons are present, ignore + if ((rpc.button_label_1 != null && rpc.button_label_1.isEmpty()) || (rpc.button_label_2 != null && rpc.button_label_2.isEmpty())) + return this; + + rpc.joinSecret = join; + rpc.spectateSecret = spectate; + return this; + } + + public Builder setInstance(boolean i) { + // Buttons are present, ignore + if ((rpc.button_label_1 != null && rpc.button_label_1.isEmpty()) || (rpc.button_label_2 != null && rpc.button_label_2.isEmpty())) + return this; + + rpc.instance = i ? 1 : 0; + return this; + } + + public Builder setButtons(RPCButton button) { + return this.setButtons(Collections.singletonList(button)); + } + + public Builder setButtons(RPCButton button1, RPCButton button2) { + return this.setButtons(Arrays.asList(button1, button2)); + } + + public Builder setButtons(List rpcButtons) { + // Limit to 2 Buttons. Discord Limitation + if (rpcButtons != null && !rpcButtons.isEmpty()) { + int length = Math.min(rpcButtons.size(), 2); + rpc.button_label_1 = rpcButtons.get(0).getLabel(); + rpc.button_url_1 = rpcButtons.get(0).getUrl(); + + if (length == 2) { + rpc.button_label_2 = rpcButtons.get(1).getLabel(); + rpc.button_url_2 = rpcButtons.get(1).getUrl(); + } + } + + return this; + } + + public DiscordRichPresence build() { + return rpc; + } + } +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordUser.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordUser.java new file mode 100644 index 0000000..e8bc085 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordUser.java @@ -0,0 +1,39 @@ +package com.hypherionmc.craterlib.core.rpcsdk; + +import com.sun.jna.Structure; + +import java.util.Arrays; +import java.util.List; + +/** + * @author HypherionSA + * Class representing the Discord User + */ +public class DiscordUser extends Structure { + + // The User ID of the User + public String userId; + + // The Username of the User + public String username; + + // The unique identifier of the user. Discontinued by Discord + @Deprecated + public String discriminator; + + // The avatar has of the user + public String avatar; + + /** + * DO NOT TOUCH THIS... EVER! + */ + @Override + protected List getFieldOrder() { + return Arrays.asList( + "userId", + "username", + "discriminator", + "avatar" + ); + } +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/DisconnectedCallback.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/DisconnectedCallback.java new file mode 100644 index 0000000..7ea59e1 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/DisconnectedCallback.java @@ -0,0 +1,18 @@ +package com.hypherionmc.craterlib.core.rpcsdk.callbacks; + +import com.sun.jna.Callback; + +/** + * @author HypherionSA + * Callback for when the Discord RPC disconnects + */ +public interface DisconnectedCallback extends Callback { + + /** + * Called when RPC disconnected + * + * @param errorCode Error code if any + * @param message Details about the disconnection + */ + void apply(int errorCode, String message); +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/ErroredCallback.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/ErroredCallback.java new file mode 100644 index 0000000..1f86c90 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/ErroredCallback.java @@ -0,0 +1,18 @@ +package com.hypherionmc.craterlib.core.rpcsdk.callbacks; + +import com.sun.jna.Callback; + +/** + * @author HypherionSA + * Callback for when the RPC ran into an error + */ +public interface ErroredCallback extends Callback { + + /** + * Called when an RPC error occurs + * + * @param errorCode Error code if any + * @param message Details about the error + */ + void apply(int errorCode, String message); +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/JoinGameCallback.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/JoinGameCallback.java new file mode 100644 index 0000000..cc752af --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/JoinGameCallback.java @@ -0,0 +1,17 @@ +package com.hypherionmc.craterlib.core.rpcsdk.callbacks; + +import com.sun.jna.Callback; + +/** + * @author HypherionSA + * Callback for when someone was approved to join your game + */ +public interface JoinGameCallback extends Callback { + + /** + * Called when someone joins a game from {@link JoinRequestCallback} + * + * @param joinSecret Secret or Password required to let the player join the game + */ + void apply(String joinSecret); +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/JoinRequestCallback.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/JoinRequestCallback.java new file mode 100644 index 0000000..115fd4f --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/JoinRequestCallback.java @@ -0,0 +1,19 @@ +package com.hypherionmc.craterlib.core.rpcsdk.callbacks; + +import com.hypherionmc.craterlib.core.rpcsdk.DiscordUser; +import com.sun.jna.Callback; + +/** + * @author HypherionSA + * Callback for when someone requests to join your game + */ +public interface JoinRequestCallback extends Callback { + + /** + * Called when someone clicks on the Join Game button + * + * @param user The Discord User trying to join your game + * @see DiscordUser + */ + void apply(DiscordUser user); +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/ReadyCallback.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/ReadyCallback.java new file mode 100644 index 0000000..66f3b59 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/ReadyCallback.java @@ -0,0 +1,19 @@ +package com.hypherionmc.craterlib.core.rpcsdk.callbacks; + +import com.hypherionmc.craterlib.core.rpcsdk.DiscordUser; +import com.sun.jna.Callback; + +/** + * @author HypherionSA + * Callback for when the RPC has connected successfully + */ +public interface ReadyCallback extends Callback { + + /** + * Called when the RPC is connected and ready to be used + * + * @param user The user the RPC is displayed on + * @see DiscordUser + */ + void apply(DiscordUser user); +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/SpectateGameCallback.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/SpectateGameCallback.java new file mode 100644 index 0000000..979e53d --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/SpectateGameCallback.java @@ -0,0 +1,17 @@ +package com.hypherionmc.craterlib.core.rpcsdk.callbacks; + +import com.sun.jna.Callback; + +/** + * @author HypherionSA + * Callback for when someone is requesting to spectate your game + */ +public interface SpectateGameCallback extends Callback { + + /** + * Called when joining the game + * + * @param spectateSecret Secret or Password required to let the player spectate + */ + void apply(String spectateSecret); +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/helpers/RPCButton.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/helpers/RPCButton.java new file mode 100644 index 0000000..ab2bdc8 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/helpers/RPCButton.java @@ -0,0 +1,55 @@ +package com.hypherionmc.craterlib.core.rpcsdk.helpers; + +import org.jetbrains.annotations.NotNull; + +import java.io.Serializable; + +/** + * @author HypherionSA + * Helper class to add Buttons to Discord Rich Presence + * This can not be used with Join/Spectate + */ +public class RPCButton implements Serializable { + + // The label of the button + private final String label; + + // The URL the button will open when clicked + private final String url; + + protected RPCButton(String label, String url) { + this.label = label; + this.url = url; + } + + /** + * Create a new RPC Button + * + * @param label The label of the button + * @param url The URL the button will open when clicked + * @return The constructed button + */ + public static RPCButton create(@NotNull String label, @NotNull String url) { + // Null check used here for users blatantly ignoring the NotNull marker + if (label == null || label.isEmpty() || url == null || url.isEmpty()) { + throw new IllegalArgumentException("RPC Buttons require both a label and url"); + } + + label = label.substring(0, Math.min(label.length(), 31)); + return new RPCButton(label, url); + } + + /** + * @return The label assigned to the button + */ + public String getLabel() { + return label; + } + + /** + * @return The URL of the button + */ + public String getUrl() { + return url; + } +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/mixin/ChatInputSuggestorMixin.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/mixin/ChatInputSuggestorMixin.java new file mode 100644 index 0000000..c48dfbc --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/mixin/ChatInputSuggestorMixin.java @@ -0,0 +1,84 @@ +package com.hypherionmc.craterlib.mixin; + +import com.hypherionmc.craterlib.client.mentions.MentionsController; +import net.minecraft.client.gui.components.CommandSuggestions; +import net.minecraft.client.gui.components.EditBox; +import org.objectweb.asm.Opcodes; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.ModifyVariable; +import org.spongepowered.asm.mixin.injection.Slice; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * @author HypherionSA + * Allow Users, Roles and Channels to be pingable from MC chat (Client Side) + */ +@Mixin(CommandSuggestions.class) +public abstract class ChatInputSuggestorMixin { + + @Shadow + public abstract void showSuggestions(boolean p_93931_); + + @Shadow @Final + EditBox input; + + @Shadow + private static int getLastWordIndex(String p_93913_) { + return 0; + } + + @Inject( + method = "updateCommandInfo", + at = @At( + value = "FIELD", + target = "Lnet/minecraft/client/gui/components/CommandSuggestions;pendingSuggestions:Ljava/util/concurrent/CompletableFuture;", + opcode = Opcodes.PUTFIELD, + shift = At.Shift.AFTER, + ordinal = 0 + ), + slice = @Slice( + from = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/gui/components/CommandSuggestions;getLastWordIndex(Ljava/lang/String;)I" + ) + ) + ) + private void injectSuggestions(CallbackInfo ci) { + if (MentionsController.hasMentions() && MentionsController.isLastMentionConditional()) { + this.showSuggestions(true); + } + } + + @SuppressWarnings("InvalidInjectorMethodSignature") + @ModifyVariable(method = "updateCommandInfo", at = @At(value = "STORE"), ordinal = 0, name = "collection") + private Collection injectMentions(Collection vanilla) { + if (!MentionsController.hasMentions()) + return vanilla; + + ArrayList newSuggest = new ArrayList<>(vanilla); + + String currentInput = this.input.getValue(); + int currentCursorPosition = this.input.getCursorPosition(); + + String textBeforeCursor = currentInput.substring(0, currentCursorPosition); + int startOfCurrentWord = getLastWordIndex(textBeforeCursor); + + String currentWord = textBeforeCursor.substring(startOfCurrentWord); + String finalWord = currentWord.replace("[", "").replace("]", ""); + + Collection mentions = MentionsController.getMentions(finalWord); + + if (!mentions.isEmpty()) { + mentions.forEach(m -> newSuggest.add("[" + m + "]")); + } + + return newSuggest; + } +} \ No newline at end of file diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/CommandMixin.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/CommandMixin.java new file mode 100644 index 0000000..aeb2e22 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/CommandMixin.java @@ -0,0 +1,37 @@ +package com.hypherionmc.craterlib.mixin.events; + +import com.google.common.base.Throwables; +import com.hypherionmc.craterlib.api.events.server.CraterCommandEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.mojang.brigadier.ParseResults; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +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.CallbackInfoReturnable; + +@Mixin(Commands.class) +public class CommandMixin { + + @Inject(method = "performCommand", + at = @At(value = "INVOKE", + target = "Lcom/mojang/brigadier/CommandDispatcher;execute(Lcom/mojang/brigadier/ParseResults;)I", + shift = At.Shift.BEFORE + ), cancellable = true + ) + private void injectCommandEvent(ParseResults stackParseResults, String command, CallbackInfoReturnable cir) { + CraterCommandEvent commandEvent = CraterCommandEvent.of(stackParseResults, command); + CraterEventBus.INSTANCE.postEvent(commandEvent); + if (commandEvent.wasCancelled()) { + cir.setReturnValue(1); + return; + } + + if (commandEvent.getException() != null) { + Throwables.throwIfUnchecked(commandEvent.getException()); + cir.setReturnValue(1); + } + } + +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerAdvancementsMixin.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerAdvancementsMixin.java new file mode 100644 index 0000000..2457176 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerAdvancementsMixin.java @@ -0,0 +1,29 @@ +package com.hypherionmc.craterlib.mixin.events; + +import com.hypherionmc.craterlib.api.events.server.CraterAdvancementEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.advancements.BridgedAdvancement; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraft.advancements.Advancement; +import net.minecraft.server.PlayerAdvancements; +import net.minecraft.server.level.ServerPlayer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(PlayerAdvancements.class) +public class PlayerAdvancementsMixin { + + @Shadow + private ServerPlayer player; + + @Inject(method = "award", at = @At(value = "INVOKE", target = "Lnet/minecraft/advancements/AdvancementRewards;grant(Lnet/minecraft/server/level/ServerPlayer;)V", shift = At.Shift.AFTER)) + private void injectAdvancementEvent(Advancement advancement, String string, CallbackInfoReturnable cir) { + if (advancement.getDisplay() == null || !advancement.getDisplay().shouldAnnounceChat()) + return; + + CraterEventBus.INSTANCE.postEvent(new CraterAdvancementEvent(BridgedPlayer.of(this.player), BridgedAdvancement.of(advancement))); + } +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerListMixin.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerListMixin.java new file mode 100644 index 0000000..0aaa866 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerListMixin.java @@ -0,0 +1,52 @@ +package com.hypherionmc.craterlib.mixin.events; + +import com.hypherionmc.craterlib.api.events.server.CraterPlayerEvent; +import com.hypherionmc.craterlib.api.events.server.MessageBroadcastEvent; +import com.hypherionmc.craterlib.api.events.server.PlayerPreLoginEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.authlib.BridgedGameProfile; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.ChatUtils; +import com.mojang.authlib.GameProfile; +import net.minecraft.network.Connection; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.players.PlayerList; +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; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.net.SocketAddress; +import java.util.function.Function; + +@Mixin(PlayerList.class) +public class PlayerListMixin { + + @Inject(method = "broadcastSystemMessage(Lnet/minecraft/network/chat/Component;Ljava/util/function/Function;Z)V", at = @At("HEAD")) + private void injectBroadcastEvent(Component component, Function function, boolean bl, CallbackInfo ci) { + String thread = Thread.currentThread().getStackTrace()[3].getClassName(); + MessageBroadcastEvent event = new MessageBroadcastEvent(ChatUtils.mojangToAdventure(component), (f) -> ChatUtils.mojangToAdventure(component), bl, thread); + CraterEventBus.INSTANCE.postEvent(event); + } + + @Inject(method = "placeNewPlayer", at = @At("TAIL")) + private void injectPlayerLoginEvent(Connection arg, ServerPlayer serverPlayer, CallbackInfo ci) { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerEvent.PlayerLoggedIn(BridgedPlayer.of(serverPlayer))); + } + + @Inject(method = "remove", at = @At("HEAD")) + private void injectPlayerLogoutEvent(ServerPlayer player, CallbackInfo ci) { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerEvent.PlayerLoggedOut(BridgedPlayer.of(player))); + } + + @Inject(method = "canPlayerLogin", at = @At("HEAD"), cancellable = true) + private void injectPreLoginEvent(SocketAddress address, GameProfile gameProfile, CallbackInfoReturnable cir) { + PlayerPreLoginEvent event = new PlayerPreLoginEvent(address, BridgedGameProfile.of(gameProfile)); + CraterEventBus.INSTANCE.postEvent(event); + if (event.getMessage() != null) { + cir.setReturnValue(ChatUtils.adventureToMojang(event.getMessage())); + } + } +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerMixin.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerMixin.java new file mode 100644 index 0000000..5fd03bd --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerMixin.java @@ -0,0 +1,21 @@ +package com.hypherionmc.craterlib.mixin.events; + +import com.hypherionmc.craterlib.api.events.common.CraterPlayerDeathEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.player.Player; +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(Player.class) +public class PlayerMixin { + + @Inject(method = "die", at = @At("HEAD")) + private void injectPlayerDeathEvent(DamageSource damageSource, CallbackInfo ci) { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerDeathEvent(BridgedPlayer.of(((Player) (Object) this)), damageSource)); + } + +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/ServerPlayerMixin.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/ServerPlayerMixin.java new file mode 100644 index 0000000..ae028e7 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/ServerPlayerMixin.java @@ -0,0 +1,21 @@ +package com.hypherionmc.craterlib.mixin.events; + +import com.hypherionmc.craterlib.api.events.common.CraterPlayerDeathEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.damagesource.DamageSource; +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(ServerPlayer.class) +public class ServerPlayerMixin { + + @Inject(method = "die", at = @At("HEAD")) + private void injectPlayerDeathEvent(DamageSource damageSource, CallbackInfo ci) { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerDeathEvent(BridgedPlayer.of(((ServerPlayer) (Object) this)), damageSource)); + } + +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/ClientLevelMixin.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/ClientLevelMixin.java new file mode 100644 index 0000000..3535d6f --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/ClientLevelMixin.java @@ -0,0 +1,25 @@ +package com.hypherionmc.craterlib.mixin.events.client; + +import com.hypherionmc.craterlib.api.events.client.CraterSinglePlayerEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; +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(ClientLevel.class) +public class ClientLevelMixin { + + @Inject(method = "addEntity", at = @At("HEAD")) + private void injectSinglePlayerJoinEvent(int i, Entity entity, CallbackInfo ci) { + if (entity instanceof Player player) { + CraterSinglePlayerEvent.PlayerLogin playerLogin = new CraterSinglePlayerEvent.PlayerLogin(BridgedPlayer.of(player)); + CraterEventBus.INSTANCE.postEvent(playerLogin); + } + } + +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/MinecraftMixin.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/MinecraftMixin.java new file mode 100644 index 0000000..2903131 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/MinecraftMixin.java @@ -0,0 +1,31 @@ +package com.hypherionmc.craterlib.mixin.events.client; + +import com.hypherionmc.craterlib.api.events.client.ScreenEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.client.gui.BridgedScreen; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.Screen; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(Minecraft.class) +public class MinecraftMixin { + + @Shadow + @Nullable + public Screen screen; + + @Inject(method = "setScreen", at = @At(value = "TAIL")) + private void injectScreenOpeningEvent(Screen screen, CallbackInfo ci) { + Screen old = this.screen; + if (screen != null) { + ScreenEvent.Opening opening = new ScreenEvent.Opening(BridgedScreen.of(old), BridgedScreen.of(screen)); + CraterEventBus.INSTANCE.postEvent(opening); + } + } + +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/RealmsMainScreenMixin.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/RealmsMainScreenMixin.java new file mode 100644 index 0000000..d2445c3 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/RealmsMainScreenMixin.java @@ -0,0 +1,23 @@ +package com.hypherionmc.craterlib.mixin.events.client; + +import com.hypherionmc.craterlib.api.events.client.PlayerJoinRealmEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.realmsclient.dto.BridgedRealmsServer; +import com.mojang.realmsclient.RealmsMainScreen; +import com.mojang.realmsclient.dto.RealmsServer; +import net.minecraft.client.gui.screens.Screen; +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 RealmsMainScreenMixin { + + @Inject(at = @At("HEAD"), method = "play") + private void play(RealmsServer serverData, Screen arg2, CallbackInfo ci) { + PlayerJoinRealmEvent playerJoinRealm = new PlayerJoinRealmEvent(BridgedRealmsServer.of(serverData)); + CraterEventBus.INSTANCE.postEvent(playerJoinRealm); + } + +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/advancements/BridgedAdvancement.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/advancements/BridgedAdvancement.java new file mode 100644 index 0000000..6b8e376 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/advancements/BridgedAdvancement.java @@ -0,0 +1,21 @@ +package com.hypherionmc.craterlib.nojang.advancements; + +import lombok.RequiredArgsConstructor; +import net.minecraft.advancements.Advancement; + +import java.util.Optional; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedAdvancement { + + private final Advancement internal; + + public Optional displayInfo() { + if (internal.getDisplay() != null) { + return Optional.of(BridgedDisplayInfo.of(internal.getDisplay())); + } + + return Optional.empty(); + } + +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/advancements/BridgedDisplayInfo.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/advancements/BridgedDisplayInfo.java new file mode 100644 index 0000000..5a0c155 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/advancements/BridgedDisplayInfo.java @@ -0,0 +1,29 @@ +package com.hypherionmc.craterlib.nojang.advancements; + +import com.hypherionmc.craterlib.utils.ChatUtils; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; +import net.minecraft.advancements.DisplayInfo; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedDisplayInfo { + + private final DisplayInfo internal; + + public boolean shouldDisplay() { + return internal.shouldAnnounceChat(); + } + + public boolean isHidden() { + return internal.isHidden(); + } + + public Component displayName() { + return ChatUtils.mojangToAdventure(internal.getTitle()); + } + + public Component description() { + return ChatUtils.mojangToAdventure(internal.getDescription()); + } + +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/authlib/BridgedGameProfile.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/authlib/BridgedGameProfile.java new file mode 100644 index 0000000..c83f3c4 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/authlib/BridgedGameProfile.java @@ -0,0 +1,29 @@ +package com.hypherionmc.craterlib.nojang.authlib; + +import com.mojang.authlib.GameProfile; +import lombok.RequiredArgsConstructor; + +import java.util.UUID; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedGameProfile { + + private final GameProfile internal; + + public static BridgedGameProfile mojang(UUID id, String name) { + return new BridgedGameProfile(new GameProfile(id, name)); + } + + public String getName() { + return internal.getName(); + } + + public UUID getId() { + return internal.getId(); + } + + public GameProfile toMojang() { + return internal; + } + +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/BridgedMinecraft.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/BridgedMinecraft.java new file mode 100644 index 0000000..2d6242b --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/BridgedMinecraft.java @@ -0,0 +1,84 @@ +package com.hypherionmc.craterlib.nojang.client; + +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedClientLevel; +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedServerData; +import com.hypherionmc.craterlib.nojang.client.server.BridgedIntegratedServer; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import lombok.Getter; +import net.minecraft.SharedConstants; +import net.minecraft.client.Minecraft; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.util.UUID; + +public class BridgedMinecraft { + + @Getter + private static final BridgedMinecraft instance = new BridgedMinecraft(); + private final Minecraft internal = Minecraft.getInstance(); + + public File getGameDirectory() { + return internal.gameDirectory; + } + + public BridgedOptions getOptions() { + return BridgedOptions.of(internal.options); + } + + @Nullable + public BridgedClientLevel getLevel() { + if (internal.level == null) + return null; + + return BridgedClientLevel.of(internal.level); + } + + public boolean isRealmServer() { + return internal.getCurrentServer() != null && internal.isConnectedToRealms(); + } + + public boolean isSinglePlayer() { + return internal.hasSingleplayerServer(); + } + + @Nullable + public BridgedPlayer getPlayer() { + if (internal.player == null) + return null; + + return BridgedPlayer.of(internal.player); + } + + public String getGameVersion() { + return SharedConstants.getCurrentVersion().getName(); + } + + public String getUserName() { + return internal.getUser().getName(); + } + + public UUID getPlayerId() { + return internal.getUser().getProfileId(); + } + + @Nullable + public BridgedServerData getCurrentServer() { + if (internal.getCurrentServer() == null) + return null; + + return BridgedServerData.of(internal.getCurrentServer()); + } + + @Nullable + public BridgedIntegratedServer getSinglePlayerServer() { + return BridgedIntegratedServer.of(internal.getSingleplayerServer()); + } + + public int getServerPlayerCount () { + if (internal.getConnection() == null) + return 0; + + return internal.getConnection().getOnlinePlayers().size(); + } +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/BridgedOptions.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/BridgedOptions.java new file mode 100644 index 0000000..7063feb --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/BridgedOptions.java @@ -0,0 +1,15 @@ +package com.hypherionmc.craterlib.nojang.client; + +import lombok.RequiredArgsConstructor; +import net.minecraft.client.Options; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedOptions { + + private final Options internal; + + public String getLanguage() { + return internal.languageCode; + } + +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/gui/BridgedScreen.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/gui/BridgedScreen.java new file mode 100644 index 0000000..c4bcc23 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/gui/BridgedScreen.java @@ -0,0 +1,36 @@ +package com.hypherionmc.craterlib.nojang.client.gui; + +import lombok.RequiredArgsConstructor; +import net.minecraft.client.gui.screens.LevelLoadingScreen; +import net.minecraft.client.gui.screens.ReceivingLevelScreen; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.gui.screens.TitleScreen; +import net.minecraft.client.gui.screens.multiplayer.JoinMultiplayerScreen; +import net.minecraft.realms.RealmsScreen; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedScreen { + + private final Screen internal; + + public boolean isTitleScreen() { + return internal instanceof TitleScreen; + } + + public boolean isRealmsScreen() { + return internal instanceof RealmsScreen; + } + + public boolean isServerBrowserScreen() { + return internal instanceof JoinMultiplayerScreen; + } + + public boolean isLoadingScreen() { + return internal instanceof LevelLoadingScreen || internal instanceof ReceivingLevelScreen; + } + + public Screen toMojang() { + return internal; + } + +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/multiplayer/BridgedClientLevel.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/multiplayer/BridgedClientLevel.java new file mode 100644 index 0000000..4e7b60b --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/multiplayer/BridgedClientLevel.java @@ -0,0 +1,59 @@ +package com.hypherionmc.craterlib.nojang.client.multiplayer; + +import com.hypherionmc.craterlib.nojang.core.BridgedBlockPos; +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; +import com.hypherionmc.craterlib.utils.ChatUtils; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; +import net.minecraft.client.multiplayer.ClientLevel; +import org.jetbrains.annotations.Nullable; + +import java.util.concurrent.atomic.AtomicReference; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedClientLevel { + + private final ClientLevel internal; + + public boolean isClientSide() { + return internal.isClientSide(); + } + + public long getGameTime() { + return internal.getGameTime(); + } + + public long getDayTime() { + return internal.getDayTime(); + } + + public long dayTime() { + return internal.dayTime(); + } + + public boolean isRaining() { + return internal.isRaining(); + } + + public boolean isThundering() { + return internal.isThundering(); + } + + @Nullable + public ResourceIdentifier getDimensionKey() { + return ResourceIdentifier.fromMojang(internal.dimension().location()); + } + + @Nullable + public ResourceIdentifier getBiomeIdentifier(BridgedBlockPos onPos) { + AtomicReference identifier = new AtomicReference<>(null); + internal.getBiome(onPos.toMojang()).unwrap().ifLeft(b -> identifier.set(ResourceIdentifier.fromMojang(b.location()))); + return identifier.get(); + } + + @Nullable + public Component getDifficulty() { + return ChatUtils.mojangToAdventure(internal.getDifficulty().getDisplayName()); + } + +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/multiplayer/BridgedServerData.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/multiplayer/BridgedServerData.java new file mode 100644 index 0000000..710d2c6 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/multiplayer/BridgedServerData.java @@ -0,0 +1,45 @@ +package com.hypherionmc.craterlib.nojang.client.multiplayer; + +import com.hypherionmc.craterlib.utils.ChatUtils; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; +import net.minecraft.ChatFormatting; +import net.minecraft.client.multiplayer.ServerData; +import net.minecraft.client.multiplayer.ServerStatusPinger; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedServerData { + + private final ServerData internal; + + public String name() { + return internal.name; + } + + public String ip() { + return internal.ip; + } + + public Component motd() { + return ChatUtils.mojangToAdventure(internal.motd); + } + + public int getMaxPlayers() { + if (!internal.pinged || internal.status.getString() == null) { + try { + new ServerStatusPinger().pingServer(internal, () -> {}); + } catch (Exception ignored) {} + } + + try { + return Integer.parseInt(ChatFormatting.stripFormatting(internal.status.getString()).split("/")[1]); + } catch (Exception ignored) {} + + return 0; + } + + public ServerData toMojang() { + return internal; + } + +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/server/BridgedIntegratedServer.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/server/BridgedIntegratedServer.java new file mode 100644 index 0000000..0ecacaf --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/server/BridgedIntegratedServer.java @@ -0,0 +1,19 @@ +package com.hypherionmc.craterlib.nojang.client.server; + +import lombok.RequiredArgsConstructor; +import net.minecraft.client.server.IntegratedServer; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedIntegratedServer { + + private final IntegratedServer internal; + + public String getLevelName() { + return internal.getWorldData().getLevelName(); + } + + public IntegratedServer toMojang() { + return internal; + } + +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/BridgedCommandSourceStack.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/BridgedCommandSourceStack.java new file mode 100644 index 0000000..b14e484 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/BridgedCommandSourceStack.java @@ -0,0 +1,22 @@ +package com.hypherionmc.craterlib.nojang.commands; + +import com.hypherionmc.craterlib.utils.ChatUtils; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; +import net.minecraft.commands.CommandSourceStack; + +import java.util.function.Supplier; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedCommandSourceStack { + + private final CommandSourceStack internal; + + public void sendSuccess(Supplier supplier, boolean bl) { + internal.sendSuccess(ChatUtils.adventureToMojang(supplier.get()), bl); + } + + public CommandSourceStack toMojang() { + return internal; + } +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/BridgedFakePlayer.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/BridgedFakePlayer.java new file mode 100644 index 0000000..1c5eaa0 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/BridgedFakePlayer.java @@ -0,0 +1,55 @@ +package com.hypherionmc.craterlib.nojang.commands; + +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import com.hypherionmc.craterlib.utils.ChatUtils; +import net.minecraft.commands.CommandSource; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.network.chat.Component; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.phys.Vec2; +import net.minecraft.world.phys.Vec3; + +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Supplier; + +public abstract class BridgedFakePlayer { + + final MojangBridge internal; + + public BridgedFakePlayer(BridgedMinecraftServer server, int perm, String name) { + internal = new MojangBridge(server.toMojang(), perm, name, this::onSuccess, this::onError); + } + + public abstract void onSuccess(Supplier supplier, Boolean aBoolean); + + public void onError(net.kyori.adventure.text.Component component) { + this.onSuccess(() -> component, false); + } + + public CommandSourceStack toMojang() { + return internal; + } + + static class MojangBridge extends CommandSourceStack { + + private final BiConsumer, Boolean> successCallback; + public final Consumer errorCallback; + + MojangBridge(MinecraftServer server, int perm, String name, BiConsumer, Boolean> successCallback, Consumer errorCallback) { + super(CommandSource.NULL, Vec3.ZERO, Vec2.ZERO, server.overworld(), perm, name, Component.literal(name), server, null); + this.successCallback = successCallback; + this.errorCallback = errorCallback; + } + + @Override + public void sendSuccess(Component supplier, boolean bl) { + successCallback.accept(() -> ChatUtils.mojangToAdventure(supplier), bl); + } + + @Override + public void sendFailure(Component arg) { + errorCallback.accept(ChatUtils.mojangToAdventure(arg)); + } + } +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/CommandsRegistry.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/CommandsRegistry.java new file mode 100644 index 0000000..37ad15b --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/CommandsRegistry.java @@ -0,0 +1,85 @@ +package com.hypherionmc.craterlib.nojang.commands; + +import com.hypherionmc.craterlib.api.commands.CraterCommand; +import com.hypherionmc.craterlib.nojang.authlib.BridgedGameProfile; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.TriConsumer; +import com.mojang.authlib.GameProfile; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.commands.arguments.GameProfileArgument; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class CommandsRegistry { + + public static final CommandsRegistry INSTANCE = new CommandsRegistry(); + + private final List commands = new ArrayList<>(); + + public void registerCommand(CraterCommand cmd) { + commands.add(cmd); + } + + public void registerCommands(CommandDispatcher stack) { + commands.forEach(cmd -> { + if (cmd.hasArguments()) { + CommandWithArguments.register(cmd, stack); + } else { + CommandWithoutArguments.register(cmd, stack); + } + }); + } + + static class CommandWithoutArguments { + + public static void register(CraterCommand cmd, CommandDispatcher dispatcher) { + LiteralArgumentBuilder command = Commands.literal(cmd.getCommandName()) + .requires(source -> source.hasPermission(cmd.getPermissionLevel())) + .executes(context -> { + cmd.getExecutor().accept(BridgedCommandSourceStack.of(context.getSource())); + return 1; + }); + + dispatcher.register(command); + } + + } + + @SuppressWarnings("unchecked") + static class CommandWithArguments { + + public static void register(CraterCommand cmd, CommandDispatcher dispatcher) { + LiteralArgumentBuilder command = Commands.literal(cmd.getCommandName()) + .requires(source -> source.hasPermission(cmd.getPermissionLevel())); + + cmd.getArguments().forEach((key, pair) -> command.then(Commands.argument(key, pair.getLeft()).executes(context -> { + + // This is FUCKING UGLY.... Need to improve this in the future + if (pair.getLeft() instanceof GameProfileArgument) { + Collection profiles = GameProfileArgument.getGameProfiles(context, key); + List bridgedGameProfiles = new ArrayList<>(); + + profiles.forEach(p -> bridgedGameProfiles.add(BridgedGameProfile.of(p))); + + ((TriConsumer, BridgedCommandSourceStack>) pair.getRight()) + .accept(BridgedPlayer.of(context.getSource().getPlayer()), bridgedGameProfiles, BridgedCommandSourceStack.of(context.getSource())); + return 1; + } + + return 1; + }))); + + dispatcher.register(command); + } + + } + +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/core/BridgedBlockPos.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/core/BridgedBlockPos.java new file mode 100644 index 0000000..bd49e2c --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/core/BridgedBlockPos.java @@ -0,0 +1,27 @@ +package com.hypherionmc.craterlib.nojang.core; + +import lombok.RequiredArgsConstructor; +import net.minecraft.core.BlockPos; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedBlockPos { + + private final BlockPos internal; + + public int getX() { + return internal.getX(); + } + + public int getY() { + return internal.getY(); + } + + public int getZ() { + return internal.getZ(); + } + + public BlockPos toMojang() { + return internal; + } + +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/nbt/BridgedCompoundTag.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/nbt/BridgedCompoundTag.java new file mode 100644 index 0000000..17a2d04 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/nbt/BridgedCompoundTag.java @@ -0,0 +1,49 @@ +package com.hypherionmc.craterlib.nojang.nbt; + +import lombok.RequiredArgsConstructor; +import net.minecraft.nbt.CompoundTag; + +import java.util.Set; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedCompoundTag { + + private final CompoundTag internal; + + public static BridgedCompoundTag empty() { + return new BridgedCompoundTag(new CompoundTag()); + } + + public BridgedCompoundTag getCompound(String key) { + return BridgedCompoundTag.of(internal.getCompound(key)); + } + + public Set getAllKeys() { + return internal.getAllKeys(); + } + + public String getString(String key) { + return internal.getString(key); + } + + public boolean getBoolean(String key) { + return internal.getBoolean(key); + } + + public void putString(String key, String value) { + internal.putString(key, value); + } + + public void put(String key, BridgedCompoundTag value) { + internal.put(key, value.toMojang()); + } + + public void putBoolean(String key, boolean value) { + internal.putBoolean(key, value); + } + + public CompoundTag toMojang() { + return internal; + } + +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/network/BridgedFriendlyByteBuf.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/network/BridgedFriendlyByteBuf.java new file mode 100644 index 0000000..a7bafd0 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/network/BridgedFriendlyByteBuf.java @@ -0,0 +1,34 @@ +package com.hypherionmc.craterlib.nojang.network; + +import com.hypherionmc.craterlib.nojang.nbt.BridgedCompoundTag; +import lombok.RequiredArgsConstructor; +import net.minecraft.network.FriendlyByteBuf; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedFriendlyByteBuf { + + private final FriendlyByteBuf internal; + + public BridgedCompoundTag readNbt() { + return BridgedCompoundTag.of(internal.readNbt()); + } + + public BridgedFriendlyByteBuf writeNbt(BridgedCompoundTag tag) { + internal.writeNbt(tag.toMojang()); + return BridgedFriendlyByteBuf.of(internal); + } + + public BridgedFriendlyByteBuf writeUtf(String value) { + internal.writeUtf(value); + return BridgedFriendlyByteBuf.of(internal); + } + + public String readUtf() { + return internal.readUtf(); + } + + public FriendlyByteBuf toMojang() { + return internal; + } + +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/package-info.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/package-info.java new file mode 100644 index 0000000..4c177a5 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/package-info.java @@ -0,0 +1,9 @@ +/** + * @author HypherionSA + * This package, called NoJang, exposes various wrapped API's. + * Using this api, a mod can essentially run on ANY minecraft version this library + * supports, from one code base. + * IMPORTANT NOTE: THESE API'S MUST NEVER EXPOSE ANY MINECRAFT CLASSES OR CODE!!!! + * THEY MUST ALWAYS BE HANDLED INTERNALLY AND ONLY RETURN WRAPPED VARIANTS + */ +package com.hypherionmc.craterlib.nojang; \ No newline at end of file diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/realmsclient/dto/BridgedRealmsServer.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/realmsclient/dto/BridgedRealmsServer.java new file mode 100644 index 0000000..dddf746 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/realmsclient/dto/BridgedRealmsServer.java @@ -0,0 +1,40 @@ +package com.hypherionmc.craterlib.nojang.realmsclient.dto; + +import com.mojang.realmsclient.dto.PlayerInfo; +import com.mojang.realmsclient.dto.RealmsServer; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedRealmsServer { + + private final RealmsServer internal; + + public String getName() { + return internal.getName(); + } + + public String getDescription() { + return internal.getDescription(); + } + + public String getWorldType() { + return internal.worldType.name(); + } + + public String getMinigameName() { + return internal.getMinigameName(); + } + + public String getMinigameImage() { + return internal.minigameImage; + } + + public long getPlayerCount() { + return internal.players.stream().filter(PlayerInfo::getOnline).count(); + } + + public RealmsServer toMojang() { + return internal; + } + +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/resources/ResourceIdentifier.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/resources/ResourceIdentifier.java new file mode 100644 index 0000000..9b848f4 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/resources/ResourceIdentifier.java @@ -0,0 +1,36 @@ +package com.hypherionmc.craterlib.nojang.resources; + +import net.minecraft.resources.ResourceLocation; + +public class ResourceIdentifier { + + private final ResourceLocation internal; + + public ResourceIdentifier(String namespace, String path) { + this.internal = new ResourceLocation(namespace, path); + } + + public ResourceIdentifier(String path) { + this.internal = new ResourceLocation(path); + } + + public String getNamespace() { + return internal.getNamespace(); + } + + public String getPath() { + return internal.getPath(); + } + + public String getString() { + return internal.toString(); + } + + public static ResourceIdentifier fromMojang(ResourceLocation location) { + return new ResourceIdentifier(location.getNamespace(), location.getPath()); + } + + public ResourceLocation toMojang() { + return internal; + } +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/server/BridgedMinecraftServer.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/server/BridgedMinecraftServer.java new file mode 100644 index 0000000..cb818b7 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/server/BridgedMinecraftServer.java @@ -0,0 +1,91 @@ +package com.hypherionmc.craterlib.nojang.server; + +import com.hypherionmc.craterlib.nojang.authlib.BridgedGameProfile; +import com.hypherionmc.craterlib.nojang.commands.BridgedFakePlayer; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.ChatUtils; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; +import net.minecraft.SharedConstants; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.players.UserBanListEntry; +import net.minecraft.server.players.UserWhiteListEntry; + +import java.util.ArrayList; +import java.util.List; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedMinecraftServer { + + private final MinecraftServer internal; + + public boolean isUsingWhitelist() { + return internal.getPlayerList().isUsingWhitelist(); + } + + public int getPlayerCount() { + return internal.getPlayerList().getPlayerCount(); + } + + public int getMaxPlayers() { + return internal.getPlayerList().getMaxPlayers(); + } + + public String getServerModName() { + return internal.getServerModName(); + } + + public String getName() { + return SharedConstants.getCurrentVersion().getName(); + } + + public boolean usesAuthentication() { + return internal.usesAuthentication(); + } + + public void broadcastSystemMessage(Component text, boolean bl) { + internal.getPlayerList().broadcastSystemMessage(ChatUtils.adventureToMojang(text), bl); + } + + public boolean isPlayerBanned(BridgedGameProfile profile) { + return internal.getPlayerList().getBans().isBanned(profile.toMojang()); + } + + public void whitelistPlayer(BridgedGameProfile gameProfile) { + if (!internal.getPlayerList().isUsingWhitelist()) + return; + + internal.getPlayerList().getWhiteList().add(new UserWhiteListEntry(gameProfile.toMojang())); + } + + public void unWhitelistPlayer(BridgedGameProfile gameProfile) { + if (!internal.getPlayerList().isUsingWhitelist()) + return; + + internal.getPlayerList().getWhiteList().remove(new UserWhiteListEntry(gameProfile.toMojang())); + } + + public List getPlayers() { + List profiles = new ArrayList<>(); + + if (internal.getPlayerList() == null) + return profiles; + + internal.getPlayerList().getPlayers().forEach(p -> profiles.add(BridgedPlayer.of(p))); + + return profiles; + } + + public void banPlayer(BridgedGameProfile profile) { + internal.getPlayerList().getBans().add(new UserBanListEntry(profile.toMojang())); + } + + public void executeCommand(BridgedMinecraftServer server, BridgedFakePlayer player, String command) { + internal.getCommands().performPrefixedCommand(player.toMojang(), command); + } + + public MinecraftServer toMojang() { + return internal; + } + +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/world/entity/player/BridgedPlayer.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/world/entity/player/BridgedPlayer.java new file mode 100644 index 0000000..8aa6d49 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/nojang/world/entity/player/BridgedPlayer.java @@ -0,0 +1,63 @@ +package com.hypherionmc.craterlib.nojang.world.entity.player; + +import com.hypherionmc.craterlib.nojang.authlib.BridgedGameProfile; +import com.hypherionmc.craterlib.nojang.core.BridgedBlockPos; +import com.hypherionmc.craterlib.utils.ChatUtils; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraft.world.entity.player.Player; +import org.jetbrains.annotations.Nullable; + +import java.util.UUID; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedPlayer { + + private final Player internal; + + public Component getDisplayName() { + return ChatUtils.mojangToAdventure(internal.getDisplayName()); + } + + public Component getName() { + return ChatUtils.mojangToAdventure(internal.getName()); + } + + public UUID getUUID() { + return internal.getUUID(); + } + + public String getStringUUID() { + return internal.getStringUUID(); + } + + public BridgedGameProfile getGameProfile() { + return BridgedGameProfile.of(internal.getGameProfile()); + } + + public boolean isServerPlayer() { + return internal instanceof ServerPlayer; + } + + public Player toMojang() { + return internal; + } + + public BridgedBlockPos getOnPos() { + return BridgedBlockPos.of(internal.getOnPos()); + } + + @Nullable + public ServerGamePacketListenerImpl getConnection() { + if (isServerPlayer()) { + return ((ServerPlayer) internal).connection; + } + return null; + } + + public ServerPlayer toMojangServerPlayer() { + return (ServerPlayer) internal; + } +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java new file mode 100644 index 0000000..7bbcfd1 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java @@ -0,0 +1,96 @@ +package com.hypherionmc.craterlib.utils; + +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; +import me.hypherionmc.mcdiscordformatter.discord.DiscordSerializer; +import me.hypherionmc.mcdiscordformatter.minecraft.MinecraftSerializer; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import net.kyori.adventure.text.serializer.json.JSONOptions; +import net.minecraft.ChatFormatting; +import net.minecraft.SharedConstants; +import net.minecraft.Util; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.Style; + +public class ChatUtils { + + private static final GsonComponentSerializer adventureSerializer = GsonComponentSerializer.builder().options( + JSONOptions.byDataVersion().at(SharedConstants.getCurrentVersion().getDataVersion().getVersion()) + ).build(); + + public static Component adventureToMojang(net.kyori.adventure.text.Component inComponent) { + final String serialised = adventureSerializer.serialize(inComponent); + return Component.Serializer.fromJson(serialised); + } + + public static net.kyori.adventure.text.Component mojangToAdventure(Component inComponent) { + final String serialised = Component.Serializer.toJson(inComponent); + return adventureSerializer.deserialize(serialised); + } + + // Some text components contain duplicate text, resulting in duplicate messages + // sent back to discord. This should help fix those issues + public static Component safeCopy(Component inComponent) { + String value = inComponent.getString(); + Style style = inComponent.getStyle(); + return Component.literal(value).withStyle(style); + } + + public static String strip(String inString, String... toStrip) { + String finalString = inString; + + for (String strip : toStrip) { + if (finalString.startsWith(strip)) + finalString = finalString.replaceFirst(strip, ""); + + if (finalString.startsWith(" ")) + finalString = finalString.replaceFirst(" ", ""); + } + + return finalString; + } + + public static String resolve(net.kyori.adventure.text.Component component, boolean formatted) { + Component c = adventureToMojang(component); + String returnVal = ChatFormatting.stripFormatting(c.getString()); + + if (formatted) { + returnVal = DiscordSerializer.INSTANCE.serialize(safeCopy(c).copy()); + } + + return returnVal; + } + + public static net.kyori.adventure.text.Component resolve(String component, boolean formatted) { + Component returnVal = Component.literal(component); + if (formatted) { + returnVal = MinecraftSerializer.INSTANCE.serialize(component); + } + + return mojangToAdventure(returnVal); + } + + public static net.kyori.adventure.text.Component getTooltipTitle(String key) { + return net.kyori.adventure.text.Component.text(NamedTextColor.YELLOW + net.kyori.adventure.text.Component.translatable(key).key()); + } + + public static String resolveTranslation(String key) { + return net.kyori.adventure.text.Component.translatable(key).key(); + } + + public static net.kyori.adventure.text.Component getTranslation(String key) { + return net.kyori.adventure.text.Component.translatable(key); + } + + public static net.kyori.adventure.text.Component makeComponent(String text) { + return net.kyori.adventure.text.Component.translatable(text); + } + + public static net.kyori.adventure.text.Component getBiomeName(ResourceIdentifier identifier) { + if (identifier == null) + return net.kyori.adventure.text.Component.text("Unknown"); + + return mojangToAdventure(Component.translatable(Util.makeDescriptionId("biome", identifier.toMojang()))); + } + +} diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/utils/InternalServiceUtil.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/utils/InternalServiceUtil.java new file mode 100644 index 0000000..2305d14 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/utils/InternalServiceUtil.java @@ -0,0 +1,27 @@ +package com.hypherionmc.craterlib.utils; + +import com.hypherionmc.craterlib.CraterConstants; + +import java.util.ServiceLoader; + +/** + * @author HypherionSA + * Utility class to handle SPI loading + */ +public class InternalServiceUtil { + + /** + * Try to load a service + * + * @param clazz The service class type to load + * @return The loaded class + */ + public static T load(Class clazz) { + final T loadedService = ServiceLoader.load(clazz) + .findFirst() + .orElseThrow(() -> new NullPointerException("Failed to load service for " + clazz.getName())); + CraterConstants.LOG.debug("Loaded {} for service {}", loadedService, clazz); + return loadedService; + } + +} \ No newline at end of file diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/utils/OptifineUtils.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/utils/OptifineUtils.java new file mode 100644 index 0000000..8a06da2 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/utils/OptifineUtils.java @@ -0,0 +1,46 @@ +package com.hypherionmc.craterlib.utils; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * @author HypherionSA + * Utility class for Optifine compatibility + */ +public class OptifineUtils { + + private static final boolean hasOptifine = checkOptifine(); + + private static boolean checkOptifine() { + try { + Class ofConfigClass = Class.forName("net.optifine.Config"); + return true; + } catch (ClassNotFoundException e) { + // Optifine is probably not present. Ignore the error + return false; + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } + + public static boolean isRenderRegions() { + try { + Class ofConfigClass = Class.forName("net.optifine.Config"); + Method rrField = ofConfigClass.getMethod("isRenderRegions"); + return (boolean) rrField.invoke(null); + } catch (ClassNotFoundException | InvocationTargetException | NoSuchMethodException | + IllegalAccessException e) { + // Optifine is probably not present. Ignore the error + return false; + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } + + public static boolean hasOptifine() { + return hasOptifine; + } + +} \ No newline at end of file diff --git a/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/utils/TriConsumer.java b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/utils/TriConsumer.java new file mode 100644 index 0000000..77f7309 --- /dev/null +++ b/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/utils/TriConsumer.java @@ -0,0 +1,5 @@ +package com.hypherionmc.craterlib.utils; + +public interface TriConsumer { + void accept(T t, U u, V v); +} diff --git a/1.19.3/Common/src/main/resources/assets/craterlib/lang/en_us.json b/1.19.3/Common/src/main/resources/assets/craterlib/lang/en_us.json new file mode 100644 index 0000000..03115d9 --- /dev/null +++ b/1.19.3/Common/src/main/resources/assets/craterlib/lang/en_us.json @@ -0,0 +1,8 @@ +{ + "t.clc.opensubconfig": "Open Config", + "t.clc.save": "Save", + "t.clc.cancel_discard": "Discard", + "t.clc.quit_config": "Unsaved Changes", + "t.clc.quit_config_sure": "You have unsaved config changes. Are you sure you want to discard them?", + "t.clc.quit_discard": "Quit & Discard" +} diff --git a/1.19.3/Common/src/main/resources/craterlib.mixins.json b/1.19.3/Common/src/main/resources/craterlib.mixins.json new file mode 100644 index 0000000..1a739db --- /dev/null +++ b/1.19.3/Common/src/main/resources/craterlib.mixins.json @@ -0,0 +1,24 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "com.hypherionmc.craterlib.mixin", + "compatibilityLevel": "JAVA_17", + "mixins": [ + ], + "client": [ + "ChatInputSuggestorMixin", + "events.PlayerMixin", + "events.client.ClientLevelMixin", + "events.client.MinecraftMixin", + "events.client.RealmsMainScreenMixin" + ], + "server": [ + "events.CommandMixin", + "events.PlayerAdvancementsMixin", + "events.PlayerListMixin", + "events.ServerPlayerMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/1.19.3/Common/src/main/resources/pack.mcmeta b/1.19.3/Common/src/main/resources/pack.mcmeta new file mode 100644 index 0000000..263d366 --- /dev/null +++ b/1.19.3/Common/src/main/resources/pack.mcmeta @@ -0,0 +1,6 @@ +{ + "pack": { + "description": "${mod_name}", + "pack_format": 18 + } +} diff --git a/1.19.3/Fabric/build.gradle b/1.19.3/Fabric/build.gradle new file mode 100644 index 0000000..9793ddc --- /dev/null +++ b/1.19.3/Fabric/build.gradle @@ -0,0 +1,126 @@ +archivesBaseName = "${mod_name.replace(" ", "")}-Fabric-${minecraft_version}" + +dependencies { + // Core + modImplementation "net.fabricmc.fabric-api:fabric-api:${fabric_api}" + + // Compat + modImplementation("com.terraformersmc:modmenu:${mod_menu_version}") { + exclude(group: "net.fabricmc.fabric-api") + } + + modImplementation "maven.modrinth:fabrictailor:${fabrictailor}" + modImplementation "maven.modrinth:vanish:${vanish}" + + // Do not edit or remove + implementation project(":Common") +} + +shadowJar { + from sourceSets.main.output + configurations = [project.configurations.shade] + + dependencies { + exclude(dependency('com.google.code.gson:.*')) + + relocate 'me.hypherionmc.moonconfig', 'shadow.hypherionmc.moonconfig' + relocate 'me.hypherionmc.mcdiscordformatter', 'shadow.hypherionmc.mcdiscordformatter' + relocate 'net.kyori', 'shadow.kyori' + } + + setArchiveClassifier('dev-shadow') +} + +/** + * =============================================================================== + * = DO NOT EDIT BELOW THIS LINE UNLESS YOU KNOW WHAT YOU ARE DOING = + * =============================================================================== + */ + +unimined.minecraft { + fabric { + loader fabric_loader + } +} + +remapJar { + inputFile.set shadowJar.archiveFile + dependsOn shadowJar + archiveClassifier.set null +} + +jar { + archiveClassifier.set "dev" +} + +processResources { + from project(":Common").sourceSets.main.resources + def buildProps = project.properties.clone() + + filesMatching(['fabric.mod.json']) { + expand buildProps + } +} + +compileTestJava.enabled = false + +tasks.withType(JavaCompile).configureEach { + source(project(":Common").sourceSets.main.allSource) +} + +/** + * Publishing Config + */ +publishing { + publications { + mavenJava(MavenPublication) { + artifactId project.archivesBaseName + from components.java + + artifact(remapJar) { + builtBy remapJar + } + + pom.withXml { + Node pomNode = asNode() + pomNode.dependencies.'*'.findAll() { + it.artifactId.text() == 'regutils-joined-fabric' || + it.artifactId.text() == 'core' || + it.artifactId.text() == 'toml' + }.each() { + it.parent().remove(it) + } + } + } + } + + repositories { + maven rootProject.orion.getPublishingMaven() + } +} + +publisher { + apiKeys { + modrinth(System.getenv("MODRINTH_TOKEN")) + curseforge(System.getenv("CURSE_TOKEN")) + } + + setCurseID(curse_id) + setModrinthID(modrinth_id) + setVersionType("release") + setChangelog("https://raw.githubusercontent.com/hypherionmc/changelogs/main/craterlib/changelog-fabric.md") + setProjectVersion("${minecraft_version}-${project.version}") + setDisplayName("[FABRIC/QUILT 1.19.3/1.19.4] CraterLib - ${project.version}") + setGameVersions("1.19.3", "1.19.4") + setLoaders("fabric", "quilt") + setArtifact(remapJar) + setCurseEnvironment("both") + + modrinthDepends { + required("fabric-api") + } + + curseDepends { + required("fabric-api") + } +} diff --git a/1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/CraterLibInitializer.java b/1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/CraterLibInitializer.java new file mode 100644 index 0000000..c66c210 --- /dev/null +++ b/1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/CraterLibInitializer.java @@ -0,0 +1,42 @@ +package com.hypherionmc.craterlib; + +import com.hypherionmc.craterlib.api.events.server.CraterRegisterCommandEvent; +import com.hypherionmc.craterlib.api.events.server.CraterServerLifecycleEvent; +import com.hypherionmc.craterlib.common.FabricCommonPlatform; +import com.hypherionmc.craterlib.compat.Vanish; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.core.networking.CraterPacketNetwork; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.core.platform.ModloaderEnvironment; +import com.hypherionmc.craterlib.network.CraterFabricNetworkHandler; +import com.hypherionmc.craterlib.nojang.commands.CommandsRegistry; +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; + +public class CraterLibInitializer implements ModInitializer { + + @Override + public void onInitialize() { + new CraterPacketNetwork(new CraterFabricNetworkHandler(PacketSide.SERVER)); + CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> { + CraterEventBus.INSTANCE.postEvent(new CraterRegisterCommandEvent()); + CommandsRegistry.INSTANCE.registerCommands(dispatcher); + }); + + + ServerLifecycleEvents.SERVER_STARTING.register(server -> { + FabricCommonPlatform.server = server; + CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Starting(BridgedMinecraftServer.of(server))); + }); + + ServerLifecycleEvents.SERVER_STARTED.register(li -> CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Started(BridgedMinecraftServer.of(li)))); + ServerLifecycleEvents.SERVER_STOPPING.register(server -> CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Stopping(BridgedMinecraftServer.of(server)))); + ServerLifecycleEvents.SERVER_STOPPED.register(server -> CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Stopped(BridgedMinecraftServer.of(server)))); + + if (ModloaderEnvironment.INSTANCE.isModLoaded("melius-vanish")) { + Vanish.register(); + } + } +} diff --git a/1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/CraterLibModMenuIntegration.java b/1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/CraterLibModMenuIntegration.java new file mode 100644 index 0000000..6615352 --- /dev/null +++ b/1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/CraterLibModMenuIntegration.java @@ -0,0 +1,30 @@ +package com.hypherionmc.craterlib; + +import com.hypherionmc.craterlib.client.gui.config.CraterConfigScreen; +import com.hypherionmc.craterlib.core.config.ConfigController; +import com.hypherionmc.craterlib.core.config.ModuleConfig; +import com.hypherionmc.craterlib.core.config.annotations.NoConfigScreen; +import com.terraformersmc.modmenu.api.ConfigScreenFactory; +import com.terraformersmc.modmenu.api.ModMenuApi; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author HypherionSA + */ +public class CraterLibModMenuIntegration implements ModMenuApi { + + @Override + public Map> getProvidedConfigScreenFactories() { + Map> configScreens = new HashMap<>(); + + ConfigController.getMonitoredConfigs().forEach((conf, watcher) -> { + if (!conf.getClass().isAnnotationPresent(NoConfigScreen.class)) { + configScreens.put(((ModuleConfig) conf).getModId(), screen -> new CraterConfigScreen((ModuleConfig) conf, screen)); + } + }); + + return configScreens; + } +} diff --git a/1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/client/CraterLibClientInitializer.java b/1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/client/CraterLibClientInitializer.java new file mode 100644 index 0000000..b5ec5b1 --- /dev/null +++ b/1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/client/CraterLibClientInitializer.java @@ -0,0 +1,27 @@ +package com.hypherionmc.craterlib.client; + +import com.hypherionmc.craterlib.api.events.client.CraterClientTickEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.core.networking.CraterPacketNetwork; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.network.CraterFabricNetworkHandler; +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedClientLevel; +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; + +public class CraterLibClientInitializer implements ClientModInitializer { + + @Override + public void onInitializeClient() { + new CraterPacketNetwork(new CraterFabricNetworkHandler(PacketSide.CLIENT)); + ClientTickEvents.START_CLIENT_TICK.register((listener) -> { + if (listener.level == null) + return; + + CraterClientTickEvent event = new CraterClientTickEvent(BridgedClientLevel.of(listener.level)); + CraterEventBus.INSTANCE.postEvent(event); + }); + + CraterEventBus.INSTANCE.registerEventListener(CraterLibClientInitializer.class); + } +} diff --git a/1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/client/FabricClientPlatform.java b/1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/client/FabricClientPlatform.java new file mode 100644 index 0000000..7852f12 --- /dev/null +++ b/1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/client/FabricClientPlatform.java @@ -0,0 +1,34 @@ +package com.hypherionmc.craterlib.client; + +import com.hypherionmc.craterlib.core.platform.ClientPlatform; +import com.hypherionmc.craterlib.nojang.client.BridgedMinecraft; +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedClientLevel; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraft.client.Minecraft; +import net.minecraft.network.Connection; + +/** + * @author HypherionSA + */ +public class FabricClientPlatform implements ClientPlatform { + + @Override + public BridgedMinecraft getClientInstance() { + return new BridgedMinecraft(); + } + + @Override + public BridgedPlayer getClientPlayer() { + return BridgedPlayer.of(Minecraft.getInstance().player); + } + + @Override + public BridgedClientLevel getClientLevel() { + return BridgedClientLevel.of(Minecraft.getInstance().level); + } + + @Override + public Connection getClientConnection() { + return Minecraft.getInstance().getConnection().getConnection(); + } +} diff --git a/1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricCommonPlatform.java b/1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricCommonPlatform.java new file mode 100644 index 0000000..c1a30c7 --- /dev/null +++ b/1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricCommonPlatform.java @@ -0,0 +1,18 @@ +package com.hypherionmc.craterlib.common; + +import com.hypherionmc.craterlib.core.platform.CommonPlatform; +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import net.minecraft.server.MinecraftServer; + +/** + * @author HypherionSA + */ +public class FabricCommonPlatform implements CommonPlatform { + + public static MinecraftServer server; + + @Override + public BridgedMinecraftServer getMCServer() { + return BridgedMinecraftServer.of(server); + } +} diff --git a/1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricCompatHelper.java b/1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricCompatHelper.java new file mode 100644 index 0000000..dbcaf63 --- /dev/null +++ b/1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricCompatHelper.java @@ -0,0 +1,23 @@ +package com.hypherionmc.craterlib.common; + +import com.hypherionmc.craterlib.compat.FabricTailor; +import com.hypherionmc.craterlib.compat.Vanish; +import com.hypherionmc.craterlib.core.platform.CompatUtils; +import com.hypherionmc.craterlib.core.platform.ModloaderEnvironment; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; + +public class FabricCompatHelper implements CompatUtils { + + @Override + public boolean isPlayerActive(BridgedPlayer player) { + if (!ModloaderEnvironment.INSTANCE.isModLoaded("melius-vanish")) + return true; + + return Vanish.isPlayerVanished(player.toMojangServerPlayer()); + } + + @Override + public String getSkinUUID(BridgedPlayer player) { + return FabricTailor.getTailorSkin(player.toMojangServerPlayer()); + } +} diff --git a/1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricLoaderHelper.java b/1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricLoaderHelper.java new file mode 100644 index 0000000..95f5c42 --- /dev/null +++ b/1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricLoaderHelper.java @@ -0,0 +1,69 @@ +package com.hypherionmc.craterlib.common; + +import com.hypherionmc.craterlib.core.platform.Environment; +import com.hypherionmc.craterlib.core.platform.ModloaderEnvironment; +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.SharedConstants; +import net.minecraft.client.Minecraft; + +import java.io.File; + +/** + * @author HypherionSA + * @date 07/08/2022 + */ +public class FabricLoaderHelper implements ModloaderEnvironment { + + @Override + public boolean isFabric() { + return true; + } + + @Override + public String getGameVersion() { + return SharedConstants.VERSION_STRING; + } + + @Override + public File getGameFolder() { + return Minecraft.getInstance().gameDirectory; + } + + @Override + public File getConfigFolder() { + return FabricLoader.getInstance().getConfigDir().toFile(); + } + + @Override + public File getModsFolder() { + return new File(FabricLoader.getInstance().getGameDir().toString() + File.separator + "mods"); + } + + @Override + public Environment getEnvironment() { + switch (FabricLoader.getInstance().getEnvironmentType()) { + case SERVER -> { + return Environment.SERVER; + } + case CLIENT -> { + return Environment.CLIENT; + } + } + return Environment.UNKNOWN; + } + + @Override + public boolean isModLoaded(String modid) { + return FabricLoader.getInstance().isModLoaded(modid); + } + + @Override + public boolean isDevEnv() { + return FabricLoader.getInstance().isDevelopmentEnvironment(); + } + + @Override + public int getModCount() { + return FabricLoader.getInstance().getAllMods().size(); + } +} diff --git a/1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/compat/FabricTailor.java b/1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/compat/FabricTailor.java new file mode 100644 index 0000000..b95a7d7 --- /dev/null +++ b/1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/compat/FabricTailor.java @@ -0,0 +1,23 @@ +package com.hypherionmc.craterlib.compat; + +import com.hypherionmc.craterlib.core.platform.ModloaderEnvironment; +import net.minecraft.server.level.ServerPlayer; +import org.samo_lego.fabrictailor.casts.TailoredPlayer; +public class FabricTailor { + + public static String getTailorSkin(ServerPlayer player) { + if (!ModloaderEnvironment.INSTANCE.isModLoaded("fabrictailor")) + return player.getStringUUID(); + + try { + if (player instanceof TailoredPlayer tp) { + return tp.getSkinId(); + } + } catch (Exception e) { + e.printStackTrace(); + } + + return player.getStringUUID(); + } + +} \ No newline at end of file diff --git a/1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java b/1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java new file mode 100644 index 0000000..cc6c1e0 --- /dev/null +++ b/1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java @@ -0,0 +1,25 @@ +package com.hypherionmc.craterlib.compat; + +import com.hypherionmc.craterlib.api.events.server.CraterPlayerEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import me.drex.vanish.api.VanishAPI; +import me.drex.vanish.api.VanishEvents; +import net.minecraft.server.level.ServerPlayer; + +public class Vanish { + + public static void register() { + VanishEvents.VANISH_EVENT.register((serverPlayer, b) -> { + if (b) { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerEvent.PlayerLoggedOut(BridgedPlayer.of(serverPlayer))); + } else { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerEvent.PlayerLoggedIn(BridgedPlayer.of(serverPlayer))); + } + }); + } + + public static boolean isPlayerVanished(ServerPlayer player) { + return VanishAPI.isVanished(player); + } +} diff --git a/1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java b/1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java new file mode 100644 index 0000000..fbd762b --- /dev/null +++ b/1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java @@ -0,0 +1,36 @@ +package com.hypherionmc.craterlib.mixin; + +import com.hypherionmc.craterlib.api.events.server.CraterServerChatEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.ChatUtils; +import net.minecraft.network.chat.PlayerChatMessage; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.concurrent.CompletableFuture; + +@Mixin(value = ServerGamePacketListenerImpl.class, priority = Integer.MIN_VALUE) +public class ServerGamePacketListenerImplMixin { + + @Shadow + public ServerPlayer player; + + @Inject( + method = "lambda$handleChat$8", + at = @At("HEAD"), + cancellable = true + ) + private void injectChatEvent(PlayerChatMessage arg, CompletableFuture completableFuture, CompletableFuture completableFuture2, Void void_, CallbackInfo ci) { + CraterServerChatEvent event = new CraterServerChatEvent(BridgedPlayer.of(this.player), arg.decoratedContent().getString(), ChatUtils.mojangToAdventure(arg.decoratedContent())); + CraterEventBus.INSTANCE.postEvent(event); + if (event.wasCancelled()) + ci.cancel(); + } + +} diff --git a/1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/TutorialMixin.java b/1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/TutorialMixin.java new file mode 100644 index 0000000..f9eaa50 --- /dev/null +++ b/1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/TutorialMixin.java @@ -0,0 +1,24 @@ +package com.hypherionmc.craterlib.mixin; + +import com.hypherionmc.craterlib.api.events.client.LateInitEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.client.BridgedMinecraft; +import com.hypherionmc.craterlib.nojang.client.BridgedOptions; +import net.minecraft.client.Minecraft; +import net.minecraft.client.Options; +import net.minecraft.client.tutorial.Tutorial; +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(Tutorial.class) +public class TutorialMixin { + + @Inject(method = "", at = @At("RETURN")) + private void injectEarlyInitEvent(Minecraft minecraft, Options options, CallbackInfo ci) { + LateInitEvent event = new LateInitEvent(new BridgedMinecraft(), BridgedOptions.of(options)); + CraterEventBus.INSTANCE.postEvent(event); + } + +} diff --git a/1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java b/1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java new file mode 100644 index 0000000..b3014a3 --- /dev/null +++ b/1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java @@ -0,0 +1,81 @@ +package com.hypherionmc.craterlib.network; + +import com.hypherionmc.craterlib.CraterConstants; +import com.hypherionmc.craterlib.core.networking.PacketRegistry; +import com.hypherionmc.craterlib.core.networking.data.PacketContext; +import com.hypherionmc.craterlib.core.networking.data.PacketHolder; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.nojang.network.BridgedFriendlyByteBuf; +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.minecraft.network.FriendlyByteBuf; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public class CraterFabricNetworkHandler extends PacketRegistry { + + private final Map, Message> CHANNELS = new HashMap(); + + public CraterFabricNetworkHandler(PacketSide side) { + super(side); + } + + protected void registerPacket(PacketHolder holder) { + if (CHANNELS.get(holder.messageType()) == null) { + CHANNELS.put(holder.messageType(), new Message<>(holder.type(), holder.encoder())); + + if (PacketSide.CLIENT.equals(this.side)) { + ClientPlayNetworking.registerGlobalReceiver(holder.type().toMojang(), ((client, listener, buf, responseSender) -> { + buf.readByte(); + T message = holder.decoder().apply(BridgedFriendlyByteBuf.of(buf)); + client.execute(() -> holder.handler().accept(new PacketContext<>(message, PacketSide.CLIENT))); + })); + } else { + + ServerPlayNetworking.registerGlobalReceiver(holder.type().toMojang(), ((server, player, listener, buf, responseSender) -> { + buf.readByte(); + T message = holder.decoder().apply(BridgedFriendlyByteBuf.of(buf)); + server.execute(() -> holder.handler().accept(new PacketContext<>(BridgedPlayer.of(player), message, PacketSide.SERVER))); + })); + } + + } else { + CraterConstants.LOG.error("Trying to register duplicate packet for type {}", holder.messageType()); + } + } + + public void sendToServer(T packet) { + this.sendToServer(packet, false); + } + + public void sendToServer(T packet, boolean ignoreCheck) { + Message message = (Message) CHANNELS.get(packet.getClass()); + + if (ClientPlayNetworking.canSend(message.id().toMojang()) || ignoreCheck) { + FriendlyByteBuf buf = PacketByteBufs.create(); + buf.writeByte(0); + message.encoder().accept(packet, BridgedFriendlyByteBuf.of(buf)); + ClientPlayNetworking.send(message.id().toMojang(), buf); + } + } + + public void sendToClient(T packet, BridgedPlayer player) { + Message message = (Message) CHANNELS.get(packet.getClass()); + if (ServerPlayNetworking.canSend(player.toMojangServerPlayer(), message.id().toMojang())) { + FriendlyByteBuf buf = PacketByteBufs.create(); + buf.writeByte(0); + message.encoder().accept(packet, BridgedFriendlyByteBuf.of(buf)); + ServerPlayNetworking.send(player.toMojangServerPlayer(), message.id().toMojang(), buf); + } + } + + public record Message(ResourceIdentifier id, BiConsumer encoder) { } +} diff --git a/1.19.3/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform b/1.19.3/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform new file mode 100644 index 0000000..a78d9e5 --- /dev/null +++ b/1.19.3/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform @@ -0,0 +1 @@ +com.hypherionmc.craterlib.client.FabricClientPlatform diff --git a/1.19.3/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform b/1.19.3/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform new file mode 100644 index 0000000..9a2fdb0 --- /dev/null +++ b/1.19.3/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform @@ -0,0 +1 @@ +com.hypherionmc.craterlib.common.FabricCommonPlatform diff --git a/1.19.3/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils b/1.19.3/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils new file mode 100644 index 0000000..62f79a8 --- /dev/null +++ b/1.19.3/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils @@ -0,0 +1 @@ +com.hypherionmc.craterlib.common.FabricCompatHelper \ No newline at end of file diff --git a/1.19.3/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment b/1.19.3/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment new file mode 100644 index 0000000..9a1fb33 --- /dev/null +++ b/1.19.3/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment @@ -0,0 +1 @@ +com.hypherionmc.craterlib.common.FabricLoaderHelper diff --git a/1.19.3/Fabric/src/main/resources/assets/craterlib/craterlib_logo.png b/1.19.3/Fabric/src/main/resources/assets/craterlib/craterlib_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..ce0159cc4aa4d823ea354042e531b4522d6dfead GIT binary patch literal 49343 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 z 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 De~rmR literal 0 HcmV?d00001 diff --git a/1.19.3/Fabric/src/main/resources/craterlib.fabric.mixins.json b/1.19.3/Fabric/src/main/resources/craterlib.fabric.mixins.json new file mode 100644 index 0000000..7c59043 --- /dev/null +++ b/1.19.3/Fabric/src/main/resources/craterlib.fabric.mixins.json @@ -0,0 +1,17 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "com.hypherionmc.craterlib.mixin", + "compatibilityLevel": "JAVA_17", + "mixins": [ + ], + "client": [ + "TutorialMixin" + ], + "server": [ + "ServerGamePacketListenerImplMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/1.19.3/Fabric/src/main/resources/fabric.mod.json b/1.19.3/Fabric/src/main/resources/fabric.mod.json new file mode 100644 index 0000000..1f3ba7c --- /dev/null +++ b/1.19.3/Fabric/src/main/resources/fabric.mod.json @@ -0,0 +1,39 @@ +{ + "schemaVersion": 1, + "id": "${mod_id}", + "version": "${version}", + "name": "${mod_name}", + "description": "A library mod used by First Dark Development and HypherionSA Mods", + "authors": [ + "${mod_author}", + "Misha" + ], + "contact": { + "homepage": "https://modrinth.com/mod/craterlib", + "sources": "https://github.com/firstdarkdev/craterLib/" + }, + "license": "MIT", + "icon": "assets/craterlib/craterlib_logo.png", + "environment": "*", + "entrypoints": { + "main": [ + "com.hypherionmc.craterlib.CraterLibInitializer" + ], + "client": [ + "com.hypherionmc.craterlib.client.CraterLibClientInitializer" + ], + "modmenu": [ + "com.hypherionmc.craterlib.CraterLibModMenuIntegration" + ] + }, + "mixins": [ + "${mod_id}.mixins.json", + "${mod_id}.fabric.mixins.json" + ], + "depends": { + "fabricloader": ">=0.15.0", + "fabric-api": "*", + "minecraft": ">=1.19.3", + "java": ">=17" + } +} diff --git a/1.19.3/Forge/build.gradle b/1.19.3/Forge/build.gradle new file mode 100644 index 0000000..85d0c98 --- /dev/null +++ b/1.19.3/Forge/build.gradle @@ -0,0 +1,112 @@ +// Adjust the output jar name here +archivesBaseName = "${mod_name.replace(" ", "")}-Forge-${minecraft_version}" + +dependencies { + // Compat + modImplementation("maven.modrinth:vanishmod:${vanishmod}") + + // Do not edit or remove + implementation project(":Common") +} + +shadowJar { + from sourceSets.main.output + configurations = [project.configurations.shade] + + dependencies { + exclude(dependency('com.google.code.gson:.*')) + + relocate 'me.hypherionmc.moonconfig', 'shadow.hypherionmc.moonconfig' + relocate 'me.hypherionmc.mcdiscordformatter', 'shadow.hypherionmc.mcdiscordformatter' + relocate 'net.kyori', 'shadow.kyori' + } + + setArchiveClassifier('dev-shadow') +} + +/** + * =============================================================================== + * = DO NOT EDIT BELOW THIS LINE UNLESS YOU KNOW WHAT YOU ARE DOING = + * =============================================================================== + */ + +unimined.minecraft { + minecraftForge { + loader forge_version + mixinConfig("${mod_id}.mixins.json", "${mod_id}.forge.mixins.json") + } +} + +remapJar { + inputFile.set shadowJar.archiveFile + dependsOn shadowJar + archiveClassifier.set null +} + +jar { + archiveClassifier.set "dev" +} + +processResources { + from project(":Common").sourceSets.main.resources + def buildProps = project.properties.clone() + + filesMatching("META-INF/mods.toml") { + expand buildProps + } +} + +compileTestJava.enabled = false + +tasks.withType(JavaCompile).configureEach { + source(project(":Common").sourceSets.main.allSource) +} + +/** + * Publishing Config + */ +publishing { + publications { + mavenJava(MavenPublication) { + artifactId project.archivesBaseName + from components.java + + artifact(remapJar) { + builtBy remapJar + } + + pom.withXml { + Node pomNode = asNode() + pomNode.dependencies.'*'.findAll() { + it.artifactId.text() == 'regutils-joined-fabric' || + it.artifactId.text() == 'core' || + it.artifactId.text() == 'toml' + }.each() { + it.parent().remove(it) + } + } + } + } + + repositories { + maven rootProject.orion.getPublishingMaven() + } +} + +publisher { + apiKeys { + modrinth(System.getenv("MODRINTH_TOKEN")) + curseforge(System.getenv("CURSE_TOKEN")) + } + + setCurseID(curse_id) + setModrinthID(modrinth_id) + setVersionType("release") + setChangelog("https://raw.githubusercontent.com/hypherionmc/changelogs/main/craterlib/changelog-forge.md") + setProjectVersion("${minecraft_version}-${project.version}") + setDisplayName("[Forge 1.19.3/1.19.4] CraterLib - ${project.version}") + setGameVersions("1.19.3", "1.19.4") + setLoaders("forge") + setArtifact(remapJar) + setCurseEnvironment("both") +} diff --git a/1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java b/1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java new file mode 100644 index 0000000..968da05 --- /dev/null +++ b/1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java @@ -0,0 +1,41 @@ +package com.hypherionmc.craterlib; + +import com.hypherionmc.craterlib.api.events.client.LateInitEvent; +import com.hypherionmc.craterlib.common.ForgeServerEvents; +import com.hypherionmc.craterlib.compat.Vanish; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.core.networking.CraterPacketNetwork; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.core.platform.ModloaderEnvironment; +import com.hypherionmc.craterlib.network.CraterForgeNetworkHandler; +import com.hypherionmc.craterlib.nojang.client.BridgedMinecraft; +import com.hypherionmc.craterlib.nojang.client.BridgedOptions; +import net.minecraft.client.Minecraft; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.DistExecutor; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import net.minecraftforge.fml.loading.FMLLoader; + +@Mod(CraterConstants.MOD_ID) +public class CraterLib { + + public CraterLib() { + MinecraftForge.EVENT_BUS.register(new ForgeServerEvents()); + FMLJavaModLoadingContext.get().getModEventBus().addListener(this::commonSetup); + } + + public void commonSetup(FMLCommonSetupEvent evt) { + new CraterPacketNetwork(new CraterForgeNetworkHandler(FMLLoader.getDist().isClient() ? PacketSide.CLIENT : PacketSide.SERVER)); + DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> { + LateInitEvent event = new LateInitEvent(new BridgedMinecraft(), BridgedOptions.of(Minecraft.getInstance().options)); + CraterEventBus.INSTANCE.postEvent(event); + }); + + if (ModloaderEnvironment.INSTANCE.isModLoaded("vmod")) { + MinecraftForge.EVENT_BUS.register(new Vanish()); + } + } +} diff --git a/1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/client/ForgeClientEvents.java b/1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/client/ForgeClientEvents.java new file mode 100644 index 0000000..cb096d5 --- /dev/null +++ b/1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/client/ForgeClientEvents.java @@ -0,0 +1,25 @@ +package com.hypherionmc.craterlib.client; + +import com.hypherionmc.craterlib.CraterConstants; +import com.hypherionmc.craterlib.api.events.client.CraterClientTickEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedClientLevel; +import net.minecraft.client.Minecraft; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.event.TickEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; + +@Mod.EventBusSubscriber(modid = CraterConstants.MOD_ID, bus = Mod.EventBusSubscriber.Bus.FORGE, value = Dist.CLIENT) +public class ForgeClientEvents { + + @SubscribeEvent + public static void clientTick(TickEvent.LevelTickEvent event) { + if (Minecraft.getInstance().level == null) + return; + + CraterClientTickEvent craterClientTickEvent = new CraterClientTickEvent(BridgedClientLevel.of(Minecraft.getInstance().level)); + CraterEventBus.INSTANCE.postEvent(craterClientTickEvent); + } + +} diff --git a/1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/client/ForgeClientHelper.java b/1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/client/ForgeClientHelper.java new file mode 100644 index 0000000..6776622 --- /dev/null +++ b/1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/client/ForgeClientHelper.java @@ -0,0 +1,41 @@ +package com.hypherionmc.craterlib.client; + +import com.hypherionmc.craterlib.core.platform.ClientPlatform; +import com.hypherionmc.craterlib.nojang.client.BridgedMinecraft; +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedClientLevel; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraft.client.Minecraft; +import net.minecraft.network.Connection; + +import java.util.Objects; + +/** + * @author HypherionSA + * @date 16/06/2022 + */ +public class ForgeClientHelper implements ClientPlatform { + + public ForgeClientHelper() { + } + + @Override + public BridgedMinecraft getClientInstance() { + return new BridgedMinecraft(); + } + + @Override + public BridgedPlayer getClientPlayer() { + return BridgedPlayer.of(Minecraft.getInstance().player); + } + + @Override + public BridgedClientLevel getClientLevel() { + return BridgedClientLevel.of(Minecraft.getInstance().level); + } + + @Override + public Connection getClientConnection() { + Objects.requireNonNull(Minecraft.getInstance().getConnection(), "Cannot send packets when not in game!"); + return Minecraft.getInstance().getConnection().getConnection(); + } +} diff --git a/1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeCommonHelper.java b/1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeCommonHelper.java new file mode 100644 index 0000000..281ddd7 --- /dev/null +++ b/1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeCommonHelper.java @@ -0,0 +1,26 @@ +package com.hypherionmc.craterlib.common; + +import com.hypherionmc.craterlib.core.platform.CommonPlatform; +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.CreativeModeTab; +import net.minecraftforge.server.ServerLifecycleHooks; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author HypherionSA + */ +public class ForgeCommonHelper implements CommonPlatform { + + public static Map TABS = new HashMap<>(); + + public ForgeCommonHelper() { + } + + @Override + public BridgedMinecraftServer getMCServer() { + return BridgedMinecraftServer.of(ServerLifecycleHooks.getCurrentServer()); + } +} diff --git a/1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeCompatHelper.java b/1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeCompatHelper.java new file mode 100644 index 0000000..f325837 --- /dev/null +++ b/1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeCompatHelper.java @@ -0,0 +1,17 @@ +package com.hypherionmc.craterlib.common; + +import com.hypherionmc.craterlib.core.platform.CompatUtils; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; + +public class ForgeCompatHelper implements CompatUtils { + + @Override + public boolean isPlayerActive(BridgedPlayer player) { + return true; + } + + @Override + public String getSkinUUID(BridgedPlayer player) { + return player.getStringUUID(); + } +} diff --git a/1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeLoaderHelper.java b/1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeLoaderHelper.java new file mode 100644 index 0000000..2be70fc --- /dev/null +++ b/1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeLoaderHelper.java @@ -0,0 +1,73 @@ +package com.hypherionmc.craterlib.common; + +import com.hypherionmc.craterlib.core.platform.Environment; +import com.hypherionmc.craterlib.core.platform.ModloaderEnvironment; +import net.minecraft.SharedConstants; +import net.minecraft.client.Minecraft; +import net.minecraftforge.fml.ModList; +import net.minecraftforge.fml.loading.FMLLoader; +import net.minecraftforge.fml.loading.FMLPaths; + +import java.io.File; + +/** + * @author HypherionSA + */ +public class ForgeLoaderHelper implements ModloaderEnvironment { + + public ForgeLoaderHelper() { + } + + @Override + public boolean isFabric() { + return false; + } + + @Override + public String getGameVersion() { + return SharedConstants.VERSION_STRING; + } + + @Override + public File getGameFolder() { + return Minecraft.getInstance().gameDirectory; + } + + @Override + public File getConfigFolder() { + return FMLPaths.CONFIGDIR.get().toFile(); + } + + @Override + public File getModsFolder() { + return FMLPaths.MODSDIR.get().toFile(); + } + + @Override + public Environment getEnvironment() { + switch (FMLLoader.getDist()) { + case CLIENT -> { + return Environment.CLIENT; + } + case DEDICATED_SERVER -> { + return Environment.SERVER; + } + } + return Environment.UNKNOWN; + } + + @Override + public boolean isModLoaded(String modid) { + return ModList.get().isLoaded(modid); + } + + @Override + public boolean isDevEnv() { + return !FMLLoader.isProduction(); + } + + @Override + public int getModCount() { + return ModList.get().size(); + } +} diff --git a/1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeServerEvents.java b/1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeServerEvents.java new file mode 100644 index 0000000..f354ddc --- /dev/null +++ b/1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeServerEvents.java @@ -0,0 +1,43 @@ +package com.hypherionmc.craterlib.common; + +import com.hypherionmc.craterlib.api.events.server.CraterRegisterCommandEvent; +import com.hypherionmc.craterlib.api.events.server.CraterServerLifecycleEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.commands.CommandsRegistry; +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import net.minecraftforge.event.RegisterCommandsEvent; +import net.minecraftforge.event.server.ServerStartedEvent; +import net.minecraftforge.event.server.ServerStartingEvent; +import net.minecraftforge.event.server.ServerStoppedEvent; +import net.minecraftforge.event.server.ServerStoppingEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; + +public class ForgeServerEvents { + + @SubscribeEvent + public void serverStarting(ServerStartingEvent event) { + CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Starting(BridgedMinecraftServer.of(event.getServer()))); + } + + @SubscribeEvent + public void serverStarted(ServerStartedEvent event) { + CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Started(BridgedMinecraftServer.of(event.getServer()))); + } + + @SubscribeEvent + public void serverStopping(ServerStoppingEvent event) { + CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Stopping(BridgedMinecraftServer.of(event.getServer()))); + } + + @SubscribeEvent + public void serverStopped(ServerStoppedEvent event) { + CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Stopped(BridgedMinecraftServer.of(event.getServer()))); + } + + @SubscribeEvent + public void onCommandRegister(RegisterCommandsEvent event) { + CraterEventBus.INSTANCE.postEvent(new CraterRegisterCommandEvent()); + CommandsRegistry.INSTANCE.registerCommands(event.getDispatcher()); + } + +} diff --git a/1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java b/1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java new file mode 100644 index 0000000..09bb181 --- /dev/null +++ b/1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java @@ -0,0 +1,24 @@ +package com.hypherionmc.craterlib.compat; + +import com.hypherionmc.craterlib.api.events.server.CraterPlayerEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import redstonedubstep.mods.vanishmod.api.PlayerVanishEvent; + +public class Vanish { + + public Vanish() { + + } + + @SubscribeEvent + public void vanishevent(PlayerVanishEvent event) { + if (event.isVanished()) { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerEvent.PlayerLoggedOut(BridgedPlayer.of(event.getEntity()))); + } else { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerEvent.PlayerLoggedIn(BridgedPlayer.of(event.getEntity()))); + } + } + +} \ No newline at end of file diff --git a/1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ConfigScreenHandlerMixin.java b/1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ConfigScreenHandlerMixin.java new file mode 100644 index 0000000..927bd23 --- /dev/null +++ b/1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ConfigScreenHandlerMixin.java @@ -0,0 +1,43 @@ +package com.hypherionmc.craterlib.mixin; + +import com.hypherionmc.craterlib.client.gui.config.CraterConfigScreen; +import com.hypherionmc.craterlib.core.config.ConfigController; +import com.hypherionmc.craterlib.core.config.ModuleConfig; +import com.hypherionmc.craterlib.core.config.annotations.NoConfigScreen; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.Screen; +import net.minecraftforge.client.ConfigScreenHandler; +import net.minecraftforge.forgespi.language.IModInfo; +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.CallbackInfoReturnable; + +import java.util.Optional; +import java.util.function.BiFunction; + +/** + * @author HypherionSA + */ +@Mixin(ConfigScreenHandler.class) +public class ConfigScreenHandlerMixin { + + /** + * Inject Auto Generated config Screens into forge + * + */ + @Inject(at = @At("RETURN"), method = "getScreenFactoryFor", cancellable = true, remap = false) + private static void injectConfigScreen(IModInfo selectedMod, CallbackInfoReturnable>> cir) { + ConfigController.getMonitoredConfigs().forEach((conf, watcher) -> { + if (!conf.getClass().isAnnotationPresent(NoConfigScreen.class)) { + ModuleConfig config = (ModuleConfig) conf; + if (config.getModId().equals(selectedMod.getModId())) { + cir.setReturnValue( + Optional.of((minecraft, screen) -> new CraterConfigScreen(config, screen)) + ); + } + } + }); + } + +} diff --git a/1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java b/1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java new file mode 100644 index 0000000..ff98802 --- /dev/null +++ b/1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java @@ -0,0 +1,36 @@ +package com.hypherionmc.craterlib.mixin; + +import com.hypherionmc.craterlib.api.events.server.CraterServerChatEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.ChatUtils; +import net.minecraft.network.chat.PlayerChatMessage; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.concurrent.CompletableFuture; + +@Mixin(value = ServerGamePacketListenerImpl.class, priority = Integer.MIN_VALUE) +public class ServerGamePacketListenerImplMixin { + + @Shadow + public ServerPlayer player; + + @Inject( + method = "lambda$handleChat$9", + at = @At("HEAD"), + cancellable = true + ) + private void injectChatEvent(CompletableFuture completablefuture1, PlayerChatMessage arg, CompletableFuture completablefuture, Void p_248218_, CallbackInfo ci) { + CraterServerChatEvent event = new CraterServerChatEvent(BridgedPlayer.of(this.player), arg.decoratedContent().getString(), ChatUtils.mojangToAdventure(arg.decoratedContent())); + CraterEventBus.INSTANCE.postEvent(event); + if (event.wasCancelled()) + ci.cancel(); + } + +} diff --git a/1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/network/CraterForgeNetworkHandler.java b/1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/network/CraterForgeNetworkHandler.java new file mode 100644 index 0000000..5b54d5d --- /dev/null +++ b/1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/network/CraterForgeNetworkHandler.java @@ -0,0 +1,100 @@ +package com.hypherionmc.craterlib.network; + +import com.hypherionmc.craterlib.CraterConstants; +import com.hypherionmc.craterlib.core.networking.PacketRegistry; +import com.hypherionmc.craterlib.core.networking.data.PacketContext; +import com.hypherionmc.craterlib.core.networking.data.PacketHolder; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.nojang.network.BridgedFriendlyByteBuf; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraft.client.Minecraft; +import net.minecraft.network.Connection; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraftforge.network.NetworkDirection; +import net.minecraftforge.network.NetworkEvent; +import net.minecraftforge.network.NetworkRegistry; +import net.minecraftforge.network.simple.SimpleChannel; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public class CraterForgeNetworkHandler extends PacketRegistry { + private final Map, SimpleChannel> CHANNELS = new HashMap<>(); + + public CraterForgeNetworkHandler(PacketSide side) { + super(side); + } + + protected void registerPacket(PacketHolder holder) { + if (CHANNELS.get(holder.messageType()) == null) { + SimpleChannel channel = NetworkRegistry.ChannelBuilder + .named(holder.type().toMojang()) + .clientAcceptedVersions((a) -> true) + .serverAcceptedVersions((a) -> true) + .networkProtocolVersion(() -> "1") + .simpleChannel(); + + channel.registerMessage( + 0, + holder.messageType(), + mojangEncoder(holder.encoder()), + mojangDecoder(holder.decoder()), + buildHandler(holder.handler())); + + CHANNELS.put(holder.messageType(), channel); + } else { + CraterConstants.LOG.error("Trying to register duplicate packet for type {}", holder.messageType()); + } + } + + public void sendToServer(T packet) { + this.sendToServer(packet, false); + } + + public void sendToServer(T packet, boolean ignoreCheck) { + SimpleChannel channel = CHANNELS.get(packet.getClass()); + Connection connection = Minecraft.getInstance().getConnection().getConnection(); + if (channel.isRemotePresent(connection) || ignoreCheck) { + channel.sendToServer(packet); + } + } + + public void sendToClient(T packet, BridgedPlayer player) { + SimpleChannel channel = CHANNELS.get(packet.getClass()); + ServerGamePacketListenerImpl connection = player.getConnection(); + if (connection == null) + return; + + if (channel.isRemotePresent(connection.connection)) { + channel.sendTo(packet, player.getConnection().connection, NetworkDirection.PLAY_TO_CLIENT); + } + } + + private Function mojangDecoder(Function handler) { + return byteBuf -> handler.apply(BridgedFriendlyByteBuf.of(byteBuf)); + } + + private BiConsumer mojangEncoder(BiConsumer handler) { + return ((t, byteBuf) -> handler.accept(t, BridgedFriendlyByteBuf.of(byteBuf))); + } + + private BiConsumer> buildHandler(Consumer> handler) { + return (message, ctx) -> { + ctx.get().enqueueWork(() -> { + PacketSide side = ctx.get().getDirection().getReceptionSide().isServer() ? PacketSide.SERVER : PacketSide.CLIENT; + ServerPlayer player = ctx.get().getSender(); + handler.accept(new PacketContext<>(BridgedPlayer.of(player), message, side)); + }); + ctx.get().setPacketHandled(true); + }; + } +} diff --git a/1.19.3/Forge/src/main/resources/META-INF/mods.toml b/1.19.3/Forge/src/main/resources/META-INF/mods.toml new file mode 100644 index 0000000..e6ebdd7 --- /dev/null +++ b/1.19.3/Forge/src/main/resources/META-INF/mods.toml @@ -0,0 +1,31 @@ +modLoader = "javafml" +loaderVersion = "[44,)" +license = "MIT" +issueTrackerURL = "https://github.com/firstdarkdev/craterLib/issues" + +[[mods]] + modId = "${mod_id}" + version = "${version}" + displayName = "${mod_name}" + displayURL = "https://modrinth.com/mod/craterlib" + logoFile = "craterlib_logo.png" + #credits="Thanks for this example mod goes to Java" + authors = "${mod_author}, Zenith" + description = ''' + A library mod used by First Dark Development and HypherionSA Mods + ''' + displayTest = "NONE" + +[[dependencies.${ mod_id }]] + modId = "forge" + mandatory = true + versionRange = "[44,)" + ordering = "NONE" + side = "BOTH" + +[[dependencies.${ mod_id }]] + modId = "minecraft" + mandatory = true + versionRange = "[1.19.3,1.20)" + ordering = "NONE" + side = "BOTH" diff --git a/1.19.3/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform b/1.19.3/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform new file mode 100644 index 0000000..a12ad8c --- /dev/null +++ b/1.19.3/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform @@ -0,0 +1 @@ +com.hypherionmc.craterlib.client.ForgeClientHelper \ No newline at end of file diff --git a/1.19.3/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform b/1.19.3/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform new file mode 100644 index 0000000..09e119f --- /dev/null +++ b/1.19.3/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform @@ -0,0 +1 @@ +com.hypherionmc.craterlib.common.ForgeCommonHelper \ No newline at end of file diff --git a/1.19.3/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils b/1.19.3/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils new file mode 100644 index 0000000..a9f823d --- /dev/null +++ b/1.19.3/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils @@ -0,0 +1 @@ +com.hypherionmc.craterlib.common.ForgeCompatHelper \ No newline at end of file diff --git a/1.19.3/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment b/1.19.3/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment new file mode 100644 index 0000000..02b4e07 --- /dev/null +++ b/1.19.3/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment @@ -0,0 +1 @@ +com.hypherionmc.craterlib.common.ForgeLoaderHelper \ No newline at end of file diff --git a/1.19.3/Forge/src/main/resources/craterlib.forge.mixins.json b/1.19.3/Forge/src/main/resources/craterlib.forge.mixins.json new file mode 100644 index 0000000..aa072d1 --- /dev/null +++ b/1.19.3/Forge/src/main/resources/craterlib.forge.mixins.json @@ -0,0 +1,17 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "com.hypherionmc.craterlib.mixin", + "compatibilityLevel": "JAVA_17", + "mixins": [ + ], + "client": [ + "ConfigScreenHandlerMixin" + ], + "server": [ + "ServerGamePacketListenerImplMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/1.19.3/Forge/src/main/resources/craterlib_logo.png b/1.19.3/Forge/src/main/resources/craterlib_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..ce0159cc4aa4d823ea354042e531b4522d6dfead GIT binary patch literal 49343 zcmb@t_g_=b6E}Jiib0B@BOMV0q*&-RB1P#Plq#qoph%aRpr}A77K+lP6Obysssse3 zgMfh1Aksm4PwwXPeV%*&f%}6m%sIPzW_EU`z9-tmNSE;>_eltX7_VQ`GJ_y$808;= z7CfC@eSGhPy=g3?XC2eo!GOS-@u>HT|Joj`46rOS zK9(qOTMkf;@-#!$-D6S88KkL-k28VADq>*fGR_7UVLEyTXD)~T#;?7yF7Uq|gI*2l z3+mesg*1EeU0(0L+D)H3Jjx_^{W{(y$c)$Dr&4*ILNcD#cpj58-n#7gVR9JoS8;DH zCa1n)3mva9mqJn}923@B$bV^!*E$ifGN;M*zIXXFq>Y46<-rfl^3zfsgw zu4?NgtZ>Qd`%ZH^`5DK*^J&_qLm>!rq{aSmV)c1U#HkPJ)HXjB1nh6_*=|umkgTx+ z+nakK`Ka?(G5Jr)dqYIDZodZDNR+vK>;)%r1!bfe+dYF;a^X zLdd=Q@~Yx-r(m^=%e;<1wyhUWK~TGUpDvdP8{Xn|2txJ}_|~?7r@^U)H-{>MwM9vLuFR5_7I*)JUYJoM@tTd>``)-TNdL%zbd zUMwaE%z&V`lcsF=ey}^~MZUD8He- z_HH1S%hxSYuk@h*XfIv-RU47PS`t>3rB_Zp{y(+^c0(x?*sOk~oJWhd{nv_FKl3m%>-H3ckI2RQH{7UmV3|VUns*NoyOVq;vs_HnkFN%N{5QZ`B#t)mdrmA=< zE$7OTIaBcLmtL8}NqDW}@2ts+*5}Y9ZGaISWvr8afaLv(T}wxBK51!Bg#7m*&ylkH zxSiGoTbK6y&665S`xNdg*u!}E##4|^3+bPZ%o-?!4rp*Ve-uv<} zT;7)Mf;ixOi*lhuJ7<>M~PMCP0^!}`+fUSXbDICKRRmy@$Q)OR`4PUe;7%-O734V zNf3@QP>`Oom7!n{WJ-7NWjE+3x2uBk2C91KZ^|~~5%$jG8KiA$u%$|wJu;fQv}gGr zizmRuh5tlkVy!~a74|D0wsO?q`RkBbNj?jO? zy6pjoV!sM8!oW{KkMqx<^ZX~kf!zEw3TPYE7iu@tfO09MWZ#g!zb8}vf3~i-+8J@-&r@KEFr5t_esWTX zKvl0Sh6jGVim`X_qb!>V1c=%yy+ z9z#+>|62n2A5Z#}lLlpr{Qog6x}av0u}lU64cXLgNMg&hj8H9dPU zf5b{pzs51g8D7(=G3{~dxAl1vT7Gj44h^xwAr$;8Ct$&iQcj)P zpTNK!sg2E1M!Lo%s%gE)vt7(kcK%-rI1t(N^r8LLtkw@-5@^Wrd@YQ-Uu+{4ytkb4 zGf&L*qmc|+*O2Vg!7R{#C=eF+oqT+&eOTj}t>}cWhpc`jCnBjJ*^C)*ht3CVJWuA> zpFX8>xMg1FBN)%3C3ZoBocuQu`t!{jf;K8bKMJ_+t*pNOqz8z=!9GHek{KO@TJh6A z$Q4Eh4tjkw&X77q`?s(4uNVySmjm2q`&51vpg*?R>Uw}qb6pzd)zH}eYLtXhV7t(Y z#BhVd@xmc61?~OYD5WdXcdQoHlhcH$rmvWe4!AnA$L<5S9v$IgQqmm0e!!oQf*rJy2V z%nXY1Q2QltVLsSP9&vIWha~M^rSeZ;bdyG4kU(z9ftz*zp6~4KH#l;fE4&LxT@j&% z+^`Q^+Mdkh;=!R>^M9Vqihw@60>ir#52%)ekdPar5QG}G*`u;$j%crbJ)C+HF8dn1 z7jMix^4s<*bNg5OYM$D$7LgLbI6biG2#9luPmvv8n7}`KJZygXK3W1w1$XmBwM`=a zG}`xQlX+&cueWVj%%4)dYI61Mcp5#KcH)FKRLu!0y^~$VeV74(+?s7uMiE#5fmMW!riWgc;bF z$jtS(O%^jm@xu>JK(}?l_puKe$m1WG%Wr5mVvlSFWL8JWXO{R5r#d3BA3u7;u$m{7 zgce8dWkg+Q_l99Sfp`}sIn*j=H00IY2VY;4)yDE2*^CDUZZxSJj(mPwZH~X+UFU8xcmAKk6o^7Y0J8I6#5~M{hrqaqPtQ+7maM z@6!_rMu$CnY2prJRcJ?|t9U?FuK2so$TZge8(SBmPqk1%i&qhlNyPj=0YehM`T=_r zDa~)62`zabwYQM6*U;!i?A9mOKH$3WVXH8cnuoRDI&?WXFmt*7xx5f7JU6&RP-;}L@=CSNpJTy7kmfYy+pBqqXdIkf9^Y# zwW~QA97h4P#XmlY>WvB)rDiA%@6X4evUwN~G*-1oMeUW{oGPO*O%2Qj!~a!KkK$nm z!U0q+V~#208tovoWU}PJM3If8j#AKDV6r7=3DRu!Rr9;dmau-K)^T4+DvVSb(7{{L zn~cemP52`^8TX zD-9%XU<_fltM(j6q&5?@(aQ%alg94A3AtOjP#L99eL}{Out~wI5Rn|?f7~zd9+HDDp4e!k(XCsB@%w0kvb9vEs0V?Hina& zWDe4Z>5)U{#J{SJ`JS|AtKW8N)@b4PGvoysp!zezFi3jrFTU-mhCe6Y)>l~#vekDD zs=2=eiPf}6Rhzqy%829bzoV(%7bBzAHaqAO{C|Fa3WLrceu+tde=w0G^`~bnE?=q@ zhoYXMzY$48e9y7Qo@6`k-MuGp$lasj5Av7YU!iuN!J^IaKYrfRzc^#kyrvv^(L4A) zqaYr?<BeO7iMIV%APOweX&RZkE4SAs4!GsvJy;3!h^iN!o8py=vb6ev$ul6yDpm zfPqU2@i00QLWO7-drFeJnBqVDRf^i|>o+o5S~RZh;Xn1pmNMMvwAmN)e zImo6Y9xTcAoTF@4R%w#jX~EqBj4gBj{YaRuXy?sAlM52>asnUkYGptD3*wxc!Vi30 z*2$em#a7hNv@gky8O8V+`_7qet8L9tj3)Gn-f2<`ZJa z$V|RM8g&tm5U{n+&vh!_>2O5<$<)b;h|Boa)OKXD-ksp_Pj^#`9Diw6{dBhDnG3PM zF2FkwEG}cs#b=Q5PKt0MA_n%O-5bgtBsMP%zdj`!tZEfl>gWgaGIRBtc2p&ev}zE~&T3lChZ>`Xci)FTz%{kHvF zq_QGbSpCl6scf?Ioa!gcXVw$kaxX=2d}kPtT(Hx_V3Kj*PjuovdCCdBmN=5SbV`83 ze&K80Pyso|x_cuRzE1j1?cE!VlHl%FJ_G&HLqNAh^vI4`=;F(3ZQ{p1$ z^OoTnn6Y5~Tr@FE>L)=RefY@bfRY?sJkB>wDc}8R9ycE{#7Gsdap+tq z?%qed$=)^ETu0>(JSq2c0cJ)9Y7P`B#ql1Fu@h_)m8s5Sq3q?p4Nc6VHWzYsYf#}= z8sU-p(gnevV>3Ve-T&6(pzPX62=!iTrp>52|5@)F2g8Z{z8BS1XpJ0~7T>Au!3?p1 z$O}${FZ^*-5kG+OsX@k(`_eNcT6Z@pb$Sz9UsW|_UM(Z4y<1in4E7l@H~)mGPtJJs zyRxrFDm)Xm2wVz_)qfo}>)>v6%8_gn#Q6Nf*@CrS*HmQ$uw{QwWufh;kt*}{eos~O zgm8Fmz592L*s~UHlzZjnjiGPrMD)Hj+97l+w~g>~4(-(LOr0EEJ<^Xpd}&7DbiNX_ zyLVMB!Ca&5aGDT^lW*NzA=?~H6OO|!`pqy-*k2|t9lzE%o^jsWu~i{_)n*mPET&u2 zK^8f^P2?0T=>+$+k0*%7?~a~f%C}T?TBOPuqVBxN$XD-beKe~dIwplsF@wKic3}p%Seqov9ctaM!dr=&dtZzy4OAJ-5 z#Z?jbsCn;bWoF_j>}v^}m!8Uu84l&gobGN|C3P{ZCt6Sq1c>9HPnF#ZnO$ETWv8$g ze(4AD8j(mXmfmrpA-|u0auTEyf=pt@2NmUfn=cD1)lHL)#{S7@jNDUC^05|w0FR^3 zdv9h5>mdG{I*0x(*+Ih0GW+OyK;5;}|WfG8B5)ztk8t;zciwhGD zsS5U)xqI=hX;<&e)`-De<-AsgSK)kgKSbJZcM?Y)kT(61ETBInTzu#yKhddu&?blY|}@1c*u^A$|C1eB1&TVd8J~A%>7~& zrluqT##kSfLxH)gGBSs4ug0GS9cBpF{mxp<60?o7KkwE!FTj%De+N5FusOP7ZTC4d z^XTRrL%8&r7p-VwG^2eRBz<`|L_l0!$&L;Gu)h%cj%*4fuNjlBR(gyvmXNQ?vC=Oc z2lB2i(~?#L(QWT!R*6pu98^?P-2wB$egpXQ0&K2Kv1W(c!9Mx;k6r)F2Wz_%M*Zmv zc0A7>9Qm!EBL8EpK}!?or?W^C8cRo#wl*$n!R^s}6nzDq z1Prr{)&7S*AO*%$KFOU>^)NkXLvNWl28_6j&F2anAKxB@)HHQk61G zCng+^yVf|-+bQIRId2>3fU!2pwVnF6fHt#3f3B$se?Yv+rUs`iKkmYpxH?Wg^ye9H zr+h2==^C&=6R{-hM&G!Icy7A-=;^;@z`a0gftpKOLDHt&_it?xy=|~@t3h>{eu?er93%@=Q5XrN)3uT3$L84JQNTa zE9RpAa#xGSinlAqfyJM5n2MtEIy*Ouhp2F~icke^s)sytm%RCTCO@U0yzxFT=J%wR zp;ZqrIRnc{ZYsDd`{}%^vFNn@aZejb$2(jotKr{>ZsK^?_SrHiZ>{XU>Vp)4Q;IEk zk^*Wb11O|YF=b?%I@!$iZrHRoRg28DcfIe9hUH9oQ>VZpAg$8(<{XcSO+t}GjQv*& zqRop;Zcj^#ByIm27H_p{Ze-x=KP&uo^l5><1KS*|b^Z5}hDs9t#uv9EPLVX*m7_LO zQwLu1iUm=uGq8|f|3FPs1AlsH#9W(S?ZB?zrQ$}HXdyQ3*-qf6d}VC*O5DDAcj`EN ze~{}%XEfh3h$c9mJQ?#N@k z+>m|ih%gm4|Hd4L{GWmtLXu4pepM<_NN>&9K+WpVL8>Fkkypj$(`K#GM_1B z|04JEYIpHl=itTJ?=)~zWiH_+v*g7GRv8c6bKHp?^!Ebi4e13H20ut{59ag}9KzAa zA5x#*zY(nPx^4VE>-qdW_c|pYKUx za0II*(wq7dBcD>#0>|@s-fIU*IJjUwFAg5dOr3o3%nMa~A6l~6`9y7L#uEOoa-niu zX{w}zm=713vx%(0IDO+Q&zliLc&_!;W%Vo5+9qkrkk}SnuyX#oFK~91RaX5+`GmP5 zXfVtXx6g6BFLY2(&fB~nI~8reF{q;Mc$Bh69wRzG32?}bU2pIbkRt7t|D4`zP>!C) zk{9|(mh0B&{Vb0|xz2ukdpl}K)L@M8@O@>($j!(a@~$JPsqfHDuM&A!XG{vw;62Me zLp`I+K31x+U#($8@@?Z^mpk5X+pF%|?lC>>Rdslr?`FLK`J!yab$`WJf zL+T9l=UtQA`znvH#IL#&p^0^WB?IPi^aC@fFp8V}*vWJ_<{_bjRV^V&Dp<*{&8vwu`Ob~C20XYiI%U?n12P`CyGH($_?2MMYO>rgjRfi4e#*#Ogsl_W}<7#^Ac#VC5V0CLw@4Fl4$x8~# z8A)lxqfbrvE)7y8F+siV{6ZkxsQ8mH(hSj$>J!@rOH)*Wnzz>-$IZ4QEP8mq<%u#j zYp~w6{`W9B@AB{b2Sn~melvwwflzXr{9-D@mZA)GcCz#OK~^g{pt+!#*jY~T0+Pv09OTJva zJ@z2*olWB{a!*_MhrPdpb2m-h_Xwnev5e_Vjs0Pb)qT4O9i82PzD#*F zw|B~xC!pVG5|-TCuQ(Co(cySJOEy3Ht#NRBg=5P5)OtUeh?XQBl%rPy8B%pcpl#M( z$oIcAUelW8k`YvtJH8TSpP;;JB6f9oM{DM@#2?kZSHUG$A&PUMBg>jU-Pn8EMcIgY z)}Q4ym7Q+(TiP-Sbze~S(QR%WxncopKJGhJ6)NTYlE(#uGn;iQJ0HD426o16Wv^`h z{`$_RQ|Ed5AMK_HxZCe(1`+G6c$JFHB$J(UqF!QTuTN36$oe)1xV*&XFFj&^?S(P> zR!p63iukHH48-wTqwVBE`Feg{G-ELK9jVLN0i{BA3-ym!dQD3z3qn}f=El)Y31=Zl&ZjW!A!(M4m6of8` zQfG4zb*kg(fAo$kR26O>CyGcndau8?9o3r3CWOwAj1)qxIO*SvPZz(+;QgNa;MPnS zt1(VG$G=hS&Kalj*YziusZp&!w`hKo^?Fs+nzgt4{UQ|QgLi-b;V~!{{f7mZA6cOd z3nK=_Jd3tmT>T@v5|}`W5-ltzFI(LI=|PNoNyqu&2K91QTUfJ|3-!#g(9d(R$jBoE ztmx^>LAAJGAtqzq`5?~{;+eyil!Q@fC@O7p{%)yOpo{YrbN>_bdpBaTiqi{y2GD~F zXGHZzv?F76USBc&mT`RV+{1F)EX<=eBsIz*60(r)6D?Hs;+_sLAFrHAZB71y$JZDs z%-T<{qdhnFd4PG=iLj@ru)a6f!{9%p;xB5qMJdPe#M$>NvNB4>w70|q0{OMG8TOHI zlZG(5uP@D$U+i5_E@6PKP2Uv^cdFSD2( z^S;`WB=NjH>U*(dosBYCAin{Wbw)Rp8N^M1qr#@@FDIH@N8E&`4yT)^9uSbnN zmuPPJ_xY^CyO%G}&WCf#=!EIsI>P>yehu^FrLtq) zkq6I%Q8EXrm-lOKW*j?8!=YZCTgSw}16_?w+{$GRT}1BV0b2M-Pr4o?!P@PQvoD%$ zB3Hh+J`uI@g|?ICyA(74U1)h!mb;i^)MyC{rulO|U?%fzXm+(tkq3ml1%LtHWx~r{ zs`NAPhrMsB&skEVdci3)I9DG!r`V7@UsJKt+=mF-Zt`b~upK84l*krZ<|)cl#YU z;C7_8uWX$2=V@Tu3=q>vNPc!nS%oxNtB8Na+92E}s z=vb>tYYdKlDEiYnXWem{lMW7nrB;i5@ASJT-u7;lt8nPZ+ zbat+NS*=+!^QKNS$BXLeJ%+A<$=u$X+%9;t#{JIYrJKL3)@v-Kr0WCY`KwgYD#hsI zSpH3EWyRYdGSgGnH|vR4{7~+M6pxz9 z)1LEi@p@*L^=9Xhe#Yj#`@b|Nc}`zdwdSdK;pf1(E^k7r}fQE8P4&)(L5booJk>d z^TXBUegQEvyS4Ax;p;b7d8TbaEufpUEt&jj904^G+W%TqMw`j)iob|NVtg!3Kh+LO zV9M$TFqcxn4eq&H%fQ)FP1Ntt8V0jihaJ+9T@jee`^SJRg-!v>>D>LY>WPDOIFkOEWOq;SRx2AsuHn7rM*kS|cou zGP_Q1?(f$rqra~=2Q_n;zGKE7?_E|;wYz8Grg$>UeETBiQG#JutgpzbBc+CSP~OA0 z-Ake6z8QwzO$9i0=P53m?RHBS+z(XEGf;x0*r#sGf=Q>5ew+uOX71_zQe&^!`Qg9R zKEkBmGlkGio_@{*{;;w+Z>JfYD(wK@?2VLY>Q$F)Hn4t44$IKX zq!j#p{Y%eDXFoY+7Q6a?X??Do96zTA9{2}(6GkIR%ghy@L7sqv=KQLq;GF;CP=X#KBN`xb{tYOE;+tvTE`I z(}jDFf9l2Z_cy+`V7u4l)8Xx_x`+ptqk~v?chwt*1 z0^1G`jc&|=;H>ZxbRdM2Ak8`+r4dyKq>{dVt^tAmutT?XUG&z(pZ>o$cNx9CEEIO7yi+b*``q5ee9My3JV)(w{KAFL_QFud8uz zn52$%Y@9J!|62ERAt>~`bKnvsjL0Hm7jZoDdQkgK(xfA~(RXI+6zSudtL?0&KSxLz z@2V}H{ok#NM7h=XW{dH^ZH={!?Iqwju9~6K$E|Bml9kBJ<%&G)y=3CIn@MxzCZd7+ z4d2f$eg0E>e^F3-uvU$MY*>sqx9JrsYZi@j>zh@Cc@44N&$?}|n)S|0ggEET>EOg> z)P+|SZ~@MXl|7#kUowywf2>&z$XqP?k;|`kt=B6_HA%`zKk|2!q=Uwb5Pd|1VWeBd zD;q(ri534Rj$%@7G#qKn0-4v^imL)2g_?hK;CqazKM{A`=rJ^|Dg;@xsgOY_u-ZLq z+CS`mX2mg&L4|@a>Db@FNf+f!rbMTolO)SeMh)qSC3BDm-Q+mPebbuz&Q)fMaY=~1 z+99WM=_{zMhU;OGa8M6#k2&);e&d7KVL}F3S&H=ecvn5C^s49bW4xg^pZ5MQ!qJMc zGGmAz+VwJVP9;?nzh)r`-7B2MqFu;>arF<@cqU$NO~m6vG~lfWxI;!st)AFw1a3Yb zGIUagQaV0qaF>B9-uvksbOWB&@_8`7jBR4(XxPtS3tFw+)rI1e>5l4K-oo2kf5zMg zVX3l>eBeMBh4f=OoO4C)$Y34_w}&5>b;${H{I(=q*5P9A;=%ZXD(VND=AbqHR{x99 z{L^Xp`HFDIcKI`fl@Z=98Y-K5bDcj8?BylkAJ>dPK4t*hi*3IUKqW&&F@};0LZHu{HW!2#_^6y%Idv zuhK?BhQpEC_pGJo8dFV}O(g9vtnsRyRdO{a?h3+Tx->&k|D*$Uod?mue`9&fZDsW-r4{Q_d`-!!S(p z$YpRX5F`#PQ#B6OEvm{Jn0gbJ9(vvO6D+-k7+`~A7Ue_`Vt!-!#?I%&;1OK<63FrN z@$Q(*Bwts@;rWx7lB-T;<6NNg#h+VVhH%ddQM)}ZpoR>`pO#(Sw{5F;8!}#RVxEzk z$z5bN^XI^l##^x3ZJpr*4B{ma(P6Riqs+)+lnGxvE|m)sI79u#-W-k*^PScRE@Ji? zs2ty49hr%8Bjmz)HJ1k1S8Xz)B*T7(2QA)#Xy`Z9c`&fCj^23Y2Se`im zqFQYpV`Or8-8&Mo>a4+W08nHB0edrR;pprP;Khx!{85v?)|ak!RN3rpAvb zYlRErQTU8Qg53eSBffPg%qf?Go!E(67C#R9y3!oNhr2O7Jm8}V@HxH90I~4RN9Z(X zF@UiU7PsUAB->QBZRGQNzm6W43PB^dPXd&citnR;8^ zF)pf43BUJu@7Z~Lws`oOF#a5Xs&ehbe0OX%J$6Lzuf-&UZ|zp^r@Fj5%vWJzC+~El z9gjfmgRr?@doXLe#?~e3C@Jl5w1%G}EtG{i3`ax|{pb%Aq4vA}t7D|kZTcHW#{$L~ zVx+ITWDklS5fof~Li%G&u76d&BS89nE=J=>H_OlQDi6rIzj&FBgMfbTJWp{BMi^Fl5R<) za!1)~vDKmL>C?xv&)vfe1U1EU;gFMm@#BRfWtH})qG3C0VwoHq1!MVS?-3&)bFhhCujvkuq?LHUfBy=jQ zgya>C7EYnQB4C$H$0CP_ZM@YW6z1#z;iU82{(EzO48Wb)OkA;q(pU&zLCwP|K=#t( zG=Q1fRb?_1p>{HNHs#J=;vmn)+=EZ&!W9VhZHlC2Y|;t%#T^7R-=bc?1)KT+JXtq3 zE*?S435BY2H#ANZr6Mtm(SM~Vxr=JCxFLTm*{^ZNuQ38;fv_-Dww`klhfI?+)6)7imSMNBAdNzjo%G50$|D252y+^4t+zmT&J@2JSiCiGu^`flKb!5&B(h zKY{NKJ^84^6jR3;Zj%m*+hBFt8Tp_4JZ1i}jnlT;mP-KuSR$yu= zByZWOvR%KZ%Y7YzO0Y5DSN6%?`}wsi#?f)_`kY-ND%-(%OvoX{=BRKytlrKp5r)Z8 zRQ1l@auwLe`RbaX+QlcC5##2jrFK0`9j z;i&5m0ur>p+U_Eydh%g|itZ`P?_&QtMqJ`)2E;TWd6Ayn96Ec86Ck(2TgqHv239?>nEnm$sQ0v3dQ`L&b z-{>G}6v&NuPi0Lbplg9_PPO@rFze0v!MPX^*_Ccg_~6G;yY;dWNI_OpID7Sx?*Jne zFYEZ5*=mw`{NZF#Lv{J+jt3RS!i<+D>h7cX-`g=d5tdtGF}-h7?#6@L)W8hC{RbHF z>Vx3iST&9sZbX?>4_(Qd?Y7e|o9h}>E<$Yj!@*{D)xJg4?=`2e7NY{~Am` z&t>jLi+BCGivgC<^`*y$U(dCZw0JY3?$KL*J0LKKL_;QLc}~wYHZ|7Ew32@oS{;YJ zbBy zj0^0K0pi8G@woRA+sn@%4aedz@^Lq!d7gn_>kDy;tY-lO2L~uA(I8P-qJ7r5AXYH@ zWIWA_^FpWsVy|>hhYyGFKVW*ij;JHXkI`YId{DHfQp@_PZOLg36{XderAU1W6b>5m zT3t5AQp}4UX%fsA2J_EsH+vj?lsu%8tOIpDO2J>Jpr~Pu6v#&bkm1>(YnQnP`~zo* z7sGty_t9gOkk1)vwjOR#mrx%XR7yPC5?4EmQuPIY<(;E=HKp2Os zbb76>d^F=#pj1$F+w&JN@EO9wCV`q5+Gy=b!>fS-+zqo2mVaHdx`GOQzb-eo3R2<{ zI|=-t+o}Cd&Tp@n-0*m~r~On{$FYICDc!+zaaUFNs@n-66qUAc9R+B39sa|(vZ(L3 z8;TW#}N!@=M#!vy?6Hf1#q+Xv=Yvz8X^WvykQMN^L;&c z3wHFUL#Y4oLs}+&pm?#km3Se63$jYV2|&lJE>tCtj9j$*EaFl2bTDp7y?4akH40)* zX)dD#0oP&q_Gh{%oeEPg*h$nlxMpD7yA{e0?NzY7hVjzOw}Dc(!B16{cp6o;{hG|K zchZrN6mrIlXlKF8996Z(oAD;N)N0fElyU4GPN;+C!hyEd1bQpJV($cITi;vYQn^I= zTF%h=VD7?sml^eoqt~vF7yW;T4Z9Gnu_3NouO`pP>y6DsT^$A4XvSlZRb32fj(6_T z2w*od>J{5Pfia93T5a2(%t|C}X`8GC+s9vaEmWS{HjfDzD{72PeM^b0>;^#E%ZAsr zY9>`OGpguz)aH_9G&Bp!<{6jD8)KdVB(F<{iA4ke`M-9jqV%0)cFiZ?R)6PcSo8Kj z<$mkSn~itYRcrjHt9iP6bE|vek86Rv1j|&BbCF(bSg{KjP+kIBBMyYM z&D-q~JpZ0q#o`n>1|~dtjx0bSP6QYl)8GJry#(nP?#|Jm=3f9tQ~2t#6|;Jtv~bg_ z2T$54=uB7Hyr&w_mSc=|x(hUmHGDO4OW%_p{{bpu6@mmH!3BbNXYD(Dcz$7Bu=%2L9`_wy`t&skn3WRP^qp0LsX}z|KjO~ z^Isy)7Q06>QlfQW2JoBww_-tSUS1MzW565Z2UvX(Ohy0w47JmAPW8P=*dVGxy2r?J zhQUeJuh^{cx1;J>4+$pNpvYoVi#ROdkC>7l)EBR*FEj{BXSWijcDhQ0voliXo^4^$ zr3MpA(rJ+j)a$BU+C6bF$O7O@@5aoj;_QHMdngcwox)7ebxQ>(13#k45y&tTQK}aNko{cI0lIs>gWqtepvWn)EzStck_%`G?oc6(GX9C zn+XJpl+j+e^a2Ocu9jekI`_ldujlpyK_dbkvstvLY&;+&EZ^;<&1|HIx}?Mt+(EZ3%1 zaiXXp?+DOIaDVsT`+Rg*D%F@VyCK|sH$P_@pj6CaH={3;mR?q!bOJa(*Vr=X*b#Hh&fF5F8oU*BPXj`U=3IC3B<5AQbjfAVj+w{t95~y5e`t{TcKr==lDQtWSJn2ty|ZEs^d?gg$0!ftq>Gu*(HHN!rDPZ zzoqI>F!AyiJFrkbiRF4H0^u<dCMk%i%q|$_ zC_N4RwWKrEnR^7-lN!>?R%KQ1cKT1rbg=PPd{43@I+@}hv7~6RV?;GUYp{6PLOj(U znLA#XqSiPEy9#!&WXD9_Dg7X3(9IRNJqq4Rhsj|i>}quo?FIPL@|KW5lmfXP0YSa* ze#I`a!>#bxyMiy0`>pF;fK=mKVW6vZDKx zCXsD8grObceX*@~gAqAeFREX01H8XOn0Ig;Po_wjGT@mW^9D#>*34bp$W?$EC>mMs z1qc2j2Dk{zLA;O~Ey$m1^~4ETHc-CJ$a8wyc6r=8?p&Ez3;??O`GRi#0H7oE$N=2$ z7N0l@nI@@#o`X$AI}l?q>L)1&q^?XK0t@Qz4ZIkMg2KB;pkDH%H$w`o+zwRfQc!Ft zUs}4+PEASQoy(Ds=a;f~JTa32h1W}RCmfoie*)A15tRQz)Ez~@75Jd*Y0z-**^B>+ z1xQ?|ZYtXy9uYJZNp=5l+hPuP_Es)ffoz6YDy`AnlJ|n6M5ey_EnPnr&~! zdyI|aNEN>C1u?=WxF`Xb%Qa`->nFXFCHgG3;6XwhRHb2t>(_EJ*KI6V{kM|TMja#KKT2`c4|KK0}^*$On zv|UwULaNkCf_hnXZOxUO%^)5@U}Vkw1>$ISHzY7$@pC&&34uKuUsu_YWYYL?fn z?_(I|KuLj&9cmL5LT!Q$yX)V_dJ-XdkSA~K;L*obVwP5lxbN&&syOO!A(Lp=*J=(= zfgA=W5-vOJKKlOnuALyy#fO&bZ*#RxRf#uTq+I)qMBbi6V%NrJ39J54-G3V+A{A5yhrWDq1&k3jIG5{;H|6^wD$&(hZ=Jo!rr}7-6={72;^z15okHZ&y z81`hx-{{5Bat$Bj1%h>KYHZd!wm8Y|G$iJ{-YD{d-ofE$kVe49jBuYbDBoI2_T|!t zyahFS32L`&#l>Kd=qa)s9h(|jj29F z(QsDeo_5W`^0)KXNon2${@t|-4k5@RW^-HI?CYD48fx1V?N$;xtz%+SOgKH4}-?WBE= z2VQVp8wS;$F%-cdv%Af;c9UgWar!nbJLYKz3K zUzC2>74M;}8+J7yXzZPYlak3g^22t;@^~GcuWfDTWHce^(>!g$-LFCT2Kl&J2->pW zGK(Rxc#n+rvwWq4)t|N89k6f#J>WgBoH@r{V*cvaC>2~01h3|!F3MhvFYq)~E))W! zHu-LPiQHRG#+ki#*Fr}tO4!OrP*#{%yyPv|&APt)$FJW7G}S6+WM9cvTI)^0aibFG z8bOXK?~!>_;$=LZ1rmG;_{}l9EFpid%tH6hHchrZ0_7Uu_6;dD3APmflzuiv1qS84 zSiFY9#TepVdj9D6!XLrH2cQv2Zw-xGrhvsk^_f{Hxi5YXEsp<&qs;~e`)p%{mY#ax zPN7-?74%0^TRbrK5i7Ng@wgt> z_52R_zXLf0plv5~Kf|C*k>S}tKk{vt0^{d1vMxO?=r1T^1g>EEvIs#8h|13rylt6Uv+k;YoggRmqbJzi1V}q97kUH zNKcrX5^}A{h)f03SZ{+313hu; zD1RtG1IN!SHm962pbXSa}&d0lhm$x>%U2oDGl?i*mS8g6ozQ@Flnrh{`+xk~Wi zXbN?XxxhP~5aDQQR@fUHcz0(Q>PcT#2?FLRaHc{o6VQ6MPddS+BEB$52#j-w6yNlu zF6$6L_aG$p^oF{e-=n#jOTdn+yQ;6PCp9m=x&-o5ca#h-79by+Y*XcjfgOFE_w!#L ze@Gnz;NojD|LQ->}Q;Ab~H(%E~q5U>jGhOr*-!XyiO;c0{biNTn@Wf0>w* zwb`!%`Z3tMDHEMMCRq!%#XvpYe}6dahA zJlf70S6fg@(Z@=p`}g~kz||EGFiiQ;LY}_&3jiLgk1gTlO#5xdBq*eMj?HMVhcsklu5An{BnBQfZpDo6Z z8)g?YEQdS+6n15hxer`^LXVE}g(}Ns1|R!nKnwc(um$o=LlJZNeQa3=8hb3df<}(sVNEY=4!xTjQQ!Fbq!9 zPu74~s|K$gTo^gfic}25^+5(&GSv5V&6*|(A1=Du{g8{wl?(is8hT5(Yk*W0N@orL z*9ylOWdn5Z6^c>QSn3#} z^Jn1FI6PyyWEkuCXQ zo!jBr_HgzkB#fhAMGxpEut@0Y(k^9;R?pSo!)vzU;0mw0LlOCA zSug6(&=43&}#x#ZnFH0xoD@n5Ao0m$T z&hFPrjLdrc_Ze2%`_8er!QRPxr2V)49WXw>rh>&?Qaz&);WF5=uh~T{v(BFo9a(w) zB@8DB!N=4Lj>@|*FC>5xQx6vz@VoK*JFyO>{gUe-?DW^q4AWjc3BdYYB`sjc(HZal zH3W7Ov@FO$J|%NvC(c2Bms`OXmqVa#>%BZ4ku1R513D!zze~;p2H23%_k2xFTvpq` z28ex?geXex`H#D-x99ii_eI3{k{P(>z1?;xc z3@Kp~%2|_5iqahIL(0Nuh+<4Q6XDnA?fRU0>Mqo@0ownJv^c?^#&bPmLUc;nGdas9< z{&1^Ue!~zg%~^;RxKm*0FY1u6iKkaKu9OIa3mfK(gf}+BK?2zYH6nuv*PMY=_H$n< z*aeH-H_t5=NKfO_ReCjCwVCtDuA-_4sT_nh;XOq#$ma21 zFkrg9hIZ^cKPZCcErn#00Fu~w!}DGe5^#+P0&q@z5E8!`;M*mP_+%OT83F}LAn7ad z3}3*YAWdpf|4d96?7CI#XPZRCPXlCfbG?YMaX?7N}{`C^ayJv6DL4ECMv&DMuKS)nf$s>2RfUj9CJG&KfFCKbK* zKDAu3Z6Mvw-Ixb`AiL}7@J~Xw|3?BT+Uk;tyQWQ3)Q~jFC233QTK?hVyjozK?0}pP zUk|o`H>=VSq)-7fFh#s4*>{=uHk^#a@|8f_w5+$ktf?uW))CE*tsp}&$0>)M zo73^q*)QXz=G-hIM`2k+m5aIzr_NvBHz3*gb=vhttpFY1*ifDZ<%h0llamPC z8^2a)1TDzwc1kpvz^%S|S9kFHxUWkb9Kwh93)`J8=FXDxBfgZgXp@sTk)#xqSQ59+ zj8gix+O^#VZwL66AmWqR_#AAGju-luKKylqP{hriaDLC28`Vrk3fh@r@L__ugM=>8 zhbWAC2N@{BW4(f11J|6yiPuuu1K_{37)qq+5>YDR00fu$=YS$@xNa6V1@zO#!M9G% zerX?>Zn6khk$?buY}4jF2e?%N=u1@LHU|R$AKml9&7|o(-LC>(J${|^UN7F9`4i21 zSBqXN4R?dJ^^AGj=st+dXZP+rlZpszEc;^wHD^9w1_h0HC(8%`z{}EHNaXIZMRn}4 z2~f`ETQ+(pIxwDzZM!{Dmg&*?;dEx-wc6XydtmgY6tW+$BzKuWn>NWrGU=e-EHu-< zDc<4#5ok!!lB>Pfunltpi41<4TWzjgtUqueZHs@x9+34HW6;OO+;8_6#0%x znc|0twlfj`_&2ld(?PE|^#pN1h(Fbp%G3}9lQ>3c1A0@2%B9~2o|rVAKt{Nu&{ss7 z_*W5tG^(~W|C*d*K#$%y(!7ZyE40k%1DE(qfyf&x@5jJcu&gYR{qq1eNfG!{LbLF~ zF}NA=Qd|Jft@x(2zEz)8PD`K$*Ua>;Ai)d`5t!}owm85KzA1-ze9MVk0lzrVQbLeH z&EJgH4HsK!14vH@@kxI?4FbfdUEl&iP9P~AJa)xF#46C&vws8k3NQ6BVN@+%9aDWW z7xL=8~AeAN~Uqy9nh-l)V}YLlKMgbK}NizLpu&+o4?(ac{QSp-8qZ+qywaq zr_EcH+t(eAc9U-CY0Mei4piU>e|NZi(rm2t=KH1(=46U{qhWt<+OJG8N)f^(I=g)J zQ<+uy4eo<5O7a=cYv=z*o!_zy2Tq@8*!Enh$-A$M0~`j{mR4^mCN;zE^-r%whgvm8 zsL3%~TJB$6FshV;k{_4QkFm1ADYf6;lBxExa{;^X9AssEiqRwZ+27>lm3kzFKH2!b zkgcGNzYQuv0l084WZ49VioivYZQSc*fCvi9=9DCaRG4-#3Z6*Q+6V;Rg$xX0G5t~f z=fuM3lfHous^~7SEj8TwkG124g)plx_dZ07BFI3EhPKe(q*b@I+=8 zCqqC&071>5yY5FX{^P@~Y$y4hN1%_L0{mBtFT+4dh}jcwM^WZ|ASj#hf0BBi0^cC58#ehEa{CQ?Ss3BL9Rl>%-%*E2IS=fsH|9Ru* zzg50-(x?2?zG=0Ejg3AX+vko`JVBqlVrCuKm-qXLyM6MviA4B^gE`B7l}OOHX^G?B zOD9`oA9gz4&qATa!S3(_lfSq)H}m-5vS6 z9(S29v|GCLt2WlNDasFQUD~`8h(YCt@}v6H!36W$@iWEk+fKIEqR2?~;Y=hY@rEEd*)=Bdx^03^tg=m1kiB`NdxgruOyc^9cKCWf_X>$3&i z^=-W42yFk-JZj%pRd%mBM+L;Nb4cf>d1VnHA#EO-7V*AU#JdXkv;LBb3l(!+2VQTU z{wgT_!}6|W{qZabiuwBmh%FUiCeH%-kz`UzPRu+XfNog;zlp5p#PUfAeGE|~%vr(7 z>jO}w=VJ3={W0tSnmWt!>!f=Bv_Gl%`GLzIzkIADCM?}ApB%FMw#350I$#5Er*3+*?rs$i%AzwDxKC$+yf$azY zMpsy(eOz=#yJ{&FFjj)voo)56iw$aNv6+a2(8{paJ}Zq>TzDBjuow2L4peUKr^tc7 zpUIk#`I~<${UNM~3WB^wddqA#g%EaE@>Ujr!K{!qA%0R?zCXT_h&C&ufa?0+9M}d- zE`bzm}!U+TSr5W9`G+F(Tpym_FT9aUd`ox?p;GWYb0CXxTE`NJIDvotjl^yfd zQWfNJ&4OUS+5uO2u>b1)e(aMZxBr~Qe;c^uSA?$D4D2p;Oicbr?ET$Y)mUD6a)0)B z()Yoq|H>&TSCT_GrZg!k&o>RcS)k(9mONNB(LwpM9ri*FC???|m&Oo>63qSxYRbfI z(Y#y!d;B39us^4dU!7=r)Qe7m3WgHLYdUV^@|waT|AoPdU2H{3WuBr&sX#>3-n8qC z4ZwD15C27Pnx|V(86Qa;_NwgPM8{FOPn3BFT!H(y8 zyiKpl9|IF?eM-T#(E8^^;>~Zyy}2y9D>{QFdXWi1f?W(Z1ri`C?iJNoYnD>or>YZQ zd7BTFcA2=mziqtUJ5=#_v2gr||3Oyv$CCpkkI>OQZ5#f+ss8!9J4)7D*+7RD;5@7^ zz;Gyt#$-#{E57S;@!yHR2HuC$qJOj`!x? zzUr4JmBB2%se$>LH+U*HrSQlT!WAE@-xs)b#2x%ykoCnqk87_Tg#1)zXED8eclvnWYLLMsS&rW9lD-P595BXNVITepfz#rio90r@{5fySd7AR$@#=oX zpr>@=A@%kiol|(7)d*Fi`|n{N3GzVm){#W+sfFLb3V~WcQmfFb9#zg$dyy-4B?`}v zbwx463dre`(q`YFK53e`ArVTPiS*X-ZqMZE?A713LAT&g`)4w^f||urv>wIKeLiRR z)Y5qN1T&IRk|9YSVq;D(CukNefP|RJ zewd5b4>V{$Fj_Cl&t=9_H{0S|EEFFDSqkC7yaX1=s#k`wtzhG@_BK?I`<$tJO zkI9f=9Rz;KW@I7;3JC)F4OoL~2B&5p=`8X85rPa#R?fCD!VKs!r1`}5eGg$@Qv^wq z8>xxSN2{Ha6Y1hZ$N7?CidLgFf}cd;ff$0T5@=ZguA{mr-L<8$<52at)gsqbG%;-* z5!8scm%`d!xV>qdUjwZmThJAs-(lspht>(7MwgDUInK2ie*W$Hoz*tiG#shP7Zjp3 z*3Ch4G(5`C3Km#NNESkpG(Ti+{-Tkv$AN%VT_`&q?TGCNkBN@FDrObvgnb^yf&h<# zK%V14w@bExK@yz$b(b4|mmF(9H>(*KvA&(8wCyL^n?o8QX1r}*F(fPnv}$zQ&M`C* z?wJ17qdvs58YKmER+)_QOZa_9XczOB{N}m!=K}Fid z2Lf{_u#3wnlu*f@n)`N^&9|C%I=V!}MwhfoGHEPV=@GOKm@#-@c#3c5_L~0OTcMgw zV_iDg zLh{9b(v-I$TxrQtVW#baK4D^Q`6Qj$-l!6|-}uUNFB`sm3+@Qx1CO$*M4ghs<<4+K z%~KwMv0paq!OiOyFLAhgMlL;bV02tF5^kra_izL$L#!}^4~|$<<=vc#5MH~Tm6n(Z z0x?j!z54H+w?>~(h?usqMUQzSKZ{<6Z5ZUtK_Pao$kk|jvfCB7p{VDmAkmvkpf?*+ zQ(kAf0Z88zU$OWZ#%i;F>7IRER~r}&FPCEg8#bZ>M}zvW+W1?eBOJnsAD{n#M9_|q zCSR4|oTLV|MPs0O866aqVS$W*66hW6!T**}K8^Zx1`!i;^@(qSrb8QLRTT;j%Vfjl z73IgzZb0jwbAsx5(E=nN!9&4==F z+(#{if&$~uZ+&l#!RZSi2^@ZVi1e3>xK*-X1Su)_M|EmiCg6-fLe6Za3*GW57 zvvbI%0L8MY9|o6lyqf*>-%dHImrr8}|83lJ=TUD!!e_9KFsK_enQ48z*9W!;`rk&76z*SHA?BH4bRz!k)5k=_6@%oLHPRe?&ws%IqA}fNa#^WajFqM zJy+OySo4k?$9#8BX<6B|J~?7pr3hUjj%J`*gz5m^_q_E6VBh z;zsm$HVN<=ZxI{c{TtIzjD0&=y=xzAVf@-gXaO}WA%FUJ{kc7te7P51?$bI}VEXGV zEiP{ZLXdC`=SP{L7_qLY@rmmEO73l56mP;F*4_`}1a25X z-}VdGkqa%_`;!en4l_HUitEPtWTK#kHSv3hEV-W}T;o3s&hZlhn!P&IMj#8_GeLu^ zt1BaSXXhKpg6#cfOqq{xaRetypgiO7Y@8@1H04aTL=AgGGgCe;X5l!V+k6d>+#C z$c8`3S!}AGmjdwi`RPKaGMV z)kEW+gUxUq*h=cT*M@4X<3A+p`}g~LXoI#YK_1-ZiUXN=H$0UNx5NNJZkx^7^*sLZ9eiM{LWU!aOi4SVe!t&tFd!+efxt8rle4z- zcI}JfzB&qH;*eh%7&^-#K+&4LQwRCe32jkAggRI1-9nZEwoOWAuHkOa)hQbY@U3T+z8|IjuJCibcq}f$X&3w}OyxGh zh%?k{|5fX-s(bw-1btpR%vKw|C!OeWD*LiRU7RQDjfL>`uS)-Fe|ihLJy5YBAj1G0J(rriHh| zS;0Y%Ie&{F<{!tOZ3F&L5#6*{E3Iv}v8bzw^e-UVmk3Oo}bs;}2S=6!U4IFWQxA$I9E8DhfMgoF%KaRZ@JAbfl&z zLG!UM>16HaD9*^ofj*)|v-bBteI`F`>2C~HIFPo}Vt47neU>|!z!odznJcR>dj>E4 za^KBl*~a{)$K_i|lNSt$-a^3moj2xc8)6{K=UNojORLpSbqKzunWz8tmj$2RXRT#A zO_e*L>Gv@2Q(udiqS=991g$FhHq)m}vp|^mx=$@I2{Yq1nAbTuxTFGk#&rdsoe3bC zFgax|BK{sY96L9kwFY1@X~ZB%vyvJiX=`!%WWQN zfO}wEm)m1{!k`prBx-d0L{ClLNo%p$WDSCEK+A={AFOSBuuj6$qvXKt+nQv>L>KAf zsQqgHtSNeK>ecDy-)1*=-vyTawduHl)St~{W@0!=2&astU(cXLse*s1-9J;q4t?mG zmUB#}!`;VWS)ZMDblr!gQbCr~asG;GgnjV9pSLIZ7<9|XB*CigEe~}%k3-KUm-isXlQRe%s0S9|+g||DOt`vcD@yW)%K7oXu-X%O&~pZ0f`)U;^$|Bs%n28yhl*jiH?Da} zns-NlVIz~or-Fbn{%E>{`32zuDmut72ok@iQstT5@h{Zo8Y)&B~PE6nQLxB~9-PM$L5F2OPvA&i08aW!)6@Vx2+Zm_KzHyCkxe5h>GVij3- z){=Jeh*sY|XX9sD1)5CUiL|_VbqqTAxc=in`@dP>B!o%-l9i`!Ac7Jq_k{G|14TW# za47!t$`h@yY&LG7r_MeoB9x2O;nEN*prYaY?KbZq zc&pur{&{kaoKh_NWTlXBc&PKsS0^`;B(&Bvvv=~Czs=8g*M^VIi6TDku>Y>T0(csHp zD=M#eRjRVl^FceDeY}O!%h_ymM_L$2=i$pA=uy7aW4Bx`j8sc4Y>@_v(w&2g;~A6F z*~p+`Bcr|d4WW1Hn||i)-16#9$ErJ(D0tTB42P?qiXY4EOpn~@yVHMX;7*(-50WSi zYBmjTpBEx4h{oXTLnH`DICRrQC|R?N#}qzr#uUO#?^ib6DsKuH4kFoI=zZ!R%-4F= zdNq4>dkuOw`rdyuMj(KDwUN&YrGt~S4>3aaHHZxBz>&Rn{$odXOmv}9f^-R$-6D)I zCm>vFx*IabtFBO%onM34e!la4hvPC>R?IU^ADdqtf!-_y5ulU0{q@j8oGgSYn$r@5 z{c$AIFaqCSh-FAwy{Vu}QWUp5I_YaoSb4X>|8HCK1THSw)snot+a~y_d!}~4-Z%^1 zYVv6dB>PGmTF$R?j5F=rO_Y>Nvdk1dT&44+CCYx95V0K(xpMbW_CV=lo9>UzMoyLD zLv{o>jPh``Jdg|5AVS{i3%WspC%;)EFEgAWZAd#eWVPf2G%U^Xo8GCv9{JCG>w}tK zC*Oxr(~IgLF!bh>cUrm=ZUXBoGUOCrN=5O(=_(^m3m+mqa%*NhR884i=IGqV`S6$H z@1Zo6C=Ind!y*D?TqU1jTJ^}6unnF+DilW8tPum30>w&x%P8Lbe&E;4Kb7YX-FGeK zFYN^V5evolLUBtxR|@S?BDJ6onyOF*0=%_;R$vOpO(<)Tcl=sh&-HA)($7qs3f-F< z4+%x5=iqw@uSa6Pg>`1z=^>b<^HwO4Pp&?;IfNAe8!#{A)Xt5TBC0!20&-H@z$e^8 z=0pC6?_N8V|6b!PJ5bc;ETeZ3g!9F-nDs4wTF(VuK-Bw}qL^dIL<5B}ZV9il=1LYr zM(GyEPWT*X>EibNOqeeV;AGwpE~(xr>NOCAbDS%Q|1Yl=K7`mN0TeuxcqJh+J3%D; zY*BnZQmVEOm1lYJ=CPl#}@PJsM*8xM-~Mi2};#vM_52rN7(F3@f*3-OoGx+v#n zJj-HI$taBmPbz`-$UxZ97y`3DOYp#rWtXm=t7E`rpY)0jY>E=6M<%WY1h(S*RgYl= z!dL>u&%-P+AQHAh85>HoS|Bj)t?%39NDJU7rgz<7PB+U9t7;?WRuQW)fl z0PVMq$;=fPW!~d5umk(C6^fnXewU9lrkiDwu3^AOr1tI_(2MY+h&51TIPUg@qGynA zKLf@KEXn1fP`m+z5{uhV@kD+gIukTgVJJxCL1f6cbM2`V%q*VpF2lWRalhYo{pYrl zNt&CfgV6Ld=nxM5sL@#vK;o609Nn^;Wc({YWh$<$A(nmt96Rp&5iIyd8vWx_p+aa0 zG=jH;e*%8+kD5;u_Lp%Ru_c2e%#eSDc6F>fI)DJo`R7+vp=srIyiz2v^_z;c-E?#6X6HiwpOd_Im)xSH+dNCr{J zzWNqVQNT>(BK@Q0eFiS{g{fCE_B(>9JJ;nv6yNxZMQx|9rAX)8h;ROC7Q^?7l z#nnODE)azfIWsMzqL5-Q07Yr5;ih}k+~{4e{IW=Ag1xrb?W4q8n0~pkanjT)B?_%~ zFB22`3BS}(L=CJ-q%P3TKNUF%*_hXjv~t(Q$kvtJ(Fw&inB&lgJyTZQYiIF7)HUTR z9q`e^O$-vxKY`d^<$sPhr0~Rq5sGs09XK76tbuuo{Fzub(-HoR9u%DEeVMJ@Gj*eb z9G>8k=5z&(djpom9`}GZ6#@Nt$`;;vbjMzwNY{P7UqM&vyWe!Z!jF74$v(@jm(^W$ z`J#6Jnrj|}$Whyyv7vY_zy^qWWA5hCFoK_DnxBYzG(`k%&XPRzs;tI{1*tLLv?*L3 zoTL8nu_!vUNZ~-B5 z6otGo;pTLCpWr%4!!CN>7@}0_f&ZV(a{VcTLs%+<8iBDU1EvQyNr|M%X;+^NN8;b} zqobCpzfjoGK^0tu&^nOKR9Ol;)q6r}q$O4Zg&$slj&g(*bT8Kma-u9Rm4A_#X8(X> zQX@Rt4DC|Cg8|(M9G9Elz;Cf}bn-WpVWA*c!Ie=cP8SBcIOkwRjZ-D~@+0vkXhGO> z1oX@Pm7S=7SIMpKKFxa;h7cwc0GdLTDY|N{n^uXDS#06;^S29tO4BVpi<7~r+y;je zo%$}o%Bw{Fj9!j&#L+FrWiYRbn5&|M_FT*d4HU5riw?$uq7uJ=-`oqwXUg~FcICcX ztJ)iN+sGm}(GZ2OU~{G7R(naS!c!!B>m%1c@)Nr>2%A;=6_pLMHJ%sAh$^Y@Ryf$F z!T*vpb)h&2LyMLR)+2Bjt(D4}wWOe}dC+^|+Okj;_J0i@)#{M(1H3rBH;_ zMA63`sR9jtDDv!U8S@NL%?brjGvZII3H$|rCX~z{$4H|G>*}1n=$)&CEQuS=w0er$ zCWr#(M$jdNu_Q*w{{vK|BxC@if~O`9AM>^jSjh|(@nMkg)}NP2M#=+=4x5F?04u_j z$wsEaQy;gF;elAZlJC*1dCIE2Yq4}G67tbvg=>++e^t2;lZOn@6?Bxb+AP+O=e290 zZgOz$@G@T?^fdrLjk_^5bOixRzJVNK?h@9NbweQwk_K`*+;8oMi#b-G(a6BUap>ur z81gJFHNyQ+b(y4GfI?x^Wg#f4Gp|YEDfFxG0t)zaNLu(p(fte4R<@UdT3Z2D{dG37 zZ+!SG?QN*!{+h=;&_ml~B5yV}?lrf-#N1$2^5yq0biC)+MnUX1P$5qc4cK9~J77wL zg98=Kto64rjs1{t$_kDwShY!&1*H@EK?XH}9RRuyz|ok3vtA03vp$O?fr4+}QG#Qo zsqKQxhaNfIhHDgBlK+lxS*Tz_5Zdn%OmKEL6@wL6|2AoH zuar= zFM~GpyWB4WEBakgNap(!*MBZj5}vQMK;PFQtj(>eIbgT?kH~HVB%Q;3#p(8Cjr+J( zo{LR$)ww$l06YmLpLOc=1B&cz9iJXd68KjcqiXl*^BVU7_)-wvp$fQNdhl$bZHRa zw@?*ZIo47Jads>&8ti{0hzj<){}&>_3F(j>16b3NjShAO_}bumTjW1G=teKTrj=}6 zqpcMcWbqmA_z9n)Yz5yZ{9lW!;{E?1NeW_hrZOeWl^WUh-=z}*9S5AB84NEn%vf6V zseU&xL0X)QU;7fy!N8=O3i?ds!(0aYyWn`n;O+W{-Z%e)o2BWOrf=uwGMT8QqwFlG ziAy%$RejF?3nhrwxT40amP!I?aY`97Jgg?I0Fsc1!t$kNg*n`%gB=^~UH%JpPZwJQ zOXb(ZK*&6%F>_UjY=K9z_uWeT|ORcCdP$eL)JI zxCWca0vSLLgLBFPX)}5Qx0EP`@LnnI@gc<*L^*N(-JU{(jyzsm2zI%|AFlsLBt-69 zzFCQqnX`0L|5j$YM~%mqQqyqxtqk~cr)8#GtVP_H^1))&yPfPbF@m71rl4s;il*}8 zn*W3NELEn3Lo4IYG;FDrW~s=zS`(T4#N~q2?cOk7pXv)z-{l_2397u>=q-XFdL+X& zODQPsa>%rpFP%4#uQmf)>)5DsWRu+Zw9%;sM(A+g!NFU00fDfz=SkNf#j zb|wHE`(Bjz+Ouv;@)L;&`VzKG3>Fj`B?iJ21!&=if#jeW1KvlW(?ESEtzWMvf z!x->+8~^GunHr9Kcxv?8pbGjROyBS@>&pjGNo)%lf@-d4J1pULSokLEDuA4<%uABC z?|P+EW=}4J?HyT6;{3xIo5dAJBlgJbqL_A{5!ZdsyG&*vxJ4w2Xvwh9UO z5p3MqhTLCeyISi>ZfBYEcsU_(@qSh7>lF(cNweTMVW9ua)27q6@Q1MZOb zN6e}zA>T}{K6V}1$5!ap8DycXC^;@gl-Om-t07d)>9^v^?`Mm7q?cvMhs5GLeg+Bc1B)Gte!P~@(EfJ z`r30(73#b^9sjZx?O^r7um;anUc(T#!ZM^?{4+Ok*n^o|9t)qjV@1weH#Of9rv;+9 z531tz9t@GAkx$p6ldrBb3?_1Vmiax>B+7W9BeH>?Byi&ruB)i7%u3>sKk+^mYKy@@ z9G8uC%jqII-dE|6ql=k%r{fq61w5@}>;?NW&+oviTq(4l;sfe#naDbc+LYh6f~=sJ zGeDEX55tG6b;!Sd&SNGN5ngYHT}wrvTn(NKHxK$M6AFzlwqB@q7xH>a-nvGepn-zu zE}C)6S`ih{rLC8|F1W61^;9ayJ%Y4}GP+%GF!rbd0lH~fXZ*^Qf}OG4dBD7o^6&;7 zuvExMk#(yPF=bN_6=oy2ZIkId9@a%gjBHzy-#t8{2k@qSnoP-pB4?{u)%+qk!~kOK zxd8c14ZEsAX^HXkuztDQBAI)>n$27gmgq?+u#}AwP#3{c&s!CiKkT?COBj}Ugaye< zV3M5$5YZAX&}zBJ+PPeCbsR!i&lNxJqYU=q_#mntQwrS_JyQ5=Axqi?Zm*XAJY=#kvgSjO%qYmsc3emva|G_c%k1 zhK}5aT~olYK=j#Ijv#nMtU9mRNu|OI)`m)xCi`Y$KL0i&4(Gi=|Lxnc>-_2^w08bk z+Pa^)EbH!bX6%Una>~Q(uP1MnH*9(UUjCKB_M@Lc9d$(tIf-P@8h6sdSL+FR1y~lq z2Svz5D8G3U6b-bzayPQ2manfm*Fa~`aYyg3TOYX%hC6ME-wOV9;>lF53fMahfQiD) zNK1l?pG6G@yJ+(VJG?sh%4l8**bIfEd`rH@06 z7}KWMnpzR^K7Q|(cJ38!>F<7S9RV(|;#fTdR!Xb_5$RrOy6c>{4)_DjXE(}I{hc0)LRDNN5rnkw0z0SX z2`1u;%7Qc`k`lpUWE|47a(XG#^~t`!IGQT%xC%_s^^R^h!N)D%UYRkGse9e>#eKg| z`HY0LKADw1wP*f$BQf1eyiS)G@&A}!Y<;c>V0!q=LNsQnDVk`g5V~1273KCAbh2Jz z69~Y>qTVm173o9Oa)+0I8n%tznHv0 z@P-Qu5;J4X)x0OHMcK3PzPzn++DAh${c9d`>!|avdsf4cr}6PxMJ7z42C|FCKwm72 zE6Q%A2}S5+!6C7T4Y0EHKVle8I|Y=a4u%M6l0)CW+>m6{aAX_n2Z4TRuT)6mKWo-`mlr0Xw|Of)H!k_wj0W*iQq>e2!Iw}EKQ|RmVR;B z^-`iKHHxm#)x*=`Or2<`!(WjexU!f61i-m2d9{p?Cvew0t4Co(kFihdpEEl-4Ve5> zH&vhT)Ou2s?D^GiG_nB}4jxgMVkKLZr;9Nuh>kt9*)L+Eb`PJ+JE7~;{wtBUkblCg zPq*$6L~C~&JdKD!TFL9^LTKXoweJiSEiPM5WR_20C4VD=v9Ul@MpwNICH1srw{}CQ zG6qYK_p1DEt7AoYCL_~o@2+XJcv84mR{{~99T-+yQ9S{wcXrdD20vL+2JC-n_l8bqg`c>* zeMbNkryuo@&3Lb(cDnQns>r7eOz4GWZSIpqe$quc)ti!c{**Ek*UO6 zc&`4Hc>_Jda1SvZA^ZC%;^dn6=#9CTjY0lB(=sMo%iWVKJ+?meVLPXi!Ct;c^W4`N zj0;3B!0;l&m#s!$lr7Pq&N;y>3&_i>xdh^7i_HeTS;u%W#cLHN6IXd~=B=}-)w_U? zSF`deue2cQH*UBN*nwPyDZF!2xf0F+;WlJ_W$iPFt4@*yuT>m0@pCBzWMpH`R9W`Y zqElaIyE{yPX;hK}?h@`t-FVd^T=5%l1ARH77Z;=Y_-SzOv<*cE@+M5ZmM;@cuQ-94 z6Drbc&sS^un|0V3gN$NVPE?;6im6j^-Mo2t>^jx#tx1004{tWqU}|gjNbfG)WpEAE z`WE@FSS`30kz@F+SC*KEBQ-6Y#|DcGpEcoKw>C*^&4#NZ#0s0EqsQ+gkGv$JP2Sbt zT|v$t*xf|;B>MM=_zq^8$i{Zn0Gg2XmOLIH=g3Gy?iZ}Z)`ZubUxvv!-n-XCqea0R zl}QqyoA~bbn3bB`G414Kg3r^gI)!uG;~pa(629AjxqMvy5aH1Mp}4h5Q#{zX5W3_f z#WFcqV9ac{P#=v41W3u)k`#`T)qE`U-n7`KyhGF4r&>q%iOM%|ZpFxJW*!0+#sL#- z#n0K3y-$Fz#0oxaIlie2rPt0>hnPqFKrL@-KbVuC!awECmp#J>X8K^Dr^3Trlm3{7K74|9r%lhuZ-*p3#{$ld7ht|oX9$tUt{OjHT zC9URt=pq22SQ8!%ou!)I4hEseL%S(Xa+S;OM5`n~E7l`$=duehLGSzR+ZuHNsS255 z67vESOzb%WiSXX=enk}@@p4cvBJ9Kxd}DLXw#RUMSRb!^F*fh?b@u)2?(o0ArfU|8 z7IXliSgHSB&gWoGYwoaqI_f9Cfq5ka5iluK1dQ9w9q@X-D%x>Nfi>Bw$gCv$PG|vpjwN{ICDV`__jBuj{U7P3HJT*4 z1r(X+%U@_~zr=eWYV^-kNr5voKT~SkXjkbqt~Z2^9{iklc-0b*CM%jM&3^OJL+zjYb2`%Fx&6!~^Niu?^piJN%g_6azI;$^YYrqQOT^W2qX;>~kvIxs(rf zl+VxlxRwqXMa(GJuP+(BxniAl=S7G$a@ae3E5*azy-QXnT_t>}eKjxcypL-5k0&8= zGg)2G$xbw{^J!gPk*4Wm-pwEOHsOEga%!Lc)w~|w&!&*_cSNq`NoaaZ(uI!-O$uMK z>m$RYQsYWK7Yk)ZE%L68skhQeW$-gW#`wc0dF3O1V%@W>=!$OW6TH=A$?Qsxz!DRR zA=I3D)A$LO#0zDfvEbsRErwUDt=Ajxu~Z8wu})gBUrewB50g1Hvk`>b`HZe|2Z|fH z7^{ppL7RWNvNJmg(XRb3HcI)5f7n~woEnj}29ye?jPRt})6hQ5@a+v{kpi%z>*Bnb zMrfJ)2E`%+WW;P&W3#e!FSS>nGF@+)-RnGW;G)}9El%_}+^ZWskRnhN92vxDm9!N9 zBJqaW%j6oE-;JLwu^C_&$1i|zt4u*%KxA$XmB0r44Al422U?;LD$*#H*->anfFh;?>(KqC$BcIVV z(V%TgoPY?ip!JbpmU)|`1N&_}UbdGSd6UmZHmG-N@HBa$(ZP)wT3m6L3$HihLEky} z1Y$-WJhcUTNf4KPTMSNP$)d95Pvh#M&qB^A`(SZpb;OgCVZre+ECAVbOuCBwasTye zuB$m*58uDf+ca2wI{D&slVj_*?|4bUf-Mj4C#_Sd*^6|*wA{Wiu(#;frgrw2!rHs> z{f+*Rz@F)YqP?|u-*>sSeE7BEks8lazy9^{mU1g;G=8X%gTCDt+3U+ibnrVvE7JY5 zYyMizw%>lPsdjVc5N~llu%51zC{>ia{3~W8NJCi-u2cWva5+=7X86wZ@d3@dFNUpz zpb`dL{f0cwt!XK-#3&d=nH94%QPQ(q7I&VQq<3E=rg*x_op{M#UpoZ`r9JfeGiJOM z5bx(ry7?V{tQ)s0&wq^+ar}w7>8JDO;r)k>q`Q20b+| zuZMc@<=-&P?RmF+B9oai?W3^SUj^2EJ+fyd^*=}DY7!3I^u*?+^Lwt5`!RIjz+Zf- zf&ABpqj@ctS_+Ysk?g%!qHtTth|Gv6 z^CGgk*1h+8ZtwT!^Zot<-`6kP*F3NDI^#JWkH^7mvuNuIol2lX2SOY0ivQ+9>5-(D zbd?;EJE1%8*}Hc^`|q@X*Q6ih%-LInxIX_uBlEQH`NDJM&9fD5=(CTiM?=@94v!6W zk6Vvo#9E2Qce|jW!qRYfdPC(+LtWf;>&Kz8(8!7}>n09Q7_PaL0E~Cn?_^72Te_sKlMUAAj5+#BVfSuUn)qoA53FsX~2C zK}~GKyx`Zu7MO*xj=eAJNHo#-3DG5zp6PLKLVN@I^so7QeJu+9x>AFD`6lStjAFD= zG&GFP(1Ne)Z96P?&pjzEag;qA)Rm_-@f696SWDNG@KYK!C$@(pwO+>B^sSf{knas% zMYccg&)>jKggh9c&eh83mmMYq58y7;?P&F{Tsc{iFfE9MDWjxpgDbW_ni}d%*3Wia z6v5E-EQ?QX7KD!8lM_oWali((wZ1o8>izXV2!q6-y?r zl5Vb;uJ1`YJVb*&v+KOcr~jN{nH(s1@nk=UXnpd`C*n-6vj@Zsmc#7(Qa}MFspWhk z9zx}lZ5JWq;b(xQkKiDs$0A7;u;yoFb`w)wl&Yqp=6mV>bNsA=`rAu0u(s97y2g4e3OxZl*)##GB(P-@{9`GuSzwa36jj^5&S5PM=&@ z)`E=Q`Cgq36!sj~IDgV(1aaS0K{d!F_>uTw?+QBCS~L=^nP; zJ8GNkASnzDI<(fmdym}=xmsa8ZeMpxed}8F?sf?-6N@W>Fq)*!g1Lt>O z^ZaD&K7VM=`0UHV0Qs@izH7!|x!H^A?EK10Fl0lcA~@W#Vf1uu68yeVVkSKl-IDg1 z>AT%Xyce=cSqz2rv9UkJLm(k8LMLX%)DH(n{>S9HbzeLIfRPh$O*h+??=A{tEoVA~ zgD0;>=U^dh31>zY;EpG;_bSD&Fmg+`Mqm@3I#(#j%R5DeWe0D*s*qNaK5nDzE@Ac+ z0X7r2-h6MKqHMKchG8R^ULHl;3cGY?6UeEQ4sq@q=|6%_&=X#i~z5%&7p3`XQdk4ZqE3$X#+8Y;N`Eato zXw+a=epB&B7E!A>8%&Y8e0$P4REPcxtwAUa_ByA+JvCfGZ2|c<%8oY>E&-+E?4s@T%aSU&%epTVvCQQ zJSWc?{F)>dj+oIf1PR}vR*|}r9Zczq9QU%{UD5XX zCulQ1jauZC-`%3%2K{ewYgWJFe0Jtj%a#=ee?k;CSF;lN zjCf6<4;|Pze`tKab{M{-VtcVvhV@AfUsIsg{WybSCi2UmjKaG|L5KT}lFB@VvS@!X za9s*Av}$*uQQ6j7iiP9=m%0tU8;ydUY&wZU1ls_=>n&nU&L?8f09_H)oRr?`A#0aV zQf`+{FZ48i*^(*!6nP#Of=SlOLjuUN`d8{))^C)y+YJSjj3!<0)+?&}W<4fm9MBx8 zr}9eiJ)wVPmoF|3pV(EZqZr4QIj(nG&pxhpdL|JLV>Y_GJJ7sP=A@T=a=hxWt)JT8 zD-Ifwp9NF`2>y(H!02hri!K8k0R1HGbc*;U#($fL;j65iUi^6GI{iF$X>nrjGAV5$ zSX1Md-vJcx(W^I|Hl4cqBv-U%hwJ9gO(}b;P~yd5hV>U~+dUhP0G{~Fi@H-oRow2! zd7b`#qw$_6fgmQH*|KtTNIY|yn zn>CwA91OoFi{IA{dy2&?j&4vFSulPR+shM-n{OdUjUXbIFJHUpC*-y?(W0yx>lE5V zT4p4^tvqPlt6ebN@ypdNu!kAEU50T<@gCXy_rJ+ zpb>6ig8El#20D?+H9mM289!TYv(@@63J<4nJH;|1XKIf& zyoqYo;wB8R#Rp#SOU{g^!V$Q9^JLCS%?>-{IVBIUOuzxkT9_qU0Y=%+GUmlDq8eOL z$c6X}%A>$_nx02Sulz0~W}u#z)m>5AcdV4nWqro@#=@b>8kfsSGjScHAn5U1$FxTK zfXOQmJAU1`C!5&1ddVNR<`~DTrf4H6ticNQvp#)YCwZAx`}^s=94F9XRygSU16SNy9__D3WxEv10O%)d+Y;c_-zhnx$b`b zsZky@_sYSAbP7~LKMJz{j4V1{2SM=Y*XA7vLmtNk?crW~hfD=6&q?jY8RL?l4|yr6 zP=B(Az9^$lO?~kOn#hlRWXtCE3NYkzi}&!N@JWS_cIfW+ZYG+o)exO?8{hOEex@YY z=LT7IoE>A#Rox$Yi zyK7Vtn|e6$97ksP8+hqqO{}v_@quwT!)zu2@IrD!0(kAkuxGR@P~_C_9V(MAsG%DT zXJ?9K<$$a)g3tXkO1+8F9GQ(&m*U-U(ay7ng_Qu|y?RQstU&rr_jN;XSgXJBLQ5h9 zDXKAawKGHZvYQ9%At~!rA}+m{EE&>OncJncx8kzXq-<=b9SW*0cX*bxOmP zk25`E%Nn2)%dLxLQg+FP_ctP*l(BQcrKR_xTz~YS%DynK`9pYEi0pn8p?)FuPV& zLSF8@J1nLwsE@C=4~Gc&-E%&$53q;__Gk8A^vX8tDfLirx*YL%h=(06f0s^J#1_vx zU?Hz$-iQsz;T1}ETnV zFTc{Avn4`KZ~~Hu?r17C6i?i#@RU@b>ZCMJ!0W%(xI>E>vNx{Gt&V``9Qua}6M|jk zJ#+}cBnDBNG3$||=4I3?dSb9meAp5Ng{HnWUI5E?JUtHS^b9xQPq_Br68?4UJ|GvX z@O{)zTtyir=meGZD1HkS3DjgdaCZkHpFm;8%;T&xfC*$qZ8X()G4(33LmDfPvx*4A zE4Ics*Ep4lZN(1nlyp*IAXuXGs(y~En(ht;+&5GKW>ofsL?ptd-YOy_#8TlBBTzQh z+~{5v8rd7*5DjT6tNRtQ-$Y@zAxDz$Fz4laEuUQXo-Qo8QW$}F4xw`8m~`Wum}4Yf za&inhaQb@Y@;>nsSGbbdz*cr-roy;Q#C_~LCds$K*HNOAS)5d%50A!HLaFW+_mWHi zwwpR|>pA=iAJrVeNEGOYmYMsSffZ?&u=bUQ)2^5>ixG#mCP6i@?%8V))H9CdqIj}%+@6!Beg;&A;?(cMD1 zZ!$X>X+TXm$qH7#IzN&Clb%X@&}TXdufe*80b`Z?#*M|=%VGGcufy)#`rG-K)@^d< zCn5%X{5?>F`Lhp6IL?Fw+UomJuTyT6^9~A9ojm|HUQ|I^6r845T#CpqL%7c72AJd=0k z0`z!4Ka%HEKg!vU9)i4>QsnT;_@t_i6f;3=EHaTXv?JLfou!v%jT42j;leExRq z4m+T)W+u2#<*>#osAG>nMrMzvMCOon1fv?rbb#}Asx8Sdl^1Y%L#|=XTqbKT6d6Dw zMAKm&sQM;mIbM*2AQeAf#!PTIkUn>1`iu_E4&bEDFW<^J4X*mFKL~^ue=W7mlV|BE{_F8v~ z0mY4Wy7;dFBNZeYB)YxQT`2V%*i+#sVJ#bWQ7fN|**x5bG8%clarp@Z7^0=`qOpg~ zUZnjHQGku)`Lmy#4^$>-bCH2fcSZV56?yub*&d*4|frxm8pIK3-A#v{(MBO@1v4qX^?NruO zpGzZG;4PSjZ0i#z46d}oSsaGj6Xy+m^6PXX{zD%az6YGKpr(6iF4)E$Oc zetfU!j6FXU2k}-l)k#K3VDtO{h+t0v*vWRLIl&JaH#wF8xrP@%(vm} z-KWSyT1lrf$2RyzMryjfpu6PUk9p4Kd^X|n7asWT2Zz?}G%}rWja5gN&R_ZfFOOYF zi7a)pV;+yxet3xEtTvkWhuc(z4YgeF!XHNLscti8;cN{K5TR?0MBHs^;qC5)fyll* zE*E+E$Op@Yjf$)mk95PdKTP{mWIXp=gjmovC!sytc|)%0?_v)$oCgXpeS0p&M8Uk>HVM6l@tjkG(k!ClL1U&Z>{ zCn(0g9m??21NcIr7NwVMr&k0BuhqJ^e_6F8p7r}v38~LkZ>0b%YcdpgQ1U3|x`@US zDM}}^`p*D|a-LMwkWdd>rgP)lgqiF!UlMlCiQO|5D*b%tF-#GoZ!@EmO~(cNgpPPo z0!YdotaP9fou1T;*^VqU&7F%^d~=e==$PL2@GK@TGb!z}pY`|Z#A7_Ge7j~N6A1Ke zJw-E3i)SN7V#sni$c5(4#@@Q@aq_!st|oC}VJZha3fw{4j_!%)XmLuJuz-lr3>4^t zzNQYI+hC!$n3H5wG-S%3>EA~O2*LSu---0dqwny!dCl7avQvQQ`;09dVAWq)E(g%0 zBPykIgl-B6D2{^vVgU@I*ql#7Wuh*T87w}HbH7o%{l|;))FN-n_$8=Ynp6w>*TI}d z4ck(UydU(9SACd~z)EK!3@CE$zeBOY-8LL>KDcURF~u1|YNWM2Vs}d@j+b5spjBXv zqKMa;mK)37oqjp6p<95a8zd(%0*GeC65uF+8eh*pQ2Thm*(7@Uwa7!m5x$OJ->Q5s z_f?p(N*;>f;|pn;U7>MHvDvkEOc;lI`4~)~(vOwSk9FZ`lX`6$Z)lmupbR6@wS}=rwEa(* z)BP4gPQxvRuBXMk9-$SjU#ENAyJ0zXsrlI{CuHH3f9ZslK>zpT)fn6?%K`i{GzT#m zyUlMFilv85qOaa54cf#t$b0miwl;g%4a2D;-cK}=`;y(2dP1s(&(+s2t)Ps!i(7`@hWejm zOIg4qK82!@>b1r{2EHcA53-P(UmYCLE_1vvB!r+4cXN-iiuHe{8g23*@_l%ric3N) zw>~rKzUq*6Z4Fi33zMk1AwRKCeF$@A43{RZ^ z4xi0TC(MaDHykf~z5>^Z%v{BZK~`814#prA&jg2?;<~iXK@T=_yXnUiNNw8OHYKF7(`)9&eVXM+1SNvzK#!#_c5 zl;D)UgvD52CH5&%DSY<6FsL_acO)eG^eccr2?w$s`z@#2XNq;t&Efva zj%cPP6GO{(f8Ih)btdV-cZj3@UZwtQ-i%&xnqU-`_<0mEx^F6sbHGTd;#R`hyapob zemT6=O!BdJ_2GAuQ-D}M%2H*#><3qhDx}-K{*z2WRl3SH_C4R=;ht=JfMP9jY-wN(rKuBowJNs8>rWw!sxvx{ErK(M#c#(yA zJgPD1mDc7lNDXtb4lYcL)?`x#nadRB=~BpCy-bs*F@LlV5Loi*pqPwRKI3a^P83Ym z2zv0|Nlg<0nQTyw+ixp&@kfK?n}UzpBqD@*@XmdSBPlU&u6K3tu7q}tZ^p1UWGItl z8+Sj{L)<4w(wy2#h|7<{SA_l+e>!tzcqQ(6hf0iVfD&iy~ z08OXs3FLo{Ljd$+A0HX`+3k*nb2}8s~de}-Kx1mP8X>Os*5;z;EhDjPeHvb zPl_?rPy$K%>)V5GwH51l3a`fTGM*aegfc809Q`VS`ODNz6EwE&k5g_?)AFvM(FY9w ziz_3+ERGZoR3IGe56D*-lScn1%!Hyk7hfP)dhb42{1#jjv@%Zpoi9AaNh>2WoGM<3q>(uNC?C%$mA7v>9kOsMAC`|jUIL#ZO;Cut_*Fl-ey ztKWSQWyZ-2bmA2V?g!IMrdk3MPtKR^-u)T^Sffs{gCP}zX^{iGBqL5;bpiyx=;A@%xFD^qJNX*}NhNE3${}HY2 z#9tQ@1i<_3HAydbNB!N5)Sb%M%ug`9D_nESyua;upel6U8mqf5ppQFVM*`_X9v1j; zZkSi@C-#I0A5?>Z}=ho4(U^|koOylv;zo)qzgeyP9 zCuh@6jqKC8EPxnyz$rpd=381hHq*qe_Tr9hKL9Z&5qGEn0In(>N&GZyK6yM;t@h z&6Gl-!V+NzPEkB!Atq!BVv%Ry9cN&a`n3RtBs=_e$A^~5$=SULli|xFv#FLi!Os0g zPvD5BI=Y839bbU}1WUgo*Uovt+8Lgs&5B9EQfEUJrX)0AdTAu@_2LP#A#TSv=`jb( zxW^%$>RgQ{a1g9<{UPLVz%+^p#UUYyWGvpzc}XheM5j|e*1ksKv*#Nt@^KyIPg3;8 z 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 De~rmR literal 0 HcmV?d00001 diff --git a/1.19.3/LICENSE b/1.19.3/LICENSE new file mode 100644 index 0000000..f4a1e2d --- /dev/null +++ b/1.19.3/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) HypherionSA and Contributors 2024 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/1.19.3/NeoForge/src/main/resources/craterlib_logo.png b/1.19.3/NeoForge/src/main/resources/craterlib_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..ce0159cc4aa4d823ea354042e531b4522d6dfead GIT binary patch literal 49343 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 z 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 De~rmR literal 0 HcmV?d00001 diff --git a/1.19.3/README.md b/1.19.3/README.md new file mode 100644 index 0000000..1e3cffb --- /dev/null +++ b/1.19.3/README.md @@ -0,0 +1,56 @@ +# CraterLib + +![badge-snapshot](https://maven.firstdarkdev.xyz/api/badge/latest/snapshots/me/hypherionmc/craterlib/CraterLib-common-1.20-pre6?color=40c14a&name=CraterLib-Snapshot) + +*** + +A Library mod and modding api for easier multi-version minecraft and mod loader development + +*** + +### Supported Minecraft Versions + +| Minecraft Version | Support Status | +|-------------------| -------------- | +| < 1.18.2 | ❌ | +| 1.18.2-1.20.2 | ✳️ | +| 1.20.4 | ✳️ | +| 1.21 | ✳️ | +| 1.21 | 🚧 | + +- ❌ - Not Supported; no bug fixes or new features. +- 🚧 - Work in Progress; not ready for release. +- ✳️ - Long Term Support; receives changes through backports only. +- ✅ - In Support; the active version, receiving all bugfixes and features directly. + +*** + +## Library Features + +* Universal Config System (TOML Based) +* Built in Helper Classes for Various minecraft features +* Built in Optifine-Compat utilities +* Various utilities for Blockstates, LANG, Math and Rendering +* Cross Mod-Loader Events - Based on [Acara](https://github.com/Keksuccino/acara) +* Cross Mod-Loader Config Screens (Based on [Cloth Config Lite](https://github.com/shedaniel/cloth-config-lite)) +* Automatic ModMenu and Forge Config screen registration +* Built in Cross Mod-Loader Network system +* Nojang Modding API + +*** + +## Setup Instructions + +There's a **wiki coming soon**, but for now, here's some basic instructions for building the project: + +1. `git clone` the project to a safe spot. +2. Install Java's JDK 17. Make sure you have the development version explicitly: + * Fedora: `sudo dnf install java-17-openjdk-devel` + * Ubuntu: `sudo apt install openjdk-17-jdk` + * macOS: `brew install openjdk@17` +3. Set it accordingly: + * Windows/macOS: Set the `JAVA_HOME` environment variable or use system settings + * Linux: `sudo update-alternatives --config java` +4. Navigate to the CraterLib folder, then run a `gradlew` file depending on your operating system: + * Windows: `.\gradlew.bat build` + * macOS/Linux/BSD: `chmod +x gradlew` and `./gradlew` diff --git a/1.19.3/build.gradle b/1.19.3/build.gradle new file mode 100644 index 0000000..5bdbcfc --- /dev/null +++ b/1.19.3/build.gradle @@ -0,0 +1,110 @@ +plugins { + id 'java' + id 'com.github.johnrengelman.shadow' version '8.1.1' apply false + id "xyz.wagyourtail.unimined" version "1.2.4" apply false + id "com.hypherionmc.modutils.modpublisher" version "2.1.+" + id "com.hypherionmc.modutils.orion" version "1.0.+" + id 'maven-publish' +} + +orion.setup { + multiProject = true + enableMirrorMaven = true + enableReleasesMaven = true + + dopplerToken = System.getenv("DOPPLER_KEY") + + versioning { + var relType = project.properties["releaseType"] ?: "${release_type}" + identifier("${relType}") + } +} + +group = project_group + +subprojects { + apply plugin: "xyz.wagyourtail.unimined" + apply plugin: "java" + apply plugin: 'maven-publish' + apply plugin: 'com.github.johnrengelman.shadow' + apply plugin: 'com.hypherionmc.modutils.modpublisher' + + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + + group = rootProject.group + + repositories { + mavenCentral() + + maven { + name = "Modrinth" + url = "https://api.modrinth.com/maven" + content { + includeGroup "maven.modrinth" + } + } + } + + configurations { + shade + modCompileOnly + implementation.extendsFrom shade + compileOnly.extendsFrom modCompileOnly + } + + dependencies { + // All Projects + shade "me.hypherionmc.moon-config:core:${moon_config}" + shade "me.hypherionmc.moon-config:toml:${moon_config}" + shade "com.hypherionmc:rpcsdk:${rpc_sdk}" + shade "me.hypherionmc.sdlink:mcdiscordformatter-1.19.1:${discord_formatter}" + shade "net.kyori:adventure-api:${adventure}" + shade "net.kyori:adventure-text-serializer-gson:${adventure}" + + compileOnly("org.projectlombok:lombok:${lombok}") + annotationProcessor("org.projectlombok:lombok:${lombok}") + } + + jar { + manifest { + attributes([ + 'Specification-Title' : mod_name, + 'Specification-Vendor' : mod_author, + 'Specification-Version' : project.jar.archiveVersion, + 'Implementation-Title' : project.name, + 'Implementation-Version' : project.jar.archiveVersion, + 'Implementation-Vendor' : mod_author, + 'Implementation-Timestamp': new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"), + 'Timestamp' : System.currentTimeMillis(), + 'Built-On-Java' : "${System.getProperty('java.vm.version')} (${System.getProperty('java.vm.vendor')})", + 'Built-On-Minecraft' : minecraft_version + ]) + } + } + +/** + * =============================================================================== + * = DO NOT EDIT BELOW THIS LINE UNLESS YOU KNOW WHAT YOU ARE DOING = + * =============================================================================== + */ + unimined.minecraft(sourceSets.main, true) { + version minecraft_version + + mappings { + mojmap() + devNamespace "mojmap" + } + } + + tasks.withType(JavaCompile).configureEach { + it.options.encoding = 'UTF-8' + it.options.release = 17 + } + + tasks.withType(GenerateModuleMetadata).configureEach { + enabled = false + } +} + +// TODO MODULE JARS diff --git a/1.19.3/gradle.properties b/1.19.3/gradle.properties new file mode 100644 index 0000000..1c58295 --- /dev/null +++ b/1.19.3/gradle.properties @@ -0,0 +1,42 @@ +#Project +version_major=2 +version_minor=0 +version_patch=0 + +#Mod +mod_author=HypherionSA +mod_id=craterlib +mod_name=CraterLib + +# Shared +minecraft_version=1.19.3 +project_group=com.hypherionmc.craterlib + +# Fabric +fabric_loader=0.15.11 +fabric_api=0.76.1+1.19.3 + +# Forge +forge_version=44.1.0 + +# Dependencies +moon_config=1.0.9 +lombok=1.18.32 +adventure=4.16.0 +rpc_sdk=1.0 +discord_formatter=2.0.0 + +# Mod Dependencies +fabrictailor=2.0.2 +vanish=1.4.0+1.19.3 +mod_menu_version=5.1.0-beta.4 +vanishmod=v1.1.9.3 + +# Publishing +curse_id=867099 +modrinth_id=Nn8Wasaq +release_type=release + +# Gradle +org.gradle.jvmargs=-Xmx3G +org.gradle.daemon=false diff --git a/1.19.3/gradle/wrapper/gradle-wrapper.jar b/1.19.3/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..7454180f2ae8848c63b8b4dea2cb829da983f2fa GIT binary patch literal 59536 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 QuEJmfE|#`Dqx|px0L@K;Y5)KL literal 0 HcmV?d00001 diff --git a/1.19.3/gradle/wrapper/gradle-wrapper.properties b/1.19.3/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..48c0a02 --- /dev/null +++ b/1.19.3/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/1.19.3/gradlew b/1.19.3/gradlew new file mode 100644 index 0000000..b4f908a --- /dev/null +++ b/1.19.3/gradlew @@ -0,0 +1,183 @@ +#!/usr/bin/env bash + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MSYS* | MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +ARGV=("$@") +eval set -- $DEFAULT_JVM_OPTS + +IFS=$' +' read -rd '' -a JAVA_OPTS_ARR <<< "$(echo $JAVA_OPTS | xargs -n1)" +IFS=$' +' read -rd '' -a GRADLE_OPTS_ARR <<< "$(echo $GRADLE_OPTS | xargs -n1)" + +exec "$JAVACMD" "$@" "${JAVA_OPTS_ARR[@]}" "${GRADLE_OPTS_ARR[@]}" "-Dorg.gradle.appname=$APP_BASE_NAME" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "${ARGV[@]}" diff --git a/1.19.3/gradlew.bat b/1.19.3/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/1.19.3/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/1.19.3/settings.gradle b/1.19.3/settings.gradle new file mode 100644 index 0000000..19b263e --- /dev/null +++ b/1.19.3/settings.gradle @@ -0,0 +1,14 @@ +pluginManagement { + repositories { + gradlePluginPortal() + maven { + url "https://mcentral.firstdark.dev/releases" + } + maven { + url "https://maven.firstdark.dev/releases" + } + } +} + +rootProject.name = 'CraterLib-1.19.3' +include("Common", "Fabric", "Forge") diff --git a/1.20.2/.gitattributes b/1.20.2/.gitattributes new file mode 100644 index 0000000..20fc528 --- /dev/null +++ b/1.20.2/.gitattributes @@ -0,0 +1,15 @@ +* text eol=lf +*.bat text eol=crlf +*.patch text eol=lf +*.java text eol=lf +*.gradle text eol=crlf +*.png binary +*.gif binary +*.exe binary +*.dll binary +*.jar binary +*.lzma binary +*.zip binary +*.pyd binary +*.cfg text eol=lf +*.jks binary \ No newline at end of file diff --git a/1.20.2/.gitignore b/1.20.2/.gitignore new file mode 100644 index 0000000..966dad4 --- /dev/null +++ b/1.20.2/.gitignore @@ -0,0 +1,29 @@ +# eclipse +bin +*.launch +.settings +.metadata +.classpath +.project + +# idea +out +*.ipr +*.iws +*.iml +.idea + +# gradle +build +.gradle + +# other +eclipse +run + +artifacts +src/test/** + +workspace +upstream +rejects \ No newline at end of file diff --git a/1.20.2/.jenkins/Jenkinsfile.deploy b/1.20.2/.jenkins/Jenkinsfile.deploy new file mode 100644 index 0000000..a30557e --- /dev/null +++ b/1.20.2/.jenkins/Jenkinsfile.deploy @@ -0,0 +1,55 @@ +def JDK = "17" + +pipeline { + agent { + docker { + image "registry.firstdark.dev/java${JDK}:latest" + alwaysPull true + args '-v gradle-cache:/home/gradle/.gradle' + } + } + + environment { + GRADLE_USER_HOME = '/home/gradle/.gradle' + } + + stages { + stage("Notify Discord") { + steps { + discordSend webhookURL: env.FDD_WH_ADMIN, + title: "Deploy Started: CraterLib 1.20.2 Deploy #${BUILD_NUMBER}", + link: env.BUILD_URL, + result: 'SUCCESS', + description: "Build: [${BUILD_NUMBER}](${env.BUILD_URL})" + } + } + stage("Prepare") { + steps { + sh "chmod +x ./gradlew" + sh "./gradlew clean" + } + } + stage("Publish to Modrinth/Curseforge") { + steps { + sh "./gradlew publishMod -Prelease=true" + } + } + stage("Publish to Maven") { + steps { + sh "./gradlew publish -Prelease=true" + } + } + } + post { + always { + sh "./gradlew --stop" + deleteDir() + + discordSend webhookURL: env.FDD_WH_ADMIN, + title: "CraterLib Port Deploy #${BUILD_NUMBER}", + link: env.BUILD_URL, + result: currentBuild.currentResult, + description: "Build: [${BUILD_NUMBER}](${env.BUILD_URL})\nStatus: ${currentBuild.currentResult}" + } + } +} diff --git a/1.20.2/.jenkins/Jenkinsfile.snapshot b/1.20.2/.jenkins/Jenkinsfile.snapshot new file mode 100644 index 0000000..fe5ddb6 --- /dev/null +++ b/1.20.2/.jenkins/Jenkinsfile.snapshot @@ -0,0 +1,65 @@ +def projectName = "CraterLib"; +def projectIcon = "https://cdn.modrinth.com/data/Nn8Wasaq/a172c634683a11a2e9ae593e56eba7885743bb44.png"; +def JDK = "17"; +def majorMc = "1.20.2"; +def modLoaders = "forge|fabric|quilt"; +def supportedMc = "1.20.2"; +def reltype = "snapshot"; + +pipeline { + agent { + docker { + image "registry.firstdark.dev/java${JDK}:latest" + alwaysPull true + args '-v gradle-cache:/home/gradle/.gradle' + } + } + + environment { + GRADLE_USER_HOME = '/home/gradle/.gradle' + } + + stages { + stage("Notify Discord") { + steps { + discordSend webhookURL: env.SSS_WEBHOOK, + title: "Deploy Started: ${projectName} ${majorMc} Deploy #${BUILD_NUMBER}", + link: env.BUILD_URL, + result: 'SUCCESS', + description: "Build: [${BUILD_NUMBER}](${env.BUILD_URL})" + } + } + + stage("Prepare") { + steps { + sh "chmod +x ./gradlew" + sh "./gradlew build -PreleaseType=port" + } + } + + stage("Publish to Maven") { + steps { + catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') { + sh "./gradlew publish -PreleaseType=${reltype}" + } + } + } + } + + post { + always { + sh "./gradlew --stop" + + fddsnapshotter apiKey: env.PLATFORM_KEY, + projectSlug: "craterlib", + projectName: "${projectName}", + projectIcon: "${projectIcon}", + modLoaders: "${modLoaders}", + minecraftVersions: "${supportedMc}", + failWebhook: env.SSS_WEBHOOK, + publishWebhooks: "${env.SSS_WEBHOOK}|${env.FDD_WH}" + + deleteDir() + } + } +} diff --git a/1.20.2/Common/build.gradle b/1.20.2/Common/build.gradle new file mode 100644 index 0000000..843d62a --- /dev/null +++ b/1.20.2/Common/build.gradle @@ -0,0 +1,68 @@ +archivesBaseName = "${mod_name.replace(" ", "")}-Common-${minecraft_version}" + +dependencies { + +} + +shadowJar { + from sourceSets.main.output + configurations = [project.configurations.shade] + + dependencies { + exclude(dependency('com.google.code.gson:.*')) + + relocate 'me.hypherionmc.moonconfig', 'shadow.hypherionmc.moonconfig' + relocate 'me.hypherionmc.mcdiscordformatter', 'shadow.hypherionmc.mcdiscordformatter' + relocate 'net.kyori', 'shadow.kyori' + } + setArchiveClassifier("dev") +} + +/** + * =============================================================================== + * = DO NOT EDIT BELOW THIS LINE UNLESS YOU KNOW WHAT YOU ARE DOING = + * =============================================================================== + */ + +unimined.minecraft { + fabric { + loader fabric_loader + } + + defaultRemapJar = false +} + +processResources { + def buildProps = project.properties.clone() + + filesMatching(['pack.mcmeta']) { + expand buildProps + } +} + +/** + * Publishing Config + */ +publishing { + publications { + mavenCommon(MavenPublication) { + artifactId project.archivesBaseName + from components.java + + pom.withXml { + Node pomNode = asNode() + pomNode.dependencies.'*'.findAll() { + it.artifactId.text() == 'regutils-joined-fabric' || + it.artifactId.text() == 'core' || + it.artifactId.text() == 'toml' + }.each() { + it.parent().remove(it) + } + } + } + } + + repositories { + maven rootProject.orion.getPublishingMaven() + } +} \ No newline at end of file diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/CraterConstants.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/CraterConstants.java new file mode 100644 index 0000000..93edffc --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/CraterConstants.java @@ -0,0 +1,10 @@ +package com.hypherionmc.craterlib; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CraterConstants { + public static final String MOD_ID = "craterlib"; + public static final String MOD_NAME = "CraterLib"; + public static final Logger LOG = LoggerFactory.getLogger(MOD_NAME); +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/commands/CraterCommand.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/commands/CraterCommand.java new file mode 100644 index 0000000..cc4969e --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/commands/CraterCommand.java @@ -0,0 +1,53 @@ +package com.hypherionmc.craterlib.api.commands; + +import com.hypherionmc.craterlib.nojang.authlib.BridgedGameProfile; +import com.hypherionmc.craterlib.nojang.commands.BridgedCommandSourceStack; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.TriConsumer; +import com.mojang.brigadier.arguments.ArgumentType; +import lombok.Getter; +import net.minecraft.commands.arguments.GameProfileArgument; +import org.apache.commons.lang3.tuple.Pair; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.function.Consumer; + +@Getter +public class CraterCommand { + + private final HashMap, TriConsumer>> arguments = new LinkedHashMap<>(); + private Consumer executor; + + private final String commandName; + private int permissionLevel = 4; + + CraterCommand(String commandName) { + this.commandName = commandName; + } + + public static CraterCommand literal(String commandName) { + return new CraterCommand(commandName); + } + + public CraterCommand requiresPermission(int perm) { + this.permissionLevel = perm; + return this; + } + + public CraterCommand withGameProfileArgument(String key, TriConsumer, BridgedCommandSourceStack> executor) { + arguments.put(key, Pair.of(GameProfileArgument.gameProfile(), executor)); + return this; + } + + public CraterCommand executes(Consumer ctx) { + executor = ctx; + return this; + } + + public boolean hasArguments() { + return !arguments.isEmpty(); + } + +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/CraterClientTickEvent.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/CraterClientTickEvent.java new file mode 100644 index 0000000..c68c596 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/CraterClientTickEvent.java @@ -0,0 +1,14 @@ +package com.hypherionmc.craterlib.api.events.client; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedClientLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public class CraterClientTickEvent extends CraterEvent { + + private final BridgedClientLevel level; + +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/CraterSinglePlayerEvent.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/CraterSinglePlayerEvent.java new file mode 100644 index 0000000..ab671f0 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/CraterSinglePlayerEvent.java @@ -0,0 +1,21 @@ +package com.hypherionmc.craterlib.api.events.client; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public class CraterSinglePlayerEvent extends CraterEvent { + + private final BridgedPlayer player; + + public static class PlayerLogin extends CraterSinglePlayerEvent { + + public PlayerLogin(BridgedPlayer player) { + super(player); + } + + } +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/LateInitEvent.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/LateInitEvent.java new file mode 100644 index 0000000..33c06fb --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/LateInitEvent.java @@ -0,0 +1,16 @@ +package com.hypherionmc.craterlib.api.events.client; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.client.BridgedMinecraft; +import com.hypherionmc.craterlib.nojang.client.BridgedOptions; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public class LateInitEvent extends CraterEvent { + + private final BridgedMinecraft minecraft; + private final BridgedOptions options; + +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/PlayerJoinRealmEvent.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/PlayerJoinRealmEvent.java new file mode 100644 index 0000000..1ef95de --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/PlayerJoinRealmEvent.java @@ -0,0 +1,14 @@ +package com.hypherionmc.craterlib.api.events.client; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.realmsclient.dto.BridgedRealmsServer; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public class PlayerJoinRealmEvent extends CraterEvent { + + private final BridgedRealmsServer server; + +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/ScreenEvent.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/ScreenEvent.java new file mode 100644 index 0000000..034ca4a --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/ScreenEvent.java @@ -0,0 +1,27 @@ +package com.hypherionmc.craterlib.api.events.client; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.client.gui.BridgedScreen; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; + +@Getter +@RequiredArgsConstructor +public class ScreenEvent extends CraterEvent { + + private final BridgedScreen screen; + + @Getter + public static class Opening extends ScreenEvent { + + private final BridgedScreen currentScreen; + @Setter private BridgedScreen newScreen; + + public Opening(BridgedScreen currentScreen, BridgedScreen newScreen) { + super(newScreen); + this.currentScreen = currentScreen; + this.newScreen = newScreen; + } + } +} \ No newline at end of file diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/common/CraterPlayerDeathEvent.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/common/CraterPlayerDeathEvent.java new file mode 100644 index 0000000..02cd63c --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/common/CraterPlayerDeathEvent.java @@ -0,0 +1,21 @@ +package com.hypherionmc.craterlib.api.events.common; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.ChatUtils; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; +import net.minecraft.world.damagesource.DamageSource; + +@RequiredArgsConstructor +@Getter +public class CraterPlayerDeathEvent extends CraterEvent { + + private final BridgedPlayer player; + private final DamageSource damageSource; + + public Component getDeathMessage() { + return ChatUtils.mojangToAdventure(damageSource.getLocalizedDeathMessage(player.toMojang())); + } +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterAdvancementEvent.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterAdvancementEvent.java new file mode 100644 index 0000000..5cd659d --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterAdvancementEvent.java @@ -0,0 +1,34 @@ +package com.hypherionmc.craterlib.api.events.server; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.advancements.BridgedAdvancement; +import com.hypherionmc.craterlib.nojang.advancements.BridgedDisplayInfo; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import lombok.Getter; +import net.kyori.adventure.text.Component; + +import java.util.Optional; + +@Getter +public class CraterAdvancementEvent extends CraterEvent { + + private final BridgedAdvancement advancement; + private final BridgedPlayer player; + private final Component title; + private final Component description; + + public CraterAdvancementEvent(BridgedPlayer player, BridgedAdvancement advancement) { + this.advancement = advancement; + this.player = player; + + Optional displayInfo = advancement.displayInfo(); + + if (displayInfo.isPresent()) { + this.title = displayInfo.get().displayName(); + this.description = displayInfo.get().description(); + } else { + this.title = Component.text("Unknown"); + this.description = Component.text("Unknown"); + } + } +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterCommandEvent.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterCommandEvent.java new file mode 100644 index 0000000..364cc22 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterCommandEvent.java @@ -0,0 +1,58 @@ +package com.hypherionmc.craterlib.api.events.server; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.ChatUtils; +import com.mojang.brigadier.ParseResults; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.context.StringRange; +import lombok.Getter; +import lombok.Setter; +import net.kyori.adventure.text.Component; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.arguments.ComponentArgument; +import net.minecraft.world.entity.player.Player; +import org.jetbrains.annotations.Nullable; + +@Getter +public class CraterCommandEvent extends CraterEvent { + + private final ParseResults parseResults; + @Setter private Throwable exception; + private final String command; + + private CraterCommandEvent(ParseResults parseResults, String command) { + this.parseResults = parseResults; + this.command = command; + } + + public static CraterCommandEvent of(ParseResults stack, String command) { + return new CraterCommandEvent(stack, command); + } + + public String getCommandString() { + return parseResults.getReader().getString(); + } + + @Nullable + public BridgedPlayer getPlayer() { + try { + Player p = parseResults.getContext().getLastChild().getSource().getPlayer(); + + if (p != null) + return BridgedPlayer.of(p); + } catch (Exception ignored) {} + + return null; + } + + public String getTarget() { + CommandContext context = parseResults.getContext().build(parseResults.getReader().getString()); + StringRange selector_range = parseResults.getContext().getArguments().get("targets").getRange(); + return context.getInput().substring(selector_range.getStart(), selector_range.getEnd()); + } + + public Component getMessage() { + return ChatUtils.mojangToAdventure(ComponentArgument.getComponent(parseResults.getContext().build(parseResults.getReader().getString()), "message")); + } +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterPlayerEvent.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterPlayerEvent.java new file mode 100644 index 0000000..7eb3701 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterPlayerEvent.java @@ -0,0 +1,30 @@ +package com.hypherionmc.craterlib.api.events.server; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public class CraterPlayerEvent extends CraterEvent { + + private final BridgedPlayer player; + + public static class PlayerLoggedIn extends CraterPlayerEvent { + + public PlayerLoggedIn(BridgedPlayer player) { + super(player); + } + + } + + public static class PlayerLoggedOut extends CraterPlayerEvent { + + public PlayerLoggedOut(BridgedPlayer player) { + super(player); + } + + } + +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterRegisterCommandEvent.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterRegisterCommandEvent.java new file mode 100644 index 0000000..269065a --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterRegisterCommandEvent.java @@ -0,0 +1,15 @@ +package com.hypherionmc.craterlib.api.events.server; + +import com.hypherionmc.craterlib.api.commands.CraterCommand; +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.commands.CommandsRegistry; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +public class CraterRegisterCommandEvent extends CraterEvent { + + public void registerCommand(CraterCommand cmd) { + CommandsRegistry.INSTANCE.registerCommand(cmd); + } + +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterServerChatEvent.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterServerChatEvent.java new file mode 100644 index 0000000..b25170a --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterServerChatEvent.java @@ -0,0 +1,25 @@ +package com.hypherionmc.craterlib.api.events.server; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.core.event.annot.Cancellable; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import lombok.Getter; +import lombok.Setter; +import net.kyori.adventure.text.Component; + +@Cancellable +@Getter +public class CraterServerChatEvent extends CraterEvent { + + public final String message, username; + public final BridgedPlayer player; + @Setter private Component component; + + public CraterServerChatEvent(BridgedPlayer player, String message, Component component) { + this.message = message; + this.player = player; + this.username = player.getGameProfile().getName(); + this.component = component; + } + +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterServerLifecycleEvent.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterServerLifecycleEvent.java new file mode 100644 index 0000000..3f87d04 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterServerLifecycleEvent.java @@ -0,0 +1,34 @@ +package com.hypherionmc.craterlib.api.events.server; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +public class CraterServerLifecycleEvent extends CraterEvent { + + @RequiredArgsConstructor + @Getter + public static class Starting extends CraterServerLifecycleEvent { + private final BridgedMinecraftServer server; + } + + @RequiredArgsConstructor + @Getter + public static class Started extends CraterServerLifecycleEvent { + private final BridgedMinecraftServer server; + } + + @RequiredArgsConstructor + @Getter + public static class Stopping extends CraterServerLifecycleEvent { + private final BridgedMinecraftServer server; + } + + @RequiredArgsConstructor + @Getter + public static class Stopped extends CraterServerLifecycleEvent { + private final BridgedMinecraftServer server; + } + +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/MessageBroadcastEvent.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/MessageBroadcastEvent.java new file mode 100644 index 0000000..6b11404 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/MessageBroadcastEvent.java @@ -0,0 +1,20 @@ +package com.hypherionmc.craterlib.api.events.server; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; + +import java.util.function.Function; + +@RequiredArgsConstructor +@Getter +public class MessageBroadcastEvent extends CraterEvent { + + private final Component component; + private final Function function; + private final boolean bl; + private final String threadName; + +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/PlayerPreLoginEvent.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/PlayerPreLoginEvent.java new file mode 100644 index 0000000..eaabffd --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/PlayerPreLoginEvent.java @@ -0,0 +1,20 @@ +package com.hypherionmc.craterlib.api.events.server; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.authlib.BridgedGameProfile; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import net.kyori.adventure.text.Component; + +import java.net.SocketAddress; + +@RequiredArgsConstructor +@Getter +public class PlayerPreLoginEvent extends CraterEvent { + + private final SocketAddress address; + private final BridgedGameProfile gameProfile; + @Setter private Component message; + +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/networking/CraterNetworkHandler.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/networking/CraterNetworkHandler.java new file mode 100644 index 0000000..6b57e96 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/api/networking/CraterNetworkHandler.java @@ -0,0 +1,28 @@ +package com.hypherionmc.craterlib.api.networking; + +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; + +import java.util.List; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public interface CraterNetworkHandler { + + void sendToServer(T packet); + + void sendToServer(T packet, boolean ignoreCheck); + + void sendToClient(T packet, BridgedPlayer player); + + default void sendToClients(T packet, List players) { + for (BridgedPlayer player : players) { + sendToClient(packet, player); + } + } + + default void sendToAllClients(T packet, BridgedMinecraftServer server) { + sendToClients(packet, server.getPlayers()); + } +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/CraterConfigScreen.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/CraterConfigScreen.java new file mode 100644 index 0000000..81a9ed7 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/CraterConfigScreen.java @@ -0,0 +1,393 @@ +package com.hypherionmc.craterlib.client.gui.config; + +import com.hypherionmc.craterlib.CraterConstants; +import com.hypherionmc.craterlib.client.gui.config.widgets.*; +import com.hypherionmc.craterlib.core.config.ModuleConfig; +import com.hypherionmc.craterlib.core.config.annotations.HideFromScreen; +import com.hypherionmc.craterlib.core.config.annotations.SubConfig; +import com.hypherionmc.craterlib.core.config.annotations.Tooltip; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.*; +import me.hypherionmc.moonconfig.core.conversion.SpecComment; +import net.minecraft.ChatFormatting; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.ConfirmScreen; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Mth; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.joml.Matrix4f; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * @author HypherionSA + */ +public class CraterConfigScreen extends Screen { + public static final float SCROLLBAR_BOTTOM_COLOR = .5f; + public static final float SCROLLBAR_TOP_COLOR = .67f; + private static final int TOP = 26; + private static final int BOTTOM = 24; + private final Screen parent; + private final List> options = new ArrayList<>(); + private final ModuleConfig config; + public double scrollerAmount; + private boolean dragging; + + public CraterConfigScreen(ModuleConfig config, Screen parent, Object subConfig) { + super(Component.translatable("cl." + config.getClass().getSimpleName().toLowerCase() + ".title")); + this.parent = parent; + this.config = config; + if (subConfig != null) { + setupScreenFromConfig(subConfig, subConfig.getClass()); + } else { + setupScreenFromConfig(config, config.getClass()); + } + } + + public CraterConfigScreen(ModuleConfig config, Screen parent) { + this(config, parent, null); + } + + private static Component toText(Enum val) { + return Component.translatable(val.toString()); + } + + private static Component toText(Boolean bool) { + return Component.translatable(bool.toString()); + } + + private void setupScreenFromConfig(Object object, Class clazz) { + while (clazz != Object.class) { + for (Field field : clazz.getDeclaredFields()) { + final int fieldModifiers = field.getModifiers(); + if (object == null || Modifier.isStatic(fieldModifiers) || Modifier.isTransient(fieldModifiers)) { + continue; + } + + try { + if (!field.isAccessible()) { + field.setAccessible(true); + } + if (field.isAnnotationPresent(HideFromScreen.class)) + return; + Object val = field.get(object); + + /* Lang Stuff */ + String baseLangKey = "cl." + clazz.getSimpleName().toLowerCase() + "." + field.getName().toLowerCase(); + String[] tooltipLang = field.isAnnotationPresent(SpecComment.class) ? new String[]{field.getAnnotation(SpecComment.class).value()} : new String[0]; + if (field.isAnnotationPresent(Tooltip.class)) { + tooltipLang = field.getAnnotation(Tooltip.class).value(); + } + + add(Component.translatable(baseLangKey), + val, + () -> val, + (ret) -> { + try { + field.set(object, ret); + config.saveConfig(config); + } catch (IllegalAccessException e) { + CraterConstants.LOG.error("Failed to update value for field {} in config {}", field.getName(), config.getConfigName(), e); + } + }, + field.isAnnotationPresent(SubConfig.class), + tooltipLang + ); + } catch (IllegalAccessException e) { + CraterConstants.LOG.error("Failed to access value for field {} in config {}", field.getName(), config.getConfigName(), e); + } + } + clazz = clazz.getSuperclass(); + } + } + + public void add(Component text, T value, @Nullable Supplier defaultValue, Consumer savingConsumer, boolean isSubConfig, String... langKeys) { + Option option = (Option) createOption(value, isSubConfig); + option.text = text; + option.defaultValue = defaultValue; + option.savingConsumer = savingConsumer; + option.originalValue = value; + option.value = value; + option.setLangKeys(List.of(langKeys)); + options.add(option); + option.onAdd(); + } + + private Option createOption(T value, boolean isSubConfig) { + if (value instanceof Enum) { + Object[] objects = value.getClass().getEnumConstants(); + return new ToggleButton>((List) Arrays.asList(objects), CraterConfigScreen::toText); + } + if (value instanceof Boolean) { + return new ToggleButton<>(Arrays.asList(Boolean.TRUE, Boolean.FALSE), CraterConfigScreen::toText); + } + if (value instanceof String) { + return new TextConfigOption<>(Function.identity(), Function.identity()); + } + if (value instanceof Integer) { + return new TextConfigOption<>(Objects::toString, Integer::valueOf); + } + if (value instanceof Long) { + return new TextConfigOption<>(Objects::toString, Long::valueOf); + } + if (value instanceof Double) { + return new TextConfigOption<>(Objects::toString, Double::valueOf); + } + if (value instanceof Float) { + return new TextConfigOption<>(Objects::toString, Float::valueOf); + } + if (value instanceof BigInteger) { + return new TextConfigOption<>(Objects::toString, BigInteger::new); + } + if (value instanceof BigDecimal) { + return new TextConfigOption<>(Objects::toString, BigDecimal::new); + } + if (value instanceof ResourceLocation) { + return new TextConfigOption<>(Objects::toString, ResourceLocation::new); + } + if (isSubConfig) { + return new SubConfigWidget<>(config, this, value); + } + throw new IllegalArgumentException(String.valueOf(value)); + } + + @Override + protected void init() { + super.init(); + ((List) children()).addAll(options); + + int buttonWidths = Math.min(200, (width - 50 - 12) / 3); + addRenderableWidget(new InternalConfigButton(this, width / 2 - buttonWidths - 3, height - 22, buttonWidths, 20, Component.empty(), true)); + addRenderableWidget(new InternalConfigButton(this, width / 2 + 3, height - 22, buttonWidths, 20, Component.empty(), false)); + } + + @Override + public void render(@NotNull GuiGraphics matrices, int mouseX, int mouseY, float delta) { + overlayBackground(matrices.pose(), TOP, height - BOTTOM, 32); + renderScrollBar(); + + matrices.pose().pushPose(); + matrices.pose().translate(0, 0, 500.0); + overlayBackground(matrices.pose(), 0, TOP, 64); + overlayBackground(matrices.pose(), height - BOTTOM, height, 64); + renderShadow(matrices.pose()); + matrices.drawCenteredString(font, getTitle(), width / 2, 9, 0xFFFFFF); + matrices.pose().popPose(); + + super.render(matrices, mouseX, mouseY, delta); + + int y = (int) (TOP + 4 - Math.round(scrollerAmount)); + for (Option option : options) { + int height1 = option.height(); + option.render(minecraft, font, 40, y, width - 80, height1, matrices, mouseX, mouseY, delta); + renderConfigTooltip(matrices, font, mouseX, mouseY, 40, y, font.width(option.text), height1, option.text.getString(), option.getLangKeys().toArray(new String[0])); + y += height1; + } + } + + private void renderScrollBar() { + int listHeight = height - BOTTOM - TOP; + int totalHeight = totalHeight(); + if (totalHeight > listHeight) { + int maxScroll = Math.max(0, totalHeight - listHeight); + int height = listHeight * listHeight / totalHeight; + height = Mth.clamp(height, 32, listHeight); + height = Math.max(10, height); + int minY = Math.min(Math.max((int) scrollerAmount * (listHeight - height) / maxScroll + TOP, TOP), this.height - BOTTOM - height); + + int scrollbarPositionMaxX = width; + int scrollbarPositionMinX = scrollbarPositionMaxX - 6; + + int maxY = this.height - BOTTOM; + //RenderSystem.disableTexture(); + Tesselator tesselator = Tesselator.getInstance(); + BufferBuilder buffer = tesselator.getBuilder(); + RenderSystem.setShader(GameRenderer::getPositionColorShader); + buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR); + + buffer.vertex(scrollbarPositionMinX, maxY, 0.0D).color(0, 0, 0, 255).endVertex(); + buffer.vertex(scrollbarPositionMaxX, maxY, 0.0D).color(0, 0, 0, 255).endVertex(); + buffer.vertex(scrollbarPositionMaxX, TOP, 0.0D).color(0, 0, 0, 255).endVertex(); + buffer.vertex(scrollbarPositionMinX, TOP, 0.0D).color(0, 0, 0, 255).endVertex(); + + buffer.vertex(scrollbarPositionMinX, minY + height, 0.0D).color(SCROLLBAR_BOTTOM_COLOR, SCROLLBAR_BOTTOM_COLOR, SCROLLBAR_BOTTOM_COLOR, 1).endVertex(); + buffer.vertex(scrollbarPositionMaxX, minY + height, 0.0D).color(SCROLLBAR_BOTTOM_COLOR, SCROLLBAR_BOTTOM_COLOR, SCROLLBAR_BOTTOM_COLOR, 1).endVertex(); + buffer.vertex(scrollbarPositionMaxX, minY, 0.0D).color(SCROLLBAR_BOTTOM_COLOR, SCROLLBAR_BOTTOM_COLOR, SCROLLBAR_BOTTOM_COLOR, 1).endVertex(); + buffer.vertex(scrollbarPositionMinX, minY, 0.0D).color(SCROLLBAR_BOTTOM_COLOR, SCROLLBAR_BOTTOM_COLOR, SCROLLBAR_BOTTOM_COLOR, 1).endVertex(); + buffer.vertex(scrollbarPositionMinX, (minY + height - 1), 0.0D).color(SCROLLBAR_TOP_COLOR, SCROLLBAR_TOP_COLOR, SCROLLBAR_TOP_COLOR, 1).endVertex(); + buffer.vertex((scrollbarPositionMaxX - 1), (minY + height - 1), 0.0D).color(SCROLLBAR_TOP_COLOR, SCROLLBAR_TOP_COLOR, SCROLLBAR_TOP_COLOR, 1).endVertex(); + buffer.vertex((scrollbarPositionMaxX - 1), minY, 0.0D).color(SCROLLBAR_TOP_COLOR, SCROLLBAR_TOP_COLOR, SCROLLBAR_TOP_COLOR, 1).endVertex(); + buffer.vertex(scrollbarPositionMinX, minY, 0.0D).color(SCROLLBAR_TOP_COLOR, SCROLLBAR_TOP_COLOR, SCROLLBAR_TOP_COLOR, 1).endVertex(); + tesselator.end(); + RenderSystem.disableBlend(); + //RenderSystem.enableTexture(); + } + } + + private void renderShadow(PoseStack matrices) { + Tesselator tesselator = Tesselator.getInstance(); + BufferBuilder buffer = tesselator.getBuilder(); + RenderSystem.enableBlend(); + RenderSystem.blendFuncSeparate(770, 771, 0, 1); + //RenderSystem.disableTexture(); + RenderSystem.setShader(GameRenderer::getPositionTexColorShader); + Matrix4f matrix = matrices.last().pose(); + buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX_COLOR); + buffer.vertex(matrix, 0, TOP + 4, 0.0F).uv(0, 1).color(0, 0, 0, 0).endVertex(); + buffer.vertex(matrix, width, TOP + 4, 0.0F).uv(1, 1).color(0, 0, 0, 0).endVertex(); + buffer.vertex(matrix, width, TOP, 0.0F).uv(1, 0).color(0, 0, 0, 185).endVertex(); + buffer.vertex(matrix, 0, TOP, 0.0F).uv(0, 0).color(0, 0, 0, 185).endVertex(); + buffer.vertex(matrix, 0, height - BOTTOM, 0.0F).uv(0, 1).color(0, 0, 0, 185).endVertex(); + buffer.vertex(matrix, width, height - BOTTOM, 0.0F).uv(1, 1).color(0, 0, 0, 185).endVertex(); + buffer.vertex(matrix, width, height - BOTTOM - 4, 0.0F).uv(1, 0).color(0, 0, 0, 0).endVertex(); + buffer.vertex(matrix, 0, height - BOTTOM - 4, 0.0F).uv(0, 0).color(0, 0, 0, 0).endVertex(); + tesselator.end(); + //RenderSystem.enableTexture(); + RenderSystem.disableBlend(); + } + + protected void overlayBackground(PoseStack matrices, int h1, int h2, int color) { + overlayBackground(matrices.last().pose(), 0, h1, width, h2, color, color, color, 255, 255); + } + + protected void overlayBackground(Matrix4f matrix, int minX, int minY, int maxX, int maxY, int red, int green, int blue, int startAlpha, int endAlpha) { + Tesselator tesselator = Tesselator.getInstance(); + BufferBuilder buffer = tesselator.getBuilder(); + RenderSystem.setShader(GameRenderer::getPositionTexColorShader); + RenderSystem.setShaderTexture(0, Screen.BACKGROUND_LOCATION); + RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f); + buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX_COLOR); + buffer.vertex(matrix, minX, maxY, 0.0F).uv(minX / 32.0F, maxY / 32.0F).color(red, green, blue, endAlpha).endVertex(); + buffer.vertex(matrix, maxX, maxY, 0.0F).uv(maxX / 32.0F, maxY / 32.0F).color(red, green, blue, endAlpha).endVertex(); + buffer.vertex(matrix, maxX, minY, 0.0F).uv(maxX / 32.0F, minY / 32.0F).color(red, green, blue, startAlpha).endVertex(); + buffer.vertex(matrix, minX, minY, 0.0F).uv(minX / 32.0F, minY / 32.0F).color(red, green, blue, startAlpha).endVertex(); + tesselator.end(); + } + + public int scrollHeight() { + int totalHeight = totalHeight(); + int listHeight = height - BOTTOM - TOP; + if (totalHeight <= listHeight) { + return 0; + } + return totalHeight - listHeight; + } + + public int totalHeight() { + int i = 8; + for (Option option : options) { + i += option.height(); + } + return i; + } + + public boolean hasErrors() { + for (Option option : options) { + if (option.hasErrors) { + return true; + } + } + return false; + } + + public boolean isEdited() { + for (Option option : options) { + if (option.isEdited()) { + return true; + } + } + return false; + } + + public void save() { + for (Option option : options) { + option.save(); + option.originalValue = option.value; + } + } + + @Override + public void onClose() { + if (isEdited()) { + minecraft.setScreen(new ConfirmScreen(this::acceptConfirm, Component.translatable("t.clc.quit_config"), + Component.translatable("t.clc.quit_config_sure"), + Component.translatable("t.clc.quit_discard"), + Component.translatable("gui.cancel"))); + } else { + minecraft.setScreen(parent); + } + } + + @Override + public boolean mouseScrolled(double d, double e, double f, double g) { + if (e >= TOP && e <= height - BOTTOM) { + scrollerAmount = Mth.clamp(scrollerAmount - f * 16.0D, 0, scrollHeight()); + return true; + } + return super.mouseScrolled(d, e, f, g); + } + + @Override + public boolean mouseClicked(double d, double e, int i) { + this.dragging = i == 0 && d >= width - 6 && d < width; + return super.mouseClicked(d, e, i) || dragging; + } + + @Override + public boolean mouseDragged(double d, double e, int i, double f, double g) { + if (super.mouseDragged(d, e, i, f, g)) { + return true; + } + if (i != 0 || !this.dragging) { + return false; + } + if (e < TOP) { + scrollerAmount = 0; + } else if (e > height - BOTTOM) { + scrollerAmount = scrollHeight(); + } else { + double h = Math.max(1, this.scrollHeight()); + int j = height - BOTTOM - TOP; + int k = Mth.clamp((int) ((float) (j * j) / (float) this.scrollHeight()), 32, j - 8); + double l = Math.max(1.0, h / (double) (j - k)); + scrollerAmount = Mth.clamp(scrollerAmount + g * l, 0, scrollHeight()); + } + return true; + } + + private void acceptConfirm(boolean t) { + if (!t) { + minecraft.setScreen(this); + } else { + minecraft.setScreen(parent); + } + } + + private void renderConfigTooltip(GuiGraphics stack, Font font, int mouseX, int mouseY, int startX, int startY, int sizeX, int sizeY, String title, String... description) { + if (mouseX > startX && mouseX < startX + sizeX) { + if (mouseY > startY && mouseY < startY + sizeY) { + List list = new ArrayList<>(); + list.add(Component.translatable(ChatFormatting.BOLD + "" + ChatFormatting.YELLOW + title)); + for (String desc : description) { + list.add(Component.translatable(desc)); + } + stack.renderComponentTooltip(font, list, mouseX, mouseY); + } + } + } +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/AbstractConfigWidget.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/AbstractConfigWidget.java new file mode 100644 index 0000000..ed34f6b --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/AbstractConfigWidget.java @@ -0,0 +1,27 @@ +package com.hypherionmc.craterlib.client.gui.config.widgets; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.client.gui.components.EditBox; + +/** + * Copied from Cloth Config Lite + * ... + */ +public class AbstractConfigWidget extends BaseWidget { + + public static final int buttonWidth = 200; + public static final int buttonHeight = 20; + public W widget; + + @Override + public void render(Minecraft minecraft, Font font, int x, int y, int width, int height, GuiGraphics matrices, int mouseX, int mouseY, float delta) { + super.render(minecraft, font, x, y, width, height, matrices, mouseX, mouseY, delta); + int i = (widget instanceof EditBox ? 1 : 0); + widget.setX(x + width - 200 - resetButtonOffset + i); + widget.setY(y + i + 1); + widget.render(matrices, mouseX, mouseY, delta); + } +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/BaseWidget.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/BaseWidget.java new file mode 100644 index 0000000..0583e61 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/BaseWidget.java @@ -0,0 +1,61 @@ +package com.hypherionmc.craterlib.client.gui.config.widgets; + +import net.minecraft.ChatFormatting; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.Button; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.network.chat.TextColor; + +/** + * Copied from Cloth Config Lite + * ... + */ +public class BaseWidget extends Option { + + public static final int resetButtonOffset = 48; + private final Button resetButton = addChild(Button.builder(Component.literal("Reset"), this::onResetPressed).size(46, 20).build()); + private boolean hideReset = false; + + private boolean isSubConfig = false; + + private void onResetPressed(Button button) { + value = defaultValue.get(); + reset(); + } + + public void hideReset() { + this.hideReset = true; + } + + public boolean isSubConfig() { + return isSubConfig; + } + + public void setSubConfig(boolean subConfig) { + isSubConfig = subConfig; + } + + @Override + public void render(Minecraft minecraft, Font font, int x, int y, int width, int height, GuiGraphics matrices, int mouseX, int mouseY, float delta) { + MutableComponent text = Component.literal(this.text.getString()); + boolean edited = isEdited() || hasErrors; + if (edited) { + text.withStyle(ChatFormatting.ITALIC); + if (hasErrors) { + text.withStyle(style -> style.withColor(TextColor.fromRgb(16733525))); + } + } else { + text.withStyle(ChatFormatting.GRAY); + } + matrices.drawString(font, text, x, y + font.lineHeight - 2, 0xFFFFFF); + resetButton.setX(x + width - 46); + resetButton.setY(y + 1); + resetButton.active = isNotDefault(); + if (!hideReset) { + resetButton.render(matrices, mouseX, mouseY, delta); + } + } +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/InternalConfigButton.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/InternalConfigButton.java new file mode 100644 index 0000000..2478c7d --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/InternalConfigButton.java @@ -0,0 +1,51 @@ +package com.hypherionmc.craterlib.client.gui.config.widgets; + +import com.hypherionmc.craterlib.client.gui.config.CraterConfigScreen; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.AbstractButton; +import net.minecraft.client.gui.narration.NarratedElementType; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.network.chat.Component; + +/** + * @author HypherionSA + */ +public class InternalConfigButton extends AbstractButton { + + CraterConfigScreen screen; + boolean cancel; + + public InternalConfigButton(CraterConfigScreen screen, int i, int j, int k, int l, Component component, boolean cancel) { + super(i, j, k, l, component); + this.screen = screen; + this.cancel = cancel; + } + + @Override + protected void renderWidget(GuiGraphics arg, int i, int j, float f) { + if (cancel) { + setMessage(Component.translatable(screen.isEdited() ? "t.clc.cancel_discard" : "gui.cancel")); + } else { + boolean hasErrors = screen.hasErrors(); + active = screen.isEdited() && !hasErrors; + setMessage(Component.translatable(hasErrors ? "t.clc.error" : "t.clc.save")); + } + super.renderWidget(arg, i, j, f); + } + + @Override + protected void updateWidgetNarration(NarrationElementOutput narrationElementOutput) { + narrationElementOutput.add(NarratedElementType.USAGE, getMessage()); + } + + @Override + public void onPress() { + if (cancel) { + screen.onClose(); + } else { + screen.save(); + } + } + + +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/Option.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/Option.java new file mode 100644 index 0000000..f49ca68 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/Option.java @@ -0,0 +1,71 @@ +package com.hypherionmc.craterlib.client.gui.config.widgets; + +import lombok.Getter; +import lombok.Setter; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.events.AbstractContainerEventHandler; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Supplier; + +/** + * Copied from Cloth Config Lite + * ... + */ +public abstract class Option extends AbstractContainerEventHandler { + + public Component text; + @Nullable + public Supplier defaultValue; + public Consumer savingConsumer; + public T originalValue; + public T value; + public boolean hasErrors; + public List children = new ArrayList<>(); + @Setter + @Getter + private List langKeys = new ArrayList<>(); + + public abstract void render(Minecraft minecraft, Font font, int x, int y, int width, int height, GuiGraphics matrices, int mouseX, int mouseY, float delta); + + public int height() { + return 22; + } + + @Override + public List children() { + return children; + } + + protected R addChild(R listener) { + ((List) children).add(listener); + return listener; + } + + public void onAdd() { + } + + protected void reset() { + onAdd(); + } + + public boolean isEdited() { + return !Objects.equals(originalValue, value); + } + + protected boolean isNotDefault() { + return defaultValue != null && !Objects.equals(defaultValue.get(), value); + } + + public void save() { + savingConsumer.accept(value); + } +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/SubConfigWidget.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/SubConfigWidget.java new file mode 100644 index 0000000..9426ccf --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/SubConfigWidget.java @@ -0,0 +1,40 @@ +package com.hypherionmc.craterlib.client.gui.config.widgets; + +import com.hypherionmc.craterlib.client.gui.config.CraterConfigScreen; +import com.hypherionmc.craterlib.core.config.ModuleConfig; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; + +/** + * @author HypherionSA + */ +public class SubConfigWidget extends AbstractConfigWidget { + + private final Object subConfig; + private final ModuleConfig config; + private final Screen screen; + + public SubConfigWidget(ModuleConfig config, Screen screen, Object subConfig) { + this.config = config; + this.subConfig = subConfig; + this.screen = screen; + + this.widget = addChild(Button.builder(Component.translatable("t.clc.opensubconfig"), this::openSubConfig).size(200, buttonHeight).build()); + } + + @Override + public void render(Minecraft minecraft, Font font, int x, int y, int width, int height, GuiGraphics matrices, int mouseX, int mouseY, float delta) { + this.text = Component.literal(subConfig.getClass().getSimpleName().toLowerCase()); + this.hideReset(); + super.render(minecraft, font, x, y, width, height, matrices, mouseX, mouseY, delta); + } + + private void openSubConfig(Button button) { + Minecraft.getInstance().setScreen(new CraterConfigScreen(config, screen, subConfig)); + } + +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/TextConfigOption.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/TextConfigOption.java new file mode 100644 index 0000000..cc05cec --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/TextConfigOption.java @@ -0,0 +1,45 @@ +package com.hypherionmc.craterlib.client.gui.config.widgets; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; + +import java.util.function.Function; + +/** + * Copied from Cloth Config Lite + * ... + */ +public class TextConfigOption extends AbstractConfigWidget { + + private final Function toString; + private final Function fromString; + + public TextConfigOption(Function toString, Function fromString) { + this.toString = toString; + this.fromString = fromString; + this.widget = addChild(new WrappedEditBox(Minecraft.getInstance().font, 0, 0, 198, 18, null)); + } + + @Override + public void onAdd() { + widget.setMaxLength(1000000); + widget.setValue(toString.apply(value)); + widget.setResponder(this::update); + } + + @Override + public void render(Minecraft minecraft, Font font, int x, int y, int width, int height, GuiGraphics matrices, int mouseX, int mouseY, float delta) { + widget.setTextColor(hasErrors ? 16733525 : 14737632); + super.render(minecraft, font, x, y, width, height, matrices, mouseX, mouseY, delta); + } + + private void update(String s) { + try { + this.value = fromString.apply(s); + this.hasErrors = false; + } catch (Exception e) { + this.hasErrors = true; + } + } +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/ToggleButton.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/ToggleButton.java new file mode 100644 index 0000000..03ea11d --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/ToggleButton.java @@ -0,0 +1,33 @@ +package com.hypherionmc.craterlib.client.gui.config.widgets; + +import net.minecraft.client.gui.components.Button; +import net.minecraft.network.chat.Component; + +import java.util.List; +import java.util.function.Function; + +/** + * Copied from Cloth Config Lite + * ... + */ +public class ToggleButton extends AbstractConfigWidget { + + private final List options; + private final Function toComponent; + + public ToggleButton(List options, Function toComponent) { + this.options = options; + this.toComponent = toComponent; + this.widget = addChild(Button.builder(Component.empty(), this::switchNext).size(buttonWidth, buttonHeight).build()); + } + + @Override + public void onAdd() { + widget.setMessage(toComponent.apply(value)); + } + + private void switchNext(Button button) { + value = options.get((options.indexOf(value) + 1) % options.size()); + onAdd(); + } +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/WrappedEditBox.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/WrappedEditBox.java new file mode 100644 index 0000000..42c0c0c --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/WrappedEditBox.java @@ -0,0 +1,29 @@ +package com.hypherionmc.craterlib.client.gui.config.widgets; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.components.EditBox; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.NotNull; + +/** + * @author HypherionSA + */ +public class WrappedEditBox extends EditBox { + + public WrappedEditBox(Font font, int i, int j, int k, int l, @NotNull Component component) { + super(font, i, j, k, l, component); + } + + @Override + public void setFocused(boolean bl) { + for (GuiEventListener child : Minecraft.getInstance().screen.children()) { + if (child instanceof TextConfigOption option) { + WrappedEditBox box = option.widget; + super.setFocused(box == this); + } + } + super.setFocused(bl); + } +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/client/mentions/MentionCondition.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/client/mentions/MentionCondition.java new file mode 100644 index 0000000..e8bffde --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/client/mentions/MentionCondition.java @@ -0,0 +1,13 @@ +package com.hypherionmc.craterlib.client.mentions; + +/** + * Based on ... + */ +@FunctionalInterface +public interface MentionCondition { + + boolean shouldAddMention(String currentWord); + + MentionCondition ALWAYS = currentWord -> true; + +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/client/mentions/MentionsController.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/client/mentions/MentionsController.java new file mode 100644 index 0000000..bad3b43 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/client/mentions/MentionsController.java @@ -0,0 +1,47 @@ +package com.hypherionmc.craterlib.client.mentions; + +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; +import lombok.Getter; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Based on ... + */ +public class MentionsController { + + private static final Map> mentions = new LinkedHashMap<>(); + private static final Map mentionConditions = new LinkedHashMap<>(); + @Getter + private static boolean lastMentionConditional = true; + + public static void registerMention(ResourceIdentifier mentionClass, Collection suggestions, MentionCondition condition) { + mentions.put(mentionClass, suggestions); + mentionConditions.put(mentionClass, condition); + } + + public static Collection getMentions(String currentWord) { + ArrayList applicableMentions = new ArrayList<>(); + lastMentionConditional = false; + + mentionConditions.forEach((mention, condition) -> { + boolean shouldSuggest = condition.shouldAddMention(currentWord); + if (!shouldSuggest) return; + + if (!lastMentionConditional && condition != MentionCondition.ALWAYS) { + lastMentionConditional = true; + } + + applicableMentions.addAll(mentions.get(mention)); + }); + + return applicableMentions; + } + + public static boolean hasMentions() { + return !mentions.isEmpty(); + } +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/ConfigController.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/ConfigController.java new file mode 100644 index 0000000..41c9471 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/ConfigController.java @@ -0,0 +1,49 @@ +package com.hypherionmc.craterlib.core.config; + +import com.hypherionmc.craterlib.CraterConstants; +import lombok.Getter; +import me.hypherionmc.moonconfig.core.file.FileWatcher; +import org.jetbrains.annotations.ApiStatus; + +import java.io.Serializable; +import java.util.HashMap; + +/** + * @author HypherionSA + * Controls Config File Reloads and Events + */ +public final class ConfigController implements Serializable { + + /** + * Cache of registered configs + */ + @Getter + private static final HashMap monitoredConfigs = new HashMap<>(); + + /** + * INTERNAL METHOD - Register and watch the config + * + * @param config - The config class to register and watch + */ + @ApiStatus.Internal + public static void register_config(ModuleConfig config) { + if (monitoredConfigs.containsKey(config)) { + CraterConstants.LOG.error("Failed to register " + config.getConfigPath().getName() + ". Config already registered"); + } else { + FileWatcher configWatcher = new FileWatcher(); + try { + configWatcher.setWatch(config.getConfigPath(), () -> { + if (!config.isSaveCalled()) { + CraterConstants.LOG.info("Sending Reload Event for: " + config.getConfigPath().getName()); + config.configReloaded(); + } + }); + } catch (Exception e) { + CraterConstants.LOG.error("Failed to register " + config.getConfigPath().getName() + " for auto reloading. " + e.getMessage()); + } + monitoredConfigs.put(config, configWatcher); + CraterConstants.LOG.info("Registered " + config.getConfigPath().getName() + " successfully!"); + } + } + +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/ModuleConfig.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/ModuleConfig.java new file mode 100644 index 0000000..181efdc --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/ModuleConfig.java @@ -0,0 +1,184 @@ +package com.hypherionmc.craterlib.core.config; + +import me.hypherionmc.moonconfig.core.CommentedConfig; +import me.hypherionmc.moonconfig.core.Config; +import me.hypherionmc.moonconfig.core.conversion.ObjectConverter; +import me.hypherionmc.moonconfig.core.file.CommentedFileConfig; + +import java.io.File; + +/** + * @author HypherionSA + * Base Config class containing the save, upgrading and loading logic. + * All config classes must extend this class + */ +public class ModuleConfig { + + /* Final Variables */ + private final transient File configPath; + private final transient String networkID; + + private final transient String configName; + + private final transient String modId; + + private transient boolean isSaveCalled = false; + + /** + * Set up the config + * + * @param modId - The ID of the Mod/Module the config belongs to + * @param configName - The name of the config file, excluding extension + */ + public ModuleConfig(String modId, String configName) { + this(modId, "", configName); + } + + public ModuleConfig(String modId, String subFolder, String configName) { + /* Preserve the order of the config values */ + Config.setInsertionOrderPreserved(true); + + /* Configure Paths and Network SYNC ID */ + File configDir = new File("config" + (subFolder.isEmpty() ? "" : File.separator + subFolder)); + configPath = new File(configDir + File.separator + configName + ".toml"); + networkID = modId + ":conf_" + configName.replace("-", "_"); + this.modId = modId; + this.configName = configName; + + /* Check if the required directories exists, otherwise we create them */ + if (!configDir.exists()) { + configDir.mkdirs(); + } + } + + /** + * This method has to be called in the config constructor. This creates or upgrades the config file as needed + * + * @param config - The config class to use + */ + public void registerAndSetup(ModuleConfig config) { + if (!configPath.exists() || configPath.length() < 2) { + saveConfig(config); + } else { + migrateConfig(config); + } + /* Register the Config for Watching and events */ + ConfigController.register_config(this); + this.configReloaded(); + } + + /** + * Save the config to the disk + * + * @param conf - The config class to serialize and save + */ + public void saveConfig(ModuleConfig conf) { + this.isSaveCalled = true; + /* Set up the Serializer and Config Object */ + ObjectConverter converter = new ObjectConverter(); + CommentedFileConfig config = CommentedFileConfig.builder(configPath).build(); + + /* Save the config and fire the reload events */ + converter.toConfig(conf, config); + config.save(); + configReloaded(); + this.isSaveCalled = false; + } + + /** + * Load the config from the file into a Class + * + * @param conf - The config Class to load + * @return - Returns the loaded version of the class + */ + public T loadConfig(Object conf) { + /* Set up the Serializer and Config Object */ + ObjectConverter converter = new ObjectConverter(); + CommentedFileConfig config = CommentedFileConfig.builder(configPath).build(); + config.load(); + + /* Load the config and return the loaded config */ + converter.toObject(config, conf); + return (T) conf; + } + + /** + * INTERNAL METHOD - Upgrades the config files in the events the config structure changes + * + * @param conf - The config class to load + */ + public void migrateConfig(ModuleConfig conf) { + /* Set up the Serializer and Config Objects */ + CommentedFileConfig config = CommentedFileConfig.builder(configPath).build(); + CommentedFileConfig newConfig = CommentedFileConfig.builder(configPath).build(); + config.load(); + + /* Upgrade the config */ + new ObjectConverter().toConfig(conf, newConfig); + updateConfigValues(config, newConfig, newConfig, ""); + newConfig.save(); + + config.close(); + newConfig.close(); + } + + public void updateConfigValues(CommentedConfig oldConfig, CommentedConfig newConfig, CommentedConfig outputConfig, String subKey) { + /* Loop over the config keys and check what has changed */ + newConfig.valueMap().forEach((key, value) -> { + String finalKey = subKey + (subKey.isEmpty() ? "" : ".") + key; + if (value instanceof CommentedConfig commentedConfig) { + updateConfigValues(oldConfig, commentedConfig, outputConfig, finalKey); + } else { + outputConfig.set(finalKey, + oldConfig.contains(finalKey) ? oldConfig.get(finalKey) : value); + } + }); + } + + /** + * Get the location of the config file + * + * @return - The FILE object containing the config file + */ + public File getConfigPath() { + return configPath; + } + + /** + * Get the NETWORK SYNC ID + * + * @return - Returns the Sync ID in format modid:config_name + */ + public String getNetworkID() { + return networkID; + } + + /** + * Fired whenever changes to the config are detected + */ + public void configReloaded() { + + } + + /** + * Get the name of the Config File + * + * @return + */ + public String getConfigName() { + return configName; + } + + /** + * Get the MODID of the Module the config is registered to + * + * @return + */ + public String getModId() { + return modId; + } + + public boolean isSaveCalled() { + return isSaveCalled; + } +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/HideFromScreen.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/HideFromScreen.java new file mode 100644 index 0000000..512a025 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/HideFromScreen.java @@ -0,0 +1,8 @@ +package com.hypherionmc.craterlib.core.config.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface HideFromScreen { +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/NoConfigScreen.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/NoConfigScreen.java new file mode 100644 index 0000000..4288ee0 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/NoConfigScreen.java @@ -0,0 +1,12 @@ +package com.hypherionmc.craterlib.core.config.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * @author HypherionSA + * Allows Modules to disable Automatic Config Screens + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface NoConfigScreen { +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/SubConfig.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/SubConfig.java new file mode 100644 index 0000000..e3ec808 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/SubConfig.java @@ -0,0 +1,16 @@ +package com.hypherionmc.craterlib.core.config.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author HypherionSA + * Used to determine if a Config section should be rendered as a separate screen + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface SubConfig { +} + diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/Syncable.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/Syncable.java new file mode 100644 index 0000000..838b35b --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/Syncable.java @@ -0,0 +1,15 @@ +package com.hypherionmc.craterlib.core.config.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author HypherionSA + * //TODO Currently unused, but to be used with Config Syncing in the future + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Syncable { +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/Tooltip.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/Tooltip.java new file mode 100644 index 0000000..538471c --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/Tooltip.java @@ -0,0 +1,16 @@ +package com.hypherionmc.craterlib.core.config.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author HypherionSA + * Provides tooltips to the config GUI + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Tooltip { + String[] value(); +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEvent.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEvent.java new file mode 100644 index 0000000..89ff00a --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEvent.java @@ -0,0 +1,30 @@ +package com.hypherionmc.craterlib.core.event; + +import com.hypherionmc.craterlib.core.event.annot.Cancellable; +import com.hypherionmc.craterlib.core.event.exception.CraterEventCancellationException; + +public class CraterEvent { + + private boolean canceled = false; + + private boolean canCancel() { + return this.getClass().isAnnotationPresent(Cancellable.class); + } + + public void cancelEvent() { + try { + if (!this.canCancel()) { + throw new CraterEventCancellationException("Tried to cancel non-cancelable event: " + this.getClass().getName()); + } + + this.canceled = true; + } catch (Exception e) { + e.printStackTrace(); + } + } + + public boolean wasCancelled() { + return this.canceled; + } + +} \ No newline at end of file diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEventBus.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEventBus.java new file mode 100644 index 0000000..74eeecc --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEventBus.java @@ -0,0 +1,241 @@ +package com.hypherionmc.craterlib.core.event; + +import com.hypherionmc.craterlib.CraterConstants; +import com.hypherionmc.craterlib.core.event.annot.CraterEventListener; +import org.slf4j.Logger; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.*; +import java.util.function.Consumer; + +public final class CraterEventBus { + + public static final CraterEventBus INSTANCE = new CraterEventBus(); + private static final Logger LOGGER = CraterConstants.LOG; + private final Map, List> events = new HashMap<>(); + + public void postEvent(CraterEvent event) { + if (eventsRegisteredForType(event.getClass())) { + List l = new ArrayList<>(events.get(event.getClass())); + l.sort((o1, o2) -> Integer.compare(o2.priority, o1.priority)); + + for (ListenerContainer c : l) { + c.notifyListener(event); + } + } + } + + public void registerEventListener(Class clazz) { + this.registerListenerMethods(this.getEventMethodsOf(clazz)); + } + + public void registerEventListener(Object object) { + this.registerListenerMethods(this.getEventMethodsOf(object)); + } + + private void registerListenerMethods(List methods) { + for (EventMethod m : methods) { + Consumer listener = (event) -> { + try { + m.method.invoke(m.parentObject, event); + } catch (Exception e) { + throw new RuntimeException(e); + } + }; + + ListenerContainer container = new ListenerContainer(m.eventType, listener, m.priority); + container.listenerParentClassName = m.parentClass.getName(); + container.listenerMethodName = m.method.getName(); + this.registerListener(container); + } + } + + private List getEventMethodsOf(Object objectOrClass) { + List l = new ArrayList<>(); + try { + if (objectOrClass != null) { + boolean isClass = (objectOrClass instanceof Class); + Class c = isClass ? (Class) objectOrClass : objectOrClass.getClass(); + for (Method m : c.getMethods()) { + if (isClass && Modifier.isStatic(m.getModifiers())) { + EventMethod em = EventMethod.tryCreateFrom(new AnalyzedMethod(m, c)); + if ((em != null) && this.hasEventAnnotation(em)) l.add(em); + } + if (!isClass && !Modifier.isStatic(m.getModifiers())) { + EventMethod em = EventMethod.tryCreateFrom(new AnalyzedMethod(m, objectOrClass)); + if ((em != null) && this.hasEventAnnotation(em)) l.add(em); + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return l; + } + + private boolean hasEventAnnotation(EventMethod m) { + for (Annotation a : m.annotations) { + if (a instanceof CraterEventListener) return true; + } + return false; + } + + public void registerListener(Consumer listener, Class eventType) { + this.registerListener(listener, eventType, 0); + } + + public void registerListener(Consumer listener, Class eventType, int priority) { + this.registerListener(new ListenerContainer(eventType, listener, priority)); + } + + private void registerListener(ListenerContainer listenerContainer) { + try { + if (!eventsRegisteredForType(listenerContainer.eventType)) { + events.put(listenerContainer.eventType, new ArrayList<>()); + } + events.get(listenerContainer.eventType).add(listenerContainer); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public boolean eventsRegisteredForType(Class eventType) { + if (eventType == null) { + return false; + } + return this.events.containsKey(eventType); + } + + protected final static class ListenerContainer { + + private final Consumer listener; + private final Class eventType; + private final int priority; + private String listenerParentClassName = "[unknown]"; + private String listenerMethodName = "[unknown]"; + + private ListenerContainer(Class eventType, Consumer listener, int priority) { + this.eventType = eventType; + this.listener = listener; + this.priority = priority; + } + + private void notifyListener(CraterEvent event) { + try { + this.listener.accept(event); + } catch (Exception e) { + LOGGER.error("##################################"); + LOGGER.error("Failed to notify event listener!"); + LOGGER.error("Event Type: " + this.eventType.getName()); + LOGGER.error("Listener Parent Class Name: " + this.listenerParentClassName); + LOGGER.error("Listener Method Name In Parent Class: " + this.listenerMethodName); + LOGGER.error("##################################"); + e.printStackTrace(); + } + } + } + + protected static class AnalyzedMethod { + + protected Method method; + protected Object parentObject; + protected Class parentClass; + protected boolean isStatic; + protected List annotations = new ArrayList<>(); + + protected AnalyzedMethod() { + } + + protected AnalyzedMethod(Method method, Object parentObjectOrClass) { + this.method = method; + this.parentObject = parentObjectOrClass; + this.parentClass = this.tryGetParentClass(); + this.isStatic = Modifier.isStatic(method.getModifiers()); + collectMethodAnnotations(this.isStatic ? null : this.parentObject.getClass(), this.method, this.annotations); + } + + protected static void collectMethodAnnotations(Class c, Method m, List addToList) { + try { + addToList.addAll(Arrays.asList(m.getAnnotations())); + if (!Modifier.isStatic(m.getModifiers()) && (c != null)) { + Class sc = c.getSuperclass(); + if (sc != null) { + try { + Method sm = sc.getMethod(m.getName(), m.getParameterTypes()); + collectMethodAnnotations(sc, sm, addToList); + } catch (Exception ignored) { + } + } + } + } catch (Exception ignored) { + } + } + + protected Class tryGetParentClass() { + if (this.parentObject instanceof Class) { + return (Class) this.parentObject; + } + return this.parentObject.getClass(); + } + + } + + protected static class EventMethod extends AnalyzedMethod { + + protected final int priority; + protected final Class eventType; + + protected EventMethod(AnalyzedMethod method) { + + super(); + this.method = method.method; + this.parentObject = method.parentObject; + this.parentClass = method.parentClass; + this.isStatic = method.isStatic; + this.annotations = method.annotations; + + this.priority = this.tryGetPriority(); + this.eventType = this.tryGetEventType(); + + } + + protected static EventMethod tryCreateFrom(AnalyzedMethod method) { + EventMethod em = new EventMethod(method); + return (em.eventType != null) ? em : null; + } + + protected Class tryGetEventType() { + try { + if (this.method != null) { + Class[] params = this.method.getParameterTypes(); + if (params.length > 0) { + Class firstParam = params[0]; + if (CraterEvent.class.isAssignableFrom(firstParam)) { + return (Class) firstParam; + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + protected int tryGetPriority() { + try { + for (Annotation a : this.annotations) { + if (a instanceof CraterEventListener craterEventListener) { + return craterEventListener.priority(); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return 0; + } + + } + +} \ No newline at end of file diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEventPriority.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEventPriority.java new file mode 100644 index 0000000..0c97134 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEventPriority.java @@ -0,0 +1,13 @@ +package com.hypherionmc.craterlib.core.event; + +public class CraterEventPriority { + + public static final int LOWEST = -3; + public static final int LOWER = -2; + public static final int LOW = -1; + public static final int NORMAL = 0; + public static final int HIGH = 1; + public static final int HIGHER = 2; + public static final int HIGHEST = 3; + +} \ No newline at end of file diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/annot/Cancellable.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/annot/Cancellable.java new file mode 100644 index 0000000..ee3c2fc --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/annot/Cancellable.java @@ -0,0 +1,8 @@ +package com.hypherionmc.craterlib.core.event.annot; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface Cancellable { +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/annot/CraterEventListener.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/annot/CraterEventListener.java new file mode 100644 index 0000000..7b42490 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/annot/CraterEventListener.java @@ -0,0 +1,11 @@ +package com.hypherionmc.craterlib.core.event.annot; + +import com.hypherionmc.craterlib.core.event.CraterEventPriority; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface CraterEventListener { + int priority() default CraterEventPriority.NORMAL; +} \ No newline at end of file diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/exception/CraterEventCancellationException.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/exception/CraterEventCancellationException.java new file mode 100644 index 0000000..fe2a8a3 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/exception/CraterEventCancellationException.java @@ -0,0 +1,9 @@ +package com.hypherionmc.craterlib.core.event.exception; + +public class CraterEventCancellationException extends Exception { + + public CraterEventCancellationException(String msg) { + super(msg); + } + +} \ No newline at end of file diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/package-info.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/package-info.java new file mode 100644 index 0000000..5ec1749 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/event/package-info.java @@ -0,0 +1,5 @@ +/** + * The event system code in this package is based on, and adapted from Acara (https://github.com/Keksuccino/acara/) + * and is licensed under MIT by Keksuccino + */ +package com.hypherionmc.craterlib.core.event; \ No newline at end of file diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/CraterPacketNetwork.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/CraterPacketNetwork.java new file mode 100644 index 0000000..081c8fb --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/CraterPacketNetwork.java @@ -0,0 +1,43 @@ +package com.hypherionmc.craterlib.core.networking; + +import com.hypherionmc.craterlib.core.networking.data.PacketContext; +import com.hypherionmc.craterlib.nojang.network.BridgedFriendlyByteBuf; +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; +import lombok.Getter; + +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +@Getter +public class CraterPacketNetwork { + + private final PacketRegistry packetRegistry; + public static CraterPacketNetwork INSTANCE; + private static DeferredPacketRegistrar delayedHandler; + + public CraterPacketNetwork(PacketRegistry registry) { + INSTANCE = this; + this.packetRegistry = registry; + getDelayedHandler().registerQueuedPackets(registry); + } + + private static DeferredPacketRegistrar getDelayedHandler() { + if (delayedHandler == null) { + delayedHandler = new DeferredPacketRegistrar(); + } + return delayedHandler; + } + + public static PacketRegistrar registerPacket(ResourceIdentifier id, Class messageType, BiConsumer encoder, Function decoder, Consumer> handler) { + if (INSTANCE != null) { + return INSTANCE.packetRegistry.registerPacket(id, messageType, encoder, decoder, handler); + } else { + return getDelayedHandler().registerPacket(id, messageType, encoder, decoder, handler); + } + } + +} \ No newline at end of file diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/DeferredPacketRegistrar.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/DeferredPacketRegistrar.java new file mode 100644 index 0000000..25556f4 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/DeferredPacketRegistrar.java @@ -0,0 +1,41 @@ +package com.hypherionmc.craterlib.core.networking; + +import com.hypherionmc.craterlib.core.networking.data.PacketContext; +import com.hypherionmc.craterlib.core.networking.data.PacketHolder; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.nojang.network.BridgedFriendlyByteBuf; +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public class DeferredPacketRegistrar implements PacketRegistrar { + + private static final Map, PacketHolder> QUEUED_PACKET_MAP = new HashMap<>(); + + @Override + public PacketSide side() { + return PacketSide.CLIENT; + } + + @Override + public PacketRegistrar registerPacket(ResourceIdentifier packetIdentifier, Class messageType, BiConsumer encoder, Function decoder, Consumer> handler) { + PacketHolder container = new PacketHolder<>(packetIdentifier, messageType, encoder, decoder, handler); + QUEUED_PACKET_MAP.put(messageType, container); + return this; + } + + + public void registerQueuedPackets(PacketRegistry packetRegistration) { + if (!QUEUED_PACKET_MAP.isEmpty()) { + packetRegistration.PACKET_MAP.putAll(QUEUED_PACKET_MAP); + QUEUED_PACKET_MAP.forEach((aClass, container) -> packetRegistration.registerPacket(container)); + } + } +} \ No newline at end of file diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/PacketRegistrar.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/PacketRegistrar.java new file mode 100644 index 0000000..3ea55fa --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/PacketRegistrar.java @@ -0,0 +1,21 @@ +package com.hypherionmc.craterlib.core.networking; + +import com.hypherionmc.craterlib.core.networking.data.PacketContext; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.nojang.network.BridgedFriendlyByteBuf; +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; + +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public interface PacketRegistrar { + + PacketSide side(); + + PacketRegistrar registerPacket(ResourceIdentifier id, Class messageType, BiConsumer encoder, Function decoder, Consumer> handler); + +} \ No newline at end of file diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/PacketRegistry.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/PacketRegistry.java new file mode 100644 index 0000000..d1ec06c --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/PacketRegistry.java @@ -0,0 +1,41 @@ +package com.hypherionmc.craterlib.core.networking; + +import com.hypherionmc.craterlib.api.networking.CraterNetworkHandler; +import com.hypherionmc.craterlib.core.networking.data.PacketContext; +import com.hypherionmc.craterlib.core.networking.data.PacketHolder; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.nojang.network.BridgedFriendlyByteBuf; +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public abstract class PacketRegistry implements CraterNetworkHandler, PacketRegistrar { + + protected final Map, PacketHolder> PACKET_MAP = new HashMap<>(); + + protected final PacketSide side; + + public PacketRegistry(PacketSide side) { + this.side = side; + } + + public PacketRegistrar registerPacket(ResourceIdentifier id, Class messageType, BiConsumer encoder, Function decoder, Consumer> handler) { + PacketHolder holder = new PacketHolder<>(id, messageType, encoder, decoder, handler); + PACKET_MAP.put(messageType, holder); + registerPacket(holder); + return this; + } + + public PacketSide side() { + return side; + } + + protected abstract void registerPacket(PacketHolder packetHolder); +} \ No newline at end of file diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketContext.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketContext.java new file mode 100644 index 0000000..fc432dc --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketContext.java @@ -0,0 +1,15 @@ +package com.hypherionmc.craterlib.core.networking.data; + +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import org.jetbrains.annotations.Nullable; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public record PacketContext(@Nullable BridgedPlayer sender, T message, PacketSide side) { + + public PacketContext(T message, PacketSide side) { + this(null, message, side); + } + +} \ No newline at end of file diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketHolder.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketHolder.java new file mode 100644 index 0000000..14b6947 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketHolder.java @@ -0,0 +1,18 @@ +package com.hypherionmc.craterlib.core.networking.data; + +import com.hypherionmc.craterlib.nojang.network.BridgedFriendlyByteBuf; +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; + +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public record PacketHolder(ResourceIdentifier type, + Class messageType, + BiConsumer encoder, + Function decoder, + Consumer> handler) { +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketSide.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketSide.java new file mode 100644 index 0000000..ad6f8ec --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketSide.java @@ -0,0 +1,13 @@ +package com.hypherionmc.craterlib.core.networking.data; + +public enum PacketSide { + CLIENT, + SERVER; + + public PacketSide flipped() { + if (CLIENT.equals(this)) + return SERVER; + + return CLIENT; + } +} \ No newline at end of file diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/ClientPlatform.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/ClientPlatform.java new file mode 100644 index 0000000..089fb36 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/ClientPlatform.java @@ -0,0 +1,23 @@ +package com.hypherionmc.craterlib.core.platform; + +import com.hypherionmc.craterlib.nojang.client.BridgedMinecraft; +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedClientLevel; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.InternalServiceUtil; +import net.minecraft.network.Connection; + +/** + * @author HypherionSA + */ +public interface ClientPlatform { + + public final ClientPlatform INSTANCE = InternalServiceUtil.load(ClientPlatform.class); + + BridgedMinecraft getClientInstance(); + + BridgedPlayer getClientPlayer(); + + BridgedClientLevel getClientLevel(); + + Connection getClientConnection(); +} \ No newline at end of file diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/CommonPlatform.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/CommonPlatform.java new file mode 100644 index 0000000..ee7f944 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/CommonPlatform.java @@ -0,0 +1,15 @@ +package com.hypherionmc.craterlib.core.platform; + +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import com.hypherionmc.craterlib.utils.InternalServiceUtil; + +/** + * @author HypherionSA + */ +public interface CommonPlatform { + + public CommonPlatform INSTANCE = InternalServiceUtil.load(CommonPlatform.class); + + BridgedMinecraftServer getMCServer(); + +} \ No newline at end of file diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/CompatUtils.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/CompatUtils.java new file mode 100644 index 0000000..fb745fc --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/CompatUtils.java @@ -0,0 +1,13 @@ +package com.hypherionmc.craterlib.core.platform; + +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.InternalServiceUtil; + +public interface CompatUtils { + + public static final CompatUtils INSTANCE = InternalServiceUtil.load(CompatUtils.class); + + boolean isPlayerActive(BridgedPlayer player); + String getSkinUUID(BridgedPlayer player); + +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/Environment.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/Environment.java new file mode 100644 index 0000000..2ef219e --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/Environment.java @@ -0,0 +1,18 @@ +package com.hypherionmc.craterlib.core.platform; + +/** + * @author HypherionSA + */ +public enum Environment { + CLIENT, + SERVER, + UNKNOWN; + + public boolean isClient() { + return this == CLIENT; + } + + public boolean isServer() { + return this == SERVER; + } +} \ No newline at end of file diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/ModloaderEnvironment.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/ModloaderEnvironment.java new file mode 100644 index 0000000..c1bc176 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/platform/ModloaderEnvironment.java @@ -0,0 +1,32 @@ +package com.hypherionmc.craterlib.core.platform; + +import com.hypherionmc.craterlib.utils.InternalServiceUtil; + +import java.io.File; + +/** + * @author HypherionSA + * Helper class to provide information about the ModLoader + */ +public interface ModloaderEnvironment { + + public final ModloaderEnvironment INSTANCE = InternalServiceUtil.load(ModloaderEnvironment.class); + + boolean isFabric(); + + String getGameVersion(); + + File getGameFolder(); + + File getConfigFolder(); + + File getModsFolder(); + + Environment getEnvironment(); + + boolean isModLoaded(String modid); + + boolean isDevEnv(); + + int getModCount(); +} \ No newline at end of file diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordEventHandlers.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordEventHandlers.java new file mode 100644 index 0000000..3f1ffcb --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordEventHandlers.java @@ -0,0 +1,90 @@ +package com.hypherionmc.craterlib.core.rpcsdk; + +import com.hypherionmc.craterlib.core.rpcsdk.callbacks.*; +import com.sun.jna.Structure; + +import java.util.Arrays; +import java.util.List; + +/** + * @author HypherionSA + * Class containing references to all available discord event handles. + * Registering a handler is optional, and non-assigned handlers will be ignored + */ +public class DiscordEventHandlers extends Structure { + + // Callback for when the RPC was initialized successfully + public ReadyCallback ready; + + // Callback for when the Discord connection was ended + public DisconnectedCallback disconnected; + + // Callback for when a Discord Error occurs + public ErroredCallback errored; + + // Callback for when a player joins the game + public JoinGameCallback joinGame; + + // Callback for when a player spectates the game + public SpectateGameCallback spectateGame; + + // Callback for when a players request to join your game + public JoinRequestCallback joinRequest; + + /** + * DO NOT TOUCH THIS... EVER! + */ + @Override + protected List getFieldOrder() { + return Arrays.asList( + "ready", + "disconnected", + "errored", + "joinGame", + "spectateGame", + "joinRequest" + ); + } + + public static class Builder { + private final DiscordEventHandlers handlers; + + public Builder() { + this.handlers = new DiscordEventHandlers(); + } + + public Builder ready(ReadyCallback readyCallback) { + handlers.ready = readyCallback; + return this; + } + + public Builder disconnected(DisconnectedCallback disconnectedCallback) { + handlers.disconnected = disconnectedCallback; + return this; + } + + public Builder errored(ErroredCallback erroredCallback) { + handlers.errored = erroredCallback; + return this; + } + + public Builder joinGame(JoinGameCallback joinGameCallback) { + handlers.joinGame = joinGameCallback; + return this; + } + + public Builder spectateGame(SpectateGameCallback spectateGameCallback) { + handlers.spectateGame = spectateGameCallback; + return this; + } + + public Builder joinRequest(JoinRequestCallback joinRequestCallback) { + handlers.joinRequest = joinRequestCallback; + return this; + } + + public DiscordEventHandlers build() { + return handlers; + } + } +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordRPC.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordRPC.java new file mode 100644 index 0000000..7cf53ff --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordRPC.java @@ -0,0 +1,99 @@ +package com.hypherionmc.craterlib.core.rpcsdk; + +import com.sun.jna.Library; +import com.sun.jna.Native; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author HypherionSA + * Java Wrapper of the Discord-RPC Library + */ +public interface DiscordRPC extends Library { + + DiscordRPC INSTANCE = Native.load("discord-rpc", DiscordRPC.class); + + /** + * Open a New RPC Connection + * + * @param applicationId The ID of the Application the RPC is tied to + * @param handlers Optional Event Callback Handlers + * @param autoRegister Auto Register the running game + * @param steamId Steam ID of the game + */ + void Discord_Initialize(@NotNull String applicationId, @Nullable DiscordEventHandlers handlers, boolean autoRegister, @Nullable String steamId); + + /** + * Shutdown the RPC instance and disconnect from discord + */ + void Discord_Shutdown(); + + /** + * Need to be called manually at least every 2 seconds, to allow RPC updates + * and callback handlers to fire + */ + void Discord_RunCallbacks(); + + /** + * Not sure about this. Believe it needs to be called manually in some circumstances + */ + void Discord_UpdateConnection(); + + /** + * Update the Rich Presence + * + * @param struct Constructed {@link DiscordRichPresence} + */ + void Discord_UpdatePresence(@Nullable DiscordRichPresence struct); + + /** + * Clear the current Rich Presence + */ + void Discord_ClearPresence(); + + /** + * Respond to Join/Spectate callback + * + * @param userid The Discord User ID of the user that initiated the request + * @param reply Reply to the request. See {@link DiscordReply} + */ + void Discord_Respond(@NotNull String userid, int reply); + + /** + * Replace the already registered {@link DiscordEventHandlers} + * + * @param handlers The new handlers to apply + */ + void Discord_UpdateHandlers(@Nullable DiscordEventHandlers handlers); + + /** + * Register the executable of the application/game + * Only applicable when autoRegister is set to false + * + * @param applicationId The Application ID + * @param command The Launch command of the game + *

+ * NB: THIS DOES NOT WORK WITH MINECRAFT + */ + void Discord_Register(String applicationId, String command); + + /** + * Register the Steam executable of the application/game + * + * @param applicationId The Application ID + * @param steamId The Steam ID of the application/game + */ + void Discord_RegisterSteamGame(String applicationId, String steamId); + + public enum DiscordReply { + NO(0), + YES(1), + IGNORE(2); + + public final int reply; + + DiscordReply(int reply) { + this.reply = reply; + } + } +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordRichPresence.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordRichPresence.java new file mode 100644 index 0000000..51fd720 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordRichPresence.java @@ -0,0 +1,248 @@ +package com.hypherionmc.craterlib.core.rpcsdk; + +import com.hypherionmc.craterlib.core.rpcsdk.helpers.RPCButton; +import com.sun.jna.Structure; +import org.jetbrains.annotations.NotNull; + +import java.time.OffsetDateTime; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * @author HypherionSA + * Class reprenting a Discord RPC activity + */ +public class DiscordRichPresence extends Structure { + + // First line of text on the RPC + public String state; + + // Second line of text on the RPC + public String details; + + // Time the activity started in UNIX-Timestamp format + public long startTimestamp; + + // Time the activity will end in UNIX-Timestamp format + public long endTimestamp; + + // URL or Asset key of the Large Image + public String largeImageKey; + + // Hover text to display when hovering the Large Image + public String largeImageText; + + // URL or Asset key of the Small Image + public String smallImageKey; + + // Hover text to display when hovering the Small Image + public String smallImageText; + + // Id of the player's party, lobby, or group. + public String partyId; + + // Current size of the player's party, lobby, or group. + public int partySize; + + // Maximum size of the player's party, lobby, or group. + public int partyMax; + + // Unused + public String partyPrivacy; + + // Unused. + public String matchSecret; + + // Unique hashed string for chat invitations and Ask to Join. + public String joinSecret; + + // Unique hashed string for Spectate button. + public String spectateSecret; + + // Label of the First RPC Button + public String button_label_1; + + // URL of the First RPC Button + public String button_url_1; + + // Label of the Second RPC Button + public String button_label_2; + + // URL of the Second RPC Button + public String button_url_2; + + // Unused + public int instance; + + public DiscordRichPresence() { + setStringEncoding("UTF-8"); + } + + /** + * DO NOT TOUCH THIS... EVER! + */ + @Override + protected List getFieldOrder() { + return Arrays.asList( + "state", + "details", + "startTimestamp", + "endTimestamp", + "largeImageKey", + "largeImageText", + "smallImageKey", + "smallImageText", + "partyId", + "partySize", + "partyMax", + "partyPrivacy", + "matchSecret", + "joinSecret", + "spectateSecret", + "button_label_1", + "button_url_1", + "button_label_2", + "button_url_2", + "instance" + ); + } + + public static class Builder { + private final DiscordRichPresence rpc; + + public Builder(String state) { + rpc = new DiscordRichPresence(); + + if (state != null && !state.isEmpty()) { + rpc.state = state.substring(0, Math.min(state.length(), 128)); + } + } + + public Builder setDetails(String details) { + if (details != null && !details.isEmpty()) { + rpc.details = details.substring(0, Math.min(details.length(), 128)); + } + return this; + } + + public Builder setStartTimestamp(long timestamp) { + rpc.startTimestamp = timestamp; + return this; + } + + public Builder setStartTimestamp(OffsetDateTime timestamp) { + rpc.startTimestamp = timestamp.toEpochSecond(); + return this; + } + + public Builder setEndTimestamp(long timestamp) { + rpc.endTimestamp = timestamp; + return this; + } + + public Builder setEndTimestamp(OffsetDateTime timestamp) { + rpc.endTimestamp = timestamp.toEpochSecond(); + return this; + } + + public Builder setLargeImage(String key) { + return this.setLargeImage(key, ""); + } + + public Builder setLargeImage(@NotNull String key, String text) { + // Null check used for users blatantly ignoring the NotNull marker + if ((text != null && !text.isEmpty()) && key != null) { + throw new IllegalArgumentException("Image key cannot be null when assigning a hover text"); + } + + rpc.largeImageKey = key; + rpc.largeImageText = text; + return this; + } + + public Builder setSmallImage(String key) { + return this.setSmallImage(key, ""); + } + + public Builder setSmallImage(@NotNull String key, String text) { + // Null check used for users blatantly ignoring the NotNull marker + if ((text != null && !text.isEmpty()) && key != null) { + throw new IllegalArgumentException("Image key cannot be null when assigning a hover text"); + } + + rpc.smallImageKey = key; + rpc.smallImageText = text; + return this; + } + + public Builder setParty(String party, int size, int max) { + // Buttons are present, ignore + if ((rpc.button_label_1 != null && rpc.button_label_1.isEmpty()) || (rpc.button_label_2 != null && rpc.button_label_2.isEmpty())) + return this; + + rpc.partyId = party; + rpc.partySize = size; + rpc.partyMax = max; + return this; + } + + public Builder setSecrets(String match, String join, String spectate) { + // Buttons are present, ignore + if ((rpc.button_label_1 != null && rpc.button_label_1.isEmpty()) || (rpc.button_label_2 != null && rpc.button_label_2.isEmpty())) + return this; + + rpc.matchSecret = match; + rpc.joinSecret = join; + rpc.spectateSecret = spectate; + return this; + } + + public Builder setSecrets(String join, String spectate) { + // Buttons are present, ignore + if ((rpc.button_label_1 != null && rpc.button_label_1.isEmpty()) || (rpc.button_label_2 != null && rpc.button_label_2.isEmpty())) + return this; + + rpc.joinSecret = join; + rpc.spectateSecret = spectate; + return this; + } + + public Builder setInstance(boolean i) { + // Buttons are present, ignore + if ((rpc.button_label_1 != null && rpc.button_label_1.isEmpty()) || (rpc.button_label_2 != null && rpc.button_label_2.isEmpty())) + return this; + + rpc.instance = i ? 1 : 0; + return this; + } + + public Builder setButtons(RPCButton button) { + return this.setButtons(Collections.singletonList(button)); + } + + public Builder setButtons(RPCButton button1, RPCButton button2) { + return this.setButtons(Arrays.asList(button1, button2)); + } + + public Builder setButtons(List rpcButtons) { + // Limit to 2 Buttons. Discord Limitation + if (rpcButtons != null && !rpcButtons.isEmpty()) { + int length = Math.min(rpcButtons.size(), 2); + rpc.button_label_1 = rpcButtons.get(0).getLabel(); + rpc.button_url_1 = rpcButtons.get(0).getUrl(); + + if (length == 2) { + rpc.button_label_2 = rpcButtons.get(1).getLabel(); + rpc.button_url_2 = rpcButtons.get(1).getUrl(); + } + } + + return this; + } + + public DiscordRichPresence build() { + return rpc; + } + } +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordUser.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordUser.java new file mode 100644 index 0000000..e8bc085 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordUser.java @@ -0,0 +1,39 @@ +package com.hypherionmc.craterlib.core.rpcsdk; + +import com.sun.jna.Structure; + +import java.util.Arrays; +import java.util.List; + +/** + * @author HypherionSA + * Class representing the Discord User + */ +public class DiscordUser extends Structure { + + // The User ID of the User + public String userId; + + // The Username of the User + public String username; + + // The unique identifier of the user. Discontinued by Discord + @Deprecated + public String discriminator; + + // The avatar has of the user + public String avatar; + + /** + * DO NOT TOUCH THIS... EVER! + */ + @Override + protected List getFieldOrder() { + return Arrays.asList( + "userId", + "username", + "discriminator", + "avatar" + ); + } +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/DisconnectedCallback.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/DisconnectedCallback.java new file mode 100644 index 0000000..7ea59e1 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/DisconnectedCallback.java @@ -0,0 +1,18 @@ +package com.hypherionmc.craterlib.core.rpcsdk.callbacks; + +import com.sun.jna.Callback; + +/** + * @author HypherionSA + * Callback for when the Discord RPC disconnects + */ +public interface DisconnectedCallback extends Callback { + + /** + * Called when RPC disconnected + * + * @param errorCode Error code if any + * @param message Details about the disconnection + */ + void apply(int errorCode, String message); +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/ErroredCallback.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/ErroredCallback.java new file mode 100644 index 0000000..1f86c90 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/ErroredCallback.java @@ -0,0 +1,18 @@ +package com.hypherionmc.craterlib.core.rpcsdk.callbacks; + +import com.sun.jna.Callback; + +/** + * @author HypherionSA + * Callback for when the RPC ran into an error + */ +public interface ErroredCallback extends Callback { + + /** + * Called when an RPC error occurs + * + * @param errorCode Error code if any + * @param message Details about the error + */ + void apply(int errorCode, String message); +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/JoinGameCallback.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/JoinGameCallback.java new file mode 100644 index 0000000..cc752af --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/JoinGameCallback.java @@ -0,0 +1,17 @@ +package com.hypherionmc.craterlib.core.rpcsdk.callbacks; + +import com.sun.jna.Callback; + +/** + * @author HypherionSA + * Callback for when someone was approved to join your game + */ +public interface JoinGameCallback extends Callback { + + /** + * Called when someone joins a game from {@link JoinRequestCallback} + * + * @param joinSecret Secret or Password required to let the player join the game + */ + void apply(String joinSecret); +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/JoinRequestCallback.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/JoinRequestCallback.java new file mode 100644 index 0000000..115fd4f --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/JoinRequestCallback.java @@ -0,0 +1,19 @@ +package com.hypherionmc.craterlib.core.rpcsdk.callbacks; + +import com.hypherionmc.craterlib.core.rpcsdk.DiscordUser; +import com.sun.jna.Callback; + +/** + * @author HypherionSA + * Callback for when someone requests to join your game + */ +public interface JoinRequestCallback extends Callback { + + /** + * Called when someone clicks on the Join Game button + * + * @param user The Discord User trying to join your game + * @see DiscordUser + */ + void apply(DiscordUser user); +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/ReadyCallback.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/ReadyCallback.java new file mode 100644 index 0000000..66f3b59 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/ReadyCallback.java @@ -0,0 +1,19 @@ +package com.hypherionmc.craterlib.core.rpcsdk.callbacks; + +import com.hypherionmc.craterlib.core.rpcsdk.DiscordUser; +import com.sun.jna.Callback; + +/** + * @author HypherionSA + * Callback for when the RPC has connected successfully + */ +public interface ReadyCallback extends Callback { + + /** + * Called when the RPC is connected and ready to be used + * + * @param user The user the RPC is displayed on + * @see DiscordUser + */ + void apply(DiscordUser user); +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/SpectateGameCallback.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/SpectateGameCallback.java new file mode 100644 index 0000000..979e53d --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/SpectateGameCallback.java @@ -0,0 +1,17 @@ +package com.hypherionmc.craterlib.core.rpcsdk.callbacks; + +import com.sun.jna.Callback; + +/** + * @author HypherionSA + * Callback for when someone is requesting to spectate your game + */ +public interface SpectateGameCallback extends Callback { + + /** + * Called when joining the game + * + * @param spectateSecret Secret or Password required to let the player spectate + */ + void apply(String spectateSecret); +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/helpers/RPCButton.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/helpers/RPCButton.java new file mode 100644 index 0000000..ab2bdc8 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/helpers/RPCButton.java @@ -0,0 +1,55 @@ +package com.hypherionmc.craterlib.core.rpcsdk.helpers; + +import org.jetbrains.annotations.NotNull; + +import java.io.Serializable; + +/** + * @author HypherionSA + * Helper class to add Buttons to Discord Rich Presence + * This can not be used with Join/Spectate + */ +public class RPCButton implements Serializable { + + // The label of the button + private final String label; + + // The URL the button will open when clicked + private final String url; + + protected RPCButton(String label, String url) { + this.label = label; + this.url = url; + } + + /** + * Create a new RPC Button + * + * @param label The label of the button + * @param url The URL the button will open when clicked + * @return The constructed button + */ + public static RPCButton create(@NotNull String label, @NotNull String url) { + // Null check used here for users blatantly ignoring the NotNull marker + if (label == null || label.isEmpty() || url == null || url.isEmpty()) { + throw new IllegalArgumentException("RPC Buttons require both a label and url"); + } + + label = label.substring(0, Math.min(label.length(), 31)); + return new RPCButton(label, url); + } + + /** + * @return The label assigned to the button + */ + public String getLabel() { + return label; + } + + /** + * @return The URL of the button + */ + public String getUrl() { + return url; + } +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/ChatInputSuggestorMixin.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/ChatInputSuggestorMixin.java new file mode 100644 index 0000000..c48dfbc --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/ChatInputSuggestorMixin.java @@ -0,0 +1,84 @@ +package com.hypherionmc.craterlib.mixin; + +import com.hypherionmc.craterlib.client.mentions.MentionsController; +import net.minecraft.client.gui.components.CommandSuggestions; +import net.minecraft.client.gui.components.EditBox; +import org.objectweb.asm.Opcodes; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.ModifyVariable; +import org.spongepowered.asm.mixin.injection.Slice; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * @author HypherionSA + * Allow Users, Roles and Channels to be pingable from MC chat (Client Side) + */ +@Mixin(CommandSuggestions.class) +public abstract class ChatInputSuggestorMixin { + + @Shadow + public abstract void showSuggestions(boolean p_93931_); + + @Shadow @Final + EditBox input; + + @Shadow + private static int getLastWordIndex(String p_93913_) { + return 0; + } + + @Inject( + method = "updateCommandInfo", + at = @At( + value = "FIELD", + target = "Lnet/minecraft/client/gui/components/CommandSuggestions;pendingSuggestions:Ljava/util/concurrent/CompletableFuture;", + opcode = Opcodes.PUTFIELD, + shift = At.Shift.AFTER, + ordinal = 0 + ), + slice = @Slice( + from = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/gui/components/CommandSuggestions;getLastWordIndex(Ljava/lang/String;)I" + ) + ) + ) + private void injectSuggestions(CallbackInfo ci) { + if (MentionsController.hasMentions() && MentionsController.isLastMentionConditional()) { + this.showSuggestions(true); + } + } + + @SuppressWarnings("InvalidInjectorMethodSignature") + @ModifyVariable(method = "updateCommandInfo", at = @At(value = "STORE"), ordinal = 0, name = "collection") + private Collection injectMentions(Collection vanilla) { + if (!MentionsController.hasMentions()) + return vanilla; + + ArrayList newSuggest = new ArrayList<>(vanilla); + + String currentInput = this.input.getValue(); + int currentCursorPosition = this.input.getCursorPosition(); + + String textBeforeCursor = currentInput.substring(0, currentCursorPosition); + int startOfCurrentWord = getLastWordIndex(textBeforeCursor); + + String currentWord = textBeforeCursor.substring(startOfCurrentWord); + String finalWord = currentWord.replace("[", "").replace("]", ""); + + Collection mentions = MentionsController.getMentions(finalWord); + + if (!mentions.isEmpty()) { + mentions.forEach(m -> newSuggest.add("[" + m + "]")); + } + + return newSuggest; + } +} \ No newline at end of file diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/CommandMixin.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/CommandMixin.java new file mode 100644 index 0000000..aeb2e22 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/CommandMixin.java @@ -0,0 +1,37 @@ +package com.hypherionmc.craterlib.mixin.events; + +import com.google.common.base.Throwables; +import com.hypherionmc.craterlib.api.events.server.CraterCommandEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.mojang.brigadier.ParseResults; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +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.CallbackInfoReturnable; + +@Mixin(Commands.class) +public class CommandMixin { + + @Inject(method = "performCommand", + at = @At(value = "INVOKE", + target = "Lcom/mojang/brigadier/CommandDispatcher;execute(Lcom/mojang/brigadier/ParseResults;)I", + shift = At.Shift.BEFORE + ), cancellable = true + ) + private void injectCommandEvent(ParseResults stackParseResults, String command, CallbackInfoReturnable cir) { + CraterCommandEvent commandEvent = CraterCommandEvent.of(stackParseResults, command); + CraterEventBus.INSTANCE.postEvent(commandEvent); + if (commandEvent.wasCancelled()) { + cir.setReturnValue(1); + return; + } + + if (commandEvent.getException() != null) { + Throwables.throwIfUnchecked(commandEvent.getException()); + cir.setReturnValue(1); + } + } + +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerAdvancementsMixin.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerAdvancementsMixin.java new file mode 100644 index 0000000..30fa64f --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerAdvancementsMixin.java @@ -0,0 +1,31 @@ +package com.hypherionmc.craterlib.mixin.events; + +import com.hypherionmc.craterlib.api.events.server.CraterAdvancementEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.advancements.BridgedAdvancement; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraft.advancements.Advancement; +import net.minecraft.advancements.AdvancementHolder; +import net.minecraft.server.PlayerAdvancements; +import net.minecraft.server.level.ServerPlayer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(PlayerAdvancements.class) +public class PlayerAdvancementsMixin { + + @Shadow + private ServerPlayer player; + + @Inject(method = "award", at = @At(value = "INVOKE", target = "Lnet/minecraft/advancements/AdvancementRewards;grant(Lnet/minecraft/server/level/ServerPlayer;)V", shift = At.Shift.AFTER)) + private void injectAdvancementEvent(AdvancementHolder advancementHolder, String string, CallbackInfoReturnable cir) { + Advancement advancement = advancementHolder.value(); + + if (advancement.display().isPresent() && advancement.display().get().shouldAnnounceChat()) { + CraterEventBus.INSTANCE.postEvent(new CraterAdvancementEvent(BridgedPlayer.of(this.player), BridgedAdvancement.of(advancementHolder.value()))); + } + } +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerListMixin.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerListMixin.java new file mode 100644 index 0000000..3bc6f6b --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerListMixin.java @@ -0,0 +1,53 @@ +package com.hypherionmc.craterlib.mixin.events; + +import com.hypherionmc.craterlib.api.events.server.CraterPlayerEvent; +import com.hypherionmc.craterlib.api.events.server.MessageBroadcastEvent; +import com.hypherionmc.craterlib.api.events.server.PlayerPreLoginEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.authlib.BridgedGameProfile; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.ChatUtils; +import com.mojang.authlib.GameProfile; +import net.minecraft.network.Connection; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.CommonListenerCookie; +import net.minecraft.server.players.PlayerList; +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; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.net.SocketAddress; +import java.util.function.Function; + +@Mixin(PlayerList.class) +public class PlayerListMixin { + + @Inject(method = "broadcastSystemMessage(Lnet/minecraft/network/chat/Component;Ljava/util/function/Function;Z)V", at = @At("HEAD")) + private void injectBroadcastEvent(Component component, Function function, boolean bl, CallbackInfo ci) { + String thread = Thread.currentThread().getStackTrace()[3].getClassName(); + MessageBroadcastEvent event = new MessageBroadcastEvent(ChatUtils.mojangToAdventure(component), (f) -> ChatUtils.mojangToAdventure(component), bl, thread); + CraterEventBus.INSTANCE.postEvent(event); + } + + @Inject(method = "placeNewPlayer", at = @At("TAIL")) + private void injectPlayerLoginEvent(Connection connection, ServerPlayer serverPlayer, CommonListenerCookie commonListenerCookie, CallbackInfo ci) { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerEvent.PlayerLoggedIn(BridgedPlayer.of(serverPlayer))); + } + + @Inject(method = "remove", at = @At("HEAD")) + private void injectPlayerLogoutEvent(ServerPlayer player, CallbackInfo ci) { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerEvent.PlayerLoggedOut(BridgedPlayer.of(player))); + } + + @Inject(method = "canPlayerLogin", at = @At("HEAD"), cancellable = true) + private void injectPreLoginEvent(SocketAddress address, GameProfile gameProfile, CallbackInfoReturnable cir) { + PlayerPreLoginEvent event = new PlayerPreLoginEvent(address, BridgedGameProfile.of(gameProfile)); + CraterEventBus.INSTANCE.postEvent(event); + if (event.getMessage() != null) { + cir.setReturnValue(ChatUtils.adventureToMojang(event.getMessage())); + } + } +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerMixin.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerMixin.java new file mode 100644 index 0000000..5fd03bd --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerMixin.java @@ -0,0 +1,21 @@ +package com.hypherionmc.craterlib.mixin.events; + +import com.hypherionmc.craterlib.api.events.common.CraterPlayerDeathEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.player.Player; +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(Player.class) +public class PlayerMixin { + + @Inject(method = "die", at = @At("HEAD")) + private void injectPlayerDeathEvent(DamageSource damageSource, CallbackInfo ci) { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerDeathEvent(BridgedPlayer.of(((Player) (Object) this)), damageSource)); + } + +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/ServerPlayerMixin.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/ServerPlayerMixin.java new file mode 100644 index 0000000..ae028e7 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/ServerPlayerMixin.java @@ -0,0 +1,21 @@ +package com.hypherionmc.craterlib.mixin.events; + +import com.hypherionmc.craterlib.api.events.common.CraterPlayerDeathEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.damagesource.DamageSource; +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(ServerPlayer.class) +public class ServerPlayerMixin { + + @Inject(method = "die", at = @At("HEAD")) + private void injectPlayerDeathEvent(DamageSource damageSource, CallbackInfo ci) { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerDeathEvent(BridgedPlayer.of(((ServerPlayer) (Object) this)), damageSource)); + } + +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/ClientLevelMixin.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/ClientLevelMixin.java new file mode 100644 index 0000000..2368d6d --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/ClientLevelMixin.java @@ -0,0 +1,25 @@ +package com.hypherionmc.craterlib.mixin.events.client; + +import com.hypherionmc.craterlib.api.events.client.CraterSinglePlayerEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; +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(ClientLevel.class) +public class ClientLevelMixin { + + @Inject(method = "addEntity", at = @At("HEAD")) + private void injectSinglePlayerJoinEvent(Entity entity, CallbackInfo ci) { + if (entity instanceof Player player) { + CraterSinglePlayerEvent.PlayerLogin playerLogin = new CraterSinglePlayerEvent.PlayerLogin(BridgedPlayer.of(player)); + CraterEventBus.INSTANCE.postEvent(playerLogin); + } + } + +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/MinecraftMixin.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/MinecraftMixin.java new file mode 100644 index 0000000..2903131 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/MinecraftMixin.java @@ -0,0 +1,31 @@ +package com.hypherionmc.craterlib.mixin.events.client; + +import com.hypherionmc.craterlib.api.events.client.ScreenEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.client.gui.BridgedScreen; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.Screen; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(Minecraft.class) +public class MinecraftMixin { + + @Shadow + @Nullable + public Screen screen; + + @Inject(method = "setScreen", at = @At(value = "TAIL")) + private void injectScreenOpeningEvent(Screen screen, CallbackInfo ci) { + Screen old = this.screen; + if (screen != null) { + ScreenEvent.Opening opening = new ScreenEvent.Opening(BridgedScreen.of(old), BridgedScreen.of(screen)); + CraterEventBus.INSTANCE.postEvent(opening); + } + } + +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/RealmsMainScreenMixin.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/RealmsMainScreenMixin.java new file mode 100644 index 0000000..3fa397d --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/RealmsMainScreenMixin.java @@ -0,0 +1,23 @@ +package com.hypherionmc.craterlib.mixin.events.client; + +import com.hypherionmc.craterlib.api.events.client.PlayerJoinRealmEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.realmsclient.dto.BridgedRealmsServer; +import com.mojang.realmsclient.RealmsMainScreen; +import com.mojang.realmsclient.dto.RealmsServer; +import net.minecraft.client.gui.screens.Screen; +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 RealmsMainScreenMixin { + + @Inject(at = @At("HEAD"), method = "play") + private static void play(RealmsServer serverData, Screen arg2, CallbackInfo ci) { + PlayerJoinRealmEvent playerJoinRealm = new PlayerJoinRealmEvent(BridgedRealmsServer.of(serverData)); + CraterEventBus.INSTANCE.postEvent(playerJoinRealm); + } + +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/advancements/BridgedAdvancement.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/advancements/BridgedAdvancement.java new file mode 100644 index 0000000..19a7e7a --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/advancements/BridgedAdvancement.java @@ -0,0 +1,21 @@ +package com.hypherionmc.craterlib.nojang.advancements; + +import lombok.RequiredArgsConstructor; +import net.minecraft.advancements.Advancement; + +import java.util.Optional; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedAdvancement { + + private final Advancement internal; + + public Optional displayInfo() { + if (internal.display().isPresent()) { + return Optional.of(BridgedDisplayInfo.of(internal.display().get())); + } + + return Optional.empty(); + } + +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/advancements/BridgedDisplayInfo.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/advancements/BridgedDisplayInfo.java new file mode 100644 index 0000000..5a0c155 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/advancements/BridgedDisplayInfo.java @@ -0,0 +1,29 @@ +package com.hypherionmc.craterlib.nojang.advancements; + +import com.hypherionmc.craterlib.utils.ChatUtils; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; +import net.minecraft.advancements.DisplayInfo; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedDisplayInfo { + + private final DisplayInfo internal; + + public boolean shouldDisplay() { + return internal.shouldAnnounceChat(); + } + + public boolean isHidden() { + return internal.isHidden(); + } + + public Component displayName() { + return ChatUtils.mojangToAdventure(internal.getTitle()); + } + + public Component description() { + return ChatUtils.mojangToAdventure(internal.getDescription()); + } + +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/authlib/BridgedGameProfile.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/authlib/BridgedGameProfile.java new file mode 100644 index 0000000..c83f3c4 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/authlib/BridgedGameProfile.java @@ -0,0 +1,29 @@ +package com.hypherionmc.craterlib.nojang.authlib; + +import com.mojang.authlib.GameProfile; +import lombok.RequiredArgsConstructor; + +import java.util.UUID; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedGameProfile { + + private final GameProfile internal; + + public static BridgedGameProfile mojang(UUID id, String name) { + return new BridgedGameProfile(new GameProfile(id, name)); + } + + public String getName() { + return internal.getName(); + } + + public UUID getId() { + return internal.getId(); + } + + public GameProfile toMojang() { + return internal; + } + +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/BridgedMinecraft.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/BridgedMinecraft.java new file mode 100644 index 0000000..89e5d05 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/BridgedMinecraft.java @@ -0,0 +1,84 @@ +package com.hypherionmc.craterlib.nojang.client; + +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedClientLevel; +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedServerData; +import com.hypherionmc.craterlib.nojang.client.server.BridgedIntegratedServer; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import lombok.Getter; +import net.minecraft.SharedConstants; +import net.minecraft.client.Minecraft; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.util.UUID; + +public class BridgedMinecraft { + + @Getter + private static final BridgedMinecraft instance = new BridgedMinecraft(); + private final Minecraft internal = Minecraft.getInstance(); + + public File getGameDirectory() { + return internal.gameDirectory; + } + + public BridgedOptions getOptions() { + return BridgedOptions.of(internal.options); + } + + @Nullable + public BridgedClientLevel getLevel() { + if (internal.level == null) + return null; + + return BridgedClientLevel.of(internal.level); + } + + public boolean isRealmServer() { + return internal.getCurrentServer() != null && internal.getCurrentServer().isRealm(); + } + + public boolean isSinglePlayer() { + return internal.hasSingleplayerServer(); + } + + @Nullable + public BridgedPlayer getPlayer() { + if (internal.player == null) + return null; + + return BridgedPlayer.of(internal.player); + } + + public String getGameVersion() { + return SharedConstants.getCurrentVersion().getName(); + } + + public String getUserName() { + return internal.getUser().getName(); + } + + public UUID getPlayerId() { + return internal.getUser().getProfileId(); + } + + @Nullable + public BridgedServerData getCurrentServer() { + if (internal.getCurrentServer() == null) + return null; + + return BridgedServerData.of(internal.getCurrentServer()); + } + + @Nullable + public BridgedIntegratedServer getSinglePlayerServer() { + return BridgedIntegratedServer.of(internal.getSingleplayerServer()); + } + + public int getServerPlayerCount () { + if (internal.getConnection() == null) + return 0; + + return internal.getConnection().getOnlinePlayers().size(); + } +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/BridgedOptions.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/BridgedOptions.java new file mode 100644 index 0000000..7063feb --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/BridgedOptions.java @@ -0,0 +1,15 @@ +package com.hypherionmc.craterlib.nojang.client; + +import lombok.RequiredArgsConstructor; +import net.minecraft.client.Options; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedOptions { + + private final Options internal; + + public String getLanguage() { + return internal.languageCode; + } + +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/gui/BridgedScreen.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/gui/BridgedScreen.java new file mode 100644 index 0000000..c4bcc23 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/gui/BridgedScreen.java @@ -0,0 +1,36 @@ +package com.hypherionmc.craterlib.nojang.client.gui; + +import lombok.RequiredArgsConstructor; +import net.minecraft.client.gui.screens.LevelLoadingScreen; +import net.minecraft.client.gui.screens.ReceivingLevelScreen; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.gui.screens.TitleScreen; +import net.minecraft.client.gui.screens.multiplayer.JoinMultiplayerScreen; +import net.minecraft.realms.RealmsScreen; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedScreen { + + private final Screen internal; + + public boolean isTitleScreen() { + return internal instanceof TitleScreen; + } + + public boolean isRealmsScreen() { + return internal instanceof RealmsScreen; + } + + public boolean isServerBrowserScreen() { + return internal instanceof JoinMultiplayerScreen; + } + + public boolean isLoadingScreen() { + return internal instanceof LevelLoadingScreen || internal instanceof ReceivingLevelScreen; + } + + public Screen toMojang() { + return internal; + } + +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/multiplayer/BridgedClientLevel.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/multiplayer/BridgedClientLevel.java new file mode 100644 index 0000000..4e7b60b --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/multiplayer/BridgedClientLevel.java @@ -0,0 +1,59 @@ +package com.hypherionmc.craterlib.nojang.client.multiplayer; + +import com.hypherionmc.craterlib.nojang.core.BridgedBlockPos; +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; +import com.hypherionmc.craterlib.utils.ChatUtils; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; +import net.minecraft.client.multiplayer.ClientLevel; +import org.jetbrains.annotations.Nullable; + +import java.util.concurrent.atomic.AtomicReference; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedClientLevel { + + private final ClientLevel internal; + + public boolean isClientSide() { + return internal.isClientSide(); + } + + public long getGameTime() { + return internal.getGameTime(); + } + + public long getDayTime() { + return internal.getDayTime(); + } + + public long dayTime() { + return internal.dayTime(); + } + + public boolean isRaining() { + return internal.isRaining(); + } + + public boolean isThundering() { + return internal.isThundering(); + } + + @Nullable + public ResourceIdentifier getDimensionKey() { + return ResourceIdentifier.fromMojang(internal.dimension().location()); + } + + @Nullable + public ResourceIdentifier getBiomeIdentifier(BridgedBlockPos onPos) { + AtomicReference identifier = new AtomicReference<>(null); + internal.getBiome(onPos.toMojang()).unwrap().ifLeft(b -> identifier.set(ResourceIdentifier.fromMojang(b.location()))); + return identifier.get(); + } + + @Nullable + public Component getDifficulty() { + return ChatUtils.mojangToAdventure(internal.getDifficulty().getDisplayName()); + } + +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/multiplayer/BridgedServerData.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/multiplayer/BridgedServerData.java new file mode 100644 index 0000000..c594872 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/multiplayer/BridgedServerData.java @@ -0,0 +1,40 @@ +package com.hypherionmc.craterlib.nojang.client.multiplayer; + +import com.hypherionmc.craterlib.utils.ChatUtils; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; +import net.minecraft.client.multiplayer.ServerData; +import net.minecraft.client.multiplayer.ServerStatusPinger; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedServerData { + + private final ServerData internal; + + public String name() { + return internal.name; + } + + public String ip() { + return internal.ip; + } + + public Component motd() { + return ChatUtils.mojangToAdventure(internal.motd); + } + + public int getMaxPlayers() { + if (!internal.pinged || internal.players == null) { + try { + new ServerStatusPinger().pingServer(internal, () -> {}); + } catch (Exception ignored) {} + } + + return internal.players == null ? 0 : internal.players.max(); + } + + public ServerData toMojang() { + return internal; + } + +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/server/BridgedIntegratedServer.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/server/BridgedIntegratedServer.java new file mode 100644 index 0000000..0ecacaf --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/server/BridgedIntegratedServer.java @@ -0,0 +1,19 @@ +package com.hypherionmc.craterlib.nojang.client.server; + +import lombok.RequiredArgsConstructor; +import net.minecraft.client.server.IntegratedServer; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedIntegratedServer { + + private final IntegratedServer internal; + + public String getLevelName() { + return internal.getWorldData().getLevelName(); + } + + public IntegratedServer toMojang() { + return internal; + } + +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/BridgedCommandSourceStack.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/BridgedCommandSourceStack.java new file mode 100644 index 0000000..5a15402 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/BridgedCommandSourceStack.java @@ -0,0 +1,22 @@ +package com.hypherionmc.craterlib.nojang.commands; + +import com.hypherionmc.craterlib.utils.ChatUtils; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; +import net.minecraft.commands.CommandSourceStack; + +import java.util.function.Supplier; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedCommandSourceStack { + + private final CommandSourceStack internal; + + public void sendSuccess(Supplier supplier, boolean bl) { + internal.sendSuccess(() -> ChatUtils.adventureToMojang(supplier.get()), bl); + } + + public CommandSourceStack toMojang() { + return internal; + } +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/BridgedFakePlayer.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/BridgedFakePlayer.java new file mode 100644 index 0000000..42b65cb --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/BridgedFakePlayer.java @@ -0,0 +1,55 @@ +package com.hypherionmc.craterlib.nojang.commands; + +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import com.hypherionmc.craterlib.utils.ChatUtils; +import net.minecraft.commands.CommandSource; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.network.chat.Component; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.phys.Vec2; +import net.minecraft.world.phys.Vec3; + +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Supplier; + +public abstract class BridgedFakePlayer { + + final MojangBridge internal; + + public BridgedFakePlayer(BridgedMinecraftServer server, int perm, String name) { + internal = new MojangBridge(server.toMojang(), perm, name, this::onSuccess, this::onError); + } + + public abstract void onSuccess(Supplier supplier, Boolean aBoolean); + + public void onError(net.kyori.adventure.text.Component component) { + this.onSuccess(() -> component, false); + } + + public CommandSourceStack toMojang() { + return internal; + } + + static class MojangBridge extends CommandSourceStack { + + private final BiConsumer, Boolean> successCallback; + public final Consumer errorCallback; + + MojangBridge(MinecraftServer server, int perm, String name, BiConsumer, Boolean> successCallback, Consumer errorCallback) { + super(CommandSource.NULL, Vec3.ZERO, Vec2.ZERO, server.overworld(), perm, name, Component.literal(name), server, null); + this.successCallback = successCallback; + this.errorCallback = errorCallback; + } + + @Override + public void sendSuccess(Supplier supplier, boolean bl) { + successCallback.accept(() -> ChatUtils.mojangToAdventure(supplier.get()), bl); + } + + @Override + public void sendFailure(Component arg) { + errorCallback.accept(ChatUtils.mojangToAdventure(arg)); + } + } +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/CommandsRegistry.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/CommandsRegistry.java new file mode 100644 index 0000000..37ad15b --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/CommandsRegistry.java @@ -0,0 +1,85 @@ +package com.hypherionmc.craterlib.nojang.commands; + +import com.hypherionmc.craterlib.api.commands.CraterCommand; +import com.hypherionmc.craterlib.nojang.authlib.BridgedGameProfile; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.TriConsumer; +import com.mojang.authlib.GameProfile; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.commands.arguments.GameProfileArgument; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class CommandsRegistry { + + public static final CommandsRegistry INSTANCE = new CommandsRegistry(); + + private final List commands = new ArrayList<>(); + + public void registerCommand(CraterCommand cmd) { + commands.add(cmd); + } + + public void registerCommands(CommandDispatcher stack) { + commands.forEach(cmd -> { + if (cmd.hasArguments()) { + CommandWithArguments.register(cmd, stack); + } else { + CommandWithoutArguments.register(cmd, stack); + } + }); + } + + static class CommandWithoutArguments { + + public static void register(CraterCommand cmd, CommandDispatcher dispatcher) { + LiteralArgumentBuilder command = Commands.literal(cmd.getCommandName()) + .requires(source -> source.hasPermission(cmd.getPermissionLevel())) + .executes(context -> { + cmd.getExecutor().accept(BridgedCommandSourceStack.of(context.getSource())); + return 1; + }); + + dispatcher.register(command); + } + + } + + @SuppressWarnings("unchecked") + static class CommandWithArguments { + + public static void register(CraterCommand cmd, CommandDispatcher dispatcher) { + LiteralArgumentBuilder command = Commands.literal(cmd.getCommandName()) + .requires(source -> source.hasPermission(cmd.getPermissionLevel())); + + cmd.getArguments().forEach((key, pair) -> command.then(Commands.argument(key, pair.getLeft()).executes(context -> { + + // This is FUCKING UGLY.... Need to improve this in the future + if (pair.getLeft() instanceof GameProfileArgument) { + Collection profiles = GameProfileArgument.getGameProfiles(context, key); + List bridgedGameProfiles = new ArrayList<>(); + + profiles.forEach(p -> bridgedGameProfiles.add(BridgedGameProfile.of(p))); + + ((TriConsumer, BridgedCommandSourceStack>) pair.getRight()) + .accept(BridgedPlayer.of(context.getSource().getPlayer()), bridgedGameProfiles, BridgedCommandSourceStack.of(context.getSource())); + return 1; + } + + return 1; + }))); + + dispatcher.register(command); + } + + } + +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/core/BridgedBlockPos.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/core/BridgedBlockPos.java new file mode 100644 index 0000000..bd49e2c --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/core/BridgedBlockPos.java @@ -0,0 +1,27 @@ +package com.hypherionmc.craterlib.nojang.core; + +import lombok.RequiredArgsConstructor; +import net.minecraft.core.BlockPos; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedBlockPos { + + private final BlockPos internal; + + public int getX() { + return internal.getX(); + } + + public int getY() { + return internal.getY(); + } + + public int getZ() { + return internal.getZ(); + } + + public BlockPos toMojang() { + return internal; + } + +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/nbt/BridgedCompoundTag.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/nbt/BridgedCompoundTag.java new file mode 100644 index 0000000..17a2d04 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/nbt/BridgedCompoundTag.java @@ -0,0 +1,49 @@ +package com.hypherionmc.craterlib.nojang.nbt; + +import lombok.RequiredArgsConstructor; +import net.minecraft.nbt.CompoundTag; + +import java.util.Set; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedCompoundTag { + + private final CompoundTag internal; + + public static BridgedCompoundTag empty() { + return new BridgedCompoundTag(new CompoundTag()); + } + + public BridgedCompoundTag getCompound(String key) { + return BridgedCompoundTag.of(internal.getCompound(key)); + } + + public Set getAllKeys() { + return internal.getAllKeys(); + } + + public String getString(String key) { + return internal.getString(key); + } + + public boolean getBoolean(String key) { + return internal.getBoolean(key); + } + + public void putString(String key, String value) { + internal.putString(key, value); + } + + public void put(String key, BridgedCompoundTag value) { + internal.put(key, value.toMojang()); + } + + public void putBoolean(String key, boolean value) { + internal.putBoolean(key, value); + } + + public CompoundTag toMojang() { + return internal; + } + +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/network/BridgedFriendlyByteBuf.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/network/BridgedFriendlyByteBuf.java new file mode 100644 index 0000000..a7bafd0 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/network/BridgedFriendlyByteBuf.java @@ -0,0 +1,34 @@ +package com.hypherionmc.craterlib.nojang.network; + +import com.hypherionmc.craterlib.nojang.nbt.BridgedCompoundTag; +import lombok.RequiredArgsConstructor; +import net.minecraft.network.FriendlyByteBuf; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedFriendlyByteBuf { + + private final FriendlyByteBuf internal; + + public BridgedCompoundTag readNbt() { + return BridgedCompoundTag.of(internal.readNbt()); + } + + public BridgedFriendlyByteBuf writeNbt(BridgedCompoundTag tag) { + internal.writeNbt(tag.toMojang()); + return BridgedFriendlyByteBuf.of(internal); + } + + public BridgedFriendlyByteBuf writeUtf(String value) { + internal.writeUtf(value); + return BridgedFriendlyByteBuf.of(internal); + } + + public String readUtf() { + return internal.readUtf(); + } + + public FriendlyByteBuf toMojang() { + return internal; + } + +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/package-info.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/package-info.java new file mode 100644 index 0000000..4c177a5 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/package-info.java @@ -0,0 +1,9 @@ +/** + * @author HypherionSA + * This package, called NoJang, exposes various wrapped API's. + * Using this api, a mod can essentially run on ANY minecraft version this library + * supports, from one code base. + * IMPORTANT NOTE: THESE API'S MUST NEVER EXPOSE ANY MINECRAFT CLASSES OR CODE!!!! + * THEY MUST ALWAYS BE HANDLED INTERNALLY AND ONLY RETURN WRAPPED VARIANTS + */ +package com.hypherionmc.craterlib.nojang; \ No newline at end of file diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/realmsclient/dto/BridgedRealmsServer.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/realmsclient/dto/BridgedRealmsServer.java new file mode 100644 index 0000000..dddf746 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/realmsclient/dto/BridgedRealmsServer.java @@ -0,0 +1,40 @@ +package com.hypherionmc.craterlib.nojang.realmsclient.dto; + +import com.mojang.realmsclient.dto.PlayerInfo; +import com.mojang.realmsclient.dto.RealmsServer; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedRealmsServer { + + private final RealmsServer internal; + + public String getName() { + return internal.getName(); + } + + public String getDescription() { + return internal.getDescription(); + } + + public String getWorldType() { + return internal.worldType.name(); + } + + public String getMinigameName() { + return internal.getMinigameName(); + } + + public String getMinigameImage() { + return internal.minigameImage; + } + + public long getPlayerCount() { + return internal.players.stream().filter(PlayerInfo::getOnline).count(); + } + + public RealmsServer toMojang() { + return internal; + } + +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/resources/ResourceIdentifier.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/resources/ResourceIdentifier.java new file mode 100644 index 0000000..9b848f4 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/resources/ResourceIdentifier.java @@ -0,0 +1,36 @@ +package com.hypherionmc.craterlib.nojang.resources; + +import net.minecraft.resources.ResourceLocation; + +public class ResourceIdentifier { + + private final ResourceLocation internal; + + public ResourceIdentifier(String namespace, String path) { + this.internal = new ResourceLocation(namespace, path); + } + + public ResourceIdentifier(String path) { + this.internal = new ResourceLocation(path); + } + + public String getNamespace() { + return internal.getNamespace(); + } + + public String getPath() { + return internal.getPath(); + } + + public String getString() { + return internal.toString(); + } + + public static ResourceIdentifier fromMojang(ResourceLocation location) { + return new ResourceIdentifier(location.getNamespace(), location.getPath()); + } + + public ResourceLocation toMojang() { + return internal; + } +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/server/BridgedMinecraftServer.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/server/BridgedMinecraftServer.java new file mode 100644 index 0000000..cb818b7 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/server/BridgedMinecraftServer.java @@ -0,0 +1,91 @@ +package com.hypherionmc.craterlib.nojang.server; + +import com.hypherionmc.craterlib.nojang.authlib.BridgedGameProfile; +import com.hypherionmc.craterlib.nojang.commands.BridgedFakePlayer; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.ChatUtils; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; +import net.minecraft.SharedConstants; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.players.UserBanListEntry; +import net.minecraft.server.players.UserWhiteListEntry; + +import java.util.ArrayList; +import java.util.List; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedMinecraftServer { + + private final MinecraftServer internal; + + public boolean isUsingWhitelist() { + return internal.getPlayerList().isUsingWhitelist(); + } + + public int getPlayerCount() { + return internal.getPlayerList().getPlayerCount(); + } + + public int getMaxPlayers() { + return internal.getPlayerList().getMaxPlayers(); + } + + public String getServerModName() { + return internal.getServerModName(); + } + + public String getName() { + return SharedConstants.getCurrentVersion().getName(); + } + + public boolean usesAuthentication() { + return internal.usesAuthentication(); + } + + public void broadcastSystemMessage(Component text, boolean bl) { + internal.getPlayerList().broadcastSystemMessage(ChatUtils.adventureToMojang(text), bl); + } + + public boolean isPlayerBanned(BridgedGameProfile profile) { + return internal.getPlayerList().getBans().isBanned(profile.toMojang()); + } + + public void whitelistPlayer(BridgedGameProfile gameProfile) { + if (!internal.getPlayerList().isUsingWhitelist()) + return; + + internal.getPlayerList().getWhiteList().add(new UserWhiteListEntry(gameProfile.toMojang())); + } + + public void unWhitelistPlayer(BridgedGameProfile gameProfile) { + if (!internal.getPlayerList().isUsingWhitelist()) + return; + + internal.getPlayerList().getWhiteList().remove(new UserWhiteListEntry(gameProfile.toMojang())); + } + + public List getPlayers() { + List profiles = new ArrayList<>(); + + if (internal.getPlayerList() == null) + return profiles; + + internal.getPlayerList().getPlayers().forEach(p -> profiles.add(BridgedPlayer.of(p))); + + return profiles; + } + + public void banPlayer(BridgedGameProfile profile) { + internal.getPlayerList().getBans().add(new UserBanListEntry(profile.toMojang())); + } + + public void executeCommand(BridgedMinecraftServer server, BridgedFakePlayer player, String command) { + internal.getCommands().performPrefixedCommand(player.toMojang(), command); + } + + public MinecraftServer toMojang() { + return internal; + } + +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/world/entity/player/BridgedPlayer.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/world/entity/player/BridgedPlayer.java new file mode 100644 index 0000000..8aa6d49 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/nojang/world/entity/player/BridgedPlayer.java @@ -0,0 +1,63 @@ +package com.hypherionmc.craterlib.nojang.world.entity.player; + +import com.hypherionmc.craterlib.nojang.authlib.BridgedGameProfile; +import com.hypherionmc.craterlib.nojang.core.BridgedBlockPos; +import com.hypherionmc.craterlib.utils.ChatUtils; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraft.world.entity.player.Player; +import org.jetbrains.annotations.Nullable; + +import java.util.UUID; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedPlayer { + + private final Player internal; + + public Component getDisplayName() { + return ChatUtils.mojangToAdventure(internal.getDisplayName()); + } + + public Component getName() { + return ChatUtils.mojangToAdventure(internal.getName()); + } + + public UUID getUUID() { + return internal.getUUID(); + } + + public String getStringUUID() { + return internal.getStringUUID(); + } + + public BridgedGameProfile getGameProfile() { + return BridgedGameProfile.of(internal.getGameProfile()); + } + + public boolean isServerPlayer() { + return internal instanceof ServerPlayer; + } + + public Player toMojang() { + return internal; + } + + public BridgedBlockPos getOnPos() { + return BridgedBlockPos.of(internal.getOnPos()); + } + + @Nullable + public ServerGamePacketListenerImpl getConnection() { + if (isServerPlayer()) { + return ((ServerPlayer) internal).connection; + } + return null; + } + + public ServerPlayer toMojangServerPlayer() { + return (ServerPlayer) internal; + } +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java new file mode 100644 index 0000000..7bbcfd1 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java @@ -0,0 +1,96 @@ +package com.hypherionmc.craterlib.utils; + +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; +import me.hypherionmc.mcdiscordformatter.discord.DiscordSerializer; +import me.hypherionmc.mcdiscordformatter.minecraft.MinecraftSerializer; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import net.kyori.adventure.text.serializer.json.JSONOptions; +import net.minecraft.ChatFormatting; +import net.minecraft.SharedConstants; +import net.minecraft.Util; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.Style; + +public class ChatUtils { + + private static final GsonComponentSerializer adventureSerializer = GsonComponentSerializer.builder().options( + JSONOptions.byDataVersion().at(SharedConstants.getCurrentVersion().getDataVersion().getVersion()) + ).build(); + + public static Component adventureToMojang(net.kyori.adventure.text.Component inComponent) { + final String serialised = adventureSerializer.serialize(inComponent); + return Component.Serializer.fromJson(serialised); + } + + public static net.kyori.adventure.text.Component mojangToAdventure(Component inComponent) { + final String serialised = Component.Serializer.toJson(inComponent); + return adventureSerializer.deserialize(serialised); + } + + // Some text components contain duplicate text, resulting in duplicate messages + // sent back to discord. This should help fix those issues + public static Component safeCopy(Component inComponent) { + String value = inComponent.getString(); + Style style = inComponent.getStyle(); + return Component.literal(value).withStyle(style); + } + + public static String strip(String inString, String... toStrip) { + String finalString = inString; + + for (String strip : toStrip) { + if (finalString.startsWith(strip)) + finalString = finalString.replaceFirst(strip, ""); + + if (finalString.startsWith(" ")) + finalString = finalString.replaceFirst(" ", ""); + } + + return finalString; + } + + public static String resolve(net.kyori.adventure.text.Component component, boolean formatted) { + Component c = adventureToMojang(component); + String returnVal = ChatFormatting.stripFormatting(c.getString()); + + if (formatted) { + returnVal = DiscordSerializer.INSTANCE.serialize(safeCopy(c).copy()); + } + + return returnVal; + } + + public static net.kyori.adventure.text.Component resolve(String component, boolean formatted) { + Component returnVal = Component.literal(component); + if (formatted) { + returnVal = MinecraftSerializer.INSTANCE.serialize(component); + } + + return mojangToAdventure(returnVal); + } + + public static net.kyori.adventure.text.Component getTooltipTitle(String key) { + return net.kyori.adventure.text.Component.text(NamedTextColor.YELLOW + net.kyori.adventure.text.Component.translatable(key).key()); + } + + public static String resolveTranslation(String key) { + return net.kyori.adventure.text.Component.translatable(key).key(); + } + + public static net.kyori.adventure.text.Component getTranslation(String key) { + return net.kyori.adventure.text.Component.translatable(key); + } + + public static net.kyori.adventure.text.Component makeComponent(String text) { + return net.kyori.adventure.text.Component.translatable(text); + } + + public static net.kyori.adventure.text.Component getBiomeName(ResourceIdentifier identifier) { + if (identifier == null) + return net.kyori.adventure.text.Component.text("Unknown"); + + return mojangToAdventure(Component.translatable(Util.makeDescriptionId("biome", identifier.toMojang()))); + } + +} diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/utils/InternalServiceUtil.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/utils/InternalServiceUtil.java new file mode 100644 index 0000000..2305d14 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/utils/InternalServiceUtil.java @@ -0,0 +1,27 @@ +package com.hypherionmc.craterlib.utils; + +import com.hypherionmc.craterlib.CraterConstants; + +import java.util.ServiceLoader; + +/** + * @author HypherionSA + * Utility class to handle SPI loading + */ +public class InternalServiceUtil { + + /** + * Try to load a service + * + * @param clazz The service class type to load + * @return The loaded class + */ + public static T load(Class clazz) { + final T loadedService = ServiceLoader.load(clazz) + .findFirst() + .orElseThrow(() -> new NullPointerException("Failed to load service for " + clazz.getName())); + CraterConstants.LOG.debug("Loaded {} for service {}", loadedService, clazz); + return loadedService; + } + +} \ No newline at end of file diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/utils/OptifineUtils.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/utils/OptifineUtils.java new file mode 100644 index 0000000..8a06da2 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/utils/OptifineUtils.java @@ -0,0 +1,46 @@ +package com.hypherionmc.craterlib.utils; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * @author HypherionSA + * Utility class for Optifine compatibility + */ +public class OptifineUtils { + + private static final boolean hasOptifine = checkOptifine(); + + private static boolean checkOptifine() { + try { + Class ofConfigClass = Class.forName("net.optifine.Config"); + return true; + } catch (ClassNotFoundException e) { + // Optifine is probably not present. Ignore the error + return false; + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } + + public static boolean isRenderRegions() { + try { + Class ofConfigClass = Class.forName("net.optifine.Config"); + Method rrField = ofConfigClass.getMethod("isRenderRegions"); + return (boolean) rrField.invoke(null); + } catch (ClassNotFoundException | InvocationTargetException | NoSuchMethodException | + IllegalAccessException e) { + // Optifine is probably not present. Ignore the error + return false; + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } + + public static boolean hasOptifine() { + return hasOptifine; + } + +} \ No newline at end of file diff --git a/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/utils/TriConsumer.java b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/utils/TriConsumer.java new file mode 100644 index 0000000..77f7309 --- /dev/null +++ b/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/utils/TriConsumer.java @@ -0,0 +1,5 @@ +package com.hypherionmc.craterlib.utils; + +public interface TriConsumer { + void accept(T t, U u, V v); +} diff --git a/1.20.2/Common/src/main/resources/assets/craterlib/lang/en_us.json b/1.20.2/Common/src/main/resources/assets/craterlib/lang/en_us.json new file mode 100644 index 0000000..03115d9 --- /dev/null +++ b/1.20.2/Common/src/main/resources/assets/craterlib/lang/en_us.json @@ -0,0 +1,8 @@ +{ + "t.clc.opensubconfig": "Open Config", + "t.clc.save": "Save", + "t.clc.cancel_discard": "Discard", + "t.clc.quit_config": "Unsaved Changes", + "t.clc.quit_config_sure": "You have unsaved config changes. Are you sure you want to discard them?", + "t.clc.quit_discard": "Quit & Discard" +} diff --git a/1.20.2/Common/src/main/resources/craterlib.mixins.json b/1.20.2/Common/src/main/resources/craterlib.mixins.json new file mode 100644 index 0000000..1a739db --- /dev/null +++ b/1.20.2/Common/src/main/resources/craterlib.mixins.json @@ -0,0 +1,24 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "com.hypherionmc.craterlib.mixin", + "compatibilityLevel": "JAVA_17", + "mixins": [ + ], + "client": [ + "ChatInputSuggestorMixin", + "events.PlayerMixin", + "events.client.ClientLevelMixin", + "events.client.MinecraftMixin", + "events.client.RealmsMainScreenMixin" + ], + "server": [ + "events.CommandMixin", + "events.PlayerAdvancementsMixin", + "events.PlayerListMixin", + "events.ServerPlayerMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/1.20.2/Common/src/main/resources/pack.mcmeta b/1.20.2/Common/src/main/resources/pack.mcmeta new file mode 100644 index 0000000..263d366 --- /dev/null +++ b/1.20.2/Common/src/main/resources/pack.mcmeta @@ -0,0 +1,6 @@ +{ + "pack": { + "description": "${mod_name}", + "pack_format": 18 + } +} diff --git a/1.20.2/Fabric/build.gradle b/1.20.2/Fabric/build.gradle new file mode 100644 index 0000000..c3269e2 --- /dev/null +++ b/1.20.2/Fabric/build.gradle @@ -0,0 +1,126 @@ +archivesBaseName = "${mod_name.replace(" ", "")}-Fabric-${minecraft_version}" + +dependencies { + // Core + modImplementation "net.fabricmc.fabric-api:fabric-api:${fabric_api}" + + // Compat + modImplementation("com.terraformersmc:modmenu:${mod_menu_version}") { + exclude(group: "net.fabricmc.fabric-api") + } + + modImplementation "maven.modrinth:fabrictailor:${fabrictailor}" + modImplementation "maven.modrinth:vanish:${vanish}" + + // Do not edit or remove + implementation project(":Common") +} + +shadowJar { + from sourceSets.main.output + configurations = [project.configurations.shade] + + dependencies { + exclude(dependency('com.google.code.gson:.*')) + + relocate 'me.hypherionmc.moonconfig', 'shadow.hypherionmc.moonconfig' + relocate 'me.hypherionmc.mcdiscordformatter', 'shadow.hypherionmc.mcdiscordformatter' + relocate 'net.kyori', 'shadow.kyori' + } + + setArchiveClassifier('dev-shadow') +} + +/** + * =============================================================================== + * = DO NOT EDIT BELOW THIS LINE UNLESS YOU KNOW WHAT YOU ARE DOING = + * =============================================================================== + */ + +unimined.minecraft { + fabric { + loader fabric_loader + } +} + +remapJar { + inputFile.set shadowJar.archiveFile + dependsOn shadowJar + archiveClassifier.set null +} + +jar { + archiveClassifier.set "dev" +} + +processResources { + from project(":Common").sourceSets.main.resources + def buildProps = project.properties.clone() + + filesMatching(['fabric.mod.json']) { + expand buildProps + } +} + +compileTestJava.enabled = false + +tasks.withType(JavaCompile).configureEach { + source(project(":Common").sourceSets.main.allSource) +} + +/** + * Publishing Config + */ +publishing { + publications { + mavenJava(MavenPublication) { + artifactId project.archivesBaseName + from components.java + + artifact(remapJar) { + builtBy remapJar + } + + pom.withXml { + Node pomNode = asNode() + pomNode.dependencies.'*'.findAll() { + it.artifactId.text() == 'regutils-joined-fabric' || + it.artifactId.text() == 'core' || + it.artifactId.text() == 'toml' + }.each() { + it.parent().remove(it) + } + } + } + } + + repositories { + maven rootProject.orion.getPublishingMaven() + } +} + +publisher { + apiKeys { + modrinth(System.getenv("MODRINTH_TOKEN")) + curseforge(System.getenv("CURSE_TOKEN")) + } + + setCurseID(curse_id) + setModrinthID(modrinth_id) + setVersionType("release") + setChangelog("https://raw.githubusercontent.com/hypherionmc/changelogs/main/craterlib/changelog-fabric.md") + setProjectVersion("${minecraft_version}-${project.version}") + setDisplayName("[FABRIC/QUILT 1.20.2] CraterLib - ${project.version}") + setGameVersions("1.20.2") + setLoaders("fabric", "quilt") + setArtifact(remapJar) + setCurseEnvironment("both") + + modrinthDepends { + required("fabric-api") + } + + curseDepends { + required("fabric-api") + } +} diff --git a/1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/CraterLibInitializer.java b/1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/CraterLibInitializer.java new file mode 100644 index 0000000..c66c210 --- /dev/null +++ b/1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/CraterLibInitializer.java @@ -0,0 +1,42 @@ +package com.hypherionmc.craterlib; + +import com.hypherionmc.craterlib.api.events.server.CraterRegisterCommandEvent; +import com.hypherionmc.craterlib.api.events.server.CraterServerLifecycleEvent; +import com.hypherionmc.craterlib.common.FabricCommonPlatform; +import com.hypherionmc.craterlib.compat.Vanish; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.core.networking.CraterPacketNetwork; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.core.platform.ModloaderEnvironment; +import com.hypherionmc.craterlib.network.CraterFabricNetworkHandler; +import com.hypherionmc.craterlib.nojang.commands.CommandsRegistry; +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; + +public class CraterLibInitializer implements ModInitializer { + + @Override + public void onInitialize() { + new CraterPacketNetwork(new CraterFabricNetworkHandler(PacketSide.SERVER)); + CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> { + CraterEventBus.INSTANCE.postEvent(new CraterRegisterCommandEvent()); + CommandsRegistry.INSTANCE.registerCommands(dispatcher); + }); + + + ServerLifecycleEvents.SERVER_STARTING.register(server -> { + FabricCommonPlatform.server = server; + CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Starting(BridgedMinecraftServer.of(server))); + }); + + ServerLifecycleEvents.SERVER_STARTED.register(li -> CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Started(BridgedMinecraftServer.of(li)))); + ServerLifecycleEvents.SERVER_STOPPING.register(server -> CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Stopping(BridgedMinecraftServer.of(server)))); + ServerLifecycleEvents.SERVER_STOPPED.register(server -> CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Stopped(BridgedMinecraftServer.of(server)))); + + if (ModloaderEnvironment.INSTANCE.isModLoaded("melius-vanish")) { + Vanish.register(); + } + } +} diff --git a/1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/CraterLibModMenuIntegration.java b/1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/CraterLibModMenuIntegration.java new file mode 100644 index 0000000..6615352 --- /dev/null +++ b/1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/CraterLibModMenuIntegration.java @@ -0,0 +1,30 @@ +package com.hypherionmc.craterlib; + +import com.hypherionmc.craterlib.client.gui.config.CraterConfigScreen; +import com.hypherionmc.craterlib.core.config.ConfigController; +import com.hypherionmc.craterlib.core.config.ModuleConfig; +import com.hypherionmc.craterlib.core.config.annotations.NoConfigScreen; +import com.terraformersmc.modmenu.api.ConfigScreenFactory; +import com.terraformersmc.modmenu.api.ModMenuApi; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author HypherionSA + */ +public class CraterLibModMenuIntegration implements ModMenuApi { + + @Override + public Map> getProvidedConfigScreenFactories() { + Map> configScreens = new HashMap<>(); + + ConfigController.getMonitoredConfigs().forEach((conf, watcher) -> { + if (!conf.getClass().isAnnotationPresent(NoConfigScreen.class)) { + configScreens.put(((ModuleConfig) conf).getModId(), screen -> new CraterConfigScreen((ModuleConfig) conf, screen)); + } + }); + + return configScreens; + } +} diff --git a/1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/client/CraterLibClientInitializer.java b/1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/client/CraterLibClientInitializer.java new file mode 100644 index 0000000..b5ec5b1 --- /dev/null +++ b/1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/client/CraterLibClientInitializer.java @@ -0,0 +1,27 @@ +package com.hypherionmc.craterlib.client; + +import com.hypherionmc.craterlib.api.events.client.CraterClientTickEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.core.networking.CraterPacketNetwork; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.network.CraterFabricNetworkHandler; +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedClientLevel; +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; + +public class CraterLibClientInitializer implements ClientModInitializer { + + @Override + public void onInitializeClient() { + new CraterPacketNetwork(new CraterFabricNetworkHandler(PacketSide.CLIENT)); + ClientTickEvents.START_CLIENT_TICK.register((listener) -> { + if (listener.level == null) + return; + + CraterClientTickEvent event = new CraterClientTickEvent(BridgedClientLevel.of(listener.level)); + CraterEventBus.INSTANCE.postEvent(event); + }); + + CraterEventBus.INSTANCE.registerEventListener(CraterLibClientInitializer.class); + } +} diff --git a/1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/client/FabricClientPlatform.java b/1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/client/FabricClientPlatform.java new file mode 100644 index 0000000..7852f12 --- /dev/null +++ b/1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/client/FabricClientPlatform.java @@ -0,0 +1,34 @@ +package com.hypherionmc.craterlib.client; + +import com.hypherionmc.craterlib.core.platform.ClientPlatform; +import com.hypherionmc.craterlib.nojang.client.BridgedMinecraft; +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedClientLevel; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraft.client.Minecraft; +import net.minecraft.network.Connection; + +/** + * @author HypherionSA + */ +public class FabricClientPlatform implements ClientPlatform { + + @Override + public BridgedMinecraft getClientInstance() { + return new BridgedMinecraft(); + } + + @Override + public BridgedPlayer getClientPlayer() { + return BridgedPlayer.of(Minecraft.getInstance().player); + } + + @Override + public BridgedClientLevel getClientLevel() { + return BridgedClientLevel.of(Minecraft.getInstance().level); + } + + @Override + public Connection getClientConnection() { + return Minecraft.getInstance().getConnection().getConnection(); + } +} diff --git a/1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricCommonPlatform.java b/1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricCommonPlatform.java new file mode 100644 index 0000000..c1a30c7 --- /dev/null +++ b/1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricCommonPlatform.java @@ -0,0 +1,18 @@ +package com.hypherionmc.craterlib.common; + +import com.hypherionmc.craterlib.core.platform.CommonPlatform; +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import net.minecraft.server.MinecraftServer; + +/** + * @author HypherionSA + */ +public class FabricCommonPlatform implements CommonPlatform { + + public static MinecraftServer server; + + @Override + public BridgedMinecraftServer getMCServer() { + return BridgedMinecraftServer.of(server); + } +} diff --git a/1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricCompatHelper.java b/1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricCompatHelper.java new file mode 100644 index 0000000..dbcaf63 --- /dev/null +++ b/1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricCompatHelper.java @@ -0,0 +1,23 @@ +package com.hypherionmc.craterlib.common; + +import com.hypherionmc.craterlib.compat.FabricTailor; +import com.hypherionmc.craterlib.compat.Vanish; +import com.hypherionmc.craterlib.core.platform.CompatUtils; +import com.hypherionmc.craterlib.core.platform.ModloaderEnvironment; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; + +public class FabricCompatHelper implements CompatUtils { + + @Override + public boolean isPlayerActive(BridgedPlayer player) { + if (!ModloaderEnvironment.INSTANCE.isModLoaded("melius-vanish")) + return true; + + return Vanish.isPlayerVanished(player.toMojangServerPlayer()); + } + + @Override + public String getSkinUUID(BridgedPlayer player) { + return FabricTailor.getTailorSkin(player.toMojangServerPlayer()); + } +} diff --git a/1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricLoaderHelper.java b/1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricLoaderHelper.java new file mode 100644 index 0000000..95f5c42 --- /dev/null +++ b/1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricLoaderHelper.java @@ -0,0 +1,69 @@ +package com.hypherionmc.craterlib.common; + +import com.hypherionmc.craterlib.core.platform.Environment; +import com.hypherionmc.craterlib.core.platform.ModloaderEnvironment; +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.SharedConstants; +import net.minecraft.client.Minecraft; + +import java.io.File; + +/** + * @author HypherionSA + * @date 07/08/2022 + */ +public class FabricLoaderHelper implements ModloaderEnvironment { + + @Override + public boolean isFabric() { + return true; + } + + @Override + public String getGameVersion() { + return SharedConstants.VERSION_STRING; + } + + @Override + public File getGameFolder() { + return Minecraft.getInstance().gameDirectory; + } + + @Override + public File getConfigFolder() { + return FabricLoader.getInstance().getConfigDir().toFile(); + } + + @Override + public File getModsFolder() { + return new File(FabricLoader.getInstance().getGameDir().toString() + File.separator + "mods"); + } + + @Override + public Environment getEnvironment() { + switch (FabricLoader.getInstance().getEnvironmentType()) { + case SERVER -> { + return Environment.SERVER; + } + case CLIENT -> { + return Environment.CLIENT; + } + } + return Environment.UNKNOWN; + } + + @Override + public boolean isModLoaded(String modid) { + return FabricLoader.getInstance().isModLoaded(modid); + } + + @Override + public boolean isDevEnv() { + return FabricLoader.getInstance().isDevelopmentEnvironment(); + } + + @Override + public int getModCount() { + return FabricLoader.getInstance().getAllMods().size(); + } +} diff --git a/1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/compat/FabricTailor.java b/1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/compat/FabricTailor.java new file mode 100644 index 0000000..b95a7d7 --- /dev/null +++ b/1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/compat/FabricTailor.java @@ -0,0 +1,23 @@ +package com.hypherionmc.craterlib.compat; + +import com.hypherionmc.craterlib.core.platform.ModloaderEnvironment; +import net.minecraft.server.level.ServerPlayer; +import org.samo_lego.fabrictailor.casts.TailoredPlayer; +public class FabricTailor { + + public static String getTailorSkin(ServerPlayer player) { + if (!ModloaderEnvironment.INSTANCE.isModLoaded("fabrictailor")) + return player.getStringUUID(); + + try { + if (player instanceof TailoredPlayer tp) { + return tp.getSkinId(); + } + } catch (Exception e) { + e.printStackTrace(); + } + + return player.getStringUUID(); + } + +} \ No newline at end of file diff --git a/1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java b/1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java new file mode 100644 index 0000000..cc6c1e0 --- /dev/null +++ b/1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java @@ -0,0 +1,25 @@ +package com.hypherionmc.craterlib.compat; + +import com.hypherionmc.craterlib.api.events.server.CraterPlayerEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import me.drex.vanish.api.VanishAPI; +import me.drex.vanish.api.VanishEvents; +import net.minecraft.server.level.ServerPlayer; + +public class Vanish { + + public static void register() { + VanishEvents.VANISH_EVENT.register((serverPlayer, b) -> { + if (b) { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerEvent.PlayerLoggedOut(BridgedPlayer.of(serverPlayer))); + } else { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerEvent.PlayerLoggedIn(BridgedPlayer.of(serverPlayer))); + } + }); + } + + public static boolean isPlayerVanished(ServerPlayer player) { + return VanishAPI.isVanished(player); + } +} diff --git a/1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java b/1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java new file mode 100644 index 0000000..4a7d904 --- /dev/null +++ b/1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java @@ -0,0 +1,36 @@ +package com.hypherionmc.craterlib.mixin; + +import com.hypherionmc.craterlib.api.events.server.CraterServerChatEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.ChatUtils; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.PlayerChatMessage; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.FilteredText; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(value = ServerGamePacketListenerImpl.class, priority = Integer.MIN_VALUE) +public class ServerGamePacketListenerImplMixin { + + @Shadow + public ServerPlayer player; + + @Inject( + method = "lambda$handleChat$6", + at = @At("HEAD"), + cancellable = true + ) + private void injectChatEvent(PlayerChatMessage arg, Component arg2, FilteredText arg3, CallbackInfo ci) { + CraterServerChatEvent event = new CraterServerChatEvent(BridgedPlayer.of(this.player), arg.decoratedContent().getString(), ChatUtils.mojangToAdventure(arg.decoratedContent())); + CraterEventBus.INSTANCE.postEvent(event); + if (event.wasCancelled()) + ci.cancel(); + } + +} diff --git a/1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/TutorialMixin.java b/1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/TutorialMixin.java new file mode 100644 index 0000000..f9eaa50 --- /dev/null +++ b/1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/TutorialMixin.java @@ -0,0 +1,24 @@ +package com.hypherionmc.craterlib.mixin; + +import com.hypherionmc.craterlib.api.events.client.LateInitEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.client.BridgedMinecraft; +import com.hypherionmc.craterlib.nojang.client.BridgedOptions; +import net.minecraft.client.Minecraft; +import net.minecraft.client.Options; +import net.minecraft.client.tutorial.Tutorial; +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(Tutorial.class) +public class TutorialMixin { + + @Inject(method = "", at = @At("RETURN")) + private void injectEarlyInitEvent(Minecraft minecraft, Options options, CallbackInfo ci) { + LateInitEvent event = new LateInitEvent(new BridgedMinecraft(), BridgedOptions.of(options)); + CraterEventBus.INSTANCE.postEvent(event); + } + +} diff --git a/1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java b/1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java new file mode 100644 index 0000000..b3014a3 --- /dev/null +++ b/1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java @@ -0,0 +1,81 @@ +package com.hypherionmc.craterlib.network; + +import com.hypherionmc.craterlib.CraterConstants; +import com.hypherionmc.craterlib.core.networking.PacketRegistry; +import com.hypherionmc.craterlib.core.networking.data.PacketContext; +import com.hypherionmc.craterlib.core.networking.data.PacketHolder; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.nojang.network.BridgedFriendlyByteBuf; +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.minecraft.network.FriendlyByteBuf; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public class CraterFabricNetworkHandler extends PacketRegistry { + + private final Map, Message> CHANNELS = new HashMap(); + + public CraterFabricNetworkHandler(PacketSide side) { + super(side); + } + + protected void registerPacket(PacketHolder holder) { + if (CHANNELS.get(holder.messageType()) == null) { + CHANNELS.put(holder.messageType(), new Message<>(holder.type(), holder.encoder())); + + if (PacketSide.CLIENT.equals(this.side)) { + ClientPlayNetworking.registerGlobalReceiver(holder.type().toMojang(), ((client, listener, buf, responseSender) -> { + buf.readByte(); + T message = holder.decoder().apply(BridgedFriendlyByteBuf.of(buf)); + client.execute(() -> holder.handler().accept(new PacketContext<>(message, PacketSide.CLIENT))); + })); + } else { + + ServerPlayNetworking.registerGlobalReceiver(holder.type().toMojang(), ((server, player, listener, buf, responseSender) -> { + buf.readByte(); + T message = holder.decoder().apply(BridgedFriendlyByteBuf.of(buf)); + server.execute(() -> holder.handler().accept(new PacketContext<>(BridgedPlayer.of(player), message, PacketSide.SERVER))); + })); + } + + } else { + CraterConstants.LOG.error("Trying to register duplicate packet for type {}", holder.messageType()); + } + } + + public void sendToServer(T packet) { + this.sendToServer(packet, false); + } + + public void sendToServer(T packet, boolean ignoreCheck) { + Message message = (Message) CHANNELS.get(packet.getClass()); + + if (ClientPlayNetworking.canSend(message.id().toMojang()) || ignoreCheck) { + FriendlyByteBuf buf = PacketByteBufs.create(); + buf.writeByte(0); + message.encoder().accept(packet, BridgedFriendlyByteBuf.of(buf)); + ClientPlayNetworking.send(message.id().toMojang(), buf); + } + } + + public void sendToClient(T packet, BridgedPlayer player) { + Message message = (Message) CHANNELS.get(packet.getClass()); + if (ServerPlayNetworking.canSend(player.toMojangServerPlayer(), message.id().toMojang())) { + FriendlyByteBuf buf = PacketByteBufs.create(); + buf.writeByte(0); + message.encoder().accept(packet, BridgedFriendlyByteBuf.of(buf)); + ServerPlayNetworking.send(player.toMojangServerPlayer(), message.id().toMojang(), buf); + } + } + + public record Message(ResourceIdentifier id, BiConsumer encoder) { } +} diff --git a/1.20.2/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform b/1.20.2/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform new file mode 100644 index 0000000..a78d9e5 --- /dev/null +++ b/1.20.2/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform @@ -0,0 +1 @@ +com.hypherionmc.craterlib.client.FabricClientPlatform diff --git a/1.20.2/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform b/1.20.2/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform new file mode 100644 index 0000000..9a2fdb0 --- /dev/null +++ b/1.20.2/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform @@ -0,0 +1 @@ +com.hypherionmc.craterlib.common.FabricCommonPlatform diff --git a/1.20.2/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils b/1.20.2/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils new file mode 100644 index 0000000..62f79a8 --- /dev/null +++ b/1.20.2/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils @@ -0,0 +1 @@ +com.hypherionmc.craterlib.common.FabricCompatHelper \ No newline at end of file diff --git a/1.20.2/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment b/1.20.2/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment new file mode 100644 index 0000000..9a1fb33 --- /dev/null +++ b/1.20.2/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment @@ -0,0 +1 @@ +com.hypherionmc.craterlib.common.FabricLoaderHelper diff --git a/1.20.2/Fabric/src/main/resources/assets/craterlib/craterlib_logo.png b/1.20.2/Fabric/src/main/resources/assets/craterlib/craterlib_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..ce0159cc4aa4d823ea354042e531b4522d6dfead GIT binary patch literal 49343 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 z z0_4I}B3{&0Z2$_UK|l6$_F5GbB1!e;?<`^ga;fFGMz-Se^MAW>Keh~s)aWsZza-Wa zh8FX{!yiI)Ljp=gIiG3cbs*3I7#lyPS*q6+XL$;krfa|T{j!X7NO%9Jw8jjQ^oGyb zuB>(Ay2y58m-xwl&;PxSaJT2vTCB#G6Yc?K6DubK#C89Og8@02Yf7+4Vmz5*NEo{& z&>W3XcH_uv{$Z45Oo+-NT$_6P>JUFKFv(gRqcz%^lx6572_M)nTWIO~h}&HFE{_}5 z8R|z4_}lCMQBU{Gd3?a4|9JCe_ew+2=R30OVOp1-?E`3=QwNM{jwLdE9Frn+%Q*$o zY-BRKU036=wxizwcph`BWI3Q^ms~Qk*&UewhP~N@df3sHJJT~7s=j`y^C&-a*BQz{ zUfNXagXU($%NqlFZDuykU1X#WZ_ow}eCTYw!3sFi-uAbIrzSa$1$QWY3^E|wM}@Ol z4BW+h^o;(}F`L>JY_v@ev&zsG194dotf7rHJTNeAQ~pNjVns}f!B?0szcS39HePIg z_wxP*(=Xeb$9Y5fV_b73G_il zXrSUx5E;tj(;!?eRdU+o*>h&72`G1&OU4{v4 zBM69zj5>>~lqkyBnHz9%3h2rC>P0E7M#!;M%}dCZ3{P zesubLp#ul3fqk?ZFeI+q%=?jY2glsYA>B6M-r!HqjRTSV;N0e=ulsLzKQi3rop&z3 zH;OHYJqan7<>>8y!_AGE$IU@Ct?|tdVpjnV&HVtf9S8T{lr1>M%n&8bazr#QxHe;< z+0=m4Q#3_|K%w|k&#HOT3hb1$#>HuRPo&*ju2UQcx&%6aH7J-;5;RG{r)uU|2ur7R z(9>i-B`X<0L0r?4>o&#P zAUra7SR&%Gp~?Ft&+{F)fCUjY&8x$@3;BPe^LC^=tG9csyL&Aa9?S$4uT%ye+iIRG zmNV!U42y%)FSuyl>HuNdKC#D;*w?j32J-D4$sK;bGQ_{n;$Fc3>RZO30i&ui2pxGX zWkF_hWr~P{+z+q`$HJnXo|lHlIC%I%qQ&U2@8ndyUDf(v9?@e&A!yQ(7_6Go$Q)CRNo z9}@w|%_mrl+}ioP`BSJ;eDly8`El>9JJbC6Oal#U0*u;%KWdR%aLwqIspxB2oF^oY z#<*A`D{k!|yW)obE;hb%qmIC^-Dk{*)?Cv2*^%OwFfD01CKw_Ee{_r9cIXAJ{p4oSiu;rX`J@;Ts29_--I|12eLb5ozFTzJf7R(FQjkK+59+`Nyxt6XV$p zS+d9cnNUyQ2jY6rjJQ~!HTUrn)&T}(-OnA^@3h@jP8_D{6MOm!f8b#LzJSUAKT4)ha9-WL$F&}@MME_!hG1YG)Ju~6e<7)tbYYw$a}uw3feLj zHx_9d#XnnHdHyK@3==#-i3G3?7`F&F6jg!m%^~WgRIMGJz+9SqhCy{7^V?Zr5lrqe zE*E2krwZF1&g7r>TV#?~ynBjY*@dP1OJ24(I#3nde2E^Mf%S~Ot|VwY0TP)j#oS7j zJ+LfR;dC>Z-J!ngPS)g53T^@_Ggh}qvR-&jy(V^V1`b@pJwKryM}3{+h3=_~c}w-s zmrqf)%trzjXpQ5odUbjqhSM?c7sX1S+g|kKjAvxwXnt!vu_25Ll$(I_R&0kiUK{sw zYkr`_S;cu^>MG=3rMC5aD#ocTL)YtiF6f6-DAu!`P4ub4xi}K95yQJB+#^=6CELF} ze9n~5+AX3lb{@H9HcrSnT=7O@5qQoV^BU}f=7Rso;Tf6G-plzq<9#hMqe1%4hm^i> zVpMoQ^_-5vyM`IDof|_87+jQos8s?(c{fI+|D@|s!e0V!R$cEn@aOXb^?;k;Up-_0j z%+y{j(M4Fvxxa1?rq>gMJ0lo zZnLCaDlCc)wV-hO*vb)z z{Aw?4m?<_im&T4DuQQ{yl(LQr;7FI;F+?)cr@tNK5!uHuIS#OwJs!X&AOamCp!T^2 zwMGCc^c<0U3Ak%R`5E2&)DL^W^dCT1G7Laq0>FM7aIJI_MrRVh2$CsG0o|WQChE5s z>=MUftW>@RRD6e#X1N}_BMO(5ZvRQ6OL_#W04zkzODy==r&0E(3?1sjUueloD8S1- zEgm|ZRWJ@!$e@gCCe${7>Y|-=NEBmizm979@!9Ad*s1B#LL46QzEXvLo6!uSqjmfC zOy~kNtQDqafvAWRo1CKk>`?6$S=qeFKpJuj#dJ_6C?_LhA-ynR_$GRnfPjkd^-B)8QTHjsGseoBoPtJi@JAII=+z1cLU5~kLwm4O(A@$K=lt|$zg*|K@{&AJBfwIZufXlAxV`NbN?8G()+jo^HH;bmZ8&+Y zpJ#+AQ2_9UcSpiH&+I?leumlSZf|B(KkK(?x?AomuAN9E^j;K}wo7KI4imBGRfEIh zaReC3hk_>9cL>SyO_2w2_ky3QrUgS4!1z%)-)f}NN43xA#Vh2fTYq8hTEIVLfbHSbf5$-m-ZJ_eqw>#~Wu z35oW!EjG6()?RI7vKVp z5Yz(N2`~JU%M?_8ge74Q3b{FNgqWYQ)PK|E`X`Timke_g`^Q$up|U7xVOrfkoFj!& z1DK>s^3`b%`k3>e-|{W|%@edE3^Siq?f-r251ZFBFvZ0SpB4FI zgxx#|VN}}j-XHH*he9>ps#>*sx!X!`U$z?VW@_G6PQA zu_BxYUmKTq;!A+57$;K#6&udHYnG%ozi>!3Uk#J-YNq5W2xJ7Y*r^SJ6r zNM#6vzkB(7`H(|)B`Bsr+TmUnj2$z$_JCtD>ou+9exFOER_$34Yu4cp^y%RaeEOT0 z=KUqZH?F1V;ioU(qV1c1lK1!b#Rp*f(mT>MT2ud4msbpL0#DHNhCjjli@Xxjb2fyJ zH;AV-lFai-<;aLb;)wUT{|+Zincr~E<3L-|z2Kr%0pV5p+_BJ$eCFnLYbsAK#GASQ zDf;&|uLRABEj?0;&tEOv!l$~^F8-DtZ%&7|QbZ-2_otcNHj<$2OiM~d*p;r!4wa!I zl*1>R|GkT6$A{u>kTwL%G4gH{!voK=8mTu=Lnt)m9Yn85sJGF|=CIx;gRiOIOQ_4I zQ&v2tZxVx#`9wR=`_OOy@iE$=FL9_U&RvCxm1yW;&x`xL_cT*Fx@DDMsMa|*oRW4<`TU7L}1 zd87?L{p)sZ`1{=FExUhpSML>rymDP!5c_ZVa(HQvz`m@lzjc`fLGzg4x>m#A_k(NS z1+jYqs2AL{&sB*r;UttXfU2@q{5p>SEIi@m=oS}V>y_O;qs}Qh>XV2^*ABoF9}+Wc z=|NSe9)9$WoURbZX`x@X|K9aOtQ}sUraok~lp0o0a-HT~Pw2Q$U;L>k;C-HsK(1Hjg#g}mYiI6?|ht{qKC6VnHn zf4BKoz8Gk{bbuzk(i9M>S-|ZfOx>eV>aD05kSy67-t0uhyTed4$kNo%uu8r))?6vW;lWz@S%Ih#S|?0?;b^qdqdP}t0NI=@b<07 zVUB~e{wkMSPdU6@Mjh`J=(4*H^kIobA0g~Fpw2t^cF}DLm!e2%N?DzF*u;WJ^+Er! zegF>;Cdaiu4yxYROZ%$?U;hGws-mhoyc?d<*po)F&j=CSw13Fq5O$P#AQ5?Cduj__ zgC*q}hbrFvkU@D{Ma(c{s?1itmC45RW6p4`NYNgC%}kuhIlJShXnjjeS5wUl$2%NA zTJn{xl?hEdf%=9Y^O(GO(;k8Y9D+iZ*GI_F5fM~ydu*%bm7~)Jya?!O8EBTPJB0rq De~rmR literal 0 HcmV?d00001 diff --git a/1.20.2/Fabric/src/main/resources/craterlib.fabric.mixins.json b/1.20.2/Fabric/src/main/resources/craterlib.fabric.mixins.json new file mode 100644 index 0000000..7c59043 --- /dev/null +++ b/1.20.2/Fabric/src/main/resources/craterlib.fabric.mixins.json @@ -0,0 +1,17 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "com.hypherionmc.craterlib.mixin", + "compatibilityLevel": "JAVA_17", + "mixins": [ + ], + "client": [ + "TutorialMixin" + ], + "server": [ + "ServerGamePacketListenerImplMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/1.20.2/Fabric/src/main/resources/fabric.mod.json b/1.20.2/Fabric/src/main/resources/fabric.mod.json new file mode 100644 index 0000000..be8caf8 --- /dev/null +++ b/1.20.2/Fabric/src/main/resources/fabric.mod.json @@ -0,0 +1,39 @@ +{ + "schemaVersion": 1, + "id": "${mod_id}", + "version": "${version}", + "name": "${mod_name}", + "description": "A library mod used by First Dark Development and HypherionSA Mods", + "authors": [ + "${mod_author}", + "Misha" + ], + "contact": { + "homepage": "https://modrinth.com/mod/craterlib", + "sources": "https://github.com/firstdarkdev/craterLib/" + }, + "license": "MIT", + "icon": "assets/craterlib/craterlib_logo.png", + "environment": "*", + "entrypoints": { + "main": [ + "com.hypherionmc.craterlib.CraterLibInitializer" + ], + "client": [ + "com.hypherionmc.craterlib.client.CraterLibClientInitializer" + ], + "modmenu": [ + "com.hypherionmc.craterlib.CraterLibModMenuIntegration" + ] + }, + "mixins": [ + "${mod_id}.mixins.json", + "${mod_id}.fabric.mixins.json" + ], + "depends": { + "fabricloader": ">=0.15.0", + "fabric-api": "*", + "minecraft": "1.20.2", + "java": ">=17" + } +} diff --git a/1.20.2/Forge/build.gradle b/1.20.2/Forge/build.gradle new file mode 100644 index 0000000..2045db6 --- /dev/null +++ b/1.20.2/Forge/build.gradle @@ -0,0 +1,112 @@ +// Adjust the output jar name here +archivesBaseName = "${mod_name.replace(" ", "")}-Forge-${minecraft_version}" + +dependencies { + // Compat + modImplementation("maven.modrinth:vanishmod:${vanishmod}") + + // Do not edit or remove + implementation project(":Common") +} + +shadowJar { + from sourceSets.main.output + configurations = [project.configurations.shade] + + dependencies { + exclude(dependency('com.google.code.gson:.*')) + + relocate 'me.hypherionmc.moonconfig', 'shadow.hypherionmc.moonconfig' + relocate 'me.hypherionmc.mcdiscordformatter', 'shadow.hypherionmc.mcdiscordformatter' + relocate 'net.kyori', 'shadow.kyori' + } + + setArchiveClassifier('dev-shadow') +} + +/** + * =============================================================================== + * = DO NOT EDIT BELOW THIS LINE UNLESS YOU KNOW WHAT YOU ARE DOING = + * =============================================================================== + */ + +unimined.minecraft { + minecraftForge { + loader forge_version + mixinConfig("${mod_id}.mixins.json", "${mod_id}.forge.mixins.json") + } +} + +remapJar { + inputFile.set shadowJar.archiveFile + dependsOn shadowJar + archiveClassifier.set null +} + +jar { + archiveClassifier.set "dev" +} + +processResources { + from project(":Common").sourceSets.main.resources + def buildProps = project.properties.clone() + + filesMatching("META-INF/mods.toml") { + expand buildProps + } +} + +compileTestJava.enabled = false + +tasks.withType(JavaCompile).configureEach { + source(project(":Common").sourceSets.main.allSource) +} + +/** + * Publishing Config + */ +publishing { + publications { + mavenJava(MavenPublication) { + artifactId project.archivesBaseName + from components.java + + artifact(remapJar) { + builtBy remapJar + } + + pom.withXml { + Node pomNode = asNode() + pomNode.dependencies.'*'.findAll() { + it.artifactId.text() == 'regutils-joined-fabric' || + it.artifactId.text() == 'core' || + it.artifactId.text() == 'toml' + }.each() { + it.parent().remove(it) + } + } + } + } + + repositories { + maven rootProject.orion.getPublishingMaven() + } +} + +publisher { + apiKeys { + modrinth(System.getenv("MODRINTH_TOKEN")) + curseforge(System.getenv("CURSE_TOKEN")) + } + + setCurseID(curse_id) + setModrinthID(modrinth_id) + setVersionType("release") + setChangelog("https://raw.githubusercontent.com/hypherionmc/changelogs/main/craterlib/changelog-forge.md") + setProjectVersion("${minecraft_version}-${project.version}") + setDisplayName("[Forge 1.20.2] CraterLib - ${project.version}") + setGameVersions("1.20.2") + setLoaders("forge") + setArtifact(remapJar) + setCurseEnvironment("both") +} diff --git a/1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java b/1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java new file mode 100644 index 0000000..968da05 --- /dev/null +++ b/1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java @@ -0,0 +1,41 @@ +package com.hypherionmc.craterlib; + +import com.hypherionmc.craterlib.api.events.client.LateInitEvent; +import com.hypherionmc.craterlib.common.ForgeServerEvents; +import com.hypherionmc.craterlib.compat.Vanish; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.core.networking.CraterPacketNetwork; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.core.platform.ModloaderEnvironment; +import com.hypherionmc.craterlib.network.CraterForgeNetworkHandler; +import com.hypherionmc.craterlib.nojang.client.BridgedMinecraft; +import com.hypherionmc.craterlib.nojang.client.BridgedOptions; +import net.minecraft.client.Minecraft; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.DistExecutor; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import net.minecraftforge.fml.loading.FMLLoader; + +@Mod(CraterConstants.MOD_ID) +public class CraterLib { + + public CraterLib() { + MinecraftForge.EVENT_BUS.register(new ForgeServerEvents()); + FMLJavaModLoadingContext.get().getModEventBus().addListener(this::commonSetup); + } + + public void commonSetup(FMLCommonSetupEvent evt) { + new CraterPacketNetwork(new CraterForgeNetworkHandler(FMLLoader.getDist().isClient() ? PacketSide.CLIENT : PacketSide.SERVER)); + DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> { + LateInitEvent event = new LateInitEvent(new BridgedMinecraft(), BridgedOptions.of(Minecraft.getInstance().options)); + CraterEventBus.INSTANCE.postEvent(event); + }); + + if (ModloaderEnvironment.INSTANCE.isModLoaded("vmod")) { + MinecraftForge.EVENT_BUS.register(new Vanish()); + } + } +} diff --git a/1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/client/ForgeClientEvents.java b/1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/client/ForgeClientEvents.java new file mode 100644 index 0000000..cb096d5 --- /dev/null +++ b/1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/client/ForgeClientEvents.java @@ -0,0 +1,25 @@ +package com.hypherionmc.craterlib.client; + +import com.hypherionmc.craterlib.CraterConstants; +import com.hypherionmc.craterlib.api.events.client.CraterClientTickEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedClientLevel; +import net.minecraft.client.Minecraft; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.event.TickEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; + +@Mod.EventBusSubscriber(modid = CraterConstants.MOD_ID, bus = Mod.EventBusSubscriber.Bus.FORGE, value = Dist.CLIENT) +public class ForgeClientEvents { + + @SubscribeEvent + public static void clientTick(TickEvent.LevelTickEvent event) { + if (Minecraft.getInstance().level == null) + return; + + CraterClientTickEvent craterClientTickEvent = new CraterClientTickEvent(BridgedClientLevel.of(Minecraft.getInstance().level)); + CraterEventBus.INSTANCE.postEvent(craterClientTickEvent); + } + +} diff --git a/1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/client/ForgeClientHelper.java b/1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/client/ForgeClientHelper.java new file mode 100644 index 0000000..6776622 --- /dev/null +++ b/1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/client/ForgeClientHelper.java @@ -0,0 +1,41 @@ +package com.hypherionmc.craterlib.client; + +import com.hypherionmc.craterlib.core.platform.ClientPlatform; +import com.hypherionmc.craterlib.nojang.client.BridgedMinecraft; +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedClientLevel; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraft.client.Minecraft; +import net.minecraft.network.Connection; + +import java.util.Objects; + +/** + * @author HypherionSA + * @date 16/06/2022 + */ +public class ForgeClientHelper implements ClientPlatform { + + public ForgeClientHelper() { + } + + @Override + public BridgedMinecraft getClientInstance() { + return new BridgedMinecraft(); + } + + @Override + public BridgedPlayer getClientPlayer() { + return BridgedPlayer.of(Minecraft.getInstance().player); + } + + @Override + public BridgedClientLevel getClientLevel() { + return BridgedClientLevel.of(Minecraft.getInstance().level); + } + + @Override + public Connection getClientConnection() { + Objects.requireNonNull(Minecraft.getInstance().getConnection(), "Cannot send packets when not in game!"); + return Minecraft.getInstance().getConnection().getConnection(); + } +} diff --git a/1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeCommonHelper.java b/1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeCommonHelper.java new file mode 100644 index 0000000..281ddd7 --- /dev/null +++ b/1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeCommonHelper.java @@ -0,0 +1,26 @@ +package com.hypherionmc.craterlib.common; + +import com.hypherionmc.craterlib.core.platform.CommonPlatform; +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.CreativeModeTab; +import net.minecraftforge.server.ServerLifecycleHooks; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author HypherionSA + */ +public class ForgeCommonHelper implements CommonPlatform { + + public static Map TABS = new HashMap<>(); + + public ForgeCommonHelper() { + } + + @Override + public BridgedMinecraftServer getMCServer() { + return BridgedMinecraftServer.of(ServerLifecycleHooks.getCurrentServer()); + } +} diff --git a/1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeCompatHelper.java b/1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeCompatHelper.java new file mode 100644 index 0000000..f325837 --- /dev/null +++ b/1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeCompatHelper.java @@ -0,0 +1,17 @@ +package com.hypherionmc.craterlib.common; + +import com.hypherionmc.craterlib.core.platform.CompatUtils; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; + +public class ForgeCompatHelper implements CompatUtils { + + @Override + public boolean isPlayerActive(BridgedPlayer player) { + return true; + } + + @Override + public String getSkinUUID(BridgedPlayer player) { + return player.getStringUUID(); + } +} diff --git a/1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeLoaderHelper.java b/1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeLoaderHelper.java new file mode 100644 index 0000000..2be70fc --- /dev/null +++ b/1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeLoaderHelper.java @@ -0,0 +1,73 @@ +package com.hypherionmc.craterlib.common; + +import com.hypherionmc.craterlib.core.platform.Environment; +import com.hypherionmc.craterlib.core.platform.ModloaderEnvironment; +import net.minecraft.SharedConstants; +import net.minecraft.client.Minecraft; +import net.minecraftforge.fml.ModList; +import net.minecraftforge.fml.loading.FMLLoader; +import net.minecraftforge.fml.loading.FMLPaths; + +import java.io.File; + +/** + * @author HypherionSA + */ +public class ForgeLoaderHelper implements ModloaderEnvironment { + + public ForgeLoaderHelper() { + } + + @Override + public boolean isFabric() { + return false; + } + + @Override + public String getGameVersion() { + return SharedConstants.VERSION_STRING; + } + + @Override + public File getGameFolder() { + return Minecraft.getInstance().gameDirectory; + } + + @Override + public File getConfigFolder() { + return FMLPaths.CONFIGDIR.get().toFile(); + } + + @Override + public File getModsFolder() { + return FMLPaths.MODSDIR.get().toFile(); + } + + @Override + public Environment getEnvironment() { + switch (FMLLoader.getDist()) { + case CLIENT -> { + return Environment.CLIENT; + } + case DEDICATED_SERVER -> { + return Environment.SERVER; + } + } + return Environment.UNKNOWN; + } + + @Override + public boolean isModLoaded(String modid) { + return ModList.get().isLoaded(modid); + } + + @Override + public boolean isDevEnv() { + return !FMLLoader.isProduction(); + } + + @Override + public int getModCount() { + return ModList.get().size(); + } +} diff --git a/1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeServerEvents.java b/1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeServerEvents.java new file mode 100644 index 0000000..f354ddc --- /dev/null +++ b/1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeServerEvents.java @@ -0,0 +1,43 @@ +package com.hypherionmc.craterlib.common; + +import com.hypherionmc.craterlib.api.events.server.CraterRegisterCommandEvent; +import com.hypherionmc.craterlib.api.events.server.CraterServerLifecycleEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.commands.CommandsRegistry; +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import net.minecraftforge.event.RegisterCommandsEvent; +import net.minecraftforge.event.server.ServerStartedEvent; +import net.minecraftforge.event.server.ServerStartingEvent; +import net.minecraftforge.event.server.ServerStoppedEvent; +import net.minecraftforge.event.server.ServerStoppingEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; + +public class ForgeServerEvents { + + @SubscribeEvent + public void serverStarting(ServerStartingEvent event) { + CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Starting(BridgedMinecraftServer.of(event.getServer()))); + } + + @SubscribeEvent + public void serverStarted(ServerStartedEvent event) { + CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Started(BridgedMinecraftServer.of(event.getServer()))); + } + + @SubscribeEvent + public void serverStopping(ServerStoppingEvent event) { + CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Stopping(BridgedMinecraftServer.of(event.getServer()))); + } + + @SubscribeEvent + public void serverStopped(ServerStoppedEvent event) { + CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Stopped(BridgedMinecraftServer.of(event.getServer()))); + } + + @SubscribeEvent + public void onCommandRegister(RegisterCommandsEvent event) { + CraterEventBus.INSTANCE.postEvent(new CraterRegisterCommandEvent()); + CommandsRegistry.INSTANCE.registerCommands(event.getDispatcher()); + } + +} diff --git a/1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java b/1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java new file mode 100644 index 0000000..09bb181 --- /dev/null +++ b/1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java @@ -0,0 +1,24 @@ +package com.hypherionmc.craterlib.compat; + +import com.hypherionmc.craterlib.api.events.server.CraterPlayerEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import redstonedubstep.mods.vanishmod.api.PlayerVanishEvent; + +public class Vanish { + + public Vanish() { + + } + + @SubscribeEvent + public void vanishevent(PlayerVanishEvent event) { + if (event.isVanished()) { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerEvent.PlayerLoggedOut(BridgedPlayer.of(event.getEntity()))); + } else { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerEvent.PlayerLoggedIn(BridgedPlayer.of(event.getEntity()))); + } + } + +} \ No newline at end of file diff --git a/1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ConfigScreenHandlerMixin.java b/1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ConfigScreenHandlerMixin.java new file mode 100644 index 0000000..927bd23 --- /dev/null +++ b/1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ConfigScreenHandlerMixin.java @@ -0,0 +1,43 @@ +package com.hypherionmc.craterlib.mixin; + +import com.hypherionmc.craterlib.client.gui.config.CraterConfigScreen; +import com.hypherionmc.craterlib.core.config.ConfigController; +import com.hypherionmc.craterlib.core.config.ModuleConfig; +import com.hypherionmc.craterlib.core.config.annotations.NoConfigScreen; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.Screen; +import net.minecraftforge.client.ConfigScreenHandler; +import net.minecraftforge.forgespi.language.IModInfo; +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.CallbackInfoReturnable; + +import java.util.Optional; +import java.util.function.BiFunction; + +/** + * @author HypherionSA + */ +@Mixin(ConfigScreenHandler.class) +public class ConfigScreenHandlerMixin { + + /** + * Inject Auto Generated config Screens into forge + * + */ + @Inject(at = @At("RETURN"), method = "getScreenFactoryFor", cancellable = true, remap = false) + private static void injectConfigScreen(IModInfo selectedMod, CallbackInfoReturnable>> cir) { + ConfigController.getMonitoredConfigs().forEach((conf, watcher) -> { + if (!conf.getClass().isAnnotationPresent(NoConfigScreen.class)) { + ModuleConfig config = (ModuleConfig) conf; + if (config.getModId().equals(selectedMod.getModId())) { + cir.setReturnValue( + Optional.of((minecraft, screen) -> new CraterConfigScreen(config, screen)) + ); + } + } + }); + } + +} diff --git a/1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java b/1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java new file mode 100644 index 0000000..6e2f601 --- /dev/null +++ b/1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java @@ -0,0 +1,36 @@ +package com.hypherionmc.craterlib.mixin; + +import com.hypherionmc.craterlib.api.events.server.CraterServerChatEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.ChatUtils; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.PlayerChatMessage; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.FilteredText; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(value = ServerGamePacketListenerImpl.class, priority = Integer.MIN_VALUE) +public class ServerGamePacketListenerImplMixin { + + @Shadow + public ServerPlayer player; + + @Inject( + method = "lambda$handleChat$6", + at = @At("HEAD"), + cancellable = true + ) + private void injectChatEvent(Component component, PlayerChatMessage arg, FilteredText p_296589_, CallbackInfo ci) { + CraterServerChatEvent event = new CraterServerChatEvent(BridgedPlayer.of(this.player), arg.decoratedContent().getString(), ChatUtils.mojangToAdventure(arg.decoratedContent())); + CraterEventBus.INSTANCE.postEvent(event); + if (event.wasCancelled()) + ci.cancel(); + } + +} diff --git a/1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/network/CraterForgeNetworkHandler.java b/1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/network/CraterForgeNetworkHandler.java new file mode 100644 index 0000000..cdaeaa1 --- /dev/null +++ b/1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/network/CraterForgeNetworkHandler.java @@ -0,0 +1,98 @@ +package com.hypherionmc.craterlib.network; + +import com.hypherionmc.craterlib.CraterConstants; +import com.hypherionmc.craterlib.core.networking.PacketRegistry; +import com.hypherionmc.craterlib.core.networking.data.PacketContext; +import com.hypherionmc.craterlib.core.networking.data.PacketHolder; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.nojang.network.BridgedFriendlyByteBuf; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraft.client.Minecraft; +import net.minecraft.network.Connection; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraftforge.event.network.CustomPayloadEvent; +import net.minecraftforge.network.ChannelBuilder; +import net.minecraftforge.network.PacketDistributor; +import net.minecraftforge.network.SimpleChannel; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public class CraterForgeNetworkHandler extends PacketRegistry { + private final Map, SimpleChannel> CHANNELS = new HashMap<>(); + + public CraterForgeNetworkHandler(PacketSide side) { + super(side); + } + + protected void registerPacket(PacketHolder holder) { + if (CHANNELS.get(holder.messageType()) == null) { + SimpleChannel channel = ChannelBuilder + .named(holder.type().toMojang()) + .clientAcceptedVersions((a, b) -> true) + .serverAcceptedVersions((a, b) -> true) + .networkProtocolVersion(1) + .simpleChannel(); + + channel.messageBuilder(holder.messageType()) + .decoder(mojangDecoder(holder.decoder())) + .encoder(mojangEncoder(holder.encoder())) + .consumerNetworkThread(buildHandler(holder.handler())) + .add(); + + CHANNELS.put(holder.messageType(), channel); + } else { + CraterConstants.LOG.error("Trying to register duplicate packet for type {}", holder.messageType()); + } + } + + public void sendToServer(T packet) { + this.sendToServer(packet, false); + } + + public void sendToServer(T packet, boolean ignoreCheck) { + SimpleChannel channel = CHANNELS.get(packet.getClass()); + Connection connection = Minecraft.getInstance().getConnection().getConnection(); + if (channel.isRemotePresent(connection) || ignoreCheck) { + channel.send(packet, PacketDistributor.SERVER.noArg()); + } + } + + public void sendToClient(T packet, BridgedPlayer player) { + SimpleChannel channel = CHANNELS.get(packet.getClass()); + ServerGamePacketListenerImpl connection = player.getConnection(); + if (connection == null) + return; + + if (channel.isRemotePresent(connection.getConnection())) { + channel.send(packet, PacketDistributor.PLAYER.with(player.toMojangServerPlayer())); + } + } + + private Function mojangDecoder(Function handler) { + return byteBuf -> handler.apply(BridgedFriendlyByteBuf.of(byteBuf)); + } + + private BiConsumer mojangEncoder(BiConsumer handler) { + return ((t, byteBuf) -> handler.accept(t, BridgedFriendlyByteBuf.of(byteBuf))); + } + + private BiConsumer buildHandler(Consumer> handler) { + return (message, ctx) -> { + ctx.enqueueWork(() -> { + PacketSide side = ctx.getDirection().getReceptionSide().isServer() ? PacketSide.SERVER : PacketSide.CLIENT; + ServerPlayer player = ctx.getSender(); + handler.accept(new PacketContext<>(BridgedPlayer.of(player), message, side)); + }); + ctx.setPacketHandled(true); + }; + } +} diff --git a/1.20.2/Forge/src/main/resources/META-INF/mods.toml b/1.20.2/Forge/src/main/resources/META-INF/mods.toml new file mode 100644 index 0000000..ad785a7 --- /dev/null +++ b/1.20.2/Forge/src/main/resources/META-INF/mods.toml @@ -0,0 +1,31 @@ +modLoader = "javafml" +loaderVersion = "[48,)" +license = "MIT" +issueTrackerURL = "https://github.com/firstdarkdev/craterLib/issues" + +[[mods]] + modId = "${mod_id}" + version = "${version}" + displayName = "${mod_name}" + displayURL = "https://modrinth.com/mod/craterlib" + logoFile = "craterlib_logo.png" + #credits="Thanks for this example mod goes to Java" + authors = "${mod_author}, Zenith" + description = ''' + A library mod used by First Dark Development and HypherionSA Mods + ''' + displayTest = "NONE" + +[[dependencies.${ mod_id }]] + modId = "forge" + mandatory = true + versionRange = "[48,)" + ordering = "NONE" + side = "BOTH" + +[[dependencies.${ mod_id }]] + modId = "minecraft" + mandatory = true + versionRange = "[1.20.2,1.20.3)" + ordering = "NONE" + side = "BOTH" diff --git a/1.20.2/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform b/1.20.2/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform new file mode 100644 index 0000000..a12ad8c --- /dev/null +++ b/1.20.2/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform @@ -0,0 +1 @@ +com.hypherionmc.craterlib.client.ForgeClientHelper \ No newline at end of file diff --git a/1.20.2/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform b/1.20.2/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform new file mode 100644 index 0000000..09e119f --- /dev/null +++ b/1.20.2/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform @@ -0,0 +1 @@ +com.hypherionmc.craterlib.common.ForgeCommonHelper \ No newline at end of file diff --git a/1.20.2/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils b/1.20.2/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils new file mode 100644 index 0000000..a9f823d --- /dev/null +++ b/1.20.2/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils @@ -0,0 +1 @@ +com.hypherionmc.craterlib.common.ForgeCompatHelper \ No newline at end of file diff --git a/1.20.2/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment b/1.20.2/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment new file mode 100644 index 0000000..02b4e07 --- /dev/null +++ b/1.20.2/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment @@ -0,0 +1 @@ +com.hypherionmc.craterlib.common.ForgeLoaderHelper \ No newline at end of file diff --git a/1.20.2/Forge/src/main/resources/craterlib.forge.mixins.json b/1.20.2/Forge/src/main/resources/craterlib.forge.mixins.json new file mode 100644 index 0000000..aa072d1 --- /dev/null +++ b/1.20.2/Forge/src/main/resources/craterlib.forge.mixins.json @@ -0,0 +1,17 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "com.hypherionmc.craterlib.mixin", + "compatibilityLevel": "JAVA_17", + "mixins": [ + ], + "client": [ + "ConfigScreenHandlerMixin" + ], + "server": [ + "ServerGamePacketListenerImplMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/1.20.2/Forge/src/main/resources/craterlib_logo.png b/1.20.2/Forge/src/main/resources/craterlib_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..ce0159cc4aa4d823ea354042e531b4522d6dfead GIT binary patch literal 49343 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 z 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 De~rmR literal 0 HcmV?d00001 diff --git a/1.20.2/LICENSE b/1.20.2/LICENSE new file mode 100644 index 0000000..f4a1e2d --- /dev/null +++ b/1.20.2/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) HypherionSA and Contributors 2024 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/1.20.2/NeoForge/src/main/resources/craterlib_logo.png b/1.20.2/NeoForge/src/main/resources/craterlib_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..ce0159cc4aa4d823ea354042e531b4522d6dfead GIT binary patch literal 49343 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 z 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 De~rmR literal 0 HcmV?d00001 diff --git a/1.20.2/README.md b/1.20.2/README.md new file mode 100644 index 0000000..49c0699 --- /dev/null +++ b/1.20.2/README.md @@ -0,0 +1,55 @@ +# CraterLib + +![badge-snapshot](https://maven.firstdarkdev.xyz/api/badge/latest/snapshots/me/hypherionmc/craterlib/CraterLib-common-1.20-pre6?color=40c14a&name=CraterLib-Snapshot) + +*** + +A Library mod and modding api for easier multi-version minecraft and mod loader development + +*** + +### Supported Minecraft Versions + +| Minecraft Version | Support Status | +|-------------------| -------------- | +| < 1.18.2 | ❌ | +| 1.18.2-1.20.2 | ✳️ | +| 1.20.4 | ✳️ | +| 1.21 | 🚧 | + +- ❌ - Not Supported; no bug fixes or new features. +- 🚧 - Work in Progress; not ready for release. +- ✳️ - Long Term Support; receives changes through backports only. +- ✅ - In Support; the active version, receiving all bugfixes and features directly. + +*** + +## Library Features + +* Universal Config System (TOML Based) +* Built in Helper Classes for Various minecraft features +* Built in Optifine-Compat utilities +* Various utilities for Blockstates, LANG, Math and Rendering +* Cross Mod-Loader Events - Based on [Acara](https://github.com/Keksuccino/acara) +* Cross Mod-Loader Config Screens (Based on [Cloth Config Lite](https://github.com/shedaniel/cloth-config-lite)) +* Automatic ModMenu and Forge Config screen registration +* Built in Cross Mod-Loader Network system +* Nojang Modding API + +*** + +## Setup Instructions + +There's a **wiki coming soon**, but for now, here's some basic instructions for building the project: + +1. `git clone` the project to a safe spot. +2. Install Java's JDK 17. Make sure you have the development version explicitly: + * Fedora: `sudo dnf install java-17-openjdk-devel` + * Ubuntu: `sudo apt install openjdk-17-jdk` + * macOS: `brew install openjdk@17` +3. Set it accordingly: + * Windows/macOS: Set the `JAVA_HOME` environment variable or use system settings + * Linux: `sudo update-alternatives --config java` +4. Navigate to the CraterLib folder, then run a `gradlew` file depending on your operating system: + * Windows: `.\gradlew.bat build` + * macOS/Linux/BSD: `chmod +x gradlew` and `./gradlew` diff --git a/1.20.2/build.gradle b/1.20.2/build.gradle new file mode 100644 index 0000000..5bdbcfc --- /dev/null +++ b/1.20.2/build.gradle @@ -0,0 +1,110 @@ +plugins { + id 'java' + id 'com.github.johnrengelman.shadow' version '8.1.1' apply false + id "xyz.wagyourtail.unimined" version "1.2.4" apply false + id "com.hypherionmc.modutils.modpublisher" version "2.1.+" + id "com.hypherionmc.modutils.orion" version "1.0.+" + id 'maven-publish' +} + +orion.setup { + multiProject = true + enableMirrorMaven = true + enableReleasesMaven = true + + dopplerToken = System.getenv("DOPPLER_KEY") + + versioning { + var relType = project.properties["releaseType"] ?: "${release_type}" + identifier("${relType}") + } +} + +group = project_group + +subprojects { + apply plugin: "xyz.wagyourtail.unimined" + apply plugin: "java" + apply plugin: 'maven-publish' + apply plugin: 'com.github.johnrengelman.shadow' + apply plugin: 'com.hypherionmc.modutils.modpublisher' + + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + + group = rootProject.group + + repositories { + mavenCentral() + + maven { + name = "Modrinth" + url = "https://api.modrinth.com/maven" + content { + includeGroup "maven.modrinth" + } + } + } + + configurations { + shade + modCompileOnly + implementation.extendsFrom shade + compileOnly.extendsFrom modCompileOnly + } + + dependencies { + // All Projects + shade "me.hypherionmc.moon-config:core:${moon_config}" + shade "me.hypherionmc.moon-config:toml:${moon_config}" + shade "com.hypherionmc:rpcsdk:${rpc_sdk}" + shade "me.hypherionmc.sdlink:mcdiscordformatter-1.19.1:${discord_formatter}" + shade "net.kyori:adventure-api:${adventure}" + shade "net.kyori:adventure-text-serializer-gson:${adventure}" + + compileOnly("org.projectlombok:lombok:${lombok}") + annotationProcessor("org.projectlombok:lombok:${lombok}") + } + + jar { + manifest { + attributes([ + 'Specification-Title' : mod_name, + 'Specification-Vendor' : mod_author, + 'Specification-Version' : project.jar.archiveVersion, + 'Implementation-Title' : project.name, + 'Implementation-Version' : project.jar.archiveVersion, + 'Implementation-Vendor' : mod_author, + 'Implementation-Timestamp': new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"), + 'Timestamp' : System.currentTimeMillis(), + 'Built-On-Java' : "${System.getProperty('java.vm.version')} (${System.getProperty('java.vm.vendor')})", + 'Built-On-Minecraft' : minecraft_version + ]) + } + } + +/** + * =============================================================================== + * = DO NOT EDIT BELOW THIS LINE UNLESS YOU KNOW WHAT YOU ARE DOING = + * =============================================================================== + */ + unimined.minecraft(sourceSets.main, true) { + version minecraft_version + + mappings { + mojmap() + devNamespace "mojmap" + } + } + + tasks.withType(JavaCompile).configureEach { + it.options.encoding = 'UTF-8' + it.options.release = 17 + } + + tasks.withType(GenerateModuleMetadata).configureEach { + enabled = false + } +} + +// TODO MODULE JARS diff --git a/1.20.2/gradle.properties b/1.20.2/gradle.properties new file mode 100644 index 0000000..65b82ba --- /dev/null +++ b/1.20.2/gradle.properties @@ -0,0 +1,42 @@ +#Project +version_major=2 +version_minor=0 +version_patch=0 + +#Mod +mod_author=HypherionSA +mod_id=craterlib +mod_name=CraterLib + +# Shared +minecraft_version=1.20.2 +project_group=com.hypherionmc.craterlib + +# Fabric +fabric_loader=0.15.11 +fabric_api=0.91.6+1.20.2 + +# Forge +forge_version=48.1.0 + +# Dependencies +moon_config=1.0.9 +lombok=1.18.32 +adventure=4.16.0 +rpc_sdk=1.0 +discord_formatter=2.0.0 + +# Mod Dependencies +fabrictailor=2.2.1 +vanish=1.5.0+1.20.2 +mod_menu_version=8.0.1 +vanishmod=1.1.15 + +# Publishing +curse_id=867099 +modrinth_id=Nn8Wasaq +release_type=release + +# Gradle +org.gradle.jvmargs=-Xmx3G +org.gradle.daemon=false diff --git a/1.20.2/gradle/wrapper/gradle-wrapper.jar b/1.20.2/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..7454180f2ae8848c63b8b4dea2cb829da983f2fa GIT binary patch literal 59536 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 QuEJmfE|#`Dqx|px0L@K;Y5)KL literal 0 HcmV?d00001 diff --git a/1.20.2/gradle/wrapper/gradle-wrapper.properties b/1.20.2/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..48c0a02 --- /dev/null +++ b/1.20.2/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/1.20.2/gradlew b/1.20.2/gradlew new file mode 100644 index 0000000..b4f908a --- /dev/null +++ b/1.20.2/gradlew @@ -0,0 +1,183 @@ +#!/usr/bin/env bash + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MSYS* | MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +ARGV=("$@") +eval set -- $DEFAULT_JVM_OPTS + +IFS=$' +' read -rd '' -a JAVA_OPTS_ARR <<< "$(echo $JAVA_OPTS | xargs -n1)" +IFS=$' +' read -rd '' -a GRADLE_OPTS_ARR <<< "$(echo $GRADLE_OPTS | xargs -n1)" + +exec "$JAVACMD" "$@" "${JAVA_OPTS_ARR[@]}" "${GRADLE_OPTS_ARR[@]}" "-Dorg.gradle.appname=$APP_BASE_NAME" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "${ARGV[@]}" diff --git a/1.20.2/gradlew.bat b/1.20.2/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/1.20.2/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/1.20.2/settings.gradle b/1.20.2/settings.gradle new file mode 100644 index 0000000..32dbb35 --- /dev/null +++ b/1.20.2/settings.gradle @@ -0,0 +1,14 @@ +pluginManagement { + repositories { + gradlePluginPortal() + maven { + url "https://mcentral.firstdark.dev/releases" + } + maven { + url "https://maven.firstdark.dev/releases" + } + } +} + +rootProject.name = 'CraterLib-1.20.2' +include("Common", "Fabric", "Forge") diff --git a/1.20.4/.gitattributes b/1.20.4/.gitattributes new file mode 100644 index 0000000..20fc528 --- /dev/null +++ b/1.20.4/.gitattributes @@ -0,0 +1,15 @@ +* text eol=lf +*.bat text eol=crlf +*.patch text eol=lf +*.java text eol=lf +*.gradle text eol=crlf +*.png binary +*.gif binary +*.exe binary +*.dll binary +*.jar binary +*.lzma binary +*.zip binary +*.pyd binary +*.cfg text eol=lf +*.jks binary \ No newline at end of file diff --git a/1.20.4/.gitignore b/1.20.4/.gitignore new file mode 100644 index 0000000..966dad4 --- /dev/null +++ b/1.20.4/.gitignore @@ -0,0 +1,29 @@ +# eclipse +bin +*.launch +.settings +.metadata +.classpath +.project + +# idea +out +*.ipr +*.iws +*.iml +.idea + +# gradle +build +.gradle + +# other +eclipse +run + +artifacts +src/test/** + +workspace +upstream +rejects \ No newline at end of file diff --git a/1.20.4/.jenkins/Jenkinsfile.deploy b/1.20.4/.jenkins/Jenkinsfile.deploy new file mode 100644 index 0000000..f523ff9 --- /dev/null +++ b/1.20.4/.jenkins/Jenkinsfile.deploy @@ -0,0 +1,55 @@ +def JDK = "17" + +pipeline { + agent { + docker { + image "registry.firstdark.dev/java${JDK}:latest" + alwaysPull true + args '-v gradle-cache:/home/gradle/.gradle' + } + } + + environment { + GRADLE_USER_HOME = '/home/gradle/.gradle' + } + + stages { + stage("Notify Discord") { + steps { + discordSend webhookURL: env.FDD_WH_ADMIN, + title: "Deploy Started: CraterLib 1.20.4 Deploy #${BUILD_NUMBER}", + link: env.BUILD_URL, + result: 'SUCCESS', + description: "Build: [${BUILD_NUMBER}](${env.BUILD_URL})" + } + } + stage("Prepare") { + steps { + sh "chmod +x ./gradlew" + sh "./gradlew clean" + } + } + stage("Publish to Modrinth/Curseforge") { + steps { + sh "./gradlew publishMod -Prelease=true" + } + } + stage("Publish to Maven") { + steps { + sh "./gradlew publish -Prelease=true" + } + } + } + post { + always { + sh "./gradlew --stop" + deleteDir() + + discordSend webhookURL: env.FDD_WH_ADMIN, + title: "CraterLib Port Deploy #${BUILD_NUMBER}", + link: env.BUILD_URL, + result: currentBuild.currentResult, + description: "Build: [${BUILD_NUMBER}](${env.BUILD_URL})\nStatus: ${currentBuild.currentResult}" + } + } +} diff --git a/1.20.4/.jenkins/Jenkinsfile.snapshot b/1.20.4/.jenkins/Jenkinsfile.snapshot new file mode 100644 index 0000000..484965a --- /dev/null +++ b/1.20.4/.jenkins/Jenkinsfile.snapshot @@ -0,0 +1,65 @@ +def projectName = "CraterLib"; +def projectIcon = "https://cdn.modrinth.com/data/Nn8Wasaq/a172c634683a11a2e9ae593e56eba7885743bb44.png"; +def JDK = "17"; +def majorMc = "1.20.4"; +def modLoaders = "neoforge|forge|fabric|quilt"; +def supportedMc = "1.20.4"; +def reltype = "snapshot"; + +pipeline { + agent { + docker { + image "registry.firstdark.dev/java${JDK}:latest" + alwaysPull true + args '-v gradle-cache:/home/gradle/.gradle' + } + } + + environment { + GRADLE_USER_HOME = '/home/gradle/.gradle' + } + + stages { + stage("Notify Discord") { + steps { + discordSend webhookURL: env.SSS_WEBHOOK, + title: "Deploy Started: ${projectName} ${majorMc} Deploy #${BUILD_NUMBER}", + link: env.BUILD_URL, + result: 'SUCCESS', + description: "Build: [${BUILD_NUMBER}](${env.BUILD_URL})" + } + } + + stage("Prepare") { + steps { + sh "chmod +x ./gradlew" + sh "./gradlew build -PreleaseType=port" + } + } + + stage("Publish to Maven") { + steps { + catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') { + sh "./gradlew publish -PreleaseType=${reltype}" + } + } + } + } + + post { + always { + sh "./gradlew --stop" + + fddsnapshotter apiKey: env.PLATFORM_KEY, + projectSlug: "craterlib", + projectName: "${projectName}", + projectIcon: "${projectIcon}", + modLoaders: "${modLoaders}", + minecraftVersions: "${supportedMc}", + failWebhook: env.SSS_WEBHOOK, + publishWebhooks: "${env.SSS_WEBHOOK}|${env.FDD_WH}" + + deleteDir() + } + } +} diff --git a/1.20.4/Common/build.gradle b/1.20.4/Common/build.gradle new file mode 100644 index 0000000..843d62a --- /dev/null +++ b/1.20.4/Common/build.gradle @@ -0,0 +1,68 @@ +archivesBaseName = "${mod_name.replace(" ", "")}-Common-${minecraft_version}" + +dependencies { + +} + +shadowJar { + from sourceSets.main.output + configurations = [project.configurations.shade] + + dependencies { + exclude(dependency('com.google.code.gson:.*')) + + relocate 'me.hypherionmc.moonconfig', 'shadow.hypherionmc.moonconfig' + relocate 'me.hypherionmc.mcdiscordformatter', 'shadow.hypherionmc.mcdiscordformatter' + relocate 'net.kyori', 'shadow.kyori' + } + setArchiveClassifier("dev") +} + +/** + * =============================================================================== + * = DO NOT EDIT BELOW THIS LINE UNLESS YOU KNOW WHAT YOU ARE DOING = + * =============================================================================== + */ + +unimined.minecraft { + fabric { + loader fabric_loader + } + + defaultRemapJar = false +} + +processResources { + def buildProps = project.properties.clone() + + filesMatching(['pack.mcmeta']) { + expand buildProps + } +} + +/** + * Publishing Config + */ +publishing { + publications { + mavenCommon(MavenPublication) { + artifactId project.archivesBaseName + from components.java + + pom.withXml { + Node pomNode = asNode() + pomNode.dependencies.'*'.findAll() { + it.artifactId.text() == 'regutils-joined-fabric' || + it.artifactId.text() == 'core' || + it.artifactId.text() == 'toml' + }.each() { + it.parent().remove(it) + } + } + } + } + + repositories { + maven rootProject.orion.getPublishingMaven() + } +} \ No newline at end of file diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/CraterConstants.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/CraterConstants.java new file mode 100644 index 0000000..93edffc --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/CraterConstants.java @@ -0,0 +1,10 @@ +package com.hypherionmc.craterlib; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CraterConstants { + public static final String MOD_ID = "craterlib"; + public static final String MOD_NAME = "CraterLib"; + public static final Logger LOG = LoggerFactory.getLogger(MOD_NAME); +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/commands/CraterCommand.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/commands/CraterCommand.java new file mode 100644 index 0000000..cc4969e --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/commands/CraterCommand.java @@ -0,0 +1,53 @@ +package com.hypherionmc.craterlib.api.commands; + +import com.hypherionmc.craterlib.nojang.authlib.BridgedGameProfile; +import com.hypherionmc.craterlib.nojang.commands.BridgedCommandSourceStack; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.TriConsumer; +import com.mojang.brigadier.arguments.ArgumentType; +import lombok.Getter; +import net.minecraft.commands.arguments.GameProfileArgument; +import org.apache.commons.lang3.tuple.Pair; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.function.Consumer; + +@Getter +public class CraterCommand { + + private final HashMap, TriConsumer>> arguments = new LinkedHashMap<>(); + private Consumer executor; + + private final String commandName; + private int permissionLevel = 4; + + CraterCommand(String commandName) { + this.commandName = commandName; + } + + public static CraterCommand literal(String commandName) { + return new CraterCommand(commandName); + } + + public CraterCommand requiresPermission(int perm) { + this.permissionLevel = perm; + return this; + } + + public CraterCommand withGameProfileArgument(String key, TriConsumer, BridgedCommandSourceStack> executor) { + arguments.put(key, Pair.of(GameProfileArgument.gameProfile(), executor)); + return this; + } + + public CraterCommand executes(Consumer ctx) { + executor = ctx; + return this; + } + + public boolean hasArguments() { + return !arguments.isEmpty(); + } + +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/CraterClientTickEvent.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/CraterClientTickEvent.java new file mode 100644 index 0000000..c68c596 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/CraterClientTickEvent.java @@ -0,0 +1,14 @@ +package com.hypherionmc.craterlib.api.events.client; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedClientLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public class CraterClientTickEvent extends CraterEvent { + + private final BridgedClientLevel level; + +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/CraterSinglePlayerEvent.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/CraterSinglePlayerEvent.java new file mode 100644 index 0000000..ab671f0 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/CraterSinglePlayerEvent.java @@ -0,0 +1,21 @@ +package com.hypherionmc.craterlib.api.events.client; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public class CraterSinglePlayerEvent extends CraterEvent { + + private final BridgedPlayer player; + + public static class PlayerLogin extends CraterSinglePlayerEvent { + + public PlayerLogin(BridgedPlayer player) { + super(player); + } + + } +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/LateInitEvent.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/LateInitEvent.java new file mode 100644 index 0000000..33c06fb --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/LateInitEvent.java @@ -0,0 +1,16 @@ +package com.hypherionmc.craterlib.api.events.client; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.client.BridgedMinecraft; +import com.hypherionmc.craterlib.nojang.client.BridgedOptions; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public class LateInitEvent extends CraterEvent { + + private final BridgedMinecraft minecraft; + private final BridgedOptions options; + +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/PlayerJoinRealmEvent.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/PlayerJoinRealmEvent.java new file mode 100644 index 0000000..1ef95de --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/PlayerJoinRealmEvent.java @@ -0,0 +1,14 @@ +package com.hypherionmc.craterlib.api.events.client; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.realmsclient.dto.BridgedRealmsServer; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public class PlayerJoinRealmEvent extends CraterEvent { + + private final BridgedRealmsServer server; + +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/ScreenEvent.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/ScreenEvent.java new file mode 100644 index 0000000..034ca4a --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/ScreenEvent.java @@ -0,0 +1,27 @@ +package com.hypherionmc.craterlib.api.events.client; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.client.gui.BridgedScreen; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; + +@Getter +@RequiredArgsConstructor +public class ScreenEvent extends CraterEvent { + + private final BridgedScreen screen; + + @Getter + public static class Opening extends ScreenEvent { + + private final BridgedScreen currentScreen; + @Setter private BridgedScreen newScreen; + + public Opening(BridgedScreen currentScreen, BridgedScreen newScreen) { + super(newScreen); + this.currentScreen = currentScreen; + this.newScreen = newScreen; + } + } +} \ No newline at end of file diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/common/CraterPlayerDeathEvent.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/common/CraterPlayerDeathEvent.java new file mode 100644 index 0000000..02cd63c --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/common/CraterPlayerDeathEvent.java @@ -0,0 +1,21 @@ +package com.hypherionmc.craterlib.api.events.common; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.ChatUtils; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; +import net.minecraft.world.damagesource.DamageSource; + +@RequiredArgsConstructor +@Getter +public class CraterPlayerDeathEvent extends CraterEvent { + + private final BridgedPlayer player; + private final DamageSource damageSource; + + public Component getDeathMessage() { + return ChatUtils.mojangToAdventure(damageSource.getLocalizedDeathMessage(player.toMojang())); + } +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterAdvancementEvent.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterAdvancementEvent.java new file mode 100644 index 0000000..5cd659d --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterAdvancementEvent.java @@ -0,0 +1,34 @@ +package com.hypherionmc.craterlib.api.events.server; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.advancements.BridgedAdvancement; +import com.hypherionmc.craterlib.nojang.advancements.BridgedDisplayInfo; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import lombok.Getter; +import net.kyori.adventure.text.Component; + +import java.util.Optional; + +@Getter +public class CraterAdvancementEvent extends CraterEvent { + + private final BridgedAdvancement advancement; + private final BridgedPlayer player; + private final Component title; + private final Component description; + + public CraterAdvancementEvent(BridgedPlayer player, BridgedAdvancement advancement) { + this.advancement = advancement; + this.player = player; + + Optional displayInfo = advancement.displayInfo(); + + if (displayInfo.isPresent()) { + this.title = displayInfo.get().displayName(); + this.description = displayInfo.get().description(); + } else { + this.title = Component.text("Unknown"); + this.description = Component.text("Unknown"); + } + } +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterCommandEvent.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterCommandEvent.java new file mode 100644 index 0000000..364cc22 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterCommandEvent.java @@ -0,0 +1,58 @@ +package com.hypherionmc.craterlib.api.events.server; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.ChatUtils; +import com.mojang.brigadier.ParseResults; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.context.StringRange; +import lombok.Getter; +import lombok.Setter; +import net.kyori.adventure.text.Component; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.arguments.ComponentArgument; +import net.minecraft.world.entity.player.Player; +import org.jetbrains.annotations.Nullable; + +@Getter +public class CraterCommandEvent extends CraterEvent { + + private final ParseResults parseResults; + @Setter private Throwable exception; + private final String command; + + private CraterCommandEvent(ParseResults parseResults, String command) { + this.parseResults = parseResults; + this.command = command; + } + + public static CraterCommandEvent of(ParseResults stack, String command) { + return new CraterCommandEvent(stack, command); + } + + public String getCommandString() { + return parseResults.getReader().getString(); + } + + @Nullable + public BridgedPlayer getPlayer() { + try { + Player p = parseResults.getContext().getLastChild().getSource().getPlayer(); + + if (p != null) + return BridgedPlayer.of(p); + } catch (Exception ignored) {} + + return null; + } + + public String getTarget() { + CommandContext context = parseResults.getContext().build(parseResults.getReader().getString()); + StringRange selector_range = parseResults.getContext().getArguments().get("targets").getRange(); + return context.getInput().substring(selector_range.getStart(), selector_range.getEnd()); + } + + public Component getMessage() { + return ChatUtils.mojangToAdventure(ComponentArgument.getComponent(parseResults.getContext().build(parseResults.getReader().getString()), "message")); + } +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterPlayerEvent.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterPlayerEvent.java new file mode 100644 index 0000000..7eb3701 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterPlayerEvent.java @@ -0,0 +1,30 @@ +package com.hypherionmc.craterlib.api.events.server; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public class CraterPlayerEvent extends CraterEvent { + + private final BridgedPlayer player; + + public static class PlayerLoggedIn extends CraterPlayerEvent { + + public PlayerLoggedIn(BridgedPlayer player) { + super(player); + } + + } + + public static class PlayerLoggedOut extends CraterPlayerEvent { + + public PlayerLoggedOut(BridgedPlayer player) { + super(player); + } + + } + +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterRegisterCommandEvent.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterRegisterCommandEvent.java new file mode 100644 index 0000000..269065a --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterRegisterCommandEvent.java @@ -0,0 +1,15 @@ +package com.hypherionmc.craterlib.api.events.server; + +import com.hypherionmc.craterlib.api.commands.CraterCommand; +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.commands.CommandsRegistry; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +public class CraterRegisterCommandEvent extends CraterEvent { + + public void registerCommand(CraterCommand cmd) { + CommandsRegistry.INSTANCE.registerCommand(cmd); + } + +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterServerChatEvent.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterServerChatEvent.java new file mode 100644 index 0000000..b25170a --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterServerChatEvent.java @@ -0,0 +1,25 @@ +package com.hypherionmc.craterlib.api.events.server; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.core.event.annot.Cancellable; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import lombok.Getter; +import lombok.Setter; +import net.kyori.adventure.text.Component; + +@Cancellable +@Getter +public class CraterServerChatEvent extends CraterEvent { + + public final String message, username; + public final BridgedPlayer player; + @Setter private Component component; + + public CraterServerChatEvent(BridgedPlayer player, String message, Component component) { + this.message = message; + this.player = player; + this.username = player.getGameProfile().getName(); + this.component = component; + } + +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterServerLifecycleEvent.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterServerLifecycleEvent.java new file mode 100644 index 0000000..3f87d04 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterServerLifecycleEvent.java @@ -0,0 +1,34 @@ +package com.hypherionmc.craterlib.api.events.server; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +public class CraterServerLifecycleEvent extends CraterEvent { + + @RequiredArgsConstructor + @Getter + public static class Starting extends CraterServerLifecycleEvent { + private final BridgedMinecraftServer server; + } + + @RequiredArgsConstructor + @Getter + public static class Started extends CraterServerLifecycleEvent { + private final BridgedMinecraftServer server; + } + + @RequiredArgsConstructor + @Getter + public static class Stopping extends CraterServerLifecycleEvent { + private final BridgedMinecraftServer server; + } + + @RequiredArgsConstructor + @Getter + public static class Stopped extends CraterServerLifecycleEvent { + private final BridgedMinecraftServer server; + } + +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/MessageBroadcastEvent.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/MessageBroadcastEvent.java new file mode 100644 index 0000000..6b11404 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/MessageBroadcastEvent.java @@ -0,0 +1,20 @@ +package com.hypherionmc.craterlib.api.events.server; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; + +import java.util.function.Function; + +@RequiredArgsConstructor +@Getter +public class MessageBroadcastEvent extends CraterEvent { + + private final Component component; + private final Function function; + private final boolean bl; + private final String threadName; + +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/PlayerPreLoginEvent.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/PlayerPreLoginEvent.java new file mode 100644 index 0000000..eaabffd --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/PlayerPreLoginEvent.java @@ -0,0 +1,20 @@ +package com.hypherionmc.craterlib.api.events.server; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.authlib.BridgedGameProfile; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import net.kyori.adventure.text.Component; + +import java.net.SocketAddress; + +@RequiredArgsConstructor +@Getter +public class PlayerPreLoginEvent extends CraterEvent { + + private final SocketAddress address; + private final BridgedGameProfile gameProfile; + @Setter private Component message; + +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/networking/CraterNetworkHandler.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/networking/CraterNetworkHandler.java new file mode 100644 index 0000000..6b57e96 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/api/networking/CraterNetworkHandler.java @@ -0,0 +1,28 @@ +package com.hypherionmc.craterlib.api.networking; + +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; + +import java.util.List; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public interface CraterNetworkHandler { + + void sendToServer(T packet); + + void sendToServer(T packet, boolean ignoreCheck); + + void sendToClient(T packet, BridgedPlayer player); + + default void sendToClients(T packet, List players) { + for (BridgedPlayer player : players) { + sendToClient(packet, player); + } + } + + default void sendToAllClients(T packet, BridgedMinecraftServer server) { + sendToClients(packet, server.getPlayers()); + } +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/CraterConfigScreen.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/CraterConfigScreen.java new file mode 100644 index 0000000..81a9ed7 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/CraterConfigScreen.java @@ -0,0 +1,393 @@ +package com.hypherionmc.craterlib.client.gui.config; + +import com.hypherionmc.craterlib.CraterConstants; +import com.hypherionmc.craterlib.client.gui.config.widgets.*; +import com.hypherionmc.craterlib.core.config.ModuleConfig; +import com.hypherionmc.craterlib.core.config.annotations.HideFromScreen; +import com.hypherionmc.craterlib.core.config.annotations.SubConfig; +import com.hypherionmc.craterlib.core.config.annotations.Tooltip; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.*; +import me.hypherionmc.moonconfig.core.conversion.SpecComment; +import net.minecraft.ChatFormatting; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.ConfirmScreen; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Mth; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.joml.Matrix4f; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * @author HypherionSA + */ +public class CraterConfigScreen extends Screen { + public static final float SCROLLBAR_BOTTOM_COLOR = .5f; + public static final float SCROLLBAR_TOP_COLOR = .67f; + private static final int TOP = 26; + private static final int BOTTOM = 24; + private final Screen parent; + private final List> options = new ArrayList<>(); + private final ModuleConfig config; + public double scrollerAmount; + private boolean dragging; + + public CraterConfigScreen(ModuleConfig config, Screen parent, Object subConfig) { + super(Component.translatable("cl." + config.getClass().getSimpleName().toLowerCase() + ".title")); + this.parent = parent; + this.config = config; + if (subConfig != null) { + setupScreenFromConfig(subConfig, subConfig.getClass()); + } else { + setupScreenFromConfig(config, config.getClass()); + } + } + + public CraterConfigScreen(ModuleConfig config, Screen parent) { + this(config, parent, null); + } + + private static Component toText(Enum val) { + return Component.translatable(val.toString()); + } + + private static Component toText(Boolean bool) { + return Component.translatable(bool.toString()); + } + + private void setupScreenFromConfig(Object object, Class clazz) { + while (clazz != Object.class) { + for (Field field : clazz.getDeclaredFields()) { + final int fieldModifiers = field.getModifiers(); + if (object == null || Modifier.isStatic(fieldModifiers) || Modifier.isTransient(fieldModifiers)) { + continue; + } + + try { + if (!field.isAccessible()) { + field.setAccessible(true); + } + if (field.isAnnotationPresent(HideFromScreen.class)) + return; + Object val = field.get(object); + + /* Lang Stuff */ + String baseLangKey = "cl." + clazz.getSimpleName().toLowerCase() + "." + field.getName().toLowerCase(); + String[] tooltipLang = field.isAnnotationPresent(SpecComment.class) ? new String[]{field.getAnnotation(SpecComment.class).value()} : new String[0]; + if (field.isAnnotationPresent(Tooltip.class)) { + tooltipLang = field.getAnnotation(Tooltip.class).value(); + } + + add(Component.translatable(baseLangKey), + val, + () -> val, + (ret) -> { + try { + field.set(object, ret); + config.saveConfig(config); + } catch (IllegalAccessException e) { + CraterConstants.LOG.error("Failed to update value for field {} in config {}", field.getName(), config.getConfigName(), e); + } + }, + field.isAnnotationPresent(SubConfig.class), + tooltipLang + ); + } catch (IllegalAccessException e) { + CraterConstants.LOG.error("Failed to access value for field {} in config {}", field.getName(), config.getConfigName(), e); + } + } + clazz = clazz.getSuperclass(); + } + } + + public void add(Component text, T value, @Nullable Supplier defaultValue, Consumer savingConsumer, boolean isSubConfig, String... langKeys) { + Option option = (Option) createOption(value, isSubConfig); + option.text = text; + option.defaultValue = defaultValue; + option.savingConsumer = savingConsumer; + option.originalValue = value; + option.value = value; + option.setLangKeys(List.of(langKeys)); + options.add(option); + option.onAdd(); + } + + private Option createOption(T value, boolean isSubConfig) { + if (value instanceof Enum) { + Object[] objects = value.getClass().getEnumConstants(); + return new ToggleButton>((List) Arrays.asList(objects), CraterConfigScreen::toText); + } + if (value instanceof Boolean) { + return new ToggleButton<>(Arrays.asList(Boolean.TRUE, Boolean.FALSE), CraterConfigScreen::toText); + } + if (value instanceof String) { + return new TextConfigOption<>(Function.identity(), Function.identity()); + } + if (value instanceof Integer) { + return new TextConfigOption<>(Objects::toString, Integer::valueOf); + } + if (value instanceof Long) { + return new TextConfigOption<>(Objects::toString, Long::valueOf); + } + if (value instanceof Double) { + return new TextConfigOption<>(Objects::toString, Double::valueOf); + } + if (value instanceof Float) { + return new TextConfigOption<>(Objects::toString, Float::valueOf); + } + if (value instanceof BigInteger) { + return new TextConfigOption<>(Objects::toString, BigInteger::new); + } + if (value instanceof BigDecimal) { + return new TextConfigOption<>(Objects::toString, BigDecimal::new); + } + if (value instanceof ResourceLocation) { + return new TextConfigOption<>(Objects::toString, ResourceLocation::new); + } + if (isSubConfig) { + return new SubConfigWidget<>(config, this, value); + } + throw new IllegalArgumentException(String.valueOf(value)); + } + + @Override + protected void init() { + super.init(); + ((List) children()).addAll(options); + + int buttonWidths = Math.min(200, (width - 50 - 12) / 3); + addRenderableWidget(new InternalConfigButton(this, width / 2 - buttonWidths - 3, height - 22, buttonWidths, 20, Component.empty(), true)); + addRenderableWidget(new InternalConfigButton(this, width / 2 + 3, height - 22, buttonWidths, 20, Component.empty(), false)); + } + + @Override + public void render(@NotNull GuiGraphics matrices, int mouseX, int mouseY, float delta) { + overlayBackground(matrices.pose(), TOP, height - BOTTOM, 32); + renderScrollBar(); + + matrices.pose().pushPose(); + matrices.pose().translate(0, 0, 500.0); + overlayBackground(matrices.pose(), 0, TOP, 64); + overlayBackground(matrices.pose(), height - BOTTOM, height, 64); + renderShadow(matrices.pose()); + matrices.drawCenteredString(font, getTitle(), width / 2, 9, 0xFFFFFF); + matrices.pose().popPose(); + + super.render(matrices, mouseX, mouseY, delta); + + int y = (int) (TOP + 4 - Math.round(scrollerAmount)); + for (Option option : options) { + int height1 = option.height(); + option.render(minecraft, font, 40, y, width - 80, height1, matrices, mouseX, mouseY, delta); + renderConfigTooltip(matrices, font, mouseX, mouseY, 40, y, font.width(option.text), height1, option.text.getString(), option.getLangKeys().toArray(new String[0])); + y += height1; + } + } + + private void renderScrollBar() { + int listHeight = height - BOTTOM - TOP; + int totalHeight = totalHeight(); + if (totalHeight > listHeight) { + int maxScroll = Math.max(0, totalHeight - listHeight); + int height = listHeight * listHeight / totalHeight; + height = Mth.clamp(height, 32, listHeight); + height = Math.max(10, height); + int minY = Math.min(Math.max((int) scrollerAmount * (listHeight - height) / maxScroll + TOP, TOP), this.height - BOTTOM - height); + + int scrollbarPositionMaxX = width; + int scrollbarPositionMinX = scrollbarPositionMaxX - 6; + + int maxY = this.height - BOTTOM; + //RenderSystem.disableTexture(); + Tesselator tesselator = Tesselator.getInstance(); + BufferBuilder buffer = tesselator.getBuilder(); + RenderSystem.setShader(GameRenderer::getPositionColorShader); + buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR); + + buffer.vertex(scrollbarPositionMinX, maxY, 0.0D).color(0, 0, 0, 255).endVertex(); + buffer.vertex(scrollbarPositionMaxX, maxY, 0.0D).color(0, 0, 0, 255).endVertex(); + buffer.vertex(scrollbarPositionMaxX, TOP, 0.0D).color(0, 0, 0, 255).endVertex(); + buffer.vertex(scrollbarPositionMinX, TOP, 0.0D).color(0, 0, 0, 255).endVertex(); + + buffer.vertex(scrollbarPositionMinX, minY + height, 0.0D).color(SCROLLBAR_BOTTOM_COLOR, SCROLLBAR_BOTTOM_COLOR, SCROLLBAR_BOTTOM_COLOR, 1).endVertex(); + buffer.vertex(scrollbarPositionMaxX, minY + height, 0.0D).color(SCROLLBAR_BOTTOM_COLOR, SCROLLBAR_BOTTOM_COLOR, SCROLLBAR_BOTTOM_COLOR, 1).endVertex(); + buffer.vertex(scrollbarPositionMaxX, minY, 0.0D).color(SCROLLBAR_BOTTOM_COLOR, SCROLLBAR_BOTTOM_COLOR, SCROLLBAR_BOTTOM_COLOR, 1).endVertex(); + buffer.vertex(scrollbarPositionMinX, minY, 0.0D).color(SCROLLBAR_BOTTOM_COLOR, SCROLLBAR_BOTTOM_COLOR, SCROLLBAR_BOTTOM_COLOR, 1).endVertex(); + buffer.vertex(scrollbarPositionMinX, (minY + height - 1), 0.0D).color(SCROLLBAR_TOP_COLOR, SCROLLBAR_TOP_COLOR, SCROLLBAR_TOP_COLOR, 1).endVertex(); + buffer.vertex((scrollbarPositionMaxX - 1), (minY + height - 1), 0.0D).color(SCROLLBAR_TOP_COLOR, SCROLLBAR_TOP_COLOR, SCROLLBAR_TOP_COLOR, 1).endVertex(); + buffer.vertex((scrollbarPositionMaxX - 1), minY, 0.0D).color(SCROLLBAR_TOP_COLOR, SCROLLBAR_TOP_COLOR, SCROLLBAR_TOP_COLOR, 1).endVertex(); + buffer.vertex(scrollbarPositionMinX, minY, 0.0D).color(SCROLLBAR_TOP_COLOR, SCROLLBAR_TOP_COLOR, SCROLLBAR_TOP_COLOR, 1).endVertex(); + tesselator.end(); + RenderSystem.disableBlend(); + //RenderSystem.enableTexture(); + } + } + + private void renderShadow(PoseStack matrices) { + Tesselator tesselator = Tesselator.getInstance(); + BufferBuilder buffer = tesselator.getBuilder(); + RenderSystem.enableBlend(); + RenderSystem.blendFuncSeparate(770, 771, 0, 1); + //RenderSystem.disableTexture(); + RenderSystem.setShader(GameRenderer::getPositionTexColorShader); + Matrix4f matrix = matrices.last().pose(); + buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX_COLOR); + buffer.vertex(matrix, 0, TOP + 4, 0.0F).uv(0, 1).color(0, 0, 0, 0).endVertex(); + buffer.vertex(matrix, width, TOP + 4, 0.0F).uv(1, 1).color(0, 0, 0, 0).endVertex(); + buffer.vertex(matrix, width, TOP, 0.0F).uv(1, 0).color(0, 0, 0, 185).endVertex(); + buffer.vertex(matrix, 0, TOP, 0.0F).uv(0, 0).color(0, 0, 0, 185).endVertex(); + buffer.vertex(matrix, 0, height - BOTTOM, 0.0F).uv(0, 1).color(0, 0, 0, 185).endVertex(); + buffer.vertex(matrix, width, height - BOTTOM, 0.0F).uv(1, 1).color(0, 0, 0, 185).endVertex(); + buffer.vertex(matrix, width, height - BOTTOM - 4, 0.0F).uv(1, 0).color(0, 0, 0, 0).endVertex(); + buffer.vertex(matrix, 0, height - BOTTOM - 4, 0.0F).uv(0, 0).color(0, 0, 0, 0).endVertex(); + tesselator.end(); + //RenderSystem.enableTexture(); + RenderSystem.disableBlend(); + } + + protected void overlayBackground(PoseStack matrices, int h1, int h2, int color) { + overlayBackground(matrices.last().pose(), 0, h1, width, h2, color, color, color, 255, 255); + } + + protected void overlayBackground(Matrix4f matrix, int minX, int minY, int maxX, int maxY, int red, int green, int blue, int startAlpha, int endAlpha) { + Tesselator tesselator = Tesselator.getInstance(); + BufferBuilder buffer = tesselator.getBuilder(); + RenderSystem.setShader(GameRenderer::getPositionTexColorShader); + RenderSystem.setShaderTexture(0, Screen.BACKGROUND_LOCATION); + RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f); + buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX_COLOR); + buffer.vertex(matrix, minX, maxY, 0.0F).uv(minX / 32.0F, maxY / 32.0F).color(red, green, blue, endAlpha).endVertex(); + buffer.vertex(matrix, maxX, maxY, 0.0F).uv(maxX / 32.0F, maxY / 32.0F).color(red, green, blue, endAlpha).endVertex(); + buffer.vertex(matrix, maxX, minY, 0.0F).uv(maxX / 32.0F, minY / 32.0F).color(red, green, blue, startAlpha).endVertex(); + buffer.vertex(matrix, minX, minY, 0.0F).uv(minX / 32.0F, minY / 32.0F).color(red, green, blue, startAlpha).endVertex(); + tesselator.end(); + } + + public int scrollHeight() { + int totalHeight = totalHeight(); + int listHeight = height - BOTTOM - TOP; + if (totalHeight <= listHeight) { + return 0; + } + return totalHeight - listHeight; + } + + public int totalHeight() { + int i = 8; + for (Option option : options) { + i += option.height(); + } + return i; + } + + public boolean hasErrors() { + for (Option option : options) { + if (option.hasErrors) { + return true; + } + } + return false; + } + + public boolean isEdited() { + for (Option option : options) { + if (option.isEdited()) { + return true; + } + } + return false; + } + + public void save() { + for (Option option : options) { + option.save(); + option.originalValue = option.value; + } + } + + @Override + public void onClose() { + if (isEdited()) { + minecraft.setScreen(new ConfirmScreen(this::acceptConfirm, Component.translatable("t.clc.quit_config"), + Component.translatable("t.clc.quit_config_sure"), + Component.translatable("t.clc.quit_discard"), + Component.translatable("gui.cancel"))); + } else { + minecraft.setScreen(parent); + } + } + + @Override + public boolean mouseScrolled(double d, double e, double f, double g) { + if (e >= TOP && e <= height - BOTTOM) { + scrollerAmount = Mth.clamp(scrollerAmount - f * 16.0D, 0, scrollHeight()); + return true; + } + return super.mouseScrolled(d, e, f, g); + } + + @Override + public boolean mouseClicked(double d, double e, int i) { + this.dragging = i == 0 && d >= width - 6 && d < width; + return super.mouseClicked(d, e, i) || dragging; + } + + @Override + public boolean mouseDragged(double d, double e, int i, double f, double g) { + if (super.mouseDragged(d, e, i, f, g)) { + return true; + } + if (i != 0 || !this.dragging) { + return false; + } + if (e < TOP) { + scrollerAmount = 0; + } else if (e > height - BOTTOM) { + scrollerAmount = scrollHeight(); + } else { + double h = Math.max(1, this.scrollHeight()); + int j = height - BOTTOM - TOP; + int k = Mth.clamp((int) ((float) (j * j) / (float) this.scrollHeight()), 32, j - 8); + double l = Math.max(1.0, h / (double) (j - k)); + scrollerAmount = Mth.clamp(scrollerAmount + g * l, 0, scrollHeight()); + } + return true; + } + + private void acceptConfirm(boolean t) { + if (!t) { + minecraft.setScreen(this); + } else { + minecraft.setScreen(parent); + } + } + + private void renderConfigTooltip(GuiGraphics stack, Font font, int mouseX, int mouseY, int startX, int startY, int sizeX, int sizeY, String title, String... description) { + if (mouseX > startX && mouseX < startX + sizeX) { + if (mouseY > startY && mouseY < startY + sizeY) { + List list = new ArrayList<>(); + list.add(Component.translatable(ChatFormatting.BOLD + "" + ChatFormatting.YELLOW + title)); + for (String desc : description) { + list.add(Component.translatable(desc)); + } + stack.renderComponentTooltip(font, list, mouseX, mouseY); + } + } + } +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/AbstractConfigWidget.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/AbstractConfigWidget.java new file mode 100644 index 0000000..ed34f6b --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/AbstractConfigWidget.java @@ -0,0 +1,27 @@ +package com.hypherionmc.craterlib.client.gui.config.widgets; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.client.gui.components.EditBox; + +/** + * Copied from Cloth Config Lite + * ... + */ +public class AbstractConfigWidget extends BaseWidget { + + public static final int buttonWidth = 200; + public static final int buttonHeight = 20; + public W widget; + + @Override + public void render(Minecraft minecraft, Font font, int x, int y, int width, int height, GuiGraphics matrices, int mouseX, int mouseY, float delta) { + super.render(minecraft, font, x, y, width, height, matrices, mouseX, mouseY, delta); + int i = (widget instanceof EditBox ? 1 : 0); + widget.setX(x + width - 200 - resetButtonOffset + i); + widget.setY(y + i + 1); + widget.render(matrices, mouseX, mouseY, delta); + } +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/BaseWidget.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/BaseWidget.java new file mode 100644 index 0000000..0583e61 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/BaseWidget.java @@ -0,0 +1,61 @@ +package com.hypherionmc.craterlib.client.gui.config.widgets; + +import net.minecraft.ChatFormatting; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.Button; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.network.chat.TextColor; + +/** + * Copied from Cloth Config Lite + * ... + */ +public class BaseWidget extends Option { + + public static final int resetButtonOffset = 48; + private final Button resetButton = addChild(Button.builder(Component.literal("Reset"), this::onResetPressed).size(46, 20).build()); + private boolean hideReset = false; + + private boolean isSubConfig = false; + + private void onResetPressed(Button button) { + value = defaultValue.get(); + reset(); + } + + public void hideReset() { + this.hideReset = true; + } + + public boolean isSubConfig() { + return isSubConfig; + } + + public void setSubConfig(boolean subConfig) { + isSubConfig = subConfig; + } + + @Override + public void render(Minecraft minecraft, Font font, int x, int y, int width, int height, GuiGraphics matrices, int mouseX, int mouseY, float delta) { + MutableComponent text = Component.literal(this.text.getString()); + boolean edited = isEdited() || hasErrors; + if (edited) { + text.withStyle(ChatFormatting.ITALIC); + if (hasErrors) { + text.withStyle(style -> style.withColor(TextColor.fromRgb(16733525))); + } + } else { + text.withStyle(ChatFormatting.GRAY); + } + matrices.drawString(font, text, x, y + font.lineHeight - 2, 0xFFFFFF); + resetButton.setX(x + width - 46); + resetButton.setY(y + 1); + resetButton.active = isNotDefault(); + if (!hideReset) { + resetButton.render(matrices, mouseX, mouseY, delta); + } + } +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/InternalConfigButton.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/InternalConfigButton.java new file mode 100644 index 0000000..2478c7d --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/InternalConfigButton.java @@ -0,0 +1,51 @@ +package com.hypherionmc.craterlib.client.gui.config.widgets; + +import com.hypherionmc.craterlib.client.gui.config.CraterConfigScreen; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.AbstractButton; +import net.minecraft.client.gui.narration.NarratedElementType; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.network.chat.Component; + +/** + * @author HypherionSA + */ +public class InternalConfigButton extends AbstractButton { + + CraterConfigScreen screen; + boolean cancel; + + public InternalConfigButton(CraterConfigScreen screen, int i, int j, int k, int l, Component component, boolean cancel) { + super(i, j, k, l, component); + this.screen = screen; + this.cancel = cancel; + } + + @Override + protected void renderWidget(GuiGraphics arg, int i, int j, float f) { + if (cancel) { + setMessage(Component.translatable(screen.isEdited() ? "t.clc.cancel_discard" : "gui.cancel")); + } else { + boolean hasErrors = screen.hasErrors(); + active = screen.isEdited() && !hasErrors; + setMessage(Component.translatable(hasErrors ? "t.clc.error" : "t.clc.save")); + } + super.renderWidget(arg, i, j, f); + } + + @Override + protected void updateWidgetNarration(NarrationElementOutput narrationElementOutput) { + narrationElementOutput.add(NarratedElementType.USAGE, getMessage()); + } + + @Override + public void onPress() { + if (cancel) { + screen.onClose(); + } else { + screen.save(); + } + } + + +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/Option.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/Option.java new file mode 100644 index 0000000..f49ca68 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/Option.java @@ -0,0 +1,71 @@ +package com.hypherionmc.craterlib.client.gui.config.widgets; + +import lombok.Getter; +import lombok.Setter; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.events.AbstractContainerEventHandler; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Supplier; + +/** + * Copied from Cloth Config Lite + * ... + */ +public abstract class Option extends AbstractContainerEventHandler { + + public Component text; + @Nullable + public Supplier defaultValue; + public Consumer savingConsumer; + public T originalValue; + public T value; + public boolean hasErrors; + public List children = new ArrayList<>(); + @Setter + @Getter + private List langKeys = new ArrayList<>(); + + public abstract void render(Minecraft minecraft, Font font, int x, int y, int width, int height, GuiGraphics matrices, int mouseX, int mouseY, float delta); + + public int height() { + return 22; + } + + @Override + public List children() { + return children; + } + + protected R addChild(R listener) { + ((List) children).add(listener); + return listener; + } + + public void onAdd() { + } + + protected void reset() { + onAdd(); + } + + public boolean isEdited() { + return !Objects.equals(originalValue, value); + } + + protected boolean isNotDefault() { + return defaultValue != null && !Objects.equals(defaultValue.get(), value); + } + + public void save() { + savingConsumer.accept(value); + } +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/SubConfigWidget.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/SubConfigWidget.java new file mode 100644 index 0000000..9426ccf --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/SubConfigWidget.java @@ -0,0 +1,40 @@ +package com.hypherionmc.craterlib.client.gui.config.widgets; + +import com.hypherionmc.craterlib.client.gui.config.CraterConfigScreen; +import com.hypherionmc.craterlib.core.config.ModuleConfig; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; + +/** + * @author HypherionSA + */ +public class SubConfigWidget extends AbstractConfigWidget { + + private final Object subConfig; + private final ModuleConfig config; + private final Screen screen; + + public SubConfigWidget(ModuleConfig config, Screen screen, Object subConfig) { + this.config = config; + this.subConfig = subConfig; + this.screen = screen; + + this.widget = addChild(Button.builder(Component.translatable("t.clc.opensubconfig"), this::openSubConfig).size(200, buttonHeight).build()); + } + + @Override + public void render(Minecraft minecraft, Font font, int x, int y, int width, int height, GuiGraphics matrices, int mouseX, int mouseY, float delta) { + this.text = Component.literal(subConfig.getClass().getSimpleName().toLowerCase()); + this.hideReset(); + super.render(minecraft, font, x, y, width, height, matrices, mouseX, mouseY, delta); + } + + private void openSubConfig(Button button) { + Minecraft.getInstance().setScreen(new CraterConfigScreen(config, screen, subConfig)); + } + +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/TextConfigOption.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/TextConfigOption.java new file mode 100644 index 0000000..cc05cec --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/TextConfigOption.java @@ -0,0 +1,45 @@ +package com.hypherionmc.craterlib.client.gui.config.widgets; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; + +import java.util.function.Function; + +/** + * Copied from Cloth Config Lite + * ... + */ +public class TextConfigOption extends AbstractConfigWidget { + + private final Function toString; + private final Function fromString; + + public TextConfigOption(Function toString, Function fromString) { + this.toString = toString; + this.fromString = fromString; + this.widget = addChild(new WrappedEditBox(Minecraft.getInstance().font, 0, 0, 198, 18, null)); + } + + @Override + public void onAdd() { + widget.setMaxLength(1000000); + widget.setValue(toString.apply(value)); + widget.setResponder(this::update); + } + + @Override + public void render(Minecraft minecraft, Font font, int x, int y, int width, int height, GuiGraphics matrices, int mouseX, int mouseY, float delta) { + widget.setTextColor(hasErrors ? 16733525 : 14737632); + super.render(minecraft, font, x, y, width, height, matrices, mouseX, mouseY, delta); + } + + private void update(String s) { + try { + this.value = fromString.apply(s); + this.hasErrors = false; + } catch (Exception e) { + this.hasErrors = true; + } + } +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/ToggleButton.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/ToggleButton.java new file mode 100644 index 0000000..03ea11d --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/ToggleButton.java @@ -0,0 +1,33 @@ +package com.hypherionmc.craterlib.client.gui.config.widgets; + +import net.minecraft.client.gui.components.Button; +import net.minecraft.network.chat.Component; + +import java.util.List; +import java.util.function.Function; + +/** + * Copied from Cloth Config Lite + * ... + */ +public class ToggleButton extends AbstractConfigWidget { + + private final List options; + private final Function toComponent; + + public ToggleButton(List options, Function toComponent) { + this.options = options; + this.toComponent = toComponent; + this.widget = addChild(Button.builder(Component.empty(), this::switchNext).size(buttonWidth, buttonHeight).build()); + } + + @Override + public void onAdd() { + widget.setMessage(toComponent.apply(value)); + } + + private void switchNext(Button button) { + value = options.get((options.indexOf(value) + 1) % options.size()); + onAdd(); + } +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/WrappedEditBox.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/WrappedEditBox.java new file mode 100644 index 0000000..42c0c0c --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/WrappedEditBox.java @@ -0,0 +1,29 @@ +package com.hypherionmc.craterlib.client.gui.config.widgets; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.components.EditBox; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.NotNull; + +/** + * @author HypherionSA + */ +public class WrappedEditBox extends EditBox { + + public WrappedEditBox(Font font, int i, int j, int k, int l, @NotNull Component component) { + super(font, i, j, k, l, component); + } + + @Override + public void setFocused(boolean bl) { + for (GuiEventListener child : Minecraft.getInstance().screen.children()) { + if (child instanceof TextConfigOption option) { + WrappedEditBox box = option.widget; + super.setFocused(box == this); + } + } + super.setFocused(bl); + } +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/client/mentions/MentionCondition.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/client/mentions/MentionCondition.java new file mode 100644 index 0000000..e8bffde --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/client/mentions/MentionCondition.java @@ -0,0 +1,13 @@ +package com.hypherionmc.craterlib.client.mentions; + +/** + * Based on ... + */ +@FunctionalInterface +public interface MentionCondition { + + boolean shouldAddMention(String currentWord); + + MentionCondition ALWAYS = currentWord -> true; + +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/client/mentions/MentionsController.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/client/mentions/MentionsController.java new file mode 100644 index 0000000..bad3b43 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/client/mentions/MentionsController.java @@ -0,0 +1,47 @@ +package com.hypherionmc.craterlib.client.mentions; + +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; +import lombok.Getter; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Based on ... + */ +public class MentionsController { + + private static final Map> mentions = new LinkedHashMap<>(); + private static final Map mentionConditions = new LinkedHashMap<>(); + @Getter + private static boolean lastMentionConditional = true; + + public static void registerMention(ResourceIdentifier mentionClass, Collection suggestions, MentionCondition condition) { + mentions.put(mentionClass, suggestions); + mentionConditions.put(mentionClass, condition); + } + + public static Collection getMentions(String currentWord) { + ArrayList applicableMentions = new ArrayList<>(); + lastMentionConditional = false; + + mentionConditions.forEach((mention, condition) -> { + boolean shouldSuggest = condition.shouldAddMention(currentWord); + if (!shouldSuggest) return; + + if (!lastMentionConditional && condition != MentionCondition.ALWAYS) { + lastMentionConditional = true; + } + + applicableMentions.addAll(mentions.get(mention)); + }); + + return applicableMentions; + } + + public static boolean hasMentions() { + return !mentions.isEmpty(); + } +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/config/ConfigController.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/config/ConfigController.java new file mode 100644 index 0000000..41c9471 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/config/ConfigController.java @@ -0,0 +1,49 @@ +package com.hypherionmc.craterlib.core.config; + +import com.hypherionmc.craterlib.CraterConstants; +import lombok.Getter; +import me.hypherionmc.moonconfig.core.file.FileWatcher; +import org.jetbrains.annotations.ApiStatus; + +import java.io.Serializable; +import java.util.HashMap; + +/** + * @author HypherionSA + * Controls Config File Reloads and Events + */ +public final class ConfigController implements Serializable { + + /** + * Cache of registered configs + */ + @Getter + private static final HashMap monitoredConfigs = new HashMap<>(); + + /** + * INTERNAL METHOD - Register and watch the config + * + * @param config - The config class to register and watch + */ + @ApiStatus.Internal + public static void register_config(ModuleConfig config) { + if (monitoredConfigs.containsKey(config)) { + CraterConstants.LOG.error("Failed to register " + config.getConfigPath().getName() + ". Config already registered"); + } else { + FileWatcher configWatcher = new FileWatcher(); + try { + configWatcher.setWatch(config.getConfigPath(), () -> { + if (!config.isSaveCalled()) { + CraterConstants.LOG.info("Sending Reload Event for: " + config.getConfigPath().getName()); + config.configReloaded(); + } + }); + } catch (Exception e) { + CraterConstants.LOG.error("Failed to register " + config.getConfigPath().getName() + " for auto reloading. " + e.getMessage()); + } + monitoredConfigs.put(config, configWatcher); + CraterConstants.LOG.info("Registered " + config.getConfigPath().getName() + " successfully!"); + } + } + +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/config/ModuleConfig.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/config/ModuleConfig.java new file mode 100644 index 0000000..181efdc --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/config/ModuleConfig.java @@ -0,0 +1,184 @@ +package com.hypherionmc.craterlib.core.config; + +import me.hypherionmc.moonconfig.core.CommentedConfig; +import me.hypherionmc.moonconfig.core.Config; +import me.hypherionmc.moonconfig.core.conversion.ObjectConverter; +import me.hypherionmc.moonconfig.core.file.CommentedFileConfig; + +import java.io.File; + +/** + * @author HypherionSA + * Base Config class containing the save, upgrading and loading logic. + * All config classes must extend this class + */ +public class ModuleConfig { + + /* Final Variables */ + private final transient File configPath; + private final transient String networkID; + + private final transient String configName; + + private final transient String modId; + + private transient boolean isSaveCalled = false; + + /** + * Set up the config + * + * @param modId - The ID of the Mod/Module the config belongs to + * @param configName - The name of the config file, excluding extension + */ + public ModuleConfig(String modId, String configName) { + this(modId, "", configName); + } + + public ModuleConfig(String modId, String subFolder, String configName) { + /* Preserve the order of the config values */ + Config.setInsertionOrderPreserved(true); + + /* Configure Paths and Network SYNC ID */ + File configDir = new File("config" + (subFolder.isEmpty() ? "" : File.separator + subFolder)); + configPath = new File(configDir + File.separator + configName + ".toml"); + networkID = modId + ":conf_" + configName.replace("-", "_"); + this.modId = modId; + this.configName = configName; + + /* Check if the required directories exists, otherwise we create them */ + if (!configDir.exists()) { + configDir.mkdirs(); + } + } + + /** + * This method has to be called in the config constructor. This creates or upgrades the config file as needed + * + * @param config - The config class to use + */ + public void registerAndSetup(ModuleConfig config) { + if (!configPath.exists() || configPath.length() < 2) { + saveConfig(config); + } else { + migrateConfig(config); + } + /* Register the Config for Watching and events */ + ConfigController.register_config(this); + this.configReloaded(); + } + + /** + * Save the config to the disk + * + * @param conf - The config class to serialize and save + */ + public void saveConfig(ModuleConfig conf) { + this.isSaveCalled = true; + /* Set up the Serializer and Config Object */ + ObjectConverter converter = new ObjectConverter(); + CommentedFileConfig config = CommentedFileConfig.builder(configPath).build(); + + /* Save the config and fire the reload events */ + converter.toConfig(conf, config); + config.save(); + configReloaded(); + this.isSaveCalled = false; + } + + /** + * Load the config from the file into a Class + * + * @param conf - The config Class to load + * @return - Returns the loaded version of the class + */ + public T loadConfig(Object conf) { + /* Set up the Serializer and Config Object */ + ObjectConverter converter = new ObjectConverter(); + CommentedFileConfig config = CommentedFileConfig.builder(configPath).build(); + config.load(); + + /* Load the config and return the loaded config */ + converter.toObject(config, conf); + return (T) conf; + } + + /** + * INTERNAL METHOD - Upgrades the config files in the events the config structure changes + * + * @param conf - The config class to load + */ + public void migrateConfig(ModuleConfig conf) { + /* Set up the Serializer and Config Objects */ + CommentedFileConfig config = CommentedFileConfig.builder(configPath).build(); + CommentedFileConfig newConfig = CommentedFileConfig.builder(configPath).build(); + config.load(); + + /* Upgrade the config */ + new ObjectConverter().toConfig(conf, newConfig); + updateConfigValues(config, newConfig, newConfig, ""); + newConfig.save(); + + config.close(); + newConfig.close(); + } + + public void updateConfigValues(CommentedConfig oldConfig, CommentedConfig newConfig, CommentedConfig outputConfig, String subKey) { + /* Loop over the config keys and check what has changed */ + newConfig.valueMap().forEach((key, value) -> { + String finalKey = subKey + (subKey.isEmpty() ? "" : ".") + key; + if (value instanceof CommentedConfig commentedConfig) { + updateConfigValues(oldConfig, commentedConfig, outputConfig, finalKey); + } else { + outputConfig.set(finalKey, + oldConfig.contains(finalKey) ? oldConfig.get(finalKey) : value); + } + }); + } + + /** + * Get the location of the config file + * + * @return - The FILE object containing the config file + */ + public File getConfigPath() { + return configPath; + } + + /** + * Get the NETWORK SYNC ID + * + * @return - Returns the Sync ID in format modid:config_name + */ + public String getNetworkID() { + return networkID; + } + + /** + * Fired whenever changes to the config are detected + */ + public void configReloaded() { + + } + + /** + * Get the name of the Config File + * + * @return + */ + public String getConfigName() { + return configName; + } + + /** + * Get the MODID of the Module the config is registered to + * + * @return + */ + public String getModId() { + return modId; + } + + public boolean isSaveCalled() { + return isSaveCalled; + } +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/HideFromScreen.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/HideFromScreen.java new file mode 100644 index 0000000..512a025 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/HideFromScreen.java @@ -0,0 +1,8 @@ +package com.hypherionmc.craterlib.core.config.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface HideFromScreen { +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/NoConfigScreen.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/NoConfigScreen.java new file mode 100644 index 0000000..4288ee0 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/NoConfigScreen.java @@ -0,0 +1,12 @@ +package com.hypherionmc.craterlib.core.config.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * @author HypherionSA + * Allows Modules to disable Automatic Config Screens + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface NoConfigScreen { +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/SubConfig.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/SubConfig.java new file mode 100644 index 0000000..e3ec808 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/SubConfig.java @@ -0,0 +1,16 @@ +package com.hypherionmc.craterlib.core.config.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author HypherionSA + * Used to determine if a Config section should be rendered as a separate screen + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface SubConfig { +} + diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/Syncable.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/Syncable.java new file mode 100644 index 0000000..838b35b --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/Syncable.java @@ -0,0 +1,15 @@ +package com.hypherionmc.craterlib.core.config.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author HypherionSA + * //TODO Currently unused, but to be used with Config Syncing in the future + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Syncable { +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/Tooltip.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/Tooltip.java new file mode 100644 index 0000000..538471c --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/Tooltip.java @@ -0,0 +1,16 @@ +package com.hypherionmc.craterlib.core.config.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author HypherionSA + * Provides tooltips to the config GUI + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Tooltip { + String[] value(); +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEvent.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEvent.java new file mode 100644 index 0000000..89ff00a --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEvent.java @@ -0,0 +1,30 @@ +package com.hypherionmc.craterlib.core.event; + +import com.hypherionmc.craterlib.core.event.annot.Cancellable; +import com.hypherionmc.craterlib.core.event.exception.CraterEventCancellationException; + +public class CraterEvent { + + private boolean canceled = false; + + private boolean canCancel() { + return this.getClass().isAnnotationPresent(Cancellable.class); + } + + public void cancelEvent() { + try { + if (!this.canCancel()) { + throw new CraterEventCancellationException("Tried to cancel non-cancelable event: " + this.getClass().getName()); + } + + this.canceled = true; + } catch (Exception e) { + e.printStackTrace(); + } + } + + public boolean wasCancelled() { + return this.canceled; + } + +} \ No newline at end of file diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEventBus.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEventBus.java new file mode 100644 index 0000000..74eeecc --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEventBus.java @@ -0,0 +1,241 @@ +package com.hypherionmc.craterlib.core.event; + +import com.hypherionmc.craterlib.CraterConstants; +import com.hypherionmc.craterlib.core.event.annot.CraterEventListener; +import org.slf4j.Logger; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.*; +import java.util.function.Consumer; + +public final class CraterEventBus { + + public static final CraterEventBus INSTANCE = new CraterEventBus(); + private static final Logger LOGGER = CraterConstants.LOG; + private final Map, List> events = new HashMap<>(); + + public void postEvent(CraterEvent event) { + if (eventsRegisteredForType(event.getClass())) { + List l = new ArrayList<>(events.get(event.getClass())); + l.sort((o1, o2) -> Integer.compare(o2.priority, o1.priority)); + + for (ListenerContainer c : l) { + c.notifyListener(event); + } + } + } + + public void registerEventListener(Class clazz) { + this.registerListenerMethods(this.getEventMethodsOf(clazz)); + } + + public void registerEventListener(Object object) { + this.registerListenerMethods(this.getEventMethodsOf(object)); + } + + private void registerListenerMethods(List methods) { + for (EventMethod m : methods) { + Consumer listener = (event) -> { + try { + m.method.invoke(m.parentObject, event); + } catch (Exception e) { + throw new RuntimeException(e); + } + }; + + ListenerContainer container = new ListenerContainer(m.eventType, listener, m.priority); + container.listenerParentClassName = m.parentClass.getName(); + container.listenerMethodName = m.method.getName(); + this.registerListener(container); + } + } + + private List getEventMethodsOf(Object objectOrClass) { + List l = new ArrayList<>(); + try { + if (objectOrClass != null) { + boolean isClass = (objectOrClass instanceof Class); + Class c = isClass ? (Class) objectOrClass : objectOrClass.getClass(); + for (Method m : c.getMethods()) { + if (isClass && Modifier.isStatic(m.getModifiers())) { + EventMethod em = EventMethod.tryCreateFrom(new AnalyzedMethod(m, c)); + if ((em != null) && this.hasEventAnnotation(em)) l.add(em); + } + if (!isClass && !Modifier.isStatic(m.getModifiers())) { + EventMethod em = EventMethod.tryCreateFrom(new AnalyzedMethod(m, objectOrClass)); + if ((em != null) && this.hasEventAnnotation(em)) l.add(em); + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return l; + } + + private boolean hasEventAnnotation(EventMethod m) { + for (Annotation a : m.annotations) { + if (a instanceof CraterEventListener) return true; + } + return false; + } + + public void registerListener(Consumer listener, Class eventType) { + this.registerListener(listener, eventType, 0); + } + + public void registerListener(Consumer listener, Class eventType, int priority) { + this.registerListener(new ListenerContainer(eventType, listener, priority)); + } + + private void registerListener(ListenerContainer listenerContainer) { + try { + if (!eventsRegisteredForType(listenerContainer.eventType)) { + events.put(listenerContainer.eventType, new ArrayList<>()); + } + events.get(listenerContainer.eventType).add(listenerContainer); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public boolean eventsRegisteredForType(Class eventType) { + if (eventType == null) { + return false; + } + return this.events.containsKey(eventType); + } + + protected final static class ListenerContainer { + + private final Consumer listener; + private final Class eventType; + private final int priority; + private String listenerParentClassName = "[unknown]"; + private String listenerMethodName = "[unknown]"; + + private ListenerContainer(Class eventType, Consumer listener, int priority) { + this.eventType = eventType; + this.listener = listener; + this.priority = priority; + } + + private void notifyListener(CraterEvent event) { + try { + this.listener.accept(event); + } catch (Exception e) { + LOGGER.error("##################################"); + LOGGER.error("Failed to notify event listener!"); + LOGGER.error("Event Type: " + this.eventType.getName()); + LOGGER.error("Listener Parent Class Name: " + this.listenerParentClassName); + LOGGER.error("Listener Method Name In Parent Class: " + this.listenerMethodName); + LOGGER.error("##################################"); + e.printStackTrace(); + } + } + } + + protected static class AnalyzedMethod { + + protected Method method; + protected Object parentObject; + protected Class parentClass; + protected boolean isStatic; + protected List annotations = new ArrayList<>(); + + protected AnalyzedMethod() { + } + + protected AnalyzedMethod(Method method, Object parentObjectOrClass) { + this.method = method; + this.parentObject = parentObjectOrClass; + this.parentClass = this.tryGetParentClass(); + this.isStatic = Modifier.isStatic(method.getModifiers()); + collectMethodAnnotations(this.isStatic ? null : this.parentObject.getClass(), this.method, this.annotations); + } + + protected static void collectMethodAnnotations(Class c, Method m, List addToList) { + try { + addToList.addAll(Arrays.asList(m.getAnnotations())); + if (!Modifier.isStatic(m.getModifiers()) && (c != null)) { + Class sc = c.getSuperclass(); + if (sc != null) { + try { + Method sm = sc.getMethod(m.getName(), m.getParameterTypes()); + collectMethodAnnotations(sc, sm, addToList); + } catch (Exception ignored) { + } + } + } + } catch (Exception ignored) { + } + } + + protected Class tryGetParentClass() { + if (this.parentObject instanceof Class) { + return (Class) this.parentObject; + } + return this.parentObject.getClass(); + } + + } + + protected static class EventMethod extends AnalyzedMethod { + + protected final int priority; + protected final Class eventType; + + protected EventMethod(AnalyzedMethod method) { + + super(); + this.method = method.method; + this.parentObject = method.parentObject; + this.parentClass = method.parentClass; + this.isStatic = method.isStatic; + this.annotations = method.annotations; + + this.priority = this.tryGetPriority(); + this.eventType = this.tryGetEventType(); + + } + + protected static EventMethod tryCreateFrom(AnalyzedMethod method) { + EventMethod em = new EventMethod(method); + return (em.eventType != null) ? em : null; + } + + protected Class tryGetEventType() { + try { + if (this.method != null) { + Class[] params = this.method.getParameterTypes(); + if (params.length > 0) { + Class firstParam = params[0]; + if (CraterEvent.class.isAssignableFrom(firstParam)) { + return (Class) firstParam; + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + protected int tryGetPriority() { + try { + for (Annotation a : this.annotations) { + if (a instanceof CraterEventListener craterEventListener) { + return craterEventListener.priority(); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return 0; + } + + } + +} \ No newline at end of file diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEventPriority.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEventPriority.java new file mode 100644 index 0000000..0c97134 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEventPriority.java @@ -0,0 +1,13 @@ +package com.hypherionmc.craterlib.core.event; + +public class CraterEventPriority { + + public static final int LOWEST = -3; + public static final int LOWER = -2; + public static final int LOW = -1; + public static final int NORMAL = 0; + public static final int HIGH = 1; + public static final int HIGHER = 2; + public static final int HIGHEST = 3; + +} \ No newline at end of file diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/event/annot/Cancellable.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/event/annot/Cancellable.java new file mode 100644 index 0000000..ee3c2fc --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/event/annot/Cancellable.java @@ -0,0 +1,8 @@ +package com.hypherionmc.craterlib.core.event.annot; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface Cancellable { +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/event/annot/CraterEventListener.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/event/annot/CraterEventListener.java new file mode 100644 index 0000000..7b42490 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/event/annot/CraterEventListener.java @@ -0,0 +1,11 @@ +package com.hypherionmc.craterlib.core.event.annot; + +import com.hypherionmc.craterlib.core.event.CraterEventPriority; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface CraterEventListener { + int priority() default CraterEventPriority.NORMAL; +} \ No newline at end of file diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/event/exception/CraterEventCancellationException.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/event/exception/CraterEventCancellationException.java new file mode 100644 index 0000000..fe2a8a3 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/event/exception/CraterEventCancellationException.java @@ -0,0 +1,9 @@ +package com.hypherionmc.craterlib.core.event.exception; + +public class CraterEventCancellationException extends Exception { + + public CraterEventCancellationException(String msg) { + super(msg); + } + +} \ No newline at end of file diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/event/package-info.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/event/package-info.java new file mode 100644 index 0000000..5ec1749 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/event/package-info.java @@ -0,0 +1,5 @@ +/** + * The event system code in this package is based on, and adapted from Acara (https://github.com/Keksuccino/acara/) + * and is licensed under MIT by Keksuccino + */ +package com.hypherionmc.craterlib.core.event; \ No newline at end of file diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/networking/CraterPacketNetwork.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/networking/CraterPacketNetwork.java new file mode 100644 index 0000000..081c8fb --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/networking/CraterPacketNetwork.java @@ -0,0 +1,43 @@ +package com.hypherionmc.craterlib.core.networking; + +import com.hypherionmc.craterlib.core.networking.data.PacketContext; +import com.hypherionmc.craterlib.nojang.network.BridgedFriendlyByteBuf; +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; +import lombok.Getter; + +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +@Getter +public class CraterPacketNetwork { + + private final PacketRegistry packetRegistry; + public static CraterPacketNetwork INSTANCE; + private static DeferredPacketRegistrar delayedHandler; + + public CraterPacketNetwork(PacketRegistry registry) { + INSTANCE = this; + this.packetRegistry = registry; + getDelayedHandler().registerQueuedPackets(registry); + } + + private static DeferredPacketRegistrar getDelayedHandler() { + if (delayedHandler == null) { + delayedHandler = new DeferredPacketRegistrar(); + } + return delayedHandler; + } + + public static PacketRegistrar registerPacket(ResourceIdentifier id, Class messageType, BiConsumer encoder, Function decoder, Consumer> handler) { + if (INSTANCE != null) { + return INSTANCE.packetRegistry.registerPacket(id, messageType, encoder, decoder, handler); + } else { + return getDelayedHandler().registerPacket(id, messageType, encoder, decoder, handler); + } + } + +} \ No newline at end of file diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/networking/DeferredPacketRegistrar.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/networking/DeferredPacketRegistrar.java new file mode 100644 index 0000000..25556f4 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/networking/DeferredPacketRegistrar.java @@ -0,0 +1,41 @@ +package com.hypherionmc.craterlib.core.networking; + +import com.hypherionmc.craterlib.core.networking.data.PacketContext; +import com.hypherionmc.craterlib.core.networking.data.PacketHolder; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.nojang.network.BridgedFriendlyByteBuf; +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public class DeferredPacketRegistrar implements PacketRegistrar { + + private static final Map, PacketHolder> QUEUED_PACKET_MAP = new HashMap<>(); + + @Override + public PacketSide side() { + return PacketSide.CLIENT; + } + + @Override + public PacketRegistrar registerPacket(ResourceIdentifier packetIdentifier, Class messageType, BiConsumer encoder, Function decoder, Consumer> handler) { + PacketHolder container = new PacketHolder<>(packetIdentifier, messageType, encoder, decoder, handler); + QUEUED_PACKET_MAP.put(messageType, container); + return this; + } + + + public void registerQueuedPackets(PacketRegistry packetRegistration) { + if (!QUEUED_PACKET_MAP.isEmpty()) { + packetRegistration.PACKET_MAP.putAll(QUEUED_PACKET_MAP); + QUEUED_PACKET_MAP.forEach((aClass, container) -> packetRegistration.registerPacket(container)); + } + } +} \ No newline at end of file diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/networking/PacketRegistrar.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/networking/PacketRegistrar.java new file mode 100644 index 0000000..3ea55fa --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/networking/PacketRegistrar.java @@ -0,0 +1,21 @@ +package com.hypherionmc.craterlib.core.networking; + +import com.hypherionmc.craterlib.core.networking.data.PacketContext; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.nojang.network.BridgedFriendlyByteBuf; +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; + +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public interface PacketRegistrar { + + PacketSide side(); + + PacketRegistrar registerPacket(ResourceIdentifier id, Class messageType, BiConsumer encoder, Function decoder, Consumer> handler); + +} \ No newline at end of file diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/networking/PacketRegistry.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/networking/PacketRegistry.java new file mode 100644 index 0000000..d1ec06c --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/networking/PacketRegistry.java @@ -0,0 +1,41 @@ +package com.hypherionmc.craterlib.core.networking; + +import com.hypherionmc.craterlib.api.networking.CraterNetworkHandler; +import com.hypherionmc.craterlib.core.networking.data.PacketContext; +import com.hypherionmc.craterlib.core.networking.data.PacketHolder; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.nojang.network.BridgedFriendlyByteBuf; +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public abstract class PacketRegistry implements CraterNetworkHandler, PacketRegistrar { + + protected final Map, PacketHolder> PACKET_MAP = new HashMap<>(); + + protected final PacketSide side; + + public PacketRegistry(PacketSide side) { + this.side = side; + } + + public PacketRegistrar registerPacket(ResourceIdentifier id, Class messageType, BiConsumer encoder, Function decoder, Consumer> handler) { + PacketHolder holder = new PacketHolder<>(id, messageType, encoder, decoder, handler); + PACKET_MAP.put(messageType, holder); + registerPacket(holder); + return this; + } + + public PacketSide side() { + return side; + } + + protected abstract void registerPacket(PacketHolder packetHolder); +} \ No newline at end of file diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketContext.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketContext.java new file mode 100644 index 0000000..fc432dc --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketContext.java @@ -0,0 +1,15 @@ +package com.hypherionmc.craterlib.core.networking.data; + +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import org.jetbrains.annotations.Nullable; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public record PacketContext(@Nullable BridgedPlayer sender, T message, PacketSide side) { + + public PacketContext(T message, PacketSide side) { + this(null, message, side); + } + +} \ No newline at end of file diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketHolder.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketHolder.java new file mode 100644 index 0000000..14b6947 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketHolder.java @@ -0,0 +1,18 @@ +package com.hypherionmc.craterlib.core.networking.data; + +import com.hypherionmc.craterlib.nojang.network.BridgedFriendlyByteBuf; +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; + +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public record PacketHolder(ResourceIdentifier type, + Class messageType, + BiConsumer encoder, + Function decoder, + Consumer> handler) { +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketSide.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketSide.java new file mode 100644 index 0000000..ad6f8ec --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketSide.java @@ -0,0 +1,13 @@ +package com.hypherionmc.craterlib.core.networking.data; + +public enum PacketSide { + CLIENT, + SERVER; + + public PacketSide flipped() { + if (CLIENT.equals(this)) + return SERVER; + + return CLIENT; + } +} \ No newline at end of file diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/platform/ClientPlatform.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/platform/ClientPlatform.java new file mode 100644 index 0000000..089fb36 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/platform/ClientPlatform.java @@ -0,0 +1,23 @@ +package com.hypherionmc.craterlib.core.platform; + +import com.hypherionmc.craterlib.nojang.client.BridgedMinecraft; +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedClientLevel; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.InternalServiceUtil; +import net.minecraft.network.Connection; + +/** + * @author HypherionSA + */ +public interface ClientPlatform { + + public final ClientPlatform INSTANCE = InternalServiceUtil.load(ClientPlatform.class); + + BridgedMinecraft getClientInstance(); + + BridgedPlayer getClientPlayer(); + + BridgedClientLevel getClientLevel(); + + Connection getClientConnection(); +} \ No newline at end of file diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/platform/CommonPlatform.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/platform/CommonPlatform.java new file mode 100644 index 0000000..ee7f944 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/platform/CommonPlatform.java @@ -0,0 +1,15 @@ +package com.hypherionmc.craterlib.core.platform; + +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import com.hypherionmc.craterlib.utils.InternalServiceUtil; + +/** + * @author HypherionSA + */ +public interface CommonPlatform { + + public CommonPlatform INSTANCE = InternalServiceUtil.load(CommonPlatform.class); + + BridgedMinecraftServer getMCServer(); + +} \ No newline at end of file diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/platform/CompatUtils.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/platform/CompatUtils.java new file mode 100644 index 0000000..fb745fc --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/platform/CompatUtils.java @@ -0,0 +1,13 @@ +package com.hypherionmc.craterlib.core.platform; + +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.InternalServiceUtil; + +public interface CompatUtils { + + public static final CompatUtils INSTANCE = InternalServiceUtil.load(CompatUtils.class); + + boolean isPlayerActive(BridgedPlayer player); + String getSkinUUID(BridgedPlayer player); + +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/platform/Environment.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/platform/Environment.java new file mode 100644 index 0000000..2ef219e --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/platform/Environment.java @@ -0,0 +1,18 @@ +package com.hypherionmc.craterlib.core.platform; + +/** + * @author HypherionSA + */ +public enum Environment { + CLIENT, + SERVER, + UNKNOWN; + + public boolean isClient() { + return this == CLIENT; + } + + public boolean isServer() { + return this == SERVER; + } +} \ No newline at end of file diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/platform/ModloaderEnvironment.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/platform/ModloaderEnvironment.java new file mode 100644 index 0000000..c1bc176 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/platform/ModloaderEnvironment.java @@ -0,0 +1,32 @@ +package com.hypherionmc.craterlib.core.platform; + +import com.hypherionmc.craterlib.utils.InternalServiceUtil; + +import java.io.File; + +/** + * @author HypherionSA + * Helper class to provide information about the ModLoader + */ +public interface ModloaderEnvironment { + + public final ModloaderEnvironment INSTANCE = InternalServiceUtil.load(ModloaderEnvironment.class); + + boolean isFabric(); + + String getGameVersion(); + + File getGameFolder(); + + File getConfigFolder(); + + File getModsFolder(); + + Environment getEnvironment(); + + boolean isModLoaded(String modid); + + boolean isDevEnv(); + + int getModCount(); +} \ No newline at end of file diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordEventHandlers.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordEventHandlers.java new file mode 100644 index 0000000..3f1ffcb --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordEventHandlers.java @@ -0,0 +1,90 @@ +package com.hypherionmc.craterlib.core.rpcsdk; + +import com.hypherionmc.craterlib.core.rpcsdk.callbacks.*; +import com.sun.jna.Structure; + +import java.util.Arrays; +import java.util.List; + +/** + * @author HypherionSA + * Class containing references to all available discord event handles. + * Registering a handler is optional, and non-assigned handlers will be ignored + */ +public class DiscordEventHandlers extends Structure { + + // Callback for when the RPC was initialized successfully + public ReadyCallback ready; + + // Callback for when the Discord connection was ended + public DisconnectedCallback disconnected; + + // Callback for when a Discord Error occurs + public ErroredCallback errored; + + // Callback for when a player joins the game + public JoinGameCallback joinGame; + + // Callback for when a player spectates the game + public SpectateGameCallback spectateGame; + + // Callback for when a players request to join your game + public JoinRequestCallback joinRequest; + + /** + * DO NOT TOUCH THIS... EVER! + */ + @Override + protected List getFieldOrder() { + return Arrays.asList( + "ready", + "disconnected", + "errored", + "joinGame", + "spectateGame", + "joinRequest" + ); + } + + public static class Builder { + private final DiscordEventHandlers handlers; + + public Builder() { + this.handlers = new DiscordEventHandlers(); + } + + public Builder ready(ReadyCallback readyCallback) { + handlers.ready = readyCallback; + return this; + } + + public Builder disconnected(DisconnectedCallback disconnectedCallback) { + handlers.disconnected = disconnectedCallback; + return this; + } + + public Builder errored(ErroredCallback erroredCallback) { + handlers.errored = erroredCallback; + return this; + } + + public Builder joinGame(JoinGameCallback joinGameCallback) { + handlers.joinGame = joinGameCallback; + return this; + } + + public Builder spectateGame(SpectateGameCallback spectateGameCallback) { + handlers.spectateGame = spectateGameCallback; + return this; + } + + public Builder joinRequest(JoinRequestCallback joinRequestCallback) { + handlers.joinRequest = joinRequestCallback; + return this; + } + + public DiscordEventHandlers build() { + return handlers; + } + } +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordRPC.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordRPC.java new file mode 100644 index 0000000..7cf53ff --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordRPC.java @@ -0,0 +1,99 @@ +package com.hypherionmc.craterlib.core.rpcsdk; + +import com.sun.jna.Library; +import com.sun.jna.Native; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author HypherionSA + * Java Wrapper of the Discord-RPC Library + */ +public interface DiscordRPC extends Library { + + DiscordRPC INSTANCE = Native.load("discord-rpc", DiscordRPC.class); + + /** + * Open a New RPC Connection + * + * @param applicationId The ID of the Application the RPC is tied to + * @param handlers Optional Event Callback Handlers + * @param autoRegister Auto Register the running game + * @param steamId Steam ID of the game + */ + void Discord_Initialize(@NotNull String applicationId, @Nullable DiscordEventHandlers handlers, boolean autoRegister, @Nullable String steamId); + + /** + * Shutdown the RPC instance and disconnect from discord + */ + void Discord_Shutdown(); + + /** + * Need to be called manually at least every 2 seconds, to allow RPC updates + * and callback handlers to fire + */ + void Discord_RunCallbacks(); + + /** + * Not sure about this. Believe it needs to be called manually in some circumstances + */ + void Discord_UpdateConnection(); + + /** + * Update the Rich Presence + * + * @param struct Constructed {@link DiscordRichPresence} + */ + void Discord_UpdatePresence(@Nullable DiscordRichPresence struct); + + /** + * Clear the current Rich Presence + */ + void Discord_ClearPresence(); + + /** + * Respond to Join/Spectate callback + * + * @param userid The Discord User ID of the user that initiated the request + * @param reply Reply to the request. See {@link DiscordReply} + */ + void Discord_Respond(@NotNull String userid, int reply); + + /** + * Replace the already registered {@link DiscordEventHandlers} + * + * @param handlers The new handlers to apply + */ + void Discord_UpdateHandlers(@Nullable DiscordEventHandlers handlers); + + /** + * Register the executable of the application/game + * Only applicable when autoRegister is set to false + * + * @param applicationId The Application ID + * @param command The Launch command of the game + *

+ * NB: THIS DOES NOT WORK WITH MINECRAFT + */ + void Discord_Register(String applicationId, String command); + + /** + * Register the Steam executable of the application/game + * + * @param applicationId The Application ID + * @param steamId The Steam ID of the application/game + */ + void Discord_RegisterSteamGame(String applicationId, String steamId); + + public enum DiscordReply { + NO(0), + YES(1), + IGNORE(2); + + public final int reply; + + DiscordReply(int reply) { + this.reply = reply; + } + } +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordRichPresence.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordRichPresence.java new file mode 100644 index 0000000..51fd720 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordRichPresence.java @@ -0,0 +1,248 @@ +package com.hypherionmc.craterlib.core.rpcsdk; + +import com.hypherionmc.craterlib.core.rpcsdk.helpers.RPCButton; +import com.sun.jna.Structure; +import org.jetbrains.annotations.NotNull; + +import java.time.OffsetDateTime; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * @author HypherionSA + * Class reprenting a Discord RPC activity + */ +public class DiscordRichPresence extends Structure { + + // First line of text on the RPC + public String state; + + // Second line of text on the RPC + public String details; + + // Time the activity started in UNIX-Timestamp format + public long startTimestamp; + + // Time the activity will end in UNIX-Timestamp format + public long endTimestamp; + + // URL or Asset key of the Large Image + public String largeImageKey; + + // Hover text to display when hovering the Large Image + public String largeImageText; + + // URL or Asset key of the Small Image + public String smallImageKey; + + // Hover text to display when hovering the Small Image + public String smallImageText; + + // Id of the player's party, lobby, or group. + public String partyId; + + // Current size of the player's party, lobby, or group. + public int partySize; + + // Maximum size of the player's party, lobby, or group. + public int partyMax; + + // Unused + public String partyPrivacy; + + // Unused. + public String matchSecret; + + // Unique hashed string for chat invitations and Ask to Join. + public String joinSecret; + + // Unique hashed string for Spectate button. + public String spectateSecret; + + // Label of the First RPC Button + public String button_label_1; + + // URL of the First RPC Button + public String button_url_1; + + // Label of the Second RPC Button + public String button_label_2; + + // URL of the Second RPC Button + public String button_url_2; + + // Unused + public int instance; + + public DiscordRichPresence() { + setStringEncoding("UTF-8"); + } + + /** + * DO NOT TOUCH THIS... EVER! + */ + @Override + protected List getFieldOrder() { + return Arrays.asList( + "state", + "details", + "startTimestamp", + "endTimestamp", + "largeImageKey", + "largeImageText", + "smallImageKey", + "smallImageText", + "partyId", + "partySize", + "partyMax", + "partyPrivacy", + "matchSecret", + "joinSecret", + "spectateSecret", + "button_label_1", + "button_url_1", + "button_label_2", + "button_url_2", + "instance" + ); + } + + public static class Builder { + private final DiscordRichPresence rpc; + + public Builder(String state) { + rpc = new DiscordRichPresence(); + + if (state != null && !state.isEmpty()) { + rpc.state = state.substring(0, Math.min(state.length(), 128)); + } + } + + public Builder setDetails(String details) { + if (details != null && !details.isEmpty()) { + rpc.details = details.substring(0, Math.min(details.length(), 128)); + } + return this; + } + + public Builder setStartTimestamp(long timestamp) { + rpc.startTimestamp = timestamp; + return this; + } + + public Builder setStartTimestamp(OffsetDateTime timestamp) { + rpc.startTimestamp = timestamp.toEpochSecond(); + return this; + } + + public Builder setEndTimestamp(long timestamp) { + rpc.endTimestamp = timestamp; + return this; + } + + public Builder setEndTimestamp(OffsetDateTime timestamp) { + rpc.endTimestamp = timestamp.toEpochSecond(); + return this; + } + + public Builder setLargeImage(String key) { + return this.setLargeImage(key, ""); + } + + public Builder setLargeImage(@NotNull String key, String text) { + // Null check used for users blatantly ignoring the NotNull marker + if ((text != null && !text.isEmpty()) && key != null) { + throw new IllegalArgumentException("Image key cannot be null when assigning a hover text"); + } + + rpc.largeImageKey = key; + rpc.largeImageText = text; + return this; + } + + public Builder setSmallImage(String key) { + return this.setSmallImage(key, ""); + } + + public Builder setSmallImage(@NotNull String key, String text) { + // Null check used for users blatantly ignoring the NotNull marker + if ((text != null && !text.isEmpty()) && key != null) { + throw new IllegalArgumentException("Image key cannot be null when assigning a hover text"); + } + + rpc.smallImageKey = key; + rpc.smallImageText = text; + return this; + } + + public Builder setParty(String party, int size, int max) { + // Buttons are present, ignore + if ((rpc.button_label_1 != null && rpc.button_label_1.isEmpty()) || (rpc.button_label_2 != null && rpc.button_label_2.isEmpty())) + return this; + + rpc.partyId = party; + rpc.partySize = size; + rpc.partyMax = max; + return this; + } + + public Builder setSecrets(String match, String join, String spectate) { + // Buttons are present, ignore + if ((rpc.button_label_1 != null && rpc.button_label_1.isEmpty()) || (rpc.button_label_2 != null && rpc.button_label_2.isEmpty())) + return this; + + rpc.matchSecret = match; + rpc.joinSecret = join; + rpc.spectateSecret = spectate; + return this; + } + + public Builder setSecrets(String join, String spectate) { + // Buttons are present, ignore + if ((rpc.button_label_1 != null && rpc.button_label_1.isEmpty()) || (rpc.button_label_2 != null && rpc.button_label_2.isEmpty())) + return this; + + rpc.joinSecret = join; + rpc.spectateSecret = spectate; + return this; + } + + public Builder setInstance(boolean i) { + // Buttons are present, ignore + if ((rpc.button_label_1 != null && rpc.button_label_1.isEmpty()) || (rpc.button_label_2 != null && rpc.button_label_2.isEmpty())) + return this; + + rpc.instance = i ? 1 : 0; + return this; + } + + public Builder setButtons(RPCButton button) { + return this.setButtons(Collections.singletonList(button)); + } + + public Builder setButtons(RPCButton button1, RPCButton button2) { + return this.setButtons(Arrays.asList(button1, button2)); + } + + public Builder setButtons(List rpcButtons) { + // Limit to 2 Buttons. Discord Limitation + if (rpcButtons != null && !rpcButtons.isEmpty()) { + int length = Math.min(rpcButtons.size(), 2); + rpc.button_label_1 = rpcButtons.get(0).getLabel(); + rpc.button_url_1 = rpcButtons.get(0).getUrl(); + + if (length == 2) { + rpc.button_label_2 = rpcButtons.get(1).getLabel(); + rpc.button_url_2 = rpcButtons.get(1).getUrl(); + } + } + + return this; + } + + public DiscordRichPresence build() { + return rpc; + } + } +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordUser.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordUser.java new file mode 100644 index 0000000..e8bc085 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordUser.java @@ -0,0 +1,39 @@ +package com.hypherionmc.craterlib.core.rpcsdk; + +import com.sun.jna.Structure; + +import java.util.Arrays; +import java.util.List; + +/** + * @author HypherionSA + * Class representing the Discord User + */ +public class DiscordUser extends Structure { + + // The User ID of the User + public String userId; + + // The Username of the User + public String username; + + // The unique identifier of the user. Discontinued by Discord + @Deprecated + public String discriminator; + + // The avatar has of the user + public String avatar; + + /** + * DO NOT TOUCH THIS... EVER! + */ + @Override + protected List getFieldOrder() { + return Arrays.asList( + "userId", + "username", + "discriminator", + "avatar" + ); + } +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/DisconnectedCallback.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/DisconnectedCallback.java new file mode 100644 index 0000000..7ea59e1 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/DisconnectedCallback.java @@ -0,0 +1,18 @@ +package com.hypherionmc.craterlib.core.rpcsdk.callbacks; + +import com.sun.jna.Callback; + +/** + * @author HypherionSA + * Callback for when the Discord RPC disconnects + */ +public interface DisconnectedCallback extends Callback { + + /** + * Called when RPC disconnected + * + * @param errorCode Error code if any + * @param message Details about the disconnection + */ + void apply(int errorCode, String message); +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/ErroredCallback.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/ErroredCallback.java new file mode 100644 index 0000000..1f86c90 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/ErroredCallback.java @@ -0,0 +1,18 @@ +package com.hypherionmc.craterlib.core.rpcsdk.callbacks; + +import com.sun.jna.Callback; + +/** + * @author HypherionSA + * Callback for when the RPC ran into an error + */ +public interface ErroredCallback extends Callback { + + /** + * Called when an RPC error occurs + * + * @param errorCode Error code if any + * @param message Details about the error + */ + void apply(int errorCode, String message); +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/JoinGameCallback.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/JoinGameCallback.java new file mode 100644 index 0000000..cc752af --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/JoinGameCallback.java @@ -0,0 +1,17 @@ +package com.hypherionmc.craterlib.core.rpcsdk.callbacks; + +import com.sun.jna.Callback; + +/** + * @author HypherionSA + * Callback for when someone was approved to join your game + */ +public interface JoinGameCallback extends Callback { + + /** + * Called when someone joins a game from {@link JoinRequestCallback} + * + * @param joinSecret Secret or Password required to let the player join the game + */ + void apply(String joinSecret); +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/JoinRequestCallback.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/JoinRequestCallback.java new file mode 100644 index 0000000..115fd4f --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/JoinRequestCallback.java @@ -0,0 +1,19 @@ +package com.hypherionmc.craterlib.core.rpcsdk.callbacks; + +import com.hypherionmc.craterlib.core.rpcsdk.DiscordUser; +import com.sun.jna.Callback; + +/** + * @author HypherionSA + * Callback for when someone requests to join your game + */ +public interface JoinRequestCallback extends Callback { + + /** + * Called when someone clicks on the Join Game button + * + * @param user The Discord User trying to join your game + * @see DiscordUser + */ + void apply(DiscordUser user); +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/ReadyCallback.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/ReadyCallback.java new file mode 100644 index 0000000..66f3b59 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/ReadyCallback.java @@ -0,0 +1,19 @@ +package com.hypherionmc.craterlib.core.rpcsdk.callbacks; + +import com.hypherionmc.craterlib.core.rpcsdk.DiscordUser; +import com.sun.jna.Callback; + +/** + * @author HypherionSA + * Callback for when the RPC has connected successfully + */ +public interface ReadyCallback extends Callback { + + /** + * Called when the RPC is connected and ready to be used + * + * @param user The user the RPC is displayed on + * @see DiscordUser + */ + void apply(DiscordUser user); +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/SpectateGameCallback.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/SpectateGameCallback.java new file mode 100644 index 0000000..979e53d --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/SpectateGameCallback.java @@ -0,0 +1,17 @@ +package com.hypherionmc.craterlib.core.rpcsdk.callbacks; + +import com.sun.jna.Callback; + +/** + * @author HypherionSA + * Callback for when someone is requesting to spectate your game + */ +public interface SpectateGameCallback extends Callback { + + /** + * Called when joining the game + * + * @param spectateSecret Secret or Password required to let the player spectate + */ + void apply(String spectateSecret); +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/helpers/RPCButton.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/helpers/RPCButton.java new file mode 100644 index 0000000..ab2bdc8 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/helpers/RPCButton.java @@ -0,0 +1,55 @@ +package com.hypherionmc.craterlib.core.rpcsdk.helpers; + +import org.jetbrains.annotations.NotNull; + +import java.io.Serializable; + +/** + * @author HypherionSA + * Helper class to add Buttons to Discord Rich Presence + * This can not be used with Join/Spectate + */ +public class RPCButton implements Serializable { + + // The label of the button + private final String label; + + // The URL the button will open when clicked + private final String url; + + protected RPCButton(String label, String url) { + this.label = label; + this.url = url; + } + + /** + * Create a new RPC Button + * + * @param label The label of the button + * @param url The URL the button will open when clicked + * @return The constructed button + */ + public static RPCButton create(@NotNull String label, @NotNull String url) { + // Null check used here for users blatantly ignoring the NotNull marker + if (label == null || label.isEmpty() || url == null || url.isEmpty()) { + throw new IllegalArgumentException("RPC Buttons require both a label and url"); + } + + label = label.substring(0, Math.min(label.length(), 31)); + return new RPCButton(label, url); + } + + /** + * @return The label assigned to the button + */ + public String getLabel() { + return label; + } + + /** + * @return The URL of the button + */ + public String getUrl() { + return url; + } +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/mixin/ChatInputSuggestorMixin.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/mixin/ChatInputSuggestorMixin.java new file mode 100644 index 0000000..c48dfbc --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/mixin/ChatInputSuggestorMixin.java @@ -0,0 +1,84 @@ +package com.hypherionmc.craterlib.mixin; + +import com.hypherionmc.craterlib.client.mentions.MentionsController; +import net.minecraft.client.gui.components.CommandSuggestions; +import net.minecraft.client.gui.components.EditBox; +import org.objectweb.asm.Opcodes; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.ModifyVariable; +import org.spongepowered.asm.mixin.injection.Slice; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * @author HypherionSA + * Allow Users, Roles and Channels to be pingable from MC chat (Client Side) + */ +@Mixin(CommandSuggestions.class) +public abstract class ChatInputSuggestorMixin { + + @Shadow + public abstract void showSuggestions(boolean p_93931_); + + @Shadow @Final + EditBox input; + + @Shadow + private static int getLastWordIndex(String p_93913_) { + return 0; + } + + @Inject( + method = "updateCommandInfo", + at = @At( + value = "FIELD", + target = "Lnet/minecraft/client/gui/components/CommandSuggestions;pendingSuggestions:Ljava/util/concurrent/CompletableFuture;", + opcode = Opcodes.PUTFIELD, + shift = At.Shift.AFTER, + ordinal = 0 + ), + slice = @Slice( + from = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/gui/components/CommandSuggestions;getLastWordIndex(Ljava/lang/String;)I" + ) + ) + ) + private void injectSuggestions(CallbackInfo ci) { + if (MentionsController.hasMentions() && MentionsController.isLastMentionConditional()) { + this.showSuggestions(true); + } + } + + @SuppressWarnings("InvalidInjectorMethodSignature") + @ModifyVariable(method = "updateCommandInfo", at = @At(value = "STORE"), ordinal = 0, name = "collection") + private Collection injectMentions(Collection vanilla) { + if (!MentionsController.hasMentions()) + return vanilla; + + ArrayList newSuggest = new ArrayList<>(vanilla); + + String currentInput = this.input.getValue(); + int currentCursorPosition = this.input.getCursorPosition(); + + String textBeforeCursor = currentInput.substring(0, currentCursorPosition); + int startOfCurrentWord = getLastWordIndex(textBeforeCursor); + + String currentWord = textBeforeCursor.substring(startOfCurrentWord); + String finalWord = currentWord.replace("[", "").replace("]", ""); + + Collection mentions = MentionsController.getMentions(finalWord); + + if (!mentions.isEmpty()) { + mentions.forEach(m -> newSuggest.add("[" + m + "]")); + } + + return newSuggest; + } +} \ No newline at end of file diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/CommandMixin.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/CommandMixin.java new file mode 100644 index 0000000..bb9dec4 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/CommandMixin.java @@ -0,0 +1,37 @@ +package com.hypherionmc.craterlib.mixin.events; + +import com.google.common.base.Throwables; +import com.hypherionmc.craterlib.api.events.server.CraterCommandEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.mojang.brigadier.ParseResults; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +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(Commands.class) +public class CommandMixin { + + @Inject(method = "performCommand", + at = @At(value = "INVOKE", + target = "Lnet/minecraft/commands/Commands;finishParsing(Lcom/mojang/brigadier/ParseResults;Ljava/lang/String;Lnet/minecraft/commands/CommandSourceStack;)Lcom/mojang/brigadier/context/ContextChain;", + shift = At.Shift.BEFORE + ), cancellable = true + ) + private void injectCommandEvent(ParseResults stackParseResults, String command, CallbackInfo ci) { + CraterCommandEvent commandEvent = CraterCommandEvent.of(stackParseResults, command); + CraterEventBus.INSTANCE.postEvent(commandEvent); + if (commandEvent.wasCancelled()) { + ci.cancel(); + return; + } + + if (commandEvent.getException() != null) { + Throwables.throwIfUnchecked(commandEvent.getException()); + ci.cancel(); + } + } + +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerAdvancementsMixin.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerAdvancementsMixin.java new file mode 100644 index 0000000..30fa64f --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerAdvancementsMixin.java @@ -0,0 +1,31 @@ +package com.hypherionmc.craterlib.mixin.events; + +import com.hypherionmc.craterlib.api.events.server.CraterAdvancementEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.advancements.BridgedAdvancement; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraft.advancements.Advancement; +import net.minecraft.advancements.AdvancementHolder; +import net.minecraft.server.PlayerAdvancements; +import net.minecraft.server.level.ServerPlayer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(PlayerAdvancements.class) +public class PlayerAdvancementsMixin { + + @Shadow + private ServerPlayer player; + + @Inject(method = "award", at = @At(value = "INVOKE", target = "Lnet/minecraft/advancements/AdvancementRewards;grant(Lnet/minecraft/server/level/ServerPlayer;)V", shift = At.Shift.AFTER)) + private void injectAdvancementEvent(AdvancementHolder advancementHolder, String string, CallbackInfoReturnable cir) { + Advancement advancement = advancementHolder.value(); + + if (advancement.display().isPresent() && advancement.display().get().shouldAnnounceChat()) { + CraterEventBus.INSTANCE.postEvent(new CraterAdvancementEvent(BridgedPlayer.of(this.player), BridgedAdvancement.of(advancementHolder.value()))); + } + } +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerListMixin.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerListMixin.java new file mode 100644 index 0000000..3bc6f6b --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerListMixin.java @@ -0,0 +1,53 @@ +package com.hypherionmc.craterlib.mixin.events; + +import com.hypherionmc.craterlib.api.events.server.CraterPlayerEvent; +import com.hypherionmc.craterlib.api.events.server.MessageBroadcastEvent; +import com.hypherionmc.craterlib.api.events.server.PlayerPreLoginEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.authlib.BridgedGameProfile; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.ChatUtils; +import com.mojang.authlib.GameProfile; +import net.minecraft.network.Connection; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.CommonListenerCookie; +import net.minecraft.server.players.PlayerList; +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; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.net.SocketAddress; +import java.util.function.Function; + +@Mixin(PlayerList.class) +public class PlayerListMixin { + + @Inject(method = "broadcastSystemMessage(Lnet/minecraft/network/chat/Component;Ljava/util/function/Function;Z)V", at = @At("HEAD")) + private void injectBroadcastEvent(Component component, Function function, boolean bl, CallbackInfo ci) { + String thread = Thread.currentThread().getStackTrace()[3].getClassName(); + MessageBroadcastEvent event = new MessageBroadcastEvent(ChatUtils.mojangToAdventure(component), (f) -> ChatUtils.mojangToAdventure(component), bl, thread); + CraterEventBus.INSTANCE.postEvent(event); + } + + @Inject(method = "placeNewPlayer", at = @At("TAIL")) + private void injectPlayerLoginEvent(Connection connection, ServerPlayer serverPlayer, CommonListenerCookie commonListenerCookie, CallbackInfo ci) { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerEvent.PlayerLoggedIn(BridgedPlayer.of(serverPlayer))); + } + + @Inject(method = "remove", at = @At("HEAD")) + private void injectPlayerLogoutEvent(ServerPlayer player, CallbackInfo ci) { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerEvent.PlayerLoggedOut(BridgedPlayer.of(player))); + } + + @Inject(method = "canPlayerLogin", at = @At("HEAD"), cancellable = true) + private void injectPreLoginEvent(SocketAddress address, GameProfile gameProfile, CallbackInfoReturnable cir) { + PlayerPreLoginEvent event = new PlayerPreLoginEvent(address, BridgedGameProfile.of(gameProfile)); + CraterEventBus.INSTANCE.postEvent(event); + if (event.getMessage() != null) { + cir.setReturnValue(ChatUtils.adventureToMojang(event.getMessage())); + } + } +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerMixin.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerMixin.java new file mode 100644 index 0000000..5fd03bd --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerMixin.java @@ -0,0 +1,21 @@ +package com.hypherionmc.craterlib.mixin.events; + +import com.hypherionmc.craterlib.api.events.common.CraterPlayerDeathEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.player.Player; +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(Player.class) +public class PlayerMixin { + + @Inject(method = "die", at = @At("HEAD")) + private void injectPlayerDeathEvent(DamageSource damageSource, CallbackInfo ci) { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerDeathEvent(BridgedPlayer.of(((Player) (Object) this)), damageSource)); + } + +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/ServerPlayerMixin.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/ServerPlayerMixin.java new file mode 100644 index 0000000..ae028e7 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/ServerPlayerMixin.java @@ -0,0 +1,21 @@ +package com.hypherionmc.craterlib.mixin.events; + +import com.hypherionmc.craterlib.api.events.common.CraterPlayerDeathEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.damagesource.DamageSource; +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(ServerPlayer.class) +public class ServerPlayerMixin { + + @Inject(method = "die", at = @At("HEAD")) + private void injectPlayerDeathEvent(DamageSource damageSource, CallbackInfo ci) { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerDeathEvent(BridgedPlayer.of(((ServerPlayer) (Object) this)), damageSource)); + } + +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/ClientLevelMixin.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/ClientLevelMixin.java new file mode 100644 index 0000000..2368d6d --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/ClientLevelMixin.java @@ -0,0 +1,25 @@ +package com.hypherionmc.craterlib.mixin.events.client; + +import com.hypherionmc.craterlib.api.events.client.CraterSinglePlayerEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; +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(ClientLevel.class) +public class ClientLevelMixin { + + @Inject(method = "addEntity", at = @At("HEAD")) + private void injectSinglePlayerJoinEvent(Entity entity, CallbackInfo ci) { + if (entity instanceof Player player) { + CraterSinglePlayerEvent.PlayerLogin playerLogin = new CraterSinglePlayerEvent.PlayerLogin(BridgedPlayer.of(player)); + CraterEventBus.INSTANCE.postEvent(playerLogin); + } + } + +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/MinecraftMixin.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/MinecraftMixin.java new file mode 100644 index 0000000..2903131 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/MinecraftMixin.java @@ -0,0 +1,31 @@ +package com.hypherionmc.craterlib.mixin.events.client; + +import com.hypherionmc.craterlib.api.events.client.ScreenEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.client.gui.BridgedScreen; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.Screen; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(Minecraft.class) +public class MinecraftMixin { + + @Shadow + @Nullable + public Screen screen; + + @Inject(method = "setScreen", at = @At(value = "TAIL")) + private void injectScreenOpeningEvent(Screen screen, CallbackInfo ci) { + Screen old = this.screen; + if (screen != null) { + ScreenEvent.Opening opening = new ScreenEvent.Opening(BridgedScreen.of(old), BridgedScreen.of(screen)); + CraterEventBus.INSTANCE.postEvent(opening); + } + } + +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/RealmsMainScreenMixin.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/RealmsMainScreenMixin.java new file mode 100644 index 0000000..c1ce895 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/RealmsMainScreenMixin.java @@ -0,0 +1,23 @@ +package com.hypherionmc.craterlib.mixin.events.client; + +import com.hypherionmc.craterlib.api.events.client.PlayerJoinRealmEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.realmsclient.dto.BridgedRealmsServer; +import com.mojang.realmsclient.RealmsMainScreen; +import com.mojang.realmsclient.dto.RealmsServer; +import net.minecraft.client.gui.screens.Screen; +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 RealmsMainScreenMixin { + + @Inject(at = @At("HEAD"), method = "play(Lcom/mojang/realmsclient/dto/RealmsServer;Lnet/minecraft/client/gui/screens/Screen;Z)V") + private static void play(RealmsServer serverData, Screen arg2, boolean bl, CallbackInfo ci) { + PlayerJoinRealmEvent playerJoinRealm = new PlayerJoinRealmEvent(BridgedRealmsServer.of(serverData)); + CraterEventBus.INSTANCE.postEvent(playerJoinRealm); + } + +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/advancements/BridgedAdvancement.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/advancements/BridgedAdvancement.java new file mode 100644 index 0000000..19a7e7a --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/advancements/BridgedAdvancement.java @@ -0,0 +1,21 @@ +package com.hypherionmc.craterlib.nojang.advancements; + +import lombok.RequiredArgsConstructor; +import net.minecraft.advancements.Advancement; + +import java.util.Optional; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedAdvancement { + + private final Advancement internal; + + public Optional displayInfo() { + if (internal.display().isPresent()) { + return Optional.of(BridgedDisplayInfo.of(internal.display().get())); + } + + return Optional.empty(); + } + +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/advancements/BridgedDisplayInfo.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/advancements/BridgedDisplayInfo.java new file mode 100644 index 0000000..5a0c155 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/advancements/BridgedDisplayInfo.java @@ -0,0 +1,29 @@ +package com.hypherionmc.craterlib.nojang.advancements; + +import com.hypherionmc.craterlib.utils.ChatUtils; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; +import net.minecraft.advancements.DisplayInfo; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedDisplayInfo { + + private final DisplayInfo internal; + + public boolean shouldDisplay() { + return internal.shouldAnnounceChat(); + } + + public boolean isHidden() { + return internal.isHidden(); + } + + public Component displayName() { + return ChatUtils.mojangToAdventure(internal.getTitle()); + } + + public Component description() { + return ChatUtils.mojangToAdventure(internal.getDescription()); + } + +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/authlib/BridgedGameProfile.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/authlib/BridgedGameProfile.java new file mode 100644 index 0000000..c83f3c4 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/authlib/BridgedGameProfile.java @@ -0,0 +1,29 @@ +package com.hypherionmc.craterlib.nojang.authlib; + +import com.mojang.authlib.GameProfile; +import lombok.RequiredArgsConstructor; + +import java.util.UUID; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedGameProfile { + + private final GameProfile internal; + + public static BridgedGameProfile mojang(UUID id, String name) { + return new BridgedGameProfile(new GameProfile(id, name)); + } + + public String getName() { + return internal.getName(); + } + + public UUID getId() { + return internal.getId(); + } + + public GameProfile toMojang() { + return internal; + } + +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/BridgedMinecraft.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/BridgedMinecraft.java new file mode 100644 index 0000000..89e5d05 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/BridgedMinecraft.java @@ -0,0 +1,84 @@ +package com.hypherionmc.craterlib.nojang.client; + +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedClientLevel; +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedServerData; +import com.hypherionmc.craterlib.nojang.client.server.BridgedIntegratedServer; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import lombok.Getter; +import net.minecraft.SharedConstants; +import net.minecraft.client.Minecraft; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.util.UUID; + +public class BridgedMinecraft { + + @Getter + private static final BridgedMinecraft instance = new BridgedMinecraft(); + private final Minecraft internal = Minecraft.getInstance(); + + public File getGameDirectory() { + return internal.gameDirectory; + } + + public BridgedOptions getOptions() { + return BridgedOptions.of(internal.options); + } + + @Nullable + public BridgedClientLevel getLevel() { + if (internal.level == null) + return null; + + return BridgedClientLevel.of(internal.level); + } + + public boolean isRealmServer() { + return internal.getCurrentServer() != null && internal.getCurrentServer().isRealm(); + } + + public boolean isSinglePlayer() { + return internal.hasSingleplayerServer(); + } + + @Nullable + public BridgedPlayer getPlayer() { + if (internal.player == null) + return null; + + return BridgedPlayer.of(internal.player); + } + + public String getGameVersion() { + return SharedConstants.getCurrentVersion().getName(); + } + + public String getUserName() { + return internal.getUser().getName(); + } + + public UUID getPlayerId() { + return internal.getUser().getProfileId(); + } + + @Nullable + public BridgedServerData getCurrentServer() { + if (internal.getCurrentServer() == null) + return null; + + return BridgedServerData.of(internal.getCurrentServer()); + } + + @Nullable + public BridgedIntegratedServer getSinglePlayerServer() { + return BridgedIntegratedServer.of(internal.getSingleplayerServer()); + } + + public int getServerPlayerCount () { + if (internal.getConnection() == null) + return 0; + + return internal.getConnection().getOnlinePlayers().size(); + } +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/BridgedOptions.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/BridgedOptions.java new file mode 100644 index 0000000..7063feb --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/BridgedOptions.java @@ -0,0 +1,15 @@ +package com.hypherionmc.craterlib.nojang.client; + +import lombok.RequiredArgsConstructor; +import net.minecraft.client.Options; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedOptions { + + private final Options internal; + + public String getLanguage() { + return internal.languageCode; + } + +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/gui/BridgedScreen.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/gui/BridgedScreen.java new file mode 100644 index 0000000..c4bcc23 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/gui/BridgedScreen.java @@ -0,0 +1,36 @@ +package com.hypherionmc.craterlib.nojang.client.gui; + +import lombok.RequiredArgsConstructor; +import net.minecraft.client.gui.screens.LevelLoadingScreen; +import net.minecraft.client.gui.screens.ReceivingLevelScreen; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.gui.screens.TitleScreen; +import net.minecraft.client.gui.screens.multiplayer.JoinMultiplayerScreen; +import net.minecraft.realms.RealmsScreen; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedScreen { + + private final Screen internal; + + public boolean isTitleScreen() { + return internal instanceof TitleScreen; + } + + public boolean isRealmsScreen() { + return internal instanceof RealmsScreen; + } + + public boolean isServerBrowserScreen() { + return internal instanceof JoinMultiplayerScreen; + } + + public boolean isLoadingScreen() { + return internal instanceof LevelLoadingScreen || internal instanceof ReceivingLevelScreen; + } + + public Screen toMojang() { + return internal; + } + +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/multiplayer/BridgedClientLevel.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/multiplayer/BridgedClientLevel.java new file mode 100644 index 0000000..4e7b60b --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/multiplayer/BridgedClientLevel.java @@ -0,0 +1,59 @@ +package com.hypherionmc.craterlib.nojang.client.multiplayer; + +import com.hypherionmc.craterlib.nojang.core.BridgedBlockPos; +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; +import com.hypherionmc.craterlib.utils.ChatUtils; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; +import net.minecraft.client.multiplayer.ClientLevel; +import org.jetbrains.annotations.Nullable; + +import java.util.concurrent.atomic.AtomicReference; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedClientLevel { + + private final ClientLevel internal; + + public boolean isClientSide() { + return internal.isClientSide(); + } + + public long getGameTime() { + return internal.getGameTime(); + } + + public long getDayTime() { + return internal.getDayTime(); + } + + public long dayTime() { + return internal.dayTime(); + } + + public boolean isRaining() { + return internal.isRaining(); + } + + public boolean isThundering() { + return internal.isThundering(); + } + + @Nullable + public ResourceIdentifier getDimensionKey() { + return ResourceIdentifier.fromMojang(internal.dimension().location()); + } + + @Nullable + public ResourceIdentifier getBiomeIdentifier(BridgedBlockPos onPos) { + AtomicReference identifier = new AtomicReference<>(null); + internal.getBiome(onPos.toMojang()).unwrap().ifLeft(b -> identifier.set(ResourceIdentifier.fromMojang(b.location()))); + return identifier.get(); + } + + @Nullable + public Component getDifficulty() { + return ChatUtils.mojangToAdventure(internal.getDifficulty().getDisplayName()); + } + +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/multiplayer/BridgedServerData.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/multiplayer/BridgedServerData.java new file mode 100644 index 0000000..c594872 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/multiplayer/BridgedServerData.java @@ -0,0 +1,40 @@ +package com.hypherionmc.craterlib.nojang.client.multiplayer; + +import com.hypherionmc.craterlib.utils.ChatUtils; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; +import net.minecraft.client.multiplayer.ServerData; +import net.minecraft.client.multiplayer.ServerStatusPinger; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedServerData { + + private final ServerData internal; + + public String name() { + return internal.name; + } + + public String ip() { + return internal.ip; + } + + public Component motd() { + return ChatUtils.mojangToAdventure(internal.motd); + } + + public int getMaxPlayers() { + if (!internal.pinged || internal.players == null) { + try { + new ServerStatusPinger().pingServer(internal, () -> {}); + } catch (Exception ignored) {} + } + + return internal.players == null ? 0 : internal.players.max(); + } + + public ServerData toMojang() { + return internal; + } + +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/server/BridgedIntegratedServer.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/server/BridgedIntegratedServer.java new file mode 100644 index 0000000..0ecacaf --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/server/BridgedIntegratedServer.java @@ -0,0 +1,19 @@ +package com.hypherionmc.craterlib.nojang.client.server; + +import lombok.RequiredArgsConstructor; +import net.minecraft.client.server.IntegratedServer; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedIntegratedServer { + + private final IntegratedServer internal; + + public String getLevelName() { + return internal.getWorldData().getLevelName(); + } + + public IntegratedServer toMojang() { + return internal; + } + +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/BridgedCommandSourceStack.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/BridgedCommandSourceStack.java new file mode 100644 index 0000000..5a15402 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/BridgedCommandSourceStack.java @@ -0,0 +1,22 @@ +package com.hypherionmc.craterlib.nojang.commands; + +import com.hypherionmc.craterlib.utils.ChatUtils; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; +import net.minecraft.commands.CommandSourceStack; + +import java.util.function.Supplier; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedCommandSourceStack { + + private final CommandSourceStack internal; + + public void sendSuccess(Supplier supplier, boolean bl) { + internal.sendSuccess(() -> ChatUtils.adventureToMojang(supplier.get()), bl); + } + + public CommandSourceStack toMojang() { + return internal; + } +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/BridgedFakePlayer.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/BridgedFakePlayer.java new file mode 100644 index 0000000..42b65cb --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/BridgedFakePlayer.java @@ -0,0 +1,55 @@ +package com.hypherionmc.craterlib.nojang.commands; + +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import com.hypherionmc.craterlib.utils.ChatUtils; +import net.minecraft.commands.CommandSource; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.network.chat.Component; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.phys.Vec2; +import net.minecraft.world.phys.Vec3; + +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Supplier; + +public abstract class BridgedFakePlayer { + + final MojangBridge internal; + + public BridgedFakePlayer(BridgedMinecraftServer server, int perm, String name) { + internal = new MojangBridge(server.toMojang(), perm, name, this::onSuccess, this::onError); + } + + public abstract void onSuccess(Supplier supplier, Boolean aBoolean); + + public void onError(net.kyori.adventure.text.Component component) { + this.onSuccess(() -> component, false); + } + + public CommandSourceStack toMojang() { + return internal; + } + + static class MojangBridge extends CommandSourceStack { + + private final BiConsumer, Boolean> successCallback; + public final Consumer errorCallback; + + MojangBridge(MinecraftServer server, int perm, String name, BiConsumer, Boolean> successCallback, Consumer errorCallback) { + super(CommandSource.NULL, Vec3.ZERO, Vec2.ZERO, server.overworld(), perm, name, Component.literal(name), server, null); + this.successCallback = successCallback; + this.errorCallback = errorCallback; + } + + @Override + public void sendSuccess(Supplier supplier, boolean bl) { + successCallback.accept(() -> ChatUtils.mojangToAdventure(supplier.get()), bl); + } + + @Override + public void sendFailure(Component arg) { + errorCallback.accept(ChatUtils.mojangToAdventure(arg)); + } + } +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/CommandsRegistry.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/CommandsRegistry.java new file mode 100644 index 0000000..37ad15b --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/CommandsRegistry.java @@ -0,0 +1,85 @@ +package com.hypherionmc.craterlib.nojang.commands; + +import com.hypherionmc.craterlib.api.commands.CraterCommand; +import com.hypherionmc.craterlib.nojang.authlib.BridgedGameProfile; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.TriConsumer; +import com.mojang.authlib.GameProfile; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.commands.arguments.GameProfileArgument; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class CommandsRegistry { + + public static final CommandsRegistry INSTANCE = new CommandsRegistry(); + + private final List commands = new ArrayList<>(); + + public void registerCommand(CraterCommand cmd) { + commands.add(cmd); + } + + public void registerCommands(CommandDispatcher stack) { + commands.forEach(cmd -> { + if (cmd.hasArguments()) { + CommandWithArguments.register(cmd, stack); + } else { + CommandWithoutArguments.register(cmd, stack); + } + }); + } + + static class CommandWithoutArguments { + + public static void register(CraterCommand cmd, CommandDispatcher dispatcher) { + LiteralArgumentBuilder command = Commands.literal(cmd.getCommandName()) + .requires(source -> source.hasPermission(cmd.getPermissionLevel())) + .executes(context -> { + cmd.getExecutor().accept(BridgedCommandSourceStack.of(context.getSource())); + return 1; + }); + + dispatcher.register(command); + } + + } + + @SuppressWarnings("unchecked") + static class CommandWithArguments { + + public static void register(CraterCommand cmd, CommandDispatcher dispatcher) { + LiteralArgumentBuilder command = Commands.literal(cmd.getCommandName()) + .requires(source -> source.hasPermission(cmd.getPermissionLevel())); + + cmd.getArguments().forEach((key, pair) -> command.then(Commands.argument(key, pair.getLeft()).executes(context -> { + + // This is FUCKING UGLY.... Need to improve this in the future + if (pair.getLeft() instanceof GameProfileArgument) { + Collection profiles = GameProfileArgument.getGameProfiles(context, key); + List bridgedGameProfiles = new ArrayList<>(); + + profiles.forEach(p -> bridgedGameProfiles.add(BridgedGameProfile.of(p))); + + ((TriConsumer, BridgedCommandSourceStack>) pair.getRight()) + .accept(BridgedPlayer.of(context.getSource().getPlayer()), bridgedGameProfiles, BridgedCommandSourceStack.of(context.getSource())); + return 1; + } + + return 1; + }))); + + dispatcher.register(command); + } + + } + +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/core/BridgedBlockPos.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/core/BridgedBlockPos.java new file mode 100644 index 0000000..bd49e2c --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/core/BridgedBlockPos.java @@ -0,0 +1,27 @@ +package com.hypherionmc.craterlib.nojang.core; + +import lombok.RequiredArgsConstructor; +import net.minecraft.core.BlockPos; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedBlockPos { + + private final BlockPos internal; + + public int getX() { + return internal.getX(); + } + + public int getY() { + return internal.getY(); + } + + public int getZ() { + return internal.getZ(); + } + + public BlockPos toMojang() { + return internal; + } + +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/nbt/BridgedCompoundTag.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/nbt/BridgedCompoundTag.java new file mode 100644 index 0000000..17a2d04 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/nbt/BridgedCompoundTag.java @@ -0,0 +1,49 @@ +package com.hypherionmc.craterlib.nojang.nbt; + +import lombok.RequiredArgsConstructor; +import net.minecraft.nbt.CompoundTag; + +import java.util.Set; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedCompoundTag { + + private final CompoundTag internal; + + public static BridgedCompoundTag empty() { + return new BridgedCompoundTag(new CompoundTag()); + } + + public BridgedCompoundTag getCompound(String key) { + return BridgedCompoundTag.of(internal.getCompound(key)); + } + + public Set getAllKeys() { + return internal.getAllKeys(); + } + + public String getString(String key) { + return internal.getString(key); + } + + public boolean getBoolean(String key) { + return internal.getBoolean(key); + } + + public void putString(String key, String value) { + internal.putString(key, value); + } + + public void put(String key, BridgedCompoundTag value) { + internal.put(key, value.toMojang()); + } + + public void putBoolean(String key, boolean value) { + internal.putBoolean(key, value); + } + + public CompoundTag toMojang() { + return internal; + } + +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/network/BridgedFriendlyByteBuf.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/network/BridgedFriendlyByteBuf.java new file mode 100644 index 0000000..a7bafd0 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/network/BridgedFriendlyByteBuf.java @@ -0,0 +1,34 @@ +package com.hypherionmc.craterlib.nojang.network; + +import com.hypherionmc.craterlib.nojang.nbt.BridgedCompoundTag; +import lombok.RequiredArgsConstructor; +import net.minecraft.network.FriendlyByteBuf; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedFriendlyByteBuf { + + private final FriendlyByteBuf internal; + + public BridgedCompoundTag readNbt() { + return BridgedCompoundTag.of(internal.readNbt()); + } + + public BridgedFriendlyByteBuf writeNbt(BridgedCompoundTag tag) { + internal.writeNbt(tag.toMojang()); + return BridgedFriendlyByteBuf.of(internal); + } + + public BridgedFriendlyByteBuf writeUtf(String value) { + internal.writeUtf(value); + return BridgedFriendlyByteBuf.of(internal); + } + + public String readUtf() { + return internal.readUtf(); + } + + public FriendlyByteBuf toMojang() { + return internal; + } + +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/package-info.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/package-info.java new file mode 100644 index 0000000..4c177a5 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/package-info.java @@ -0,0 +1,9 @@ +/** + * @author HypherionSA + * This package, called NoJang, exposes various wrapped API's. + * Using this api, a mod can essentially run on ANY minecraft version this library + * supports, from one code base. + * IMPORTANT NOTE: THESE API'S MUST NEVER EXPOSE ANY MINECRAFT CLASSES OR CODE!!!! + * THEY MUST ALWAYS BE HANDLED INTERNALLY AND ONLY RETURN WRAPPED VARIANTS + */ +package com.hypherionmc.craterlib.nojang; \ No newline at end of file diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/realmsclient/dto/BridgedRealmsServer.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/realmsclient/dto/BridgedRealmsServer.java new file mode 100644 index 0000000..dddf746 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/realmsclient/dto/BridgedRealmsServer.java @@ -0,0 +1,40 @@ +package com.hypherionmc.craterlib.nojang.realmsclient.dto; + +import com.mojang.realmsclient.dto.PlayerInfo; +import com.mojang.realmsclient.dto.RealmsServer; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedRealmsServer { + + private final RealmsServer internal; + + public String getName() { + return internal.getName(); + } + + public String getDescription() { + return internal.getDescription(); + } + + public String getWorldType() { + return internal.worldType.name(); + } + + public String getMinigameName() { + return internal.getMinigameName(); + } + + public String getMinigameImage() { + return internal.minigameImage; + } + + public long getPlayerCount() { + return internal.players.stream().filter(PlayerInfo::getOnline).count(); + } + + public RealmsServer toMojang() { + return internal; + } + +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/resources/ResourceIdentifier.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/resources/ResourceIdentifier.java new file mode 100644 index 0000000..9b848f4 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/resources/ResourceIdentifier.java @@ -0,0 +1,36 @@ +package com.hypherionmc.craterlib.nojang.resources; + +import net.minecraft.resources.ResourceLocation; + +public class ResourceIdentifier { + + private final ResourceLocation internal; + + public ResourceIdentifier(String namespace, String path) { + this.internal = new ResourceLocation(namespace, path); + } + + public ResourceIdentifier(String path) { + this.internal = new ResourceLocation(path); + } + + public String getNamespace() { + return internal.getNamespace(); + } + + public String getPath() { + return internal.getPath(); + } + + public String getString() { + return internal.toString(); + } + + public static ResourceIdentifier fromMojang(ResourceLocation location) { + return new ResourceIdentifier(location.getNamespace(), location.getPath()); + } + + public ResourceLocation toMojang() { + return internal; + } +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/server/BridgedMinecraftServer.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/server/BridgedMinecraftServer.java new file mode 100644 index 0000000..cb818b7 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/server/BridgedMinecraftServer.java @@ -0,0 +1,91 @@ +package com.hypherionmc.craterlib.nojang.server; + +import com.hypherionmc.craterlib.nojang.authlib.BridgedGameProfile; +import com.hypherionmc.craterlib.nojang.commands.BridgedFakePlayer; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.ChatUtils; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; +import net.minecraft.SharedConstants; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.players.UserBanListEntry; +import net.minecraft.server.players.UserWhiteListEntry; + +import java.util.ArrayList; +import java.util.List; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedMinecraftServer { + + private final MinecraftServer internal; + + public boolean isUsingWhitelist() { + return internal.getPlayerList().isUsingWhitelist(); + } + + public int getPlayerCount() { + return internal.getPlayerList().getPlayerCount(); + } + + public int getMaxPlayers() { + return internal.getPlayerList().getMaxPlayers(); + } + + public String getServerModName() { + return internal.getServerModName(); + } + + public String getName() { + return SharedConstants.getCurrentVersion().getName(); + } + + public boolean usesAuthentication() { + return internal.usesAuthentication(); + } + + public void broadcastSystemMessage(Component text, boolean bl) { + internal.getPlayerList().broadcastSystemMessage(ChatUtils.adventureToMojang(text), bl); + } + + public boolean isPlayerBanned(BridgedGameProfile profile) { + return internal.getPlayerList().getBans().isBanned(profile.toMojang()); + } + + public void whitelistPlayer(BridgedGameProfile gameProfile) { + if (!internal.getPlayerList().isUsingWhitelist()) + return; + + internal.getPlayerList().getWhiteList().add(new UserWhiteListEntry(gameProfile.toMojang())); + } + + public void unWhitelistPlayer(BridgedGameProfile gameProfile) { + if (!internal.getPlayerList().isUsingWhitelist()) + return; + + internal.getPlayerList().getWhiteList().remove(new UserWhiteListEntry(gameProfile.toMojang())); + } + + public List getPlayers() { + List profiles = new ArrayList<>(); + + if (internal.getPlayerList() == null) + return profiles; + + internal.getPlayerList().getPlayers().forEach(p -> profiles.add(BridgedPlayer.of(p))); + + return profiles; + } + + public void banPlayer(BridgedGameProfile profile) { + internal.getPlayerList().getBans().add(new UserBanListEntry(profile.toMojang())); + } + + public void executeCommand(BridgedMinecraftServer server, BridgedFakePlayer player, String command) { + internal.getCommands().performPrefixedCommand(player.toMojang(), command); + } + + public MinecraftServer toMojang() { + return internal; + } + +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/world/entity/player/BridgedPlayer.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/world/entity/player/BridgedPlayer.java new file mode 100644 index 0000000..8aa6d49 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/nojang/world/entity/player/BridgedPlayer.java @@ -0,0 +1,63 @@ +package com.hypherionmc.craterlib.nojang.world.entity.player; + +import com.hypherionmc.craterlib.nojang.authlib.BridgedGameProfile; +import com.hypherionmc.craterlib.nojang.core.BridgedBlockPos; +import com.hypherionmc.craterlib.utils.ChatUtils; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraft.world.entity.player.Player; +import org.jetbrains.annotations.Nullable; + +import java.util.UUID; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedPlayer { + + private final Player internal; + + public Component getDisplayName() { + return ChatUtils.mojangToAdventure(internal.getDisplayName()); + } + + public Component getName() { + return ChatUtils.mojangToAdventure(internal.getName()); + } + + public UUID getUUID() { + return internal.getUUID(); + } + + public String getStringUUID() { + return internal.getStringUUID(); + } + + public BridgedGameProfile getGameProfile() { + return BridgedGameProfile.of(internal.getGameProfile()); + } + + public boolean isServerPlayer() { + return internal instanceof ServerPlayer; + } + + public Player toMojang() { + return internal; + } + + public BridgedBlockPos getOnPos() { + return BridgedBlockPos.of(internal.getOnPos()); + } + + @Nullable + public ServerGamePacketListenerImpl getConnection() { + if (isServerPlayer()) { + return ((ServerPlayer) internal).connection; + } + return null; + } + + public ServerPlayer toMojangServerPlayer() { + return (ServerPlayer) internal; + } +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java new file mode 100644 index 0000000..7bbcfd1 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java @@ -0,0 +1,96 @@ +package com.hypherionmc.craterlib.utils; + +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; +import me.hypherionmc.mcdiscordformatter.discord.DiscordSerializer; +import me.hypherionmc.mcdiscordformatter.minecraft.MinecraftSerializer; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import net.kyori.adventure.text.serializer.json.JSONOptions; +import net.minecraft.ChatFormatting; +import net.minecraft.SharedConstants; +import net.minecraft.Util; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.Style; + +public class ChatUtils { + + private static final GsonComponentSerializer adventureSerializer = GsonComponentSerializer.builder().options( + JSONOptions.byDataVersion().at(SharedConstants.getCurrentVersion().getDataVersion().getVersion()) + ).build(); + + public static Component adventureToMojang(net.kyori.adventure.text.Component inComponent) { + final String serialised = adventureSerializer.serialize(inComponent); + return Component.Serializer.fromJson(serialised); + } + + public static net.kyori.adventure.text.Component mojangToAdventure(Component inComponent) { + final String serialised = Component.Serializer.toJson(inComponent); + return adventureSerializer.deserialize(serialised); + } + + // Some text components contain duplicate text, resulting in duplicate messages + // sent back to discord. This should help fix those issues + public static Component safeCopy(Component inComponent) { + String value = inComponent.getString(); + Style style = inComponent.getStyle(); + return Component.literal(value).withStyle(style); + } + + public static String strip(String inString, String... toStrip) { + String finalString = inString; + + for (String strip : toStrip) { + if (finalString.startsWith(strip)) + finalString = finalString.replaceFirst(strip, ""); + + if (finalString.startsWith(" ")) + finalString = finalString.replaceFirst(" ", ""); + } + + return finalString; + } + + public static String resolve(net.kyori.adventure.text.Component component, boolean formatted) { + Component c = adventureToMojang(component); + String returnVal = ChatFormatting.stripFormatting(c.getString()); + + if (formatted) { + returnVal = DiscordSerializer.INSTANCE.serialize(safeCopy(c).copy()); + } + + return returnVal; + } + + public static net.kyori.adventure.text.Component resolve(String component, boolean formatted) { + Component returnVal = Component.literal(component); + if (formatted) { + returnVal = MinecraftSerializer.INSTANCE.serialize(component); + } + + return mojangToAdventure(returnVal); + } + + public static net.kyori.adventure.text.Component getTooltipTitle(String key) { + return net.kyori.adventure.text.Component.text(NamedTextColor.YELLOW + net.kyori.adventure.text.Component.translatable(key).key()); + } + + public static String resolveTranslation(String key) { + return net.kyori.adventure.text.Component.translatable(key).key(); + } + + public static net.kyori.adventure.text.Component getTranslation(String key) { + return net.kyori.adventure.text.Component.translatable(key); + } + + public static net.kyori.adventure.text.Component makeComponent(String text) { + return net.kyori.adventure.text.Component.translatable(text); + } + + public static net.kyori.adventure.text.Component getBiomeName(ResourceIdentifier identifier) { + if (identifier == null) + return net.kyori.adventure.text.Component.text("Unknown"); + + return mojangToAdventure(Component.translatable(Util.makeDescriptionId("biome", identifier.toMojang()))); + } + +} diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/utils/InternalServiceUtil.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/utils/InternalServiceUtil.java new file mode 100644 index 0000000..2305d14 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/utils/InternalServiceUtil.java @@ -0,0 +1,27 @@ +package com.hypherionmc.craterlib.utils; + +import com.hypherionmc.craterlib.CraterConstants; + +import java.util.ServiceLoader; + +/** + * @author HypherionSA + * Utility class to handle SPI loading + */ +public class InternalServiceUtil { + + /** + * Try to load a service + * + * @param clazz The service class type to load + * @return The loaded class + */ + public static T load(Class clazz) { + final T loadedService = ServiceLoader.load(clazz) + .findFirst() + .orElseThrow(() -> new NullPointerException("Failed to load service for " + clazz.getName())); + CraterConstants.LOG.debug("Loaded {} for service {}", loadedService, clazz); + return loadedService; + } + +} \ No newline at end of file diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/utils/OptifineUtils.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/utils/OptifineUtils.java new file mode 100644 index 0000000..8a06da2 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/utils/OptifineUtils.java @@ -0,0 +1,46 @@ +package com.hypherionmc.craterlib.utils; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * @author HypherionSA + * Utility class for Optifine compatibility + */ +public class OptifineUtils { + + private static final boolean hasOptifine = checkOptifine(); + + private static boolean checkOptifine() { + try { + Class ofConfigClass = Class.forName("net.optifine.Config"); + return true; + } catch (ClassNotFoundException e) { + // Optifine is probably not present. Ignore the error + return false; + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } + + public static boolean isRenderRegions() { + try { + Class ofConfigClass = Class.forName("net.optifine.Config"); + Method rrField = ofConfigClass.getMethod("isRenderRegions"); + return (boolean) rrField.invoke(null); + } catch (ClassNotFoundException | InvocationTargetException | NoSuchMethodException | + IllegalAccessException e) { + // Optifine is probably not present. Ignore the error + return false; + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } + + public static boolean hasOptifine() { + return hasOptifine; + } + +} \ No newline at end of file diff --git a/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/utils/TriConsumer.java b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/utils/TriConsumer.java new file mode 100644 index 0000000..77f7309 --- /dev/null +++ b/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/utils/TriConsumer.java @@ -0,0 +1,5 @@ +package com.hypherionmc.craterlib.utils; + +public interface TriConsumer { + void accept(T t, U u, V v); +} diff --git a/1.20.4/Common/src/main/resources/assets/craterlib/lang/en_us.json b/1.20.4/Common/src/main/resources/assets/craterlib/lang/en_us.json new file mode 100644 index 0000000..03115d9 --- /dev/null +++ b/1.20.4/Common/src/main/resources/assets/craterlib/lang/en_us.json @@ -0,0 +1,8 @@ +{ + "t.clc.opensubconfig": "Open Config", + "t.clc.save": "Save", + "t.clc.cancel_discard": "Discard", + "t.clc.quit_config": "Unsaved Changes", + "t.clc.quit_config_sure": "You have unsaved config changes. Are you sure you want to discard them?", + "t.clc.quit_discard": "Quit & Discard" +} diff --git a/1.20.4/Common/src/main/resources/craterlib.mixins.json b/1.20.4/Common/src/main/resources/craterlib.mixins.json new file mode 100644 index 0000000..1a739db --- /dev/null +++ b/1.20.4/Common/src/main/resources/craterlib.mixins.json @@ -0,0 +1,24 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "com.hypherionmc.craterlib.mixin", + "compatibilityLevel": "JAVA_17", + "mixins": [ + ], + "client": [ + "ChatInputSuggestorMixin", + "events.PlayerMixin", + "events.client.ClientLevelMixin", + "events.client.MinecraftMixin", + "events.client.RealmsMainScreenMixin" + ], + "server": [ + "events.CommandMixin", + "events.PlayerAdvancementsMixin", + "events.PlayerListMixin", + "events.ServerPlayerMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/1.20.4/Common/src/main/resources/pack.mcmeta b/1.20.4/Common/src/main/resources/pack.mcmeta new file mode 100644 index 0000000..263d366 --- /dev/null +++ b/1.20.4/Common/src/main/resources/pack.mcmeta @@ -0,0 +1,6 @@ +{ + "pack": { + "description": "${mod_name}", + "pack_format": 18 + } +} diff --git a/1.20.4/Fabric/build.gradle b/1.20.4/Fabric/build.gradle new file mode 100644 index 0000000..9bbd780 --- /dev/null +++ b/1.20.4/Fabric/build.gradle @@ -0,0 +1,126 @@ +archivesBaseName = "${mod_name.replace(" ", "")}-Fabric-${minecraft_version}" + +dependencies { + // Core + modImplementation "net.fabricmc.fabric-api:fabric-api:${fabric_api}" + + // Compat + modImplementation("com.terraformersmc:modmenu:${mod_menu_version}") { + exclude(group: "net.fabricmc.fabric-api") + } + + modImplementation "maven.modrinth:fabrictailor:${fabrictailor}" + modImplementation "maven.modrinth:vanish:${vanish}" + + // Do not edit or remove + implementation project(":Common") +} + +shadowJar { + from sourceSets.main.output + configurations = [project.configurations.shade] + + dependencies { + exclude(dependency('com.google.code.gson:.*')) + + relocate 'me.hypherionmc.moonconfig', 'shadow.hypherionmc.moonconfig' + relocate 'me.hypherionmc.mcdiscordformatter', 'shadow.hypherionmc.mcdiscordformatter' + relocate 'net.kyori', 'shadow.kyori' + } + + setArchiveClassifier('dev-shadow') +} + +/** + * =============================================================================== + * = DO NOT EDIT BELOW THIS LINE UNLESS YOU KNOW WHAT YOU ARE DOING = + * =============================================================================== + */ + +unimined.minecraft { + fabric { + loader fabric_loader + } +} + +remapJar { + inputFile.set shadowJar.archiveFile + dependsOn shadowJar + archiveClassifier.set null +} + +jar { + archiveClassifier.set "dev" +} + +processResources { + from project(":Common").sourceSets.main.resources + def buildProps = project.properties.clone() + + filesMatching(['fabric.mod.json']) { + expand buildProps + } +} + +compileTestJava.enabled = false + +tasks.withType(JavaCompile).configureEach { + source(project(":Common").sourceSets.main.allSource) +} + +/** + * Publishing Config + */ +publishing { + publications { + mavenJava(MavenPublication) { + artifactId project.archivesBaseName + from components.java + + artifact(remapJar) { + builtBy remapJar + } + + pom.withXml { + Node pomNode = asNode() + pomNode.dependencies.'*'.findAll() { + it.artifactId.text() == 'regutils-joined-fabric' || + it.artifactId.text() == 'core' || + it.artifactId.text() == 'toml' + }.each() { + it.parent().remove(it) + } + } + } + } + + repositories { + maven rootProject.orion.getPublishingMaven() + } +} + +publisher { + apiKeys { + modrinth(System.getenv("MODRINTH_TOKEN")) + curseforge(System.getenv("CURSE_TOKEN")) + } + + setCurseID(curse_id) + setModrinthID(modrinth_id) + setVersionType("release") + setChangelog("https://raw.githubusercontent.com/hypherionmc/changelogs/main/craterlib/changelog-fabric.md") + setProjectVersion("${minecraft_version}-${project.version}") + setDisplayName("[FABRIC/QUILT 1.20.4] CraterLib - ${project.version}") + setGameVersions("1.20.4") + setLoaders("fabric", "quilt") + setArtifact(remapJar) + setCurseEnvironment("both") + + modrinthDepends { + required("fabric-api") + } + + curseDepends { + required("fabric-api") + } +} diff --git a/1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/CraterLibInitializer.java b/1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/CraterLibInitializer.java new file mode 100644 index 0000000..c66c210 --- /dev/null +++ b/1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/CraterLibInitializer.java @@ -0,0 +1,42 @@ +package com.hypherionmc.craterlib; + +import com.hypherionmc.craterlib.api.events.server.CraterRegisterCommandEvent; +import com.hypherionmc.craterlib.api.events.server.CraterServerLifecycleEvent; +import com.hypherionmc.craterlib.common.FabricCommonPlatform; +import com.hypherionmc.craterlib.compat.Vanish; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.core.networking.CraterPacketNetwork; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.core.platform.ModloaderEnvironment; +import com.hypherionmc.craterlib.network.CraterFabricNetworkHandler; +import com.hypherionmc.craterlib.nojang.commands.CommandsRegistry; +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; + +public class CraterLibInitializer implements ModInitializer { + + @Override + public void onInitialize() { + new CraterPacketNetwork(new CraterFabricNetworkHandler(PacketSide.SERVER)); + CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> { + CraterEventBus.INSTANCE.postEvent(new CraterRegisterCommandEvent()); + CommandsRegistry.INSTANCE.registerCommands(dispatcher); + }); + + + ServerLifecycleEvents.SERVER_STARTING.register(server -> { + FabricCommonPlatform.server = server; + CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Starting(BridgedMinecraftServer.of(server))); + }); + + ServerLifecycleEvents.SERVER_STARTED.register(li -> CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Started(BridgedMinecraftServer.of(li)))); + ServerLifecycleEvents.SERVER_STOPPING.register(server -> CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Stopping(BridgedMinecraftServer.of(server)))); + ServerLifecycleEvents.SERVER_STOPPED.register(server -> CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Stopped(BridgedMinecraftServer.of(server)))); + + if (ModloaderEnvironment.INSTANCE.isModLoaded("melius-vanish")) { + Vanish.register(); + } + } +} diff --git a/1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/CraterLibModMenuIntegration.java b/1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/CraterLibModMenuIntegration.java new file mode 100644 index 0000000..6615352 --- /dev/null +++ b/1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/CraterLibModMenuIntegration.java @@ -0,0 +1,30 @@ +package com.hypherionmc.craterlib; + +import com.hypherionmc.craterlib.client.gui.config.CraterConfigScreen; +import com.hypherionmc.craterlib.core.config.ConfigController; +import com.hypherionmc.craterlib.core.config.ModuleConfig; +import com.hypherionmc.craterlib.core.config.annotations.NoConfigScreen; +import com.terraformersmc.modmenu.api.ConfigScreenFactory; +import com.terraformersmc.modmenu.api.ModMenuApi; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author HypherionSA + */ +public class CraterLibModMenuIntegration implements ModMenuApi { + + @Override + public Map> getProvidedConfigScreenFactories() { + Map> configScreens = new HashMap<>(); + + ConfigController.getMonitoredConfigs().forEach((conf, watcher) -> { + if (!conf.getClass().isAnnotationPresent(NoConfigScreen.class)) { + configScreens.put(((ModuleConfig) conf).getModId(), screen -> new CraterConfigScreen((ModuleConfig) conf, screen)); + } + }); + + return configScreens; + } +} diff --git a/1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/client/CraterLibClientInitializer.java b/1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/client/CraterLibClientInitializer.java new file mode 100644 index 0000000..b5ec5b1 --- /dev/null +++ b/1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/client/CraterLibClientInitializer.java @@ -0,0 +1,27 @@ +package com.hypherionmc.craterlib.client; + +import com.hypherionmc.craterlib.api.events.client.CraterClientTickEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.core.networking.CraterPacketNetwork; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.network.CraterFabricNetworkHandler; +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedClientLevel; +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; + +public class CraterLibClientInitializer implements ClientModInitializer { + + @Override + public void onInitializeClient() { + new CraterPacketNetwork(new CraterFabricNetworkHandler(PacketSide.CLIENT)); + ClientTickEvents.START_CLIENT_TICK.register((listener) -> { + if (listener.level == null) + return; + + CraterClientTickEvent event = new CraterClientTickEvent(BridgedClientLevel.of(listener.level)); + CraterEventBus.INSTANCE.postEvent(event); + }); + + CraterEventBus.INSTANCE.registerEventListener(CraterLibClientInitializer.class); + } +} diff --git a/1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/client/FabricClientPlatform.java b/1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/client/FabricClientPlatform.java new file mode 100644 index 0000000..7852f12 --- /dev/null +++ b/1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/client/FabricClientPlatform.java @@ -0,0 +1,34 @@ +package com.hypherionmc.craterlib.client; + +import com.hypherionmc.craterlib.core.platform.ClientPlatform; +import com.hypherionmc.craterlib.nojang.client.BridgedMinecraft; +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedClientLevel; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraft.client.Minecraft; +import net.minecraft.network.Connection; + +/** + * @author HypherionSA + */ +public class FabricClientPlatform implements ClientPlatform { + + @Override + public BridgedMinecraft getClientInstance() { + return new BridgedMinecraft(); + } + + @Override + public BridgedPlayer getClientPlayer() { + return BridgedPlayer.of(Minecraft.getInstance().player); + } + + @Override + public BridgedClientLevel getClientLevel() { + return BridgedClientLevel.of(Minecraft.getInstance().level); + } + + @Override + public Connection getClientConnection() { + return Minecraft.getInstance().getConnection().getConnection(); + } +} diff --git a/1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricCommonPlatform.java b/1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricCommonPlatform.java new file mode 100644 index 0000000..c1a30c7 --- /dev/null +++ b/1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricCommonPlatform.java @@ -0,0 +1,18 @@ +package com.hypherionmc.craterlib.common; + +import com.hypherionmc.craterlib.core.platform.CommonPlatform; +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import net.minecraft.server.MinecraftServer; + +/** + * @author HypherionSA + */ +public class FabricCommonPlatform implements CommonPlatform { + + public static MinecraftServer server; + + @Override + public BridgedMinecraftServer getMCServer() { + return BridgedMinecraftServer.of(server); + } +} diff --git a/1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricCompatHelper.java b/1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricCompatHelper.java new file mode 100644 index 0000000..dbcaf63 --- /dev/null +++ b/1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricCompatHelper.java @@ -0,0 +1,23 @@ +package com.hypherionmc.craterlib.common; + +import com.hypherionmc.craterlib.compat.FabricTailor; +import com.hypherionmc.craterlib.compat.Vanish; +import com.hypherionmc.craterlib.core.platform.CompatUtils; +import com.hypherionmc.craterlib.core.platform.ModloaderEnvironment; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; + +public class FabricCompatHelper implements CompatUtils { + + @Override + public boolean isPlayerActive(BridgedPlayer player) { + if (!ModloaderEnvironment.INSTANCE.isModLoaded("melius-vanish")) + return true; + + return Vanish.isPlayerVanished(player.toMojangServerPlayer()); + } + + @Override + public String getSkinUUID(BridgedPlayer player) { + return FabricTailor.getTailorSkin(player.toMojangServerPlayer()); + } +} diff --git a/1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricLoaderHelper.java b/1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricLoaderHelper.java new file mode 100644 index 0000000..95f5c42 --- /dev/null +++ b/1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricLoaderHelper.java @@ -0,0 +1,69 @@ +package com.hypherionmc.craterlib.common; + +import com.hypherionmc.craterlib.core.platform.Environment; +import com.hypherionmc.craterlib.core.platform.ModloaderEnvironment; +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.SharedConstants; +import net.minecraft.client.Minecraft; + +import java.io.File; + +/** + * @author HypherionSA + * @date 07/08/2022 + */ +public class FabricLoaderHelper implements ModloaderEnvironment { + + @Override + public boolean isFabric() { + return true; + } + + @Override + public String getGameVersion() { + return SharedConstants.VERSION_STRING; + } + + @Override + public File getGameFolder() { + return Minecraft.getInstance().gameDirectory; + } + + @Override + public File getConfigFolder() { + return FabricLoader.getInstance().getConfigDir().toFile(); + } + + @Override + public File getModsFolder() { + return new File(FabricLoader.getInstance().getGameDir().toString() + File.separator + "mods"); + } + + @Override + public Environment getEnvironment() { + switch (FabricLoader.getInstance().getEnvironmentType()) { + case SERVER -> { + return Environment.SERVER; + } + case CLIENT -> { + return Environment.CLIENT; + } + } + return Environment.UNKNOWN; + } + + @Override + public boolean isModLoaded(String modid) { + return FabricLoader.getInstance().isModLoaded(modid); + } + + @Override + public boolean isDevEnv() { + return FabricLoader.getInstance().isDevelopmentEnvironment(); + } + + @Override + public int getModCount() { + return FabricLoader.getInstance().getAllMods().size(); + } +} diff --git a/1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/compat/FabricTailor.java b/1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/compat/FabricTailor.java new file mode 100644 index 0000000..b95a7d7 --- /dev/null +++ b/1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/compat/FabricTailor.java @@ -0,0 +1,23 @@ +package com.hypherionmc.craterlib.compat; + +import com.hypherionmc.craterlib.core.platform.ModloaderEnvironment; +import net.minecraft.server.level.ServerPlayer; +import org.samo_lego.fabrictailor.casts.TailoredPlayer; +public class FabricTailor { + + public static String getTailorSkin(ServerPlayer player) { + if (!ModloaderEnvironment.INSTANCE.isModLoaded("fabrictailor")) + return player.getStringUUID(); + + try { + if (player instanceof TailoredPlayer tp) { + return tp.getSkinId(); + } + } catch (Exception e) { + e.printStackTrace(); + } + + return player.getStringUUID(); + } + +} \ No newline at end of file diff --git a/1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java b/1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java new file mode 100644 index 0000000..cc6c1e0 --- /dev/null +++ b/1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java @@ -0,0 +1,25 @@ +package com.hypherionmc.craterlib.compat; + +import com.hypherionmc.craterlib.api.events.server.CraterPlayerEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import me.drex.vanish.api.VanishAPI; +import me.drex.vanish.api.VanishEvents; +import net.minecraft.server.level.ServerPlayer; + +public class Vanish { + + public static void register() { + VanishEvents.VANISH_EVENT.register((serverPlayer, b) -> { + if (b) { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerEvent.PlayerLoggedOut(BridgedPlayer.of(serverPlayer))); + } else { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerEvent.PlayerLoggedIn(BridgedPlayer.of(serverPlayer))); + } + }); + } + + public static boolean isPlayerVanished(ServerPlayer player) { + return VanishAPI.isVanished(player); + } +} diff --git a/1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java b/1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java new file mode 100644 index 0000000..4a7d904 --- /dev/null +++ b/1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java @@ -0,0 +1,36 @@ +package com.hypherionmc.craterlib.mixin; + +import com.hypherionmc.craterlib.api.events.server.CraterServerChatEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.ChatUtils; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.PlayerChatMessage; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.FilteredText; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(value = ServerGamePacketListenerImpl.class, priority = Integer.MIN_VALUE) +public class ServerGamePacketListenerImplMixin { + + @Shadow + public ServerPlayer player; + + @Inject( + method = "lambda$handleChat$6", + at = @At("HEAD"), + cancellable = true + ) + private void injectChatEvent(PlayerChatMessage arg, Component arg2, FilteredText arg3, CallbackInfo ci) { + CraterServerChatEvent event = new CraterServerChatEvent(BridgedPlayer.of(this.player), arg.decoratedContent().getString(), ChatUtils.mojangToAdventure(arg.decoratedContent())); + CraterEventBus.INSTANCE.postEvent(event); + if (event.wasCancelled()) + ci.cancel(); + } + +} diff --git a/1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/TutorialMixin.java b/1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/TutorialMixin.java new file mode 100644 index 0000000..f9eaa50 --- /dev/null +++ b/1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/TutorialMixin.java @@ -0,0 +1,24 @@ +package com.hypherionmc.craterlib.mixin; + +import com.hypherionmc.craterlib.api.events.client.LateInitEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.client.BridgedMinecraft; +import com.hypherionmc.craterlib.nojang.client.BridgedOptions; +import net.minecraft.client.Minecraft; +import net.minecraft.client.Options; +import net.minecraft.client.tutorial.Tutorial; +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(Tutorial.class) +public class TutorialMixin { + + @Inject(method = "", at = @At("RETURN")) + private void injectEarlyInitEvent(Minecraft minecraft, Options options, CallbackInfo ci) { + LateInitEvent event = new LateInitEvent(new BridgedMinecraft(), BridgedOptions.of(options)); + CraterEventBus.INSTANCE.postEvent(event); + } + +} diff --git a/1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java b/1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java new file mode 100644 index 0000000..b3014a3 --- /dev/null +++ b/1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java @@ -0,0 +1,81 @@ +package com.hypherionmc.craterlib.network; + +import com.hypherionmc.craterlib.CraterConstants; +import com.hypherionmc.craterlib.core.networking.PacketRegistry; +import com.hypherionmc.craterlib.core.networking.data.PacketContext; +import com.hypherionmc.craterlib.core.networking.data.PacketHolder; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.nojang.network.BridgedFriendlyByteBuf; +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.minecraft.network.FriendlyByteBuf; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public class CraterFabricNetworkHandler extends PacketRegistry { + + private final Map, Message> CHANNELS = new HashMap(); + + public CraterFabricNetworkHandler(PacketSide side) { + super(side); + } + + protected void registerPacket(PacketHolder holder) { + if (CHANNELS.get(holder.messageType()) == null) { + CHANNELS.put(holder.messageType(), new Message<>(holder.type(), holder.encoder())); + + if (PacketSide.CLIENT.equals(this.side)) { + ClientPlayNetworking.registerGlobalReceiver(holder.type().toMojang(), ((client, listener, buf, responseSender) -> { + buf.readByte(); + T message = holder.decoder().apply(BridgedFriendlyByteBuf.of(buf)); + client.execute(() -> holder.handler().accept(new PacketContext<>(message, PacketSide.CLIENT))); + })); + } else { + + ServerPlayNetworking.registerGlobalReceiver(holder.type().toMojang(), ((server, player, listener, buf, responseSender) -> { + buf.readByte(); + T message = holder.decoder().apply(BridgedFriendlyByteBuf.of(buf)); + server.execute(() -> holder.handler().accept(new PacketContext<>(BridgedPlayer.of(player), message, PacketSide.SERVER))); + })); + } + + } else { + CraterConstants.LOG.error("Trying to register duplicate packet for type {}", holder.messageType()); + } + } + + public void sendToServer(T packet) { + this.sendToServer(packet, false); + } + + public void sendToServer(T packet, boolean ignoreCheck) { + Message message = (Message) CHANNELS.get(packet.getClass()); + + if (ClientPlayNetworking.canSend(message.id().toMojang()) || ignoreCheck) { + FriendlyByteBuf buf = PacketByteBufs.create(); + buf.writeByte(0); + message.encoder().accept(packet, BridgedFriendlyByteBuf.of(buf)); + ClientPlayNetworking.send(message.id().toMojang(), buf); + } + } + + public void sendToClient(T packet, BridgedPlayer player) { + Message message = (Message) CHANNELS.get(packet.getClass()); + if (ServerPlayNetworking.canSend(player.toMojangServerPlayer(), message.id().toMojang())) { + FriendlyByteBuf buf = PacketByteBufs.create(); + buf.writeByte(0); + message.encoder().accept(packet, BridgedFriendlyByteBuf.of(buf)); + ServerPlayNetworking.send(player.toMojangServerPlayer(), message.id().toMojang(), buf); + } + } + + public record Message(ResourceIdentifier id, BiConsumer encoder) { } +} diff --git a/1.20.4/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform b/1.20.4/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform new file mode 100644 index 0000000..a78d9e5 --- /dev/null +++ b/1.20.4/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform @@ -0,0 +1 @@ +com.hypherionmc.craterlib.client.FabricClientPlatform diff --git a/1.20.4/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform b/1.20.4/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform new file mode 100644 index 0000000..9a2fdb0 --- /dev/null +++ b/1.20.4/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform @@ -0,0 +1 @@ +com.hypherionmc.craterlib.common.FabricCommonPlatform diff --git a/1.20.4/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils b/1.20.4/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils new file mode 100644 index 0000000..62f79a8 --- /dev/null +++ b/1.20.4/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils @@ -0,0 +1 @@ +com.hypherionmc.craterlib.common.FabricCompatHelper \ No newline at end of file diff --git a/1.20.4/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment b/1.20.4/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment new file mode 100644 index 0000000..9a1fb33 --- /dev/null +++ b/1.20.4/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment @@ -0,0 +1 @@ +com.hypherionmc.craterlib.common.FabricLoaderHelper diff --git a/1.20.4/Fabric/src/main/resources/assets/craterlib/craterlib_logo.png b/1.20.4/Fabric/src/main/resources/assets/craterlib/craterlib_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..ce0159cc4aa4d823ea354042e531b4522d6dfead GIT binary patch literal 49343 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 z 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 De~rmR literal 0 HcmV?d00001 diff --git a/1.20.4/Fabric/src/main/resources/craterlib.fabric.mixins.json b/1.20.4/Fabric/src/main/resources/craterlib.fabric.mixins.json new file mode 100644 index 0000000..7c59043 --- /dev/null +++ b/1.20.4/Fabric/src/main/resources/craterlib.fabric.mixins.json @@ -0,0 +1,17 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "com.hypherionmc.craterlib.mixin", + "compatibilityLevel": "JAVA_17", + "mixins": [ + ], + "client": [ + "TutorialMixin" + ], + "server": [ + "ServerGamePacketListenerImplMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/1.20.4/Fabric/src/main/resources/fabric.mod.json b/1.20.4/Fabric/src/main/resources/fabric.mod.json new file mode 100644 index 0000000..12c6b1c --- /dev/null +++ b/1.20.4/Fabric/src/main/resources/fabric.mod.json @@ -0,0 +1,39 @@ +{ + "schemaVersion": 1, + "id": "${mod_id}", + "version": "${version}", + "name": "${mod_name}", + "description": "A library mod used by First Dark Development and HypherionSA Mods", + "authors": [ + "${mod_author}", + "Misha" + ], + "contact": { + "homepage": "https://modrinth.com/mod/craterlib", + "sources": "https://github.com/firstdarkdev/craterLib/" + }, + "license": "MIT", + "icon": "assets/craterlib/craterlib_logo.png", + "environment": "*", + "entrypoints": { + "main": [ + "com.hypherionmc.craterlib.CraterLibInitializer" + ], + "client": [ + "com.hypherionmc.craterlib.client.CraterLibClientInitializer" + ], + "modmenu": [ + "com.hypherionmc.craterlib.CraterLibModMenuIntegration" + ] + }, + "mixins": [ + "${mod_id}.mixins.json", + "${mod_id}.fabric.mixins.json" + ], + "depends": { + "fabricloader": ">=0.15.0", + "fabric-api": "*", + "minecraft": "1.20.4", + "java": ">=17" + } +} diff --git a/1.20.4/Forge/build.gradle b/1.20.4/Forge/build.gradle new file mode 100644 index 0000000..1678277 --- /dev/null +++ b/1.20.4/Forge/build.gradle @@ -0,0 +1,112 @@ +// Adjust the output jar name here +archivesBaseName = "${mod_name.replace(" ", "")}-Forge-${minecraft_version}" + +dependencies { + // Compat + modImplementation("maven.modrinth:vanishmod:${vanishmod}") + + // Do not edit or remove + implementation project(":Common") +} + +shadowJar { + from sourceSets.main.output + configurations = [project.configurations.shade] + + dependencies { + exclude(dependency('com.google.code.gson:.*')) + + relocate 'me.hypherionmc.moonconfig', 'shadow.hypherionmc.moonconfig' + relocate 'me.hypherionmc.mcdiscordformatter', 'shadow.hypherionmc.mcdiscordformatter' + relocate 'net.kyori', 'shadow.kyori' + } + + setArchiveClassifier('dev-shadow') +} + +/** + * =============================================================================== + * = DO NOT EDIT BELOW THIS LINE UNLESS YOU KNOW WHAT YOU ARE DOING = + * =============================================================================== + */ + +unimined.minecraft { + minecraftForge { + loader forge_version + mixinConfig("${mod_id}.mixins.json", "${mod_id}.forge.mixins.json") + } +} + +remapJar { + inputFile.set shadowJar.archiveFile + dependsOn shadowJar + archiveClassifier.set null +} + +jar { + archiveClassifier.set "dev" +} + +processResources { + from project(":Common").sourceSets.main.resources + def buildProps = project.properties.clone() + + filesMatching("META-INF/mods.toml") { + expand buildProps + } +} + +compileTestJava.enabled = false + +tasks.withType(JavaCompile).configureEach { + source(project(":Common").sourceSets.main.allSource) +} + +/** + * Publishing Config + */ +publishing { + publications { + mavenJava(MavenPublication) { + artifactId project.archivesBaseName + from components.java + + artifact(remapJar) { + builtBy remapJar + } + + pom.withXml { + Node pomNode = asNode() + pomNode.dependencies.'*'.findAll() { + it.artifactId.text() == 'regutils-joined-fabric' || + it.artifactId.text() == 'core' || + it.artifactId.text() == 'toml' + }.each() { + it.parent().remove(it) + } + } + } + } + + repositories { + maven rootProject.orion.getPublishingMaven() + } +} + +publisher { + apiKeys { + modrinth(System.getenv("MODRINTH_TOKEN")) + curseforge(System.getenv("CURSE_TOKEN")) + } + + setCurseID(curse_id) + setModrinthID(modrinth_id) + setVersionType("release") + setChangelog("https://raw.githubusercontent.com/hypherionmc/changelogs/main/craterlib/changelog-forge.md") + setProjectVersion("${minecraft_version}-${project.version}") + setDisplayName("[Forge 1.20.4] CraterLib - ${project.version}") + setGameVersions("1.20.4") + setLoaders("forge") + setArtifact(remapJar) + setCurseEnvironment("both") +} diff --git a/1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java b/1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java new file mode 100644 index 0000000..968da05 --- /dev/null +++ b/1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java @@ -0,0 +1,41 @@ +package com.hypherionmc.craterlib; + +import com.hypherionmc.craterlib.api.events.client.LateInitEvent; +import com.hypherionmc.craterlib.common.ForgeServerEvents; +import com.hypherionmc.craterlib.compat.Vanish; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.core.networking.CraterPacketNetwork; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.core.platform.ModloaderEnvironment; +import com.hypherionmc.craterlib.network.CraterForgeNetworkHandler; +import com.hypherionmc.craterlib.nojang.client.BridgedMinecraft; +import com.hypherionmc.craterlib.nojang.client.BridgedOptions; +import net.minecraft.client.Minecraft; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.DistExecutor; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import net.minecraftforge.fml.loading.FMLLoader; + +@Mod(CraterConstants.MOD_ID) +public class CraterLib { + + public CraterLib() { + MinecraftForge.EVENT_BUS.register(new ForgeServerEvents()); + FMLJavaModLoadingContext.get().getModEventBus().addListener(this::commonSetup); + } + + public void commonSetup(FMLCommonSetupEvent evt) { + new CraterPacketNetwork(new CraterForgeNetworkHandler(FMLLoader.getDist().isClient() ? PacketSide.CLIENT : PacketSide.SERVER)); + DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> { + LateInitEvent event = new LateInitEvent(new BridgedMinecraft(), BridgedOptions.of(Minecraft.getInstance().options)); + CraterEventBus.INSTANCE.postEvent(event); + }); + + if (ModloaderEnvironment.INSTANCE.isModLoaded("vmod")) { + MinecraftForge.EVENT_BUS.register(new Vanish()); + } + } +} diff --git a/1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/client/ForgeClientEvents.java b/1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/client/ForgeClientEvents.java new file mode 100644 index 0000000..cb096d5 --- /dev/null +++ b/1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/client/ForgeClientEvents.java @@ -0,0 +1,25 @@ +package com.hypherionmc.craterlib.client; + +import com.hypherionmc.craterlib.CraterConstants; +import com.hypherionmc.craterlib.api.events.client.CraterClientTickEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedClientLevel; +import net.minecraft.client.Minecraft; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.event.TickEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; + +@Mod.EventBusSubscriber(modid = CraterConstants.MOD_ID, bus = Mod.EventBusSubscriber.Bus.FORGE, value = Dist.CLIENT) +public class ForgeClientEvents { + + @SubscribeEvent + public static void clientTick(TickEvent.LevelTickEvent event) { + if (Minecraft.getInstance().level == null) + return; + + CraterClientTickEvent craterClientTickEvent = new CraterClientTickEvent(BridgedClientLevel.of(Minecraft.getInstance().level)); + CraterEventBus.INSTANCE.postEvent(craterClientTickEvent); + } + +} diff --git a/1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/client/ForgeClientHelper.java b/1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/client/ForgeClientHelper.java new file mode 100644 index 0000000..6776622 --- /dev/null +++ b/1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/client/ForgeClientHelper.java @@ -0,0 +1,41 @@ +package com.hypherionmc.craterlib.client; + +import com.hypherionmc.craterlib.core.platform.ClientPlatform; +import com.hypherionmc.craterlib.nojang.client.BridgedMinecraft; +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedClientLevel; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraft.client.Minecraft; +import net.minecraft.network.Connection; + +import java.util.Objects; + +/** + * @author HypherionSA + * @date 16/06/2022 + */ +public class ForgeClientHelper implements ClientPlatform { + + public ForgeClientHelper() { + } + + @Override + public BridgedMinecraft getClientInstance() { + return new BridgedMinecraft(); + } + + @Override + public BridgedPlayer getClientPlayer() { + return BridgedPlayer.of(Minecraft.getInstance().player); + } + + @Override + public BridgedClientLevel getClientLevel() { + return BridgedClientLevel.of(Minecraft.getInstance().level); + } + + @Override + public Connection getClientConnection() { + Objects.requireNonNull(Minecraft.getInstance().getConnection(), "Cannot send packets when not in game!"); + return Minecraft.getInstance().getConnection().getConnection(); + } +} diff --git a/1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeCommonHelper.java b/1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeCommonHelper.java new file mode 100644 index 0000000..281ddd7 --- /dev/null +++ b/1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeCommonHelper.java @@ -0,0 +1,26 @@ +package com.hypherionmc.craterlib.common; + +import com.hypherionmc.craterlib.core.platform.CommonPlatform; +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.CreativeModeTab; +import net.minecraftforge.server.ServerLifecycleHooks; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author HypherionSA + */ +public class ForgeCommonHelper implements CommonPlatform { + + public static Map TABS = new HashMap<>(); + + public ForgeCommonHelper() { + } + + @Override + public BridgedMinecraftServer getMCServer() { + return BridgedMinecraftServer.of(ServerLifecycleHooks.getCurrentServer()); + } +} diff --git a/1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeCompatHelper.java b/1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeCompatHelper.java new file mode 100644 index 0000000..f325837 --- /dev/null +++ b/1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeCompatHelper.java @@ -0,0 +1,17 @@ +package com.hypherionmc.craterlib.common; + +import com.hypherionmc.craterlib.core.platform.CompatUtils; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; + +public class ForgeCompatHelper implements CompatUtils { + + @Override + public boolean isPlayerActive(BridgedPlayer player) { + return true; + } + + @Override + public String getSkinUUID(BridgedPlayer player) { + return player.getStringUUID(); + } +} diff --git a/1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeLoaderHelper.java b/1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeLoaderHelper.java new file mode 100644 index 0000000..2be70fc --- /dev/null +++ b/1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeLoaderHelper.java @@ -0,0 +1,73 @@ +package com.hypherionmc.craterlib.common; + +import com.hypherionmc.craterlib.core.platform.Environment; +import com.hypherionmc.craterlib.core.platform.ModloaderEnvironment; +import net.minecraft.SharedConstants; +import net.minecraft.client.Minecraft; +import net.minecraftforge.fml.ModList; +import net.minecraftforge.fml.loading.FMLLoader; +import net.minecraftforge.fml.loading.FMLPaths; + +import java.io.File; + +/** + * @author HypherionSA + */ +public class ForgeLoaderHelper implements ModloaderEnvironment { + + public ForgeLoaderHelper() { + } + + @Override + public boolean isFabric() { + return false; + } + + @Override + public String getGameVersion() { + return SharedConstants.VERSION_STRING; + } + + @Override + public File getGameFolder() { + return Minecraft.getInstance().gameDirectory; + } + + @Override + public File getConfigFolder() { + return FMLPaths.CONFIGDIR.get().toFile(); + } + + @Override + public File getModsFolder() { + return FMLPaths.MODSDIR.get().toFile(); + } + + @Override + public Environment getEnvironment() { + switch (FMLLoader.getDist()) { + case CLIENT -> { + return Environment.CLIENT; + } + case DEDICATED_SERVER -> { + return Environment.SERVER; + } + } + return Environment.UNKNOWN; + } + + @Override + public boolean isModLoaded(String modid) { + return ModList.get().isLoaded(modid); + } + + @Override + public boolean isDevEnv() { + return !FMLLoader.isProduction(); + } + + @Override + public int getModCount() { + return ModList.get().size(); + } +} diff --git a/1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeServerEvents.java b/1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeServerEvents.java new file mode 100644 index 0000000..f354ddc --- /dev/null +++ b/1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeServerEvents.java @@ -0,0 +1,43 @@ +package com.hypherionmc.craterlib.common; + +import com.hypherionmc.craterlib.api.events.server.CraterRegisterCommandEvent; +import com.hypherionmc.craterlib.api.events.server.CraterServerLifecycleEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.commands.CommandsRegistry; +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import net.minecraftforge.event.RegisterCommandsEvent; +import net.minecraftforge.event.server.ServerStartedEvent; +import net.minecraftforge.event.server.ServerStartingEvent; +import net.minecraftforge.event.server.ServerStoppedEvent; +import net.minecraftforge.event.server.ServerStoppingEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; + +public class ForgeServerEvents { + + @SubscribeEvent + public void serverStarting(ServerStartingEvent event) { + CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Starting(BridgedMinecraftServer.of(event.getServer()))); + } + + @SubscribeEvent + public void serverStarted(ServerStartedEvent event) { + CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Started(BridgedMinecraftServer.of(event.getServer()))); + } + + @SubscribeEvent + public void serverStopping(ServerStoppingEvent event) { + CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Stopping(BridgedMinecraftServer.of(event.getServer()))); + } + + @SubscribeEvent + public void serverStopped(ServerStoppedEvent event) { + CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Stopped(BridgedMinecraftServer.of(event.getServer()))); + } + + @SubscribeEvent + public void onCommandRegister(RegisterCommandsEvent event) { + CraterEventBus.INSTANCE.postEvent(new CraterRegisterCommandEvent()); + CommandsRegistry.INSTANCE.registerCommands(event.getDispatcher()); + } + +} diff --git a/1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java b/1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java new file mode 100644 index 0000000..09bb181 --- /dev/null +++ b/1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java @@ -0,0 +1,24 @@ +package com.hypherionmc.craterlib.compat; + +import com.hypherionmc.craterlib.api.events.server.CraterPlayerEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import redstonedubstep.mods.vanishmod.api.PlayerVanishEvent; + +public class Vanish { + + public Vanish() { + + } + + @SubscribeEvent + public void vanishevent(PlayerVanishEvent event) { + if (event.isVanished()) { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerEvent.PlayerLoggedOut(BridgedPlayer.of(event.getEntity()))); + } else { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerEvent.PlayerLoggedIn(BridgedPlayer.of(event.getEntity()))); + } + } + +} \ No newline at end of file diff --git a/1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ConfigScreenHandlerMixin.java b/1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ConfigScreenHandlerMixin.java new file mode 100644 index 0000000..927bd23 --- /dev/null +++ b/1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ConfigScreenHandlerMixin.java @@ -0,0 +1,43 @@ +package com.hypherionmc.craterlib.mixin; + +import com.hypherionmc.craterlib.client.gui.config.CraterConfigScreen; +import com.hypherionmc.craterlib.core.config.ConfigController; +import com.hypherionmc.craterlib.core.config.ModuleConfig; +import com.hypherionmc.craterlib.core.config.annotations.NoConfigScreen; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.Screen; +import net.minecraftforge.client.ConfigScreenHandler; +import net.minecraftforge.forgespi.language.IModInfo; +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.CallbackInfoReturnable; + +import java.util.Optional; +import java.util.function.BiFunction; + +/** + * @author HypherionSA + */ +@Mixin(ConfigScreenHandler.class) +public class ConfigScreenHandlerMixin { + + /** + * Inject Auto Generated config Screens into forge + * + */ + @Inject(at = @At("RETURN"), method = "getScreenFactoryFor", cancellable = true, remap = false) + private static void injectConfigScreen(IModInfo selectedMod, CallbackInfoReturnable>> cir) { + ConfigController.getMonitoredConfigs().forEach((conf, watcher) -> { + if (!conf.getClass().isAnnotationPresent(NoConfigScreen.class)) { + ModuleConfig config = (ModuleConfig) conf; + if (config.getModId().equals(selectedMod.getModId())) { + cir.setReturnValue( + Optional.of((minecraft, screen) -> new CraterConfigScreen(config, screen)) + ); + } + } + }); + } + +} diff --git a/1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java b/1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java new file mode 100644 index 0000000..6e2f601 --- /dev/null +++ b/1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java @@ -0,0 +1,36 @@ +package com.hypherionmc.craterlib.mixin; + +import com.hypherionmc.craterlib.api.events.server.CraterServerChatEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.ChatUtils; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.PlayerChatMessage; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.FilteredText; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(value = ServerGamePacketListenerImpl.class, priority = Integer.MIN_VALUE) +public class ServerGamePacketListenerImplMixin { + + @Shadow + public ServerPlayer player; + + @Inject( + method = "lambda$handleChat$6", + at = @At("HEAD"), + cancellable = true + ) + private void injectChatEvent(Component component, PlayerChatMessage arg, FilteredText p_296589_, CallbackInfo ci) { + CraterServerChatEvent event = new CraterServerChatEvent(BridgedPlayer.of(this.player), arg.decoratedContent().getString(), ChatUtils.mojangToAdventure(arg.decoratedContent())); + CraterEventBus.INSTANCE.postEvent(event); + if (event.wasCancelled()) + ci.cancel(); + } + +} diff --git a/1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/network/CraterForgeNetworkHandler.java b/1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/network/CraterForgeNetworkHandler.java new file mode 100644 index 0000000..cdaeaa1 --- /dev/null +++ b/1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/network/CraterForgeNetworkHandler.java @@ -0,0 +1,98 @@ +package com.hypherionmc.craterlib.network; + +import com.hypherionmc.craterlib.CraterConstants; +import com.hypherionmc.craterlib.core.networking.PacketRegistry; +import com.hypherionmc.craterlib.core.networking.data.PacketContext; +import com.hypherionmc.craterlib.core.networking.data.PacketHolder; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.nojang.network.BridgedFriendlyByteBuf; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraft.client.Minecraft; +import net.minecraft.network.Connection; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraftforge.event.network.CustomPayloadEvent; +import net.minecraftforge.network.ChannelBuilder; +import net.minecraftforge.network.PacketDistributor; +import net.minecraftforge.network.SimpleChannel; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public class CraterForgeNetworkHandler extends PacketRegistry { + private final Map, SimpleChannel> CHANNELS = new HashMap<>(); + + public CraterForgeNetworkHandler(PacketSide side) { + super(side); + } + + protected void registerPacket(PacketHolder holder) { + if (CHANNELS.get(holder.messageType()) == null) { + SimpleChannel channel = ChannelBuilder + .named(holder.type().toMojang()) + .clientAcceptedVersions((a, b) -> true) + .serverAcceptedVersions((a, b) -> true) + .networkProtocolVersion(1) + .simpleChannel(); + + channel.messageBuilder(holder.messageType()) + .decoder(mojangDecoder(holder.decoder())) + .encoder(mojangEncoder(holder.encoder())) + .consumerNetworkThread(buildHandler(holder.handler())) + .add(); + + CHANNELS.put(holder.messageType(), channel); + } else { + CraterConstants.LOG.error("Trying to register duplicate packet for type {}", holder.messageType()); + } + } + + public void sendToServer(T packet) { + this.sendToServer(packet, false); + } + + public void sendToServer(T packet, boolean ignoreCheck) { + SimpleChannel channel = CHANNELS.get(packet.getClass()); + Connection connection = Minecraft.getInstance().getConnection().getConnection(); + if (channel.isRemotePresent(connection) || ignoreCheck) { + channel.send(packet, PacketDistributor.SERVER.noArg()); + } + } + + public void sendToClient(T packet, BridgedPlayer player) { + SimpleChannel channel = CHANNELS.get(packet.getClass()); + ServerGamePacketListenerImpl connection = player.getConnection(); + if (connection == null) + return; + + if (channel.isRemotePresent(connection.getConnection())) { + channel.send(packet, PacketDistributor.PLAYER.with(player.toMojangServerPlayer())); + } + } + + private Function mojangDecoder(Function handler) { + return byteBuf -> handler.apply(BridgedFriendlyByteBuf.of(byteBuf)); + } + + private BiConsumer mojangEncoder(BiConsumer handler) { + return ((t, byteBuf) -> handler.accept(t, BridgedFriendlyByteBuf.of(byteBuf))); + } + + private BiConsumer buildHandler(Consumer> handler) { + return (message, ctx) -> { + ctx.enqueueWork(() -> { + PacketSide side = ctx.getDirection().getReceptionSide().isServer() ? PacketSide.SERVER : PacketSide.CLIENT; + ServerPlayer player = ctx.getSender(); + handler.accept(new PacketContext<>(BridgedPlayer.of(player), message, side)); + }); + ctx.setPacketHandled(true); + }; + } +} diff --git a/1.20.4/Forge/src/main/resources/META-INF/mods.toml b/1.20.4/Forge/src/main/resources/META-INF/mods.toml new file mode 100644 index 0000000..89532de --- /dev/null +++ b/1.20.4/Forge/src/main/resources/META-INF/mods.toml @@ -0,0 +1,31 @@ +modLoader = "javafml" +loaderVersion = "[49,)" +license = "MIT" +issueTrackerURL = "https://github.com/firstdarkdev/craterLib/issues" + +[[mods]] + modId = "${mod_id}" + version = "${version}" + displayName = "${mod_name}" + displayURL = "https://modrinth.com/mod/craterlib" + logoFile = "craterlib_logo.png" + #credits="Thanks for this example mod goes to Java" + authors = "${mod_author}, Zenith" + description = ''' + A library mod used by First Dark Development and HypherionSA Mods + ''' + displayTest = "NONE" + +[[dependencies.${ mod_id }]] + modId = "forge" + mandatory = true + versionRange = "[49,)" + ordering = "NONE" + side = "BOTH" + +[[dependencies.${ mod_id }]] + modId = "minecraft" + mandatory = true + versionRange = "[1.20.4,1.20.5)" + ordering = "NONE" + side = "BOTH" diff --git a/1.20.4/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform b/1.20.4/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform new file mode 100644 index 0000000..a12ad8c --- /dev/null +++ b/1.20.4/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform @@ -0,0 +1 @@ +com.hypherionmc.craterlib.client.ForgeClientHelper \ No newline at end of file diff --git a/1.20.4/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform b/1.20.4/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform new file mode 100644 index 0000000..09e119f --- /dev/null +++ b/1.20.4/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform @@ -0,0 +1 @@ +com.hypherionmc.craterlib.common.ForgeCommonHelper \ No newline at end of file diff --git a/1.20.4/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils b/1.20.4/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils new file mode 100644 index 0000000..a9f823d --- /dev/null +++ b/1.20.4/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils @@ -0,0 +1 @@ +com.hypherionmc.craterlib.common.ForgeCompatHelper \ No newline at end of file diff --git a/1.20.4/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment b/1.20.4/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment new file mode 100644 index 0000000..02b4e07 --- /dev/null +++ b/1.20.4/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment @@ -0,0 +1 @@ +com.hypherionmc.craterlib.common.ForgeLoaderHelper \ No newline at end of file diff --git a/1.20.4/Forge/src/main/resources/craterlib.forge.mixins.json b/1.20.4/Forge/src/main/resources/craterlib.forge.mixins.json new file mode 100644 index 0000000..aa072d1 --- /dev/null +++ b/1.20.4/Forge/src/main/resources/craterlib.forge.mixins.json @@ -0,0 +1,17 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "com.hypherionmc.craterlib.mixin", + "compatibilityLevel": "JAVA_17", + "mixins": [ + ], + "client": [ + "ConfigScreenHandlerMixin" + ], + "server": [ + "ServerGamePacketListenerImplMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/1.20.4/Forge/src/main/resources/craterlib_logo.png b/1.20.4/Forge/src/main/resources/craterlib_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..ce0159cc4aa4d823ea354042e531b4522d6dfead GIT binary patch literal 49343 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 z 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 De~rmR literal 0 HcmV?d00001 diff --git a/1.20.4/LICENSE b/1.20.4/LICENSE new file mode 100644 index 0000000..f4a1e2d --- /dev/null +++ b/1.20.4/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) HypherionSA and Contributors 2024 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/1.20.4/NeoForge/build.gradle b/1.20.4/NeoForge/build.gradle new file mode 100644 index 0000000..489972e --- /dev/null +++ b/1.20.4/NeoForge/build.gradle @@ -0,0 +1,111 @@ +archivesBaseName = "${mod_name.replace(" ", "")}-NeoForge-${minecraft_version}" + +dependencies { + // Compat + modImplementation("maven.modrinth:vanishmod:${vanishmod_neo}") + + // Do not edit or remove + implementation project(":Common") +} + +shadowJar { + from sourceSets.main.output + configurations = [project.configurations.shade] + + dependencies { + exclude(dependency('com.google.code.gson:.*')) + + relocate 'me.hypherionmc.moonconfig', 'shadow.hypherionmc.moonconfig' + relocate 'me.hypherionmc.mcdiscordformatter', 'shadow.hypherionmc.mcdiscordformatter' + relocate 'net.kyori', 'shadow.kyori' + } + + setArchiveClassifier('dev-shadow') +} + +/** + * =============================================================================== + * = DO NOT EDIT BELOW THIS LINE UNLESS YOU KNOW WHAT YOU ARE DOING = + * =============================================================================== + */ + +unimined.minecraft { + neoForged { + loader neoforge_version + mixinConfig("${mod_id}.mixins.json", "${mod_id}.neoforge.mixins.json") + } +} + +remapJar { + inputFile.set shadowJar.archiveFile + dependsOn shadowJar + archiveClassifier.set null +} + +jar { + archiveClassifier.set "dev" +} + +processResources { + from project(":Common").sourceSets.main.resources + def buildProps = project.properties.clone() + + filesMatching("META-INF/mods.toml") { + expand buildProps + } +} + +compileTestJava.enabled = false + +tasks.withType(JavaCompile).configureEach { + source(project(":Common").sourceSets.main.allSource) +} + +/** + * Publishing Config + */ +publishing { + publications { + mavenJava(MavenPublication) { + artifactId project.archivesBaseName + from components.java + + artifact(remapJar) { + builtBy remapJar + } + + pom.withXml { + Node pomNode = asNode() + pomNode.dependencies.'*'.findAll() { + it.artifactId.text() == 'regutils-joined-fabric' || + it.artifactId.text() == 'core' || + it.artifactId.text() == 'toml' + }.each() { + it.parent().remove(it) + } + } + } + } + + repositories { + maven rootProject.orion.getPublishingMaven() + } +} + +publisher { + apiKeys { + modrinth(System.getenv("MODRINTH_TOKEN")) + curseforge(System.getenv("CURSE_TOKEN")) + } + + setCurseID(curse_id) + setModrinthID(modrinth_id) + setVersionType("release") + setChangelog("https://raw.githubusercontent.com/hypherionmc/changelogs/main/craterlib/changelog-forge.md") + setProjectVersion("${minecraft_version}-${project.version}") + setDisplayName("[NeoForge 1.20.4] CraterLib - ${project.version}") + setGameVersions("1.20.4") + setLoaders("neoforge") + setArtifact(remapJar) + setCurseEnvironment("both") +} diff --git a/1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/CraterLib.java b/1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/CraterLib.java new file mode 100644 index 0000000..2ae793b --- /dev/null +++ b/1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/CraterLib.java @@ -0,0 +1,44 @@ +package com.hypherionmc.craterlib; + +import com.hypherionmc.craterlib.api.events.client.LateInitEvent; +import com.hypherionmc.craterlib.common.NeoForgeServerEvents; +import com.hypherionmc.craterlib.compat.Vanish; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.core.networking.CraterPacketNetwork; +import com.hypherionmc.craterlib.core.networking.PacketRegistry; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.core.platform.ModloaderEnvironment; +import com.hypherionmc.craterlib.network.CraterNeoForgeNetworkHandler; +import com.hypherionmc.craterlib.nojang.client.BridgedMinecraft; +import com.hypherionmc.craterlib.nojang.client.BridgedOptions; +import net.minecraft.client.Minecraft; +import net.neoforged.bus.api.IEventBus; +import net.neoforged.fml.common.Mod; +import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent; +import net.neoforged.fml.loading.FMLEnvironment; +import net.neoforged.fml.loading.FMLLoader; +import net.neoforged.neoforge.common.NeoForge; + +@Mod(CraterConstants.MOD_ID) +public class CraterLib { + + private final PacketRegistry handler; + + public CraterLib(IEventBus eventBus) { + NeoForge.EVENT_BUS.register(new NeoForgeServerEvents()); + eventBus.addListener(this::commonSetup); + handler = new CraterNeoForgeNetworkHandler(FMLLoader.getDist().isClient() ? PacketSide.CLIENT : PacketSide.SERVER); + + if (ModloaderEnvironment.INSTANCE.isModLoaded("vmod")) { + eventBus.register(new Vanish()); + } + } + + public void commonSetup(FMLCommonSetupEvent evt) { + new CraterPacketNetwork(handler); + if (FMLEnvironment.dist.isClient()) { + LateInitEvent event = new LateInitEvent(new BridgedMinecraft(), BridgedOptions.of(Minecraft.getInstance().options)); + CraterEventBus.INSTANCE.postEvent(event); + } + } +} diff --git a/1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/client/NeoForgeClientEvents.java b/1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/client/NeoForgeClientEvents.java new file mode 100644 index 0000000..04ccf1d --- /dev/null +++ b/1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/client/NeoForgeClientEvents.java @@ -0,0 +1,25 @@ +package com.hypherionmc.craterlib.client; + +import com.hypherionmc.craterlib.CraterConstants; +import com.hypherionmc.craterlib.api.events.client.CraterClientTickEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedClientLevel; +import net.minecraft.client.Minecraft; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.Mod; +import net.neoforged.neoforge.event.TickEvent; + +@Mod.EventBusSubscriber(modid = CraterConstants.MOD_ID, bus = Mod.EventBusSubscriber.Bus.FORGE, value = Dist.CLIENT) +public class NeoForgeClientEvents { + + @SubscribeEvent + public static void clientTick(TickEvent.LevelTickEvent event) { + if (Minecraft.getInstance().level == null) + return; + + CraterClientTickEvent craterClientTickEvent = new CraterClientTickEvent(BridgedClientLevel.of(Minecraft.getInstance().level)); + CraterEventBus.INSTANCE.postEvent(craterClientTickEvent); + } + +} diff --git a/1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/client/NeoForgeClientHelper.java b/1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/client/NeoForgeClientHelper.java new file mode 100644 index 0000000..d6c2ef7 --- /dev/null +++ b/1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/client/NeoForgeClientHelper.java @@ -0,0 +1,40 @@ +package com.hypherionmc.craterlib.client; + +import com.hypherionmc.craterlib.core.platform.ClientPlatform; +import com.hypherionmc.craterlib.nojang.client.BridgedMinecraft; +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedClientLevel; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraft.client.Minecraft; +import net.minecraft.network.Connection; + +import java.util.Objects; + +/** + * @author HypherionSA + */ +public class NeoForgeClientHelper implements ClientPlatform { + + public NeoForgeClientHelper() { + } + + @Override + public BridgedMinecraft getClientInstance() { + return new BridgedMinecraft(); + } + + @Override + public BridgedPlayer getClientPlayer() { + return BridgedPlayer.of(Minecraft.getInstance().player); + } + + @Override + public BridgedClientLevel getClientLevel() { + return BridgedClientLevel.of(Minecraft.getInstance().level); + } + + @Override + public Connection getClientConnection() { + Objects.requireNonNull(Minecraft.getInstance().getConnection(), "Cannot send packets when not in game!"); + return Minecraft.getInstance().getConnection().getConnection(); + } +} diff --git a/1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/common/NeoForgeCommonHelper.java b/1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/common/NeoForgeCommonHelper.java new file mode 100644 index 0000000..50622a8 --- /dev/null +++ b/1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/common/NeoForgeCommonHelper.java @@ -0,0 +1,19 @@ +package com.hypherionmc.craterlib.common; + +import com.hypherionmc.craterlib.core.platform.CommonPlatform; +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import net.neoforged.neoforge.server.ServerLifecycleHooks; + +/** + * @author HypherionSA + */ +public class NeoForgeCommonHelper implements CommonPlatform { + + public NeoForgeCommonHelper() { + } + + @Override + public BridgedMinecraftServer getMCServer() { + return BridgedMinecraftServer.of(ServerLifecycleHooks.getCurrentServer()); + } +} diff --git a/1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/common/NeoForgeCompatHelper.java b/1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/common/NeoForgeCompatHelper.java new file mode 100644 index 0000000..469c172 --- /dev/null +++ b/1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/common/NeoForgeCompatHelper.java @@ -0,0 +1,22 @@ +package com.hypherionmc.craterlib.common; + +import com.hypherionmc.craterlib.core.platform.CompatUtils; +import com.hypherionmc.craterlib.core.platform.ModloaderEnvironment; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import redstonedubstep.mods.vanishmod.VanishUtil; + +public class NeoForgeCompatHelper implements CompatUtils { + + @Override + public boolean isPlayerActive(BridgedPlayer player) { + if (!ModloaderEnvironment.INSTANCE.isModLoaded("vmod")) + return true; + + return VanishUtil.isVanished(player.toMojangServerPlayer()); + } + + @Override + public String getSkinUUID(BridgedPlayer player) { + return player.getStringUUID(); + } +} diff --git a/1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/common/NeoForgeLoaderHelper.java b/1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/common/NeoForgeLoaderHelper.java new file mode 100644 index 0000000..2eb4c4a --- /dev/null +++ b/1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/common/NeoForgeLoaderHelper.java @@ -0,0 +1,73 @@ +package com.hypherionmc.craterlib.common; + +import com.hypherionmc.craterlib.core.platform.Environment; +import com.hypherionmc.craterlib.core.platform.ModloaderEnvironment; +import net.minecraft.SharedConstants; +import net.minecraft.client.Minecraft; +import net.neoforged.fml.ModList; +import net.neoforged.fml.loading.FMLLoader; +import net.neoforged.fml.loading.FMLPaths; + +import java.io.File; + +/** + * @author HypherionSA + */ +public class NeoForgeLoaderHelper implements ModloaderEnvironment { + + public NeoForgeLoaderHelper() { + } + + @Override + public boolean isFabric() { + return false; + } + + @Override + public String getGameVersion() { + return SharedConstants.VERSION_STRING; + } + + @Override + public File getGameFolder() { + return Minecraft.getInstance().gameDirectory; + } + + @Override + public File getConfigFolder() { + return FMLPaths.CONFIGDIR.get().toFile(); + } + + @Override + public File getModsFolder() { + return FMLPaths.MODSDIR.get().toFile(); + } + + @Override + public Environment getEnvironment() { + switch (FMLLoader.getDist()) { + case CLIENT -> { + return Environment.CLIENT; + } + case DEDICATED_SERVER -> { + return Environment.SERVER; + } + } + return Environment.UNKNOWN; + } + + @Override + public boolean isModLoaded(String modid) { + return ModList.get().isLoaded(modid); + } + + @Override + public boolean isDevEnv() { + return !FMLLoader.isProduction(); + } + + @Override + public int getModCount() { + return ModList.get().size(); + } +} diff --git a/1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/common/NeoForgeServerEvents.java b/1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/common/NeoForgeServerEvents.java new file mode 100644 index 0000000..91ca5b7 --- /dev/null +++ b/1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/common/NeoForgeServerEvents.java @@ -0,0 +1,43 @@ +package com.hypherionmc.craterlib.common; + +import com.hypherionmc.craterlib.api.events.server.CraterRegisterCommandEvent; +import com.hypherionmc.craterlib.api.events.server.CraterServerLifecycleEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.commands.CommandsRegistry; +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.neoforge.event.RegisterCommandsEvent; +import net.neoforged.neoforge.event.server.ServerStartedEvent; +import net.neoforged.neoforge.event.server.ServerStartingEvent; +import net.neoforged.neoforge.event.server.ServerStoppedEvent; +import net.neoforged.neoforge.event.server.ServerStoppingEvent; + +public class NeoForgeServerEvents { + + @SubscribeEvent + public void serverStarting(ServerStartingEvent event) { + CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Starting(BridgedMinecraftServer.of(event.getServer()))); + } + + @SubscribeEvent + public void serverStarted(ServerStartedEvent event) { + CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Started(BridgedMinecraftServer.of(event.getServer()))); + } + + @SubscribeEvent + public void serverStopping(ServerStoppingEvent event) { + CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Stopping(BridgedMinecraftServer.of(event.getServer()))); + } + + @SubscribeEvent + public void serverStopped(ServerStoppedEvent event) { + CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Stopped(BridgedMinecraftServer.of(event.getServer()))); + } + + @SubscribeEvent + public void onCommandRegister(RegisterCommandsEvent event) { + CraterEventBus.INSTANCE.postEvent(new CraterRegisterCommandEvent()); + CommandsRegistry.INSTANCE.registerCommands(event.getDispatcher()); + } + +} diff --git a/1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java b/1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java new file mode 100644 index 0000000..0302e29 --- /dev/null +++ b/1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java @@ -0,0 +1,24 @@ +package com.hypherionmc.craterlib.compat; + +import com.hypherionmc.craterlib.api.events.server.CraterPlayerEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.neoforged.bus.api.SubscribeEvent; +import redstonedubstep.mods.vanishmod.api.PlayerVanishEvent; + +public class Vanish { + + public Vanish() { + + } + + @SubscribeEvent + public void vanishevent(PlayerVanishEvent event) { + if (event.isVanished()) { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerEvent.PlayerLoggedOut(BridgedPlayer.of(event.getEntity()))); + } else { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerEvent.PlayerLoggedIn(BridgedPlayer.of(event.getEntity()))); + } + } + +} diff --git a/1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/mixin/ConfigScreenHandlerMixin.java b/1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/mixin/ConfigScreenHandlerMixin.java new file mode 100644 index 0000000..809671b --- /dev/null +++ b/1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/mixin/ConfigScreenHandlerMixin.java @@ -0,0 +1,43 @@ +package com.hypherionmc.craterlib.mixin; + +import com.hypherionmc.craterlib.client.gui.config.CraterConfigScreen; +import com.hypherionmc.craterlib.core.config.ConfigController; +import com.hypherionmc.craterlib.core.config.ModuleConfig; +import com.hypherionmc.craterlib.core.config.annotations.NoConfigScreen; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.Screen; +import net.neoforged.neoforge.client.ConfigScreenHandler; +import net.neoforged.neoforgespi.language.IModInfo; +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.CallbackInfoReturnable; + +import java.util.Optional; +import java.util.function.BiFunction; + +/** + * @author HypherionSA + */ +@Mixin(ConfigScreenHandler.class) +public class ConfigScreenHandlerMixin { + + /** + * Inject Auto Generated config Screens into forge + * + */ + @Inject(at = @At("RETURN"), method = "getScreenFactoryFor", cancellable = true, remap = false) + private static void injectConfigScreen(IModInfo selectedMod, CallbackInfoReturnable>> cir) { + ConfigController.getMonitoredConfigs().forEach((conf, watcher) -> { + if (!conf.getClass().isAnnotationPresent(NoConfigScreen.class)) { + ModuleConfig config = (ModuleConfig) conf; + if (config.getModId().equals(selectedMod.getModId())) { + cir.setReturnValue( + Optional.of((minecraft, screen) -> new CraterConfigScreen(config, screen)) + ); + } + } + }); + } + +} \ No newline at end of file diff --git a/1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java b/1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java new file mode 100644 index 0000000..6e2f601 --- /dev/null +++ b/1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java @@ -0,0 +1,36 @@ +package com.hypherionmc.craterlib.mixin; + +import com.hypherionmc.craterlib.api.events.server.CraterServerChatEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.ChatUtils; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.PlayerChatMessage; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.FilteredText; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(value = ServerGamePacketListenerImpl.class, priority = Integer.MIN_VALUE) +public class ServerGamePacketListenerImplMixin { + + @Shadow + public ServerPlayer player; + + @Inject( + method = "lambda$handleChat$6", + at = @At("HEAD"), + cancellable = true + ) + private void injectChatEvent(Component component, PlayerChatMessage arg, FilteredText p_296589_, CallbackInfo ci) { + CraterServerChatEvent event = new CraterServerChatEvent(BridgedPlayer.of(this.player), arg.decoratedContent().getString(), ChatUtils.mojangToAdventure(arg.decoratedContent())); + CraterEventBus.INSTANCE.postEvent(event); + if (event.wasCancelled()) + ci.cancel(); + } + +} diff --git a/1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/network/CraterNeoForgeNetworkHandler.java b/1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/network/CraterNeoForgeNetworkHandler.java new file mode 100644 index 0000000..a4f2450 --- /dev/null +++ b/1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/network/CraterNeoForgeNetworkHandler.java @@ -0,0 +1,116 @@ +package com.hypherionmc.craterlib.network; + +import com.hypherionmc.craterlib.CraterConstants; +import com.hypherionmc.craterlib.core.networking.PacketRegistry; +import com.hypherionmc.craterlib.core.networking.data.PacketContext; +import com.hypherionmc.craterlib.core.networking.data.PacketHolder; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.nojang.network.BridgedFriendlyByteBuf; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.world.entity.player.Player; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.LogicalSide; +import net.neoforged.neoforge.network.PacketDistributor; +import net.neoforged.neoforge.network.event.RegisterPayloadHandlerEvent; +import net.neoforged.neoforge.network.handling.IPayloadHandler; +import net.neoforged.neoforge.network.registration.IPayloadRegistrar; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public class CraterNeoForgeNetworkHandler extends PacketRegistry { + + private final Map, NeoForgePacketContainer> PACKETS = new HashMap<>(); + + public CraterNeoForgeNetworkHandler(PacketSide side) { + super(side); + } + + @SubscribeEvent + public void register(final RegisterPayloadHandlerEvent event) { + if (!PACKETS.isEmpty()) { + PACKETS.forEach((type, container) -> { + final IPayloadRegistrar registrar = event.registrar(container.packetId().getNamespace()); + registrar.common( + container.packetId(), + container.decoder(), + container.handler()); + }); + } + } + + protected void registerPacket(PacketHolder container) { + if (PACKETS.get(container.messageType()) == null) { + var packetContainer = new NeoForgePacketContainer<>( + container.messageType(), + container.type().toMojang(), + this.mojangEncoder(container.encoder()), + decoder(this.mojangDecoder(container.decoder())), + buildHandler(container.handler()) + ); + + PACKETS.put(container.messageType(), packetContainer); + } + } + + private FriendlyByteBuf.Reader> decoder(Function decoder) { + return (buf -> { + T packet = decoder.apply(buf); + return new NeoForgePacket(PACKETS.get(packet.getClass()), packet); + }); + } + + public void sendToServer(T packet) { + this.sendToServer(packet, false); + } + + public void sendToServer(T packet, boolean ignoreCheck) { + NeoForgePacketContainer container = PACKETS.get(packet.getClass()); + try { + PacketDistributor.SERVER.noArg().send(new NeoForgePacket<>(container, packet)); + } catch (Throwable t) { + CraterConstants.LOG.error("{} packet not registered on the client, this is needed.", packet.getClass(), t); + } + } + + public void sendToClient(T packet, BridgedPlayer player) { + NeoForgePacketContainer container = PACKETS.get(packet.getClass()); + try { + if (player.getConnection() == null) + return; + + if (player.getConnection().isConnected(container.packetId())) { + PacketDistributor.PLAYER.with(player.toMojangServerPlayer()).send(new NeoForgePacket<>(container, packet)); + } + } catch (Throwable t) { + CraterConstants.LOG.error("{} packet not registered on the server, this is needed.", packet.getClass(), t); + } + } + + private > IPayloadHandler buildHandler(Consumer> handler) { + return (payload, ctx) -> { + try { + PacketSide side = ctx.flow().getReceptionSide().equals(LogicalSide.SERVER) ? PacketSide.SERVER : PacketSide.CLIENT; + Player player = ctx.player().orElse(null); + handler.accept(new PacketContext<>(BridgedPlayer.of(player), payload.packet(), side)); + } catch (Throwable t) { + CraterConstants.LOG.error("Error handling packet: {} -> ", payload.packet().getClass(), t); + } + }; + } + + private Function mojangDecoder(Function handler) { + return (byteBuf) -> handler.apply(BridgedFriendlyByteBuf.of(byteBuf)); + } + + private BiConsumer mojangEncoder(BiConsumer handler) { + return (t, byteBuf) -> handler.accept(t, BridgedFriendlyByteBuf.of(byteBuf)); + } +} diff --git a/1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/network/NeoForgePacket.java b/1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/network/NeoForgePacket.java new file mode 100644 index 0000000..77775fc --- /dev/null +++ b/1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/network/NeoForgePacket.java @@ -0,0 +1,21 @@ +package com.hypherionmc.craterlib.network; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public record NeoForgePacket(NeoForgePacketContainer container, T packet) implements CustomPacketPayload { + + @Override + public void write(FriendlyByteBuf buff) { + container().encoder().accept(packet(), buff); + } + + @Override + public ResourceLocation id() { + return container().packetId(); + } +} \ No newline at end of file diff --git a/1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/network/NeoForgePacketContainer.java b/1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/network/NeoForgePacketContainer.java new file mode 100644 index 0000000..cddddb6 --- /dev/null +++ b/1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/network/NeoForgePacketContainer.java @@ -0,0 +1,16 @@ +package com.hypherionmc.craterlib.network; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.neoforge.network.handling.IPayloadHandler; + +import java.util.function.BiConsumer; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public record NeoForgePacketContainer(Class messageType, + ResourceLocation packetId, + BiConsumer encoder, + FriendlyByteBuf.Reader> decoder, + IPayloadHandler> handler) { } \ No newline at end of file diff --git a/1.20.4/NeoForge/src/main/resources/META-INF/mods.toml b/1.20.4/NeoForge/src/main/resources/META-INF/mods.toml new file mode 100644 index 0000000..519dde3 --- /dev/null +++ b/1.20.4/NeoForge/src/main/resources/META-INF/mods.toml @@ -0,0 +1,33 @@ +modLoader = "javafml" +loaderVersion = "[1,)" +license = "MIT" +issueTrackerURL = "https://github.com/firstdarkdev/craterLib/issues" + +[[mods]] + modId = "${mod_id}" + version = "${version}" + displayName = "${mod_name}" + displayURL = "https://modrinth.com/mod/craterlib" + logoFile = "craterlib_logo.png" + #credits="Thanks for this example mod goes to Java" + authors = "${mod_author}, Zenith" + description = ''' + A library mod used by First Dark Development and HypherionSA Mods + ''' + displayTest = "NONE" + +[[dependencies.${ mod_id }]] + modId = "neoforge" + type="required" + required=true + versionRange = "[20.4,)" + ordering = "NONE" + side = "BOTH" + +[[dependencies.${ mod_id }]] + modId = "minecraft" + type="required" + required=true + versionRange = "[1.20.4,1.20.5)" + ordering = "NONE" + side = "BOTH" \ No newline at end of file diff --git a/1.20.4/NeoForge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform b/1.20.4/NeoForge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform new file mode 100644 index 0000000..5bd719f --- /dev/null +++ b/1.20.4/NeoForge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform @@ -0,0 +1 @@ +com.hypherionmc.craterlib.client.NeoForgeClientHelper \ No newline at end of file diff --git a/1.20.4/NeoForge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform b/1.20.4/NeoForge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform new file mode 100644 index 0000000..6cb6efb --- /dev/null +++ b/1.20.4/NeoForge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform @@ -0,0 +1 @@ +com.hypherionmc.craterlib.common.NeoForgeCommonHelper \ No newline at end of file diff --git a/1.20.4/NeoForge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils b/1.20.4/NeoForge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils new file mode 100644 index 0000000..9475f11 --- /dev/null +++ b/1.20.4/NeoForge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils @@ -0,0 +1 @@ +com.hypherionmc.craterlib.common.NeoForgeCompatHelper \ No newline at end of file diff --git a/1.20.4/NeoForge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment b/1.20.4/NeoForge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment new file mode 100644 index 0000000..9a41f05 --- /dev/null +++ b/1.20.4/NeoForge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment @@ -0,0 +1 @@ +com.hypherionmc.craterlib.common.NeoForgeLoaderHelper \ No newline at end of file diff --git a/1.20.4/NeoForge/src/main/resources/craterlib.neoforge.mixins.json b/1.20.4/NeoForge/src/main/resources/craterlib.neoforge.mixins.json new file mode 100644 index 0000000..aa072d1 --- /dev/null +++ b/1.20.4/NeoForge/src/main/resources/craterlib.neoforge.mixins.json @@ -0,0 +1,17 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "com.hypherionmc.craterlib.mixin", + "compatibilityLevel": "JAVA_17", + "mixins": [ + ], + "client": [ + "ConfigScreenHandlerMixin" + ], + "server": [ + "ServerGamePacketListenerImplMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/1.20.4/NeoForge/src/main/resources/craterlib_logo.png b/1.20.4/NeoForge/src/main/resources/craterlib_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..ce0159cc4aa4d823ea354042e531b4522d6dfead GIT binary patch literal 49343 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 z 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 De~rmR literal 0 HcmV?d00001 diff --git a/1.20.4/README.md b/1.20.4/README.md new file mode 100644 index 0000000..49c0699 --- /dev/null +++ b/1.20.4/README.md @@ -0,0 +1,55 @@ +# CraterLib + +![badge-snapshot](https://maven.firstdarkdev.xyz/api/badge/latest/snapshots/me/hypherionmc/craterlib/CraterLib-common-1.20-pre6?color=40c14a&name=CraterLib-Snapshot) + +*** + +A Library mod and modding api for easier multi-version minecraft and mod loader development + +*** + +### Supported Minecraft Versions + +| Minecraft Version | Support Status | +|-------------------| -------------- | +| < 1.18.2 | ❌ | +| 1.18.2-1.20.2 | ✳️ | +| 1.20.4 | ✳️ | +| 1.21 | 🚧 | + +- ❌ - Not Supported; no bug fixes or new features. +- 🚧 - Work in Progress; not ready for release. +- ✳️ - Long Term Support; receives changes through backports only. +- ✅ - In Support; the active version, receiving all bugfixes and features directly. + +*** + +## Library Features + +* Universal Config System (TOML Based) +* Built in Helper Classes for Various minecraft features +* Built in Optifine-Compat utilities +* Various utilities for Blockstates, LANG, Math and Rendering +* Cross Mod-Loader Events - Based on [Acara](https://github.com/Keksuccino/acara) +* Cross Mod-Loader Config Screens (Based on [Cloth Config Lite](https://github.com/shedaniel/cloth-config-lite)) +* Automatic ModMenu and Forge Config screen registration +* Built in Cross Mod-Loader Network system +* Nojang Modding API + +*** + +## Setup Instructions + +There's a **wiki coming soon**, but for now, here's some basic instructions for building the project: + +1. `git clone` the project to a safe spot. +2. Install Java's JDK 17. Make sure you have the development version explicitly: + * Fedora: `sudo dnf install java-17-openjdk-devel` + * Ubuntu: `sudo apt install openjdk-17-jdk` + * macOS: `brew install openjdk@17` +3. Set it accordingly: + * Windows/macOS: Set the `JAVA_HOME` environment variable or use system settings + * Linux: `sudo update-alternatives --config java` +4. Navigate to the CraterLib folder, then run a `gradlew` file depending on your operating system: + * Windows: `.\gradlew.bat build` + * macOS/Linux/BSD: `chmod +x gradlew` and `./gradlew` diff --git a/1.20.4/build.gradle b/1.20.4/build.gradle new file mode 100644 index 0000000..92059b0 --- /dev/null +++ b/1.20.4/build.gradle @@ -0,0 +1,110 @@ +plugins { + id 'java' + id 'com.github.johnrengelman.shadow' version '8.1.1' apply false + id "xyz.wagyourtail.unimined" version "1.2.4" apply false + id "com.hypherionmc.modutils.modpublisher" version "2.1.+" + id "com.hypherionmc.modutils.orion" version "1.0.+" + id 'maven-publish' +} + +orion.setup { + multiProject = true + enableMirrorMaven = true + enableReleasesMaven = true + + dopplerToken = System.getenv("DOPPLER_KEY") + + versioning { + var relType = project.properties["releaseType"] ?: "${release_type}" + identifier("${relType}") + } +} + +group = project_group + +subprojects { + apply plugin: "xyz.wagyourtail.unimined" + apply plugin: "java" + apply plugin: 'maven-publish' + apply plugin: 'com.github.johnrengelman.shadow' + apply plugin: 'com.hypherionmc.modutils.modpublisher' + + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + + group = rootProject.group + + repositories { + mavenCentral() + + maven { + name = "Modrinth" + url = "https://api.modrinth.com/maven" + content { + includeGroup "maven.modrinth" + } + } + } + + configurations { + shade + modCompileOnly + implementation.extendsFrom shade + compileOnly.extendsFrom modCompileOnly + } + + dependencies { + // All Projects + shade "me.hypherionmc.moon-config:core:${moon_config}" + shade "me.hypherionmc.moon-config:toml:${moon_config}" + shade "com.hypherionmc:rpcsdk:${rpc_sdk}" + shade "me.hypherionmc.sdlink:mcdiscordformatter-1.20.3:${discord_formatter}" + shade "net.kyori:adventure-api:${adventure}" + shade "net.kyori:adventure-text-serializer-gson:${adventure}" + + compileOnly("org.projectlombok:lombok:${lombok}") + annotationProcessor("org.projectlombok:lombok:${lombok}") + } + + jar { + manifest { + attributes([ + 'Specification-Title' : mod_name, + 'Specification-Vendor' : mod_author, + 'Specification-Version' : project.jar.archiveVersion, + 'Implementation-Title' : project.name, + 'Implementation-Version' : project.jar.archiveVersion, + 'Implementation-Vendor' : mod_author, + 'Implementation-Timestamp': new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"), + 'Timestamp' : System.currentTimeMillis(), + 'Built-On-Java' : "${System.getProperty('java.vm.version')} (${System.getProperty('java.vm.vendor')})", + 'Built-On-Minecraft' : minecraft_version + ]) + } + } + +/** + * =============================================================================== + * = DO NOT EDIT BELOW THIS LINE UNLESS YOU KNOW WHAT YOU ARE DOING = + * =============================================================================== + */ + unimined.minecraft(sourceSets.main, true) { + version minecraft_version + + mappings { + mojmap() + devNamespace "mojmap" + } + } + + tasks.withType(JavaCompile).configureEach { + it.options.encoding = 'UTF-8' + it.options.release = 17 + } + + tasks.withType(GenerateModuleMetadata).configureEach { + enabled = false + } +} + +// TODO MODULE JARS diff --git a/1.20.4/gradle.properties b/1.20.4/gradle.properties new file mode 100644 index 0000000..3e54d04 --- /dev/null +++ b/1.20.4/gradle.properties @@ -0,0 +1,46 @@ +#Project +version_major=2 +version_minor=0 +version_patch=0 + +#Mod +mod_author=HypherionSA +mod_id=craterlib +mod_name=CraterLib + +# Shared +minecraft_version=1.20.4 +project_group=com.hypherionmc.craterlib + +# Fabric +fabric_loader=0.15.11 +fabric_api=0.97.0+1.20.4 + +# Forge +forge_version=49.0.49 + +# NeoForged +neoforge_version=234 + +# Dependencies +moon_config=1.0.9 +lombok=1.18.32 +adventure=4.16.0 +rpc_sdk=1.0 +discord_formatter=2.0.0 + +# Mod Dependencies +fabrictailor=2.3.1 +vanish=1.5.4+1.20.4 +mod_menu_version=9.2.0-beta.2 +vanishmod=1.1.15 +vanishmod_neo=puxrKAMr + +# Publishing +curse_id=867099 +modrinth_id=Nn8Wasaq +release_type=release + +# Gradle +org.gradle.jvmargs=-Xmx3G +org.gradle.daemon=false diff --git a/1.20.4/gradle/wrapper/gradle-wrapper.jar b/1.20.4/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..7454180f2ae8848c63b8b4dea2cb829da983f2fa GIT binary patch literal 59536 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 QuEJmfE|#`Dqx|px0L@K;Y5)KL literal 0 HcmV?d00001 diff --git a/1.20.4/gradle/wrapper/gradle-wrapper.properties b/1.20.4/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..48c0a02 --- /dev/null +++ b/1.20.4/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/1.20.4/gradlew b/1.20.4/gradlew new file mode 100644 index 0000000..b4f908a --- /dev/null +++ b/1.20.4/gradlew @@ -0,0 +1,183 @@ +#!/usr/bin/env bash + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MSYS* | MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +ARGV=("$@") +eval set -- $DEFAULT_JVM_OPTS + +IFS=$' +' read -rd '' -a JAVA_OPTS_ARR <<< "$(echo $JAVA_OPTS | xargs -n1)" +IFS=$' +' read -rd '' -a GRADLE_OPTS_ARR <<< "$(echo $GRADLE_OPTS | xargs -n1)" + +exec "$JAVACMD" "$@" "${JAVA_OPTS_ARR[@]}" "${GRADLE_OPTS_ARR[@]}" "-Dorg.gradle.appname=$APP_BASE_NAME" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "${ARGV[@]}" diff --git a/1.20.4/gradlew.bat b/1.20.4/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/1.20.4/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/1.20.4/settings.gradle b/1.20.4/settings.gradle new file mode 100644 index 0000000..0c53847 --- /dev/null +++ b/1.20.4/settings.gradle @@ -0,0 +1,14 @@ +pluginManagement { + repositories { + gradlePluginPortal() + maven { + url "https://mcentral.firstdark.dev/releases" + } + maven { + url "https://maven.firstdark.dev/releases" + } + } +} + +rootProject.name = 'CraterLib-1.20.4' +include("Common", "Fabric", "Forge", "NeoForge") diff --git a/1.20/.gitattributes b/1.20/.gitattributes new file mode 100644 index 0000000..20fc528 --- /dev/null +++ b/1.20/.gitattributes @@ -0,0 +1,15 @@ +* text eol=lf +*.bat text eol=crlf +*.patch text eol=lf +*.java text eol=lf +*.gradle text eol=crlf +*.png binary +*.gif binary +*.exe binary +*.dll binary +*.jar binary +*.lzma binary +*.zip binary +*.pyd binary +*.cfg text eol=lf +*.jks binary \ No newline at end of file diff --git a/1.20/.gitignore b/1.20/.gitignore new file mode 100644 index 0000000..966dad4 --- /dev/null +++ b/1.20/.gitignore @@ -0,0 +1,29 @@ +# eclipse +bin +*.launch +.settings +.metadata +.classpath +.project + +# idea +out +*.ipr +*.iws +*.iml +.idea + +# gradle +build +.gradle + +# other +eclipse +run + +artifacts +src/test/** + +workspace +upstream +rejects \ No newline at end of file diff --git a/1.20/.jenkins/Jenkinsfile.deploy b/1.20/.jenkins/Jenkinsfile.deploy new file mode 100644 index 0000000..f5e1bad --- /dev/null +++ b/1.20/.jenkins/Jenkinsfile.deploy @@ -0,0 +1,55 @@ +def JDK = "17" + +pipeline { + agent { + docker { + image "registry.firstdark.dev/java${JDK}:latest" + alwaysPull true + args '-v gradle-cache:/home/gradle/.gradle' + } + } + + environment { + GRADLE_USER_HOME = '/home/gradle/.gradle' + } + + stages { + stage("Notify Discord") { + steps { + discordSend webhookURL: env.FDD_WH_ADMIN, + title: "Deploy Started: CraterLib 1.20/1 Deploy #${BUILD_NUMBER}", + link: env.BUILD_URL, + result: 'SUCCESS', + description: "Build: [${BUILD_NUMBER}](${env.BUILD_URL})" + } + } + stage("Prepare") { + steps { + sh "chmod +x ./gradlew" + sh "./gradlew clean" + } + } + stage("Publish to Modrinth/Curseforge") { + steps { + sh "./gradlew publishMod -Prelease=true" + } + } + stage("Publish to Maven") { + steps { + sh "./gradlew publish -Prelease=true" + } + } + } + post { + always { + sh "./gradlew --stop" + deleteDir() + + discordSend webhookURL: env.FDD_WH_ADMIN, + title: "CraterLib Port Deploy #${BUILD_NUMBER}", + link: env.BUILD_URL, + result: currentBuild.currentResult, + description: "Build: [${BUILD_NUMBER}](${env.BUILD_URL})\nStatus: ${currentBuild.currentResult}" + } + } +} diff --git a/1.20/.jenkins/Jenkinsfile.snapshot b/1.20/.jenkins/Jenkinsfile.snapshot new file mode 100644 index 0000000..bd6f323 --- /dev/null +++ b/1.20/.jenkins/Jenkinsfile.snapshot @@ -0,0 +1,65 @@ +def projectName = "CraterLib"; +def projectIcon = "https://cdn.modrinth.com/data/Nn8Wasaq/a172c634683a11a2e9ae593e56eba7885743bb44.png"; +def JDK = "17"; +def majorMc = "1.20/1"; +def modLoaders = "forge|fabric|quilt"; +def supportedMc = "1.20|1.20.1"; +def reltype = "snapshot"; + +pipeline { + agent { + docker { + image "registry.firstdark.dev/java${JDK}:latest" + alwaysPull true + args '-v gradle-cache:/home/gradle/.gradle' + } + } + + environment { + GRADLE_USER_HOME = '/home/gradle/.gradle' + } + + stages { + stage("Notify Discord") { + steps { + discordSend webhookURL: env.SSS_WEBHOOK, + title: "Deploy Started: ${projectName} ${majorMc} Deploy #${BUILD_NUMBER}", + link: env.BUILD_URL, + result: 'SUCCESS', + description: "Build: [${BUILD_NUMBER}](${env.BUILD_URL})" + } + } + + stage("Prepare") { + steps { + sh "chmod +x ./gradlew" + sh "./gradlew build -PreleaseType=port" + } + } + + stage("Publish to Maven") { + steps { + catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') { + sh "./gradlew publish -PreleaseType=${reltype}" + } + } + } + } + + post { + always { + sh "./gradlew --stop" + + fddsnapshotter apiKey: env.PLATFORM_KEY, + projectSlug: "craterlib", + projectName: "${projectName}", + projectIcon: "${projectIcon}", + modLoaders: "${modLoaders}", + minecraftVersions: "${supportedMc}", + failWebhook: env.SSS_WEBHOOK, + publishWebhooks: "${env.SSS_WEBHOOK}|${env.FDD_WH}" + + deleteDir() + } + } +} diff --git a/1.20/Common/build.gradle b/1.20/Common/build.gradle new file mode 100644 index 0000000..843d62a --- /dev/null +++ b/1.20/Common/build.gradle @@ -0,0 +1,68 @@ +archivesBaseName = "${mod_name.replace(" ", "")}-Common-${minecraft_version}" + +dependencies { + +} + +shadowJar { + from sourceSets.main.output + configurations = [project.configurations.shade] + + dependencies { + exclude(dependency('com.google.code.gson:.*')) + + relocate 'me.hypherionmc.moonconfig', 'shadow.hypherionmc.moonconfig' + relocate 'me.hypherionmc.mcdiscordformatter', 'shadow.hypherionmc.mcdiscordformatter' + relocate 'net.kyori', 'shadow.kyori' + } + setArchiveClassifier("dev") +} + +/** + * =============================================================================== + * = DO NOT EDIT BELOW THIS LINE UNLESS YOU KNOW WHAT YOU ARE DOING = + * =============================================================================== + */ + +unimined.minecraft { + fabric { + loader fabric_loader + } + + defaultRemapJar = false +} + +processResources { + def buildProps = project.properties.clone() + + filesMatching(['pack.mcmeta']) { + expand buildProps + } +} + +/** + * Publishing Config + */ +publishing { + publications { + mavenCommon(MavenPublication) { + artifactId project.archivesBaseName + from components.java + + pom.withXml { + Node pomNode = asNode() + pomNode.dependencies.'*'.findAll() { + it.artifactId.text() == 'regutils-joined-fabric' || + it.artifactId.text() == 'core' || + it.artifactId.text() == 'toml' + }.each() { + it.parent().remove(it) + } + } + } + } + + repositories { + maven rootProject.orion.getPublishingMaven() + } +} \ No newline at end of file diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/CraterConstants.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/CraterConstants.java new file mode 100644 index 0000000..93edffc --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/CraterConstants.java @@ -0,0 +1,10 @@ +package com.hypherionmc.craterlib; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CraterConstants { + public static final String MOD_ID = "craterlib"; + public static final String MOD_NAME = "CraterLib"; + public static final Logger LOG = LoggerFactory.getLogger(MOD_NAME); +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/api/commands/CraterCommand.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/api/commands/CraterCommand.java new file mode 100644 index 0000000..cc4969e --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/api/commands/CraterCommand.java @@ -0,0 +1,53 @@ +package com.hypherionmc.craterlib.api.commands; + +import com.hypherionmc.craterlib.nojang.authlib.BridgedGameProfile; +import com.hypherionmc.craterlib.nojang.commands.BridgedCommandSourceStack; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.TriConsumer; +import com.mojang.brigadier.arguments.ArgumentType; +import lombok.Getter; +import net.minecraft.commands.arguments.GameProfileArgument; +import org.apache.commons.lang3.tuple.Pair; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.function.Consumer; + +@Getter +public class CraterCommand { + + private final HashMap, TriConsumer>> arguments = new LinkedHashMap<>(); + private Consumer executor; + + private final String commandName; + private int permissionLevel = 4; + + CraterCommand(String commandName) { + this.commandName = commandName; + } + + public static CraterCommand literal(String commandName) { + return new CraterCommand(commandName); + } + + public CraterCommand requiresPermission(int perm) { + this.permissionLevel = perm; + return this; + } + + public CraterCommand withGameProfileArgument(String key, TriConsumer, BridgedCommandSourceStack> executor) { + arguments.put(key, Pair.of(GameProfileArgument.gameProfile(), executor)); + return this; + } + + public CraterCommand executes(Consumer ctx) { + executor = ctx; + return this; + } + + public boolean hasArguments() { + return !arguments.isEmpty(); + } + +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/CraterClientTickEvent.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/CraterClientTickEvent.java new file mode 100644 index 0000000..c68c596 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/CraterClientTickEvent.java @@ -0,0 +1,14 @@ +package com.hypherionmc.craterlib.api.events.client; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedClientLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public class CraterClientTickEvent extends CraterEvent { + + private final BridgedClientLevel level; + +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/CraterSinglePlayerEvent.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/CraterSinglePlayerEvent.java new file mode 100644 index 0000000..ab671f0 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/CraterSinglePlayerEvent.java @@ -0,0 +1,21 @@ +package com.hypherionmc.craterlib.api.events.client; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public class CraterSinglePlayerEvent extends CraterEvent { + + private final BridgedPlayer player; + + public static class PlayerLogin extends CraterSinglePlayerEvent { + + public PlayerLogin(BridgedPlayer player) { + super(player); + } + + } +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/LateInitEvent.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/LateInitEvent.java new file mode 100644 index 0000000..33c06fb --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/LateInitEvent.java @@ -0,0 +1,16 @@ +package com.hypherionmc.craterlib.api.events.client; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.client.BridgedMinecraft; +import com.hypherionmc.craterlib.nojang.client.BridgedOptions; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public class LateInitEvent extends CraterEvent { + + private final BridgedMinecraft minecraft; + private final BridgedOptions options; + +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/PlayerJoinRealmEvent.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/PlayerJoinRealmEvent.java new file mode 100644 index 0000000..1ef95de --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/PlayerJoinRealmEvent.java @@ -0,0 +1,14 @@ +package com.hypherionmc.craterlib.api.events.client; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.realmsclient.dto.BridgedRealmsServer; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public class PlayerJoinRealmEvent extends CraterEvent { + + private final BridgedRealmsServer server; + +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/ScreenEvent.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/ScreenEvent.java new file mode 100644 index 0000000..034ca4a --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/client/ScreenEvent.java @@ -0,0 +1,27 @@ +package com.hypherionmc.craterlib.api.events.client; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.client.gui.BridgedScreen; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; + +@Getter +@RequiredArgsConstructor +public class ScreenEvent extends CraterEvent { + + private final BridgedScreen screen; + + @Getter + public static class Opening extends ScreenEvent { + + private final BridgedScreen currentScreen; + @Setter private BridgedScreen newScreen; + + public Opening(BridgedScreen currentScreen, BridgedScreen newScreen) { + super(newScreen); + this.currentScreen = currentScreen; + this.newScreen = newScreen; + } + } +} \ No newline at end of file diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/common/CraterPlayerDeathEvent.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/common/CraterPlayerDeathEvent.java new file mode 100644 index 0000000..02cd63c --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/common/CraterPlayerDeathEvent.java @@ -0,0 +1,21 @@ +package com.hypherionmc.craterlib.api.events.common; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.ChatUtils; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; +import net.minecraft.world.damagesource.DamageSource; + +@RequiredArgsConstructor +@Getter +public class CraterPlayerDeathEvent extends CraterEvent { + + private final BridgedPlayer player; + private final DamageSource damageSource; + + public Component getDeathMessage() { + return ChatUtils.mojangToAdventure(damageSource.getLocalizedDeathMessage(player.toMojang())); + } +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterAdvancementEvent.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterAdvancementEvent.java new file mode 100644 index 0000000..5cd659d --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterAdvancementEvent.java @@ -0,0 +1,34 @@ +package com.hypherionmc.craterlib.api.events.server; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.advancements.BridgedAdvancement; +import com.hypherionmc.craterlib.nojang.advancements.BridgedDisplayInfo; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import lombok.Getter; +import net.kyori.adventure.text.Component; + +import java.util.Optional; + +@Getter +public class CraterAdvancementEvent extends CraterEvent { + + private final BridgedAdvancement advancement; + private final BridgedPlayer player; + private final Component title; + private final Component description; + + public CraterAdvancementEvent(BridgedPlayer player, BridgedAdvancement advancement) { + this.advancement = advancement; + this.player = player; + + Optional displayInfo = advancement.displayInfo(); + + if (displayInfo.isPresent()) { + this.title = displayInfo.get().displayName(); + this.description = displayInfo.get().description(); + } else { + this.title = Component.text("Unknown"); + this.description = Component.text("Unknown"); + } + } +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterCommandEvent.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterCommandEvent.java new file mode 100644 index 0000000..364cc22 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterCommandEvent.java @@ -0,0 +1,58 @@ +package com.hypherionmc.craterlib.api.events.server; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.ChatUtils; +import com.mojang.brigadier.ParseResults; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.context.StringRange; +import lombok.Getter; +import lombok.Setter; +import net.kyori.adventure.text.Component; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.arguments.ComponentArgument; +import net.minecraft.world.entity.player.Player; +import org.jetbrains.annotations.Nullable; + +@Getter +public class CraterCommandEvent extends CraterEvent { + + private final ParseResults parseResults; + @Setter private Throwable exception; + private final String command; + + private CraterCommandEvent(ParseResults parseResults, String command) { + this.parseResults = parseResults; + this.command = command; + } + + public static CraterCommandEvent of(ParseResults stack, String command) { + return new CraterCommandEvent(stack, command); + } + + public String getCommandString() { + return parseResults.getReader().getString(); + } + + @Nullable + public BridgedPlayer getPlayer() { + try { + Player p = parseResults.getContext().getLastChild().getSource().getPlayer(); + + if (p != null) + return BridgedPlayer.of(p); + } catch (Exception ignored) {} + + return null; + } + + public String getTarget() { + CommandContext context = parseResults.getContext().build(parseResults.getReader().getString()); + StringRange selector_range = parseResults.getContext().getArguments().get("targets").getRange(); + return context.getInput().substring(selector_range.getStart(), selector_range.getEnd()); + } + + public Component getMessage() { + return ChatUtils.mojangToAdventure(ComponentArgument.getComponent(parseResults.getContext().build(parseResults.getReader().getString()), "message")); + } +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterPlayerEvent.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterPlayerEvent.java new file mode 100644 index 0000000..7eb3701 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterPlayerEvent.java @@ -0,0 +1,30 @@ +package com.hypherionmc.craterlib.api.events.server; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public class CraterPlayerEvent extends CraterEvent { + + private final BridgedPlayer player; + + public static class PlayerLoggedIn extends CraterPlayerEvent { + + public PlayerLoggedIn(BridgedPlayer player) { + super(player); + } + + } + + public static class PlayerLoggedOut extends CraterPlayerEvent { + + public PlayerLoggedOut(BridgedPlayer player) { + super(player); + } + + } + +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterRegisterCommandEvent.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterRegisterCommandEvent.java new file mode 100644 index 0000000..269065a --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterRegisterCommandEvent.java @@ -0,0 +1,15 @@ +package com.hypherionmc.craterlib.api.events.server; + +import com.hypherionmc.craterlib.api.commands.CraterCommand; +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.commands.CommandsRegistry; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +public class CraterRegisterCommandEvent extends CraterEvent { + + public void registerCommand(CraterCommand cmd) { + CommandsRegistry.INSTANCE.registerCommand(cmd); + } + +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterServerChatEvent.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterServerChatEvent.java new file mode 100644 index 0000000..b25170a --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterServerChatEvent.java @@ -0,0 +1,25 @@ +package com.hypherionmc.craterlib.api.events.server; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.core.event.annot.Cancellable; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import lombok.Getter; +import lombok.Setter; +import net.kyori.adventure.text.Component; + +@Cancellable +@Getter +public class CraterServerChatEvent extends CraterEvent { + + public final String message, username; + public final BridgedPlayer player; + @Setter private Component component; + + public CraterServerChatEvent(BridgedPlayer player, String message, Component component) { + this.message = message; + this.player = player; + this.username = player.getGameProfile().getName(); + this.component = component; + } + +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterServerLifecycleEvent.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterServerLifecycleEvent.java new file mode 100644 index 0000000..3f87d04 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/CraterServerLifecycleEvent.java @@ -0,0 +1,34 @@ +package com.hypherionmc.craterlib.api.events.server; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +public class CraterServerLifecycleEvent extends CraterEvent { + + @RequiredArgsConstructor + @Getter + public static class Starting extends CraterServerLifecycleEvent { + private final BridgedMinecraftServer server; + } + + @RequiredArgsConstructor + @Getter + public static class Started extends CraterServerLifecycleEvent { + private final BridgedMinecraftServer server; + } + + @RequiredArgsConstructor + @Getter + public static class Stopping extends CraterServerLifecycleEvent { + private final BridgedMinecraftServer server; + } + + @RequiredArgsConstructor + @Getter + public static class Stopped extends CraterServerLifecycleEvent { + private final BridgedMinecraftServer server; + } + +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/MessageBroadcastEvent.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/MessageBroadcastEvent.java new file mode 100644 index 0000000..6b11404 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/MessageBroadcastEvent.java @@ -0,0 +1,20 @@ +package com.hypherionmc.craterlib.api.events.server; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; + +import java.util.function.Function; + +@RequiredArgsConstructor +@Getter +public class MessageBroadcastEvent extends CraterEvent { + + private final Component component; + private final Function function; + private final boolean bl; + private final String threadName; + +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/PlayerPreLoginEvent.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/PlayerPreLoginEvent.java new file mode 100644 index 0000000..eaabffd --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/api/events/server/PlayerPreLoginEvent.java @@ -0,0 +1,20 @@ +package com.hypherionmc.craterlib.api.events.server; + +import com.hypherionmc.craterlib.core.event.CraterEvent; +import com.hypherionmc.craterlib.nojang.authlib.BridgedGameProfile; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import net.kyori.adventure.text.Component; + +import java.net.SocketAddress; + +@RequiredArgsConstructor +@Getter +public class PlayerPreLoginEvent extends CraterEvent { + + private final SocketAddress address; + private final BridgedGameProfile gameProfile; + @Setter private Component message; + +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/api/networking/CraterNetworkHandler.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/api/networking/CraterNetworkHandler.java new file mode 100644 index 0000000..6b57e96 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/api/networking/CraterNetworkHandler.java @@ -0,0 +1,28 @@ +package com.hypherionmc.craterlib.api.networking; + +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; + +import java.util.List; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public interface CraterNetworkHandler { + + void sendToServer(T packet); + + void sendToServer(T packet, boolean ignoreCheck); + + void sendToClient(T packet, BridgedPlayer player); + + default void sendToClients(T packet, List players) { + for (BridgedPlayer player : players) { + sendToClient(packet, player); + } + } + + default void sendToAllClients(T packet, BridgedMinecraftServer server) { + sendToClients(packet, server.getPlayers()); + } +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/CraterConfigScreen.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/CraterConfigScreen.java new file mode 100644 index 0000000..50fb3cf --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/CraterConfigScreen.java @@ -0,0 +1,393 @@ +package com.hypherionmc.craterlib.client.gui.config; + +import com.hypherionmc.craterlib.CraterConstants; +import com.hypherionmc.craterlib.client.gui.config.widgets.*; +import com.hypherionmc.craterlib.core.config.ModuleConfig; +import com.hypherionmc.craterlib.core.config.annotations.HideFromScreen; +import com.hypherionmc.craterlib.core.config.annotations.SubConfig; +import com.hypherionmc.craterlib.core.config.annotations.Tooltip; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.*; +import me.hypherionmc.moonconfig.core.conversion.SpecComment; +import net.minecraft.ChatFormatting; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.ConfirmScreen; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Mth; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.joml.Matrix4f; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * @author HypherionSA + */ +public class CraterConfigScreen extends Screen { + public static final float SCROLLBAR_BOTTOM_COLOR = .5f; + public static final float SCROLLBAR_TOP_COLOR = .67f; + private static final int TOP = 26; + private static final int BOTTOM = 24; + private final Screen parent; + private final List> options = new ArrayList<>(); + private final ModuleConfig config; + public double scrollerAmount; + private boolean dragging; + + public CraterConfigScreen(ModuleConfig config, Screen parent, Object subConfig) { + super(Component.translatable("cl." + config.getClass().getSimpleName().toLowerCase() + ".title")); + this.parent = parent; + this.config = config; + if (subConfig != null) { + setupScreenFromConfig(subConfig, subConfig.getClass()); + } else { + setupScreenFromConfig(config, config.getClass()); + } + } + + public CraterConfigScreen(ModuleConfig config, Screen parent) { + this(config, parent, null); + } + + private static Component toText(Enum val) { + return Component.translatable(val.toString()); + } + + private static Component toText(Boolean bool) { + return Component.translatable(bool.toString()); + } + + private void setupScreenFromConfig(Object object, Class clazz) { + while (clazz != Object.class) { + for (Field field : clazz.getDeclaredFields()) { + final int fieldModifiers = field.getModifiers(); + if (object == null || Modifier.isStatic(fieldModifiers) || Modifier.isTransient(fieldModifiers)) { + continue; + } + + try { + if (!field.isAccessible()) { + field.setAccessible(true); + } + if (field.isAnnotationPresent(HideFromScreen.class)) + return; + Object val = field.get(object); + + /* Lang Stuff */ + String baseLangKey = "cl." + clazz.getSimpleName().toLowerCase() + "." + field.getName().toLowerCase(); + String[] tooltipLang = field.isAnnotationPresent(SpecComment.class) ? new String[]{field.getAnnotation(SpecComment.class).value()} : new String[0]; + if (field.isAnnotationPresent(Tooltip.class)) { + tooltipLang = field.getAnnotation(Tooltip.class).value(); + } + + add(Component.translatable(baseLangKey), + val, + () -> val, + (ret) -> { + try { + field.set(object, ret); + config.saveConfig(config); + } catch (IllegalAccessException e) { + CraterConstants.LOG.error("Failed to update value for field {} in config {}", field.getName(), config.getConfigName(), e); + } + }, + field.isAnnotationPresent(SubConfig.class), + tooltipLang + ); + } catch (IllegalAccessException e) { + CraterConstants.LOG.error("Failed to access value for field {} in config {}", field.getName(), config.getConfigName(), e); + } + } + clazz = clazz.getSuperclass(); + } + } + + public void add(Component text, T value, @Nullable Supplier defaultValue, Consumer savingConsumer, boolean isSubConfig, String... langKeys) { + Option option = (Option) createOption(value, isSubConfig); + option.text = text; + option.defaultValue = defaultValue; + option.savingConsumer = savingConsumer; + option.originalValue = value; + option.value = value; + option.setLangKeys(List.of(langKeys)); + options.add(option); + option.onAdd(); + } + + private Option createOption(T value, boolean isSubConfig) { + if (value instanceof Enum) { + Object[] objects = value.getClass().getEnumConstants(); + return new ToggleButton>((List) Arrays.asList(objects), CraterConfigScreen::toText); + } + if (value instanceof Boolean) { + return new ToggleButton<>(Arrays.asList(Boolean.TRUE, Boolean.FALSE), CraterConfigScreen::toText); + } + if (value instanceof String) { + return new TextConfigOption<>(Function.identity(), Function.identity()); + } + if (value instanceof Integer) { + return new TextConfigOption<>(Objects::toString, Integer::valueOf); + } + if (value instanceof Long) { + return new TextConfigOption<>(Objects::toString, Long::valueOf); + } + if (value instanceof Double) { + return new TextConfigOption<>(Objects::toString, Double::valueOf); + } + if (value instanceof Float) { + return new TextConfigOption<>(Objects::toString, Float::valueOf); + } + if (value instanceof BigInteger) { + return new TextConfigOption<>(Objects::toString, BigInteger::new); + } + if (value instanceof BigDecimal) { + return new TextConfigOption<>(Objects::toString, BigDecimal::new); + } + if (value instanceof ResourceLocation) { + return new TextConfigOption<>(Objects::toString, ResourceLocation::new); + } + if (isSubConfig) { + return new SubConfigWidget<>(config, this, value); + } + throw new IllegalArgumentException(String.valueOf(value)); + } + + @Override + protected void init() { + super.init(); + ((List) children()).addAll(options); + + int buttonWidths = Math.min(200, (width - 50 - 12) / 3); + addRenderableWidget(new InternalConfigButton(this, width / 2 - buttonWidths - 3, height - 22, buttonWidths, 20, Component.empty(), true)); + addRenderableWidget(new InternalConfigButton(this, width / 2 + 3, height - 22, buttonWidths, 20, Component.empty(), false)); + } + + @Override + public void render(@NotNull GuiGraphics matrices, int mouseX, int mouseY, float delta) { + overlayBackground(matrices.pose(), TOP, height - BOTTOM, 32); + + renderScrollBar(); + + matrices.pose().pushPose(); + matrices.pose().translate(0, 0, 500.0); + overlayBackground(matrices.pose(), 0, TOP, 64); + overlayBackground(matrices.pose(), height - BOTTOM, height, 64); + renderShadow(matrices.pose()); + matrices.drawCenteredString(font, getTitle(), width / 2, 9, 0xFFFFFF); + super.render(matrices, mouseX, mouseY, delta); + matrices.pose().popPose(); + + int y = (int) (TOP + 4 - Math.round(scrollerAmount)); + for (Option option : options) { + int height1 = option.height(); + option.render(minecraft, font, 40, y, width - 80, height1, matrices, mouseX, mouseY, delta); + renderConfigTooltip(matrices, font, mouseX, mouseY, 40, y, font.width(option.text), height1, option.text.getString(), option.getLangKeys().toArray(new String[0])); + y += height1; + } + } + + private void renderScrollBar() { + int listHeight = height - BOTTOM - TOP; + int totalHeight = totalHeight(); + if (totalHeight > listHeight) { + int maxScroll = Math.max(0, totalHeight - listHeight); + int height = listHeight * listHeight / totalHeight; + height = Mth.clamp(height, 32, listHeight); + height = Math.max(10, height); + int minY = Math.min(Math.max((int) scrollerAmount * (listHeight - height) / maxScroll + TOP, TOP), this.height - BOTTOM - height); + + int scrollbarPositionMaxX = width; + int scrollbarPositionMinX = scrollbarPositionMaxX - 6; + + int maxY = this.height - BOTTOM; + //RenderSystem.disableTexture(); + Tesselator tesselator = Tesselator.getInstance(); + BufferBuilder buffer = tesselator.getBuilder(); + RenderSystem.setShader(GameRenderer::getPositionColorShader); + buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR); + + buffer.vertex(scrollbarPositionMinX, maxY, 0.0D).color(0, 0, 0, 255).endVertex(); + buffer.vertex(scrollbarPositionMaxX, maxY, 0.0D).color(0, 0, 0, 255).endVertex(); + buffer.vertex(scrollbarPositionMaxX, TOP, 0.0D).color(0, 0, 0, 255).endVertex(); + buffer.vertex(scrollbarPositionMinX, TOP, 0.0D).color(0, 0, 0, 255).endVertex(); + + buffer.vertex(scrollbarPositionMinX, minY + height, 0.0D).color(SCROLLBAR_BOTTOM_COLOR, SCROLLBAR_BOTTOM_COLOR, SCROLLBAR_BOTTOM_COLOR, 1).endVertex(); + buffer.vertex(scrollbarPositionMaxX, minY + height, 0.0D).color(SCROLLBAR_BOTTOM_COLOR, SCROLLBAR_BOTTOM_COLOR, SCROLLBAR_BOTTOM_COLOR, 1).endVertex(); + buffer.vertex(scrollbarPositionMaxX, minY, 0.0D).color(SCROLLBAR_BOTTOM_COLOR, SCROLLBAR_BOTTOM_COLOR, SCROLLBAR_BOTTOM_COLOR, 1).endVertex(); + buffer.vertex(scrollbarPositionMinX, minY, 0.0D).color(SCROLLBAR_BOTTOM_COLOR, SCROLLBAR_BOTTOM_COLOR, SCROLLBAR_BOTTOM_COLOR, 1).endVertex(); + buffer.vertex(scrollbarPositionMinX, (minY + height - 1), 0.0D).color(SCROLLBAR_TOP_COLOR, SCROLLBAR_TOP_COLOR, SCROLLBAR_TOP_COLOR, 1).endVertex(); + buffer.vertex((scrollbarPositionMaxX - 1), (minY + height - 1), 0.0D).color(SCROLLBAR_TOP_COLOR, SCROLLBAR_TOP_COLOR, SCROLLBAR_TOP_COLOR, 1).endVertex(); + buffer.vertex((scrollbarPositionMaxX - 1), minY, 0.0D).color(SCROLLBAR_TOP_COLOR, SCROLLBAR_TOP_COLOR, SCROLLBAR_TOP_COLOR, 1).endVertex(); + buffer.vertex(scrollbarPositionMinX, minY, 0.0D).color(SCROLLBAR_TOP_COLOR, SCROLLBAR_TOP_COLOR, SCROLLBAR_TOP_COLOR, 1).endVertex(); + tesselator.end(); + RenderSystem.disableBlend(); + //RenderSystem.enableTexture(); + } + } + + private void renderShadow(PoseStack matrices) { + Tesselator tesselator = Tesselator.getInstance(); + BufferBuilder buffer = tesselator.getBuilder(); + RenderSystem.enableBlend(); + RenderSystem.blendFuncSeparate(770, 771, 0, 1); + //RenderSystem.disableTexture(); + RenderSystem.setShader(GameRenderer::getPositionTexColorShader); + Matrix4f matrix = matrices.last().pose(); + buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX_COLOR); + buffer.vertex(matrix, 0, TOP + 4, 0.0F).uv(0, 1).color(0, 0, 0, 0).endVertex(); + buffer.vertex(matrix, width, TOP + 4, 0.0F).uv(1, 1).color(0, 0, 0, 0).endVertex(); + buffer.vertex(matrix, width, TOP, 0.0F).uv(1, 0).color(0, 0, 0, 185).endVertex(); + buffer.vertex(matrix, 0, TOP, 0.0F).uv(0, 0).color(0, 0, 0, 185).endVertex(); + buffer.vertex(matrix, 0, height - BOTTOM, 0.0F).uv(0, 1).color(0, 0, 0, 185).endVertex(); + buffer.vertex(matrix, width, height - BOTTOM, 0.0F).uv(1, 1).color(0, 0, 0, 185).endVertex(); + buffer.vertex(matrix, width, height - BOTTOM - 4, 0.0F).uv(1, 0).color(0, 0, 0, 0).endVertex(); + buffer.vertex(matrix, 0, height - BOTTOM - 4, 0.0F).uv(0, 0).color(0, 0, 0, 0).endVertex(); + tesselator.end(); + //RenderSystem.enableTexture(); + RenderSystem.disableBlend(); + } + + protected void overlayBackground(PoseStack matrices, int h1, int h2, int color) { + overlayBackground(matrices.last().pose(), 0, h1, width, h2, color, color, color, 255, 255); + } + + protected void overlayBackground(Matrix4f matrix, int minX, int minY, int maxX, int maxY, int red, int green, int blue, int startAlpha, int endAlpha) { + Tesselator tesselator = Tesselator.getInstance(); + BufferBuilder buffer = tesselator.getBuilder(); + RenderSystem.setShaderTexture(0, Screen.BACKGROUND_LOCATION); + RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f); + RenderSystem.setShader(GameRenderer::getPositionTexColorShader); + buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX_COLOR); + buffer.vertex(matrix, minX, maxY, 0.0F).uv(minX / 32.0F, maxY / 32.0F).color(red, green, blue, endAlpha).endVertex(); + buffer.vertex(matrix, maxX, maxY, 0.0F).uv(maxX / 32.0F, maxY / 32.0F).color(red, green, blue, endAlpha).endVertex(); + buffer.vertex(matrix, maxX, minY, 0.0F).uv(maxX / 32.0F, minY / 32.0F).color(red, green, blue, startAlpha).endVertex(); + buffer.vertex(matrix, minX, minY, 0.0F).uv(minX / 32.0F, minY / 32.0F).color(red, green, blue, startAlpha).endVertex(); + tesselator.end(); + } + + public int scrollHeight() { + int totalHeight = totalHeight(); + int listHeight = height - BOTTOM - TOP; + if (totalHeight <= listHeight) { + return 0; + } + return totalHeight - listHeight; + } + + public int totalHeight() { + int i = 8; + for (Option option : options) { + i += option.height(); + } + return i; + } + + public boolean hasErrors() { + for (Option option : options) { + if (option.hasErrors) { + return true; + } + } + return false; + } + + public boolean isEdited() { + for (Option option : options) { + if (option.isEdited()) { + return true; + } + } + return false; + } + + public void save() { + for (Option option : options) { + option.save(); + option.originalValue = option.value; + } + } + + @Override + public void onClose() { + if (isEdited()) { + minecraft.setScreen(new ConfirmScreen(this::acceptConfirm, Component.translatable("t.clc.quit_config"), + Component.translatable("t.clc.quit_config_sure"), + Component.translatable("t.clc.quit_discard"), + Component.translatable("gui.cancel"))); + } else { + minecraft.setScreen(parent); + } + } + + @Override + public boolean mouseScrolled(double d, double e, double f) { + if (e >= TOP && e <= height - BOTTOM) { + scrollerAmount = Mth.clamp(scrollerAmount - f * 16.0D, 0, scrollHeight()); + return true; + } + return super.mouseScrolled(d, e, f); + } + + @Override + public boolean mouseClicked(double d, double e, int i) { + this.dragging = i == 0 && d >= width - 6 && d < width; + return super.mouseClicked(d, e, i) || dragging; + } + + @Override + public boolean mouseDragged(double d, double e, int i, double f, double g) { + if (super.mouseDragged(d, e, i, f, g)) { + return true; + } + if (i != 0 || !this.dragging) { + return false; + } + if (e < TOP) { + scrollerAmount = 0; + } else if (e > height - BOTTOM) { + scrollerAmount = scrollHeight(); + } else { + double h = Math.max(1, this.scrollHeight()); + int j = height - BOTTOM - TOP; + int k = Mth.clamp((int) ((float) (j * j) / (float) this.scrollHeight()), 32, j - 8); + double l = Math.max(1.0, h / (double) (j - k)); + scrollerAmount = Mth.clamp(scrollerAmount + g * l, 0, scrollHeight()); + } + return true; + } + + private void acceptConfirm(boolean t) { + if (!t) { + minecraft.setScreen(this); + } else { + minecraft.setScreen(parent); + } + } + + private void renderConfigTooltip(GuiGraphics stack, Font font, int mouseX, int mouseY, int startX, int startY, int sizeX, int sizeY, String title, String... description) { + if (mouseX > startX && mouseX < startX + sizeX) { + if (mouseY > startY && mouseY < startY + sizeY) { + List list = new ArrayList<>(); + list.add(Component.translatable(ChatFormatting.BOLD + "" + ChatFormatting.YELLOW + title)); + for (String desc : description) { + list.add(Component.translatable(desc)); + } + stack.renderComponentTooltip(font, list, mouseX, mouseY); + } + } + } +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/AbstractConfigWidget.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/AbstractConfigWidget.java new file mode 100644 index 0000000..ed34f6b --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/AbstractConfigWidget.java @@ -0,0 +1,27 @@ +package com.hypherionmc.craterlib.client.gui.config.widgets; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.client.gui.components.EditBox; + +/** + * Copied from Cloth Config Lite + * ... + */ +public class AbstractConfigWidget extends BaseWidget { + + public static final int buttonWidth = 200; + public static final int buttonHeight = 20; + public W widget; + + @Override + public void render(Minecraft minecraft, Font font, int x, int y, int width, int height, GuiGraphics matrices, int mouseX, int mouseY, float delta) { + super.render(minecraft, font, x, y, width, height, matrices, mouseX, mouseY, delta); + int i = (widget instanceof EditBox ? 1 : 0); + widget.setX(x + width - 200 - resetButtonOffset + i); + widget.setY(y + i + 1); + widget.render(matrices, mouseX, mouseY, delta); + } +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/BaseWidget.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/BaseWidget.java new file mode 100644 index 0000000..0583e61 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/BaseWidget.java @@ -0,0 +1,61 @@ +package com.hypherionmc.craterlib.client.gui.config.widgets; + +import net.minecraft.ChatFormatting; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.Button; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.network.chat.TextColor; + +/** + * Copied from Cloth Config Lite + * ... + */ +public class BaseWidget extends Option { + + public static final int resetButtonOffset = 48; + private final Button resetButton = addChild(Button.builder(Component.literal("Reset"), this::onResetPressed).size(46, 20).build()); + private boolean hideReset = false; + + private boolean isSubConfig = false; + + private void onResetPressed(Button button) { + value = defaultValue.get(); + reset(); + } + + public void hideReset() { + this.hideReset = true; + } + + public boolean isSubConfig() { + return isSubConfig; + } + + public void setSubConfig(boolean subConfig) { + isSubConfig = subConfig; + } + + @Override + public void render(Minecraft minecraft, Font font, int x, int y, int width, int height, GuiGraphics matrices, int mouseX, int mouseY, float delta) { + MutableComponent text = Component.literal(this.text.getString()); + boolean edited = isEdited() || hasErrors; + if (edited) { + text.withStyle(ChatFormatting.ITALIC); + if (hasErrors) { + text.withStyle(style -> style.withColor(TextColor.fromRgb(16733525))); + } + } else { + text.withStyle(ChatFormatting.GRAY); + } + matrices.drawString(font, text, x, y + font.lineHeight - 2, 0xFFFFFF); + resetButton.setX(x + width - 46); + resetButton.setY(y + 1); + resetButton.active = isNotDefault(); + if (!hideReset) { + resetButton.render(matrices, mouseX, mouseY, delta); + } + } +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/InternalConfigButton.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/InternalConfigButton.java new file mode 100644 index 0000000..c1ee0fd --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/InternalConfigButton.java @@ -0,0 +1,52 @@ +package com.hypherionmc.craterlib.client.gui.config.widgets; + +import com.hypherionmc.craterlib.client.gui.config.CraterConfigScreen; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.AbstractButton; +import net.minecraft.client.gui.narration.NarratedElementType; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.NotNull; + +/** + * @author HypherionSA + */ +public class InternalConfigButton extends AbstractButton { + + CraterConfigScreen screen; + boolean cancel; + + public InternalConfigButton(CraterConfigScreen screen, int i, int j, int k, int l, Component component, boolean cancel) { + super(i, j, k, l, component); + this.screen = screen; + this.cancel = cancel; + } + + @Override + public void render(@NotNull GuiGraphics poseStack, int i, int j, float f) { + if (cancel) { + setMessage(Component.translatable(screen.isEdited() ? "t.clc.cancel_discard" : "gui.cancel")); + } else { + boolean hasErrors = screen.hasErrors(); + active = screen.isEdited() && !hasErrors; + setMessage(Component.translatable(hasErrors ? "t.clc.error" : "t.clc.save")); + } + super.render(poseStack, i, j, f); + } + + @Override + protected void updateWidgetNarration(NarrationElementOutput narrationElementOutput) { + narrationElementOutput.add(NarratedElementType.USAGE, getMessage()); + } + + @Override + public void onPress() { + if (cancel) { + screen.onClose(); + } else { + screen.save(); + } + } + + +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/Option.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/Option.java new file mode 100644 index 0000000..f49ca68 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/Option.java @@ -0,0 +1,71 @@ +package com.hypherionmc.craterlib.client.gui.config.widgets; + +import lombok.Getter; +import lombok.Setter; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.events.AbstractContainerEventHandler; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Supplier; + +/** + * Copied from Cloth Config Lite + * ... + */ +public abstract class Option extends AbstractContainerEventHandler { + + public Component text; + @Nullable + public Supplier defaultValue; + public Consumer savingConsumer; + public T originalValue; + public T value; + public boolean hasErrors; + public List children = new ArrayList<>(); + @Setter + @Getter + private List langKeys = new ArrayList<>(); + + public abstract void render(Minecraft minecraft, Font font, int x, int y, int width, int height, GuiGraphics matrices, int mouseX, int mouseY, float delta); + + public int height() { + return 22; + } + + @Override + public List children() { + return children; + } + + protected R addChild(R listener) { + ((List) children).add(listener); + return listener; + } + + public void onAdd() { + } + + protected void reset() { + onAdd(); + } + + public boolean isEdited() { + return !Objects.equals(originalValue, value); + } + + protected boolean isNotDefault() { + return defaultValue != null && !Objects.equals(defaultValue.get(), value); + } + + public void save() { + savingConsumer.accept(value); + } +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/SubConfigWidget.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/SubConfigWidget.java new file mode 100644 index 0000000..9426ccf --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/SubConfigWidget.java @@ -0,0 +1,40 @@ +package com.hypherionmc.craterlib.client.gui.config.widgets; + +import com.hypherionmc.craterlib.client.gui.config.CraterConfigScreen; +import com.hypherionmc.craterlib.core.config.ModuleConfig; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; + +/** + * @author HypherionSA + */ +public class SubConfigWidget extends AbstractConfigWidget { + + private final Object subConfig; + private final ModuleConfig config; + private final Screen screen; + + public SubConfigWidget(ModuleConfig config, Screen screen, Object subConfig) { + this.config = config; + this.subConfig = subConfig; + this.screen = screen; + + this.widget = addChild(Button.builder(Component.translatable("t.clc.opensubconfig"), this::openSubConfig).size(200, buttonHeight).build()); + } + + @Override + public void render(Minecraft minecraft, Font font, int x, int y, int width, int height, GuiGraphics matrices, int mouseX, int mouseY, float delta) { + this.text = Component.literal(subConfig.getClass().getSimpleName().toLowerCase()); + this.hideReset(); + super.render(minecraft, font, x, y, width, height, matrices, mouseX, mouseY, delta); + } + + private void openSubConfig(Button button) { + Minecraft.getInstance().setScreen(new CraterConfigScreen(config, screen, subConfig)); + } + +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/TextConfigOption.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/TextConfigOption.java new file mode 100644 index 0000000..cc05cec --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/TextConfigOption.java @@ -0,0 +1,45 @@ +package com.hypherionmc.craterlib.client.gui.config.widgets; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; + +import java.util.function.Function; + +/** + * Copied from Cloth Config Lite + * ... + */ +public class TextConfigOption extends AbstractConfigWidget { + + private final Function toString; + private final Function fromString; + + public TextConfigOption(Function toString, Function fromString) { + this.toString = toString; + this.fromString = fromString; + this.widget = addChild(new WrappedEditBox(Minecraft.getInstance().font, 0, 0, 198, 18, null)); + } + + @Override + public void onAdd() { + widget.setMaxLength(1000000); + widget.setValue(toString.apply(value)); + widget.setResponder(this::update); + } + + @Override + public void render(Minecraft minecraft, Font font, int x, int y, int width, int height, GuiGraphics matrices, int mouseX, int mouseY, float delta) { + widget.setTextColor(hasErrors ? 16733525 : 14737632); + super.render(minecraft, font, x, y, width, height, matrices, mouseX, mouseY, delta); + } + + private void update(String s) { + try { + this.value = fromString.apply(s); + this.hasErrors = false; + } catch (Exception e) { + this.hasErrors = true; + } + } +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/ToggleButton.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/ToggleButton.java new file mode 100644 index 0000000..03ea11d --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/ToggleButton.java @@ -0,0 +1,33 @@ +package com.hypherionmc.craterlib.client.gui.config.widgets; + +import net.minecraft.client.gui.components.Button; +import net.minecraft.network.chat.Component; + +import java.util.List; +import java.util.function.Function; + +/** + * Copied from Cloth Config Lite + * ... + */ +public class ToggleButton extends AbstractConfigWidget { + + private final List options; + private final Function toComponent; + + public ToggleButton(List options, Function toComponent) { + this.options = options; + this.toComponent = toComponent; + this.widget = addChild(Button.builder(Component.empty(), this::switchNext).size(buttonWidth, buttonHeight).build()); + } + + @Override + public void onAdd() { + widget.setMessage(toComponent.apply(value)); + } + + private void switchNext(Button button) { + value = options.get((options.indexOf(value) + 1) % options.size()); + onAdd(); + } +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/WrappedEditBox.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/WrappedEditBox.java new file mode 100644 index 0000000..42c0c0c --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/client/gui/config/widgets/WrappedEditBox.java @@ -0,0 +1,29 @@ +package com.hypherionmc.craterlib.client.gui.config.widgets; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.components.EditBox; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.NotNull; + +/** + * @author HypherionSA + */ +public class WrappedEditBox extends EditBox { + + public WrappedEditBox(Font font, int i, int j, int k, int l, @NotNull Component component) { + super(font, i, j, k, l, component); + } + + @Override + public void setFocused(boolean bl) { + for (GuiEventListener child : Minecraft.getInstance().screen.children()) { + if (child instanceof TextConfigOption option) { + WrappedEditBox box = option.widget; + super.setFocused(box == this); + } + } + super.setFocused(bl); + } +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/client/mentions/MentionCondition.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/client/mentions/MentionCondition.java new file mode 100644 index 0000000..e8bffde --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/client/mentions/MentionCondition.java @@ -0,0 +1,13 @@ +package com.hypherionmc.craterlib.client.mentions; + +/** + * Based on ... + */ +@FunctionalInterface +public interface MentionCondition { + + boolean shouldAddMention(String currentWord); + + MentionCondition ALWAYS = currentWord -> true; + +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/client/mentions/MentionsController.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/client/mentions/MentionsController.java new file mode 100644 index 0000000..bad3b43 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/client/mentions/MentionsController.java @@ -0,0 +1,47 @@ +package com.hypherionmc.craterlib.client.mentions; + +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; +import lombok.Getter; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Based on ... + */ +public class MentionsController { + + private static final Map> mentions = new LinkedHashMap<>(); + private static final Map mentionConditions = new LinkedHashMap<>(); + @Getter + private static boolean lastMentionConditional = true; + + public static void registerMention(ResourceIdentifier mentionClass, Collection suggestions, MentionCondition condition) { + mentions.put(mentionClass, suggestions); + mentionConditions.put(mentionClass, condition); + } + + public static Collection getMentions(String currentWord) { + ArrayList applicableMentions = new ArrayList<>(); + lastMentionConditional = false; + + mentionConditions.forEach((mention, condition) -> { + boolean shouldSuggest = condition.shouldAddMention(currentWord); + if (!shouldSuggest) return; + + if (!lastMentionConditional && condition != MentionCondition.ALWAYS) { + lastMentionConditional = true; + } + + applicableMentions.addAll(mentions.get(mention)); + }); + + return applicableMentions; + } + + public static boolean hasMentions() { + return !mentions.isEmpty(); + } +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/config/ConfigController.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/config/ConfigController.java new file mode 100644 index 0000000..41c9471 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/config/ConfigController.java @@ -0,0 +1,49 @@ +package com.hypherionmc.craterlib.core.config; + +import com.hypherionmc.craterlib.CraterConstants; +import lombok.Getter; +import me.hypherionmc.moonconfig.core.file.FileWatcher; +import org.jetbrains.annotations.ApiStatus; + +import java.io.Serializable; +import java.util.HashMap; + +/** + * @author HypherionSA + * Controls Config File Reloads and Events + */ +public final class ConfigController implements Serializable { + + /** + * Cache of registered configs + */ + @Getter + private static final HashMap monitoredConfigs = new HashMap<>(); + + /** + * INTERNAL METHOD - Register and watch the config + * + * @param config - The config class to register and watch + */ + @ApiStatus.Internal + public static void register_config(ModuleConfig config) { + if (monitoredConfigs.containsKey(config)) { + CraterConstants.LOG.error("Failed to register " + config.getConfigPath().getName() + ". Config already registered"); + } else { + FileWatcher configWatcher = new FileWatcher(); + try { + configWatcher.setWatch(config.getConfigPath(), () -> { + if (!config.isSaveCalled()) { + CraterConstants.LOG.info("Sending Reload Event for: " + config.getConfigPath().getName()); + config.configReloaded(); + } + }); + } catch (Exception e) { + CraterConstants.LOG.error("Failed to register " + config.getConfigPath().getName() + " for auto reloading. " + e.getMessage()); + } + monitoredConfigs.put(config, configWatcher); + CraterConstants.LOG.info("Registered " + config.getConfigPath().getName() + " successfully!"); + } + } + +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/config/ModuleConfig.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/config/ModuleConfig.java new file mode 100644 index 0000000..181efdc --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/config/ModuleConfig.java @@ -0,0 +1,184 @@ +package com.hypherionmc.craterlib.core.config; + +import me.hypherionmc.moonconfig.core.CommentedConfig; +import me.hypherionmc.moonconfig.core.Config; +import me.hypherionmc.moonconfig.core.conversion.ObjectConverter; +import me.hypherionmc.moonconfig.core.file.CommentedFileConfig; + +import java.io.File; + +/** + * @author HypherionSA + * Base Config class containing the save, upgrading and loading logic. + * All config classes must extend this class + */ +public class ModuleConfig { + + /* Final Variables */ + private final transient File configPath; + private final transient String networkID; + + private final transient String configName; + + private final transient String modId; + + private transient boolean isSaveCalled = false; + + /** + * Set up the config + * + * @param modId - The ID of the Mod/Module the config belongs to + * @param configName - The name of the config file, excluding extension + */ + public ModuleConfig(String modId, String configName) { + this(modId, "", configName); + } + + public ModuleConfig(String modId, String subFolder, String configName) { + /* Preserve the order of the config values */ + Config.setInsertionOrderPreserved(true); + + /* Configure Paths and Network SYNC ID */ + File configDir = new File("config" + (subFolder.isEmpty() ? "" : File.separator + subFolder)); + configPath = new File(configDir + File.separator + configName + ".toml"); + networkID = modId + ":conf_" + configName.replace("-", "_"); + this.modId = modId; + this.configName = configName; + + /* Check if the required directories exists, otherwise we create them */ + if (!configDir.exists()) { + configDir.mkdirs(); + } + } + + /** + * This method has to be called in the config constructor. This creates or upgrades the config file as needed + * + * @param config - The config class to use + */ + public void registerAndSetup(ModuleConfig config) { + if (!configPath.exists() || configPath.length() < 2) { + saveConfig(config); + } else { + migrateConfig(config); + } + /* Register the Config for Watching and events */ + ConfigController.register_config(this); + this.configReloaded(); + } + + /** + * Save the config to the disk + * + * @param conf - The config class to serialize and save + */ + public void saveConfig(ModuleConfig conf) { + this.isSaveCalled = true; + /* Set up the Serializer and Config Object */ + ObjectConverter converter = new ObjectConverter(); + CommentedFileConfig config = CommentedFileConfig.builder(configPath).build(); + + /* Save the config and fire the reload events */ + converter.toConfig(conf, config); + config.save(); + configReloaded(); + this.isSaveCalled = false; + } + + /** + * Load the config from the file into a Class + * + * @param conf - The config Class to load + * @return - Returns the loaded version of the class + */ + public T loadConfig(Object conf) { + /* Set up the Serializer and Config Object */ + ObjectConverter converter = new ObjectConverter(); + CommentedFileConfig config = CommentedFileConfig.builder(configPath).build(); + config.load(); + + /* Load the config and return the loaded config */ + converter.toObject(config, conf); + return (T) conf; + } + + /** + * INTERNAL METHOD - Upgrades the config files in the events the config structure changes + * + * @param conf - The config class to load + */ + public void migrateConfig(ModuleConfig conf) { + /* Set up the Serializer and Config Objects */ + CommentedFileConfig config = CommentedFileConfig.builder(configPath).build(); + CommentedFileConfig newConfig = CommentedFileConfig.builder(configPath).build(); + config.load(); + + /* Upgrade the config */ + new ObjectConverter().toConfig(conf, newConfig); + updateConfigValues(config, newConfig, newConfig, ""); + newConfig.save(); + + config.close(); + newConfig.close(); + } + + public void updateConfigValues(CommentedConfig oldConfig, CommentedConfig newConfig, CommentedConfig outputConfig, String subKey) { + /* Loop over the config keys and check what has changed */ + newConfig.valueMap().forEach((key, value) -> { + String finalKey = subKey + (subKey.isEmpty() ? "" : ".") + key; + if (value instanceof CommentedConfig commentedConfig) { + updateConfigValues(oldConfig, commentedConfig, outputConfig, finalKey); + } else { + outputConfig.set(finalKey, + oldConfig.contains(finalKey) ? oldConfig.get(finalKey) : value); + } + }); + } + + /** + * Get the location of the config file + * + * @return - The FILE object containing the config file + */ + public File getConfigPath() { + return configPath; + } + + /** + * Get the NETWORK SYNC ID + * + * @return - Returns the Sync ID in format modid:config_name + */ + public String getNetworkID() { + return networkID; + } + + /** + * Fired whenever changes to the config are detected + */ + public void configReloaded() { + + } + + /** + * Get the name of the Config File + * + * @return + */ + public String getConfigName() { + return configName; + } + + /** + * Get the MODID of the Module the config is registered to + * + * @return + */ + public String getModId() { + return modId; + } + + public boolean isSaveCalled() { + return isSaveCalled; + } +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/HideFromScreen.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/HideFromScreen.java new file mode 100644 index 0000000..512a025 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/HideFromScreen.java @@ -0,0 +1,8 @@ +package com.hypherionmc.craterlib.core.config.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface HideFromScreen { +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/NoConfigScreen.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/NoConfigScreen.java new file mode 100644 index 0000000..4288ee0 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/NoConfigScreen.java @@ -0,0 +1,12 @@ +package com.hypherionmc.craterlib.core.config.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * @author HypherionSA + * Allows Modules to disable Automatic Config Screens + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface NoConfigScreen { +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/SubConfig.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/SubConfig.java new file mode 100644 index 0000000..e3ec808 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/SubConfig.java @@ -0,0 +1,16 @@ +package com.hypherionmc.craterlib.core.config.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author HypherionSA + * Used to determine if a Config section should be rendered as a separate screen + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface SubConfig { +} + diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/Syncable.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/Syncable.java new file mode 100644 index 0000000..838b35b --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/Syncable.java @@ -0,0 +1,15 @@ +package com.hypherionmc.craterlib.core.config.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author HypherionSA + * //TODO Currently unused, but to be used with Config Syncing in the future + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Syncable { +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/Tooltip.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/Tooltip.java new file mode 100644 index 0000000..538471c --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/config/annotations/Tooltip.java @@ -0,0 +1,16 @@ +package com.hypherionmc.craterlib.core.config.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author HypherionSA + * Provides tooltips to the config GUI + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Tooltip { + String[] value(); +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEvent.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEvent.java new file mode 100644 index 0000000..89ff00a --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEvent.java @@ -0,0 +1,30 @@ +package com.hypherionmc.craterlib.core.event; + +import com.hypherionmc.craterlib.core.event.annot.Cancellable; +import com.hypherionmc.craterlib.core.event.exception.CraterEventCancellationException; + +public class CraterEvent { + + private boolean canceled = false; + + private boolean canCancel() { + return this.getClass().isAnnotationPresent(Cancellable.class); + } + + public void cancelEvent() { + try { + if (!this.canCancel()) { + throw new CraterEventCancellationException("Tried to cancel non-cancelable event: " + this.getClass().getName()); + } + + this.canceled = true; + } catch (Exception e) { + e.printStackTrace(); + } + } + + public boolean wasCancelled() { + return this.canceled; + } + +} \ No newline at end of file diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEventBus.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEventBus.java new file mode 100644 index 0000000..74eeecc --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEventBus.java @@ -0,0 +1,241 @@ +package com.hypherionmc.craterlib.core.event; + +import com.hypherionmc.craterlib.CraterConstants; +import com.hypherionmc.craterlib.core.event.annot.CraterEventListener; +import org.slf4j.Logger; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.*; +import java.util.function.Consumer; + +public final class CraterEventBus { + + public static final CraterEventBus INSTANCE = new CraterEventBus(); + private static final Logger LOGGER = CraterConstants.LOG; + private final Map, List> events = new HashMap<>(); + + public void postEvent(CraterEvent event) { + if (eventsRegisteredForType(event.getClass())) { + List l = new ArrayList<>(events.get(event.getClass())); + l.sort((o1, o2) -> Integer.compare(o2.priority, o1.priority)); + + for (ListenerContainer c : l) { + c.notifyListener(event); + } + } + } + + public void registerEventListener(Class clazz) { + this.registerListenerMethods(this.getEventMethodsOf(clazz)); + } + + public void registerEventListener(Object object) { + this.registerListenerMethods(this.getEventMethodsOf(object)); + } + + private void registerListenerMethods(List methods) { + for (EventMethod m : methods) { + Consumer listener = (event) -> { + try { + m.method.invoke(m.parentObject, event); + } catch (Exception e) { + throw new RuntimeException(e); + } + }; + + ListenerContainer container = new ListenerContainer(m.eventType, listener, m.priority); + container.listenerParentClassName = m.parentClass.getName(); + container.listenerMethodName = m.method.getName(); + this.registerListener(container); + } + } + + private List getEventMethodsOf(Object objectOrClass) { + List l = new ArrayList<>(); + try { + if (objectOrClass != null) { + boolean isClass = (objectOrClass instanceof Class); + Class c = isClass ? (Class) objectOrClass : objectOrClass.getClass(); + for (Method m : c.getMethods()) { + if (isClass && Modifier.isStatic(m.getModifiers())) { + EventMethod em = EventMethod.tryCreateFrom(new AnalyzedMethod(m, c)); + if ((em != null) && this.hasEventAnnotation(em)) l.add(em); + } + if (!isClass && !Modifier.isStatic(m.getModifiers())) { + EventMethod em = EventMethod.tryCreateFrom(new AnalyzedMethod(m, objectOrClass)); + if ((em != null) && this.hasEventAnnotation(em)) l.add(em); + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return l; + } + + private boolean hasEventAnnotation(EventMethod m) { + for (Annotation a : m.annotations) { + if (a instanceof CraterEventListener) return true; + } + return false; + } + + public void registerListener(Consumer listener, Class eventType) { + this.registerListener(listener, eventType, 0); + } + + public void registerListener(Consumer listener, Class eventType, int priority) { + this.registerListener(new ListenerContainer(eventType, listener, priority)); + } + + private void registerListener(ListenerContainer listenerContainer) { + try { + if (!eventsRegisteredForType(listenerContainer.eventType)) { + events.put(listenerContainer.eventType, new ArrayList<>()); + } + events.get(listenerContainer.eventType).add(listenerContainer); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public boolean eventsRegisteredForType(Class eventType) { + if (eventType == null) { + return false; + } + return this.events.containsKey(eventType); + } + + protected final static class ListenerContainer { + + private final Consumer listener; + private final Class eventType; + private final int priority; + private String listenerParentClassName = "[unknown]"; + private String listenerMethodName = "[unknown]"; + + private ListenerContainer(Class eventType, Consumer listener, int priority) { + this.eventType = eventType; + this.listener = listener; + this.priority = priority; + } + + private void notifyListener(CraterEvent event) { + try { + this.listener.accept(event); + } catch (Exception e) { + LOGGER.error("##################################"); + LOGGER.error("Failed to notify event listener!"); + LOGGER.error("Event Type: " + this.eventType.getName()); + LOGGER.error("Listener Parent Class Name: " + this.listenerParentClassName); + LOGGER.error("Listener Method Name In Parent Class: " + this.listenerMethodName); + LOGGER.error("##################################"); + e.printStackTrace(); + } + } + } + + protected static class AnalyzedMethod { + + protected Method method; + protected Object parentObject; + protected Class parentClass; + protected boolean isStatic; + protected List annotations = new ArrayList<>(); + + protected AnalyzedMethod() { + } + + protected AnalyzedMethod(Method method, Object parentObjectOrClass) { + this.method = method; + this.parentObject = parentObjectOrClass; + this.parentClass = this.tryGetParentClass(); + this.isStatic = Modifier.isStatic(method.getModifiers()); + collectMethodAnnotations(this.isStatic ? null : this.parentObject.getClass(), this.method, this.annotations); + } + + protected static void collectMethodAnnotations(Class c, Method m, List addToList) { + try { + addToList.addAll(Arrays.asList(m.getAnnotations())); + if (!Modifier.isStatic(m.getModifiers()) && (c != null)) { + Class sc = c.getSuperclass(); + if (sc != null) { + try { + Method sm = sc.getMethod(m.getName(), m.getParameterTypes()); + collectMethodAnnotations(sc, sm, addToList); + } catch (Exception ignored) { + } + } + } + } catch (Exception ignored) { + } + } + + protected Class tryGetParentClass() { + if (this.parentObject instanceof Class) { + return (Class) this.parentObject; + } + return this.parentObject.getClass(); + } + + } + + protected static class EventMethod extends AnalyzedMethod { + + protected final int priority; + protected final Class eventType; + + protected EventMethod(AnalyzedMethod method) { + + super(); + this.method = method.method; + this.parentObject = method.parentObject; + this.parentClass = method.parentClass; + this.isStatic = method.isStatic; + this.annotations = method.annotations; + + this.priority = this.tryGetPriority(); + this.eventType = this.tryGetEventType(); + + } + + protected static EventMethod tryCreateFrom(AnalyzedMethod method) { + EventMethod em = new EventMethod(method); + return (em.eventType != null) ? em : null; + } + + protected Class tryGetEventType() { + try { + if (this.method != null) { + Class[] params = this.method.getParameterTypes(); + if (params.length > 0) { + Class firstParam = params[0]; + if (CraterEvent.class.isAssignableFrom(firstParam)) { + return (Class) firstParam; + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + protected int tryGetPriority() { + try { + for (Annotation a : this.annotations) { + if (a instanceof CraterEventListener craterEventListener) { + return craterEventListener.priority(); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return 0; + } + + } + +} \ No newline at end of file diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEventPriority.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEventPriority.java new file mode 100644 index 0000000..0c97134 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/event/CraterEventPriority.java @@ -0,0 +1,13 @@ +package com.hypherionmc.craterlib.core.event; + +public class CraterEventPriority { + + public static final int LOWEST = -3; + public static final int LOWER = -2; + public static final int LOW = -1; + public static final int NORMAL = 0; + public static final int HIGH = 1; + public static final int HIGHER = 2; + public static final int HIGHEST = 3; + +} \ No newline at end of file diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/event/annot/Cancellable.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/event/annot/Cancellable.java new file mode 100644 index 0000000..ee3c2fc --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/event/annot/Cancellable.java @@ -0,0 +1,8 @@ +package com.hypherionmc.craterlib.core.event.annot; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface Cancellable { +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/event/annot/CraterEventListener.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/event/annot/CraterEventListener.java new file mode 100644 index 0000000..7b42490 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/event/annot/CraterEventListener.java @@ -0,0 +1,11 @@ +package com.hypherionmc.craterlib.core.event.annot; + +import com.hypherionmc.craterlib.core.event.CraterEventPriority; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface CraterEventListener { + int priority() default CraterEventPriority.NORMAL; +} \ No newline at end of file diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/event/exception/CraterEventCancellationException.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/event/exception/CraterEventCancellationException.java new file mode 100644 index 0000000..fe2a8a3 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/event/exception/CraterEventCancellationException.java @@ -0,0 +1,9 @@ +package com.hypherionmc.craterlib.core.event.exception; + +public class CraterEventCancellationException extends Exception { + + public CraterEventCancellationException(String msg) { + super(msg); + } + +} \ No newline at end of file diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/event/package-info.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/event/package-info.java new file mode 100644 index 0000000..5ec1749 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/event/package-info.java @@ -0,0 +1,5 @@ +/** + * The event system code in this package is based on, and adapted from Acara (https://github.com/Keksuccino/acara/) + * and is licensed under MIT by Keksuccino + */ +package com.hypherionmc.craterlib.core.event; \ No newline at end of file diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/networking/CraterPacketNetwork.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/networking/CraterPacketNetwork.java new file mode 100644 index 0000000..081c8fb --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/networking/CraterPacketNetwork.java @@ -0,0 +1,43 @@ +package com.hypherionmc.craterlib.core.networking; + +import com.hypherionmc.craterlib.core.networking.data.PacketContext; +import com.hypherionmc.craterlib.nojang.network.BridgedFriendlyByteBuf; +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; +import lombok.Getter; + +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +@Getter +public class CraterPacketNetwork { + + private final PacketRegistry packetRegistry; + public static CraterPacketNetwork INSTANCE; + private static DeferredPacketRegistrar delayedHandler; + + public CraterPacketNetwork(PacketRegistry registry) { + INSTANCE = this; + this.packetRegistry = registry; + getDelayedHandler().registerQueuedPackets(registry); + } + + private static DeferredPacketRegistrar getDelayedHandler() { + if (delayedHandler == null) { + delayedHandler = new DeferredPacketRegistrar(); + } + return delayedHandler; + } + + public static PacketRegistrar registerPacket(ResourceIdentifier id, Class messageType, BiConsumer encoder, Function decoder, Consumer> handler) { + if (INSTANCE != null) { + return INSTANCE.packetRegistry.registerPacket(id, messageType, encoder, decoder, handler); + } else { + return getDelayedHandler().registerPacket(id, messageType, encoder, decoder, handler); + } + } + +} \ No newline at end of file diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/networking/DeferredPacketRegistrar.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/networking/DeferredPacketRegistrar.java new file mode 100644 index 0000000..25556f4 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/networking/DeferredPacketRegistrar.java @@ -0,0 +1,41 @@ +package com.hypherionmc.craterlib.core.networking; + +import com.hypherionmc.craterlib.core.networking.data.PacketContext; +import com.hypherionmc.craterlib.core.networking.data.PacketHolder; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.nojang.network.BridgedFriendlyByteBuf; +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public class DeferredPacketRegistrar implements PacketRegistrar { + + private static final Map, PacketHolder> QUEUED_PACKET_MAP = new HashMap<>(); + + @Override + public PacketSide side() { + return PacketSide.CLIENT; + } + + @Override + public PacketRegistrar registerPacket(ResourceIdentifier packetIdentifier, Class messageType, BiConsumer encoder, Function decoder, Consumer> handler) { + PacketHolder container = new PacketHolder<>(packetIdentifier, messageType, encoder, decoder, handler); + QUEUED_PACKET_MAP.put(messageType, container); + return this; + } + + + public void registerQueuedPackets(PacketRegistry packetRegistration) { + if (!QUEUED_PACKET_MAP.isEmpty()) { + packetRegistration.PACKET_MAP.putAll(QUEUED_PACKET_MAP); + QUEUED_PACKET_MAP.forEach((aClass, container) -> packetRegistration.registerPacket(container)); + } + } +} \ No newline at end of file diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/networking/PacketRegistrar.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/networking/PacketRegistrar.java new file mode 100644 index 0000000..3ea55fa --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/networking/PacketRegistrar.java @@ -0,0 +1,21 @@ +package com.hypherionmc.craterlib.core.networking; + +import com.hypherionmc.craterlib.core.networking.data.PacketContext; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.nojang.network.BridgedFriendlyByteBuf; +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; + +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public interface PacketRegistrar { + + PacketSide side(); + + PacketRegistrar registerPacket(ResourceIdentifier id, Class messageType, BiConsumer encoder, Function decoder, Consumer> handler); + +} \ No newline at end of file diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/networking/PacketRegistry.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/networking/PacketRegistry.java new file mode 100644 index 0000000..d1ec06c --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/networking/PacketRegistry.java @@ -0,0 +1,41 @@ +package com.hypherionmc.craterlib.core.networking; + +import com.hypherionmc.craterlib.api.networking.CraterNetworkHandler; +import com.hypherionmc.craterlib.core.networking.data.PacketContext; +import com.hypherionmc.craterlib.core.networking.data.PacketHolder; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.nojang.network.BridgedFriendlyByteBuf; +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public abstract class PacketRegistry implements CraterNetworkHandler, PacketRegistrar { + + protected final Map, PacketHolder> PACKET_MAP = new HashMap<>(); + + protected final PacketSide side; + + public PacketRegistry(PacketSide side) { + this.side = side; + } + + public PacketRegistrar registerPacket(ResourceIdentifier id, Class messageType, BiConsumer encoder, Function decoder, Consumer> handler) { + PacketHolder holder = new PacketHolder<>(id, messageType, encoder, decoder, handler); + PACKET_MAP.put(messageType, holder); + registerPacket(holder); + return this; + } + + public PacketSide side() { + return side; + } + + protected abstract void registerPacket(PacketHolder packetHolder); +} \ No newline at end of file diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketContext.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketContext.java new file mode 100644 index 0000000..fc432dc --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketContext.java @@ -0,0 +1,15 @@ +package com.hypherionmc.craterlib.core.networking.data; + +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import org.jetbrains.annotations.Nullable; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public record PacketContext(@Nullable BridgedPlayer sender, T message, PacketSide side) { + + public PacketContext(T message, PacketSide side) { + this(null, message, side); + } + +} \ No newline at end of file diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketHolder.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketHolder.java new file mode 100644 index 0000000..14b6947 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketHolder.java @@ -0,0 +1,18 @@ +package com.hypherionmc.craterlib.core.networking.data; + +import com.hypherionmc.craterlib.nojang.network.BridgedFriendlyByteBuf; +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; + +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public record PacketHolder(ResourceIdentifier type, + Class messageType, + BiConsumer encoder, + Function decoder, + Consumer> handler) { +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketSide.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketSide.java new file mode 100644 index 0000000..ad6f8ec --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/networking/data/PacketSide.java @@ -0,0 +1,13 @@ +package com.hypherionmc.craterlib.core.networking.data; + +public enum PacketSide { + CLIENT, + SERVER; + + public PacketSide flipped() { + if (CLIENT.equals(this)) + return SERVER; + + return CLIENT; + } +} \ No newline at end of file diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/platform/ClientPlatform.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/platform/ClientPlatform.java new file mode 100644 index 0000000..089fb36 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/platform/ClientPlatform.java @@ -0,0 +1,23 @@ +package com.hypherionmc.craterlib.core.platform; + +import com.hypherionmc.craterlib.nojang.client.BridgedMinecraft; +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedClientLevel; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.InternalServiceUtil; +import net.minecraft.network.Connection; + +/** + * @author HypherionSA + */ +public interface ClientPlatform { + + public final ClientPlatform INSTANCE = InternalServiceUtil.load(ClientPlatform.class); + + BridgedMinecraft getClientInstance(); + + BridgedPlayer getClientPlayer(); + + BridgedClientLevel getClientLevel(); + + Connection getClientConnection(); +} \ No newline at end of file diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/platform/CommonPlatform.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/platform/CommonPlatform.java new file mode 100644 index 0000000..ee7f944 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/platform/CommonPlatform.java @@ -0,0 +1,15 @@ +package com.hypherionmc.craterlib.core.platform; + +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import com.hypherionmc.craterlib.utils.InternalServiceUtil; + +/** + * @author HypherionSA + */ +public interface CommonPlatform { + + public CommonPlatform INSTANCE = InternalServiceUtil.load(CommonPlatform.class); + + BridgedMinecraftServer getMCServer(); + +} \ No newline at end of file diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/platform/CompatUtils.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/platform/CompatUtils.java new file mode 100644 index 0000000..fb745fc --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/platform/CompatUtils.java @@ -0,0 +1,13 @@ +package com.hypherionmc.craterlib.core.platform; + +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.InternalServiceUtil; + +public interface CompatUtils { + + public static final CompatUtils INSTANCE = InternalServiceUtil.load(CompatUtils.class); + + boolean isPlayerActive(BridgedPlayer player); + String getSkinUUID(BridgedPlayer player); + +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/platform/Environment.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/platform/Environment.java new file mode 100644 index 0000000..2ef219e --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/platform/Environment.java @@ -0,0 +1,18 @@ +package com.hypherionmc.craterlib.core.platform; + +/** + * @author HypherionSA + */ +public enum Environment { + CLIENT, + SERVER, + UNKNOWN; + + public boolean isClient() { + return this == CLIENT; + } + + public boolean isServer() { + return this == SERVER; + } +} \ No newline at end of file diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/platform/ModloaderEnvironment.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/platform/ModloaderEnvironment.java new file mode 100644 index 0000000..c1bc176 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/platform/ModloaderEnvironment.java @@ -0,0 +1,32 @@ +package com.hypherionmc.craterlib.core.platform; + +import com.hypherionmc.craterlib.utils.InternalServiceUtil; + +import java.io.File; + +/** + * @author HypherionSA + * Helper class to provide information about the ModLoader + */ +public interface ModloaderEnvironment { + + public final ModloaderEnvironment INSTANCE = InternalServiceUtil.load(ModloaderEnvironment.class); + + boolean isFabric(); + + String getGameVersion(); + + File getGameFolder(); + + File getConfigFolder(); + + File getModsFolder(); + + Environment getEnvironment(); + + boolean isModLoaded(String modid); + + boolean isDevEnv(); + + int getModCount(); +} \ No newline at end of file diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordEventHandlers.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordEventHandlers.java new file mode 100644 index 0000000..3f1ffcb --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordEventHandlers.java @@ -0,0 +1,90 @@ +package com.hypherionmc.craterlib.core.rpcsdk; + +import com.hypherionmc.craterlib.core.rpcsdk.callbacks.*; +import com.sun.jna.Structure; + +import java.util.Arrays; +import java.util.List; + +/** + * @author HypherionSA + * Class containing references to all available discord event handles. + * Registering a handler is optional, and non-assigned handlers will be ignored + */ +public class DiscordEventHandlers extends Structure { + + // Callback for when the RPC was initialized successfully + public ReadyCallback ready; + + // Callback for when the Discord connection was ended + public DisconnectedCallback disconnected; + + // Callback for when a Discord Error occurs + public ErroredCallback errored; + + // Callback for when a player joins the game + public JoinGameCallback joinGame; + + // Callback for when a player spectates the game + public SpectateGameCallback spectateGame; + + // Callback for when a players request to join your game + public JoinRequestCallback joinRequest; + + /** + * DO NOT TOUCH THIS... EVER! + */ + @Override + protected List getFieldOrder() { + return Arrays.asList( + "ready", + "disconnected", + "errored", + "joinGame", + "spectateGame", + "joinRequest" + ); + } + + public static class Builder { + private final DiscordEventHandlers handlers; + + public Builder() { + this.handlers = new DiscordEventHandlers(); + } + + public Builder ready(ReadyCallback readyCallback) { + handlers.ready = readyCallback; + return this; + } + + public Builder disconnected(DisconnectedCallback disconnectedCallback) { + handlers.disconnected = disconnectedCallback; + return this; + } + + public Builder errored(ErroredCallback erroredCallback) { + handlers.errored = erroredCallback; + return this; + } + + public Builder joinGame(JoinGameCallback joinGameCallback) { + handlers.joinGame = joinGameCallback; + return this; + } + + public Builder spectateGame(SpectateGameCallback spectateGameCallback) { + handlers.spectateGame = spectateGameCallback; + return this; + } + + public Builder joinRequest(JoinRequestCallback joinRequestCallback) { + handlers.joinRequest = joinRequestCallback; + return this; + } + + public DiscordEventHandlers build() { + return handlers; + } + } +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordRPC.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordRPC.java new file mode 100644 index 0000000..7cf53ff --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordRPC.java @@ -0,0 +1,99 @@ +package com.hypherionmc.craterlib.core.rpcsdk; + +import com.sun.jna.Library; +import com.sun.jna.Native; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author HypherionSA + * Java Wrapper of the Discord-RPC Library + */ +public interface DiscordRPC extends Library { + + DiscordRPC INSTANCE = Native.load("discord-rpc", DiscordRPC.class); + + /** + * Open a New RPC Connection + * + * @param applicationId The ID of the Application the RPC is tied to + * @param handlers Optional Event Callback Handlers + * @param autoRegister Auto Register the running game + * @param steamId Steam ID of the game + */ + void Discord_Initialize(@NotNull String applicationId, @Nullable DiscordEventHandlers handlers, boolean autoRegister, @Nullable String steamId); + + /** + * Shutdown the RPC instance and disconnect from discord + */ + void Discord_Shutdown(); + + /** + * Need to be called manually at least every 2 seconds, to allow RPC updates + * and callback handlers to fire + */ + void Discord_RunCallbacks(); + + /** + * Not sure about this. Believe it needs to be called manually in some circumstances + */ + void Discord_UpdateConnection(); + + /** + * Update the Rich Presence + * + * @param struct Constructed {@link DiscordRichPresence} + */ + void Discord_UpdatePresence(@Nullable DiscordRichPresence struct); + + /** + * Clear the current Rich Presence + */ + void Discord_ClearPresence(); + + /** + * Respond to Join/Spectate callback + * + * @param userid The Discord User ID of the user that initiated the request + * @param reply Reply to the request. See {@link DiscordReply} + */ + void Discord_Respond(@NotNull String userid, int reply); + + /** + * Replace the already registered {@link DiscordEventHandlers} + * + * @param handlers The new handlers to apply + */ + void Discord_UpdateHandlers(@Nullable DiscordEventHandlers handlers); + + /** + * Register the executable of the application/game + * Only applicable when autoRegister is set to false + * + * @param applicationId The Application ID + * @param command The Launch command of the game + *

+ * NB: THIS DOES NOT WORK WITH MINECRAFT + */ + void Discord_Register(String applicationId, String command); + + /** + * Register the Steam executable of the application/game + * + * @param applicationId The Application ID + * @param steamId The Steam ID of the application/game + */ + void Discord_RegisterSteamGame(String applicationId, String steamId); + + public enum DiscordReply { + NO(0), + YES(1), + IGNORE(2); + + public final int reply; + + DiscordReply(int reply) { + this.reply = reply; + } + } +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordRichPresence.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordRichPresence.java new file mode 100644 index 0000000..51fd720 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordRichPresence.java @@ -0,0 +1,248 @@ +package com.hypherionmc.craterlib.core.rpcsdk; + +import com.hypherionmc.craterlib.core.rpcsdk.helpers.RPCButton; +import com.sun.jna.Structure; +import org.jetbrains.annotations.NotNull; + +import java.time.OffsetDateTime; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * @author HypherionSA + * Class reprenting a Discord RPC activity + */ +public class DiscordRichPresence extends Structure { + + // First line of text on the RPC + public String state; + + // Second line of text on the RPC + public String details; + + // Time the activity started in UNIX-Timestamp format + public long startTimestamp; + + // Time the activity will end in UNIX-Timestamp format + public long endTimestamp; + + // URL or Asset key of the Large Image + public String largeImageKey; + + // Hover text to display when hovering the Large Image + public String largeImageText; + + // URL or Asset key of the Small Image + public String smallImageKey; + + // Hover text to display when hovering the Small Image + public String smallImageText; + + // Id of the player's party, lobby, or group. + public String partyId; + + // Current size of the player's party, lobby, or group. + public int partySize; + + // Maximum size of the player's party, lobby, or group. + public int partyMax; + + // Unused + public String partyPrivacy; + + // Unused. + public String matchSecret; + + // Unique hashed string for chat invitations and Ask to Join. + public String joinSecret; + + // Unique hashed string for Spectate button. + public String spectateSecret; + + // Label of the First RPC Button + public String button_label_1; + + // URL of the First RPC Button + public String button_url_1; + + // Label of the Second RPC Button + public String button_label_2; + + // URL of the Second RPC Button + public String button_url_2; + + // Unused + public int instance; + + public DiscordRichPresence() { + setStringEncoding("UTF-8"); + } + + /** + * DO NOT TOUCH THIS... EVER! + */ + @Override + protected List getFieldOrder() { + return Arrays.asList( + "state", + "details", + "startTimestamp", + "endTimestamp", + "largeImageKey", + "largeImageText", + "smallImageKey", + "smallImageText", + "partyId", + "partySize", + "partyMax", + "partyPrivacy", + "matchSecret", + "joinSecret", + "spectateSecret", + "button_label_1", + "button_url_1", + "button_label_2", + "button_url_2", + "instance" + ); + } + + public static class Builder { + private final DiscordRichPresence rpc; + + public Builder(String state) { + rpc = new DiscordRichPresence(); + + if (state != null && !state.isEmpty()) { + rpc.state = state.substring(0, Math.min(state.length(), 128)); + } + } + + public Builder setDetails(String details) { + if (details != null && !details.isEmpty()) { + rpc.details = details.substring(0, Math.min(details.length(), 128)); + } + return this; + } + + public Builder setStartTimestamp(long timestamp) { + rpc.startTimestamp = timestamp; + return this; + } + + public Builder setStartTimestamp(OffsetDateTime timestamp) { + rpc.startTimestamp = timestamp.toEpochSecond(); + return this; + } + + public Builder setEndTimestamp(long timestamp) { + rpc.endTimestamp = timestamp; + return this; + } + + public Builder setEndTimestamp(OffsetDateTime timestamp) { + rpc.endTimestamp = timestamp.toEpochSecond(); + return this; + } + + public Builder setLargeImage(String key) { + return this.setLargeImage(key, ""); + } + + public Builder setLargeImage(@NotNull String key, String text) { + // Null check used for users blatantly ignoring the NotNull marker + if ((text != null && !text.isEmpty()) && key != null) { + throw new IllegalArgumentException("Image key cannot be null when assigning a hover text"); + } + + rpc.largeImageKey = key; + rpc.largeImageText = text; + return this; + } + + public Builder setSmallImage(String key) { + return this.setSmallImage(key, ""); + } + + public Builder setSmallImage(@NotNull String key, String text) { + // Null check used for users blatantly ignoring the NotNull marker + if ((text != null && !text.isEmpty()) && key != null) { + throw new IllegalArgumentException("Image key cannot be null when assigning a hover text"); + } + + rpc.smallImageKey = key; + rpc.smallImageText = text; + return this; + } + + public Builder setParty(String party, int size, int max) { + // Buttons are present, ignore + if ((rpc.button_label_1 != null && rpc.button_label_1.isEmpty()) || (rpc.button_label_2 != null && rpc.button_label_2.isEmpty())) + return this; + + rpc.partyId = party; + rpc.partySize = size; + rpc.partyMax = max; + return this; + } + + public Builder setSecrets(String match, String join, String spectate) { + // Buttons are present, ignore + if ((rpc.button_label_1 != null && rpc.button_label_1.isEmpty()) || (rpc.button_label_2 != null && rpc.button_label_2.isEmpty())) + return this; + + rpc.matchSecret = match; + rpc.joinSecret = join; + rpc.spectateSecret = spectate; + return this; + } + + public Builder setSecrets(String join, String spectate) { + // Buttons are present, ignore + if ((rpc.button_label_1 != null && rpc.button_label_1.isEmpty()) || (rpc.button_label_2 != null && rpc.button_label_2.isEmpty())) + return this; + + rpc.joinSecret = join; + rpc.spectateSecret = spectate; + return this; + } + + public Builder setInstance(boolean i) { + // Buttons are present, ignore + if ((rpc.button_label_1 != null && rpc.button_label_1.isEmpty()) || (rpc.button_label_2 != null && rpc.button_label_2.isEmpty())) + return this; + + rpc.instance = i ? 1 : 0; + return this; + } + + public Builder setButtons(RPCButton button) { + return this.setButtons(Collections.singletonList(button)); + } + + public Builder setButtons(RPCButton button1, RPCButton button2) { + return this.setButtons(Arrays.asList(button1, button2)); + } + + public Builder setButtons(List rpcButtons) { + // Limit to 2 Buttons. Discord Limitation + if (rpcButtons != null && !rpcButtons.isEmpty()) { + int length = Math.min(rpcButtons.size(), 2); + rpc.button_label_1 = rpcButtons.get(0).getLabel(); + rpc.button_url_1 = rpcButtons.get(0).getUrl(); + + if (length == 2) { + rpc.button_label_2 = rpcButtons.get(1).getLabel(); + rpc.button_url_2 = rpcButtons.get(1).getUrl(); + } + } + + return this; + } + + public DiscordRichPresence build() { + return rpc; + } + } +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordUser.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordUser.java new file mode 100644 index 0000000..e8bc085 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/DiscordUser.java @@ -0,0 +1,39 @@ +package com.hypherionmc.craterlib.core.rpcsdk; + +import com.sun.jna.Structure; + +import java.util.Arrays; +import java.util.List; + +/** + * @author HypherionSA + * Class representing the Discord User + */ +public class DiscordUser extends Structure { + + // The User ID of the User + public String userId; + + // The Username of the User + public String username; + + // The unique identifier of the user. Discontinued by Discord + @Deprecated + public String discriminator; + + // The avatar has of the user + public String avatar; + + /** + * DO NOT TOUCH THIS... EVER! + */ + @Override + protected List getFieldOrder() { + return Arrays.asList( + "userId", + "username", + "discriminator", + "avatar" + ); + } +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/DisconnectedCallback.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/DisconnectedCallback.java new file mode 100644 index 0000000..7ea59e1 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/DisconnectedCallback.java @@ -0,0 +1,18 @@ +package com.hypherionmc.craterlib.core.rpcsdk.callbacks; + +import com.sun.jna.Callback; + +/** + * @author HypherionSA + * Callback for when the Discord RPC disconnects + */ +public interface DisconnectedCallback extends Callback { + + /** + * Called when RPC disconnected + * + * @param errorCode Error code if any + * @param message Details about the disconnection + */ + void apply(int errorCode, String message); +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/ErroredCallback.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/ErroredCallback.java new file mode 100644 index 0000000..1f86c90 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/ErroredCallback.java @@ -0,0 +1,18 @@ +package com.hypherionmc.craterlib.core.rpcsdk.callbacks; + +import com.sun.jna.Callback; + +/** + * @author HypherionSA + * Callback for when the RPC ran into an error + */ +public interface ErroredCallback extends Callback { + + /** + * Called when an RPC error occurs + * + * @param errorCode Error code if any + * @param message Details about the error + */ + void apply(int errorCode, String message); +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/JoinGameCallback.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/JoinGameCallback.java new file mode 100644 index 0000000..cc752af --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/JoinGameCallback.java @@ -0,0 +1,17 @@ +package com.hypherionmc.craterlib.core.rpcsdk.callbacks; + +import com.sun.jna.Callback; + +/** + * @author HypherionSA + * Callback for when someone was approved to join your game + */ +public interface JoinGameCallback extends Callback { + + /** + * Called when someone joins a game from {@link JoinRequestCallback} + * + * @param joinSecret Secret or Password required to let the player join the game + */ + void apply(String joinSecret); +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/JoinRequestCallback.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/JoinRequestCallback.java new file mode 100644 index 0000000..115fd4f --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/JoinRequestCallback.java @@ -0,0 +1,19 @@ +package com.hypherionmc.craterlib.core.rpcsdk.callbacks; + +import com.hypherionmc.craterlib.core.rpcsdk.DiscordUser; +import com.sun.jna.Callback; + +/** + * @author HypherionSA + * Callback for when someone requests to join your game + */ +public interface JoinRequestCallback extends Callback { + + /** + * Called when someone clicks on the Join Game button + * + * @param user The Discord User trying to join your game + * @see DiscordUser + */ + void apply(DiscordUser user); +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/ReadyCallback.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/ReadyCallback.java new file mode 100644 index 0000000..66f3b59 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/ReadyCallback.java @@ -0,0 +1,19 @@ +package com.hypherionmc.craterlib.core.rpcsdk.callbacks; + +import com.hypherionmc.craterlib.core.rpcsdk.DiscordUser; +import com.sun.jna.Callback; + +/** + * @author HypherionSA + * Callback for when the RPC has connected successfully + */ +public interface ReadyCallback extends Callback { + + /** + * Called when the RPC is connected and ready to be used + * + * @param user The user the RPC is displayed on + * @see DiscordUser + */ + void apply(DiscordUser user); +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/SpectateGameCallback.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/SpectateGameCallback.java new file mode 100644 index 0000000..979e53d --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/callbacks/SpectateGameCallback.java @@ -0,0 +1,17 @@ +package com.hypherionmc.craterlib.core.rpcsdk.callbacks; + +import com.sun.jna.Callback; + +/** + * @author HypherionSA + * Callback for when someone is requesting to spectate your game + */ +public interface SpectateGameCallback extends Callback { + + /** + * Called when joining the game + * + * @param spectateSecret Secret or Password required to let the player spectate + */ + void apply(String spectateSecret); +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/helpers/RPCButton.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/helpers/RPCButton.java new file mode 100644 index 0000000..ab2bdc8 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/core/rpcsdk/helpers/RPCButton.java @@ -0,0 +1,55 @@ +package com.hypherionmc.craterlib.core.rpcsdk.helpers; + +import org.jetbrains.annotations.NotNull; + +import java.io.Serializable; + +/** + * @author HypherionSA + * Helper class to add Buttons to Discord Rich Presence + * This can not be used with Join/Spectate + */ +public class RPCButton implements Serializable { + + // The label of the button + private final String label; + + // The URL the button will open when clicked + private final String url; + + protected RPCButton(String label, String url) { + this.label = label; + this.url = url; + } + + /** + * Create a new RPC Button + * + * @param label The label of the button + * @param url The URL the button will open when clicked + * @return The constructed button + */ + public static RPCButton create(@NotNull String label, @NotNull String url) { + // Null check used here for users blatantly ignoring the NotNull marker + if (label == null || label.isEmpty() || url == null || url.isEmpty()) { + throw new IllegalArgumentException("RPC Buttons require both a label and url"); + } + + label = label.substring(0, Math.min(label.length(), 31)); + return new RPCButton(label, url); + } + + /** + * @return The label assigned to the button + */ + public String getLabel() { + return label; + } + + /** + * @return The URL of the button + */ + public String getUrl() { + return url; + } +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/mixin/ChatInputSuggestorMixin.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/mixin/ChatInputSuggestorMixin.java new file mode 100644 index 0000000..c48dfbc --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/mixin/ChatInputSuggestorMixin.java @@ -0,0 +1,84 @@ +package com.hypherionmc.craterlib.mixin; + +import com.hypherionmc.craterlib.client.mentions.MentionsController; +import net.minecraft.client.gui.components.CommandSuggestions; +import net.minecraft.client.gui.components.EditBox; +import org.objectweb.asm.Opcodes; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.ModifyVariable; +import org.spongepowered.asm.mixin.injection.Slice; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * @author HypherionSA + * Allow Users, Roles and Channels to be pingable from MC chat (Client Side) + */ +@Mixin(CommandSuggestions.class) +public abstract class ChatInputSuggestorMixin { + + @Shadow + public abstract void showSuggestions(boolean p_93931_); + + @Shadow @Final + EditBox input; + + @Shadow + private static int getLastWordIndex(String p_93913_) { + return 0; + } + + @Inject( + method = "updateCommandInfo", + at = @At( + value = "FIELD", + target = "Lnet/minecraft/client/gui/components/CommandSuggestions;pendingSuggestions:Ljava/util/concurrent/CompletableFuture;", + opcode = Opcodes.PUTFIELD, + shift = At.Shift.AFTER, + ordinal = 0 + ), + slice = @Slice( + from = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/gui/components/CommandSuggestions;getLastWordIndex(Ljava/lang/String;)I" + ) + ) + ) + private void injectSuggestions(CallbackInfo ci) { + if (MentionsController.hasMentions() && MentionsController.isLastMentionConditional()) { + this.showSuggestions(true); + } + } + + @SuppressWarnings("InvalidInjectorMethodSignature") + @ModifyVariable(method = "updateCommandInfo", at = @At(value = "STORE"), ordinal = 0, name = "collection") + private Collection injectMentions(Collection vanilla) { + if (!MentionsController.hasMentions()) + return vanilla; + + ArrayList newSuggest = new ArrayList<>(vanilla); + + String currentInput = this.input.getValue(); + int currentCursorPosition = this.input.getCursorPosition(); + + String textBeforeCursor = currentInput.substring(0, currentCursorPosition); + int startOfCurrentWord = getLastWordIndex(textBeforeCursor); + + String currentWord = textBeforeCursor.substring(startOfCurrentWord); + String finalWord = currentWord.replace("[", "").replace("]", ""); + + Collection mentions = MentionsController.getMentions(finalWord); + + if (!mentions.isEmpty()) { + mentions.forEach(m -> newSuggest.add("[" + m + "]")); + } + + return newSuggest; + } +} \ No newline at end of file diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/CommandMixin.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/CommandMixin.java new file mode 100644 index 0000000..aeb2e22 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/CommandMixin.java @@ -0,0 +1,37 @@ +package com.hypherionmc.craterlib.mixin.events; + +import com.google.common.base.Throwables; +import com.hypherionmc.craterlib.api.events.server.CraterCommandEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.mojang.brigadier.ParseResults; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +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.CallbackInfoReturnable; + +@Mixin(Commands.class) +public class CommandMixin { + + @Inject(method = "performCommand", + at = @At(value = "INVOKE", + target = "Lcom/mojang/brigadier/CommandDispatcher;execute(Lcom/mojang/brigadier/ParseResults;)I", + shift = At.Shift.BEFORE + ), cancellable = true + ) + private void injectCommandEvent(ParseResults stackParseResults, String command, CallbackInfoReturnable cir) { + CraterCommandEvent commandEvent = CraterCommandEvent.of(stackParseResults, command); + CraterEventBus.INSTANCE.postEvent(commandEvent); + if (commandEvent.wasCancelled()) { + cir.setReturnValue(1); + return; + } + + if (commandEvent.getException() != null) { + Throwables.throwIfUnchecked(commandEvent.getException()); + cir.setReturnValue(1); + } + } + +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerAdvancementsMixin.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerAdvancementsMixin.java new file mode 100644 index 0000000..2457176 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerAdvancementsMixin.java @@ -0,0 +1,29 @@ +package com.hypherionmc.craterlib.mixin.events; + +import com.hypherionmc.craterlib.api.events.server.CraterAdvancementEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.advancements.BridgedAdvancement; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraft.advancements.Advancement; +import net.minecraft.server.PlayerAdvancements; +import net.minecraft.server.level.ServerPlayer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(PlayerAdvancements.class) +public class PlayerAdvancementsMixin { + + @Shadow + private ServerPlayer player; + + @Inject(method = "award", at = @At(value = "INVOKE", target = "Lnet/minecraft/advancements/AdvancementRewards;grant(Lnet/minecraft/server/level/ServerPlayer;)V", shift = At.Shift.AFTER)) + private void injectAdvancementEvent(Advancement advancement, String string, CallbackInfoReturnable cir) { + if (advancement.getDisplay() == null || !advancement.getDisplay().shouldAnnounceChat()) + return; + + CraterEventBus.INSTANCE.postEvent(new CraterAdvancementEvent(BridgedPlayer.of(this.player), BridgedAdvancement.of(advancement))); + } +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerListMixin.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerListMixin.java new file mode 100644 index 0000000..0aaa866 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerListMixin.java @@ -0,0 +1,52 @@ +package com.hypherionmc.craterlib.mixin.events; + +import com.hypherionmc.craterlib.api.events.server.CraterPlayerEvent; +import com.hypherionmc.craterlib.api.events.server.MessageBroadcastEvent; +import com.hypherionmc.craterlib.api.events.server.PlayerPreLoginEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.authlib.BridgedGameProfile; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.ChatUtils; +import com.mojang.authlib.GameProfile; +import net.minecraft.network.Connection; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.players.PlayerList; +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; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.net.SocketAddress; +import java.util.function.Function; + +@Mixin(PlayerList.class) +public class PlayerListMixin { + + @Inject(method = "broadcastSystemMessage(Lnet/minecraft/network/chat/Component;Ljava/util/function/Function;Z)V", at = @At("HEAD")) + private void injectBroadcastEvent(Component component, Function function, boolean bl, CallbackInfo ci) { + String thread = Thread.currentThread().getStackTrace()[3].getClassName(); + MessageBroadcastEvent event = new MessageBroadcastEvent(ChatUtils.mojangToAdventure(component), (f) -> ChatUtils.mojangToAdventure(component), bl, thread); + CraterEventBus.INSTANCE.postEvent(event); + } + + @Inject(method = "placeNewPlayer", at = @At("TAIL")) + private void injectPlayerLoginEvent(Connection arg, ServerPlayer serverPlayer, CallbackInfo ci) { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerEvent.PlayerLoggedIn(BridgedPlayer.of(serverPlayer))); + } + + @Inject(method = "remove", at = @At("HEAD")) + private void injectPlayerLogoutEvent(ServerPlayer player, CallbackInfo ci) { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerEvent.PlayerLoggedOut(BridgedPlayer.of(player))); + } + + @Inject(method = "canPlayerLogin", at = @At("HEAD"), cancellable = true) + private void injectPreLoginEvent(SocketAddress address, GameProfile gameProfile, CallbackInfoReturnable cir) { + PlayerPreLoginEvent event = new PlayerPreLoginEvent(address, BridgedGameProfile.of(gameProfile)); + CraterEventBus.INSTANCE.postEvent(event); + if (event.getMessage() != null) { + cir.setReturnValue(ChatUtils.adventureToMojang(event.getMessage())); + } + } +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerMixin.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerMixin.java new file mode 100644 index 0000000..5fd03bd --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerMixin.java @@ -0,0 +1,21 @@ +package com.hypherionmc.craterlib.mixin.events; + +import com.hypherionmc.craterlib.api.events.common.CraterPlayerDeathEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.player.Player; +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(Player.class) +public class PlayerMixin { + + @Inject(method = "die", at = @At("HEAD")) + private void injectPlayerDeathEvent(DamageSource damageSource, CallbackInfo ci) { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerDeathEvent(BridgedPlayer.of(((Player) (Object) this)), damageSource)); + } + +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/ServerPlayerMixin.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/ServerPlayerMixin.java new file mode 100644 index 0000000..ae028e7 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/ServerPlayerMixin.java @@ -0,0 +1,21 @@ +package com.hypherionmc.craterlib.mixin.events; + +import com.hypherionmc.craterlib.api.events.common.CraterPlayerDeathEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.damagesource.DamageSource; +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(ServerPlayer.class) +public class ServerPlayerMixin { + + @Inject(method = "die", at = @At("HEAD")) + private void injectPlayerDeathEvent(DamageSource damageSource, CallbackInfo ci) { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerDeathEvent(BridgedPlayer.of(((ServerPlayer) (Object) this)), damageSource)); + } + +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/ClientLevelMixin.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/ClientLevelMixin.java new file mode 100644 index 0000000..3535d6f --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/ClientLevelMixin.java @@ -0,0 +1,25 @@ +package com.hypherionmc.craterlib.mixin.events.client; + +import com.hypherionmc.craterlib.api.events.client.CraterSinglePlayerEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; +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(ClientLevel.class) +public class ClientLevelMixin { + + @Inject(method = "addEntity", at = @At("HEAD")) + private void injectSinglePlayerJoinEvent(int i, Entity entity, CallbackInfo ci) { + if (entity instanceof Player player) { + CraterSinglePlayerEvent.PlayerLogin playerLogin = new CraterSinglePlayerEvent.PlayerLogin(BridgedPlayer.of(player)); + CraterEventBus.INSTANCE.postEvent(playerLogin); + } + } + +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/MinecraftMixin.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/MinecraftMixin.java new file mode 100644 index 0000000..2903131 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/MinecraftMixin.java @@ -0,0 +1,31 @@ +package com.hypherionmc.craterlib.mixin.events.client; + +import com.hypherionmc.craterlib.api.events.client.ScreenEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.client.gui.BridgedScreen; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.Screen; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(Minecraft.class) +public class MinecraftMixin { + + @Shadow + @Nullable + public Screen screen; + + @Inject(method = "setScreen", at = @At(value = "TAIL")) + private void injectScreenOpeningEvent(Screen screen, CallbackInfo ci) { + Screen old = this.screen; + if (screen != null) { + ScreenEvent.Opening opening = new ScreenEvent.Opening(BridgedScreen.of(old), BridgedScreen.of(screen)); + CraterEventBus.INSTANCE.postEvent(opening); + } + } + +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/RealmsMainScreenMixin.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/RealmsMainScreenMixin.java new file mode 100644 index 0000000..d2445c3 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/client/RealmsMainScreenMixin.java @@ -0,0 +1,23 @@ +package com.hypherionmc.craterlib.mixin.events.client; + +import com.hypherionmc.craterlib.api.events.client.PlayerJoinRealmEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.realmsclient.dto.BridgedRealmsServer; +import com.mojang.realmsclient.RealmsMainScreen; +import com.mojang.realmsclient.dto.RealmsServer; +import net.minecraft.client.gui.screens.Screen; +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 RealmsMainScreenMixin { + + @Inject(at = @At("HEAD"), method = "play") + private void play(RealmsServer serverData, Screen arg2, CallbackInfo ci) { + PlayerJoinRealmEvent playerJoinRealm = new PlayerJoinRealmEvent(BridgedRealmsServer.of(serverData)); + CraterEventBus.INSTANCE.postEvent(playerJoinRealm); + } + +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/advancements/BridgedAdvancement.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/advancements/BridgedAdvancement.java new file mode 100644 index 0000000..6b8e376 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/advancements/BridgedAdvancement.java @@ -0,0 +1,21 @@ +package com.hypherionmc.craterlib.nojang.advancements; + +import lombok.RequiredArgsConstructor; +import net.minecraft.advancements.Advancement; + +import java.util.Optional; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedAdvancement { + + private final Advancement internal; + + public Optional displayInfo() { + if (internal.getDisplay() != null) { + return Optional.of(BridgedDisplayInfo.of(internal.getDisplay())); + } + + return Optional.empty(); + } + +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/advancements/BridgedDisplayInfo.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/advancements/BridgedDisplayInfo.java new file mode 100644 index 0000000..5a0c155 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/advancements/BridgedDisplayInfo.java @@ -0,0 +1,29 @@ +package com.hypherionmc.craterlib.nojang.advancements; + +import com.hypherionmc.craterlib.utils.ChatUtils; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; +import net.minecraft.advancements.DisplayInfo; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedDisplayInfo { + + private final DisplayInfo internal; + + public boolean shouldDisplay() { + return internal.shouldAnnounceChat(); + } + + public boolean isHidden() { + return internal.isHidden(); + } + + public Component displayName() { + return ChatUtils.mojangToAdventure(internal.getTitle()); + } + + public Component description() { + return ChatUtils.mojangToAdventure(internal.getDescription()); + } + +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/authlib/BridgedGameProfile.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/authlib/BridgedGameProfile.java new file mode 100644 index 0000000..c83f3c4 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/authlib/BridgedGameProfile.java @@ -0,0 +1,29 @@ +package com.hypherionmc.craterlib.nojang.authlib; + +import com.mojang.authlib.GameProfile; +import lombok.RequiredArgsConstructor; + +import java.util.UUID; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedGameProfile { + + private final GameProfile internal; + + public static BridgedGameProfile mojang(UUID id, String name) { + return new BridgedGameProfile(new GameProfile(id, name)); + } + + public String getName() { + return internal.getName(); + } + + public UUID getId() { + return internal.getId(); + } + + public GameProfile toMojang() { + return internal; + } + +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/BridgedMinecraft.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/BridgedMinecraft.java new file mode 100644 index 0000000..2d6242b --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/BridgedMinecraft.java @@ -0,0 +1,84 @@ +package com.hypherionmc.craterlib.nojang.client; + +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedClientLevel; +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedServerData; +import com.hypherionmc.craterlib.nojang.client.server.BridgedIntegratedServer; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import lombok.Getter; +import net.minecraft.SharedConstants; +import net.minecraft.client.Minecraft; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.util.UUID; + +public class BridgedMinecraft { + + @Getter + private static final BridgedMinecraft instance = new BridgedMinecraft(); + private final Minecraft internal = Minecraft.getInstance(); + + public File getGameDirectory() { + return internal.gameDirectory; + } + + public BridgedOptions getOptions() { + return BridgedOptions.of(internal.options); + } + + @Nullable + public BridgedClientLevel getLevel() { + if (internal.level == null) + return null; + + return BridgedClientLevel.of(internal.level); + } + + public boolean isRealmServer() { + return internal.getCurrentServer() != null && internal.isConnectedToRealms(); + } + + public boolean isSinglePlayer() { + return internal.hasSingleplayerServer(); + } + + @Nullable + public BridgedPlayer getPlayer() { + if (internal.player == null) + return null; + + return BridgedPlayer.of(internal.player); + } + + public String getGameVersion() { + return SharedConstants.getCurrentVersion().getName(); + } + + public String getUserName() { + return internal.getUser().getName(); + } + + public UUID getPlayerId() { + return internal.getUser().getProfileId(); + } + + @Nullable + public BridgedServerData getCurrentServer() { + if (internal.getCurrentServer() == null) + return null; + + return BridgedServerData.of(internal.getCurrentServer()); + } + + @Nullable + public BridgedIntegratedServer getSinglePlayerServer() { + return BridgedIntegratedServer.of(internal.getSingleplayerServer()); + } + + public int getServerPlayerCount () { + if (internal.getConnection() == null) + return 0; + + return internal.getConnection().getOnlinePlayers().size(); + } +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/BridgedOptions.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/BridgedOptions.java new file mode 100644 index 0000000..7063feb --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/BridgedOptions.java @@ -0,0 +1,15 @@ +package com.hypherionmc.craterlib.nojang.client; + +import lombok.RequiredArgsConstructor; +import net.minecraft.client.Options; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedOptions { + + private final Options internal; + + public String getLanguage() { + return internal.languageCode; + } + +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/gui/BridgedScreen.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/gui/BridgedScreen.java new file mode 100644 index 0000000..c4bcc23 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/gui/BridgedScreen.java @@ -0,0 +1,36 @@ +package com.hypherionmc.craterlib.nojang.client.gui; + +import lombok.RequiredArgsConstructor; +import net.minecraft.client.gui.screens.LevelLoadingScreen; +import net.minecraft.client.gui.screens.ReceivingLevelScreen; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.gui.screens.TitleScreen; +import net.minecraft.client.gui.screens.multiplayer.JoinMultiplayerScreen; +import net.minecraft.realms.RealmsScreen; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedScreen { + + private final Screen internal; + + public boolean isTitleScreen() { + return internal instanceof TitleScreen; + } + + public boolean isRealmsScreen() { + return internal instanceof RealmsScreen; + } + + public boolean isServerBrowserScreen() { + return internal instanceof JoinMultiplayerScreen; + } + + public boolean isLoadingScreen() { + return internal instanceof LevelLoadingScreen || internal instanceof ReceivingLevelScreen; + } + + public Screen toMojang() { + return internal; + } + +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/multiplayer/BridgedClientLevel.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/multiplayer/BridgedClientLevel.java new file mode 100644 index 0000000..4e7b60b --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/multiplayer/BridgedClientLevel.java @@ -0,0 +1,59 @@ +package com.hypherionmc.craterlib.nojang.client.multiplayer; + +import com.hypherionmc.craterlib.nojang.core.BridgedBlockPos; +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; +import com.hypherionmc.craterlib.utils.ChatUtils; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; +import net.minecraft.client.multiplayer.ClientLevel; +import org.jetbrains.annotations.Nullable; + +import java.util.concurrent.atomic.AtomicReference; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedClientLevel { + + private final ClientLevel internal; + + public boolean isClientSide() { + return internal.isClientSide(); + } + + public long getGameTime() { + return internal.getGameTime(); + } + + public long getDayTime() { + return internal.getDayTime(); + } + + public long dayTime() { + return internal.dayTime(); + } + + public boolean isRaining() { + return internal.isRaining(); + } + + public boolean isThundering() { + return internal.isThundering(); + } + + @Nullable + public ResourceIdentifier getDimensionKey() { + return ResourceIdentifier.fromMojang(internal.dimension().location()); + } + + @Nullable + public ResourceIdentifier getBiomeIdentifier(BridgedBlockPos onPos) { + AtomicReference identifier = new AtomicReference<>(null); + internal.getBiome(onPos.toMojang()).unwrap().ifLeft(b -> identifier.set(ResourceIdentifier.fromMojang(b.location()))); + return identifier.get(); + } + + @Nullable + public Component getDifficulty() { + return ChatUtils.mojangToAdventure(internal.getDifficulty().getDisplayName()); + } + +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/multiplayer/BridgedServerData.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/multiplayer/BridgedServerData.java new file mode 100644 index 0000000..c594872 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/multiplayer/BridgedServerData.java @@ -0,0 +1,40 @@ +package com.hypherionmc.craterlib.nojang.client.multiplayer; + +import com.hypherionmc.craterlib.utils.ChatUtils; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; +import net.minecraft.client.multiplayer.ServerData; +import net.minecraft.client.multiplayer.ServerStatusPinger; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedServerData { + + private final ServerData internal; + + public String name() { + return internal.name; + } + + public String ip() { + return internal.ip; + } + + public Component motd() { + return ChatUtils.mojangToAdventure(internal.motd); + } + + public int getMaxPlayers() { + if (!internal.pinged || internal.players == null) { + try { + new ServerStatusPinger().pingServer(internal, () -> {}); + } catch (Exception ignored) {} + } + + return internal.players == null ? 0 : internal.players.max(); + } + + public ServerData toMojang() { + return internal; + } + +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/server/BridgedIntegratedServer.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/server/BridgedIntegratedServer.java new file mode 100644 index 0000000..0ecacaf --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/client/server/BridgedIntegratedServer.java @@ -0,0 +1,19 @@ +package com.hypherionmc.craterlib.nojang.client.server; + +import lombok.RequiredArgsConstructor; +import net.minecraft.client.server.IntegratedServer; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedIntegratedServer { + + private final IntegratedServer internal; + + public String getLevelName() { + return internal.getWorldData().getLevelName(); + } + + public IntegratedServer toMojang() { + return internal; + } + +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/BridgedCommandSourceStack.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/BridgedCommandSourceStack.java new file mode 100644 index 0000000..5a15402 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/BridgedCommandSourceStack.java @@ -0,0 +1,22 @@ +package com.hypherionmc.craterlib.nojang.commands; + +import com.hypherionmc.craterlib.utils.ChatUtils; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; +import net.minecraft.commands.CommandSourceStack; + +import java.util.function.Supplier; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedCommandSourceStack { + + private final CommandSourceStack internal; + + public void sendSuccess(Supplier supplier, boolean bl) { + internal.sendSuccess(() -> ChatUtils.adventureToMojang(supplier.get()), bl); + } + + public CommandSourceStack toMojang() { + return internal; + } +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/BridgedFakePlayer.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/BridgedFakePlayer.java new file mode 100644 index 0000000..42b65cb --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/BridgedFakePlayer.java @@ -0,0 +1,55 @@ +package com.hypherionmc.craterlib.nojang.commands; + +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import com.hypherionmc.craterlib.utils.ChatUtils; +import net.minecraft.commands.CommandSource; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.network.chat.Component; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.phys.Vec2; +import net.minecraft.world.phys.Vec3; + +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Supplier; + +public abstract class BridgedFakePlayer { + + final MojangBridge internal; + + public BridgedFakePlayer(BridgedMinecraftServer server, int perm, String name) { + internal = new MojangBridge(server.toMojang(), perm, name, this::onSuccess, this::onError); + } + + public abstract void onSuccess(Supplier supplier, Boolean aBoolean); + + public void onError(net.kyori.adventure.text.Component component) { + this.onSuccess(() -> component, false); + } + + public CommandSourceStack toMojang() { + return internal; + } + + static class MojangBridge extends CommandSourceStack { + + private final BiConsumer, Boolean> successCallback; + public final Consumer errorCallback; + + MojangBridge(MinecraftServer server, int perm, String name, BiConsumer, Boolean> successCallback, Consumer errorCallback) { + super(CommandSource.NULL, Vec3.ZERO, Vec2.ZERO, server.overworld(), perm, name, Component.literal(name), server, null); + this.successCallback = successCallback; + this.errorCallback = errorCallback; + } + + @Override + public void sendSuccess(Supplier supplier, boolean bl) { + successCallback.accept(() -> ChatUtils.mojangToAdventure(supplier.get()), bl); + } + + @Override + public void sendFailure(Component arg) { + errorCallback.accept(ChatUtils.mojangToAdventure(arg)); + } + } +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/CommandsRegistry.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/CommandsRegistry.java new file mode 100644 index 0000000..37ad15b --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/commands/CommandsRegistry.java @@ -0,0 +1,85 @@ +package com.hypherionmc.craterlib.nojang.commands; + +import com.hypherionmc.craterlib.api.commands.CraterCommand; +import com.hypherionmc.craterlib.nojang.authlib.BridgedGameProfile; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.TriConsumer; +import com.mojang.authlib.GameProfile; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.commands.arguments.GameProfileArgument; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class CommandsRegistry { + + public static final CommandsRegistry INSTANCE = new CommandsRegistry(); + + private final List commands = new ArrayList<>(); + + public void registerCommand(CraterCommand cmd) { + commands.add(cmd); + } + + public void registerCommands(CommandDispatcher stack) { + commands.forEach(cmd -> { + if (cmd.hasArguments()) { + CommandWithArguments.register(cmd, stack); + } else { + CommandWithoutArguments.register(cmd, stack); + } + }); + } + + static class CommandWithoutArguments { + + public static void register(CraterCommand cmd, CommandDispatcher dispatcher) { + LiteralArgumentBuilder command = Commands.literal(cmd.getCommandName()) + .requires(source -> source.hasPermission(cmd.getPermissionLevel())) + .executes(context -> { + cmd.getExecutor().accept(BridgedCommandSourceStack.of(context.getSource())); + return 1; + }); + + dispatcher.register(command); + } + + } + + @SuppressWarnings("unchecked") + static class CommandWithArguments { + + public static void register(CraterCommand cmd, CommandDispatcher dispatcher) { + LiteralArgumentBuilder command = Commands.literal(cmd.getCommandName()) + .requires(source -> source.hasPermission(cmd.getPermissionLevel())); + + cmd.getArguments().forEach((key, pair) -> command.then(Commands.argument(key, pair.getLeft()).executes(context -> { + + // This is FUCKING UGLY.... Need to improve this in the future + if (pair.getLeft() instanceof GameProfileArgument) { + Collection profiles = GameProfileArgument.getGameProfiles(context, key); + List bridgedGameProfiles = new ArrayList<>(); + + profiles.forEach(p -> bridgedGameProfiles.add(BridgedGameProfile.of(p))); + + ((TriConsumer, BridgedCommandSourceStack>) pair.getRight()) + .accept(BridgedPlayer.of(context.getSource().getPlayer()), bridgedGameProfiles, BridgedCommandSourceStack.of(context.getSource())); + return 1; + } + + return 1; + }))); + + dispatcher.register(command); + } + + } + +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/core/BridgedBlockPos.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/core/BridgedBlockPos.java new file mode 100644 index 0000000..bd49e2c --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/core/BridgedBlockPos.java @@ -0,0 +1,27 @@ +package com.hypherionmc.craterlib.nojang.core; + +import lombok.RequiredArgsConstructor; +import net.minecraft.core.BlockPos; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedBlockPos { + + private final BlockPos internal; + + public int getX() { + return internal.getX(); + } + + public int getY() { + return internal.getY(); + } + + public int getZ() { + return internal.getZ(); + } + + public BlockPos toMojang() { + return internal; + } + +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/nbt/BridgedCompoundTag.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/nbt/BridgedCompoundTag.java new file mode 100644 index 0000000..17a2d04 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/nbt/BridgedCompoundTag.java @@ -0,0 +1,49 @@ +package com.hypherionmc.craterlib.nojang.nbt; + +import lombok.RequiredArgsConstructor; +import net.minecraft.nbt.CompoundTag; + +import java.util.Set; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedCompoundTag { + + private final CompoundTag internal; + + public static BridgedCompoundTag empty() { + return new BridgedCompoundTag(new CompoundTag()); + } + + public BridgedCompoundTag getCompound(String key) { + return BridgedCompoundTag.of(internal.getCompound(key)); + } + + public Set getAllKeys() { + return internal.getAllKeys(); + } + + public String getString(String key) { + return internal.getString(key); + } + + public boolean getBoolean(String key) { + return internal.getBoolean(key); + } + + public void putString(String key, String value) { + internal.putString(key, value); + } + + public void put(String key, BridgedCompoundTag value) { + internal.put(key, value.toMojang()); + } + + public void putBoolean(String key, boolean value) { + internal.putBoolean(key, value); + } + + public CompoundTag toMojang() { + return internal; + } + +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/network/BridgedFriendlyByteBuf.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/network/BridgedFriendlyByteBuf.java new file mode 100644 index 0000000..a7bafd0 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/network/BridgedFriendlyByteBuf.java @@ -0,0 +1,34 @@ +package com.hypherionmc.craterlib.nojang.network; + +import com.hypherionmc.craterlib.nojang.nbt.BridgedCompoundTag; +import lombok.RequiredArgsConstructor; +import net.minecraft.network.FriendlyByteBuf; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedFriendlyByteBuf { + + private final FriendlyByteBuf internal; + + public BridgedCompoundTag readNbt() { + return BridgedCompoundTag.of(internal.readNbt()); + } + + public BridgedFriendlyByteBuf writeNbt(BridgedCompoundTag tag) { + internal.writeNbt(tag.toMojang()); + return BridgedFriendlyByteBuf.of(internal); + } + + public BridgedFriendlyByteBuf writeUtf(String value) { + internal.writeUtf(value); + return BridgedFriendlyByteBuf.of(internal); + } + + public String readUtf() { + return internal.readUtf(); + } + + public FriendlyByteBuf toMojang() { + return internal; + } + +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/package-info.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/package-info.java new file mode 100644 index 0000000..4c177a5 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/package-info.java @@ -0,0 +1,9 @@ +/** + * @author HypherionSA + * This package, called NoJang, exposes various wrapped API's. + * Using this api, a mod can essentially run on ANY minecraft version this library + * supports, from one code base. + * IMPORTANT NOTE: THESE API'S MUST NEVER EXPOSE ANY MINECRAFT CLASSES OR CODE!!!! + * THEY MUST ALWAYS BE HANDLED INTERNALLY AND ONLY RETURN WRAPPED VARIANTS + */ +package com.hypherionmc.craterlib.nojang; \ No newline at end of file diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/realmsclient/dto/BridgedRealmsServer.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/realmsclient/dto/BridgedRealmsServer.java new file mode 100644 index 0000000..dddf746 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/realmsclient/dto/BridgedRealmsServer.java @@ -0,0 +1,40 @@ +package com.hypherionmc.craterlib.nojang.realmsclient.dto; + +import com.mojang.realmsclient.dto.PlayerInfo; +import com.mojang.realmsclient.dto.RealmsServer; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedRealmsServer { + + private final RealmsServer internal; + + public String getName() { + return internal.getName(); + } + + public String getDescription() { + return internal.getDescription(); + } + + public String getWorldType() { + return internal.worldType.name(); + } + + public String getMinigameName() { + return internal.getMinigameName(); + } + + public String getMinigameImage() { + return internal.minigameImage; + } + + public long getPlayerCount() { + return internal.players.stream().filter(PlayerInfo::getOnline).count(); + } + + public RealmsServer toMojang() { + return internal; + } + +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/resources/ResourceIdentifier.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/resources/ResourceIdentifier.java new file mode 100644 index 0000000..9b848f4 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/resources/ResourceIdentifier.java @@ -0,0 +1,36 @@ +package com.hypherionmc.craterlib.nojang.resources; + +import net.minecraft.resources.ResourceLocation; + +public class ResourceIdentifier { + + private final ResourceLocation internal; + + public ResourceIdentifier(String namespace, String path) { + this.internal = new ResourceLocation(namespace, path); + } + + public ResourceIdentifier(String path) { + this.internal = new ResourceLocation(path); + } + + public String getNamespace() { + return internal.getNamespace(); + } + + public String getPath() { + return internal.getPath(); + } + + public String getString() { + return internal.toString(); + } + + public static ResourceIdentifier fromMojang(ResourceLocation location) { + return new ResourceIdentifier(location.getNamespace(), location.getPath()); + } + + public ResourceLocation toMojang() { + return internal; + } +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/server/BridgedMinecraftServer.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/server/BridgedMinecraftServer.java new file mode 100644 index 0000000..cb818b7 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/server/BridgedMinecraftServer.java @@ -0,0 +1,91 @@ +package com.hypherionmc.craterlib.nojang.server; + +import com.hypherionmc.craterlib.nojang.authlib.BridgedGameProfile; +import com.hypherionmc.craterlib.nojang.commands.BridgedFakePlayer; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.ChatUtils; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; +import net.minecraft.SharedConstants; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.players.UserBanListEntry; +import net.minecraft.server.players.UserWhiteListEntry; + +import java.util.ArrayList; +import java.util.List; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedMinecraftServer { + + private final MinecraftServer internal; + + public boolean isUsingWhitelist() { + return internal.getPlayerList().isUsingWhitelist(); + } + + public int getPlayerCount() { + return internal.getPlayerList().getPlayerCount(); + } + + public int getMaxPlayers() { + return internal.getPlayerList().getMaxPlayers(); + } + + public String getServerModName() { + return internal.getServerModName(); + } + + public String getName() { + return SharedConstants.getCurrentVersion().getName(); + } + + public boolean usesAuthentication() { + return internal.usesAuthentication(); + } + + public void broadcastSystemMessage(Component text, boolean bl) { + internal.getPlayerList().broadcastSystemMessage(ChatUtils.adventureToMojang(text), bl); + } + + public boolean isPlayerBanned(BridgedGameProfile profile) { + return internal.getPlayerList().getBans().isBanned(profile.toMojang()); + } + + public void whitelistPlayer(BridgedGameProfile gameProfile) { + if (!internal.getPlayerList().isUsingWhitelist()) + return; + + internal.getPlayerList().getWhiteList().add(new UserWhiteListEntry(gameProfile.toMojang())); + } + + public void unWhitelistPlayer(BridgedGameProfile gameProfile) { + if (!internal.getPlayerList().isUsingWhitelist()) + return; + + internal.getPlayerList().getWhiteList().remove(new UserWhiteListEntry(gameProfile.toMojang())); + } + + public List getPlayers() { + List profiles = new ArrayList<>(); + + if (internal.getPlayerList() == null) + return profiles; + + internal.getPlayerList().getPlayers().forEach(p -> profiles.add(BridgedPlayer.of(p))); + + return profiles; + } + + public void banPlayer(BridgedGameProfile profile) { + internal.getPlayerList().getBans().add(new UserBanListEntry(profile.toMojang())); + } + + public void executeCommand(BridgedMinecraftServer server, BridgedFakePlayer player, String command) { + internal.getCommands().performPrefixedCommand(player.toMojang(), command); + } + + public MinecraftServer toMojang() { + return internal; + } + +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/world/entity/player/BridgedPlayer.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/world/entity/player/BridgedPlayer.java new file mode 100644 index 0000000..8aa6d49 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/nojang/world/entity/player/BridgedPlayer.java @@ -0,0 +1,63 @@ +package com.hypherionmc.craterlib.nojang.world.entity.player; + +import com.hypherionmc.craterlib.nojang.authlib.BridgedGameProfile; +import com.hypherionmc.craterlib.nojang.core.BridgedBlockPos; +import com.hypherionmc.craterlib.utils.ChatUtils; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraft.world.entity.player.Player; +import org.jetbrains.annotations.Nullable; + +import java.util.UUID; + +@RequiredArgsConstructor(staticName = "of") +public class BridgedPlayer { + + private final Player internal; + + public Component getDisplayName() { + return ChatUtils.mojangToAdventure(internal.getDisplayName()); + } + + public Component getName() { + return ChatUtils.mojangToAdventure(internal.getName()); + } + + public UUID getUUID() { + return internal.getUUID(); + } + + public String getStringUUID() { + return internal.getStringUUID(); + } + + public BridgedGameProfile getGameProfile() { + return BridgedGameProfile.of(internal.getGameProfile()); + } + + public boolean isServerPlayer() { + return internal instanceof ServerPlayer; + } + + public Player toMojang() { + return internal; + } + + public BridgedBlockPos getOnPos() { + return BridgedBlockPos.of(internal.getOnPos()); + } + + @Nullable + public ServerGamePacketListenerImpl getConnection() { + if (isServerPlayer()) { + return ((ServerPlayer) internal).connection; + } + return null; + } + + public ServerPlayer toMojangServerPlayer() { + return (ServerPlayer) internal; + } +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java new file mode 100644 index 0000000..7bbcfd1 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java @@ -0,0 +1,96 @@ +package com.hypherionmc.craterlib.utils; + +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; +import me.hypherionmc.mcdiscordformatter.discord.DiscordSerializer; +import me.hypherionmc.mcdiscordformatter.minecraft.MinecraftSerializer; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import net.kyori.adventure.text.serializer.json.JSONOptions; +import net.minecraft.ChatFormatting; +import net.minecraft.SharedConstants; +import net.minecraft.Util; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.Style; + +public class ChatUtils { + + private static final GsonComponentSerializer adventureSerializer = GsonComponentSerializer.builder().options( + JSONOptions.byDataVersion().at(SharedConstants.getCurrentVersion().getDataVersion().getVersion()) + ).build(); + + public static Component adventureToMojang(net.kyori.adventure.text.Component inComponent) { + final String serialised = adventureSerializer.serialize(inComponent); + return Component.Serializer.fromJson(serialised); + } + + public static net.kyori.adventure.text.Component mojangToAdventure(Component inComponent) { + final String serialised = Component.Serializer.toJson(inComponent); + return adventureSerializer.deserialize(serialised); + } + + // Some text components contain duplicate text, resulting in duplicate messages + // sent back to discord. This should help fix those issues + public static Component safeCopy(Component inComponent) { + String value = inComponent.getString(); + Style style = inComponent.getStyle(); + return Component.literal(value).withStyle(style); + } + + public static String strip(String inString, String... toStrip) { + String finalString = inString; + + for (String strip : toStrip) { + if (finalString.startsWith(strip)) + finalString = finalString.replaceFirst(strip, ""); + + if (finalString.startsWith(" ")) + finalString = finalString.replaceFirst(" ", ""); + } + + return finalString; + } + + public static String resolve(net.kyori.adventure.text.Component component, boolean formatted) { + Component c = adventureToMojang(component); + String returnVal = ChatFormatting.stripFormatting(c.getString()); + + if (formatted) { + returnVal = DiscordSerializer.INSTANCE.serialize(safeCopy(c).copy()); + } + + return returnVal; + } + + public static net.kyori.adventure.text.Component resolve(String component, boolean formatted) { + Component returnVal = Component.literal(component); + if (formatted) { + returnVal = MinecraftSerializer.INSTANCE.serialize(component); + } + + return mojangToAdventure(returnVal); + } + + public static net.kyori.adventure.text.Component getTooltipTitle(String key) { + return net.kyori.adventure.text.Component.text(NamedTextColor.YELLOW + net.kyori.adventure.text.Component.translatable(key).key()); + } + + public static String resolveTranslation(String key) { + return net.kyori.adventure.text.Component.translatable(key).key(); + } + + public static net.kyori.adventure.text.Component getTranslation(String key) { + return net.kyori.adventure.text.Component.translatable(key); + } + + public static net.kyori.adventure.text.Component makeComponent(String text) { + return net.kyori.adventure.text.Component.translatable(text); + } + + public static net.kyori.adventure.text.Component getBiomeName(ResourceIdentifier identifier) { + if (identifier == null) + return net.kyori.adventure.text.Component.text("Unknown"); + + return mojangToAdventure(Component.translatable(Util.makeDescriptionId("biome", identifier.toMojang()))); + } + +} diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/utils/InternalServiceUtil.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/utils/InternalServiceUtil.java new file mode 100644 index 0000000..2305d14 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/utils/InternalServiceUtil.java @@ -0,0 +1,27 @@ +package com.hypherionmc.craterlib.utils; + +import com.hypherionmc.craterlib.CraterConstants; + +import java.util.ServiceLoader; + +/** + * @author HypherionSA + * Utility class to handle SPI loading + */ +public class InternalServiceUtil { + + /** + * Try to load a service + * + * @param clazz The service class type to load + * @return The loaded class + */ + public static T load(Class clazz) { + final T loadedService = ServiceLoader.load(clazz) + .findFirst() + .orElseThrow(() -> new NullPointerException("Failed to load service for " + clazz.getName())); + CraterConstants.LOG.debug("Loaded {} for service {}", loadedService, clazz); + return loadedService; + } + +} \ No newline at end of file diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/utils/OptifineUtils.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/utils/OptifineUtils.java new file mode 100644 index 0000000..8a06da2 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/utils/OptifineUtils.java @@ -0,0 +1,46 @@ +package com.hypherionmc.craterlib.utils; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * @author HypherionSA + * Utility class for Optifine compatibility + */ +public class OptifineUtils { + + private static final boolean hasOptifine = checkOptifine(); + + private static boolean checkOptifine() { + try { + Class ofConfigClass = Class.forName("net.optifine.Config"); + return true; + } catch (ClassNotFoundException e) { + // Optifine is probably not present. Ignore the error + return false; + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } + + public static boolean isRenderRegions() { + try { + Class ofConfigClass = Class.forName("net.optifine.Config"); + Method rrField = ofConfigClass.getMethod("isRenderRegions"); + return (boolean) rrField.invoke(null); + } catch (ClassNotFoundException | InvocationTargetException | NoSuchMethodException | + IllegalAccessException e) { + // Optifine is probably not present. Ignore the error + return false; + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } + + public static boolean hasOptifine() { + return hasOptifine; + } + +} \ No newline at end of file diff --git a/1.20/Common/src/main/java/com/hypherionmc/craterlib/utils/TriConsumer.java b/1.20/Common/src/main/java/com/hypherionmc/craterlib/utils/TriConsumer.java new file mode 100644 index 0000000..77f7309 --- /dev/null +++ b/1.20/Common/src/main/java/com/hypherionmc/craterlib/utils/TriConsumer.java @@ -0,0 +1,5 @@ +package com.hypherionmc.craterlib.utils; + +public interface TriConsumer { + void accept(T t, U u, V v); +} diff --git a/1.20/Common/src/main/resources/assets/craterlib/lang/en_us.json b/1.20/Common/src/main/resources/assets/craterlib/lang/en_us.json new file mode 100644 index 0000000..03115d9 --- /dev/null +++ b/1.20/Common/src/main/resources/assets/craterlib/lang/en_us.json @@ -0,0 +1,8 @@ +{ + "t.clc.opensubconfig": "Open Config", + "t.clc.save": "Save", + "t.clc.cancel_discard": "Discard", + "t.clc.quit_config": "Unsaved Changes", + "t.clc.quit_config_sure": "You have unsaved config changes. Are you sure you want to discard them?", + "t.clc.quit_discard": "Quit & Discard" +} diff --git a/1.20/Common/src/main/resources/craterlib.mixins.json b/1.20/Common/src/main/resources/craterlib.mixins.json new file mode 100644 index 0000000..1a739db --- /dev/null +++ b/1.20/Common/src/main/resources/craterlib.mixins.json @@ -0,0 +1,24 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "com.hypherionmc.craterlib.mixin", + "compatibilityLevel": "JAVA_17", + "mixins": [ + ], + "client": [ + "ChatInputSuggestorMixin", + "events.PlayerMixin", + "events.client.ClientLevelMixin", + "events.client.MinecraftMixin", + "events.client.RealmsMainScreenMixin" + ], + "server": [ + "events.CommandMixin", + "events.PlayerAdvancementsMixin", + "events.PlayerListMixin", + "events.ServerPlayerMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/1.20/Common/src/main/resources/pack.mcmeta b/1.20/Common/src/main/resources/pack.mcmeta new file mode 100644 index 0000000..263d366 --- /dev/null +++ b/1.20/Common/src/main/resources/pack.mcmeta @@ -0,0 +1,6 @@ +{ + "pack": { + "description": "${mod_name}", + "pack_format": 18 + } +} diff --git a/1.20/Fabric/build.gradle b/1.20/Fabric/build.gradle new file mode 100644 index 0000000..7848d60 --- /dev/null +++ b/1.20/Fabric/build.gradle @@ -0,0 +1,126 @@ +archivesBaseName = "${mod_name.replace(" ", "")}-Fabric-${minecraft_version}" + +dependencies { + // Core + modImplementation "net.fabricmc.fabric-api:fabric-api:${fabric_api}" + + // Compat + modImplementation("com.terraformersmc:modmenu:${mod_menu_version}") { + exclude(group: "net.fabricmc.fabric-api") + } + + modImplementation "maven.modrinth:fabrictailor:${fabrictailor}" + modImplementation "maven.modrinth:vanish:${vanish}" + + // Do not edit or remove + implementation project(":Common") +} + +shadowJar { + from sourceSets.main.output + configurations = [project.configurations.shade] + + dependencies { + exclude(dependency('com.google.code.gson:.*')) + + relocate 'me.hypherionmc.moonconfig', 'shadow.hypherionmc.moonconfig' + relocate 'me.hypherionmc.mcdiscordformatter', 'shadow.hypherionmc.mcdiscordformatter' + relocate 'net.kyori', 'shadow.kyori' + } + + setArchiveClassifier('dev-shadow') +} + +/** + * =============================================================================== + * = DO NOT EDIT BELOW THIS LINE UNLESS YOU KNOW WHAT YOU ARE DOING = + * =============================================================================== + */ + +unimined.minecraft { + fabric { + loader fabric_loader + } +} + +remapJar { + inputFile.set shadowJar.archiveFile + dependsOn shadowJar + archiveClassifier.set null +} + +jar { + archiveClassifier.set "dev" +} + +processResources { + from project(":Common").sourceSets.main.resources + def buildProps = project.properties.clone() + + filesMatching(['fabric.mod.json']) { + expand buildProps + } +} + +compileTestJava.enabled = false + +tasks.withType(JavaCompile).configureEach { + source(project(":Common").sourceSets.main.allSource) +} + +/** + * Publishing Config + */ +publishing { + publications { + mavenJava(MavenPublication) { + artifactId project.archivesBaseName + from components.java + + artifact(remapJar) { + builtBy remapJar + } + + pom.withXml { + Node pomNode = asNode() + pomNode.dependencies.'*'.findAll() { + it.artifactId.text() == 'regutils-joined-fabric' || + it.artifactId.text() == 'core' || + it.artifactId.text() == 'toml' + }.each() { + it.parent().remove(it) + } + } + } + } + + repositories { + maven rootProject.orion.getPublishingMaven() + } +} + +publisher { + apiKeys { + modrinth(System.getenv("MODRINTH_TOKEN")) + curseforge(System.getenv("CURSE_TOKEN")) + } + + setCurseID(curse_id) + setModrinthID(modrinth_id) + setVersionType("release") + setChangelog("https://raw.githubusercontent.com/hypherionmc/changelogs/main/craterlib/changelog-fabric.md") + setProjectVersion("${minecraft_version}-${project.version}") + setDisplayName("[FABRIC/QUILT 1.20/1.20.1] CraterLib - ${project.version}") + setGameVersions("1.20", "1.20.1") + setLoaders("fabric", "quilt") + setArtifact(remapJar) + setCurseEnvironment("both") + + modrinthDepends { + required("fabric-api") + } + + curseDepends { + required("fabric-api") + } +} diff --git a/1.20/Fabric/src/main/java/com/hypherionmc/craterlib/CraterLibInitializer.java b/1.20/Fabric/src/main/java/com/hypherionmc/craterlib/CraterLibInitializer.java new file mode 100644 index 0000000..c66c210 --- /dev/null +++ b/1.20/Fabric/src/main/java/com/hypherionmc/craterlib/CraterLibInitializer.java @@ -0,0 +1,42 @@ +package com.hypherionmc.craterlib; + +import com.hypherionmc.craterlib.api.events.server.CraterRegisterCommandEvent; +import com.hypherionmc.craterlib.api.events.server.CraterServerLifecycleEvent; +import com.hypherionmc.craterlib.common.FabricCommonPlatform; +import com.hypherionmc.craterlib.compat.Vanish; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.core.networking.CraterPacketNetwork; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.core.platform.ModloaderEnvironment; +import com.hypherionmc.craterlib.network.CraterFabricNetworkHandler; +import com.hypherionmc.craterlib.nojang.commands.CommandsRegistry; +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; + +public class CraterLibInitializer implements ModInitializer { + + @Override + public void onInitialize() { + new CraterPacketNetwork(new CraterFabricNetworkHandler(PacketSide.SERVER)); + CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> { + CraterEventBus.INSTANCE.postEvent(new CraterRegisterCommandEvent()); + CommandsRegistry.INSTANCE.registerCommands(dispatcher); + }); + + + ServerLifecycleEvents.SERVER_STARTING.register(server -> { + FabricCommonPlatform.server = server; + CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Starting(BridgedMinecraftServer.of(server))); + }); + + ServerLifecycleEvents.SERVER_STARTED.register(li -> CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Started(BridgedMinecraftServer.of(li)))); + ServerLifecycleEvents.SERVER_STOPPING.register(server -> CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Stopping(BridgedMinecraftServer.of(server)))); + ServerLifecycleEvents.SERVER_STOPPED.register(server -> CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Stopped(BridgedMinecraftServer.of(server)))); + + if (ModloaderEnvironment.INSTANCE.isModLoaded("melius-vanish")) { + Vanish.register(); + } + } +} diff --git a/1.20/Fabric/src/main/java/com/hypherionmc/craterlib/CraterLibModMenuIntegration.java b/1.20/Fabric/src/main/java/com/hypherionmc/craterlib/CraterLibModMenuIntegration.java new file mode 100644 index 0000000..6615352 --- /dev/null +++ b/1.20/Fabric/src/main/java/com/hypherionmc/craterlib/CraterLibModMenuIntegration.java @@ -0,0 +1,30 @@ +package com.hypherionmc.craterlib; + +import com.hypherionmc.craterlib.client.gui.config.CraterConfigScreen; +import com.hypherionmc.craterlib.core.config.ConfigController; +import com.hypherionmc.craterlib.core.config.ModuleConfig; +import com.hypherionmc.craterlib.core.config.annotations.NoConfigScreen; +import com.terraformersmc.modmenu.api.ConfigScreenFactory; +import com.terraformersmc.modmenu.api.ModMenuApi; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author HypherionSA + */ +public class CraterLibModMenuIntegration implements ModMenuApi { + + @Override + public Map> getProvidedConfigScreenFactories() { + Map> configScreens = new HashMap<>(); + + ConfigController.getMonitoredConfigs().forEach((conf, watcher) -> { + if (!conf.getClass().isAnnotationPresent(NoConfigScreen.class)) { + configScreens.put(((ModuleConfig) conf).getModId(), screen -> new CraterConfigScreen((ModuleConfig) conf, screen)); + } + }); + + return configScreens; + } +} diff --git a/1.20/Fabric/src/main/java/com/hypherionmc/craterlib/client/CraterLibClientInitializer.java b/1.20/Fabric/src/main/java/com/hypherionmc/craterlib/client/CraterLibClientInitializer.java new file mode 100644 index 0000000..b5ec5b1 --- /dev/null +++ b/1.20/Fabric/src/main/java/com/hypherionmc/craterlib/client/CraterLibClientInitializer.java @@ -0,0 +1,27 @@ +package com.hypherionmc.craterlib.client; + +import com.hypherionmc.craterlib.api.events.client.CraterClientTickEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.core.networking.CraterPacketNetwork; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.network.CraterFabricNetworkHandler; +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedClientLevel; +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; + +public class CraterLibClientInitializer implements ClientModInitializer { + + @Override + public void onInitializeClient() { + new CraterPacketNetwork(new CraterFabricNetworkHandler(PacketSide.CLIENT)); + ClientTickEvents.START_CLIENT_TICK.register((listener) -> { + if (listener.level == null) + return; + + CraterClientTickEvent event = new CraterClientTickEvent(BridgedClientLevel.of(listener.level)); + CraterEventBus.INSTANCE.postEvent(event); + }); + + CraterEventBus.INSTANCE.registerEventListener(CraterLibClientInitializer.class); + } +} diff --git a/1.20/Fabric/src/main/java/com/hypherionmc/craterlib/client/FabricClientPlatform.java b/1.20/Fabric/src/main/java/com/hypherionmc/craterlib/client/FabricClientPlatform.java new file mode 100644 index 0000000..7852f12 --- /dev/null +++ b/1.20/Fabric/src/main/java/com/hypherionmc/craterlib/client/FabricClientPlatform.java @@ -0,0 +1,34 @@ +package com.hypherionmc.craterlib.client; + +import com.hypherionmc.craterlib.core.platform.ClientPlatform; +import com.hypherionmc.craterlib.nojang.client.BridgedMinecraft; +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedClientLevel; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraft.client.Minecraft; +import net.minecraft.network.Connection; + +/** + * @author HypherionSA + */ +public class FabricClientPlatform implements ClientPlatform { + + @Override + public BridgedMinecraft getClientInstance() { + return new BridgedMinecraft(); + } + + @Override + public BridgedPlayer getClientPlayer() { + return BridgedPlayer.of(Minecraft.getInstance().player); + } + + @Override + public BridgedClientLevel getClientLevel() { + return BridgedClientLevel.of(Minecraft.getInstance().level); + } + + @Override + public Connection getClientConnection() { + return Minecraft.getInstance().getConnection().getConnection(); + } +} diff --git a/1.20/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricCommonPlatform.java b/1.20/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricCommonPlatform.java new file mode 100644 index 0000000..c1a30c7 --- /dev/null +++ b/1.20/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricCommonPlatform.java @@ -0,0 +1,18 @@ +package com.hypherionmc.craterlib.common; + +import com.hypherionmc.craterlib.core.platform.CommonPlatform; +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import net.minecraft.server.MinecraftServer; + +/** + * @author HypherionSA + */ +public class FabricCommonPlatform implements CommonPlatform { + + public static MinecraftServer server; + + @Override + public BridgedMinecraftServer getMCServer() { + return BridgedMinecraftServer.of(server); + } +} diff --git a/1.20/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricCompatHelper.java b/1.20/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricCompatHelper.java new file mode 100644 index 0000000..dbcaf63 --- /dev/null +++ b/1.20/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricCompatHelper.java @@ -0,0 +1,23 @@ +package com.hypherionmc.craterlib.common; + +import com.hypherionmc.craterlib.compat.FabricTailor; +import com.hypherionmc.craterlib.compat.Vanish; +import com.hypherionmc.craterlib.core.platform.CompatUtils; +import com.hypherionmc.craterlib.core.platform.ModloaderEnvironment; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; + +public class FabricCompatHelper implements CompatUtils { + + @Override + public boolean isPlayerActive(BridgedPlayer player) { + if (!ModloaderEnvironment.INSTANCE.isModLoaded("melius-vanish")) + return true; + + return Vanish.isPlayerVanished(player.toMojangServerPlayer()); + } + + @Override + public String getSkinUUID(BridgedPlayer player) { + return FabricTailor.getTailorSkin(player.toMojangServerPlayer()); + } +} diff --git a/1.20/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricLoaderHelper.java b/1.20/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricLoaderHelper.java new file mode 100644 index 0000000..95f5c42 --- /dev/null +++ b/1.20/Fabric/src/main/java/com/hypherionmc/craterlib/common/FabricLoaderHelper.java @@ -0,0 +1,69 @@ +package com.hypherionmc.craterlib.common; + +import com.hypherionmc.craterlib.core.platform.Environment; +import com.hypherionmc.craterlib.core.platform.ModloaderEnvironment; +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.SharedConstants; +import net.minecraft.client.Minecraft; + +import java.io.File; + +/** + * @author HypherionSA + * @date 07/08/2022 + */ +public class FabricLoaderHelper implements ModloaderEnvironment { + + @Override + public boolean isFabric() { + return true; + } + + @Override + public String getGameVersion() { + return SharedConstants.VERSION_STRING; + } + + @Override + public File getGameFolder() { + return Minecraft.getInstance().gameDirectory; + } + + @Override + public File getConfigFolder() { + return FabricLoader.getInstance().getConfigDir().toFile(); + } + + @Override + public File getModsFolder() { + return new File(FabricLoader.getInstance().getGameDir().toString() + File.separator + "mods"); + } + + @Override + public Environment getEnvironment() { + switch (FabricLoader.getInstance().getEnvironmentType()) { + case SERVER -> { + return Environment.SERVER; + } + case CLIENT -> { + return Environment.CLIENT; + } + } + return Environment.UNKNOWN; + } + + @Override + public boolean isModLoaded(String modid) { + return FabricLoader.getInstance().isModLoaded(modid); + } + + @Override + public boolean isDevEnv() { + return FabricLoader.getInstance().isDevelopmentEnvironment(); + } + + @Override + public int getModCount() { + return FabricLoader.getInstance().getAllMods().size(); + } +} diff --git a/1.20/Fabric/src/main/java/com/hypherionmc/craterlib/compat/FabricTailor.java b/1.20/Fabric/src/main/java/com/hypherionmc/craterlib/compat/FabricTailor.java new file mode 100644 index 0000000..b95a7d7 --- /dev/null +++ b/1.20/Fabric/src/main/java/com/hypherionmc/craterlib/compat/FabricTailor.java @@ -0,0 +1,23 @@ +package com.hypherionmc.craterlib.compat; + +import com.hypherionmc.craterlib.core.platform.ModloaderEnvironment; +import net.minecraft.server.level.ServerPlayer; +import org.samo_lego.fabrictailor.casts.TailoredPlayer; +public class FabricTailor { + + public static String getTailorSkin(ServerPlayer player) { + if (!ModloaderEnvironment.INSTANCE.isModLoaded("fabrictailor")) + return player.getStringUUID(); + + try { + if (player instanceof TailoredPlayer tp) { + return tp.getSkinId(); + } + } catch (Exception e) { + e.printStackTrace(); + } + + return player.getStringUUID(); + } + +} \ No newline at end of file diff --git a/1.20/Fabric/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java b/1.20/Fabric/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java new file mode 100644 index 0000000..cc6c1e0 --- /dev/null +++ b/1.20/Fabric/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java @@ -0,0 +1,25 @@ +package com.hypherionmc.craterlib.compat; + +import com.hypherionmc.craterlib.api.events.server.CraterPlayerEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import me.drex.vanish.api.VanishAPI; +import me.drex.vanish.api.VanishEvents; +import net.minecraft.server.level.ServerPlayer; + +public class Vanish { + + public static void register() { + VanishEvents.VANISH_EVENT.register((serverPlayer, b) -> { + if (b) { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerEvent.PlayerLoggedOut(BridgedPlayer.of(serverPlayer))); + } else { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerEvent.PlayerLoggedIn(BridgedPlayer.of(serverPlayer))); + } + }); + } + + public static boolean isPlayerVanished(ServerPlayer player) { + return VanishAPI.isVanished(player); + } +} diff --git a/1.20/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java b/1.20/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java new file mode 100644 index 0000000..fbd762b --- /dev/null +++ b/1.20/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java @@ -0,0 +1,36 @@ +package com.hypherionmc.craterlib.mixin; + +import com.hypherionmc.craterlib.api.events.server.CraterServerChatEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.ChatUtils; +import net.minecraft.network.chat.PlayerChatMessage; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.concurrent.CompletableFuture; + +@Mixin(value = ServerGamePacketListenerImpl.class, priority = Integer.MIN_VALUE) +public class ServerGamePacketListenerImplMixin { + + @Shadow + public ServerPlayer player; + + @Inject( + method = "lambda$handleChat$8", + at = @At("HEAD"), + cancellable = true + ) + private void injectChatEvent(PlayerChatMessage arg, CompletableFuture completableFuture, CompletableFuture completableFuture2, Void void_, CallbackInfo ci) { + CraterServerChatEvent event = new CraterServerChatEvent(BridgedPlayer.of(this.player), arg.decoratedContent().getString(), ChatUtils.mojangToAdventure(arg.decoratedContent())); + CraterEventBus.INSTANCE.postEvent(event); + if (event.wasCancelled()) + ci.cancel(); + } + +} diff --git a/1.20/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/TutorialMixin.java b/1.20/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/TutorialMixin.java new file mode 100644 index 0000000..f9eaa50 --- /dev/null +++ b/1.20/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/TutorialMixin.java @@ -0,0 +1,24 @@ +package com.hypherionmc.craterlib.mixin; + +import com.hypherionmc.craterlib.api.events.client.LateInitEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.client.BridgedMinecraft; +import com.hypherionmc.craterlib.nojang.client.BridgedOptions; +import net.minecraft.client.Minecraft; +import net.minecraft.client.Options; +import net.minecraft.client.tutorial.Tutorial; +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(Tutorial.class) +public class TutorialMixin { + + @Inject(method = "", at = @At("RETURN")) + private void injectEarlyInitEvent(Minecraft minecraft, Options options, CallbackInfo ci) { + LateInitEvent event = new LateInitEvent(new BridgedMinecraft(), BridgedOptions.of(options)); + CraterEventBus.INSTANCE.postEvent(event); + } + +} diff --git a/1.20/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java b/1.20/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java new file mode 100644 index 0000000..b3014a3 --- /dev/null +++ b/1.20/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java @@ -0,0 +1,81 @@ +package com.hypherionmc.craterlib.network; + +import com.hypherionmc.craterlib.CraterConstants; +import com.hypherionmc.craterlib.core.networking.PacketRegistry; +import com.hypherionmc.craterlib.core.networking.data.PacketContext; +import com.hypherionmc.craterlib.core.networking.data.PacketHolder; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.nojang.network.BridgedFriendlyByteBuf; +import com.hypherionmc.craterlib.nojang.resources.ResourceIdentifier; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.minecraft.network.FriendlyByteBuf; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public class CraterFabricNetworkHandler extends PacketRegistry { + + private final Map, Message> CHANNELS = new HashMap(); + + public CraterFabricNetworkHandler(PacketSide side) { + super(side); + } + + protected void registerPacket(PacketHolder holder) { + if (CHANNELS.get(holder.messageType()) == null) { + CHANNELS.put(holder.messageType(), new Message<>(holder.type(), holder.encoder())); + + if (PacketSide.CLIENT.equals(this.side)) { + ClientPlayNetworking.registerGlobalReceiver(holder.type().toMojang(), ((client, listener, buf, responseSender) -> { + buf.readByte(); + T message = holder.decoder().apply(BridgedFriendlyByteBuf.of(buf)); + client.execute(() -> holder.handler().accept(new PacketContext<>(message, PacketSide.CLIENT))); + })); + } else { + + ServerPlayNetworking.registerGlobalReceiver(holder.type().toMojang(), ((server, player, listener, buf, responseSender) -> { + buf.readByte(); + T message = holder.decoder().apply(BridgedFriendlyByteBuf.of(buf)); + server.execute(() -> holder.handler().accept(new PacketContext<>(BridgedPlayer.of(player), message, PacketSide.SERVER))); + })); + } + + } else { + CraterConstants.LOG.error("Trying to register duplicate packet for type {}", holder.messageType()); + } + } + + public void sendToServer(T packet) { + this.sendToServer(packet, false); + } + + public void sendToServer(T packet, boolean ignoreCheck) { + Message message = (Message) CHANNELS.get(packet.getClass()); + + if (ClientPlayNetworking.canSend(message.id().toMojang()) || ignoreCheck) { + FriendlyByteBuf buf = PacketByteBufs.create(); + buf.writeByte(0); + message.encoder().accept(packet, BridgedFriendlyByteBuf.of(buf)); + ClientPlayNetworking.send(message.id().toMojang(), buf); + } + } + + public void sendToClient(T packet, BridgedPlayer player) { + Message message = (Message) CHANNELS.get(packet.getClass()); + if (ServerPlayNetworking.canSend(player.toMojangServerPlayer(), message.id().toMojang())) { + FriendlyByteBuf buf = PacketByteBufs.create(); + buf.writeByte(0); + message.encoder().accept(packet, BridgedFriendlyByteBuf.of(buf)); + ServerPlayNetworking.send(player.toMojangServerPlayer(), message.id().toMojang(), buf); + } + } + + public record Message(ResourceIdentifier id, BiConsumer encoder) { } +} diff --git a/1.20/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform b/1.20/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform new file mode 100644 index 0000000..a78d9e5 --- /dev/null +++ b/1.20/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform @@ -0,0 +1 @@ +com.hypherionmc.craterlib.client.FabricClientPlatform diff --git a/1.20/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform b/1.20/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform new file mode 100644 index 0000000..9a2fdb0 --- /dev/null +++ b/1.20/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform @@ -0,0 +1 @@ +com.hypherionmc.craterlib.common.FabricCommonPlatform diff --git a/1.20/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils b/1.20/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils new file mode 100644 index 0000000..62f79a8 --- /dev/null +++ b/1.20/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils @@ -0,0 +1 @@ +com.hypherionmc.craterlib.common.FabricCompatHelper \ No newline at end of file diff --git a/1.20/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment b/1.20/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment new file mode 100644 index 0000000..9a1fb33 --- /dev/null +++ b/1.20/Fabric/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment @@ -0,0 +1 @@ +com.hypherionmc.craterlib.common.FabricLoaderHelper diff --git a/1.20/Fabric/src/main/resources/assets/craterlib/craterlib_logo.png b/1.20/Fabric/src/main/resources/assets/craterlib/craterlib_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..ce0159cc4aa4d823ea354042e531b4522d6dfead GIT binary patch literal 49343 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 z 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 De~rmR literal 0 HcmV?d00001 diff --git a/1.20/Fabric/src/main/resources/craterlib.fabric.mixins.json b/1.20/Fabric/src/main/resources/craterlib.fabric.mixins.json new file mode 100644 index 0000000..7c59043 --- /dev/null +++ b/1.20/Fabric/src/main/resources/craterlib.fabric.mixins.json @@ -0,0 +1,17 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "com.hypherionmc.craterlib.mixin", + "compatibilityLevel": "JAVA_17", + "mixins": [ + ], + "client": [ + "TutorialMixin" + ], + "server": [ + "ServerGamePacketListenerImplMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/1.20/Fabric/src/main/resources/fabric.mod.json b/1.20/Fabric/src/main/resources/fabric.mod.json new file mode 100644 index 0000000..c84abf2 --- /dev/null +++ b/1.20/Fabric/src/main/resources/fabric.mod.json @@ -0,0 +1,39 @@ +{ + "schemaVersion": 1, + "id": "${mod_id}", + "version": "${version}", + "name": "${mod_name}", + "description": "A library mod used by First Dark Development and HypherionSA Mods", + "authors": [ + "${mod_author}", + "Misha" + ], + "contact": { + "homepage": "https://modrinth.com/mod/craterlib", + "sources": "https://github.com/firstdarkdev/craterLib/" + }, + "license": "MIT", + "icon": "assets/craterlib/craterlib_logo.png", + "environment": "*", + "entrypoints": { + "main": [ + "com.hypherionmc.craterlib.CraterLibInitializer" + ], + "client": [ + "com.hypherionmc.craterlib.client.CraterLibClientInitializer" + ], + "modmenu": [ + "com.hypherionmc.craterlib.CraterLibModMenuIntegration" + ] + }, + "mixins": [ + "${mod_id}.mixins.json", + "${mod_id}.fabric.mixins.json" + ], + "depends": { + "fabricloader": ">=0.15.0", + "fabric-api": "*", + "minecraft": ">=1.20", + "java": ">=17" + } +} diff --git a/1.20/Forge/build.gradle b/1.20/Forge/build.gradle new file mode 100644 index 0000000..10c0a67 --- /dev/null +++ b/1.20/Forge/build.gradle @@ -0,0 +1,112 @@ +// Adjust the output jar name here +archivesBaseName = "${mod_name.replace(" ", "")}-Forge-${minecraft_version}" + +dependencies { + // Compat + modImplementation("maven.modrinth:vanishmod:${vanishmod}") + + // Do not edit or remove + implementation project(":Common") +} + +shadowJar { + from sourceSets.main.output + configurations = [project.configurations.shade] + + dependencies { + exclude(dependency('com.google.code.gson:.*')) + + relocate 'me.hypherionmc.moonconfig', 'shadow.hypherionmc.moonconfig' + relocate 'me.hypherionmc.mcdiscordformatter', 'shadow.hypherionmc.mcdiscordformatter' + relocate 'net.kyori', 'shadow.kyori' + } + + setArchiveClassifier('dev-shadow') +} + +/** + * =============================================================================== + * = DO NOT EDIT BELOW THIS LINE UNLESS YOU KNOW WHAT YOU ARE DOING = + * =============================================================================== + */ + +unimined.minecraft { + minecraftForge { + loader forge_version + mixinConfig("${mod_id}.mixins.json", "${mod_id}.forge.mixins.json") + } +} + +remapJar { + inputFile.set shadowJar.archiveFile + dependsOn shadowJar + archiveClassifier.set null +} + +jar { + archiveClassifier.set "dev" +} + +processResources { + from project(":Common").sourceSets.main.resources + def buildProps = project.properties.clone() + + filesMatching("META-INF/mods.toml") { + expand buildProps + } +} + +compileTestJava.enabled = false + +tasks.withType(JavaCompile).configureEach { + source(project(":Common").sourceSets.main.allSource) +} + +/** + * Publishing Config + */ +publishing { + publications { + mavenJava(MavenPublication) { + artifactId project.archivesBaseName + from components.java + + artifact(remapJar) { + builtBy remapJar + } + + pom.withXml { + Node pomNode = asNode() + pomNode.dependencies.'*'.findAll() { + it.artifactId.text() == 'regutils-joined-fabric' || + it.artifactId.text() == 'core' || + it.artifactId.text() == 'toml' + }.each() { + it.parent().remove(it) + } + } + } + } + + repositories { + maven rootProject.orion.getPublishingMaven() + } +} + +publisher { + apiKeys { + modrinth(System.getenv("MODRINTH_TOKEN")) + curseforge(System.getenv("CURSE_TOKEN")) + } + + setCurseID(curse_id) + setModrinthID(modrinth_id) + setVersionType("release") + setChangelog("https://raw.githubusercontent.com/hypherionmc/changelogs/main/craterlib/changelog-forge.md") + setProjectVersion("${minecraft_version}-${project.version}") + setDisplayName("[Forge 1.20/1.20.1] CraterLib - ${project.version}") + setGameVersions("1.20", "1.20.1") + setLoaders("forge") + setArtifact(remapJar) + setCurseEnvironment("both") +} diff --git a/1.20/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java b/1.20/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java new file mode 100644 index 0000000..968da05 --- /dev/null +++ b/1.20/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java @@ -0,0 +1,41 @@ +package com.hypherionmc.craterlib; + +import com.hypherionmc.craterlib.api.events.client.LateInitEvent; +import com.hypherionmc.craterlib.common.ForgeServerEvents; +import com.hypherionmc.craterlib.compat.Vanish; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.core.networking.CraterPacketNetwork; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.core.platform.ModloaderEnvironment; +import com.hypherionmc.craterlib.network.CraterForgeNetworkHandler; +import com.hypherionmc.craterlib.nojang.client.BridgedMinecraft; +import com.hypherionmc.craterlib.nojang.client.BridgedOptions; +import net.minecraft.client.Minecraft; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.DistExecutor; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import net.minecraftforge.fml.loading.FMLLoader; + +@Mod(CraterConstants.MOD_ID) +public class CraterLib { + + public CraterLib() { + MinecraftForge.EVENT_BUS.register(new ForgeServerEvents()); + FMLJavaModLoadingContext.get().getModEventBus().addListener(this::commonSetup); + } + + public void commonSetup(FMLCommonSetupEvent evt) { + new CraterPacketNetwork(new CraterForgeNetworkHandler(FMLLoader.getDist().isClient() ? PacketSide.CLIENT : PacketSide.SERVER)); + DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> { + LateInitEvent event = new LateInitEvent(new BridgedMinecraft(), BridgedOptions.of(Minecraft.getInstance().options)); + CraterEventBus.INSTANCE.postEvent(event); + }); + + if (ModloaderEnvironment.INSTANCE.isModLoaded("vmod")) { + MinecraftForge.EVENT_BUS.register(new Vanish()); + } + } +} diff --git a/1.20/Forge/src/main/java/com/hypherionmc/craterlib/client/ForgeClientEvents.java b/1.20/Forge/src/main/java/com/hypherionmc/craterlib/client/ForgeClientEvents.java new file mode 100644 index 0000000..cb096d5 --- /dev/null +++ b/1.20/Forge/src/main/java/com/hypherionmc/craterlib/client/ForgeClientEvents.java @@ -0,0 +1,25 @@ +package com.hypherionmc.craterlib.client; + +import com.hypherionmc.craterlib.CraterConstants; +import com.hypherionmc.craterlib.api.events.client.CraterClientTickEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedClientLevel; +import net.minecraft.client.Minecraft; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.event.TickEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; + +@Mod.EventBusSubscriber(modid = CraterConstants.MOD_ID, bus = Mod.EventBusSubscriber.Bus.FORGE, value = Dist.CLIENT) +public class ForgeClientEvents { + + @SubscribeEvent + public static void clientTick(TickEvent.LevelTickEvent event) { + if (Minecraft.getInstance().level == null) + return; + + CraterClientTickEvent craterClientTickEvent = new CraterClientTickEvent(BridgedClientLevel.of(Minecraft.getInstance().level)); + CraterEventBus.INSTANCE.postEvent(craterClientTickEvent); + } + +} diff --git a/1.20/Forge/src/main/java/com/hypherionmc/craterlib/client/ForgeClientHelper.java b/1.20/Forge/src/main/java/com/hypherionmc/craterlib/client/ForgeClientHelper.java new file mode 100644 index 0000000..6776622 --- /dev/null +++ b/1.20/Forge/src/main/java/com/hypherionmc/craterlib/client/ForgeClientHelper.java @@ -0,0 +1,41 @@ +package com.hypherionmc.craterlib.client; + +import com.hypherionmc.craterlib.core.platform.ClientPlatform; +import com.hypherionmc.craterlib.nojang.client.BridgedMinecraft; +import com.hypherionmc.craterlib.nojang.client.multiplayer.BridgedClientLevel; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraft.client.Minecraft; +import net.minecraft.network.Connection; + +import java.util.Objects; + +/** + * @author HypherionSA + * @date 16/06/2022 + */ +public class ForgeClientHelper implements ClientPlatform { + + public ForgeClientHelper() { + } + + @Override + public BridgedMinecraft getClientInstance() { + return new BridgedMinecraft(); + } + + @Override + public BridgedPlayer getClientPlayer() { + return BridgedPlayer.of(Minecraft.getInstance().player); + } + + @Override + public BridgedClientLevel getClientLevel() { + return BridgedClientLevel.of(Minecraft.getInstance().level); + } + + @Override + public Connection getClientConnection() { + Objects.requireNonNull(Minecraft.getInstance().getConnection(), "Cannot send packets when not in game!"); + return Minecraft.getInstance().getConnection().getConnection(); + } +} diff --git a/1.20/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeCommonHelper.java b/1.20/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeCommonHelper.java new file mode 100644 index 0000000..281ddd7 --- /dev/null +++ b/1.20/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeCommonHelper.java @@ -0,0 +1,26 @@ +package com.hypherionmc.craterlib.common; + +import com.hypherionmc.craterlib.core.platform.CommonPlatform; +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.CreativeModeTab; +import net.minecraftforge.server.ServerLifecycleHooks; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author HypherionSA + */ +public class ForgeCommonHelper implements CommonPlatform { + + public static Map TABS = new HashMap<>(); + + public ForgeCommonHelper() { + } + + @Override + public BridgedMinecraftServer getMCServer() { + return BridgedMinecraftServer.of(ServerLifecycleHooks.getCurrentServer()); + } +} diff --git a/1.20/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeCompatHelper.java b/1.20/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeCompatHelper.java new file mode 100644 index 0000000..f325837 --- /dev/null +++ b/1.20/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeCompatHelper.java @@ -0,0 +1,17 @@ +package com.hypherionmc.craterlib.common; + +import com.hypherionmc.craterlib.core.platform.CompatUtils; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; + +public class ForgeCompatHelper implements CompatUtils { + + @Override + public boolean isPlayerActive(BridgedPlayer player) { + return true; + } + + @Override + public String getSkinUUID(BridgedPlayer player) { + return player.getStringUUID(); + } +} diff --git a/1.20/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeLoaderHelper.java b/1.20/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeLoaderHelper.java new file mode 100644 index 0000000..2be70fc --- /dev/null +++ b/1.20/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeLoaderHelper.java @@ -0,0 +1,73 @@ +package com.hypherionmc.craterlib.common; + +import com.hypherionmc.craterlib.core.platform.Environment; +import com.hypherionmc.craterlib.core.platform.ModloaderEnvironment; +import net.minecraft.SharedConstants; +import net.minecraft.client.Minecraft; +import net.minecraftforge.fml.ModList; +import net.minecraftforge.fml.loading.FMLLoader; +import net.minecraftforge.fml.loading.FMLPaths; + +import java.io.File; + +/** + * @author HypherionSA + */ +public class ForgeLoaderHelper implements ModloaderEnvironment { + + public ForgeLoaderHelper() { + } + + @Override + public boolean isFabric() { + return false; + } + + @Override + public String getGameVersion() { + return SharedConstants.VERSION_STRING; + } + + @Override + public File getGameFolder() { + return Minecraft.getInstance().gameDirectory; + } + + @Override + public File getConfigFolder() { + return FMLPaths.CONFIGDIR.get().toFile(); + } + + @Override + public File getModsFolder() { + return FMLPaths.MODSDIR.get().toFile(); + } + + @Override + public Environment getEnvironment() { + switch (FMLLoader.getDist()) { + case CLIENT -> { + return Environment.CLIENT; + } + case DEDICATED_SERVER -> { + return Environment.SERVER; + } + } + return Environment.UNKNOWN; + } + + @Override + public boolean isModLoaded(String modid) { + return ModList.get().isLoaded(modid); + } + + @Override + public boolean isDevEnv() { + return !FMLLoader.isProduction(); + } + + @Override + public int getModCount() { + return ModList.get().size(); + } +} diff --git a/1.20/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeServerEvents.java b/1.20/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeServerEvents.java new file mode 100644 index 0000000..f354ddc --- /dev/null +++ b/1.20/Forge/src/main/java/com/hypherionmc/craterlib/common/ForgeServerEvents.java @@ -0,0 +1,43 @@ +package com.hypherionmc.craterlib.common; + +import com.hypherionmc.craterlib.api.events.server.CraterRegisterCommandEvent; +import com.hypherionmc.craterlib.api.events.server.CraterServerLifecycleEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.commands.CommandsRegistry; +import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer; +import net.minecraftforge.event.RegisterCommandsEvent; +import net.minecraftforge.event.server.ServerStartedEvent; +import net.minecraftforge.event.server.ServerStartingEvent; +import net.minecraftforge.event.server.ServerStoppedEvent; +import net.minecraftforge.event.server.ServerStoppingEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; + +public class ForgeServerEvents { + + @SubscribeEvent + public void serverStarting(ServerStartingEvent event) { + CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Starting(BridgedMinecraftServer.of(event.getServer()))); + } + + @SubscribeEvent + public void serverStarted(ServerStartedEvent event) { + CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Started(BridgedMinecraftServer.of(event.getServer()))); + } + + @SubscribeEvent + public void serverStopping(ServerStoppingEvent event) { + CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Stopping(BridgedMinecraftServer.of(event.getServer()))); + } + + @SubscribeEvent + public void serverStopped(ServerStoppedEvent event) { + CraterEventBus.INSTANCE.postEvent(new CraterServerLifecycleEvent.Stopped(BridgedMinecraftServer.of(event.getServer()))); + } + + @SubscribeEvent + public void onCommandRegister(RegisterCommandsEvent event) { + CraterEventBus.INSTANCE.postEvent(new CraterRegisterCommandEvent()); + CommandsRegistry.INSTANCE.registerCommands(event.getDispatcher()); + } + +} diff --git a/1.20/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java b/1.20/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java new file mode 100644 index 0000000..09bb181 --- /dev/null +++ b/1.20/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java @@ -0,0 +1,24 @@ +package com.hypherionmc.craterlib.compat; + +import com.hypherionmc.craterlib.api.events.server.CraterPlayerEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import redstonedubstep.mods.vanishmod.api.PlayerVanishEvent; + +public class Vanish { + + public Vanish() { + + } + + @SubscribeEvent + public void vanishevent(PlayerVanishEvent event) { + if (event.isVanished()) { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerEvent.PlayerLoggedOut(BridgedPlayer.of(event.getEntity()))); + } else { + CraterEventBus.INSTANCE.postEvent(new CraterPlayerEvent.PlayerLoggedIn(BridgedPlayer.of(event.getEntity()))); + } + } + +} \ No newline at end of file diff --git a/1.20/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ConfigScreenHandlerMixin.java b/1.20/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ConfigScreenHandlerMixin.java new file mode 100644 index 0000000..927bd23 --- /dev/null +++ b/1.20/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ConfigScreenHandlerMixin.java @@ -0,0 +1,43 @@ +package com.hypherionmc.craterlib.mixin; + +import com.hypherionmc.craterlib.client.gui.config.CraterConfigScreen; +import com.hypherionmc.craterlib.core.config.ConfigController; +import com.hypherionmc.craterlib.core.config.ModuleConfig; +import com.hypherionmc.craterlib.core.config.annotations.NoConfigScreen; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.Screen; +import net.minecraftforge.client.ConfigScreenHandler; +import net.minecraftforge.forgespi.language.IModInfo; +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.CallbackInfoReturnable; + +import java.util.Optional; +import java.util.function.BiFunction; + +/** + * @author HypherionSA + */ +@Mixin(ConfigScreenHandler.class) +public class ConfigScreenHandlerMixin { + + /** + * Inject Auto Generated config Screens into forge + * + */ + @Inject(at = @At("RETURN"), method = "getScreenFactoryFor", cancellable = true, remap = false) + private static void injectConfigScreen(IModInfo selectedMod, CallbackInfoReturnable>> cir) { + ConfigController.getMonitoredConfigs().forEach((conf, watcher) -> { + if (!conf.getClass().isAnnotationPresent(NoConfigScreen.class)) { + ModuleConfig config = (ModuleConfig) conf; + if (config.getModId().equals(selectedMod.getModId())) { + cir.setReturnValue( + Optional.of((minecraft, screen) -> new CraterConfigScreen(config, screen)) + ); + } + } + }); + } + +} diff --git a/1.20/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java b/1.20/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java new file mode 100644 index 0000000..2b79896 --- /dev/null +++ b/1.20/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java @@ -0,0 +1,36 @@ +package com.hypherionmc.craterlib.mixin; + +import com.hypherionmc.craterlib.api.events.server.CraterServerChatEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import com.hypherionmc.craterlib.utils.ChatUtils; +import net.minecraft.network.chat.PlayerChatMessage; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.concurrent.CompletableFuture; + +@Mixin(value = ServerGamePacketListenerImpl.class, priority = Integer.MIN_VALUE) +public class ServerGamePacketListenerImplMixin { + + @Shadow + public ServerPlayer player; + + @Inject( + method = "lambda$handleChat$8", + at = @At("HEAD"), + cancellable = true + ) + private void injectChatEvent(CompletableFuture completablefuture1, PlayerChatMessage arg, CompletableFuture completablefuture, Void p_248218_, CallbackInfo ci) { + CraterServerChatEvent event = new CraterServerChatEvent(BridgedPlayer.of(this.player), arg.decoratedContent().getString(), ChatUtils.mojangToAdventure(arg.decoratedContent())); + CraterEventBus.INSTANCE.postEvent(event); + if (event.wasCancelled()) + ci.cancel(); + } + +} diff --git a/1.20/Forge/src/main/java/com/hypherionmc/craterlib/network/CraterForgeNetworkHandler.java b/1.20/Forge/src/main/java/com/hypherionmc/craterlib/network/CraterForgeNetworkHandler.java new file mode 100644 index 0000000..5b54d5d --- /dev/null +++ b/1.20/Forge/src/main/java/com/hypherionmc/craterlib/network/CraterForgeNetworkHandler.java @@ -0,0 +1,100 @@ +package com.hypherionmc.craterlib.network; + +import com.hypherionmc.craterlib.CraterConstants; +import com.hypherionmc.craterlib.core.networking.PacketRegistry; +import com.hypherionmc.craterlib.core.networking.data.PacketContext; +import com.hypherionmc.craterlib.core.networking.data.PacketHolder; +import com.hypherionmc.craterlib.core.networking.data.PacketSide; +import com.hypherionmc.craterlib.nojang.network.BridgedFriendlyByteBuf; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraft.client.Minecraft; +import net.minecraft.network.Connection; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraftforge.network.NetworkDirection; +import net.minecraftforge.network.NetworkEvent; +import net.minecraftforge.network.NetworkRegistry; +import net.minecraftforge.network.simple.SimpleChannel; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * Based on https://github.com/mysticdrew/common-networking/tree/1.20.4 + */ +public class CraterForgeNetworkHandler extends PacketRegistry { + private final Map, SimpleChannel> CHANNELS = new HashMap<>(); + + public CraterForgeNetworkHandler(PacketSide side) { + super(side); + } + + protected void registerPacket(PacketHolder holder) { + if (CHANNELS.get(holder.messageType()) == null) { + SimpleChannel channel = NetworkRegistry.ChannelBuilder + .named(holder.type().toMojang()) + .clientAcceptedVersions((a) -> true) + .serverAcceptedVersions((a) -> true) + .networkProtocolVersion(() -> "1") + .simpleChannel(); + + channel.registerMessage( + 0, + holder.messageType(), + mojangEncoder(holder.encoder()), + mojangDecoder(holder.decoder()), + buildHandler(holder.handler())); + + CHANNELS.put(holder.messageType(), channel); + } else { + CraterConstants.LOG.error("Trying to register duplicate packet for type {}", holder.messageType()); + } + } + + public void sendToServer(T packet) { + this.sendToServer(packet, false); + } + + public void sendToServer(T packet, boolean ignoreCheck) { + SimpleChannel channel = CHANNELS.get(packet.getClass()); + Connection connection = Minecraft.getInstance().getConnection().getConnection(); + if (channel.isRemotePresent(connection) || ignoreCheck) { + channel.sendToServer(packet); + } + } + + public void sendToClient(T packet, BridgedPlayer player) { + SimpleChannel channel = CHANNELS.get(packet.getClass()); + ServerGamePacketListenerImpl connection = player.getConnection(); + if (connection == null) + return; + + if (channel.isRemotePresent(connection.connection)) { + channel.sendTo(packet, player.getConnection().connection, NetworkDirection.PLAY_TO_CLIENT); + } + } + + private Function mojangDecoder(Function handler) { + return byteBuf -> handler.apply(BridgedFriendlyByteBuf.of(byteBuf)); + } + + private BiConsumer mojangEncoder(BiConsumer handler) { + return ((t, byteBuf) -> handler.accept(t, BridgedFriendlyByteBuf.of(byteBuf))); + } + + private BiConsumer> buildHandler(Consumer> handler) { + return (message, ctx) -> { + ctx.get().enqueueWork(() -> { + PacketSide side = ctx.get().getDirection().getReceptionSide().isServer() ? PacketSide.SERVER : PacketSide.CLIENT; + ServerPlayer player = ctx.get().getSender(); + handler.accept(new PacketContext<>(BridgedPlayer.of(player), message, side)); + }); + ctx.get().setPacketHandled(true); + }; + } +} diff --git a/1.20/Forge/src/main/resources/META-INF/mods.toml b/1.20/Forge/src/main/resources/META-INF/mods.toml new file mode 100644 index 0000000..764aceb --- /dev/null +++ b/1.20/Forge/src/main/resources/META-INF/mods.toml @@ -0,0 +1,31 @@ +modLoader = "javafml" +loaderVersion = "[46,)" +license = "MIT" +issueTrackerURL = "https://github.com/firstdarkdev/craterLib/issues" + +[[mods]] + modId = "${mod_id}" + version = "${version}" + displayName = "${mod_name}" + displayURL = "https://modrinth.com/mod/craterlib" + logoFile = "craterlib_logo.png" + #credits="Thanks for this example mod goes to Java" + authors = "${mod_author}, Zenith" + description = ''' + A library mod used by First Dark Development and HypherionSA Mods + ''' + displayTest = "NONE" + +[[dependencies.${ mod_id }]] + modId = "forge" + mandatory = true + versionRange = "[46,)" + ordering = "NONE" + side = "BOTH" + +[[dependencies.${ mod_id }]] + modId = "minecraft" + mandatory = true + versionRange = "[1.20,1.20.2)" + ordering = "NONE" + side = "BOTH" diff --git a/1.20/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform b/1.20/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform new file mode 100644 index 0000000..a12ad8c --- /dev/null +++ b/1.20/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ClientPlatform @@ -0,0 +1 @@ +com.hypherionmc.craterlib.client.ForgeClientHelper \ No newline at end of file diff --git a/1.20/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform b/1.20/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform new file mode 100644 index 0000000..09e119f --- /dev/null +++ b/1.20/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CommonPlatform @@ -0,0 +1 @@ +com.hypherionmc.craterlib.common.ForgeCommonHelper \ No newline at end of file diff --git a/1.20/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils b/1.20/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils new file mode 100644 index 0000000..a9f823d --- /dev/null +++ b/1.20/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.CompatUtils @@ -0,0 +1 @@ +com.hypherionmc.craterlib.common.ForgeCompatHelper \ No newline at end of file diff --git a/1.20/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment b/1.20/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment new file mode 100644 index 0000000..02b4e07 --- /dev/null +++ b/1.20/Forge/src/main/resources/META-INF/services/com.hypherionmc.craterlib.core.platform.ModloaderEnvironment @@ -0,0 +1 @@ +com.hypherionmc.craterlib.common.ForgeLoaderHelper \ No newline at end of file diff --git a/1.20/Forge/src/main/resources/craterlib.forge.mixins.json b/1.20/Forge/src/main/resources/craterlib.forge.mixins.json new file mode 100644 index 0000000..aa072d1 --- /dev/null +++ b/1.20/Forge/src/main/resources/craterlib.forge.mixins.json @@ -0,0 +1,17 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "com.hypherionmc.craterlib.mixin", + "compatibilityLevel": "JAVA_17", + "mixins": [ + ], + "client": [ + "ConfigScreenHandlerMixin" + ], + "server": [ + "ServerGamePacketListenerImplMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/1.20/Forge/src/main/resources/craterlib_logo.png b/1.20/Forge/src/main/resources/craterlib_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..ce0159cc4aa4d823ea354042e531b4522d6dfead GIT binary patch literal 49343 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 z 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 De~rmR literal 0 HcmV?d00001 diff --git a/1.20/LICENSE b/1.20/LICENSE new file mode 100644 index 0000000..f4a1e2d --- /dev/null +++ b/1.20/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) HypherionSA and Contributors 2024 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/1.20/NeoForge/src/main/resources/craterlib_logo.png b/1.20/NeoForge/src/main/resources/craterlib_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..ce0159cc4aa4d823ea354042e531b4522d6dfead GIT binary patch literal 49343 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 z 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 De~rmR literal 0 HcmV?d00001 diff --git a/1.20/README.md b/1.20/README.md new file mode 100644 index 0000000..49c0699 --- /dev/null +++ b/1.20/README.md @@ -0,0 +1,55 @@ +# CraterLib + +![badge-snapshot](https://maven.firstdarkdev.xyz/api/badge/latest/snapshots/me/hypherionmc/craterlib/CraterLib-common-1.20-pre6?color=40c14a&name=CraterLib-Snapshot) + +*** + +A Library mod and modding api for easier multi-version minecraft and mod loader development + +*** + +### Supported Minecraft Versions + +| Minecraft Version | Support Status | +|-------------------| -------------- | +| < 1.18.2 | ❌ | +| 1.18.2-1.20.2 | ✳️ | +| 1.20.4 | ✳️ | +| 1.21 | 🚧 | + +- ❌ - Not Supported; no bug fixes or new features. +- 🚧 - Work in Progress; not ready for release. +- ✳️ - Long Term Support; receives changes through backports only. +- ✅ - In Support; the active version, receiving all bugfixes and features directly. + +*** + +## Library Features + +* Universal Config System (TOML Based) +* Built in Helper Classes for Various minecraft features +* Built in Optifine-Compat utilities +* Various utilities for Blockstates, LANG, Math and Rendering +* Cross Mod-Loader Events - Based on [Acara](https://github.com/Keksuccino/acara) +* Cross Mod-Loader Config Screens (Based on [Cloth Config Lite](https://github.com/shedaniel/cloth-config-lite)) +* Automatic ModMenu and Forge Config screen registration +* Built in Cross Mod-Loader Network system +* Nojang Modding API + +*** + +## Setup Instructions + +There's a **wiki coming soon**, but for now, here's some basic instructions for building the project: + +1. `git clone` the project to a safe spot. +2. Install Java's JDK 17. Make sure you have the development version explicitly: + * Fedora: `sudo dnf install java-17-openjdk-devel` + * Ubuntu: `sudo apt install openjdk-17-jdk` + * macOS: `brew install openjdk@17` +3. Set it accordingly: + * Windows/macOS: Set the `JAVA_HOME` environment variable or use system settings + * Linux: `sudo update-alternatives --config java` +4. Navigate to the CraterLib folder, then run a `gradlew` file depending on your operating system: + * Windows: `.\gradlew.bat build` + * macOS/Linux/BSD: `chmod +x gradlew` and `./gradlew` diff --git a/1.20/build.gradle b/1.20/build.gradle new file mode 100644 index 0000000..b21d3d5 --- /dev/null +++ b/1.20/build.gradle @@ -0,0 +1,109 @@ +plugins { + id 'java' + id 'com.github.johnrengelman.shadow' version '8.1.1' apply false + id "xyz.wagyourtail.unimined" version "1.2.4" apply false + id "com.hypherionmc.modutils.modpublisher" version "2.1.+" + id "com.hypherionmc.modutils.orion" version "1.0.+" + id 'maven-publish' +} + +orion.setup { + multiProject = true + enableMirrorMaven = true + enableReleasesMaven = true + dopplerToken = System.getenv("DOPPLER_KEY") + + versioning { + var relType = project.properties["releaseType"] ?: "${release_type}" + identifier("${relType}") + } +} + +group = project_group + +subprojects { + apply plugin: "xyz.wagyourtail.unimined" + apply plugin: "java" + apply plugin: 'maven-publish' + apply plugin: 'com.github.johnrengelman.shadow' + apply plugin: 'com.hypherionmc.modutils.modpublisher' + + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + + group = rootProject.group + + repositories { + mavenCentral() + + maven { + name = "Modrinth" + url = "https://api.modrinth.com/maven" + content { + includeGroup "maven.modrinth" + } + } + } + + configurations { + shade + modCompileOnly + implementation.extendsFrom shade + compileOnly.extendsFrom modCompileOnly + } + + dependencies { + // All Projects + shade "me.hypherionmc.moon-config:core:${moon_config}" + shade "me.hypherionmc.moon-config:toml:${moon_config}" + shade "com.hypherionmc:rpcsdk:${rpc_sdk}" + shade "me.hypherionmc.sdlink:mcdiscordformatter-1.19.1:${discord_formatter}" + shade "net.kyori:adventure-api:${adventure}" + shade "net.kyori:adventure-text-serializer-gson:${adventure}" + + compileOnly("org.projectlombok:lombok:${lombok}") + annotationProcessor("org.projectlombok:lombok:${lombok}") + } + + jar { + manifest { + attributes([ + 'Specification-Title' : mod_name, + 'Specification-Vendor' : mod_author, + 'Specification-Version' : project.jar.archiveVersion, + 'Implementation-Title' : project.name, + 'Implementation-Version' : project.jar.archiveVersion, + 'Implementation-Vendor' : mod_author, + 'Implementation-Timestamp': new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"), + 'Timestamp' : System.currentTimeMillis(), + 'Built-On-Java' : "${System.getProperty('java.vm.version')} (${System.getProperty('java.vm.vendor')})", + 'Built-On-Minecraft' : minecraft_version + ]) + } + } + +/** + * =============================================================================== + * = DO NOT EDIT BELOW THIS LINE UNLESS YOU KNOW WHAT YOU ARE DOING = + * =============================================================================== + */ + unimined.minecraft(sourceSets.main, true) { + version minecraft_version + + mappings { + mojmap() + devNamespace "mojmap" + } + } + + tasks.withType(JavaCompile).configureEach { + it.options.encoding = 'UTF-8' + it.options.release = 17 + } + + tasks.withType(GenerateModuleMetadata).configureEach { + enabled = false + } +} + +// TODO MODULE JARS diff --git a/1.20/gradle.properties b/1.20/gradle.properties new file mode 100644 index 0000000..af67ed0 --- /dev/null +++ b/1.20/gradle.properties @@ -0,0 +1,42 @@ +#Project +version_major=2 +version_minor=0 +version_patch=0 + +#Mod +mod_author=HypherionSA +mod_id=craterlib +mod_name=CraterLib + +# Shared +minecraft_version=1.20 +project_group=com.hypherionmc.craterlib + +# Fabric +fabric_loader=0.15.11 +fabric_api=0.83.0+1.20 + +# Forge +forge_version=46.0.14 + +# Dependencies +moon_config=1.0.9 +lombok=1.18.32 +adventure=4.16.0 +rpc_sdk=1.0 +discord_formatter=2.0.0 + +# Mod Dependencies +fabrictailor=2.2.1 +vanish=1.5.4+1.20.1 +mod_menu_version=7.0.1 +vanishmod=1.1.11 + +# Publishing +curse_id=867099 +modrinth_id=Nn8Wasaq +release_type=release + +# Gradle +org.gradle.jvmargs=-Xmx3G +org.gradle.daemon=false diff --git a/1.20/gradle/wrapper/gradle-wrapper.jar b/1.20/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..7454180f2ae8848c63b8b4dea2cb829da983f2fa GIT binary patch literal 59536 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 QuEJmfE|#`Dqx|px0L@K;Y5)KL literal 0 HcmV?d00001 diff --git a/1.20/gradle/wrapper/gradle-wrapper.properties b/1.20/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..48c0a02 --- /dev/null +++ b/1.20/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/1.20/gradlew b/1.20/gradlew new file mode 100644 index 0000000..b4f908a --- /dev/null +++ b/1.20/gradlew @@ -0,0 +1,183 @@ +#!/usr/bin/env bash + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MSYS* | MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +ARGV=("$@") +eval set -- $DEFAULT_JVM_OPTS + +IFS=$' +' read -rd '' -a JAVA_OPTS_ARR <<< "$(echo $JAVA_OPTS | xargs -n1)" +IFS=$' +' read -rd '' -a GRADLE_OPTS_ARR <<< "$(echo $GRADLE_OPTS | xargs -n1)" + +exec "$JAVACMD" "$@" "${JAVA_OPTS_ARR[@]}" "${GRADLE_OPTS_ARR[@]}" "-Dorg.gradle.appname=$APP_BASE_NAME" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "${ARGV[@]}" diff --git a/1.20/gradlew.bat b/1.20/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/1.20/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/1.20/settings.gradle b/1.20/settings.gradle new file mode 100644 index 0000000..34c54f3 --- /dev/null +++ b/1.20/settings.gradle @@ -0,0 +1,14 @@ +pluginManagement { + repositories { + gradlePluginPortal() + maven { + url "https://mcentral.firstdark.dev/releases" + } + maven { + url "https://maven.firstdark.dev/releases" + } + } +} + +rootProject.name = 'CraterLib-1.20' +include("Common", "Fabric", "Forge") diff --git a/README.md b/README.md index 817be6c..136bc73 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # CraterLib -This is a branch for porting automation. Commits and Pull Requests should NEVER target this branch directly +This is a branch for all supported LTS versions. Please do NOT directly submit changes and PR's here. Submit them to the DEV branch instead ### Supported Minecraft Versions diff --git a/build.gradle b/build.gradle index 0267cf0..c04858f 100644 --- a/build.gradle +++ b/build.gradle @@ -1,9 +1,13 @@ +import codechicken.diffpatch.util.PatchMode + plugins { id 'java' - id "com.hypherionmc.modutils.orion" version "1.0.17" + id "com.hypherionmc.modutils.orion.porting" version "1.0.18" } orionporting { upstreamBranch = "dev" + // Enable During Porting + //patchMode = PatchMode.FUZZY porting("1.20.4", "1.20.2", "1.20", "1.19.3", "1.19.2", "1.18.2") } \ No newline at end of file diff --git a/commit.sha b/commit.sha index 35b3f69..0404471 100644 --- a/commit.sha +++ b/commit.sha @@ -1 +1 @@ -648441ec78fbc0cfadcd398a85f54f2723f4a081 \ No newline at end of file +fabef0e6f111bc4620334afb1da820a90a12b8da \ No newline at end of file diff --git a/gradle.properties b/gradle.properties deleted file mode 100644 index 1866634..0000000 --- a/gradle.properties +++ /dev/null @@ -1,3 +0,0 @@ -# Gradle -org.gradle.jvmargs=-Xmx3G -org.gradle.daemon=false \ No newline at end of file diff --git a/orbit.json b/orbit.json deleted file mode 100644 index 50c7925..0000000 --- a/orbit.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "action": "split", - "targetBranches": ["1.20.4", "1.20.2", "1.20", "1.19.3", "1.19.2", "1.18.2"] -} \ No newline at end of file diff --git a/patches/1.18.2/.jenkins/Jenkinsfile.deploy.patch b/patches/1.18.2/.jenkins/Jenkinsfile.deploy.patch index 52d9953..6fbfdf9 100644 --- a/patches/1.18.2/.jenkins/Jenkinsfile.deploy.patch +++ b/patches/1.18.2/.jenkins/Jenkinsfile.deploy.patch @@ -1,15 +1,12 @@ --- a/.jenkins/Jenkinsfile.deploy +++ b/.jenkins/Jenkinsfile.deploy -@@ -1,7 +1,7 @@ +@@ -1,4 +1,4 @@ +-def JDK = "21" ++def JDK = "17" + pipeline { agent { - docker { -- image "registry.firstdark.dev/java21:latest" -+ image "registry.firstdark.dev/java17:latest" - alwaysPull true - args '-v gradle-cache:/home/gradle/.gradle' - } -@@ -15,7 +15,7 @@ +@@ -17,7 +17,7 @@ stage("Notify Discord") { steps { discordSend webhookURL: env.FDD_WH_ADMIN, @@ -18,12 +15,3 @@ link: env.BUILD_URL, result: 'SUCCESS', description: "Build: [${BUILD_NUMBER}](${env.BUILD_URL})" -@@ -44,7 +44,7 @@ - deleteDir() - - discordSend webhookURL: env.FDD_WH_ADMIN, -- title: "CraterLib Port Deploy #${BUILD_NUMBER}", -+ title: "CraterLib 1.18.2 Deploy #${BUILD_NUMBER}", - link: env.BUILD_URL, - result: currentBuild.currentResult, - description: "Build: [${BUILD_NUMBER}](${env.BUILD_URL})\nStatus: ${currentBuild.currentResult}" diff --git a/patches/1.18.2/.jenkins/Jenkinsfile.snapshot.patch b/patches/1.18.2/.jenkins/Jenkinsfile.snapshot.patch index 81daeba..c32ef6d 100644 --- a/patches/1.18.2/.jenkins/Jenkinsfile.snapshot.patch +++ b/patches/1.18.2/.jenkins/Jenkinsfile.snapshot.patch @@ -1,52 +1,18 @@ --- a/.jenkins/Jenkinsfile.snapshot +++ b/.jenkins/Jenkinsfile.snapshot -@@ -1,10 +1,11 @@ +@@ -1,10 +1,10 @@ def projectName = "CraterLib"; def projectIcon = "https://cdn.modrinth.com/data/Nn8Wasaq/a172c634683a11a2e9ae593e56eba7885743bb44.png"; -+def relType = "snapshot" +-def JDK = "21"; +-def majorMc = "Port"; +-def modLoaders = "neoforge|fabric|quilt"; +-def supportedMc = "1.21"; +-def reltype = "port"; ++def JDK = "17"; ++def majorMc = "1.18.2"; ++def modLoaders = "forge|fabric|quilt"; ++def supportedMc = "1.18.2"; ++def reltype = "snapshot"; pipeline { agent { - docker { -- image "registry.firstdark.dev/java21:latest" -+ image "registry.firstdark.dev/java17:latest" - alwaysPull true - args '-v gradle-cache:/home/gradle/.gradle' - } -@@ -18,7 +19,7 @@ - stage("Notify Discord") { - steps { - discordSend webhookURL: env.SSS_WEBHOOK, -- title: "Deploy Started: ${projectName} Port Deploy #${BUILD_NUMBER}", -+ title: "Deploy Started: ${projectName} 1.18.2 Deploy #${BUILD_NUMBER}", - link: env.BUILD_URL, - result: 'SUCCESS', - description: "Build: [${BUILD_NUMBER}](${env.BUILD_URL})" -@@ -34,14 +35,14 @@ - - stage("Build") { - steps { -- sh "./gradlew build -PreleaseType=port" -+ sh "./gradlew build -PreleaseType=${relType}" - } - } - - stage("Publish to Maven") { - steps { - catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') { -- sh "./gradlew publish -PreleaseType=port" -+ sh "./gradlew publish -PreleaseType=${relType}" - } - } - } -@@ -56,8 +57,8 @@ - projectSlug: "craterlib", - projectName: "${projectName}", - projectIcon: "${projectIcon}", -- modLoaders: "neoforge|fabric|quilt", -- minecraftVersions: "1.21", -+ modLoaders: "forge|fabric|quilt", -+ minecraftVersions: "1.18.2", - failWebhook: env.SSS_WEBHOOK, - publishWebhooks: "${env.SSS_WEBHOOK}|${env.FDD_WH}" - diff --git a/patches/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerListMixin.java.patch b/patches/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerListMixin.java.patch index ad11c3b..87a36fe 100644 --- a/patches/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerListMixin.java.patch +++ b/patches/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/mixin/events/PlayerListMixin.java.patch @@ -11,12 +11,12 @@ import net.minecraft.server.players.PlayerList; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; -@@ -20,20 +20,21 @@ +@@ -20,20 +20,20 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import java.net.SocketAddress; +-import java.util.function.Function; +import java.util.UUID; - import java.util.function.Function; @Mixin(PlayerList.class) public class PlayerListMixin { diff --git a/patches/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java.patch b/patches/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java.patch index 60266ab..abfedb3 100644 --- a/patches/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java.patch +++ b/patches/1.18.2/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java.patch @@ -1,43 +1,34 @@ --- a/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java +++ b/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java -@@ -5,22 +5,31 @@ - import me.hypherionmc.mcdiscordformatter.minecraft.MinecraftSerializer; - import net.kyori.adventure.text.format.NamedTextColor; - import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; -+import net.kyori.adventure.text.serializer.json.JSONOptions; -+import net.kyori.option.OptionState; +@@ -9,11 +9,10 @@ import net.minecraft.ChatFormatting; + import net.minecraft.SharedConstants; import net.minecraft.Util; -import net.minecraft.core.RegistryAccess; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Style; +- +-import java.util.function.Consumer; +import net.minecraft.network.chat.TextComponent; +import net.minecraft.network.chat.TranslatableComponent; public class ChatUtils { -+ private static final GsonComponentSerializer adventureSerializer = GsonComponentSerializer.builder().options( -+ OptionState.optionState() -+ .value(JSONOptions.EMIT_HOVER_SHOW_ENTITY_ID_AS_INT_ARRAY, false) -+ .value(JSONOptions.EMIT_HOVER_EVENT_TYPE, JSONOptions.HoverEventValueMode.MODERN_ONLY).build() -+ ).build(); -+ +@@ -23,11 +22,11 @@ + public static Component adventureToMojang(net.kyori.adventure.text.Component inComponent) { -- final String serialised = GsonComponentSerializer.gson().serialize(inComponent); + final String serialised = adventureSerializer.serialize(inComponent); - return Component.Serializer.fromJson(serialised, RegistryAccess.EMPTY); -+ final String serialised = adventureSerializer.serialize(inComponent); + return Component.Serializer.fromJson(serialised); } public static net.kyori.adventure.text.Component mojangToAdventure(Component inComponent) { - final String serialised = Component.Serializer.toJson(inComponent, RegistryAccess.EMPTY); -- return GsonComponentSerializer.gson().deserialize(serialised); + final String serialised = Component.Serializer.toJson(inComponent); -+ return adventureSerializer.deserialize(serialised); + return adventureSerializer.deserialize(serialised); } - // Some text components contain duplicate text, resulting in duplicate messages -@@ -28,7 +37,7 @@ +@@ -36,7 +35,7 @@ public static Component safeCopy(Component inComponent) { String value = inComponent.getString(); Style style = inComponent.getStyle(); @@ -46,7 +37,7 @@ } public static String strip(String inString, String... toStrip) { -@@ -57,7 +66,7 @@ +@@ -65,7 +64,7 @@ } public static net.kyori.adventure.text.Component resolve(String component, boolean formatted) { @@ -55,11 +46,12 @@ if (formatted) { returnVal = MinecraftSerializer.INSTANCE.serialize(component); } -@@ -85,6 +94,6 @@ +@@ -93,7 +92,7 @@ if (identifier == null) return net.kyori.adventure.text.Component.text("Unknown"); - return mojangToAdventure(Component.translatable(Util.makeDescriptionId("biome", identifier.toMojang()))); + return mojangToAdventure(new TranslatableComponent(Util.makeDescriptionId("biome", identifier.toMojang()))); } + } diff --git a/patches/1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java.patch b/patches/1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java.patch index dbe9cc7..32e58e0 100644 --- a/patches/1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java.patch +++ b/patches/1.18.2/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java.patch @@ -1,6 +1,6 @@ --- a/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java +++ b/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java -@@ -1,46 +1,57 @@ +@@ -1,46 +1,55 @@ package com.hypherionmc.craterlib.network; -import com.hypherionmc.craterlib.api.networking.CommonPacketWrapper; @@ -17,8 +17,6 @@ +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.minecraft.network.FriendlyByteBuf; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.server.level.ServerPlayer; + +import java.util.HashMap; +import java.util.Map; @@ -81,7 +79,7 @@ } public void sendToServer(T packet) { -@@ -48,21 +59,25 @@ +@@ -48,21 +57,25 @@ } public void sendToServer(T packet, boolean ignoreCheck) { diff --git a/patches/1.18.2/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java.patch b/patches/1.18.2/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java.patch index e07211e..fd38e13 100644 --- a/patches/1.18.2/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java.patch +++ b/patches/1.18.2/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java.patch @@ -1,13 +1,13 @@ --- a/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java +++ b/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java -@@ -3,6 +3,7 @@ +@@ -2,6 +2,7 @@ + import com.hypherionmc.craterlib.api.events.client.LateInitEvent; import com.hypherionmc.craterlib.common.ForgeServerEvents; - import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.compat.Vanish; + import com.hypherionmc.craterlib.core.event.CraterEventBus; import com.hypherionmc.craterlib.core.networking.CraterPacketNetwork; import com.hypherionmc.craterlib.core.networking.data.PacketSide; - import com.hypherionmc.craterlib.core.platform.ModloaderEnvironment; @@ -32,5 +33,9 @@ LateInitEvent event = new LateInitEvent(new BridgedMinecraft(), BridgedOptions.of(Minecraft.getInstance().options)); CraterEventBus.INSTANCE.postEvent(event); diff --git a/patches/1.18.2/README.md.patch b/patches/1.18.2/README.md.patch index e51d5b3..5fd4d92 100644 --- a/patches/1.18.2/README.md.patch +++ b/patches/1.18.2/README.md.patch @@ -1,10 +1,11 @@ --- a/README.md +++ b/README.md -@@ -15,7 +15,7 @@ +@@ -15,8 +15,7 @@ | < 1.18.2 | ❌ | | 1.18.2-1.20.2 | ✳️ | | 1.20.4 | ✳️ | --| 1.20.6 | 🚧 | +-| 1.20.6 | ❌ | +-| 1.21 | ✳️ | +| 1.21 | 🚧 | - ❌ - Not Supported; no bug fixes or new features. diff --git a/patches/1.18.2/build.gradle.patch b/patches/1.18.2/build.gradle.patch new file mode 100644 index 0000000..cc72c88 --- /dev/null +++ b/patches/1.18.2/build.gradle.patch @@ -0,0 +1,30 @@ +--- a/build.gradle ++++ b/build.gradle +@@ -11,7 +11,6 @@ + multiProject = true + enableMirrorMaven = true + enableReleasesMaven = true +- enableSnapshotsMaven = true + + dopplerToken = System.getenv("DOPPLER_KEY") + +@@ -30,8 +29,8 @@ + apply plugin: 'com.github.johnrengelman.shadow' + apply plugin: 'com.hypherionmc.modutils.modpublisher' + +- sourceCompatibility = JavaVersion.VERSION_21 +- targetCompatibility = JavaVersion.VERSION_21 ++ sourceCompatibility = JavaVersion.VERSION_17 ++ targetCompatibility = JavaVersion.VERSION_17 + + group = rootProject.group + +@@ -59,7 +58,7 @@ + shade "me.hypherionmc.moon-config:core:${moon_config}" + shade "me.hypherionmc.moon-config:toml:${moon_config}" + shade "com.hypherionmc:rpcsdk:${rpc_sdk}" +- shade "me.hypherionmc.sdlink:mcdiscordformatter-1.20.3:${discord_formatter}" ++ shade "me.hypherionmc.sdlink:mcdiscordformatter-1.18.1:${discord_formatter}" + shade "net.kyori:adventure-api:${adventure}" + shade "net.kyori:adventure-text-serializer-gson:${adventure}" + diff --git a/patches/1.18.2/orbit.json.patch b/patches/1.18.2/orbit.json.patch deleted file mode 100644 index 4f0263d..0000000 --- a/patches/1.18.2/orbit.json.patch +++ /dev/null @@ -1,7 +0,0 @@ ---- a/orbit.json -+++ /dev/null -@@ -1,4 +1,0 @@ --{ -- "action": "port", -- "devBranch": "porting" --} diff --git a/patches/1.18.2/settings.gradle.patch b/patches/1.18.2/settings.gradle.patch index b23adaa..6affdd1 100644 --- a/patches/1.18.2/settings.gradle.patch +++ b/patches/1.18.2/settings.gradle.patch @@ -1,8 +1,10 @@ --- a/settings.gradle +++ b/settings.gradle -@@ -11,4 +11,4 @@ +@@ -10,5 +10,5 @@ + } } - rootProject.name = 'CraterLib' +-rootProject.name = 'CraterLib' -include("Common", "Fabric", "NeoForge") ++rootProject.name = 'CraterLib-1.18.2' +include("Common", "Fabric", "Forge") diff --git a/patches/1.19.2/.jenkins/Jenkinsfile.deploy.patch b/patches/1.19.2/.jenkins/Jenkinsfile.deploy.patch index 2090d87..c06f715 100644 --- a/patches/1.19.2/.jenkins/Jenkinsfile.deploy.patch +++ b/patches/1.19.2/.jenkins/Jenkinsfile.deploy.patch @@ -1,15 +1,12 @@ --- a/.jenkins/Jenkinsfile.deploy +++ b/.jenkins/Jenkinsfile.deploy -@@ -1,7 +1,7 @@ +@@ -1,4 +1,4 @@ +-def JDK = "21" ++def JDK = "17" + pipeline { agent { - docker { -- image "registry.firstdark.dev/java21:latest" -+ image "registry.firstdark.dev/java17:latest" - alwaysPull true - args '-v gradle-cache:/home/gradle/.gradle' - } -@@ -15,7 +15,7 @@ +@@ -17,7 +17,7 @@ stage("Notify Discord") { steps { discordSend webhookURL: env.FDD_WH_ADMIN, @@ -18,12 +15,3 @@ link: env.BUILD_URL, result: 'SUCCESS', description: "Build: [${BUILD_NUMBER}](${env.BUILD_URL})" -@@ -44,7 +44,7 @@ - deleteDir() - - discordSend webhookURL: env.FDD_WH_ADMIN, -- title: "CraterLib Port Deploy #${BUILD_NUMBER}", -+ title: "CraterLib 1.19.2 Deploy #${BUILD_NUMBER}", - link: env.BUILD_URL, - result: currentBuild.currentResult, - description: "Build: [${BUILD_NUMBER}](${env.BUILD_URL})\nStatus: ${currentBuild.currentResult}" diff --git a/patches/1.19.2/.jenkins/Jenkinsfile.snapshot.patch b/patches/1.19.2/.jenkins/Jenkinsfile.snapshot.patch index 258fa84..0d5b2ad 100644 --- a/patches/1.19.2/.jenkins/Jenkinsfile.snapshot.patch +++ b/patches/1.19.2/.jenkins/Jenkinsfile.snapshot.patch @@ -1,52 +1,18 @@ --- a/.jenkins/Jenkinsfile.snapshot +++ b/.jenkins/Jenkinsfile.snapshot -@@ -1,10 +1,11 @@ +@@ -1,10 +1,10 @@ def projectName = "CraterLib"; def projectIcon = "https://cdn.modrinth.com/data/Nn8Wasaq/a172c634683a11a2e9ae593e56eba7885743bb44.png"; -+def relType = "snapshot" +-def JDK = "21"; +-def majorMc = "Port"; +-def modLoaders = "neoforge|fabric|quilt"; +-def supportedMc = "1.21"; +-def reltype = "port"; ++def JDK = "17"; ++def majorMc = "1.19.2"; ++def modLoaders = "forge|fabric|quilt"; ++def supportedMc = "1.19.2"; ++def reltype = "snapshot"; pipeline { agent { - docker { -- image "registry.firstdark.dev/java21:latest" -+ image "registry.firstdark.dev/java17:latest" - alwaysPull true - args '-v gradle-cache:/home/gradle/.gradle' - } -@@ -18,7 +19,7 @@ - stage("Notify Discord") { - steps { - discordSend webhookURL: env.SSS_WEBHOOK, -- title: "Deploy Started: ${projectName} Port Deploy #${BUILD_NUMBER}", -+ title: "Deploy Started: ${projectName} 1.19.2 Deploy #${BUILD_NUMBER}", - link: env.BUILD_URL, - result: 'SUCCESS', - description: "Build: [${BUILD_NUMBER}](${env.BUILD_URL})" -@@ -34,14 +35,14 @@ - - stage("Build") { - steps { -- sh "./gradlew build -PreleaseType=port" -+ sh "./gradlew build -PreleaseType=${relType}" - } - } - - stage("Publish to Maven") { - steps { - catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') { -- sh "./gradlew publish -PreleaseType=port" -+ sh "./gradlew publish -PreleaseType=${relType}" - } - } - } -@@ -56,8 +57,8 @@ - projectSlug: "craterlib", - projectName: "${projectName}", - projectIcon: "${projectIcon}", -- modLoaders: "neoforge|fabric|quilt", -- minecraftVersions: "1.21", -+ modLoaders: "forge|fabric|quilt", -+ minecraftVersions: "1.19.2", - failWebhook: env.SSS_WEBHOOK, - publishWebhooks: "${env.SSS_WEBHOOK}|${env.FDD_WH}" - diff --git a/patches/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java.patch b/patches/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java.patch index b76cf82..3c0c085 100644 --- a/patches/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java.patch +++ b/patches/1.19.2/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java.patch @@ -1,37 +1,29 @@ --- a/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java +++ b/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java -@@ -5,22 +5,29 @@ - import me.hypherionmc.mcdiscordformatter.minecraft.MinecraftSerializer; - import net.kyori.adventure.text.format.NamedTextColor; - import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; -+import net.kyori.adventure.text.serializer.json.JSONOptions; -+import net.kyori.option.OptionState; +@@ -9,12 +9,9 @@ import net.minecraft.ChatFormatting; + import net.minecraft.SharedConstants; import net.minecraft.Util; -import net.minecraft.core.RegistryAccess; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Style; +-import java.util.function.Consumer; +- public class ChatUtils { -+ private static final GsonComponentSerializer adventureSerializer = GsonComponentSerializer.builder().options( -+ OptionState.optionState() -+ .value(JSONOptions.EMIT_HOVER_SHOW_ENTITY_ID_AS_INT_ARRAY, false) -+ .value(JSONOptions.EMIT_HOVER_EVENT_TYPE, JSONOptions.HoverEventValueMode.MODERN_ONLY).build() -+ ).build(); -+ + private static final GsonComponentSerializer adventureSerializer = GsonComponentSerializer.builder().options( +@@ -23,11 +20,11 @@ + public static Component adventureToMojang(net.kyori.adventure.text.Component inComponent) { -- final String serialised = GsonComponentSerializer.gson().serialize(inComponent); + final String serialised = adventureSerializer.serialize(inComponent); - return Component.Serializer.fromJson(serialised, RegistryAccess.EMPTY); -+ final String serialised = adventureSerializer.serialize(inComponent); + return Component.Serializer.fromJson(serialised); } public static net.kyori.adventure.text.Component mojangToAdventure(Component inComponent) { - final String serialised = Component.Serializer.toJson(inComponent, RegistryAccess.EMPTY); -- return GsonComponentSerializer.gson().deserialize(serialised); + final String serialised = Component.Serializer.toJson(inComponent); -+ return adventureSerializer.deserialize(serialised); + return adventureSerializer.deserialize(serialised); } - // Some text components contain duplicate text, resulting in duplicate messages diff --git a/patches/1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java.patch b/patches/1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java.patch index 20539d2..d79d961 100644 --- a/patches/1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java.patch +++ b/patches/1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java.patch @@ -1,16 +1,26 @@ --- a/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java +++ b/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java -@@ -14,6 +14,9 @@ +@@ -4,16 +4,16 @@ + import com.hypherionmc.craterlib.core.event.CraterEventBus; + import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; + import com.hypherionmc.craterlib.utils.ChatUtils; +-import net.minecraft.network.chat.Component; + import net.minecraft.network.chat.PlayerChatMessage; + import net.minecraft.server.level.ServerPlayer; +-import net.minecraft.server.network.FilteredText; + import net.minecraft.server.network.ServerGamePacketListenerImpl; + import org.spongepowered.asm.mixin.Mixin; + import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; - import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +-import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.concurrent.CompletableFuture; @Mixin(value = ServerGamePacketListenerImpl.class, priority = Integer.MIN_VALUE) public class ServerGamePacketListenerImplMixin { -@@ -22,12 +25,12 @@ +@@ -22,12 +22,12 @@ public ServerPlayer player; @Inject( diff --git a/patches/1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java.patch b/patches/1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java.patch index dbe9cc7..32e58e0 100644 --- a/patches/1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java.patch +++ b/patches/1.19.2/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java.patch @@ -1,6 +1,6 @@ --- a/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java +++ b/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java -@@ -1,46 +1,57 @@ +@@ -1,46 +1,55 @@ package com.hypherionmc.craterlib.network; -import com.hypherionmc.craterlib.api.networking.CommonPacketWrapper; @@ -17,8 +17,6 @@ +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.minecraft.network.FriendlyByteBuf; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.server.level.ServerPlayer; + +import java.util.HashMap; +import java.util.Map; @@ -81,7 +79,7 @@ } public void sendToServer(T packet) { -@@ -48,21 +59,25 @@ +@@ -48,21 +57,25 @@ } public void sendToServer(T packet, boolean ignoreCheck) { diff --git a/patches/1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java.patch b/patches/1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java.patch index e07211e..fd38e13 100644 --- a/patches/1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java.patch +++ b/patches/1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java.patch @@ -1,13 +1,13 @@ --- a/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java +++ b/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java -@@ -3,6 +3,7 @@ +@@ -2,6 +2,7 @@ + import com.hypherionmc.craterlib.api.events.client.LateInitEvent; import com.hypherionmc.craterlib.common.ForgeServerEvents; - import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.compat.Vanish; + import com.hypherionmc.craterlib.core.event.CraterEventBus; import com.hypherionmc.craterlib.core.networking.CraterPacketNetwork; import com.hypherionmc.craterlib.core.networking.data.PacketSide; - import com.hypherionmc.craterlib.core.platform.ModloaderEnvironment; @@ -32,5 +33,9 @@ LateInitEvent event = new LateInitEvent(new BridgedMinecraft(), BridgedOptions.of(Minecraft.getInstance().options)); CraterEventBus.INSTANCE.postEvent(event); diff --git a/patches/1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java.patch b/patches/1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java.patch index 39cc093..847d18e 100644 --- a/patches/1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java.patch +++ b/patches/1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java.patch @@ -1,12 +1,11 @@ --- /dev/null +++ b/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java -@@ -1,0 +1,25 @@ +@@ -1,0 +1,24 @@ +package com.hypherionmc.craterlib.compat; + +import com.hypherionmc.craterlib.api.events.server.CraterPlayerEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; -+import net.minecraft.world.entity.player.Player; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import redstonedubstep.mods.vanishmod.api.PlayerVanishEvent; + diff --git a/patches/1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java.patch b/patches/1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java.patch index a565efe..c24fbe2 100644 --- a/patches/1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java.patch +++ b/patches/1.19.2/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java.patch @@ -1,16 +1,26 @@ --- a/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java +++ b/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java -@@ -14,6 +14,9 @@ +@@ -4,16 +4,16 @@ + import com.hypherionmc.craterlib.core.event.CraterEventBus; + import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; + import com.hypherionmc.craterlib.utils.ChatUtils; +-import net.minecraft.network.chat.Component; + import net.minecraft.network.chat.PlayerChatMessage; + import net.minecraft.server.level.ServerPlayer; +-import net.minecraft.server.network.FilteredText; + import net.minecraft.server.network.ServerGamePacketListenerImpl; + import org.spongepowered.asm.mixin.Mixin; + import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; - import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +-import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.concurrent.CompletableFuture; @Mixin(value = ServerGamePacketListenerImpl.class, priority = Integer.MIN_VALUE) public class ServerGamePacketListenerImplMixin { -@@ -22,12 +25,12 @@ +@@ -22,12 +22,12 @@ public ServerPlayer player; @Inject( diff --git a/patches/1.19.2/README.md.patch b/patches/1.19.2/README.md.patch index e51d5b3..5fd4d92 100644 --- a/patches/1.19.2/README.md.patch +++ b/patches/1.19.2/README.md.patch @@ -1,10 +1,11 @@ --- a/README.md +++ b/README.md -@@ -15,7 +15,7 @@ +@@ -15,8 +15,7 @@ | < 1.18.2 | ❌ | | 1.18.2-1.20.2 | ✳️ | | 1.20.4 | ✳️ | --| 1.20.6 | 🚧 | +-| 1.20.6 | ❌ | +-| 1.21 | ✳️ | +| 1.21 | 🚧 | - ❌ - Not Supported; no bug fixes or new features. diff --git a/patches/1.19.2/build.gradle.patch b/patches/1.19.2/build.gradle.patch new file mode 100644 index 0000000..169744c --- /dev/null +++ b/patches/1.19.2/build.gradle.patch @@ -0,0 +1,30 @@ +--- a/build.gradle ++++ b/build.gradle +@@ -11,7 +11,6 @@ + multiProject = true + enableMirrorMaven = true + enableReleasesMaven = true +- enableSnapshotsMaven = true + + dopplerToken = System.getenv("DOPPLER_KEY") + +@@ -30,8 +29,8 @@ + apply plugin: 'com.github.johnrengelman.shadow' + apply plugin: 'com.hypherionmc.modutils.modpublisher' + +- sourceCompatibility = JavaVersion.VERSION_21 +- targetCompatibility = JavaVersion.VERSION_21 ++ sourceCompatibility = JavaVersion.VERSION_17 ++ targetCompatibility = JavaVersion.VERSION_17 + + group = rootProject.group + +@@ -59,7 +58,7 @@ + shade "me.hypherionmc.moon-config:core:${moon_config}" + shade "me.hypherionmc.moon-config:toml:${moon_config}" + shade "com.hypherionmc:rpcsdk:${rpc_sdk}" +- shade "me.hypherionmc.sdlink:mcdiscordformatter-1.20.3:${discord_formatter}" ++ shade "me.hypherionmc.sdlink:mcdiscordformatter-1.19.1:${discord_formatter}" + shade "net.kyori:adventure-api:${adventure}" + shade "net.kyori:adventure-text-serializer-gson:${adventure}" + diff --git a/patches/1.19.2/orbit.json.patch b/patches/1.19.2/orbit.json.patch deleted file mode 100644 index 4f0263d..0000000 --- a/patches/1.19.2/orbit.json.patch +++ /dev/null @@ -1,7 +0,0 @@ ---- a/orbit.json -+++ /dev/null -@@ -1,4 +1,0 @@ --{ -- "action": "port", -- "devBranch": "porting" --} diff --git a/patches/1.19.2/settings.gradle.patch b/patches/1.19.2/settings.gradle.patch index b23adaa..ed73f8a 100644 --- a/patches/1.19.2/settings.gradle.patch +++ b/patches/1.19.2/settings.gradle.patch @@ -1,8 +1,10 @@ --- a/settings.gradle +++ b/settings.gradle -@@ -11,4 +11,4 @@ +@@ -10,5 +10,5 @@ + } } - rootProject.name = 'CraterLib' +-rootProject.name = 'CraterLib' -include("Common", "Fabric", "NeoForge") ++rootProject.name = 'CraterLib-1.19.2' +include("Common", "Fabric", "Forge") diff --git a/patches/1.19.3/.jenkins/Jenkinsfile.deploy.patch b/patches/1.19.3/.jenkins/Jenkinsfile.deploy.patch index d630ab8..76fbd82 100644 --- a/patches/1.19.3/.jenkins/Jenkinsfile.deploy.patch +++ b/patches/1.19.3/.jenkins/Jenkinsfile.deploy.patch @@ -1,15 +1,12 @@ --- a/.jenkins/Jenkinsfile.deploy +++ b/.jenkins/Jenkinsfile.deploy -@@ -1,7 +1,7 @@ +@@ -1,4 +1,4 @@ +-def JDK = "21" ++def JDK = "17" + pipeline { agent { - docker { -- image "registry.firstdark.dev/java21:latest" -+ image "registry.firstdark.dev/java17:latest" - alwaysPull true - args '-v gradle-cache:/home/gradle/.gradle' - } -@@ -15,7 +15,7 @@ +@@ -17,7 +17,7 @@ stage("Notify Discord") { steps { discordSend webhookURL: env.FDD_WH_ADMIN, @@ -18,12 +15,3 @@ link: env.BUILD_URL, result: 'SUCCESS', description: "Build: [${BUILD_NUMBER}](${env.BUILD_URL})" -@@ -44,7 +44,7 @@ - deleteDir() - - discordSend webhookURL: env.FDD_WH_ADMIN, -- title: "CraterLib Port Deploy #${BUILD_NUMBER}", -+ title: "CraterLib 1.19.3/4 Deploy #${BUILD_NUMBER}", - link: env.BUILD_URL, - result: currentBuild.currentResult, - description: "Build: [${BUILD_NUMBER}](${env.BUILD_URL})\nStatus: ${currentBuild.currentResult}" diff --git a/patches/1.19.3/.jenkins/Jenkinsfile.snapshot.patch b/patches/1.19.3/.jenkins/Jenkinsfile.snapshot.patch index d1666a7..7356400 100644 --- a/patches/1.19.3/.jenkins/Jenkinsfile.snapshot.patch +++ b/patches/1.19.3/.jenkins/Jenkinsfile.snapshot.patch @@ -1,46 +1,18 @@ --- a/.jenkins/Jenkinsfile.snapshot +++ b/.jenkins/Jenkinsfile.snapshot -@@ -1,5 +1,6 @@ +@@ -1,10 +1,10 @@ def projectName = "CraterLib"; def projectIcon = "https://cdn.modrinth.com/data/Nn8Wasaq/a172c634683a11a2e9ae593e56eba7885743bb44.png"; -+def relType = "snapshot" +-def JDK = "21"; +-def majorMc = "Port"; +-def modLoaders = "neoforge|fabric|quilt"; +-def supportedMc = "1.21"; +-def reltype = "port"; ++def JDK = "17"; ++def majorMc = "1.19.3/4"; ++def modLoaders = "forge|fabric|quilt"; ++def supportedMc = "1.19.3|1.19.4"; ++def reltype = "snapshot"; pipeline { agent { -@@ -18,7 +19,7 @@ - stage("Notify Discord") { - steps { - discordSend webhookURL: env.SSS_WEBHOOK, -- title: "Deploy Started: ${projectName} Port Deploy #${BUILD_NUMBER}", -+ title: "Deploy Started: ${projectName} 1.19.3/4 Deploy #${BUILD_NUMBER}", - link: env.BUILD_URL, - result: 'SUCCESS', - description: "Build: [${BUILD_NUMBER}](${env.BUILD_URL})" -@@ -34,14 +35,14 @@ - - stage("Build") { - steps { -- sh "./gradlew build -PreleaseType=port" -+ sh "./gradlew build -PreleaseType=${relType}" - } - } - - stage("Publish to Maven") { - steps { - catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') { -- sh "./gradlew publish -PreleaseType=port" -+ sh "./gradlew publish -PreleaseType=${relType}" - } - } - } -@@ -56,8 +57,8 @@ - projectSlug: "craterlib", - projectName: "${projectName}", - projectIcon: "${projectIcon}", -- modLoaders: "neoforge|fabric|quilt", -- minecraftVersions: "1.21", -+ modLoaders: "forge|fabric|quilt", -+ minecraftVersions: "1.19.3|1.19.4", - failWebhook: env.SSS_WEBHOOK, - publishWebhooks: "${env.SSS_WEBHOOK}|${env.FDD_WH}" - diff --git a/patches/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java.patch b/patches/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java.patch index 7074cae..3c0c085 100644 --- a/patches/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java.patch +++ b/patches/1.19.3/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java.patch @@ -1,37 +1,29 @@ --- a/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java +++ b/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java -@@ -5,22 +5,29 @@ - import me.hypherionmc.mcdiscordformatter.minecraft.MinecraftSerializer; - import net.kyori.adventure.text.format.NamedTextColor; - import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; -+import net.kyori.adventure.text.serializer.json.JSONOptions; -+import net.kyori.option.OptionState; +@@ -9,12 +9,9 @@ import net.minecraft.ChatFormatting; + import net.minecraft.SharedConstants; import net.minecraft.Util; -import net.minecraft.core.RegistryAccess; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Style; +-import java.util.function.Consumer; +- public class ChatUtils { -+ private static final GsonComponentSerializer adventureSerializer = GsonComponentSerializer.builder().options( -+ OptionState.optionState() -+ .value(JSONOptions.EMIT_HOVER_SHOW_ENTITY_ID_AS_INT_ARRAY, false) -+ .value(JSONOptions.EMIT_HOVER_EVENT_TYPE, JSONOptions.HoverEventValueMode.MODERN_ONLY).build() -+ ).build(); -+ + private static final GsonComponentSerializer adventureSerializer = GsonComponentSerializer.builder().options( +@@ -23,11 +20,11 @@ + public static Component adventureToMojang(net.kyori.adventure.text.Component inComponent) { -- final String serialised = GsonComponentSerializer.gson().serialize(inComponent); + final String serialised = adventureSerializer.serialize(inComponent); - return Component.Serializer.fromJson(serialised, RegistryAccess.EMPTY); -+ final String serialised = adventureSerializer.serialize(inComponent); + return Component.Serializer.fromJson(serialised); } public static net.kyori.adventure.text.Component mojangToAdventure(Component inComponent) { - final String serialised = Component.Serializer.toJson(inComponent, RegistryAccess.EMPTY); -- return GsonComponentSerializer.gson().deserialize(serialised); + final String serialised = Component.Serializer.toJson(inComponent); -+ return adventureSerializer.deserialize(serialised); + return adventureSerializer.deserialize(serialised); } - // Some text components contain duplicate text, resulting in duplicate messages diff --git a/patches/1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java.patch b/patches/1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java.patch index de34181..da64010 100644 --- a/patches/1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java.patch +++ b/patches/1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java.patch @@ -1,6 +1,17 @@ --- a/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java +++ b/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java -@@ -15,6 +15,8 @@ +@@ -4,10 +4,8 @@ + import com.hypherionmc.craterlib.core.event.CraterEventBus; + import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; + import com.hypherionmc.craterlib.utils.ChatUtils; +-import net.minecraft.network.chat.Component; + import net.minecraft.network.chat.PlayerChatMessage; + import net.minecraft.server.level.ServerPlayer; +-import net.minecraft.server.network.FilteredText; + import net.minecraft.server.network.ServerGamePacketListenerImpl; + import org.spongepowered.asm.mixin.Mixin; + import org.spongepowered.asm.mixin.Shadow; +@@ -15,6 +13,8 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @@ -9,7 +20,7 @@ @Mixin(value = ServerGamePacketListenerImpl.class, priority = Integer.MIN_VALUE) public class ServerGamePacketListenerImplMixin { -@@ -22,11 +24,11 @@ +@@ -22,11 +22,11 @@ public ServerPlayer player; @Inject( diff --git a/patches/1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java.patch b/patches/1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java.patch index dbe9cc7..32e58e0 100644 --- a/patches/1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java.patch +++ b/patches/1.19.3/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java.patch @@ -1,6 +1,6 @@ --- a/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java +++ b/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java -@@ -1,46 +1,57 @@ +@@ -1,46 +1,55 @@ package com.hypherionmc.craterlib.network; -import com.hypherionmc.craterlib.api.networking.CommonPacketWrapper; @@ -17,8 +17,6 @@ +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.minecraft.network.FriendlyByteBuf; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.server.level.ServerPlayer; + +import java.util.HashMap; +import java.util.Map; @@ -81,7 +79,7 @@ } public void sendToServer(T packet) { -@@ -48,21 +59,25 @@ +@@ -48,21 +57,25 @@ } public void sendToServer(T packet, boolean ignoreCheck) { diff --git a/patches/1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java.patch b/patches/1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java.patch index e07211e..fd38e13 100644 --- a/patches/1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java.patch +++ b/patches/1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java.patch @@ -1,13 +1,13 @@ --- a/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java +++ b/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java -@@ -3,6 +3,7 @@ +@@ -2,6 +2,7 @@ + import com.hypherionmc.craterlib.api.events.client.LateInitEvent; import com.hypherionmc.craterlib.common.ForgeServerEvents; - import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.compat.Vanish; + import com.hypherionmc.craterlib.core.event.CraterEventBus; import com.hypherionmc.craterlib.core.networking.CraterPacketNetwork; import com.hypherionmc.craterlib.core.networking.data.PacketSide; - import com.hypherionmc.craterlib.core.platform.ModloaderEnvironment; @@ -32,5 +33,9 @@ LateInitEvent event = new LateInitEvent(new BridgedMinecraft(), BridgedOptions.of(Minecraft.getInstance().options)); CraterEventBus.INSTANCE.postEvent(event); diff --git a/patches/1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java.patch b/patches/1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java.patch index 39cc093..847d18e 100644 --- a/patches/1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java.patch +++ b/patches/1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java.patch @@ -1,12 +1,11 @@ --- /dev/null +++ b/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java -@@ -1,0 +1,25 @@ +@@ -1,0 +1,24 @@ +package com.hypherionmc.craterlib.compat; + +import com.hypherionmc.craterlib.api.events.server.CraterPlayerEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; -+import net.minecraft.world.entity.player.Player; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import redstonedubstep.mods.vanishmod.api.PlayerVanishEvent; + diff --git a/patches/1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java.patch b/patches/1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java.patch index 7f496e9..edc9fa9 100644 --- a/patches/1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java.patch +++ b/patches/1.19.3/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java.patch @@ -1,6 +1,17 @@ --- a/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java +++ b/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java -@@ -15,6 +15,8 @@ +@@ -4,10 +4,8 @@ + import com.hypherionmc.craterlib.core.event.CraterEventBus; + import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; + import com.hypherionmc.craterlib.utils.ChatUtils; +-import net.minecraft.network.chat.Component; + import net.minecraft.network.chat.PlayerChatMessage; + import net.minecraft.server.level.ServerPlayer; +-import net.minecraft.server.network.FilteredText; + import net.minecraft.server.network.ServerGamePacketListenerImpl; + import org.spongepowered.asm.mixin.Mixin; + import org.spongepowered.asm.mixin.Shadow; +@@ -15,6 +13,8 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @@ -9,7 +20,7 @@ @Mixin(value = ServerGamePacketListenerImpl.class, priority = Integer.MIN_VALUE) public class ServerGamePacketListenerImplMixin { -@@ -22,11 +24,11 @@ +@@ -22,11 +22,11 @@ public ServerPlayer player; @Inject( diff --git a/patches/1.19.3/README.md.patch b/patches/1.19.3/README.md.patch index e51d5b3..c98b984 100644 --- a/patches/1.19.3/README.md.patch +++ b/patches/1.19.3/README.md.patch @@ -1,10 +1,11 @@ --- a/README.md +++ b/README.md -@@ -15,7 +15,7 @@ +@@ -15,8 +15,8 @@ | < 1.18.2 | ❌ | | 1.18.2-1.20.2 | ✳️ | | 1.20.4 | ✳️ | --| 1.20.6 | 🚧 | +-| 1.20.6 | ❌ | + | 1.21 | ✳️ | +| 1.21 | 🚧 | - ❌ - Not Supported; no bug fixes or new features. diff --git a/patches/1.19.3/build.gradle.patch b/patches/1.19.3/build.gradle.patch new file mode 100644 index 0000000..169744c --- /dev/null +++ b/patches/1.19.3/build.gradle.patch @@ -0,0 +1,30 @@ +--- a/build.gradle ++++ b/build.gradle +@@ -11,7 +11,6 @@ + multiProject = true + enableMirrorMaven = true + enableReleasesMaven = true +- enableSnapshotsMaven = true + + dopplerToken = System.getenv("DOPPLER_KEY") + +@@ -30,8 +29,8 @@ + apply plugin: 'com.github.johnrengelman.shadow' + apply plugin: 'com.hypherionmc.modutils.modpublisher' + +- sourceCompatibility = JavaVersion.VERSION_21 +- targetCompatibility = JavaVersion.VERSION_21 ++ sourceCompatibility = JavaVersion.VERSION_17 ++ targetCompatibility = JavaVersion.VERSION_17 + + group = rootProject.group + +@@ -59,7 +58,7 @@ + shade "me.hypherionmc.moon-config:core:${moon_config}" + shade "me.hypherionmc.moon-config:toml:${moon_config}" + shade "com.hypherionmc:rpcsdk:${rpc_sdk}" +- shade "me.hypherionmc.sdlink:mcdiscordformatter-1.20.3:${discord_formatter}" ++ shade "me.hypherionmc.sdlink:mcdiscordformatter-1.19.1:${discord_formatter}" + shade "net.kyori:adventure-api:${adventure}" + shade "net.kyori:adventure-text-serializer-gson:${adventure}" + diff --git a/patches/1.19.3/orbit.json.patch b/patches/1.19.3/orbit.json.patch deleted file mode 100644 index 4f0263d..0000000 --- a/patches/1.19.3/orbit.json.patch +++ /dev/null @@ -1,7 +0,0 @@ ---- a/orbit.json -+++ /dev/null -@@ -1,4 +1,0 @@ --{ -- "action": "port", -- "devBranch": "porting" --} diff --git a/patches/1.19.3/settings.gradle.patch b/patches/1.19.3/settings.gradle.patch index b23adaa..09c3682 100644 --- a/patches/1.19.3/settings.gradle.patch +++ b/patches/1.19.3/settings.gradle.patch @@ -1,8 +1,10 @@ --- a/settings.gradle +++ b/settings.gradle -@@ -11,4 +11,4 @@ +@@ -10,5 +10,5 @@ + } } - rootProject.name = 'CraterLib' +-rootProject.name = 'CraterLib' -include("Common", "Fabric", "NeoForge") ++rootProject.name = 'CraterLib-1.19.3' +include("Common", "Fabric", "Forge") diff --git a/patches/1.20.2/.jenkins/Jenkinsfile.deploy.patch b/patches/1.20.2/.jenkins/Jenkinsfile.deploy.patch index 01197fa..99507ae 100644 --- a/patches/1.20.2/.jenkins/Jenkinsfile.deploy.patch +++ b/patches/1.20.2/.jenkins/Jenkinsfile.deploy.patch @@ -1,15 +1,12 @@ --- a/.jenkins/Jenkinsfile.deploy +++ b/.jenkins/Jenkinsfile.deploy -@@ -1,7 +1,7 @@ +@@ -1,4 +1,4 @@ +-def JDK = "21" ++def JDK = "17" + pipeline { agent { - docker { -- image "registry.firstdark.dev/java21:latest" -+ image "registry.firstdark.dev/java17:latest" - alwaysPull true - args '-v gradle-cache:/home/gradle/.gradle' - } -@@ -15,7 +15,7 @@ +@@ -17,7 +17,7 @@ stage("Notify Discord") { steps { discordSend webhookURL: env.FDD_WH_ADMIN, @@ -18,12 +15,3 @@ link: env.BUILD_URL, result: 'SUCCESS', description: "Build: [${BUILD_NUMBER}](${env.BUILD_URL})" -@@ -44,7 +44,7 @@ - deleteDir() - - discordSend webhookURL: env.FDD_WH_ADMIN, -- title: "CraterLib Port Deploy #${BUILD_NUMBER}", -+ title: "CraterLib 1.20.2 Deploy #${BUILD_NUMBER}", - link: env.BUILD_URL, - result: currentBuild.currentResult, - description: "Build: [${BUILD_NUMBER}](${env.BUILD_URL})\nStatus: ${currentBuild.currentResult}" diff --git a/patches/1.20.2/.jenkins/Jenkinsfile.snapshot.patch b/patches/1.20.2/.jenkins/Jenkinsfile.snapshot.patch index 530e015..d08c1bc 100644 --- a/patches/1.20.2/.jenkins/Jenkinsfile.snapshot.patch +++ b/patches/1.20.2/.jenkins/Jenkinsfile.snapshot.patch @@ -1,52 +1,18 @@ --- a/.jenkins/Jenkinsfile.snapshot +++ b/.jenkins/Jenkinsfile.snapshot -@@ -1,10 +1,11 @@ +@@ -1,10 +1,10 @@ def projectName = "CraterLib"; def projectIcon = "https://cdn.modrinth.com/data/Nn8Wasaq/a172c634683a11a2e9ae593e56eba7885743bb44.png"; -+def relType = "snapshot" +-def JDK = "21"; +-def majorMc = "Port"; +-def modLoaders = "neoforge|fabric|quilt"; +-def supportedMc = "1.21"; +-def reltype = "port"; ++def JDK = "17"; ++def majorMc = "1.20.2"; ++def modLoaders = "forge|fabric|quilt"; ++def supportedMc = "1.20.2"; ++def reltype = "snapshot"; pipeline { agent { - docker { -- image "registry.firstdark.dev/java21:latest" -+ image "registry.firstdark.dev/java17:latest" - alwaysPull true - args '-v gradle-cache:/home/gradle/.gradle' - } -@@ -18,7 +19,7 @@ - stage("Notify Discord") { - steps { - discordSend webhookURL: env.SSS_WEBHOOK, -- title: "Deploy Started: ${projectName} Port Deploy #${BUILD_NUMBER}", -+ title: "Deploy Started: ${projectName} 1.20.2 Deploy #${BUILD_NUMBER}", - link: env.BUILD_URL, - result: 'SUCCESS', - description: "Build: [${BUILD_NUMBER}](${env.BUILD_URL})" -@@ -34,14 +35,14 @@ - - stage("Build") { - steps { -- sh "./gradlew build -PreleaseType=port" -+ sh "./gradlew build -PreleaseType=${relType}" - } - } - - stage("Publish to Maven") { - steps { - catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') { -- sh "./gradlew publish -PreleaseType=port" -+ sh "./gradlew publish -PreleaseType=${relType}" - } - } - } -@@ -56,8 +57,8 @@ - projectSlug: "craterlib", - projectName: "${projectName}", - projectIcon: "${projectIcon}", -- modLoaders: "neoforge|fabric|quilt", -- minecraftVersions: "1.21", -+ modLoaders: "forge|fabric|quilt", -+ minecraftVersions: "1.20.2", - failWebhook: env.SSS_WEBHOOK, - publishWebhooks: "${env.SSS_WEBHOOK}|${env.FDD_WH}" - diff --git a/patches/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java.patch b/patches/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java.patch index 264807f..3c0c085 100644 --- a/patches/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java.patch +++ b/patches/1.20.2/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java.patch @@ -1,37 +1,29 @@ --- a/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java +++ b/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java -@@ -5,22 +5,29 @@ - import me.hypherionmc.mcdiscordformatter.minecraft.MinecraftSerializer; - import net.kyori.adventure.text.format.NamedTextColor; - import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; -+import net.kyori.adventure.text.serializer.json.JSONOptions; -+import net.kyori.option.OptionState; +@@ -9,12 +9,9 @@ import net.minecraft.ChatFormatting; + import net.minecraft.SharedConstants; import net.minecraft.Util; -import net.minecraft.core.RegistryAccess; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Style; +-import java.util.function.Consumer; +- public class ChatUtils { -+ private static final GsonComponentSerializer adventureSerializer = GsonComponentSerializer.builder().options( -+ OptionState.optionState() -+ .value(JSONOptions.EMIT_HOVER_SHOW_ENTITY_ID_AS_INT_ARRAY, false) -+ .value(JSONOptions.EMIT_HOVER_EVENT_TYPE, JSONOptions.HoverEventValueMode.MODERN_ONLY).build() -+ ).build(); -+ + private static final GsonComponentSerializer adventureSerializer = GsonComponentSerializer.builder().options( +@@ -23,11 +20,11 @@ + public static Component adventureToMojang(net.kyori.adventure.text.Component inComponent) { -- final String serialised = GsonComponentSerializer.gson().serialize(inComponent); + final String serialised = adventureSerializer.serialize(inComponent); - return Component.Serializer.fromJson(serialised, RegistryAccess.EMPTY); -+ final String serialised = adventureSerializer.serialize(inComponent); + return Component.Serializer.fromJson(serialised); } public static net.kyori.adventure.text.Component mojangToAdventure(Component inComponent) { - final String serialised = Component.Serializer.toJson(inComponent, RegistryAccess.EMPTY); -- return GsonComponentSerializer.gson().deserialize(serialised); + final String serialised = Component.Serializer.toJson(inComponent); -+ return adventureSerializer.deserialize(serialised); + return adventureSerializer.deserialize(serialised); } - // Some text components contain duplicate text, resulting in duplicate messages diff --git a/patches/1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java.patch b/patches/1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java.patch index dbe9cc7..32e58e0 100644 --- a/patches/1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java.patch +++ b/patches/1.20.2/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java.patch @@ -1,6 +1,6 @@ --- a/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java +++ b/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java -@@ -1,46 +1,57 @@ +@@ -1,46 +1,55 @@ package com.hypherionmc.craterlib.network; -import com.hypherionmc.craterlib.api.networking.CommonPacketWrapper; @@ -17,8 +17,6 @@ +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.minecraft.network.FriendlyByteBuf; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.server.level.ServerPlayer; + +import java.util.HashMap; +import java.util.Map; @@ -81,7 +79,7 @@ } public void sendToServer(T packet) { -@@ -48,21 +59,25 @@ +@@ -48,21 +57,25 @@ } public void sendToServer(T packet, boolean ignoreCheck) { diff --git a/patches/1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java.patch b/patches/1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java.patch index e07211e..fd38e13 100644 --- a/patches/1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java.patch +++ b/patches/1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java.patch @@ -1,13 +1,13 @@ --- a/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java +++ b/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java -@@ -3,6 +3,7 @@ +@@ -2,6 +2,7 @@ + import com.hypherionmc.craterlib.api.events.client.LateInitEvent; import com.hypherionmc.craterlib.common.ForgeServerEvents; - import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.compat.Vanish; + import com.hypherionmc.craterlib.core.event.CraterEventBus; import com.hypherionmc.craterlib.core.networking.CraterPacketNetwork; import com.hypherionmc.craterlib.core.networking.data.PacketSide; - import com.hypherionmc.craterlib.core.platform.ModloaderEnvironment; @@ -32,5 +33,9 @@ LateInitEvent event = new LateInitEvent(new BridgedMinecraft(), BridgedOptions.of(Minecraft.getInstance().options)); CraterEventBus.INSTANCE.postEvent(event); diff --git a/patches/1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java.patch b/patches/1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java.patch index 39cc093..847d18e 100644 --- a/patches/1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java.patch +++ b/patches/1.20.2/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java.patch @@ -1,12 +1,11 @@ --- /dev/null +++ b/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java -@@ -1,0 +1,25 @@ +@@ -1,0 +1,24 @@ +package com.hypherionmc.craterlib.compat; + +import com.hypherionmc.craterlib.api.events.server.CraterPlayerEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; -+import net.minecraft.world.entity.player.Player; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import redstonedubstep.mods.vanishmod.api.PlayerVanishEvent; + diff --git a/patches/1.20.2/README.md.patch b/patches/1.20.2/README.md.patch index e51d5b3..5fd4d92 100644 --- a/patches/1.20.2/README.md.patch +++ b/patches/1.20.2/README.md.patch @@ -1,10 +1,11 @@ --- a/README.md +++ b/README.md -@@ -15,7 +15,7 @@ +@@ -15,8 +15,7 @@ | < 1.18.2 | ❌ | | 1.18.2-1.20.2 | ✳️ | | 1.20.4 | ✳️ | --| 1.20.6 | 🚧 | +-| 1.20.6 | ❌ | +-| 1.21 | ✳️ | +| 1.21 | 🚧 | - ❌ - Not Supported; no bug fixes or new features. diff --git a/patches/1.20.2/build.gradle.patch b/patches/1.20.2/build.gradle.patch new file mode 100644 index 0000000..169744c --- /dev/null +++ b/patches/1.20.2/build.gradle.patch @@ -0,0 +1,30 @@ +--- a/build.gradle ++++ b/build.gradle +@@ -11,7 +11,6 @@ + multiProject = true + enableMirrorMaven = true + enableReleasesMaven = true +- enableSnapshotsMaven = true + + dopplerToken = System.getenv("DOPPLER_KEY") + +@@ -30,8 +29,8 @@ + apply plugin: 'com.github.johnrengelman.shadow' + apply plugin: 'com.hypherionmc.modutils.modpublisher' + +- sourceCompatibility = JavaVersion.VERSION_21 +- targetCompatibility = JavaVersion.VERSION_21 ++ sourceCompatibility = JavaVersion.VERSION_17 ++ targetCompatibility = JavaVersion.VERSION_17 + + group = rootProject.group + +@@ -59,7 +58,7 @@ + shade "me.hypherionmc.moon-config:core:${moon_config}" + shade "me.hypherionmc.moon-config:toml:${moon_config}" + shade "com.hypherionmc:rpcsdk:${rpc_sdk}" +- shade "me.hypherionmc.sdlink:mcdiscordformatter-1.20.3:${discord_formatter}" ++ shade "me.hypherionmc.sdlink:mcdiscordformatter-1.19.1:${discord_formatter}" + shade "net.kyori:adventure-api:${adventure}" + shade "net.kyori:adventure-text-serializer-gson:${adventure}" + diff --git a/patches/1.20.2/orbit.json.patch b/patches/1.20.2/orbit.json.patch deleted file mode 100644 index 4f0263d..0000000 --- a/patches/1.20.2/orbit.json.patch +++ /dev/null @@ -1,7 +0,0 @@ ---- a/orbit.json -+++ /dev/null -@@ -1,4 +1,0 @@ --{ -- "action": "port", -- "devBranch": "porting" --} diff --git a/patches/1.20.2/settings.gradle.patch b/patches/1.20.2/settings.gradle.patch index b23adaa..b3e2592 100644 --- a/patches/1.20.2/settings.gradle.patch +++ b/patches/1.20.2/settings.gradle.patch @@ -1,8 +1,10 @@ --- a/settings.gradle +++ b/settings.gradle -@@ -11,4 +11,4 @@ +@@ -10,5 +10,5 @@ + } } - rootProject.name = 'CraterLib' +-rootProject.name = 'CraterLib' -include("Common", "Fabric", "NeoForge") ++rootProject.name = 'CraterLib-1.20.2' +include("Common", "Fabric", "Forge") diff --git a/patches/1.20.4/.jenkins/Jenkinsfile.deploy.patch b/patches/1.20.4/.jenkins/Jenkinsfile.deploy.patch index 7a533ff..f0dd219 100644 --- a/patches/1.20.4/.jenkins/Jenkinsfile.deploy.patch +++ b/patches/1.20.4/.jenkins/Jenkinsfile.deploy.patch @@ -1,15 +1,12 @@ --- a/.jenkins/Jenkinsfile.deploy +++ b/.jenkins/Jenkinsfile.deploy -@@ -1,7 +1,7 @@ +@@ -1,4 +1,4 @@ +-def JDK = "21" ++def JDK = "17" + pipeline { agent { - docker { -- image "registry.firstdark.dev/java21:latest" -+ image "registry.firstdark.dev/java17:latest" - alwaysPull true - args '-v gradle-cache:/home/gradle/.gradle' - } -@@ -15,7 +15,7 @@ +@@ -17,7 +17,7 @@ stage("Notify Discord") { steps { discordSend webhookURL: env.FDD_WH_ADMIN, @@ -18,12 +15,3 @@ link: env.BUILD_URL, result: 'SUCCESS', description: "Build: [${BUILD_NUMBER}](${env.BUILD_URL})" -@@ -44,7 +44,7 @@ - deleteDir() - - discordSend webhookURL: env.FDD_WH_ADMIN, -- title: "CraterLib Port Deploy #${BUILD_NUMBER}", -+ title: "CraterLib 1.20.4 Deploy #${BUILD_NUMBER}", - link: env.BUILD_URL, - result: currentBuild.currentResult, - description: "Build: [${BUILD_NUMBER}](${env.BUILD_URL})\nStatus: ${currentBuild.currentResult}" diff --git a/patches/1.20.4/.jenkins/Jenkinsfile.snapshot.patch b/patches/1.20.4/.jenkins/Jenkinsfile.snapshot.patch index 49a4964..c7154ec 100644 --- a/patches/1.20.4/.jenkins/Jenkinsfile.snapshot.patch +++ b/patches/1.20.4/.jenkins/Jenkinsfile.snapshot.patch @@ -1,41 +1,18 @@ --- a/.jenkins/Jenkinsfile.snapshot +++ b/.jenkins/Jenkinsfile.snapshot -@@ -1,10 +1,11 @@ +@@ -1,10 +1,10 @@ def projectName = "CraterLib"; def projectIcon = "https://cdn.modrinth.com/data/Nn8Wasaq/a172c634683a11a2e9ae593e56eba7885743bb44.png"; -+def relType = "snapshot" +-def JDK = "21"; +-def majorMc = "Port"; +-def modLoaders = "neoforge|fabric|quilt"; +-def supportedMc = "1.21"; +-def reltype = "port"; ++def JDK = "17"; ++def majorMc = "1.20.4"; ++def modLoaders = "neoforge|forge|fabric|quilt"; ++def supportedMc = "1.20.4"; ++def reltype = "snapshot"; pipeline { agent { - docker { -- image "registry.firstdark.dev/java21:latest" -+ image "registry.firstdark.dev/java17:latest" - alwaysPull true - args '-v gradle-cache:/home/gradle/.gradle' - } -@@ -34,14 +35,14 @@ - - stage("Build") { - steps { -- sh "./gradlew build -PreleaseType=port" -+ sh "./gradlew build -PreleaseType=${relType}" - } - } - - stage("Publish to Maven") { - steps { - catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') { -- sh "./gradlew publish -PreleaseType=port" -+ sh "./gradlew publish -PreleaseType=${relType}" - } - } - } -@@ -57,7 +58,7 @@ - projectName: "${projectName}", - projectIcon: "${projectIcon}", - modLoaders: "neoforge|fabric|quilt", -- minecraftVersions: "1.21", -+ minecraftVersions: "1.20.4", - failWebhook: env.SSS_WEBHOOK, - publishWebhooks: "${env.SSS_WEBHOOK}|${env.FDD_WH}" - diff --git a/patches/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java.patch b/patches/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java.patch index 24f97af..3c0c085 100644 --- a/patches/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java.patch +++ b/patches/1.20.4/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java.patch @@ -1,17 +1,22 @@ --- a/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java +++ b/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java -@@ -7,7 +7,6 @@ - import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +@@ -9,12 +9,9 @@ import net.minecraft.ChatFormatting; + import net.minecraft.SharedConstants; import net.minecraft.Util; -import net.minecraft.core.RegistryAccess; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Style; -@@ -15,11 +14,11 @@ +-import java.util.function.Consumer; +- + public class ChatUtils { + + private static final GsonComponentSerializer adventureSerializer = GsonComponentSerializer.builder().options( +@@ -23,11 +20,11 @@ public static Component adventureToMojang(net.kyori.adventure.text.Component inComponent) { - final String serialised = GsonComponentSerializer.gson().serialize(inComponent); + final String serialised = adventureSerializer.serialize(inComponent); - return Component.Serializer.fromJson(serialised, RegistryAccess.EMPTY); + return Component.Serializer.fromJson(serialised); } @@ -19,6 +24,6 @@ public static net.kyori.adventure.text.Component mojangToAdventure(Component inComponent) { - final String serialised = Component.Serializer.toJson(inComponent, RegistryAccess.EMPTY); + final String serialised = Component.Serializer.toJson(inComponent); - return GsonComponentSerializer.gson().deserialize(serialised); + return adventureSerializer.deserialize(serialised); } diff --git a/patches/1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java.patch b/patches/1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java.patch index dbe9cc7..32e58e0 100644 --- a/patches/1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java.patch +++ b/patches/1.20.4/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java.patch @@ -1,6 +1,6 @@ --- a/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java +++ b/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java -@@ -1,46 +1,57 @@ +@@ -1,46 +1,55 @@ package com.hypherionmc.craterlib.network; -import com.hypherionmc.craterlib.api.networking.CommonPacketWrapper; @@ -17,8 +17,6 @@ +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.minecraft.network.FriendlyByteBuf; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.server.level.ServerPlayer; + +import java.util.HashMap; +import java.util.Map; @@ -81,7 +79,7 @@ } public void sendToServer(T packet) { -@@ -48,21 +59,25 @@ +@@ -48,21 +57,25 @@ } public void sendToServer(T packet, boolean ignoreCheck) { diff --git a/patches/1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java.patch b/patches/1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java.patch index e07211e..fd38e13 100644 --- a/patches/1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java.patch +++ b/patches/1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java.patch @@ -1,13 +1,13 @@ --- a/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java +++ b/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java -@@ -3,6 +3,7 @@ +@@ -2,6 +2,7 @@ + import com.hypherionmc.craterlib.api.events.client.LateInitEvent; import com.hypherionmc.craterlib.common.ForgeServerEvents; - import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.compat.Vanish; + import com.hypherionmc.craterlib.core.event.CraterEventBus; import com.hypherionmc.craterlib.core.networking.CraterPacketNetwork; import com.hypherionmc.craterlib.core.networking.data.PacketSide; - import com.hypherionmc.craterlib.core.platform.ModloaderEnvironment; @@ -32,5 +33,9 @@ LateInitEvent event = new LateInitEvent(new BridgedMinecraft(), BridgedOptions.of(Minecraft.getInstance().options)); CraterEventBus.INSTANCE.postEvent(event); diff --git a/patches/1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java.patch b/patches/1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java.patch index 39cc093..847d18e 100644 --- a/patches/1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java.patch +++ b/patches/1.20.4/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java.patch @@ -1,12 +1,11 @@ --- /dev/null +++ b/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java -@@ -1,0 +1,25 @@ +@@ -1,0 +1,24 @@ +package com.hypherionmc.craterlib.compat; + +import com.hypherionmc.craterlib.api.events.server.CraterPlayerEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; -+import net.minecraft.world.entity.player.Player; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import redstonedubstep.mods.vanishmod.api.PlayerVanishEvent; + diff --git a/patches/1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/network/CraterNeoForgeNetworkHandler.java.patch b/patches/1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/network/CraterNeoForgeNetworkHandler.java.patch index 93a34a9..b78e952 100644 --- a/patches/1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/network/CraterNeoForgeNetworkHandler.java.patch +++ b/patches/1.20.4/NeoForge/src/main/java/com/hypherionmc/craterlib/network/CraterNeoForgeNetworkHandler.java.patch @@ -1,6 +1,6 @@ --- a/NeoForge/src/main/java/com/hypherionmc/craterlib/network/CraterNeoForgeNetworkHandler.java +++ b/NeoForge/src/main/java/com/hypherionmc/craterlib/network/CraterNeoForgeNetworkHandler.java -@@ -1,40 +1,71 @@ +@@ -1,40 +1,70 @@ package com.hypherionmc.craterlib.network; import com.hypherionmc.craterlib.CraterConstants; @@ -12,7 +12,6 @@ +import com.hypherionmc.craterlib.nojang.network.BridgedFriendlyByteBuf; import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; +import net.minecraft.network.FriendlyByteBuf; -+import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Player; import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.LogicalSide; @@ -80,7 +79,7 @@ } public void sendToServer(T packet) { -@@ -42,36 +73,45 @@ +@@ -42,36 +72,45 @@ } public void sendToServer(T packet, boolean ignoreCheck) { diff --git a/patches/1.20.4/README.md.patch b/patches/1.20.4/README.md.patch index e51d5b3..5fd4d92 100644 --- a/patches/1.20.4/README.md.patch +++ b/patches/1.20.4/README.md.patch @@ -1,10 +1,11 @@ --- a/README.md +++ b/README.md -@@ -15,7 +15,7 @@ +@@ -15,8 +15,7 @@ | < 1.18.2 | ❌ | | 1.18.2-1.20.2 | ✳️ | | 1.20.4 | ✳️ | --| 1.20.6 | 🚧 | +-| 1.20.6 | ❌ | +-| 1.21 | ✳️ | +| 1.21 | 🚧 | - ❌ - Not Supported; no bug fixes or new features. diff --git a/patches/1.20.4/build.gradle.patch b/patches/1.20.4/build.gradle.patch new file mode 100644 index 0000000..d1d5292 --- /dev/null +++ b/patches/1.20.4/build.gradle.patch @@ -0,0 +1,21 @@ +--- a/build.gradle ++++ b/build.gradle +@@ -11,7 +11,6 @@ + multiProject = true + enableMirrorMaven = true + enableReleasesMaven = true +- enableSnapshotsMaven = true + + dopplerToken = System.getenv("DOPPLER_KEY") + +@@ -30,8 +29,8 @@ + apply plugin: 'com.github.johnrengelman.shadow' + apply plugin: 'com.hypherionmc.modutils.modpublisher' + +- sourceCompatibility = JavaVersion.VERSION_21 +- targetCompatibility = JavaVersion.VERSION_21 ++ sourceCompatibility = JavaVersion.VERSION_17 ++ targetCompatibility = JavaVersion.VERSION_17 + + group = rootProject.group + diff --git a/patches/1.20.4/orbit.json.patch b/patches/1.20.4/orbit.json.patch deleted file mode 100644 index 4f0263d..0000000 --- a/patches/1.20.4/orbit.json.patch +++ /dev/null @@ -1,7 +0,0 @@ ---- a/orbit.json -+++ /dev/null -@@ -1,4 +1,0 @@ --{ -- "action": "port", -- "devBranch": "porting" --} diff --git a/patches/1.20.4/settings.gradle.patch b/patches/1.20.4/settings.gradle.patch index 6ee7e7a..decd09a 100644 --- a/patches/1.20.4/settings.gradle.patch +++ b/patches/1.20.4/settings.gradle.patch @@ -1,8 +1,10 @@ --- a/settings.gradle +++ b/settings.gradle -@@ -11,4 +11,4 @@ +@@ -10,5 +10,5 @@ + } } - rootProject.name = 'CraterLib' +-rootProject.name = 'CraterLib' -include("Common", "Fabric", "NeoForge") ++rootProject.name = 'CraterLib-1.20.4' +include("Common", "Fabric", "Forge", "NeoForge") diff --git a/patches/1.20/.jenkins/Jenkinsfile.deploy.patch b/patches/1.20/.jenkins/Jenkinsfile.deploy.patch index 7a3499f..75577c0 100644 --- a/patches/1.20/.jenkins/Jenkinsfile.deploy.patch +++ b/patches/1.20/.jenkins/Jenkinsfile.deploy.patch @@ -1,15 +1,12 @@ --- a/.jenkins/Jenkinsfile.deploy +++ b/.jenkins/Jenkinsfile.deploy -@@ -1,7 +1,7 @@ +@@ -1,4 +1,4 @@ +-def JDK = "21" ++def JDK = "17" + pipeline { agent { - docker { -- image "registry.firstdark.dev/java21:latest" -+ image "registry.firstdark.dev/java17:latest" - alwaysPull true - args '-v gradle-cache:/home/gradle/.gradle' - } -@@ -15,7 +15,7 @@ +@@ -17,7 +17,7 @@ stage("Notify Discord") { steps { discordSend webhookURL: env.FDD_WH_ADMIN, @@ -18,12 +15,3 @@ link: env.BUILD_URL, result: 'SUCCESS', description: "Build: [${BUILD_NUMBER}](${env.BUILD_URL})" -@@ -44,7 +44,7 @@ - deleteDir() - - discordSend webhookURL: env.FDD_WH_ADMIN, -- title: "CraterLib Port Deploy #${BUILD_NUMBER}", -+ title: "CraterLib 1.20/1 Deploy #${BUILD_NUMBER}", - link: env.BUILD_URL, - result: currentBuild.currentResult, - description: "Build: [${BUILD_NUMBER}](${env.BUILD_URL})\nStatus: ${currentBuild.currentResult}" diff --git a/patches/1.20/.jenkins/Jenkinsfile.snapshot.patch b/patches/1.20/.jenkins/Jenkinsfile.snapshot.patch index f9f1fc3..b0e47ba 100644 --- a/patches/1.20/.jenkins/Jenkinsfile.snapshot.patch +++ b/patches/1.20/.jenkins/Jenkinsfile.snapshot.patch @@ -1,52 +1,18 @@ --- a/.jenkins/Jenkinsfile.snapshot +++ b/.jenkins/Jenkinsfile.snapshot -@@ -1,10 +1,11 @@ +@@ -1,10 +1,10 @@ def projectName = "CraterLib"; def projectIcon = "https://cdn.modrinth.com/data/Nn8Wasaq/a172c634683a11a2e9ae593e56eba7885743bb44.png"; -+def relType = "snapshot" +-def JDK = "21"; +-def majorMc = "Port"; +-def modLoaders = "neoforge|fabric|quilt"; +-def supportedMc = "1.21"; +-def reltype = "port"; ++def JDK = "17"; ++def majorMc = "1.20/1"; ++def modLoaders = "forge|fabric|quilt"; ++def supportedMc = "1.20|1.20.1"; ++def reltype = "snapshot"; pipeline { agent { - docker { -- image "registry.firstdark.dev/java21:latest" -+ image "registry.firstdark.dev/java17:latest" - alwaysPull true - args '-v gradle-cache:/home/gradle/.gradle' - } -@@ -18,7 +19,7 @@ - stage("Notify Discord") { - steps { - discordSend webhookURL: env.SSS_WEBHOOK, -- title: "Deploy Started: ${projectName} Port Deploy #${BUILD_NUMBER}", -+ title: "Deploy Started: ${projectName} 1.20/1 Deploy #${BUILD_NUMBER}", - link: env.BUILD_URL, - result: 'SUCCESS', - description: "Build: [${BUILD_NUMBER}](${env.BUILD_URL})" -@@ -34,14 +35,14 @@ - - stage("Build") { - steps { -- sh "./gradlew build -PreleaseType=port" -+ sh "./gradlew build -PreleaseType=${relType}" - } - } - - stage("Publish to Maven") { - steps { - catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') { -- sh "./gradlew publish -PreleaseType=port" -+ sh "./gradlew publish -PreleaseType=${relType}" - } - } - } -@@ -56,8 +57,8 @@ - projectSlug: "craterlib", - projectName: "${projectName}", - projectIcon: "${projectIcon}", -- modLoaders: "neoforge|fabric|quilt", -- minecraftVersions: "1.21", -+ modLoaders: "forge|fabric|quilt", -+ minecraftVersions: "1.20|1.20.1", - failWebhook: env.SSS_WEBHOOK, - publishWebhooks: "${env.SSS_WEBHOOK}|${env.FDD_WH}" - diff --git a/patches/1.20/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java.patch b/patches/1.20/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java.patch index 264807f..3c0c085 100644 --- a/patches/1.20/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java.patch +++ b/patches/1.20/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java.patch @@ -1,37 +1,29 @@ --- a/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java +++ b/Common/src/main/java/com/hypherionmc/craterlib/utils/ChatUtils.java -@@ -5,22 +5,29 @@ - import me.hypherionmc.mcdiscordformatter.minecraft.MinecraftSerializer; - import net.kyori.adventure.text.format.NamedTextColor; - import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; -+import net.kyori.adventure.text.serializer.json.JSONOptions; -+import net.kyori.option.OptionState; +@@ -9,12 +9,9 @@ import net.minecraft.ChatFormatting; + import net.minecraft.SharedConstants; import net.minecraft.Util; -import net.minecraft.core.RegistryAccess; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Style; +-import java.util.function.Consumer; +- public class ChatUtils { -+ private static final GsonComponentSerializer adventureSerializer = GsonComponentSerializer.builder().options( -+ OptionState.optionState() -+ .value(JSONOptions.EMIT_HOVER_SHOW_ENTITY_ID_AS_INT_ARRAY, false) -+ .value(JSONOptions.EMIT_HOVER_EVENT_TYPE, JSONOptions.HoverEventValueMode.MODERN_ONLY).build() -+ ).build(); -+ + private static final GsonComponentSerializer adventureSerializer = GsonComponentSerializer.builder().options( +@@ -23,11 +20,11 @@ + public static Component adventureToMojang(net.kyori.adventure.text.Component inComponent) { -- final String serialised = GsonComponentSerializer.gson().serialize(inComponent); + final String serialised = adventureSerializer.serialize(inComponent); - return Component.Serializer.fromJson(serialised, RegistryAccess.EMPTY); -+ final String serialised = adventureSerializer.serialize(inComponent); + return Component.Serializer.fromJson(serialised); } public static net.kyori.adventure.text.Component mojangToAdventure(Component inComponent) { - final String serialised = Component.Serializer.toJson(inComponent, RegistryAccess.EMPTY); -- return GsonComponentSerializer.gson().deserialize(serialised); + final String serialised = Component.Serializer.toJson(inComponent); -+ return adventureSerializer.deserialize(serialised); + return adventureSerializer.deserialize(serialised); } - // Some text components contain duplicate text, resulting in duplicate messages diff --git a/patches/1.20/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java.patch b/patches/1.20/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java.patch index de34181..da64010 100644 --- a/patches/1.20/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java.patch +++ b/patches/1.20/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java.patch @@ -1,6 +1,17 @@ --- a/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java +++ b/Fabric/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java -@@ -15,6 +15,8 @@ +@@ -4,10 +4,8 @@ + import com.hypherionmc.craterlib.core.event.CraterEventBus; + import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; + import com.hypherionmc.craterlib.utils.ChatUtils; +-import net.minecraft.network.chat.Component; + import net.minecraft.network.chat.PlayerChatMessage; + import net.minecraft.server.level.ServerPlayer; +-import net.minecraft.server.network.FilteredText; + import net.minecraft.server.network.ServerGamePacketListenerImpl; + import org.spongepowered.asm.mixin.Mixin; + import org.spongepowered.asm.mixin.Shadow; +@@ -15,6 +13,8 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @@ -9,7 +20,7 @@ @Mixin(value = ServerGamePacketListenerImpl.class, priority = Integer.MIN_VALUE) public class ServerGamePacketListenerImplMixin { -@@ -22,11 +24,11 @@ +@@ -22,11 +22,11 @@ public ServerPlayer player; @Inject( diff --git a/patches/1.20/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java.patch b/patches/1.20/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java.patch index dbe9cc7..32e58e0 100644 --- a/patches/1.20/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java.patch +++ b/patches/1.20/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java.patch @@ -1,6 +1,6 @@ --- a/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java +++ b/Fabric/src/main/java/com/hypherionmc/craterlib/network/CraterFabricNetworkHandler.java -@@ -1,46 +1,57 @@ +@@ -1,46 +1,55 @@ package com.hypherionmc.craterlib.network; -import com.hypherionmc.craterlib.api.networking.CommonPacketWrapper; @@ -17,8 +17,6 @@ +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.minecraft.network.FriendlyByteBuf; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.server.level.ServerPlayer; + +import java.util.HashMap; +import java.util.Map; @@ -81,7 +79,7 @@ } public void sendToServer(T packet) { -@@ -48,21 +59,25 @@ +@@ -48,21 +57,25 @@ } public void sendToServer(T packet, boolean ignoreCheck) { diff --git a/patches/1.20/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java.patch b/patches/1.20/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java.patch index e07211e..fd38e13 100644 --- a/patches/1.20/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java.patch +++ b/patches/1.20/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java.patch @@ -1,13 +1,13 @@ --- a/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java +++ b/Forge/src/main/java/com/hypherionmc/craterlib/CraterLib.java -@@ -3,6 +3,7 @@ +@@ -2,6 +2,7 @@ + import com.hypherionmc.craterlib.api.events.client.LateInitEvent; import com.hypherionmc.craterlib.common.ForgeServerEvents; - import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.compat.Vanish; + import com.hypherionmc.craterlib.core.event.CraterEventBus; import com.hypherionmc.craterlib.core.networking.CraterPacketNetwork; import com.hypherionmc.craterlib.core.networking.data.PacketSide; - import com.hypherionmc.craterlib.core.platform.ModloaderEnvironment; @@ -32,5 +33,9 @@ LateInitEvent event = new LateInitEvent(new BridgedMinecraft(), BridgedOptions.of(Minecraft.getInstance().options)); CraterEventBus.INSTANCE.postEvent(event); diff --git a/patches/1.20/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java.patch b/patches/1.20/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java.patch index 39cc093..847d18e 100644 --- a/patches/1.20/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java.patch +++ b/patches/1.20/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java.patch @@ -1,12 +1,11 @@ --- /dev/null +++ b/Forge/src/main/java/com/hypherionmc/craterlib/compat/Vanish.java -@@ -1,0 +1,25 @@ +@@ -1,0 +1,24 @@ +package com.hypherionmc.craterlib.compat; + +import com.hypherionmc.craterlib.api.events.server.CraterPlayerEvent; +import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; -+import net.minecraft.world.entity.player.Player; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import redstonedubstep.mods.vanishmod.api.PlayerVanishEvent; + diff --git a/patches/1.20/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java.patch b/patches/1.20/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java.patch index f862c35..3156e3e 100644 --- a/patches/1.20/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java.patch +++ b/patches/1.20/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java.patch @@ -1,6 +1,17 @@ --- a/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java +++ b/Forge/src/main/java/com/hypherionmc/craterlib/mixin/ServerGamePacketListenerImplMixin.java -@@ -15,6 +15,8 @@ +@@ -4,10 +4,8 @@ + import com.hypherionmc.craterlib.core.event.CraterEventBus; + import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer; + import com.hypherionmc.craterlib.utils.ChatUtils; +-import net.minecraft.network.chat.Component; + import net.minecraft.network.chat.PlayerChatMessage; + import net.minecraft.server.level.ServerPlayer; +-import net.minecraft.server.network.FilteredText; + import net.minecraft.server.network.ServerGamePacketListenerImpl; + import org.spongepowered.asm.mixin.Mixin; + import org.spongepowered.asm.mixin.Shadow; +@@ -15,6 +13,8 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @@ -9,7 +20,7 @@ @Mixin(value = ServerGamePacketListenerImpl.class, priority = Integer.MIN_VALUE) public class ServerGamePacketListenerImplMixin { -@@ -22,11 +24,11 @@ +@@ -22,11 +22,11 @@ public ServerPlayer player; @Inject( diff --git a/patches/1.20/README.md.patch b/patches/1.20/README.md.patch index e51d5b3..5fd4d92 100644 --- a/patches/1.20/README.md.patch +++ b/patches/1.20/README.md.patch @@ -1,10 +1,11 @@ --- a/README.md +++ b/README.md -@@ -15,7 +15,7 @@ +@@ -15,8 +15,7 @@ | < 1.18.2 | ❌ | | 1.18.2-1.20.2 | ✳️ | | 1.20.4 | ✳️ | --| 1.20.6 | 🚧 | +-| 1.20.6 | ❌ | +-| 1.21 | ✳️ | +| 1.21 | 🚧 | - ❌ - Not Supported; no bug fixes or new features. diff --git a/patches/1.20/build.gradle.patch b/patches/1.20/build.gradle.patch new file mode 100644 index 0000000..e44add1 --- /dev/null +++ b/patches/1.20/build.gradle.patch @@ -0,0 +1,31 @@ +--- a/build.gradle ++++ b/build.gradle +@@ -11,8 +11,6 @@ + multiProject = true + enableMirrorMaven = true + enableReleasesMaven = true +- enableSnapshotsMaven = true +- + dopplerToken = System.getenv("DOPPLER_KEY") + + versioning { +@@ -30,8 +28,8 @@ + apply plugin: 'com.github.johnrengelman.shadow' + apply plugin: 'com.hypherionmc.modutils.modpublisher' + +- sourceCompatibility = JavaVersion.VERSION_21 +- targetCompatibility = JavaVersion.VERSION_21 ++ sourceCompatibility = JavaVersion.VERSION_17 ++ targetCompatibility = JavaVersion.VERSION_17 + + group = rootProject.group + +@@ -59,7 +57,7 @@ + shade "me.hypherionmc.moon-config:core:${moon_config}" + shade "me.hypherionmc.moon-config:toml:${moon_config}" + shade "com.hypherionmc:rpcsdk:${rpc_sdk}" +- shade "me.hypherionmc.sdlink:mcdiscordformatter-1.20.3:${discord_formatter}" ++ shade "me.hypherionmc.sdlink:mcdiscordformatter-1.19.1:${discord_formatter}" + shade "net.kyori:adventure-api:${adventure}" + shade "net.kyori:adventure-text-serializer-gson:${adventure}" + diff --git a/patches/1.20/orbit.json.patch b/patches/1.20/orbit.json.patch deleted file mode 100644 index 4f0263d..0000000 --- a/patches/1.20/orbit.json.patch +++ /dev/null @@ -1,7 +0,0 @@ ---- a/orbit.json -+++ /dev/null -@@ -1,4 +1,0 @@ --{ -- "action": "port", -- "devBranch": "porting" --} diff --git a/patches/1.20/settings.gradle.patch b/patches/1.20/settings.gradle.patch index b23adaa..e089efd 100644 --- a/patches/1.20/settings.gradle.patch +++ b/patches/1.20/settings.gradle.patch @@ -1,8 +1,10 @@ --- a/settings.gradle +++ b/settings.gradle -@@ -11,4 +11,4 @@ +@@ -10,5 +10,5 @@ + } } - rootProject.name = 'CraterLib' +-rootProject.name = 'CraterLib' -include("Common", "Fabric", "NeoForge") ++rootProject.name = 'CraterLib-1.20' +include("Common", "Fabric", "Forge")