From d2f240ff0ca0d27f417f837e706c781a98c31311 Mon Sep 17 00:00:00 2001 From: Linnea Gräf Date: Wed, 28 Aug 2024 19:04:24 +0200 Subject: Refactor source layout Introduce compat source sets and move all kotlin sources to the main directory [no changelog] --- build.gradle.kts | 76 +++- gradle/libs.versions.toml | 66 ++- src/compat/sodium/java/SodiumChunkReloader.kt | 10 + .../sodium/AccessorSodiumWorldRenderer.java | 14 + .../PatchBlockModelInSodiumChunkGenerator.java | 29 ++ .../nea/firmament/init/AutoDiscoveryPlugin.java | 173 ++++++++ .../java/moe/nea/firmament/init/MixinPlugin.java | 5 +- .../sodium/AccessorSodiumWorldRenderer.java | 14 - .../PatchBlockModelInSodiumChunkGenerator.java | 29 -- src/main/kotlin/Firmament.kt | 148 +++++++ src/main/kotlin/apis/Profiles.kt | 194 ++++++++ src/main/kotlin/apis/Routes.kt | 95 ++++ src/main/kotlin/apis/UrsaManager.kt | 72 +++ .../commands/CaseInsensitiveLiteralCommandNode.kt | 75 ++++ src/main/kotlin/commands/RestArgumentType.kt | 15 + src/main/kotlin/commands/dsl.kt | 118 +++++ src/main/kotlin/commands/rome.kt | 230 ++++++++++ src/main/kotlin/events/AllowChatEvent.kt | 16 + src/main/kotlin/events/AttackBlockEvent.kt | 18 + src/main/kotlin/events/BakeExtraModelsEvent.kt | 21 + src/main/kotlin/events/ClientStartedEvent.kt | 6 + src/main/kotlin/events/CommandEvent.kt | 45 ++ src/main/kotlin/events/CustomItemModelEvent.kt | 43 ++ src/main/kotlin/events/EarlyResourceReloadEvent.kt | 10 + src/main/kotlin/events/EntityDespawnEvent.kt | 11 + src/main/kotlin/events/EntityInteractionEvent.kt | 29 ++ src/main/kotlin/events/EntityUpdateEvent.kt | 31 ++ src/main/kotlin/events/FeaturesInitializedEvent.kt | 8 + .../kotlin/events/FinalizeResourceManagerEvent.kt | 10 + src/main/kotlin/events/FirmamentEvent.kt | 38 ++ src/main/kotlin/events/FirmamentEventBus.kt | 52 +++ src/main/kotlin/events/HandledScreenClickEvent.kt | 10 + .../kotlin/events/HandledScreenForegroundEvent.kt | 16 + .../kotlin/events/HandledScreenKeyPressedEvent.kt | 24 + .../kotlin/events/HandledScreenPushREIEvent.kt | 18 + src/main/kotlin/events/HotbarItemRenderEvent.kt | 17 + src/main/kotlin/events/HudRenderEvent.kt | 13 + src/main/kotlin/events/IsSlotProtectedEvent.kt | 46 ++ src/main/kotlin/events/ItemTooltipEvent.kt | 14 + src/main/kotlin/events/MaskCommands.kt | 13 + src/main/kotlin/events/ModifyChatEvent.kt | 21 + src/main/kotlin/events/OutgoingPacketEvent.kt | 9 + src/main/kotlin/events/ParticleSpawnEvent.kt | 18 + src/main/kotlin/events/PlayerInventoryUpdate.kt | 11 + src/main/kotlin/events/ProcessChatEvent.kt | 28 ++ src/main/kotlin/events/ReloadRegistrationEvent.kt | 7 + src/main/kotlin/events/ScreenChangeEvent.kt | 10 + src/main/kotlin/events/ScreenRenderPostEvent.kt | 16 + src/main/kotlin/events/ServerConnectedEvent.kt | 18 + .../kotlin/events/SkyblockServerUpdateEvent.kt | 15 + src/main/kotlin/events/SlotClickEvent.kt | 15 + src/main/kotlin/events/SlotRenderEvents.kt | 34 ++ src/main/kotlin/events/SoundReceiveEvent.kt | 18 + src/main/kotlin/events/TickEvent.kt | 7 + src/main/kotlin/events/TooltipEvent.kt | 17 + src/main/kotlin/events/UseBlockEvent.kt | 11 + src/main/kotlin/events/WorldKeyboardEvent.kt | 18 + src/main/kotlin/events/WorldReadyEvent.kt | 7 + src/main/kotlin/events/WorldRenderLastEvent.kt | 27 ++ src/main/kotlin/events/registration/ChatEvents.kt | 54 +++ .../kotlin/events/subscription/Subscription.kt | 16 + src/main/kotlin/features/FeatureManager.kt | 120 +++++ src/main/kotlin/features/FirmamentFeature.kt | 23 + src/main/kotlin/features/chat/AutoCompletions.kt | 57 +++ src/main/kotlin/features/chat/ChatLinks.kt | 161 +++++++ src/main/kotlin/features/chat/QuickCommands.kt | 100 +++++ src/main/kotlin/features/debug/DebugLogger.kt | 13 + src/main/kotlin/features/debug/DebugView.kt | 38 ++ .../kotlin/features/debug/DeveloperFeatures.kt | 55 +++ src/main/kotlin/features/debug/MinorTrolling.kt | 27 ++ src/main/kotlin/features/debug/PowerUserTools.kt | 193 ++++++++ .../kotlin/features/diana/AncestralSpadeSolver.kt | 131 ++++++ src/main/kotlin/features/diana/DianaWaypoints.kt | 35 ++ .../kotlin/features/diana/NearbyBurrowsSolver.kt | 144 ++++++ .../events/anniversity/AnniversaryFeatures.kt | 224 ++++++++++ .../features/events/carnival/CarnivalFeatures.kt | 17 + .../features/events/carnival/MinesweeperHelper.kt | 276 ++++++++++++ .../kotlin/features/fixes/CompatibliltyFeatures.kt | 51 +++ src/main/kotlin/features/fixes/Fixes.kt | 71 +++ .../kotlin/features/inventory/CraftingOverlay.kt | 66 +++ .../features/inventory/ItemRarityCosmetics.kt | 85 ++++ src/main/kotlin/features/inventory/PriceData.kt | 51 +++ .../features/inventory/SaveCursorPosition.kt | 66 +++ src/main/kotlin/features/inventory/SlotLocking.kt | 203 +++++++++ .../features/inventory/buttons/InventoryButton.kt | 85 ++++ .../inventory/buttons/InventoryButtonEditor.kt | 184 ++++++++ .../inventory/buttons/InventoryButtonTemplates.kt | 35 ++ .../features/inventory/buttons/InventoryButtons.kt | 88 ++++ .../storageoverlay/StorageBackingHandle.kt | 53 +++ .../inventory/storageoverlay/StorageData.kt | 21 + .../inventory/storageoverlay/StorageOverlay.kt | 154 +++++++ .../storageoverlay/StorageOverlayCustom.kt | 98 +++++ .../storageoverlay/StorageOverlayScreen.kt | 296 +++++++++++++ .../storageoverlay/StorageOverviewScreen.kt | 123 ++++++ .../inventory/storageoverlay/StoragePageSlot.kt | 66 +++ .../inventory/storageoverlay/VirtualInventory.kt | 65 +++ src/main/kotlin/features/mining/Histogram.kt | 81 ++++ src/main/kotlin/features/mining/PickaxeAbility.kt | 176 ++++++++ .../features/mining/PristineProfitTracker.kt | 133 ++++++ .../kotlin/features/notifications/Notifications.kt | 7 + .../kotlin/features/texturepack/AlwaysPredicate.kt | 17 + .../kotlin/features/texturepack/AndPredicate.kt | 26 ++ .../kotlin/features/texturepack/BakedModelExtra.kt | 9 + .../features/texturepack/BakedOverrideData.kt | 8 + .../features/texturepack/CustomBlockTextures.kt | 295 +++++++++++++ .../texturepack/CustomGlobalArmorOverrides.kt | 106 +++++ .../features/texturepack/CustomGlobalTextures.kt | 167 +++++++ .../texturepack/CustomModelOverrideParser.kt | 74 ++++ .../features/texturepack/CustomSkyBlockTextures.kt | 114 +++++ .../features/texturepack/DisplayNamePredicate.kt | 22 + .../texturepack/ExtraAttributesPredicate.kt | 268 +++++++++++ .../texturepack/FirmamentModelPredicate.kt | 8 + .../texturepack/FirmamentModelPredicateParser.kt | 8 + .../kotlin/features/texturepack/ItemPredicate.kt | 32 ++ .../texturepack/JsonUnbakedModelFirmExtra.kt | 10 + .../kotlin/features/texturepack/LorePredicate.kt | 19 + .../features/texturepack/ModelOverrideData.kt | 7 + .../features/texturepack/ModelOverrideFilterSet.kt | 19 + .../kotlin/features/texturepack/NotPredicate.kt | 18 + .../kotlin/features/texturepack/NumberMatcher.kt | 125 ++++++ .../kotlin/features/texturepack/OrPredicate.kt | 26 ++ .../kotlin/features/texturepack/PetPredicate.kt | 66 +++ .../kotlin/features/texturepack/RarityMatcher.kt | 69 +++ .../kotlin/features/texturepack/StringMatcher.kt | 159 +++++++ src/main/kotlin/features/world/FairySouls.kt | 131 ++++++ src/main/kotlin/features/world/NPCWaypoints.kt | 40 ++ .../kotlin/features/world/NavigableWaypoint.kt | 22 + src/main/kotlin/features/world/NavigationHelper.kt | 121 +++++ src/main/kotlin/features/world/NpcWaypointGui.kt | 68 +++ src/main/kotlin/features/world/Waypoints.kt | 297 +++++++++++++ src/main/kotlin/gui/BarComponent.kt | 125 ++++++ src/main/kotlin/gui/FirmButtonComponent.kt | 81 ++++ src/main/kotlin/gui/FirmHoverComponent.kt | 59 +++ src/main/kotlin/gui/FixedComponent.kt | 38 ++ src/main/kotlin/gui/ImageComponent.kt | 33 ++ src/main/kotlin/gui/TickComponent.kt | 18 + src/main/kotlin/gui/config/AllConfigsGui.kt | 46 ++ src/main/kotlin/gui/config/BooleanHandler.kt | 37 ++ src/main/kotlin/gui/config/ClickHandler.kt | 24 + src/main/kotlin/gui/config/DurationHandler.kt | 58 +++ src/main/kotlin/gui/config/GuiAppender.kt | 40 ++ src/main/kotlin/gui/config/HudMetaHandler.kt | 39 ++ src/main/kotlin/gui/config/IntegerHandler.kt | 54 +++ src/main/kotlin/gui/config/JAnyHud.kt | 48 ++ src/main/kotlin/gui/config/KeyBindingHandler.kt | 149 +++++++ src/main/kotlin/gui/config/ManagedConfig.kt | 181 ++++++++ src/main/kotlin/gui/config/ManagedConfigElement.kt | 8 + src/main/kotlin/gui/config/ManagedOption.kt | 62 +++ src/main/kotlin/gui/config/StringHandler.kt | 36 ++ src/main/kotlin/gui/entity/EntityModifier.kt | 9 + src/main/kotlin/gui/entity/EntityRenderer.kt | 197 +++++++++ src/main/kotlin/gui/entity/EntityWidget.kt | 35 ++ src/main/kotlin/gui/entity/FakeWorld.kt | 488 +++++++++++++++++++++ src/main/kotlin/gui/entity/GuiPlayer.kt | 54 +++ src/main/kotlin/gui/entity/ModifyAge.kt | 25 ++ src/main/kotlin/gui/entity/ModifyCharged.kt | 14 + src/main/kotlin/gui/entity/ModifyEquipment.kt | 55 +++ src/main/kotlin/gui/entity/ModifyHorse.kt | 61 +++ src/main/kotlin/gui/entity/ModifyInvisible.kt | 13 + src/main/kotlin/gui/entity/ModifyName.kt | 14 + src/main/kotlin/gui/entity/ModifyPlayerSkin.kt | 47 ++ src/main/kotlin/gui/entity/ModifyRiding.kt | 15 + src/main/kotlin/gui/entity/ModifyWither.kt | 20 + src/main/kotlin/gui/hud/MoulConfigHud.kt | 66 +++ src/main/kotlin/jarvis/JarvisIntegration.kt | 64 +++ .../kotlin/keybindings/FirmamentKeyBindings.kt | 26 ++ src/main/kotlin/keybindings/IKeyBinding.kt | 29 ++ src/main/kotlin/keybindings/SavedKeyBinding.kt | 106 +++++ src/main/kotlin/modmenu/FirmamentModMenuPlugin.kt | 14 + src/main/kotlin/moe/nea/firmament/Firmament.kt | 148 ------- src/main/kotlin/moe/nea/firmament/apis/Profiles.kt | 194 -------- src/main/kotlin/moe/nea/firmament/apis/Routes.kt | 95 ---- .../kotlin/moe/nea/firmament/apis/UrsaManager.kt | 72 --- .../commands/CaseInsensitiveLiteralCommandNode.kt | 75 ---- .../moe/nea/firmament/commands/RestArgumentType.kt | 15 - src/main/kotlin/moe/nea/firmament/commands/dsl.kt | 118 ----- src/main/kotlin/moe/nea/firmament/commands/rome.kt | 230 ---------- .../nea/firmament/compat/SodiumChunkReloader.kt | 12 - .../moe/nea/firmament/events/AllowChatEvent.kt | 16 - .../moe/nea/firmament/events/AttackBlockEvent.kt | 18 - .../nea/firmament/events/BakeExtraModelsEvent.kt | 21 - .../moe/nea/firmament/events/ClientStartedEvent.kt | 6 - .../moe/nea/firmament/events/CommandEvent.kt | 45 -- .../nea/firmament/events/CustomItemModelEvent.kt | 43 -- .../firmament/events/EarlyResourceReloadEvent.kt | 10 - .../moe/nea/firmament/events/EntityDespawnEvent.kt | 11 - .../nea/firmament/events/EntityInteractionEvent.kt | 29 -- .../moe/nea/firmament/events/EntityUpdateEvent.kt | 31 -- .../firmament/events/FeaturesInitializedEvent.kt | 8 - .../events/FinalizeResourceManagerEvent.kt | 10 - .../moe/nea/firmament/events/FirmamentEvent.kt | 38 -- .../moe/nea/firmament/events/FirmamentEventBus.kt | 52 --- .../firmament/events/HandledScreenClickEvent.kt | 10 - .../events/HandledScreenForegroundEvent.kt | 16 - .../events/HandledScreenKeyPressedEvent.kt | 24 - .../firmament/events/HandledScreenPushREIEvent.kt | 18 - .../nea/firmament/events/HotbarItemRenderEvent.kt | 17 - .../moe/nea/firmament/events/HudRenderEvent.kt | 13 - .../nea/firmament/events/IsSlotProtectedEvent.kt | 46 -- .../moe/nea/firmament/events/ItemTooltipEvent.kt | 14 - .../moe/nea/firmament/events/MaskCommands.kt | 13 - .../moe/nea/firmament/events/ModifyChatEvent.kt | 21 - .../nea/firmament/events/OutgoingPacketEvent.kt | 9 - .../moe/nea/firmament/events/ParticleSpawnEvent.kt | 18 - .../nea/firmament/events/PlayerInventoryUpdate.kt | 11 - .../moe/nea/firmament/events/ProcessChatEvent.kt | 28 -- .../firmament/events/ReloadRegistrationEvent.kt | 7 - .../moe/nea/firmament/events/ScreenChangeEvent.kt | 10 - .../nea/firmament/events/ScreenRenderPostEvent.kt | 16 - .../nea/firmament/events/ServerConnectedEvent.kt | 18 - .../firmament/events/SkyblockServerUpdateEvent.kt | 15 - .../moe/nea/firmament/events/SlotClickEvent.kt | 15 - .../moe/nea/firmament/events/SlotRenderEvents.kt | 34 -- .../moe/nea/firmament/events/SoundReceiveEvent.kt | 18 - .../kotlin/moe/nea/firmament/events/TickEvent.kt | 7 - .../moe/nea/firmament/events/TooltipEvent.kt | 17 - .../moe/nea/firmament/events/UseBlockEvent.kt | 11 - .../moe/nea/firmament/events/WorldKeyboardEvent.kt | 18 - .../moe/nea/firmament/events/WorldReadyEvent.kt | 7 - .../nea/firmament/events/WorldRenderLastEvent.kt | 27 -- .../firmament/events/registration/ChatEvents.kt | 54 --- .../firmament/events/subscription/Subscription.kt | 16 - .../moe/nea/firmament/features/FeatureManager.kt | 120 ----- .../moe/nea/firmament/features/FirmamentFeature.kt | 23 - .../nea/firmament/features/chat/AutoCompletions.kt | 57 --- .../moe/nea/firmament/features/chat/ChatLinks.kt | 161 ------- .../nea/firmament/features/chat/QuickCommands.kt | 100 ----- .../nea/firmament/features/debug/DebugLogger.kt | 13 - .../moe/nea/firmament/features/debug/DebugView.kt | 38 -- .../firmament/features/debug/DeveloperFeatures.kt | 55 --- .../nea/firmament/features/debug/MinorTrolling.kt | 27 -- .../nea/firmament/features/debug/PowerUserTools.kt | 193 -------- .../features/diana/AncestralSpadeSolver.kt | 131 ------ .../nea/firmament/features/diana/DianaWaypoints.kt | 35 -- .../features/diana/NearbyBurrowsSolver.kt | 144 ------ .../events/anniversity/AnniversaryFeatures.kt | 224 ---------- .../features/events/carnival/CarnivalFeatures.kt | 17 - .../features/events/carnival/MinesweeperHelper.kt | 276 ------------ .../features/fixes/CompatibliltyFeatures.kt | 51 --- .../moe/nea/firmament/features/fixes/Fixes.kt | 71 --- .../features/inventory/CraftingOverlay.kt | 66 --- .../features/inventory/ItemRarityCosmetics.kt | 85 ---- .../nea/firmament/features/inventory/PriceData.kt | 51 --- .../features/inventory/SaveCursorPosition.kt | 66 --- .../firmament/features/inventory/SlotLocking.kt | 203 --------- .../features/inventory/buttons/InventoryButton.kt | 85 ---- .../inventory/buttons/InventoryButtonEditor.kt | 184 -------- .../inventory/buttons/InventoryButtonTemplates.kt | 35 -- .../features/inventory/buttons/InventoryButtons.kt | 88 ---- .../storageoverlay/StorageBackingHandle.kt | 53 --- .../inventory/storageoverlay/StorageData.kt | 21 - .../inventory/storageoverlay/StorageOverlay.kt | 154 ------- .../storageoverlay/StorageOverlayCustom.kt | 98 ----- .../storageoverlay/StorageOverlayScreen.kt | 296 ------------- .../storageoverlay/StorageOverviewScreen.kt | 123 ------ .../inventory/storageoverlay/StoragePageSlot.kt | 66 --- .../inventory/storageoverlay/VirtualInventory.kt | 65 --- .../moe/nea/firmament/features/mining/Histogram.kt | 81 ---- .../firmament/features/mining/PickaxeAbility.kt | 176 -------- .../features/mining/PristineProfitTracker.kt | 133 ------ .../features/notifications/Notifications.kt | 7 - .../features/texturepack/AlwaysPredicate.kt | 17 - .../firmament/features/texturepack/AndPredicate.kt | 26 -- .../features/texturepack/BakedModelExtra.kt | 9 - .../features/texturepack/BakedOverrideData.kt | 8 - .../features/texturepack/CustomBlockTextures.kt | 296 ------------- .../texturepack/CustomGlobalArmorOverrides.kt | 106 ----- .../features/texturepack/CustomGlobalTextures.kt | 167 ------- .../texturepack/CustomModelOverrideParser.kt | 74 ---- .../features/texturepack/CustomSkyBlockTextures.kt | 114 ----- .../features/texturepack/DisplayNamePredicate.kt | 22 - .../texturepack/ExtraAttributesPredicate.kt | 268 ----------- .../texturepack/FirmamentModelPredicate.kt | 8 - .../texturepack/FirmamentModelPredicateParser.kt | 8 - .../features/texturepack/ItemPredicate.kt | 32 -- .../texturepack/JsonUnbakedModelFirmExtra.kt | 10 - .../features/texturepack/LorePredicate.kt | 19 - .../features/texturepack/ModelOverrideData.kt | 7 - .../features/texturepack/ModelOverrideFilterSet.kt | 19 - .../firmament/features/texturepack/NotPredicate.kt | 18 - .../features/texturepack/NumberMatcher.kt | 125 ------ .../firmament/features/texturepack/OrPredicate.kt | 26 -- .../firmament/features/texturepack/PetPredicate.kt | 66 --- .../features/texturepack/RarityMatcher.kt | 69 --- .../features/texturepack/StringMatcher.kt | 159 ------- .../moe/nea/firmament/features/world/FairySouls.kt | 131 ------ .../nea/firmament/features/world/NPCWaypoints.kt | 40 -- .../firmament/features/world/NavigableWaypoint.kt | 22 - .../firmament/features/world/NavigationHelper.kt | 121 ----- .../nea/firmament/features/world/NpcWaypointGui.kt | 68 --- .../moe/nea/firmament/features/world/Waypoints.kt | 297 ------------- .../kotlin/moe/nea/firmament/gui/BarComponent.kt | 125 ------ .../moe/nea/firmament/gui/FirmButtonComponent.kt | 81 ---- .../moe/nea/firmament/gui/FirmHoverComponent.kt | 59 --- .../kotlin/moe/nea/firmament/gui/FixedComponent.kt | 38 -- .../kotlin/moe/nea/firmament/gui/ImageComponent.kt | 33 -- .../kotlin/moe/nea/firmament/gui/TickComponent.kt | 18 - .../moe/nea/firmament/gui/config/AllConfigsGui.kt | 46 -- .../moe/nea/firmament/gui/config/BooleanHandler.kt | 37 -- .../moe/nea/firmament/gui/config/ClickHandler.kt | 24 - .../nea/firmament/gui/config/DurationHandler.kt | 58 --- .../moe/nea/firmament/gui/config/GuiAppender.kt | 40 -- .../moe/nea/firmament/gui/config/HudMetaHandler.kt | 39 -- .../moe/nea/firmament/gui/config/IntegerHandler.kt | 54 --- .../kotlin/moe/nea/firmament/gui/config/JAnyHud.kt | 48 -- .../nea/firmament/gui/config/KeyBindingHandler.kt | 149 ------- .../moe/nea/firmament/gui/config/ManagedConfig.kt | 181 -------- .../firmament/gui/config/ManagedConfigElement.kt | 8 - .../moe/nea/firmament/gui/config/ManagedOption.kt | 62 --- .../moe/nea/firmament/gui/config/StringHandler.kt | 36 -- .../moe/nea/firmament/gui/entity/EntityModifier.kt | 9 - .../moe/nea/firmament/gui/entity/EntityRenderer.kt | 197 --------- .../moe/nea/firmament/gui/entity/EntityWidget.kt | 35 -- .../moe/nea/firmament/gui/entity/FakeWorld.kt | 488 --------------------- .../moe/nea/firmament/gui/entity/GuiPlayer.kt | 54 --- .../moe/nea/firmament/gui/entity/ModifyAge.kt | 25 -- .../moe/nea/firmament/gui/entity/ModifyCharged.kt | 14 - .../nea/firmament/gui/entity/ModifyEquipment.kt | 55 --- .../moe/nea/firmament/gui/entity/ModifyHorse.kt | 61 --- .../nea/firmament/gui/entity/ModifyInvisible.kt | 13 - .../moe/nea/firmament/gui/entity/ModifyName.kt | 14 - .../nea/firmament/gui/entity/ModifyPlayerSkin.kt | 47 -- .../moe/nea/firmament/gui/entity/ModifyRiding.kt | 15 - .../moe/nea/firmament/gui/entity/ModifyWither.kt | 20 - .../moe/nea/firmament/gui/hud/MoulConfigHud.kt | 66 --- .../moe/nea/firmament/jarvis/JarvisIntegration.kt | 64 --- .../firmament/keybindings/FirmamentKeyBindings.kt | 26 -- .../moe/nea/firmament/keybindings/IKeyBinding.kt | 29 -- .../nea/firmament/keybindings/SavedKeyBinding.kt | 106 ----- .../firmament/modmenu/FirmamentModMenuPlugin.kt | 14 - .../moe/nea/firmament/rei/FirmamentReiPlugin.kt | 128 ------ .../moe/nea/firmament/rei/NEUItemEntryRenderer.kt | 186 -------- .../nea/firmament/rei/NEUItemEntrySerializer.kt | 29 -- .../moe/nea/firmament/rei/SBItemEntryDefinition.kt | 254 ----------- .../rei/SkyblockCraftingRecipeDynamicGenerator.kt | 64 --- .../rei/SkyblockItemIdFocusedStackProvider.kt | 25 -- src/main/kotlin/moe/nea/firmament/rei/math.kt | 10 - .../nea/firmament/rei/recipes/SBCraftingRecipe.kt | 55 --- .../rei/recipes/SBEssenceUpgradeRecipe.kt | 62 --- .../moe/nea/firmament/rei/recipes/SBForgeRecipe.kt | 71 --- .../moe/nea/firmament/rei/recipes/SBKatRecipe.kt | 224 ---------- .../nea/firmament/rei/recipes/SBMobDropRecipe.kt | 108 ----- .../moe/nea/firmament/rei/recipes/SBRecipe.kt | 31 -- .../nea/firmament/repo/BetterRepoRecipeCache.kt | 28 -- .../nea/firmament/repo/EssenceRecipeProvider.kt | 50 --- .../kotlin/moe/nea/firmament/repo/ExpLadder.kt | 94 ---- .../moe/nea/firmament/repo/HypixelStaticData.kt | 107 ----- .../kotlin/moe/nea/firmament/repo/ItemCache.kt | 215 --------- .../moe/nea/firmament/repo/ItemNameLookup.kt | 98 ----- .../moe/nea/firmament/repo/RepoDownloadManager.kt | 128 ------ .../kotlin/moe/nea/firmament/repo/RepoManager.kt | 145 ------ .../moe/nea/firmament/repo/RepoModResourcePack.kt | 126 ------ .../kotlin/moe/nea/firmament/util/Base64Util.kt | 10 - .../moe/nea/firmament/util/BazaarPriceStrategy.kt | 19 - .../moe/nea/firmament/util/ClipboardUtils.kt | 24 - .../moe/nea/firmament/util/CommonSoundEffects.kt | 26 -- .../moe/nea/firmament/util/DurabilityBarEvent.kt | 20 - .../kotlin/moe/nea/firmament/util/ErrorBoundary.kt | 10 - .../moe/nea/firmament/util/FirmFormatters.kt | 59 --- .../moe/nea/firmament/util/FragmentGuiScreen.kt | 93 ---- .../kotlin/moe/nea/firmament/util/GetRectangle.kt | 17 - .../moe/nea/firmament/util/HoveredItemStack.kt | 31 -- .../moe/nea/firmament/util/IdentifierSerializer.kt | 25 -- .../nea/firmament/util/IdentityCharacteristics.kt | 15 - src/main/kotlin/moe/nea/firmament/util/ItemUtil.kt | 26 -- .../moe/nea/firmament/util/LegacyFormattingCode.kt | 35 -- .../moe/nea/firmament/util/LegacyTagParser.kt | 245 ----------- .../kotlin/moe/nea/firmament/util/LoadResource.kt | 20 - src/main/kotlin/moe/nea/firmament/util/Locraw.kt | 12 - .../kotlin/moe/nea/firmament/util/LogIfNull.kt | 8 - src/main/kotlin/moe/nea/firmament/util/MC.kt | 94 ---- .../moe/nea/firmament/util/MinecraftDispatcher.kt | 8 - .../moe/nea/firmament/util/MoulConfigFragment.kt | 44 -- .../moe/nea/firmament/util/MoulConfigUtils.kt | 230 ---------- .../nea/firmament/util/MutableMapWithMaxSize.kt | 38 -- src/main/kotlin/moe/nea/firmament/util/SBData.kt | 66 --- .../moe/nea/firmament/util/ScoreboardUtil.kt | 45 -- .../kotlin/moe/nea/firmament/util/ScreenUtil.kt | 38 -- .../kotlin/moe/nea/firmament/util/SequenceUtil.kt | 11 - .../moe/nea/firmament/util/SkyBlockIsland.kt | 42 -- .../kotlin/moe/nea/firmament/util/SkyblockId.kt | 149 ------- .../moe/nea/firmament/util/SortedMapSerializer.kt | 25 -- .../kotlin/moe/nea/firmament/util/TemplateUtil.kt | 85 ---- src/main/kotlin/moe/nea/firmament/util/TimeMark.kt | 44 -- src/main/kotlin/moe/nea/firmament/util/Timer.kt | 25 -- src/main/kotlin/moe/nea/firmament/util/WarpUtil.kt | 75 ---- .../kotlin/moe/nea/firmament/util/assertions.kt | 25 -- .../kotlin/moe/nea/firmament/util/async/input.kt | 47 -- .../moe/nea/firmament/util/colorconversion.kt | 13 - .../util/customgui/CoordRememberingSlot.kt | 14 - .../moe/nea/firmament/util/customgui/CustomGui.kt | 72 --- .../nea/firmament/util/customgui/HasCustomGui.kt | 17 - .../moe/nea/firmament/util/data/DataHolder.kt | 62 --- .../moe/nea/firmament/util/data/IDataHolder.kt | 77 ---- .../util/data/ProfileSpecificDataHolder.kt | 84 ---- .../nea/firmament/util/filter/IteratorFilterSet.kt | 33 -- .../moe/nea/firmament/util/item/NbtItemData.kt | 24 - .../moe/nea/firmament/util/item/SkullItemData.kt | 90 ---- .../nea/firmament/util/json/BlockPosSerializer.kt | 25 -- .../firmament/util/json/DashlessUUIDSerializer.kt | 29 -- .../firmament/util/json/InstantAsLongSerializer.kt | 22 - .../util/json/SingletonSerializableList.kt | 31 -- src/main/kotlin/moe/nea/firmament/util/listutil.kt | 9 - .../kotlin/moe/nea/firmament/util/propertyutil.kt | 9 - src/main/kotlin/moe/nea/firmament/util/regex.kt | 55 --- .../util/render/FacingThePlayerContext.kt | 101 ----- .../moe/nea/firmament/util/render/LerpUtils.kt | 33 -- .../firmament/util/render/RenderCircleProgress.kt | 95 ---- .../nea/firmament/util/render/RenderContextDSL.kt | 6 - .../firmament/util/render/RenderInWorldContext.kt | 294 ------------- .../firmament/util/render/TranslatedScissors.kt | 22 - .../kotlin/moe/nea/firmament/util/stringutil.kt | 6 - src/main/kotlin/moe/nea/firmament/util/textutil.kt | 117 ----- src/main/kotlin/moe/nea/firmament/util/uuid.kt | 12 - src/main/kotlin/rei/FirmamentReiPlugin.kt | 128 ++++++ src/main/kotlin/rei/NEUItemEntryRenderer.kt | 186 ++++++++ src/main/kotlin/rei/NEUItemEntrySerializer.kt | 29 ++ src/main/kotlin/rei/SBItemEntryDefinition.kt | 254 +++++++++++ .../rei/SkyblockCraftingRecipeDynamicGenerator.kt | 64 +++ .../rei/SkyblockItemIdFocusedStackProvider.kt | 25 ++ src/main/kotlin/rei/math.kt | 10 + src/main/kotlin/rei/recipes/SBCraftingRecipe.kt | 55 +++ .../kotlin/rei/recipes/SBEssenceUpgradeRecipe.kt | 62 +++ src/main/kotlin/rei/recipes/SBForgeRecipe.kt | 71 +++ src/main/kotlin/rei/recipes/SBKatRecipe.kt | 224 ++++++++++ src/main/kotlin/rei/recipes/SBMobDropRecipe.kt | 108 +++++ src/main/kotlin/rei/recipes/SBRecipe.kt | 31 ++ src/main/kotlin/repo/BetterRepoRecipeCache.kt | 28 ++ src/main/kotlin/repo/EssenceRecipeProvider.kt | 50 +++ src/main/kotlin/repo/ExpLadder.kt | 94 ++++ src/main/kotlin/repo/HypixelStaticData.kt | 107 +++++ src/main/kotlin/repo/ItemCache.kt | 215 +++++++++ src/main/kotlin/repo/ItemNameLookup.kt | 98 +++++ src/main/kotlin/repo/RepoDownloadManager.kt | 128 ++++++ src/main/kotlin/repo/RepoManager.kt | 145 ++++++ src/main/kotlin/repo/RepoModResourcePack.kt | 126 ++++++ src/main/kotlin/util/Base64Util.kt | 10 + src/main/kotlin/util/BazaarPriceStrategy.kt | 19 + src/main/kotlin/util/ClipboardUtils.kt | 24 + src/main/kotlin/util/CommonSoundEffects.kt | 26 ++ src/main/kotlin/util/DurabilityBarEvent.kt | 20 + src/main/kotlin/util/ErrorBoundary.kt | 10 + src/main/kotlin/util/FirmFormatters.kt | 59 +++ src/main/kotlin/util/FragmentGuiScreen.kt | 93 ++++ src/main/kotlin/util/GetRectangle.kt | 17 + src/main/kotlin/util/HoveredItemStack.kt | 31 ++ src/main/kotlin/util/IdentifierSerializer.kt | 25 ++ src/main/kotlin/util/IdentityCharacteristics.kt | 15 + src/main/kotlin/util/ItemUtil.kt | 26 ++ src/main/kotlin/util/LegacyFormattingCode.kt | 35 ++ src/main/kotlin/util/LegacyTagParser.kt | 245 +++++++++++ src/main/kotlin/util/LoadResource.kt | 20 + src/main/kotlin/util/Locraw.kt | 12 + src/main/kotlin/util/LogIfNull.kt | 8 + src/main/kotlin/util/MC.kt | 94 ++++ src/main/kotlin/util/MinecraftDispatcher.kt | 8 + src/main/kotlin/util/MoulConfigFragment.kt | 44 ++ src/main/kotlin/util/MoulConfigUtils.kt | 230 ++++++++++ src/main/kotlin/util/MutableMapWithMaxSize.kt | 38 ++ src/main/kotlin/util/SBData.kt | 66 +++ src/main/kotlin/util/ScoreboardUtil.kt | 45 ++ src/main/kotlin/util/ScreenUtil.kt | 38 ++ src/main/kotlin/util/SequenceUtil.kt | 11 + src/main/kotlin/util/SkyBlockIsland.kt | 42 ++ src/main/kotlin/util/SkyblockId.kt | 149 +++++++ src/main/kotlin/util/SortedMapSerializer.kt | 25 ++ src/main/kotlin/util/TemplateUtil.kt | 85 ++++ src/main/kotlin/util/TimeMark.kt | 44 ++ src/main/kotlin/util/Timer.kt | 25 ++ src/main/kotlin/util/WarpUtil.kt | 75 ++++ src/main/kotlin/util/assertions.kt | 25 ++ src/main/kotlin/util/async/input.kt | 47 ++ src/main/kotlin/util/colorconversion.kt | 13 + .../kotlin/util/customgui/CoordRememberingSlot.kt | 14 + src/main/kotlin/util/customgui/CustomGui.kt | 72 +++ src/main/kotlin/util/customgui/HasCustomGui.kt | 17 + src/main/kotlin/util/data/DataHolder.kt | 62 +++ src/main/kotlin/util/data/IDataHolder.kt | 77 ++++ .../kotlin/util/data/ProfileSpecificDataHolder.kt | 84 ++++ src/main/kotlin/util/filter/IteratorFilterSet.kt | 33 ++ src/main/kotlin/util/item/NbtItemData.kt | 24 + src/main/kotlin/util/item/SkullItemData.kt | 90 ++++ src/main/kotlin/util/json/BlockPosSerializer.kt | 25 ++ .../kotlin/util/json/DashlessUUIDSerializer.kt | 29 ++ .../kotlin/util/json/InstantAsLongSerializer.kt | 22 + .../kotlin/util/json/SingletonSerializableList.kt | 31 ++ src/main/kotlin/util/listutil.kt | 9 + src/main/kotlin/util/propertyutil.kt | 9 + src/main/kotlin/util/regex.kt | 55 +++ .../kotlin/util/render/FacingThePlayerContext.kt | 101 +++++ src/main/kotlin/util/render/LerpUtils.kt | 33 ++ .../kotlin/util/render/RenderCircleProgress.kt | 95 ++++ src/main/kotlin/util/render/RenderContextDSL.kt | 6 + .../kotlin/util/render/RenderInWorldContext.kt | 294 +++++++++++++ src/main/kotlin/util/render/TranslatedScissors.kt | 22 + src/main/kotlin/util/stringutil.kt | 6 + src/main/kotlin/util/textutil.kt | 117 +++++ src/main/kotlin/util/uuid.kt | 12 + 498 files changed, 16263 insertions(+), 16006 deletions(-) create mode 100644 src/compat/sodium/java/SodiumChunkReloader.kt create mode 100644 src/compat/sodium/java/moe/nea/firmament/mixins/accessor/sodium/AccessorSodiumWorldRenderer.java create mode 100644 src/compat/sodium/java/moe/nea/firmament/mixins/custommodels/PatchBlockModelInSodiumChunkGenerator.java create mode 100644 src/main/java/moe/nea/firmament/init/AutoDiscoveryPlugin.java delete mode 100644 src/main/java/moe/nea/firmament/mixins/accessor/sodium/AccessorSodiumWorldRenderer.java delete mode 100644 src/main/java/moe/nea/firmament/mixins/custommodels/PatchBlockModelInSodiumChunkGenerator.java create mode 100644 src/main/kotlin/Firmament.kt create mode 100644 src/main/kotlin/apis/Profiles.kt create mode 100644 src/main/kotlin/apis/Routes.kt create mode 100644 src/main/kotlin/apis/UrsaManager.kt create mode 100644 src/main/kotlin/commands/CaseInsensitiveLiteralCommandNode.kt create mode 100644 src/main/kotlin/commands/RestArgumentType.kt create mode 100644 src/main/kotlin/commands/dsl.kt create mode 100644 src/main/kotlin/commands/rome.kt create mode 100644 src/main/kotlin/events/AllowChatEvent.kt create mode 100644 src/main/kotlin/events/AttackBlockEvent.kt create mode 100644 src/main/kotlin/events/BakeExtraModelsEvent.kt create mode 100644 src/main/kotlin/events/ClientStartedEvent.kt create mode 100644 src/main/kotlin/events/CommandEvent.kt create mode 100644 src/main/kotlin/events/CustomItemModelEvent.kt create mode 100644 src/main/kotlin/events/EarlyResourceReloadEvent.kt create mode 100644 src/main/kotlin/events/EntityDespawnEvent.kt create mode 100644 src/main/kotlin/events/EntityInteractionEvent.kt create mode 100644 src/main/kotlin/events/EntityUpdateEvent.kt create mode 100644 src/main/kotlin/events/FeaturesInitializedEvent.kt create mode 100644 src/main/kotlin/events/FinalizeResourceManagerEvent.kt create mode 100644 src/main/kotlin/events/FirmamentEvent.kt create mode 100644 src/main/kotlin/events/FirmamentEventBus.kt create mode 100644 src/main/kotlin/events/HandledScreenClickEvent.kt create mode 100644 src/main/kotlin/events/HandledScreenForegroundEvent.kt create mode 100644 src/main/kotlin/events/HandledScreenKeyPressedEvent.kt create mode 100644 src/main/kotlin/events/HandledScreenPushREIEvent.kt create mode 100644 src/main/kotlin/events/HotbarItemRenderEvent.kt create mode 100644 src/main/kotlin/events/HudRenderEvent.kt create mode 100644 src/main/kotlin/events/IsSlotProtectedEvent.kt create mode 100644 src/main/kotlin/events/ItemTooltipEvent.kt create mode 100644 src/main/kotlin/events/MaskCommands.kt create mode 100644 src/main/kotlin/events/ModifyChatEvent.kt create mode 100644 src/main/kotlin/events/OutgoingPacketEvent.kt create mode 100644 src/main/kotlin/events/ParticleSpawnEvent.kt create mode 100644 src/main/kotlin/events/PlayerInventoryUpdate.kt create mode 100644 src/main/kotlin/events/ProcessChatEvent.kt create mode 100644 src/main/kotlin/events/ReloadRegistrationEvent.kt create mode 100644 src/main/kotlin/events/ScreenChangeEvent.kt create mode 100644 src/main/kotlin/events/ScreenRenderPostEvent.kt create mode 100644 src/main/kotlin/events/ServerConnectedEvent.kt create mode 100644 src/main/kotlin/events/SkyblockServerUpdateEvent.kt create mode 100644 src/main/kotlin/events/SlotClickEvent.kt create mode 100644 src/main/kotlin/events/SlotRenderEvents.kt create mode 100644 src/main/kotlin/events/SoundReceiveEvent.kt create mode 100644 src/main/kotlin/events/TickEvent.kt create mode 100644 src/main/kotlin/events/TooltipEvent.kt create mode 100644 src/main/kotlin/events/UseBlockEvent.kt create mode 100644 src/main/kotlin/events/WorldKeyboardEvent.kt create mode 100644 src/main/kotlin/events/WorldReadyEvent.kt create mode 100644 src/main/kotlin/events/WorldRenderLastEvent.kt create mode 100644 src/main/kotlin/events/registration/ChatEvents.kt create mode 100644 src/main/kotlin/events/subscription/Subscription.kt create mode 100644 src/main/kotlin/features/FeatureManager.kt create mode 100644 src/main/kotlin/features/FirmamentFeature.kt create mode 100644 src/main/kotlin/features/chat/AutoCompletions.kt create mode 100644 src/main/kotlin/features/chat/ChatLinks.kt create mode 100644 src/main/kotlin/features/chat/QuickCommands.kt create mode 100644 src/main/kotlin/features/debug/DebugLogger.kt create mode 100644 src/main/kotlin/features/debug/DebugView.kt create mode 100644 src/main/kotlin/features/debug/DeveloperFeatures.kt create mode 100644 src/main/kotlin/features/debug/MinorTrolling.kt create mode 100644 src/main/kotlin/features/debug/PowerUserTools.kt create mode 100644 src/main/kotlin/features/diana/AncestralSpadeSolver.kt create mode 100644 src/main/kotlin/features/diana/DianaWaypoints.kt create mode 100644 src/main/kotlin/features/diana/NearbyBurrowsSolver.kt create mode 100644 src/main/kotlin/features/events/anniversity/AnniversaryFeatures.kt create mode 100644 src/main/kotlin/features/events/carnival/CarnivalFeatures.kt create mode 100644 src/main/kotlin/features/events/carnival/MinesweeperHelper.kt create mode 100644 src/main/kotlin/features/fixes/CompatibliltyFeatures.kt create mode 100644 src/main/kotlin/features/fixes/Fixes.kt create mode 100644 src/main/kotlin/features/inventory/CraftingOverlay.kt create mode 100644 src/main/kotlin/features/inventory/ItemRarityCosmetics.kt create mode 100644 src/main/kotlin/features/inventory/PriceData.kt create mode 100644 src/main/kotlin/features/inventory/SaveCursorPosition.kt create mode 100644 src/main/kotlin/features/inventory/SlotLocking.kt create mode 100644 src/main/kotlin/features/inventory/buttons/InventoryButton.kt create mode 100644 src/main/kotlin/features/inventory/buttons/InventoryButtonEditor.kt create mode 100644 src/main/kotlin/features/inventory/buttons/InventoryButtonTemplates.kt create mode 100644 src/main/kotlin/features/inventory/buttons/InventoryButtons.kt create mode 100644 src/main/kotlin/features/inventory/storageoverlay/StorageBackingHandle.kt create mode 100644 src/main/kotlin/features/inventory/storageoverlay/StorageData.kt create mode 100644 src/main/kotlin/features/inventory/storageoverlay/StorageOverlay.kt create mode 100644 src/main/kotlin/features/inventory/storageoverlay/StorageOverlayCustom.kt create mode 100644 src/main/kotlin/features/inventory/storageoverlay/StorageOverlayScreen.kt create mode 100644 src/main/kotlin/features/inventory/storageoverlay/StorageOverviewScreen.kt create mode 100644 src/main/kotlin/features/inventory/storageoverlay/StoragePageSlot.kt create mode 100644 src/main/kotlin/features/inventory/storageoverlay/VirtualInventory.kt create mode 100644 src/main/kotlin/features/mining/Histogram.kt create mode 100644 src/main/kotlin/features/mining/PickaxeAbility.kt create mode 100644 src/main/kotlin/features/mining/PristineProfitTracker.kt create mode 100644 src/main/kotlin/features/notifications/Notifications.kt create mode 100644 src/main/kotlin/features/texturepack/AlwaysPredicate.kt create mode 100644 src/main/kotlin/features/texturepack/AndPredicate.kt create mode 100644 src/main/kotlin/features/texturepack/BakedModelExtra.kt create mode 100644 src/main/kotlin/features/texturepack/BakedOverrideData.kt create mode 100644 src/main/kotlin/features/texturepack/CustomBlockTextures.kt create mode 100644 src/main/kotlin/features/texturepack/CustomGlobalArmorOverrides.kt create mode 100644 src/main/kotlin/features/texturepack/CustomGlobalTextures.kt create mode 100644 src/main/kotlin/features/texturepack/CustomModelOverrideParser.kt create mode 100644 src/main/kotlin/features/texturepack/CustomSkyBlockTextures.kt create mode 100644 src/main/kotlin/features/texturepack/DisplayNamePredicate.kt create mode 100644 src/main/kotlin/features/texturepack/ExtraAttributesPredicate.kt create mode 100644 src/main/kotlin/features/texturepack/FirmamentModelPredicate.kt create mode 100644 src/main/kotlin/features/texturepack/FirmamentModelPredicateParser.kt create mode 100644 src/main/kotlin/features/texturepack/ItemPredicate.kt create mode 100644 src/main/kotlin/features/texturepack/JsonUnbakedModelFirmExtra.kt create mode 100644 src/main/kotlin/features/texturepack/LorePredicate.kt create mode 100644 src/main/kotlin/features/texturepack/ModelOverrideData.kt create mode 100644 src/main/kotlin/features/texturepack/ModelOverrideFilterSet.kt create mode 100644 src/main/kotlin/features/texturepack/NotPredicate.kt create mode 100644 src/main/kotlin/features/texturepack/NumberMatcher.kt create mode 100644 src/main/kotlin/features/texturepack/OrPredicate.kt create mode 100644 src/main/kotlin/features/texturepack/PetPredicate.kt create mode 100644 src/main/kotlin/features/texturepack/RarityMatcher.kt create mode 100644 src/main/kotlin/features/texturepack/StringMatcher.kt create mode 100644 src/main/kotlin/features/world/FairySouls.kt create mode 100644 src/main/kotlin/features/world/NPCWaypoints.kt create mode 100644 src/main/kotlin/features/world/NavigableWaypoint.kt create mode 100644 src/main/kotlin/features/world/NavigationHelper.kt create mode 100644 src/main/kotlin/features/world/NpcWaypointGui.kt create mode 100644 src/main/kotlin/features/world/Waypoints.kt create mode 100644 src/main/kotlin/gui/BarComponent.kt create mode 100644 src/main/kotlin/gui/FirmButtonComponent.kt create mode 100644 src/main/kotlin/gui/FirmHoverComponent.kt create mode 100644 src/main/kotlin/gui/FixedComponent.kt create mode 100644 src/main/kotlin/gui/ImageComponent.kt create mode 100644 src/main/kotlin/gui/TickComponent.kt create mode 100644 src/main/kotlin/gui/config/AllConfigsGui.kt create mode 100644 src/main/kotlin/gui/config/BooleanHandler.kt create mode 100644 src/main/kotlin/gui/config/ClickHandler.kt create mode 100644 src/main/kotlin/gui/config/DurationHandler.kt create mode 100644 src/main/kotlin/gui/config/GuiAppender.kt create mode 100644 src/main/kotlin/gui/config/HudMetaHandler.kt create mode 100644 src/main/kotlin/gui/config/IntegerHandler.kt create mode 100644 src/main/kotlin/gui/config/JAnyHud.kt create mode 100644 src/main/kotlin/gui/config/KeyBindingHandler.kt create mode 100644 src/main/kotlin/gui/config/ManagedConfig.kt create mode 100644 src/main/kotlin/gui/config/ManagedConfigElement.kt create mode 100644 src/main/kotlin/gui/config/ManagedOption.kt create mode 100644 src/main/kotlin/gui/config/StringHandler.kt create mode 100644 src/main/kotlin/gui/entity/EntityModifier.kt create mode 100644 src/main/kotlin/gui/entity/EntityRenderer.kt create mode 100644 src/main/kotlin/gui/entity/EntityWidget.kt create mode 100644 src/main/kotlin/gui/entity/FakeWorld.kt create mode 100644 src/main/kotlin/gui/entity/GuiPlayer.kt create mode 100644 src/main/kotlin/gui/entity/ModifyAge.kt create mode 100644 src/main/kotlin/gui/entity/ModifyCharged.kt create mode 100644 src/main/kotlin/gui/entity/ModifyEquipment.kt create mode 100644 src/main/kotlin/gui/entity/ModifyHorse.kt create mode 100644 src/main/kotlin/gui/entity/ModifyInvisible.kt create mode 100644 src/main/kotlin/gui/entity/ModifyName.kt create mode 100644 src/main/kotlin/gui/entity/ModifyPlayerSkin.kt create mode 100644 src/main/kotlin/gui/entity/ModifyRiding.kt create mode 100644 src/main/kotlin/gui/entity/ModifyWither.kt create mode 100644 src/main/kotlin/gui/hud/MoulConfigHud.kt create mode 100644 src/main/kotlin/jarvis/JarvisIntegration.kt create mode 100644 src/main/kotlin/keybindings/FirmamentKeyBindings.kt create mode 100644 src/main/kotlin/keybindings/IKeyBinding.kt create mode 100644 src/main/kotlin/keybindings/SavedKeyBinding.kt create mode 100644 src/main/kotlin/modmenu/FirmamentModMenuPlugin.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/Firmament.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/apis/Profiles.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/apis/Routes.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/apis/UrsaManager.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/commands/CaseInsensitiveLiteralCommandNode.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/commands/RestArgumentType.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/commands/dsl.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/commands/rome.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/compat/SodiumChunkReloader.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/events/AllowChatEvent.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/events/AttackBlockEvent.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/events/BakeExtraModelsEvent.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/events/ClientStartedEvent.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/events/CommandEvent.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/events/CustomItemModelEvent.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/events/EarlyResourceReloadEvent.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/events/EntityDespawnEvent.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/events/EntityInteractionEvent.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/events/EntityUpdateEvent.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/events/FeaturesInitializedEvent.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/events/FinalizeResourceManagerEvent.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/events/FirmamentEvent.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/events/FirmamentEventBus.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/events/HandledScreenClickEvent.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/events/HandledScreenForegroundEvent.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/events/HandledScreenKeyPressedEvent.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/events/HandledScreenPushREIEvent.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/events/HotbarItemRenderEvent.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/events/HudRenderEvent.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/events/IsSlotProtectedEvent.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/events/ItemTooltipEvent.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/events/MaskCommands.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/events/ModifyChatEvent.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/events/OutgoingPacketEvent.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/events/ParticleSpawnEvent.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/events/PlayerInventoryUpdate.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/events/ProcessChatEvent.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/events/ReloadRegistrationEvent.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/events/ScreenChangeEvent.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/events/ScreenRenderPostEvent.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/events/ServerConnectedEvent.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/events/SkyblockServerUpdateEvent.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/events/SlotClickEvent.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/events/SlotRenderEvents.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/events/SoundReceiveEvent.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/events/TickEvent.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/events/TooltipEvent.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/events/UseBlockEvent.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/events/WorldKeyboardEvent.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/events/WorldReadyEvent.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/events/WorldRenderLastEvent.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/events/registration/ChatEvents.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/events/subscription/Subscription.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/FirmamentFeature.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/chat/AutoCompletions.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/chat/ChatLinks.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/chat/QuickCommands.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/debug/DebugLogger.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/debug/DebugView.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/debug/DeveloperFeatures.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/debug/MinorTrolling.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/debug/PowerUserTools.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/diana/AncestralSpadeSolver.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/diana/DianaWaypoints.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/diana/NearbyBurrowsSolver.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/events/anniversity/AnniversaryFeatures.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/events/carnival/CarnivalFeatures.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/events/carnival/MinesweeperHelper.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/fixes/CompatibliltyFeatures.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/fixes/Fixes.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/inventory/CraftingOverlay.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/inventory/ItemRarityCosmetics.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/inventory/PriceData.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/inventory/SaveCursorPosition.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/inventory/SlotLocking.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/inventory/buttons/InventoryButton.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/inventory/buttons/InventoryButtonEditor.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/inventory/buttons/InventoryButtonTemplates.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/inventory/buttons/InventoryButtons.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageBackingHandle.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageData.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverlay.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverlayCustom.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverlayScreen.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverviewScreen.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StoragePageSlot.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/VirtualInventory.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/mining/Histogram.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/mining/PickaxeAbility.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/mining/PristineProfitTracker.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/notifications/Notifications.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/texturepack/AlwaysPredicate.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/texturepack/AndPredicate.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/texturepack/BakedModelExtra.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/texturepack/BakedOverrideData.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/texturepack/CustomBlockTextures.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/texturepack/CustomGlobalArmorOverrides.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/texturepack/CustomGlobalTextures.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/texturepack/CustomModelOverrideParser.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/texturepack/CustomSkyBlockTextures.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/texturepack/DisplayNamePredicate.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/texturepack/ExtraAttributesPredicate.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/texturepack/FirmamentModelPredicate.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/texturepack/FirmamentModelPredicateParser.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/texturepack/ItemPredicate.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/texturepack/JsonUnbakedModelFirmExtra.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/texturepack/LorePredicate.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/texturepack/ModelOverrideData.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/texturepack/ModelOverrideFilterSet.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/texturepack/NotPredicate.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/texturepack/NumberMatcher.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/texturepack/OrPredicate.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/texturepack/PetPredicate.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/texturepack/RarityMatcher.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/texturepack/StringMatcher.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/world/FairySouls.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/world/NPCWaypoints.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/world/NavigableWaypoint.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/world/NavigationHelper.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/world/NpcWaypointGui.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/features/world/Waypoints.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/gui/BarComponent.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/gui/FirmButtonComponent.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/gui/FirmHoverComponent.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/gui/FixedComponent.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/gui/ImageComponent.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/gui/TickComponent.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/gui/config/AllConfigsGui.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/gui/config/BooleanHandler.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/gui/config/ClickHandler.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/gui/config/DurationHandler.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/gui/config/GuiAppender.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/gui/config/HudMetaHandler.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/gui/config/IntegerHandler.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/gui/config/JAnyHud.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/gui/config/KeyBindingHandler.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/gui/config/ManagedConfig.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/gui/config/ManagedConfigElement.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/gui/config/ManagedOption.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/gui/config/StringHandler.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/gui/entity/EntityModifier.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/gui/entity/EntityRenderer.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/gui/entity/EntityWidget.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/gui/entity/FakeWorld.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/gui/entity/GuiPlayer.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/gui/entity/ModifyAge.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/gui/entity/ModifyCharged.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/gui/entity/ModifyEquipment.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/gui/entity/ModifyHorse.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/gui/entity/ModifyInvisible.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/gui/entity/ModifyName.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/gui/entity/ModifyPlayerSkin.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/gui/entity/ModifyRiding.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/gui/entity/ModifyWither.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/gui/hud/MoulConfigHud.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/jarvis/JarvisIntegration.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/keybindings/FirmamentKeyBindings.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/keybindings/IKeyBinding.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/keybindings/SavedKeyBinding.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/modmenu/FirmamentModMenuPlugin.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/rei/FirmamentReiPlugin.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/rei/NEUItemEntryRenderer.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/rei/NEUItemEntrySerializer.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/rei/SBItemEntryDefinition.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/rei/SkyblockCraftingRecipeDynamicGenerator.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/rei/SkyblockItemIdFocusedStackProvider.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/rei/math.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/rei/recipes/SBCraftingRecipe.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/rei/recipes/SBEssenceUpgradeRecipe.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/rei/recipes/SBForgeRecipe.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/rei/recipes/SBKatRecipe.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/rei/recipes/SBMobDropRecipe.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/rei/recipes/SBRecipe.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/repo/BetterRepoRecipeCache.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/repo/EssenceRecipeProvider.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/repo/ExpLadder.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/repo/HypixelStaticData.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/repo/ItemCache.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/repo/ItemNameLookup.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/repo/RepoDownloadManager.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/repo/RepoManager.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/repo/RepoModResourcePack.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/Base64Util.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/BazaarPriceStrategy.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/ClipboardUtils.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/CommonSoundEffects.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/DurabilityBarEvent.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/ErrorBoundary.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/FirmFormatters.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/FragmentGuiScreen.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/GetRectangle.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/HoveredItemStack.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/IdentifierSerializer.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/IdentityCharacteristics.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/ItemUtil.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/LegacyFormattingCode.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/LegacyTagParser.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/LoadResource.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/Locraw.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/LogIfNull.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/MC.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/MinecraftDispatcher.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/MoulConfigFragment.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/MoulConfigUtils.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/MutableMapWithMaxSize.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/SBData.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/ScoreboardUtil.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/ScreenUtil.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/SequenceUtil.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/SkyBlockIsland.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/SkyblockId.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/SortedMapSerializer.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/TemplateUtil.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/TimeMark.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/Timer.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/WarpUtil.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/assertions.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/async/input.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/colorconversion.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/customgui/CoordRememberingSlot.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/customgui/CustomGui.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/customgui/HasCustomGui.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/data/DataHolder.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/data/IDataHolder.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/data/ProfileSpecificDataHolder.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/filter/IteratorFilterSet.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/item/NbtItemData.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/item/SkullItemData.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/json/BlockPosSerializer.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/json/DashlessUUIDSerializer.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/json/InstantAsLongSerializer.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/json/SingletonSerializableList.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/listutil.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/propertyutil.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/regex.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/render/FacingThePlayerContext.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/render/LerpUtils.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/render/RenderCircleProgress.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/render/RenderContextDSL.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/render/RenderInWorldContext.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/render/TranslatedScissors.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/stringutil.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/textutil.kt delete mode 100644 src/main/kotlin/moe/nea/firmament/util/uuid.kt create mode 100644 src/main/kotlin/rei/FirmamentReiPlugin.kt create mode 100644 src/main/kotlin/rei/NEUItemEntryRenderer.kt create mode 100644 src/main/kotlin/rei/NEUItemEntrySerializer.kt create mode 100644 src/main/kotlin/rei/SBItemEntryDefinition.kt create mode 100644 src/main/kotlin/rei/SkyblockCraftingRecipeDynamicGenerator.kt create mode 100644 src/main/kotlin/rei/SkyblockItemIdFocusedStackProvider.kt create mode 100644 src/main/kotlin/rei/math.kt create mode 100644 src/main/kotlin/rei/recipes/SBCraftingRecipe.kt create mode 100644 src/main/kotlin/rei/recipes/SBEssenceUpgradeRecipe.kt create mode 100644 src/main/kotlin/rei/recipes/SBForgeRecipe.kt create mode 100644 src/main/kotlin/rei/recipes/SBKatRecipe.kt create mode 100644 src/main/kotlin/rei/recipes/SBMobDropRecipe.kt create mode 100644 src/main/kotlin/rei/recipes/SBRecipe.kt create mode 100644 src/main/kotlin/repo/BetterRepoRecipeCache.kt create mode 100644 src/main/kotlin/repo/EssenceRecipeProvider.kt create mode 100644 src/main/kotlin/repo/ExpLadder.kt create mode 100644 src/main/kotlin/repo/HypixelStaticData.kt create mode 100644 src/main/kotlin/repo/ItemCache.kt create mode 100644 src/main/kotlin/repo/ItemNameLookup.kt create mode 100644 src/main/kotlin/repo/RepoDownloadManager.kt create mode 100644 src/main/kotlin/repo/RepoManager.kt create mode 100644 src/main/kotlin/repo/RepoModResourcePack.kt create mode 100644 src/main/kotlin/util/Base64Util.kt create mode 100644 src/main/kotlin/util/BazaarPriceStrategy.kt create mode 100644 src/main/kotlin/util/ClipboardUtils.kt create mode 100644 src/main/kotlin/util/CommonSoundEffects.kt create mode 100644 src/main/kotlin/util/DurabilityBarEvent.kt create mode 100644 src/main/kotlin/util/ErrorBoundary.kt create mode 100644 src/main/kotlin/util/FirmFormatters.kt create mode 100644 src/main/kotlin/util/FragmentGuiScreen.kt create mode 100644 src/main/kotlin/util/GetRectangle.kt create mode 100644 src/main/kotlin/util/HoveredItemStack.kt create mode 100644 src/main/kotlin/util/IdentifierSerializer.kt create mode 100644 src/main/kotlin/util/IdentityCharacteristics.kt create mode 100644 src/main/kotlin/util/ItemUtil.kt create mode 100644 src/main/kotlin/util/LegacyFormattingCode.kt create mode 100644 src/main/kotlin/util/LegacyTagParser.kt create mode 100644 src/main/kotlin/util/LoadResource.kt create mode 100644 src/main/kotlin/util/Locraw.kt create mode 100644 src/main/kotlin/util/LogIfNull.kt create mode 100644 src/main/kotlin/util/MC.kt create mode 100644 src/main/kotlin/util/MinecraftDispatcher.kt create mode 100644 src/main/kotlin/util/MoulConfigFragment.kt create mode 100644 src/main/kotlin/util/MoulConfigUtils.kt create mode 100644 src/main/kotlin/util/MutableMapWithMaxSize.kt create mode 100644 src/main/kotlin/util/SBData.kt create mode 100644 src/main/kotlin/util/ScoreboardUtil.kt create mode 100644 src/main/kotlin/util/ScreenUtil.kt create mode 100644 src/main/kotlin/util/SequenceUtil.kt create mode 100644 src/main/kotlin/util/SkyBlockIsland.kt create mode 100644 src/main/kotlin/util/SkyblockId.kt create mode 100644 src/main/kotlin/util/SortedMapSerializer.kt create mode 100644 src/main/kotlin/util/TemplateUtil.kt create mode 100644 src/main/kotlin/util/TimeMark.kt create mode 100644 src/main/kotlin/util/Timer.kt create mode 100644 src/main/kotlin/util/WarpUtil.kt create mode 100644 src/main/kotlin/util/assertions.kt create mode 100644 src/main/kotlin/util/async/input.kt create mode 100644 src/main/kotlin/util/colorconversion.kt create mode 100644 src/main/kotlin/util/customgui/CoordRememberingSlot.kt create mode 100644 src/main/kotlin/util/customgui/CustomGui.kt create mode 100644 src/main/kotlin/util/customgui/HasCustomGui.kt create mode 100644 src/main/kotlin/util/data/DataHolder.kt create mode 100644 src/main/kotlin/util/data/IDataHolder.kt create mode 100644 src/main/kotlin/util/data/ProfileSpecificDataHolder.kt create mode 100644 src/main/kotlin/util/filter/IteratorFilterSet.kt create mode 100644 src/main/kotlin/util/item/NbtItemData.kt create mode 100644 src/main/kotlin/util/item/SkullItemData.kt create mode 100644 src/main/kotlin/util/json/BlockPosSerializer.kt create mode 100644 src/main/kotlin/util/json/DashlessUUIDSerializer.kt create mode 100644 src/main/kotlin/util/json/InstantAsLongSerializer.kt create mode 100644 src/main/kotlin/util/json/SingletonSerializableList.kt create mode 100644 src/main/kotlin/util/listutil.kt create mode 100644 src/main/kotlin/util/propertyutil.kt create mode 100644 src/main/kotlin/util/regex.kt create mode 100644 src/main/kotlin/util/render/FacingThePlayerContext.kt create mode 100644 src/main/kotlin/util/render/LerpUtils.kt create mode 100644 src/main/kotlin/util/render/RenderCircleProgress.kt create mode 100644 src/main/kotlin/util/render/RenderContextDSL.kt create mode 100644 src/main/kotlin/util/render/RenderInWorldContext.kt create mode 100644 src/main/kotlin/util/render/TranslatedScissors.kt create mode 100644 src/main/kotlin/util/stringutil.kt create mode 100644 src/main/kotlin/util/textutil.kt create mode 100644 src/main/kotlin/util/uuid.kt diff --git a/build.gradle.kts b/build.gradle.kts index fec1afb..fe527ba 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,21 +8,24 @@ import moe.nea.licenseextractificator.LicenseDiscoveryTask import net.fabricmc.loom.LoomGradleExtension +import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { java `maven-publish` - id("com.google.devtools.ksp") version "1.9.23-1.0.20" - kotlin("jvm") version "1.9.23" - kotlin("plugin.serialization") version "1.9.23" -// id("com.bnorm.power.kotlin-power-assert") version "0.13.0" - id("dev.architectury.loom") version "1.6.397" + alias(libs.plugins.kotlin.jvm) + alias(libs.plugins.kotlin.plugin.serialization) + alias(libs.plugins.kotlin.plugin.powerassert) + alias(libs.plugins.kotlin.plugin.ksp) + alias(libs.plugins.loom) id("com.github.johnrengelman.shadow") version "8.1.1" id("moe.nea.licenseextractificator") -// id("io.github.juuxel.loom-vineflower") version "1.11.0" } +version = getGitTagInfo() +group = rootProject.property("maven_group").toString() + java { withSourcesJar() toolchain { @@ -30,13 +33,11 @@ java { } } -val compileKotlin: KotlinCompile by tasks -compileKotlin.kotlinOptions { - jvmTarget = "21" -} -val compileTestKotlin: KotlinCompile by tasks -compileTestKotlin.kotlinOptions { - jvmTarget = "21" + +tasks.withType(KotlinCompile::class) { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_21) + } } allprojects { @@ -80,17 +81,54 @@ allprojects { maven("https://repo.hypixel.net/repository/Hypixel/") maven("https://maven.azureaaron.net/snapshots") maven("https://maven.azureaaron.net/releases") + maven("https://www.cursemaven.com") mavenLocal() } } kotlin { sourceSets.all { languageSettings { -// languageVersion = "2.0" enableLanguageFeature("BreakContinueInInlineLambdas") } } } +val compatSourceSets: MutableSet = mutableSetOf() +fun createIsolatedSourceSet(name: String, path: String = "compat/$name"): SourceSet { + val ss = sourceSets.create(name) { + this.java.setSrcDirs(listOf(layout.projectDirectory.dir("src/$path/java"))) + this.kotlin.setSrcDirs(listOf(layout.projectDirectory.dir("src/$path/kotlin"))) + } + compatSourceSets.add(ss) + loom.createRemapConfigurations(ss) + val mainSS = sourceSets.main.get() + configurations { + (ss.implementationConfigurationName) { + extendsFrom(getByName(mainSS.compileClasspathConfigurationName)) + } + (ss.annotationProcessorConfigurationName) { + extendsFrom(getByName(mainSS.annotationProcessorConfigurationName)) + } + (mainSS.runtimeOnlyConfigurationName) { + extendsFrom(getByName(ss.runtimeClasspathConfigurationName)) + } + } + dependencies { + runtimeOnly(ss.output) + (ss.implementationConfigurationName)(sourceSets.main.get().output) + } + tasks.jar { + from(ss.output) + } + return ss +} + +val SourceSet.modImplementationConfigurationName + get() = + loom.remapConfigurations.find { + it.targetConfigurationName.get() == this.implementationConfigurationName + }!!.sourceConfiguration +val configuredSourceSet = createIsolatedSourceSet("configured") +val sodiumSourceSet = createIsolatedSourceSet("sodium") val shadowMe by configurations.creating { exclude(group = "org.jetbrains.kotlin") @@ -148,10 +186,11 @@ dependencies { modRuntimeOnly(libs.fabric.api.deprecated) modApi(libs.architectury) modCompileOnly(libs.jarvis.api) - modCompileOnly(libs.sodium) include(libs.jarvis.fabric) modCompileOnly(libs.femalegender) + (configuredSourceSet.modImplementationConfigurationName)(libs.configured) + (sodiumSourceSet.modImplementationConfigurationName)(libs.sodium) // Actual dependencies modCompileOnly(libs.rei.api) { @@ -189,9 +228,6 @@ tasks.test { useJUnitPlatform() } -version = getGitTagInfo() -group = rootProject.property("maven_group").toString() - loom { clientOnlyMinecraftJar() accessWidenerPath.set(project.file("src/main/resources/firmament.accesswidener")) @@ -201,6 +237,10 @@ loom { property("devauth.enabled", "true") property("fabric.log.level", "info") property("firmament.debug", "true") + property("firmament.classroots", + compatSourceSets.joinToString(File.pathSeparator) { + File(it.output.classesDirs.asPath).absolutePath + }) property("mixin.debug", "true") parseEnvFile(file(".env")).forEach { (t, u) -> diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5cf8a27..335df1e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,7 +4,14 @@ [versions] minecraft = "1.21" -fabric_loader = "0.15.11" + +# Update from https://kotlinlang.org/ +kotlin = "2.0.20" +# Update from https://github.com/google/ksp/releases +kotlin_ksp = "2.0.20-1.0.24" + +# Update from https://linkie.shedaniel.me/dependencies?loader=fabric +fabric_loader = "0.16.3" fabric_api = "0.100.4+1.21" fabric_kotlin = "1.11.0+kotlin.2.0.0" yarn = "1.21+build.7" @@ -12,25 +19,58 @@ rei = "16.0.729" modmenu = "11.0.1" architectury = "13.0.3" -qolify = "1.3.0-1.20.2" -citresewn = "1.1.3+1.20" +# Update from https://maven.architectury.dev/dev/architectury/loom/dev.architectury.loom.gradle.plugin/ +loom = "1.7.412" + +# Update from https://modrinth.com/mod/qolify/versions?l=fabric +qolify = "1.6.0-1.21.1" + +# Update from https://modrinth.com/mod/sodium/versions?l=fabric sodium = "mc1.21-0.5.11" -freecammod = "U5eR0FyR" + +# Update from https://modrinth.com/mod/freecam/versions?l=fabric +freecammod = "vomskVK3" + +# Update from https://modrinth.com/mod/no-chat-reports/versions?l=fabric ncr = "Fabric-1.21-v2.8.0" + +# Update from https://modrinth.com/mod/female-gender/versions?l=fabric femalegender = "kJmjQvAS" + +# Update from https://modrinth.com/mod/explosive-enhancement/versions?l=fabric explosiveenhancement = "1.2.3-1.21.0" -notenoughanimations = "WaI2x21x" + +# Update from https://modrinth.com/mod/not-enough-animations/versions?l=fabric +notenoughanimations = "BQ8qstAV" devauth = "1.2.0" -ktor = "2.3.0" + +# Update from https://ktor.io/ +ktor = "2.3.12" + +# Update from https://repo.nea.moe/#/releases/moe/nea/neurepoparser neurepoparser = "1.5.0" + +# Update from https://github.com/HotswapProjects/HotswapAgent/releases hotswap_agent = "1.4.2-SNAPSHOT" + +# Update from https://github.com/LlamaLad7/MixinExtras/tags mixinextras = "0.3.5" + jarvis = "1.1.3" nealisp = "1.0.0" + +# Update from https://github.com/NotEnoughUpdates/MoulConfig/tags moulconfig = "3.0.0-beta.15" + +# Update from https://www.curseforge.com/minecraft/mc-mods/configured/files/all?page=1&pageSize=20 +configured = "5441234" + +# Update from https://modrinth.com/mod/hypixel-mod-api/versions hypixelmodapi = "1.0.1" hypixelmodapi_fabric = "1.0.1+build.1+mc1.21" + +# Update from https://github.com/shedaniel/fabric-asm or https://maven.shedaniel.me/me/shedaniel/mm/ manninghamMills = "2.4.1" [libraries] @@ -52,6 +92,7 @@ manninghamMills = { module = "me.shedaniel:mm", version.ref = "manninghamMills" aaronhmapi = { module = "net.azureaaron:hm-api", version = "1.0.0+1.21" } hypixelmodapi = { module = "net.hypixel:mod-api", version.ref = "hypixelmodapi" } hypixelmodapi_fabric = { module = "maven.modrinth:hypixel-mod-api", version.ref = "hypixelmodapi_fabric" } +configured = { module = "curse.maven:configured-457570", version.ref = "configured" } # Runtime: notenoughanimations = { module = "maven.modrinth:not-enough-animations", version.ref = "notenoughanimations" } hotswap = { module = "virtual.github.hotswapagent:hotswap-agent", version.ref = "hotswap_agent" } @@ -60,7 +101,6 @@ rei_fabric = { module = "me.shedaniel:RoughlyEnoughItems-fabric", version.ref = devauth = { module = "me.djtheredstoner:DevAuth-fabric", version.ref = "devauth" } modmenu = { module = "maven.modrinth:modmenu", version.ref = "modmenu" } qolify = { module = "maven.modrinth:qolify", version.ref = "qolify" } -citresewn = { module = "maven.modrinth:cit-resewn", version.ref = "citresewn" } ncr = { module = "maven.modrinth:no-chat-reports", version.ref = "ncr" } sodium = { module = "maven.modrinth:sodium", version.ref = "sodium" } freecammod = { module = "maven.modrinth:freecam", version.ref = "freecammod" } @@ -78,9 +118,13 @@ runtime_optional = [ "devauth", "freecammod", "sodium", - # "qolify", - # "citresewn", - # "ncr", +# "qolify", + "ncr", ] - +[plugins] +kotlin_jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } +kotlin_plugin_serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } +kotlin_plugin_powerassert = { id = "org.jetbrains.kotlin.plugin.power-assert", version.ref = "kotlin" } +kotlin_plugin_ksp = { id = "com.google.devtools.ksp", version.ref = "kotlin_ksp" } +loom = { id = "dev.architectury.loom", version.ref = "loom" } diff --git a/src/compat/sodium/java/SodiumChunkReloader.kt b/src/compat/sodium/java/SodiumChunkReloader.kt new file mode 100644 index 0000000..9456861 --- /dev/null +++ b/src/compat/sodium/java/SodiumChunkReloader.kt @@ -0,0 +1,10 @@ +import me.jellysquid.mods.sodium.client.render.SodiumWorldRenderer +import moe.nea.firmament.mixins.accessor.sodium.AccessorSodiumWorldRenderer + +class SodiumChunkReloader : Runnable { + override fun run() { + (SodiumWorldRenderer.instanceNullable() as? AccessorSodiumWorldRenderer) + ?.renderSectionManager_firmament + ?.markGraphDirty() + } +} diff --git a/src/compat/sodium/java/moe/nea/firmament/mixins/accessor/sodium/AccessorSodiumWorldRenderer.java b/src/compat/sodium/java/moe/nea/firmament/mixins/accessor/sodium/AccessorSodiumWorldRenderer.java new file mode 100644 index 0000000..d585cbc --- /dev/null +++ b/src/compat/sodium/java/moe/nea/firmament/mixins/accessor/sodium/AccessorSodiumWorldRenderer.java @@ -0,0 +1,14 @@ +package moe.nea.firmament.mixins.accessor.sodium; + +import me.jellysquid.mods.sodium.client.render.SodiumWorldRenderer; +import me.jellysquid.mods.sodium.client.render.chunk.RenderSectionManager; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Pseudo; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(SodiumWorldRenderer.class) +@Pseudo +public interface AccessorSodiumWorldRenderer { + @Accessor(value = "renderSectionManager", remap = false) + RenderSectionManager getRenderSectionManager_firmament(); +} diff --git a/src/compat/sodium/java/moe/nea/firmament/mixins/custommodels/PatchBlockModelInSodiumChunkGenerator.java b/src/compat/sodium/java/moe/nea/firmament/mixins/custommodels/PatchBlockModelInSodiumChunkGenerator.java new file mode 100644 index 0000000..90f20bc --- /dev/null +++ b/src/compat/sodium/java/moe/nea/firmament/mixins/custommodels/PatchBlockModelInSodiumChunkGenerator.java @@ -0,0 +1,29 @@ +package moe.nea.firmament.mixins.custommodels; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Local; +import me.jellysquid.mods.sodium.client.render.chunk.compile.tasks.ChunkBuilderMeshingTask; +import moe.nea.firmament.features.texturepack.CustomBlockTextures; +import net.minecraft.block.BlockState; +import net.minecraft.client.render.block.BlockModels; +import net.minecraft.client.render.model.BakedModel; +import net.minecraft.util.math.BlockPos; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(ChunkBuilderMeshingTask.class) +public class PatchBlockModelInSodiumChunkGenerator { + @WrapOperation( + method = "execute(Lme/jellysquid/mods/sodium/client/render/chunk/compile/ChunkBuildContext;Lme/jellysquid/mods/sodium/client/util/task/CancellationToken;)Lme/jellysquid/mods/sodium/client/render/chunk/compile/ChunkBuildOutput;", + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/block/BlockModels;getModel(Lnet/minecraft/block/BlockState;)Lnet/minecraft/client/render/model/BakedModel;")) + private BakedModel replaceBlockModel(BlockModels instance, BlockState state, Operation original, + @Local(name = "blockPos") BlockPos.Mutable pos) { + var replacement = CustomBlockTextures.getReplacementModel(state, pos); + if (replacement != null) return replacement; + CustomBlockTextures.enterFallbackCall(); + var fallback = original.call(instance, state); + CustomBlockTextures.exitFallbackCall(); + return fallback; + } +} diff --git a/src/main/java/moe/nea/firmament/init/AutoDiscoveryPlugin.java b/src/main/java/moe/nea/firmament/init/AutoDiscoveryPlugin.java new file mode 100644 index 0000000..e3644c0 --- /dev/null +++ b/src/main/java/moe/nea/firmament/init/AutoDiscoveryPlugin.java @@ -0,0 +1,173 @@ +package moe.nea.firmament.init; + + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +public class AutoDiscoveryPlugin { + private static final List mixinPlugins = new ArrayList<>(); + + public static List getMixinPlugins() { + return mixinPlugins; + } + + private String mixinPackage; + + public void setMixinPackage(String mixinPackage) { + this.mixinPackage = mixinPackage; + mixinPlugins.add(this); + } + + /** + * Resolves the base class root for a given class URL. This resolves either the JAR root, or the class file root. + * In either case the return value of this + the class name will resolve back to the original class url, or to other + * class urls for other classes. + */ + public URL getBaseUrlForClassUrl(URL classUrl) { + String string = classUrl.toString(); + if (classUrl.getProtocol().equals("jar")) { + try { + return new URL(string.substring(4).split("!")[0]); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + if (string.endsWith(".class")) { + try { + return new URL(string.replace("\\", "/") + .replace(getClass().getCanonicalName() + .replace(".", "/") + ".class", "")); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + return classUrl; + } + + /** + * Get the package that contains all the mixins. This value is set using {@link #setMixinPackage}. + */ + public String getMixinPackage() { + return mixinPackage; + } + + /** + * Get the path inside the class root to the mixin package + */ + public String getMixinBaseDir() { + return mixinPackage.replace(".", "/"); + } + + /** + * A list of all discovered mixins. + */ + private List mixins = null; + + /** + * Try to add mixin class ot the mixins based on the filepath inside of the class root. + * Removes the {@code .class} file suffix, as well as the base mixin package. + *

This method cannot be called after mixin initialization.

+ * + * @param className the name or path of a class to be registered as a mixin. + */ + public void tryAddMixinClass(String className) { + if (!className.endsWith(".class")) return; + String norm = (className.substring(0, className.length() - ".class".length())) + .replace("\\", "/") + .replace("/", "."); + if (norm.startsWith(getMixinPackage() + ".") && !norm.endsWith(".")) { + mixins.add(norm.substring(getMixinPackage().length() + 1)); + } + } + + private void tryDiscoverFromContentFile(URL url) { + Path file; + try { + file = Paths.get(getBaseUrlForClassUrl(url).toURI()); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + System.out.println("Base directory found at " + file); + if (!Files.exists(file)) { + System.out.println("Skipping non-existing mixin root: " + file); + return; + } + if (Files.isDirectory(file)) { + walkDir(file); + } else { + walkJar(file); + } + System.out.println("Found mixins: " + mixins); + + } + + /** + * Search through the JAR or class directory to find mixins contained in {@link #getMixinPackage()} + */ + public List getMixins() { + if (mixins != null) return mixins; + System.out.println("Trying to discover mixins"); + mixins = new ArrayList<>(); + URL classUrl = getClass().getProtectionDomain().getCodeSource().getLocation(); + System.out.println("Found classes at " + classUrl); + tryDiscoverFromContentFile(classUrl); + var classRoots = System.getProperty("firmament.classroots"); + if (classRoots != null && !classRoots.isBlank()) { + System.out.println("Found firmament class roots: " + classRoots); + for (String s : classRoots.split(File.pathSeparator)) { + if (s.isBlank()) { + continue; + } + try { + tryDiscoverFromContentFile(new File(s).toURI().toURL()); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + } + return mixins; + } + + /** + * Search through directory for mixin classes based on {@link #getMixinBaseDir}. + * + * @param classRoot The root directory in which classes are stored for the default package. + */ + private void walkDir(Path classRoot) { + System.out.println("Trying to find mixins from directory"); + try (Stream classes = Files.walk(classRoot.resolve(getMixinBaseDir()))) { + classes.map(it -> classRoot.relativize(it).toString()) + .forEach(this::tryAddMixinClass); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * Read through a JAR file, trying to find all mixins inside. + */ + private void walkJar(Path file) { + System.out.println("Trying to find mixins from jar file"); + try (ZipInputStream zis = new ZipInputStream(Files.newInputStream(file))) { + ZipEntry next; + while ((next = zis.getNextEntry()) != null) { + tryAddMixinClass(next.getName()); + zis.closeEntry(); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/src/main/java/moe/nea/firmament/init/MixinPlugin.java b/src/main/java/moe/nea/firmament/init/MixinPlugin.java index ea8709b..e7a02b5 100644 --- a/src/main/java/moe/nea/firmament/init/MixinPlugin.java +++ b/src/main/java/moe/nea/firmament/init/MixinPlugin.java @@ -12,9 +12,12 @@ import java.util.Set; public class MixinPlugin implements IMixinConfigPlugin { + AutoDiscoveryPlugin autoDiscoveryPlugin = new AutoDiscoveryPlugin(); + @Override public void onLoad(String mixinPackage) { MixinExtrasBootstrap.init(); + autoDiscoveryPlugin.setMixinPackage(mixinPackage); } @Override @@ -37,7 +40,7 @@ public class MixinPlugin implements IMixinConfigPlugin { @Override public List getMixins() { - return null; + return autoDiscoveryPlugin.getMixins(); } @Override diff --git a/src/main/java/moe/nea/firmament/mixins/accessor/sodium/AccessorSodiumWorldRenderer.java b/src/main/java/moe/nea/firmament/mixins/accessor/sodium/AccessorSodiumWorldRenderer.java deleted file mode 100644 index b759204..0000000 --- a/src/main/java/moe/nea/firmament/mixins/accessor/sodium/AccessorSodiumWorldRenderer.java +++ /dev/null @@ -1,14 +0,0 @@ -package moe.nea.firmament.mixins.accessor.sodium; - -import me.jellysquid.mods.sodium.client.render.SodiumWorldRenderer; -import me.jellysquid.mods.sodium.client.render.chunk.RenderSectionManager; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Pseudo; -import org.spongepowered.asm.mixin.gen.Accessor; - -@Mixin(SodiumWorldRenderer.class) -@Pseudo -public interface AccessorSodiumWorldRenderer { - @Accessor("renderSectionManager") - RenderSectionManager getRenderSectionManager_firmament(); -} diff --git a/src/main/java/moe/nea/firmament/mixins/custommodels/PatchBlockModelInSodiumChunkGenerator.java b/src/main/java/moe/nea/firmament/mixins/custommodels/PatchBlockModelInSodiumChunkGenerator.java deleted file mode 100644 index 90f20bc..0000000 --- a/src/main/java/moe/nea/firmament/mixins/custommodels/PatchBlockModelInSodiumChunkGenerator.java +++ /dev/null @@ -1,29 +0,0 @@ -package moe.nea.firmament.mixins.custommodels; - -import com.llamalad7.mixinextras.injector.wrapoperation.Operation; -import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; -import com.llamalad7.mixinextras.sugar.Local; -import me.jellysquid.mods.sodium.client.render.chunk.compile.tasks.ChunkBuilderMeshingTask; -import moe.nea.firmament.features.texturepack.CustomBlockTextures; -import net.minecraft.block.BlockState; -import net.minecraft.client.render.block.BlockModels; -import net.minecraft.client.render.model.BakedModel; -import net.minecraft.util.math.BlockPos; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; - -@Mixin(ChunkBuilderMeshingTask.class) -public class PatchBlockModelInSodiumChunkGenerator { - @WrapOperation( - method = "execute(Lme/jellysquid/mods/sodium/client/render/chunk/compile/ChunkBuildContext;Lme/jellysquid/mods/sodium/client/util/task/CancellationToken;)Lme/jellysquid/mods/sodium/client/render/chunk/compile/ChunkBuildOutput;", - at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/block/BlockModels;getModel(Lnet/minecraft/block/BlockState;)Lnet/minecraft/client/render/model/BakedModel;")) - private BakedModel replaceBlockModel(BlockModels instance, BlockState state, Operation original, - @Local(name = "blockPos") BlockPos.Mutable pos) { - var replacement = CustomBlockTextures.getReplacementModel(state, pos); - if (replacement != null) return replacement; - CustomBlockTextures.enterFallbackCall(); - var fallback = original.call(instance, state); - CustomBlockTextures.exitFallbackCall(); - return fallback; - } -} diff --git a/src/main/kotlin/Firmament.kt b/src/main/kotlin/Firmament.kt new file mode 100644 index 0000000..c1801f4 --- /dev/null +++ b/src/main/kotlin/Firmament.kt @@ -0,0 +1,148 @@ + + +package moe.nea.firmament + +import com.mojang.brigadier.CommandDispatcher +import io.ktor.client.HttpClient +import io.ktor.client.plugins.UserAgent +import io.ktor.client.plugins.cache.HttpCache +import io.ktor.client.plugins.compression.ContentEncoding +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation +import io.ktor.client.plugins.logging.LogLevel +import io.ktor.client.plugins.logging.Logging +import io.ktor.serialization.kotlinx.json.json +import java.io.InputStream +import java.nio.file.Files +import java.nio.file.Path +import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback +import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents +import net.fabricmc.fabric.api.client.item.v1.ItemTooltipCallback +import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents +import net.fabricmc.loader.api.FabricLoader +import net.fabricmc.loader.api.Version +import net.fabricmc.loader.api.metadata.ModMetadata +import org.apache.logging.log4j.LogManager +import org.apache.logging.log4j.Logger +import kotlinx.coroutines.CoroutineName +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.plus +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.decodeFromStream +import kotlin.coroutines.EmptyCoroutineContext +import net.minecraft.client.render.chunk.SectionBuilder +import net.minecraft.command.CommandRegistryAccess +import net.minecraft.util.Identifier +import moe.nea.firmament.commands.registerFirmamentCommand +import moe.nea.firmament.events.ClientStartedEvent +import moe.nea.firmament.events.CommandEvent +import moe.nea.firmament.events.ItemTooltipEvent +import moe.nea.firmament.events.ScreenRenderPostEvent +import moe.nea.firmament.events.TickEvent +import moe.nea.firmament.events.registration.registerFirmamentEvents +import moe.nea.firmament.features.FeatureManager +import moe.nea.firmament.repo.HypixelStaticData +import moe.nea.firmament.repo.RepoManager +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.SBData +import moe.nea.firmament.util.data.IDataHolder + +object Firmament { + const val MOD_ID = "firmament" + + val DEBUG = System.getProperty("firmament.debug") == "true" + val DATA_DIR: Path = Path.of(".firmament").also { Files.createDirectories(it) } + val CONFIG_DIR: Path = Path.of("config/firmament").also { Files.createDirectories(it) } + val logger: Logger = LogManager.getLogger("Firmament") + private val metadata: ModMetadata by lazy { + FabricLoader.getInstance().getModContainer(MOD_ID).orElseThrow().metadata + } + val version: Version by lazy { metadata.version } + + val json = Json { + prettyPrint = DEBUG + isLenient = true + ignoreUnknownKeys = true + encodeDefaults = true + } + + val httpClient by lazy { + HttpClient { + install(ContentNegotiation) { + json(json) + } + install(ContentEncoding) { + gzip() + deflate() + } + install(UserAgent) { + agent = "Firmament/$version" + } + if (DEBUG) + install(Logging) { + level = LogLevel.INFO + } + install(HttpCache) + } + } + + val globalJob = Job() + val coroutineScope = + CoroutineScope(EmptyCoroutineContext + CoroutineName("Firmament")) + SupervisorJob(globalJob) + + private fun registerCommands( + dispatcher: CommandDispatcher, + @Suppress("UNUSED_PARAMETER") + ctx: CommandRegistryAccess + ) { + registerFirmamentCommand(dispatcher) + CommandEvent.publish(CommandEvent(dispatcher, ctx, MC.networkHandler?.commandDispatcher)) + } + + @JvmStatic + fun onInitialize() { + } + + @JvmStatic + fun onClientInitialize() { + FeatureManager.subscribeEvents() + var tick = 0 + ClientTickEvents.END_CLIENT_TICK.register(ClientTickEvents.EndTick { instance -> + TickEvent.publish(TickEvent(tick++)) + }) + IDataHolder.registerEvents() + RepoManager.initialize() + SBData.init() + FeatureManager.autoload() + HypixelStaticData.spawnDataCollectionLoop() + ClientCommandRegistrationCallback.EVENT.register(this::registerCommands) + ClientLifecycleEvents.CLIENT_STARTED.register(ClientLifecycleEvents.ClientStarted { + ClientStartedEvent.publish(ClientStartedEvent()) + }) + ClientLifecycleEvents.CLIENT_STOPPING.register(ClientLifecycleEvents.ClientStopping { + logger.info("Shutting down Firmament coroutines") + globalJob.cancel() + }) + registerFirmamentEvents() + ItemTooltipCallback.EVENT.register { stack, context, type, lines -> + ItemTooltipEvent.publish(ItemTooltipEvent(stack, context, type, lines)) + } + ScreenEvents.AFTER_INIT.register(ScreenEvents.AfterInit { client, screen, scaledWidth, scaledHeight -> + ScreenEvents.afterRender(screen) + .register(ScreenEvents.AfterRender { screen, drawContext, mouseX, mouseY, tickDelta -> + ScreenRenderPostEvent.publish(ScreenRenderPostEvent(screen, mouseX, mouseY, tickDelta, drawContext)) + }) + }) + } + + + fun identifier(path: String) = Identifier.of(MOD_ID, path) + inline fun tryDecodeJsonFromStream(inputStream: InputStream): Result { + return runCatching { + json.decodeFromStream(inputStream) + } + } +} diff --git a/src/main/kotlin/apis/Profiles.kt b/src/main/kotlin/apis/Profiles.kt new file mode 100644 index 0000000..789364a --- /dev/null +++ b/src/main/kotlin/apis/Profiles.kt @@ -0,0 +1,194 @@ + + +@file:UseSerializers(DashlessUUIDSerializer::class, InstantAsLongSerializer::class) + +package moe.nea.firmament.apis + +import io.github.moulberry.repo.constants.Leveling +import io.github.moulberry.repo.data.Rarity +import kotlinx.datetime.Instant +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.UseSerializers +import moe.nea.firmament.repo.RepoManager +import moe.nea.firmament.util.LegacyFormattingCode +import moe.nea.firmament.util.SkyblockId +import moe.nea.firmament.util.assertNotNullOr +import moe.nea.firmament.util.json.DashlessUUIDSerializer +import moe.nea.firmament.util.json.InstantAsLongSerializer +import net.minecraft.util.DyeColor +import net.minecraft.util.Formatting +import java.util.* +import kotlin.reflect.KProperty1 + + +@Serializable +data class CollectionSkillData( + val items: Map +) + +@Serializable +data class CollectionResponse( + val success: Boolean, + val collections: Map +) + +@Serializable +data class CollectionInfo( + val name: String, + val maxTiers: Int, + val tiers: List +) + +@Serializable +data class CollectionTier( + val tier: Int, + val amountRequired: Long, + val unlocks: List, +) + + +@Serializable +data class Profiles( + val success: Boolean, + val profiles: List? +) + +@Serializable +data class Profile( + @SerialName("profile_id") + val profileId: UUID, + @SerialName("cute_name") + val cuteName: String, + val selected: Boolean = false, + val members: Map, +) + +enum class Skill(val accessor: KProperty1, val color: DyeColor, val icon: SkyblockId) { + FARMING(Member::experienceSkillFarming, DyeColor.YELLOW, SkyblockId("ROOKIE_HOE")), + FORAGING(Member::experienceSkillForaging, DyeColor.BROWN, SkyblockId("TREECAPITATOR_AXE")), + MINING(Member::experienceSkillMining, DyeColor.LIGHT_GRAY, SkyblockId("DIAMOND_PICKAXE")), + ALCHEMY(Member::experienceSkillAlchemy, DyeColor.PURPLE, SkyblockId("BREWING_STAND")), + TAMING(Member::experienceSkillTaming, DyeColor.GREEN, SkyblockId("SUPER_EGG")), + FISHING(Member::experienceSkillFishing, DyeColor.BLUE, SkyblockId("FARMER_ROD")), + RUNECRAFTING(Member::experienceSkillRunecrafting, DyeColor.PINK, SkyblockId("MUSIC_RUNE;1")), + CARPENTRY(Member::experienceSkillCarpentry, DyeColor.ORANGE, SkyblockId("WORKBENCH")), + COMBAT(Member::experienceSkillCombat, DyeColor.RED, SkyblockId("UNDEAD_SWORD")), + SOCIAL(Member::experienceSkillSocial, DyeColor.WHITE, SkyblockId("EGG_HUNT")), + ENCHANTING(Member::experienceSkillEnchanting, DyeColor.MAGENTA, SkyblockId("ENCHANTMENT_TABLE")), + ; + + fun getMaximumLevel(leveling: Leveling) = assertNotNullOr(leveling.maximumLevels[name.lowercase()]) { 50 } + + fun getLadder(leveling: Leveling): List { + if (this == SOCIAL) return leveling.socialExperienceRequiredPerLevel + if (this == RUNECRAFTING) return leveling.runecraftingExperienceRequiredPerLevel + return leveling.skillExperienceRequiredPerLevel + } +} + +enum class CollectionCategory(val skill: Skill?, val color: DyeColor, val icon: SkyblockId) { + FARMING(Skill.FARMING, DyeColor.YELLOW, SkyblockId("ROOKIE_HOE")), + FORAGING(Skill.FORAGING, DyeColor.BROWN, SkyblockId("TREECAPITATOR_AXE")), + MINING(Skill.MINING, DyeColor.LIGHT_GRAY, SkyblockId("DIAMOND_PICKAXE")), + FISHING(Skill.FISHING, DyeColor.BLUE, SkyblockId("FARMER_ROD")), + COMBAT(Skill.COMBAT, DyeColor.RED, SkyblockId("UNDEAD_SWORD")), + RIFT(null, DyeColor.PURPLE, SkyblockId("SKYBLOCK_MOTE")), +} + +@Serializable +@JvmInline +value class CollectionType(val string: String) { + val skyblockId get() = SkyblockId(string.replace(":", "-").replace("MUSHROOM_COLLECTION", "HUGE_MUSHROOM_2")) +} + +@Serializable +data class Member( + val pets: List = listOf(), + @SerialName("coop_invitation") + val coopInvitation: CoopInvitation? = null, + @SerialName("experience_skill_farming") + val experienceSkillFarming: Double = 0.0, + @SerialName("experience_skill_alchemy") + val experienceSkillAlchemy: Double = 0.0, + @SerialName("experience_skill_combat") + val experienceSkillCombat: Double = 0.0, + @SerialName("experience_skill_taming") + val experienceSkillTaming: Double = 0.0, + @SerialName("experience_skill_social2") + val experienceSkillSocial: Double = 0.0, + @SerialName("experience_skill_enchanting") + val experienceSkillEnchanting: Double = 0.0, + @SerialName("experience_skill_fishing") + val experienceSkillFishing: Double = 0.0, + @SerialName("experience_skill_foraging") + val experienceSkillForaging: Double = 0.0, + @SerialName("experience_skill_mining") + val experienceSkillMining: Double = 0.0, + @SerialName("experience_skill_runecrafting") + val experienceSkillRunecrafting: Double = 0.0, + @SerialName("experience_skill_carpentry") + val experienceSkillCarpentry: Double = 0.0, + val collection: Map = mapOf() +) + +@Serializable +data class CoopInvitation( + val timestamp: Instant, + @SerialName("invited_by") + val invitedBy: UUID? = null, + val confirmed: Boolean, +) + +@JvmInline +@Serializable +value class PetType(val name: String) + +@Serializable +data class Pet( + val uuid: UUID? = null, + val type: PetType, + val exp: Double = 0.0, + val active: Boolean = false, + val tier: Rarity, + val candyUsed: Int = 0, + val heldItem: String? = null, + val skin: String? = null, +) { + val itemId get() = SkyblockId("${type.name};${tier.ordinal}") +} + +@Serializable +data class PlayerResponse( + val success: Boolean, + val player: PlayerData, +) + +@Serializable +data class PlayerData( + val uuid: UUID, + val firstLogin: Instant, + val lastLogin: Instant? = null, + @SerialName("playername") + val playerName: String, + val achievementsOneTime: List = listOf(), + @SerialName("newPackageRank") + val packageRank: String? = null, + val monthlyPackageRank: String? = null, + val rankPlusColor: String = "GOLD" +) { + val rankPlusDyeColor = LegacyFormattingCode.values().find { it.name == rankPlusColor } ?: LegacyFormattingCode.GOLD + val rankData get() = RepoManager.neuRepo.constants.misc.ranks[if (monthlyPackageRank == "NONE" || monthlyPackageRank == null) packageRank else monthlyPackageRank] + fun getDisplayName(name: String = playerName) = rankData?.let { + ("§${it.color}[${it.tag}${rankPlusDyeColor.modern}" + + "${it.plus ?: ""}§${it.color}] $name") + } ?: "${Formatting.GRAY}$name" + + +} + +@Serializable +data class AshconNameLookup( + val username: String, + val uuid: UUID, +) diff --git a/src/main/kotlin/apis/Routes.kt b/src/main/kotlin/apis/Routes.kt new file mode 100644 index 0000000..bf55a2d --- /dev/null +++ b/src/main/kotlin/apis/Routes.kt @@ -0,0 +1,95 @@ + + +package moe.nea.firmament.apis + +import io.ktor.client.call.* +import io.ktor.client.request.* +import io.ktor.http.* +import io.ktor.util.* +import java.util.* +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.async +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import kotlin.collections.MutableMap +import kotlin.collections.listOf +import kotlin.collections.mutableMapOf +import kotlin.collections.set +import moe.nea.firmament.Firmament +import moe.nea.firmament.util.MinecraftDispatcher + +object Routes { + private val nameToUUID: MutableMap> = CaseInsensitiveMap() + private val profiles: MutableMap> = mutableMapOf() + private val accounts: MutableMap> = mutableMapOf() + private val UUIDToName: MutableMap> = mutableMapOf() + + suspend fun getPlayerNameForUUID(uuid: UUID): String? { + return withContext(MinecraftDispatcher) { + UUIDToName.computeIfAbsent(uuid) { + async(Firmament.coroutineScope.coroutineContext) { + val response = Firmament.httpClient.get("https://api.ashcon.app/mojang/v2/user/$uuid") + if (!response.status.isSuccess()) return@async null + val data = response.body() + launch(MinecraftDispatcher) { + nameToUUID[data.username] = async { data.uuid } + } + data.username + } + } + }.await() + } + + suspend fun getUUIDForPlayerName(name: String): UUID? { + return withContext(MinecraftDispatcher) { + nameToUUID.computeIfAbsent(name) { + async(Firmament.coroutineScope.coroutineContext) { + val response = Firmament.httpClient.get("https://api.ashcon.app/mojang/v2/user/$name") + if (!response.status.isSuccess()) return@async null + val data = response.body() + launch(MinecraftDispatcher) { + UUIDToName[data.uuid] = async { data.username } + } + data.uuid + } + } + }.await() + } + + suspend fun getAccountData(uuid: UUID): PlayerData? { + return withContext(MinecraftDispatcher) { + accounts.computeIfAbsent(uuid) { + async(Firmament.coroutineScope.coroutineContext) { + val response = UrsaManager.request(listOf("v1", "hypixel","player", uuid.toString())) + if (!response.status.isSuccess()) { + launch(MinecraftDispatcher) { + @Suppress("DeferredResultUnused") + accounts.remove(uuid) + } + return@async null + } + response.body().player + } + } + }.await() + } + + suspend fun getProfiles(uuid: UUID): Profiles? { + return withContext(MinecraftDispatcher) { + profiles.computeIfAbsent(uuid) { + async(Firmament.coroutineScope.coroutineContext) { + val response = UrsaManager.request(listOf("v1", "hypixel","profiles", uuid.toString())) + if (!response.status.isSuccess()) { + launch(MinecraftDispatcher) { + @Suppress("DeferredResultUnused") + profiles.remove(uuid) + } + return@async null + } + response.body() + } + } + }.await() + } + +} diff --git a/src/main/kotlin/apis/UrsaManager.kt b/src/main/kotlin/apis/UrsaManager.kt new file mode 100644 index 0000000..13f7aef --- /dev/null +++ b/src/main/kotlin/apis/UrsaManager.kt @@ -0,0 +1,72 @@ + + +package moe.nea.firmament.apis + +import io.ktor.client.request.* +import io.ktor.client.statement.* +import io.ktor.http.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.withContext +import moe.nea.firmament.Firmament +import net.minecraft.client.MinecraftClient +import java.time.Duration +import java.time.Instant +import java.util.* + +object UrsaManager { + private data class Token( + val validUntil: Instant, + val token: String, + val obtainedFrom: String, + ) { + fun isValid(host: String) = Instant.now().plusSeconds(60) < validUntil && obtainedFrom == host + } + + private var currentToken: Token? = null + private val lock = Mutex() + private fun getToken(host: String) = currentToken?.takeIf { it.isValid(host) } + + suspend fun request(path: List): HttpResponse { + var didLock = false + try { + val host = "ursa.notenoughupdates.org" + var token = getToken(host) + if (token == null) { + lock.lock() + didLock = true + token = getToken(host) + } + val response = Firmament.httpClient.get { + url { + this.host = host + appendPathSegments(path, encodeSlash = true) + } + if (token == null) { + withContext(Dispatchers.IO) { + val mc = MinecraftClient.getInstance() + val serverId = UUID.randomUUID().toString() + mc.sessionService.joinServer(mc.session.uuidOrNull, mc.session.accessToken, serverId) + header("x-ursa-username", mc.session.username) + header("x-ursa-serverid", serverId) + } + } else { + header("x-ursa-token", token.token) + } + } + val savedToken = response.headers["x-ursa-token"] + if (savedToken != null) { + val validUntil = response.headers["x-ursa-expires"]?.toLongOrNull()?.let { Instant.ofEpochMilli(it) } + ?: (Instant.now() + Duration.ofMinutes(55)) + currentToken = Token(validUntil, savedToken, host) + } + if (response.status.value != 200) { + Firmament.logger.error("Failed to contact ursa minor: ${response.bodyAsText()}") + } + return response + } finally { + if (didLock) + lock.unlock() + } + } +} diff --git a/src/main/kotlin/commands/CaseInsensitiveLiteralCommandNode.kt b/src/main/kotlin/commands/CaseInsensitiveLiteralCommandNode.kt new file mode 100644 index 0000000..10772b0 --- /dev/null +++ b/src/main/kotlin/commands/CaseInsensitiveLiteralCommandNode.kt @@ -0,0 +1,75 @@ + + +package moe.nea.firmament.commands + +import com.mojang.brigadier.Command +import com.mojang.brigadier.RedirectModifier +import com.mojang.brigadier.StringReader +import com.mojang.brigadier.builder.LiteralArgumentBuilder +import com.mojang.brigadier.context.CommandContextBuilder +import com.mojang.brigadier.context.StringRange +import com.mojang.brigadier.exceptions.CommandSyntaxException +import com.mojang.brigadier.tree.CommandNode +import com.mojang.brigadier.tree.LiteralCommandNode +import java.util.function.Predicate + +class CaseInsensitiveLiteralCommandNode( + literal: String, command: Command?, requirement: Predicate?, + redirect: CommandNode?, modifier: RedirectModifier?, forks: Boolean +) : LiteralCommandNode( + literal.lowercase(), command, requirement, redirect, modifier, forks +) { + class Builder(literal: String) : LiteralArgumentBuilder(literal) { + override fun build(): LiteralCommandNode { + val result = CaseInsensitiveLiteralCommandNode( + literal, + command, requirement, redirect, redirectModifier, isFork + ) + for (argument in arguments) { + result.addChild(argument) + } + return result + } + } + + override fun createBuilder(): LiteralArgumentBuilder { + return Builder(literal).also { + it.requires(requirement) + it.forward(redirect, redirectModifier, isFork) + if (command != null) + it.executes(command) + } + } + + override fun parse(reader: StringReader, contextBuilder: CommandContextBuilder) { + val start = reader.cursor + val end = parse0(reader) + if (end > -1) { + contextBuilder.withNode(this, StringRange.between(start, end)) + return + } + + throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.literalIncorrect().createWithContext(reader, literal) + } + + override fun toString(): String { + return "" + } + + private fun parse0(reader: StringReader): Int { + val start = reader.cursor + if (reader.canRead(literal.length)) { + val end = start + literal.length + if (reader.string.substring(start, end).equals(literal, true)) { + reader.cursor = end + if (!reader.canRead() || reader.peek() == ' ') { + return end + } else { + reader.cursor = start + } + } + } + return -1 + } + +} diff --git a/src/main/kotlin/commands/RestArgumentType.kt b/src/main/kotlin/commands/RestArgumentType.kt new file mode 100644 index 0000000..361907f --- /dev/null +++ b/src/main/kotlin/commands/RestArgumentType.kt @@ -0,0 +1,15 @@ + + +package moe.nea.firmament.commands + +import com.mojang.brigadier.StringReader +import com.mojang.brigadier.arguments.ArgumentType + +object RestArgumentType : ArgumentType { + override fun parse(reader: StringReader): String { + val remaining = reader.remaining + reader.cursor += remaining.length + return remaining + } + +} diff --git a/src/main/kotlin/commands/dsl.kt b/src/main/kotlin/commands/dsl.kt new file mode 100644 index 0000000..d1f0d8c --- /dev/null +++ b/src/main/kotlin/commands/dsl.kt @@ -0,0 +1,118 @@ + + +package moe.nea.firmament.commands + +import com.mojang.brigadier.arguments.ArgumentType +import com.mojang.brigadier.builder.ArgumentBuilder +import com.mojang.brigadier.builder.RequiredArgumentBuilder +import com.mojang.brigadier.context.CommandContext +import com.mojang.brigadier.suggestion.SuggestionProvider +import kotlinx.coroutines.launch +import moe.nea.firmament.Firmament +import moe.nea.firmament.util.MinecraftDispatcher +import moe.nea.firmament.util.iterate +import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource +import java.lang.reflect.ParameterizedType +import java.lang.reflect.Type +import java.lang.reflect.TypeVariable + + +typealias DefaultSource = FabricClientCommandSource + + +inline val > T.context get() = this +operator fun > C.get(arg: TypeSafeArg): T { + return arg.get(this) +} + +fun literal( + name: String, + block: CaseInsensitiveLiteralCommandNode.Builder.() -> Unit +): CaseInsensitiveLiteralCommandNode.Builder = + CaseInsensitiveLiteralCommandNode.Builder(name).also(block) + + +private fun normalizeGeneric(argument: Type): Class<*> { + return when (argument) { + is Class<*> -> argument + is TypeVariable<*> -> normalizeGeneric(argument.bounds[0]) + is ParameterizedType -> normalizeGeneric(argument.rawType) + else -> Any::class.java + } +} + +data class TypeSafeArg(val name: String, val argument: ArgumentType) { + val argClass by lazy { + argument.javaClass + .iterate>> { + it.superclass + } + .flatMap { + it.genericInterfaces.toList() + } + .filterIsInstance() + .find { it.rawType == ArgumentType::class.java }!! + .let { normalizeGeneric(it.actualTypeArguments[0]) } + } + + @JvmName("getWithThis") + fun CommandContext.get(): T = + get(this) + + + fun get(ctx: CommandContext): T { + try { + return ctx.getArgument(name, argClass) as T + } catch (e: Exception) { + if (ctx.child != null) { + return get(ctx.child) + } + throw e + } + } +} + + +fun argument( + name: String, + argument: ArgumentType, + block: RequiredArgumentBuilder.(TypeSafeArg) -> Unit +): RequiredArgumentBuilder = + RequiredArgumentBuilder.argument(name, argument).also { block(it, TypeSafeArg(name, argument)) } + +fun , AT : Any> T.thenArgument( + name: String, + argument: ArgumentType, + block: RequiredArgumentBuilder.(TypeSafeArg) -> Unit +): T = then(argument(name, argument, block)) + +fun > T.suggestsList(provider: CommandContext.() -> Iterable) { + suggests(SuggestionProvider { context, builder -> + provider(context) + .asSequence() + .filter { it.startsWith(builder.remaining, ignoreCase = true) } + .forEach { + builder.suggest(it) + } + builder.buildFuture() + }) +} + +fun > T.thenLiteral( + name: String, + block: CaseInsensitiveLiteralCommandNode.Builder.() -> Unit +): T = + then(literal(name, block)) + +fun > T.then(node: ArgumentBuilder, block: T.() -> Unit): T = + then(node).also(block) + +fun > T.thenExecute(block: suspend CommandContext.() -> Unit): T = + executes { + Firmament.coroutineScope.launch(MinecraftDispatcher) { + block(it) + } + 1 + } + + diff --git a/src/main/kotlin/commands/rome.kt b/src/main/kotlin/commands/rome.kt new file mode 100644 index 0000000..015512d --- /dev/null +++ b/src/main/kotlin/commands/rome.kt @@ -0,0 +1,230 @@ + + +package moe.nea.firmament.commands + +import com.mojang.brigadier.CommandDispatcher +import com.mojang.brigadier.arguments.StringArgumentType.string +import io.ktor.client.statement.bodyAsText +import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource +import net.minecraft.text.Text +import moe.nea.firmament.apis.UrsaManager +import moe.nea.firmament.events.CommandEvent +import moe.nea.firmament.features.debug.PowerUserTools +import moe.nea.firmament.features.inventory.buttons.InventoryButtons +import moe.nea.firmament.features.inventory.storageoverlay.StorageOverlayScreen +import moe.nea.firmament.features.inventory.storageoverlay.StorageOverviewScreen +import moe.nea.firmament.gui.config.AllConfigsGui +import moe.nea.firmament.gui.config.BooleanHandler +import moe.nea.firmament.gui.config.ManagedOption +import moe.nea.firmament.repo.HypixelStaticData +import moe.nea.firmament.repo.RepoManager +import moe.nea.firmament.util.FirmFormatters +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.SBData +import moe.nea.firmament.util.ScreenUtil +import moe.nea.firmament.util.SkyblockId + + +fun firmamentCommand() = literal("firmament") { + thenLiteral("config") { + thenExecute { + AllConfigsGui.showAllGuis() + } + thenLiteral("toggle") { + thenArgument("config", string()) { config -> + suggestsList { + AllConfigsGui.allConfigs.asSequence().map { it.name }.asIterable() + } + thenArgument("property", string()) { property -> + suggestsList { + (AllConfigsGui.allConfigs.find { it.name == this[config] } ?: return@suggestsList listOf()) + .allOptions.entries.asSequence().filter { it.value.handler is BooleanHandler } + .map { it.key } + .asIterable() + } + thenExecute { + val config = this[config] + val property = this[property] + + val configObj = AllConfigsGui.allConfigs.find { it.name == config } + if (configObj == null) { + source.sendFeedback( + Text.stringifiedTranslatable( + "firmament.command.toggle.no-config-found", + config + ) + ) + return@thenExecute + } + val propertyObj = configObj.allOptions[property] + if (propertyObj == null) { + source.sendFeedback( + Text.stringifiedTranslatable("firmament.command.toggle.no-property-found", property) + ) + return@thenExecute + } + if (propertyObj.handler !is BooleanHandler) { + source.sendFeedback( + Text.stringifiedTranslatable("firmament.command.toggle.not-a-toggle", property) + ) + return@thenExecute + } + propertyObj as ManagedOption + propertyObj.value = !propertyObj.value + configObj.save() + source.sendFeedback( + Text.stringifiedTranslatable( + "firmament.command.toggle.toggled", configObj.labelText, + propertyObj.labelText, + Text.translatable("firmament.toggle.${propertyObj.value}") + ) + ) + } + } + } + } + } + thenLiteral("buttons") { + thenExecute { + InventoryButtons.openEditor() + } + } + thenLiteral("sendcoords") { + thenExecute { + val p = MC.player ?: return@thenExecute + MC.sendServerChat("x: ${p.blockX}, y: ${p.blockY}, z: ${p.blockZ}") + } + thenArgument("rest", RestArgumentType) { rest -> + thenExecute { + val p = MC.player ?: return@thenExecute + MC.sendServerChat("x: ${p.blockX}, y: ${p.blockY}, z: ${p.blockZ} ${this[rest]}") + } + } + } + thenLiteral("storageoverview") { + thenExecute { + ScreenUtil.setScreenLater(StorageOverviewScreen()) + MC.player?.networkHandler?.sendChatCommand("storage") + } + } + thenLiteral("storage") { + thenExecute { + ScreenUtil.setScreenLater(StorageOverlayScreen()) + MC.player?.networkHandler?.sendChatCommand("storage") + } + } + thenLiteral("repo") { + thenLiteral("reload") { + thenLiteral("fetch") { + thenExecute { + source.sendFeedback(Text.translatable("firmament.repo.reload.network")) // TODO better reporting + RepoManager.launchAsyncUpdate() + } + } + thenExecute { + source.sendFeedback(Text.translatable("firmament.repo.reload.disk")) + RepoManager.reload() + } + } + } + thenLiteral("price") { + thenArgument("item", string()) { item -> + suggestsList { RepoManager.neuRepo.items.items.keys } + thenExecute { + val itemName = SkyblockId(get(item)) + source.sendFeedback(Text.stringifiedTranslatable("firmament.price", itemName.neuItem)) + val bazaarData = HypixelStaticData.bazaarData[itemName] + if (bazaarData != null) { + source.sendFeedback(Text.translatable("firmament.price.bazaar")) + source.sendFeedback( + Text.stringifiedTranslatable("firmament.price.bazaar.productid", bazaarData.productId.bazaarId) + ) + source.sendFeedback( + Text.stringifiedTranslatable( + "firmament.price.bazaar.buy.price", + FirmFormatters.formatCommas(bazaarData.quickStatus.buyPrice, 1) + ) + ) + source.sendFeedback( + Text.stringifiedTranslatable( + "firmament.price.bazaar.buy.order", + bazaarData.quickStatus.buyOrders + ) + ) + source.sendFeedback( + Text.stringifiedTranslatable( + "firmament.price.bazaar.sell.price", + FirmFormatters.formatCommas(bazaarData.quickStatus.sellPrice, 1) + ) + ) + source.sendFeedback( + Text.stringifiedTranslatable( + "firmament.price.bazaar.sell.order", + bazaarData.quickStatus.sellOrders + ) + ) + } + val lowestBin = HypixelStaticData.lowestBin[itemName] + if (lowestBin != null) { + source.sendFeedback( + Text.stringifiedTranslatable( + "firmament.price.lowestbin", + FirmFormatters.formatCommas(lowestBin, 1) + ) + ) + } + } + } + } + thenLiteral("dev") { + thenLiteral("simulate") { + thenArgument("message", RestArgumentType) { message -> + thenExecute { + MC.instance.messageHandler.onGameMessage(Text.literal(get(message)), false) + } + } + } + thenLiteral("sbdata") { + thenExecute { + source.sendFeedback(Text.stringifiedTranslatable("firmament.sbinfo.profile", SBData.profileId)) + val locrawInfo = SBData.locraw + if (locrawInfo == null) { + source.sendFeedback(Text.translatable("firmament.sbinfo.nolocraw")) + } else { + source.sendFeedback(Text.stringifiedTranslatable("firmament.sbinfo.server", locrawInfo.server)) + source.sendFeedback(Text.stringifiedTranslatable("firmament.sbinfo.gametype", locrawInfo.gametype)) + source.sendFeedback(Text.stringifiedTranslatable("firmament.sbinfo.mode", locrawInfo.mode)) + source.sendFeedback(Text.stringifiedTranslatable("firmament.sbinfo.map", locrawInfo.map)) + } + } + } + thenLiteral("copyEntities") { + thenExecute { + val player = MC.player ?: return@thenExecute + player.world.getOtherEntities(player, player.boundingBox.expand(12.0)).forEach(PowerUserTools::showEntity) + } + } + thenLiteral("callUrsa") { + thenArgument("path", string()) { path -> + thenExecute { + source.sendFeedback(Text.translatable("firmament.ursa.debugrequest.start")) + val text = UrsaManager.request(this[path].split("/")).bodyAsText() + source.sendFeedback(Text.stringifiedTranslatable("firmament.ursa.debugrequest.result", text)) + } + } + } + } + CommandEvent.SubCommand.publish(CommandEvent.SubCommand(this@literal)) +} + + +fun registerFirmamentCommand(dispatcher: CommandDispatcher) { + val firmament = dispatcher.register(firmamentCommand()) + dispatcher.register(literal("firm") { + redirect(firmament) + }) +} + + + + diff --git a/src/main/kotlin/events/AllowChatEvent.kt b/src/main/kotlin/events/AllowChatEvent.kt new file mode 100644 index 0000000..3069843 --- /dev/null +++ b/src/main/kotlin/events/AllowChatEvent.kt @@ -0,0 +1,16 @@ + + +package moe.nea.firmament.events + +import moe.nea.firmament.util.unformattedString +import net.minecraft.text.Text + +/** + * Filter whether the user should see a chat message altogether. May or may not be called for every chat packet sent by + * the server. When that quality is desired, consider [ProcessChatEvent] instead. + */ +data class AllowChatEvent(val text: Text) : FirmamentEvent.Cancellable() { + val unformattedString = text.unformattedString + + companion object : FirmamentEventBus() +} diff --git a/src/main/kotlin/events/AttackBlockEvent.kt b/src/main/kotlin/events/AttackBlockEvent.kt new file mode 100644 index 0000000..bbaa81d --- /dev/null +++ b/src/main/kotlin/events/AttackBlockEvent.kt @@ -0,0 +1,18 @@ + +package moe.nea.firmament.events + +import net.minecraft.entity.player.PlayerEntity +import net.minecraft.util.Hand +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Direction +import net.minecraft.world.World + +data class AttackBlockEvent( + val player: PlayerEntity, + val world: World, + val hand: Hand, + val blockPos: BlockPos, + val direction: Direction +) : FirmamentEvent.Cancellable() { + companion object : FirmamentEventBus() +} diff --git a/src/main/kotlin/events/BakeExtraModelsEvent.kt b/src/main/kotlin/events/BakeExtraModelsEvent.kt new file mode 100644 index 0000000..f75bedc --- /dev/null +++ b/src/main/kotlin/events/BakeExtraModelsEvent.kt @@ -0,0 +1,21 @@ + +package moe.nea.firmament.events + +import java.util.function.Consumer +import net.minecraft.client.util.ModelIdentifier + +class BakeExtraModelsEvent( + private val addItemModel: Consumer, + private val addAnyModel: Consumer, +) : FirmamentEvent() { + + fun addNonItemModel(modelIdentifier: ModelIdentifier) { + this.addAnyModel.accept(modelIdentifier) + } + + fun addItemModel(modelIdentifier: ModelIdentifier) { + this.addItemModel.accept(modelIdentifier) + } + + companion object : FirmamentEventBus() +} diff --git a/src/main/kotlin/events/ClientStartedEvent.kt b/src/main/kotlin/events/ClientStartedEvent.kt new file mode 100644 index 0000000..637916d --- /dev/null +++ b/src/main/kotlin/events/ClientStartedEvent.kt @@ -0,0 +1,6 @@ + +package moe.nea.firmament.events + +class ClientStartedEvent : FirmamentEvent() { + companion object : FirmamentEventBus() +} diff --git a/src/main/kotlin/events/CommandEvent.kt b/src/main/kotlin/events/CommandEvent.kt new file mode 100644 index 0000000..cc9cf45 --- /dev/null +++ b/src/main/kotlin/events/CommandEvent.kt @@ -0,0 +1,45 @@ + + +package moe.nea.firmament.events + +import com.mojang.brigadier.CommandDispatcher +import com.mojang.brigadier.tree.LiteralCommandNode +import net.minecraft.command.CommandRegistryAccess +import moe.nea.firmament.commands.CaseInsensitiveLiteralCommandNode +import moe.nea.firmament.commands.DefaultSource +import moe.nea.firmament.commands.literal +import moe.nea.firmament.commands.thenLiteral + +data class CommandEvent( + val dispatcher: CommandDispatcher, + val ctx: CommandRegistryAccess, + val serverCommands: CommandDispatcher<*>?, +) : FirmamentEvent() { + companion object : FirmamentEventBus() + + /** + * Register subcommands to `/firm`. For new top level commands use [CommandEvent]. Cannot be used to register + * subcommands to other commands. + */ + data class SubCommand( + val builder: CaseInsensitiveLiteralCommandNode.Builder, + ) : FirmamentEvent() { + companion object : FirmamentEventBus() + + fun subcommand(name: String, block: CaseInsensitiveLiteralCommandNode.Builder.() -> Unit) { + builder.thenLiteral(name, block) + } + } + + fun deleteCommand(name: String) { + dispatcher.root.children.removeIf { it.name.equals(name, ignoreCase = false) } + serverCommands?.root?.children?.removeIf { it.name.equals(name, ignoreCase = false) } + } + + fun register( + name: String, + block: CaseInsensitiveLiteralCommandNode.Builder.() -> Unit + ): LiteralCommandNode { + return dispatcher.register(literal(name, block)) + } +} diff --git a/src/main/kotlin/events/CustomItemModelEvent.kt b/src/main/kotlin/events/CustomItemModelEvent.kt new file mode 100644 index 0000000..27524a9 --- /dev/null +++ b/src/main/kotlin/events/CustomItemModelEvent.kt @@ -0,0 +1,43 @@ + + +package moe.nea.firmament.events + +import java.util.* +import net.minecraft.client.render.model.BakedModel +import net.minecraft.client.render.model.BakedModelManager +import net.minecraft.client.util.ModelIdentifier +import net.minecraft.item.ItemStack + +data class CustomItemModelEvent( + val itemStack: ItemStack, + var overrideModel: ModelIdentifier? = null, +) : FirmamentEvent() { + companion object : FirmamentEventBus() { + private val cache = IdentityHashMap() + private val sentinelNull = Object() + + fun clearCache() { + cache.clear() + } + + @JvmStatic + fun getModelIdentifier(itemStack: ItemStack?): ModelIdentifier? { + if (itemStack == null) return null + return publish(CustomItemModelEvent(itemStack)).overrideModel + } + + @JvmStatic + fun getModel(itemStack: ItemStack?, thing: BakedModelManager): BakedModel? { + if (itemStack == null) return null + val cachedValue = cache.getOrPut(itemStack) { + val modelId = getModelIdentifier(itemStack) ?: return@getOrPut sentinelNull + val bakedModel = thing.getModel(modelId) + if (bakedModel === thing.missingModel) return@getOrPut sentinelNull + bakedModel + } + if (cachedValue === sentinelNull) + return null + return cachedValue as BakedModel + } + } +} diff --git a/src/main/kotlin/events/EarlyResourceReloadEvent.kt b/src/main/kotlin/events/EarlyResourceReloadEvent.kt new file mode 100644 index 0000000..ec8377a --- /dev/null +++ b/src/main/kotlin/events/EarlyResourceReloadEvent.kt @@ -0,0 +1,10 @@ + +package moe.nea.firmament.events + +import java.util.concurrent.Executor +import net.minecraft.resource.ResourceManager + +data class EarlyResourceReloadEvent(val resourceManager: ResourceManager, val preparationExecutor: Executor) : + FirmamentEvent() { + companion object : FirmamentEventBus() +} diff --git a/src/main/kotlin/events/EntityDespawnEvent.kt b/src/main/kotlin/events/EntityDespawnEvent.kt new file mode 100644 index 0000000..93dc477 --- /dev/null +++ b/src/main/kotlin/events/EntityDespawnEvent.kt @@ -0,0 +1,11 @@ + +package moe.nea.firmament.events + +import net.minecraft.entity.Entity + +data class EntityDespawnEvent( + val entity: Entity?, val entityId: Int, + val reason: Entity.RemovalReason, +) : FirmamentEvent() { + companion object: FirmamentEventBus() +} diff --git a/src/main/kotlin/events/EntityInteractionEvent.kt b/src/main/kotlin/events/EntityInteractionEvent.kt new file mode 100644 index 0000000..123ea39 --- /dev/null +++ b/src/main/kotlin/events/EntityInteractionEvent.kt @@ -0,0 +1,29 @@ + +package moe.nea.firmament.events + +import net.minecraft.entity.Entity +import net.minecraft.util.Hand + +data class EntityInteractionEvent( + val kind: InteractionKind, + val entity: Entity, + val hand: Hand, +) : FirmamentEvent() { + companion object : FirmamentEventBus() + enum class InteractionKind { + /** + * Is sent when left-clicking an entity + */ + ATTACK, + + /** + * Is a fallback when [INTERACT_AT_LOCATION] fails + */ + INTERACT, + + /** + * Is tried first on right click + */ + INTERACT_AT_LOCATION, + } +} diff --git a/src/main/kotlin/events/EntityUpdateEvent.kt b/src/main/kotlin/events/EntityUpdateEvent.kt new file mode 100644 index 0000000..d091984 --- /dev/null +++ b/src/main/kotlin/events/EntityUpdateEvent.kt @@ -0,0 +1,31 @@ + +package moe.nea.firmament.events + +import net.minecraft.entity.Entity +import net.minecraft.entity.LivingEntity +import net.minecraft.entity.data.DataTracker +import net.minecraft.network.packet.s2c.play.EntityAttributesS2CPacket + +/** + * This event is fired when some entity properties are updated. + * It is not fired for common changes like position, but is for less common ones, + * like health, tracked data, names, equipment. It is always fired + * *after* the values have been applied to the entity. + */ +sealed class EntityUpdateEvent : FirmamentEvent() { + companion object : FirmamentEventBus() + + abstract val entity: Entity + + data class AttributeUpdate( + override val entity: LivingEntity, + val attributes: List, + ) : EntityUpdateEvent() + + data class TrackedDataUpdate( + override val entity: Entity, + val trackedValues: List>, + ) : EntityUpdateEvent() + +// TODO: onEntityPassengersSet, onEntityAttach?, onEntityEquipmentUpdate, onEntityStatusEffect +} diff --git a/src/main/kotlin/events/FeaturesInitializedEvent.kt b/src/main/kotlin/events/FeaturesInitializedEvent.kt new file mode 100644 index 0000000..ad2ad8a --- /dev/null +++ b/src/main/kotlin/events/FeaturesInitializedEvent.kt @@ -0,0 +1,8 @@ + +package moe.nea.firmament.events + +import moe.nea.firmament.features.FirmamentFeature + +data class FeaturesInitializedEvent(val features: List) : FirmamentEvent() { + companion object : FirmamentEventBus() +} diff --git a/src/main/kotlin/events/FinalizeResourceManagerEvent.kt b/src/main/kotlin/events/FinalizeResourceManagerEvent.kt new file mode 100644 index 0000000..c43ad3b --- /dev/null +++ b/src/main/kotlin/events/FinalizeResourceManagerEvent.kt @@ -0,0 +1,10 @@ + +package moe.nea.firmament.events + +import net.minecraft.resource.ReloadableResourceManagerImpl + +data class FinalizeResourceManagerEvent( + val resourceManager: ReloadableResourceManagerImpl, +) : FirmamentEvent() { + companion object : FirmamentEventBus() +} diff --git a/src/main/kotlin/events/FirmamentEvent.kt b/src/main/kotlin/events/FirmamentEvent.kt new file mode 100644 index 0000000..1a93ef5 --- /dev/null +++ b/src/main/kotlin/events/FirmamentEvent.kt @@ -0,0 +1,38 @@ + + +package moe.nea.firmament.events + +/** + * An event that can be fired by a [FirmamentEventBus]. + * + * Typically, that event bus is implemented as a companion object + * + * ``` + * class SomeEvent : FirmamentEvent() { + * companion object : FirmamentEventBus() + * } + * ``` + */ +abstract class FirmamentEvent { + /** + * A [FirmamentEvent] that can be [cancelled] + */ + abstract class Cancellable : FirmamentEvent() { + /** + * Cancels this is event. + * + * @see cancelled + */ + fun cancel() { + cancelled = true + } + + /** + * Whether this event is cancelled. + * + * Cancelled events will bypass handlers unless otherwise specified and will prevent the action that this + * event was originally fired for. + */ + var cancelled: Boolean = false + } +} diff --git a/src/main/kotlin/events/FirmamentEventBus.kt b/src/main/kotlin/events/FirmamentEventBus.kt new file mode 100644 index 0000000..ee9e6c8 --- /dev/null +++ b/src/main/kotlin/events/FirmamentEventBus.kt @@ -0,0 +1,52 @@ + + +package moe.nea.firmament.events + +import java.util.concurrent.CopyOnWriteArrayList +import moe.nea.firmament.Firmament +import moe.nea.firmament.util.MC + +/** + * A pubsub event bus. + * + * [subscribe] to events [publish]ed on this event bus. + * Subscriptions may not necessarily be delivered in the order of registering. + */ +open class FirmamentEventBus { + data class Handler( + val invocation: (T) -> Unit, val receivesCancelled: Boolean, + var knownErrors: MutableSet> = mutableSetOf(), + ) + + private val toHandle: MutableList> = CopyOnWriteArrayList() + fun subscribe(handle: (T) -> Unit) { + subscribe(false, handle) + } + + fun subscribe(receivesCancelled: Boolean, handle: (T) -> Unit) { + toHandle.add(Handler(handle, receivesCancelled)) + } + + fun publish(event: T): T { + for (function in toHandle) { + if (function.receivesCancelled || event !is FirmamentEvent.Cancellable || !event.cancelled) { + try { + function.invocation(event) + } catch (e: Exception) { + val klass = e.javaClass + if (!function.knownErrors.contains(klass) || Firmament.DEBUG) { + function.knownErrors.add(klass) + Firmament.logger.error("Caught exception during processing event $event by $function", e) + } + } + } + } + return event + } + + fun publishSync(event: T) { + MC.onMainThread { + publish(event) + } + } +} diff --git a/src/main/kotlin/events/HandledScreenClickEvent.kt b/src/main/kotlin/events/HandledScreenClickEvent.kt new file mode 100644 index 0000000..4c3003c --- /dev/null +++ b/src/main/kotlin/events/HandledScreenClickEvent.kt @@ -0,0 +1,10 @@ + + +package moe.nea.firmament.events + +import net.minecraft.client.gui.screen.ingame.HandledScreen + +data class HandledScreenClickEvent(val screen: HandledScreen<*>, val mouseX: Double, val mouseY: Double, val button: Int) : + FirmamentEvent.Cancellable() { + companion object : FirmamentEventBus() +} diff --git a/src/main/kotlin/events/HandledScreenForegroundEvent.kt b/src/main/kotlin/events/HandledScreenForegroundEvent.kt new file mode 100644 index 0000000..f16d30e --- /dev/null +++ b/src/main/kotlin/events/HandledScreenForegroundEvent.kt @@ -0,0 +1,16 @@ + + +package moe.nea.firmament.events + +import net.minecraft.client.gui.DrawContext +import net.minecraft.client.gui.screen.ingame.HandledScreen + +data class HandledScreenForegroundEvent( + val screen: HandledScreen<*>, + val context: DrawContext, + val mouseX: Int, + val mouseY: Int, + val delta: Float +) : FirmamentEvent() { + companion object : FirmamentEventBus() +} diff --git a/src/main/kotlin/events/HandledScreenKeyPressedEvent.kt b/src/main/kotlin/events/HandledScreenKeyPressedEvent.kt new file mode 100644 index 0000000..7ec2abb --- /dev/null +++ b/src/main/kotlin/events/HandledScreenKeyPressedEvent.kt @@ -0,0 +1,24 @@ + + +package moe.nea.firmament.events + +import net.minecraft.client.gui.screen.ingame.HandledScreen +import net.minecraft.client.option.KeyBinding +import moe.nea.firmament.keybindings.IKeyBinding + +data class HandledScreenKeyPressedEvent( + val screen: HandledScreen<*>, + val keyCode: Int, + val scanCode: Int, + val modifiers: Int +) : FirmamentEvent.Cancellable() { + companion object : FirmamentEventBus() + + fun matches(keyBinding: KeyBinding): Boolean { + return matches(IKeyBinding.minecraft(keyBinding)) + } + + fun matches(keyBinding: IKeyBinding): Boolean { + return keyBinding.matches(keyCode, scanCode, modifiers) + } +} diff --git a/src/main/kotlin/events/HandledScreenPushREIEvent.kt b/src/main/kotlin/events/HandledScreenPushREIEvent.kt new file mode 100644 index 0000000..1bb495a --- /dev/null +++ b/src/main/kotlin/events/HandledScreenPushREIEvent.kt @@ -0,0 +1,18 @@ + + +package moe.nea.firmament.events + +import me.shedaniel.math.Rectangle +import net.minecraft.client.gui.screen.ingame.HandledScreen + +data class HandledScreenPushREIEvent( + val screen: HandledScreen<*>, + val rectangles: MutableList = mutableListOf() +) : FirmamentEvent() { + + fun block(rectangle: Rectangle) { + rectangles.add(rectangle) + } + + companion object : FirmamentEventBus() +} diff --git a/src/main/kotlin/events/HotbarItemRenderEvent.kt b/src/main/kotlin/events/HotbarItemRenderEvent.kt new file mode 100644 index 0000000..a1940e6 --- /dev/null +++ b/src/main/kotlin/events/HotbarItemRenderEvent.kt @@ -0,0 +1,17 @@ + + +package moe.nea.firmament.events + +import net.minecraft.client.gui.DrawContext +import net.minecraft.client.render.RenderTickCounter +import net.minecraft.item.ItemStack + +data class HotbarItemRenderEvent( + val item: ItemStack, + val context: DrawContext, + val x: Int, + val y: Int, + val tickDelta: RenderTickCounter, +) : FirmamentEvent() { + companion object : FirmamentEventBus() +} diff --git a/src/main/kotlin/events/HudRenderEvent.kt b/src/main/kotlin/events/HudRenderEvent.kt new file mode 100644 index 0000000..555b3c8 --- /dev/null +++ b/src/main/kotlin/events/HudRenderEvent.kt @@ -0,0 +1,13 @@ + + +package moe.nea.firmament.events + +import net.minecraft.client.gui.DrawContext +import net.minecraft.client.render.RenderTickCounter + +/** + * Called when hud elements should be rendered, before the screen, but after the world. + */ +data class HudRenderEvent(val context: DrawContext, val tickDelta: RenderTickCounter) : FirmamentEvent() { + companion object : FirmamentEventBus() +} diff --git a/src/main/kotlin/events/IsSlotProtectedEvent.kt b/src/main/kotlin/events/IsSlotProtectedEvent.kt new file mode 100644 index 0000000..cd431f7 --- /dev/null +++ b/src/main/kotlin/events/IsSlotProtectedEvent.kt @@ -0,0 +1,46 @@ + + +package moe.nea.firmament.events + +import net.minecraft.item.ItemStack +import net.minecraft.screen.slot.Slot +import net.minecraft.screen.slot.SlotActionType +import net.minecraft.text.Text +import moe.nea.firmament.util.CommonSoundEffects +import moe.nea.firmament.util.MC + +data class IsSlotProtectedEvent( + val slot: Slot?, + val actionType: SlotActionType, + var isProtected: Boolean, + val itemStackOverride: ItemStack?, + var silent: Boolean = false, +) : FirmamentEvent() { + val itemStack get() = itemStackOverride ?: slot!!.stack + + fun protect() { + isProtected = true + } + + fun protectSilent() { + if (!isProtected) { + silent = true + } + isProtected = true + } + + companion object : FirmamentEventBus() { + @JvmStatic + @JvmOverloads + fun shouldBlockInteraction(slot: Slot?, action: SlotActionType, itemStackOverride: ItemStack? = null): Boolean { + if (slot == null && itemStackOverride == null) return false + val event = IsSlotProtectedEvent(slot, action, false, itemStackOverride) + publish(event) + if (event.isProtected && !event.silent) { + MC.player?.sendMessage(Text.translatable("firmament.protectitem").append(event.itemStack.name)) + CommonSoundEffects.playFailure() + } + return event.isProtected + } + } +} diff --git a/src/main/kotlin/events/ItemTooltipEvent.kt b/src/main/kotlin/events/ItemTooltipEvent.kt new file mode 100644 index 0000000..d86e06f --- /dev/null +++ b/src/main/kotlin/events/ItemTooltipEvent.kt @@ -0,0 +1,14 @@ + + +package moe.nea.firmament.events + +import net.minecraft.item.Item.TooltipContext +import net.minecraft.item.ItemStack +import net.minecraft.item.tooltip.TooltipType +import net.minecraft.text.Text + +data class ItemTooltipEvent( + val stack: ItemStack, val context: TooltipContext, val type: TooltipType, val lines: MutableList +) : FirmamentEvent() { + companion object : FirmamentEventBus() +} diff --git a/src/main/kotlin/events/MaskCommands.kt b/src/main/kotlin/events/MaskCommands.kt new file mode 100644 index 0000000..35aade0 --- /dev/null +++ b/src/main/kotlin/events/MaskCommands.kt @@ -0,0 +1,13 @@ + + +package moe.nea.firmament.events + +import com.mojang.brigadier.CommandDispatcher + +data class MaskCommands(val dispatcher: CommandDispatcher<*>) : FirmamentEvent() { + companion object : FirmamentEventBus() + + fun mask(name: String) { + dispatcher.root.children.removeIf { it.name.equals(name, ignoreCase = true) } + } +} diff --git a/src/main/kotlin/events/ModifyChatEvent.kt b/src/main/kotlin/events/ModifyChatEvent.kt new file mode 100644 index 0000000..a5868e8 --- /dev/null +++ b/src/main/kotlin/events/ModifyChatEvent.kt @@ -0,0 +1,21 @@ + + +package moe.nea.firmament.events + +import moe.nea.firmament.util.unformattedString +import net.minecraft.text.Text + +/** + * Allow modification of a chat message before it is sent off to the user. Intended for display purposes. + */ +data class ModifyChatEvent(val originalText: Text) : FirmamentEvent() { + var unformattedString = originalText.unformattedString + private set + var replaceWith: Text = originalText + set(value) { + field = value + unformattedString = value.unformattedString + } + + companion object : FirmamentEventBus() +} diff --git a/src/main/kotlin/events/OutgoingPacketEvent.kt b/src/main/kotlin/events/OutgoingPacketEvent.kt new file mode 100644 index 0000000..93890ea --- /dev/null +++ b/src/main/kotlin/events/OutgoingPacketEvent.kt @@ -0,0 +1,9 @@ + + +package moe.nea.firmament.events + +import net.minecraft.network.packet.Packet + +data class OutgoingPacketEvent(val packet: Packet<*>) : FirmamentEvent.Cancellable() { + companion object : FirmamentEventBus() +} diff --git a/src/main/kotlin/events/ParticleSpawnEvent.kt b/src/main/kotlin/events/ParticleSpawnEvent.kt new file mode 100644 index 0000000..9359e4b --- /dev/null +++ b/src/main/kotlin/events/ParticleSpawnEvent.kt @@ -0,0 +1,18 @@ + + +package moe.nea.firmament.events + +import org.joml.Vector3f +import net.minecraft.particle.ParticleEffect +import net.minecraft.util.math.Vec3d + +data class ParticleSpawnEvent( + val particleEffect: ParticleEffect, + val position: Vec3d, + val offset: Vector3f, + val longDistance: Boolean, + val count: Int, + val speed: Float, +) : FirmamentEvent.Cancellable() { + companion object : FirmamentEventBus() +} diff --git a/src/main/kotlin/events/PlayerInventoryUpdate.kt b/src/main/kotlin/events/PlayerInventoryUpdate.kt new file mode 100644 index 0000000..6e8203a --- /dev/null +++ b/src/main/kotlin/events/PlayerInventoryUpdate.kt @@ -0,0 +1,11 @@ + +package moe.nea.firmament.events + +import net.minecraft.item.ItemStack + +sealed class PlayerInventoryUpdate : FirmamentEvent() { + companion object : FirmamentEventBus() + data class Single(val slot: Int, val stack: ItemStack) : PlayerInventoryUpdate() + data class Multi(val contents: List) : PlayerInventoryUpdate() + +} diff --git a/src/main/kotlin/events/ProcessChatEvent.kt b/src/main/kotlin/events/ProcessChatEvent.kt new file mode 100644 index 0000000..76c0b27 --- /dev/null +++ b/src/main/kotlin/events/ProcessChatEvent.kt @@ -0,0 +1,28 @@ + + +package moe.nea.firmament.events + +import net.minecraft.text.Text +import moe.nea.firmament.util.unformattedString + +/** + * Behaves like [AllowChatEvent], but is triggered even when cancelled by other mods. Intended for data collection. + * Make sure to subscribe to cancellable events as well when using. + */ +data class ProcessChatEvent(val text: Text, val wasExternallyCancelled: Boolean) : FirmamentEvent.Cancellable() { + val unformattedString = text.unformattedString + + val nameHeuristic: String? = run { + val firstColon = unformattedString.indexOf(':') + if (firstColon < 0) return@run null + val firstSpace = unformattedString.lastIndexOf(' ', firstColon) + unformattedString.substring(firstSpace + 1 until firstColon).takeIf { it.isNotEmpty() } + } + + init { + if (wasExternallyCancelled) + cancelled = true + } + + companion object : FirmamentEventBus() +} diff --git a/src/main/kotlin/events/ReloadRegistrationEvent.kt b/src/main/kotlin/events/ReloadRegistrationEvent.kt new file mode 100644 index 0000000..4c3083e --- /dev/null +++ b/src/main/kotlin/events/ReloadRegistrationEvent.kt @@ -0,0 +1,7 @@ +package moe.nea.firmament.events + +import io.github.moulberry.repo.NEURepository + +data class ReloadRegistrationEvent(val repo: NEURepository) : FirmamentEvent() { + companion object : FirmamentEventBus() +} diff --git a/src/main/kotlin/events/ScreenChangeEvent.kt b/src/main/kotlin/events/ScreenChangeEvent.kt new file mode 100644 index 0000000..489e487 --- /dev/null +++ b/src/main/kotlin/events/ScreenChangeEvent.kt @@ -0,0 +1,10 @@ + + +package moe.nea.firmament.events + +import net.minecraft.client.gui.screen.Screen + +data class ScreenChangeEvent(val old: Screen?, val new: Screen?) : FirmamentEvent.Cancellable() { + var overrideScreen: Screen? = null + companion object : FirmamentEventBus() +} diff --git a/src/main/kotlin/events/ScreenRenderPostEvent.kt b/src/main/kotlin/events/ScreenRenderPostEvent.kt new file mode 100644 index 0000000..79f4913 --- /dev/null +++ b/src/main/kotlin/events/ScreenRenderPostEvent.kt @@ -0,0 +1,16 @@ + + +package moe.nea.firmament.events + +import net.minecraft.client.gui.DrawContext +import net.minecraft.client.gui.screen.Screen + +data class ScreenRenderPostEvent( + val screen: Screen, + val mouseX: Int, + val mouseY: Int, + val tickDelta: Float, + val drawContext: DrawContext +) : FirmamentEvent() { + companion object : FirmamentEventBus() +} diff --git a/src/main/kotlin/events/ServerConnectedEvent.kt b/src/main/kotlin/events/ServerConnectedEvent.kt new file mode 100644 index 0000000..26897f2 --- /dev/null +++ b/src/main/kotlin/events/ServerConnectedEvent.kt @@ -0,0 +1,18 @@ +package moe.nea.firmament.events + +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents +import net.minecraft.client.MinecraftClient +import net.minecraft.client.network.ClientPlayNetworkHandler +import net.minecraft.network.ClientConnection + +data class ServerConnectedEvent( + val connection: ClientConnection +) : FirmamentEvent() { + companion object : FirmamentEventBus() { + init { + ClientPlayConnectionEvents.INIT.register(ClientPlayConnectionEvents.Init { clientPlayNetworkHandler: ClientPlayNetworkHandler, minecraftClient: MinecraftClient -> + publishSync(ServerConnectedEvent(clientPlayNetworkHandler.connection)) + }) + } + } +} diff --git a/src/main/kotlin/events/SkyblockServerUpdateEvent.kt b/src/main/kotlin/events/SkyblockServerUpdateEvent.kt new file mode 100644 index 0000000..0bc5143 --- /dev/null +++ b/src/main/kotlin/events/SkyblockServerUpdateEvent.kt @@ -0,0 +1,15 @@ + + +package moe.nea.firmament.events + +import moe.nea.firmament.util.Locraw + +/** + * This event gets published whenever `/locraw` is queried and HyPixel returns a location different to the old one. + * + * **N.B.:** This event may get fired multiple times while on the server (for example, first to null, then to the + * correct location). + */ +data class SkyblockServerUpdateEvent(val oldLocraw: Locraw?, val newLocraw: Locraw?) : FirmamentEvent() { + companion object : FirmamentEventBus() +} diff --git a/src/main/kotlin/events/SlotClickEvent.kt b/src/main/kotlin/events/SlotClickEvent.kt new file mode 100644 index 0000000..d4abfb0 --- /dev/null +++ b/src/main/kotlin/events/SlotClickEvent.kt @@ -0,0 +1,15 @@ + +package moe.nea.firmament.events + +import net.minecraft.item.ItemStack +import net.minecraft.screen.slot.Slot +import net.minecraft.screen.slot.SlotActionType + +data class SlotClickEvent( + val slot: Slot, + val stack: ItemStack, + val button: Int, + val actionType: SlotActionType, +) : FirmamentEvent() { + companion object : FirmamentEventBus() +} diff --git a/src/main/kotlin/events/SlotRenderEvents.kt b/src/main/kotlin/events/SlotRenderEvents.kt new file mode 100644 index 0000000..8352581 --- /dev/null +++ b/src/main/kotlin/events/SlotRenderEvents.kt @@ -0,0 +1,34 @@ + + +package moe.nea.firmament.events + +import net.minecraft.client.gui.DrawContext +import net.minecraft.screen.slot.Slot + +interface SlotRenderEvents { + val context: DrawContext + val slot: Slot + val mouseX: Int + val mouseY: Int + val delta: Float + + data class Before( + override val context: DrawContext, override val slot: Slot, + override val mouseX: Int, + override val mouseY: Int, + override val delta: Float + ) : FirmamentEvent(), + SlotRenderEvents { + companion object : FirmamentEventBus() + } + + data class After( + override val context: DrawContext, override val slot: Slot, + override val mouseX: Int, + override val mouseY: Int, + override val delta: Float + ) : FirmamentEvent(), + SlotRenderEvents { + companion object : FirmamentEventBus() + } +} diff --git a/src/main/kotlin/events/SoundReceiveEvent.kt b/src/main/kotlin/events/SoundReceiveEvent.kt new file mode 100644 index 0000000..d1b85b6 --- /dev/null +++ b/src/main/kotlin/events/SoundReceiveEvent.kt @@ -0,0 +1,18 @@ + +package moe.nea.firmament.events + +import net.minecraft.registry.entry.RegistryEntry +import net.minecraft.sound.SoundCategory +import net.minecraft.sound.SoundEvent +import net.minecraft.util.math.Vec3d + +data class SoundReceiveEvent( + val sound: RegistryEntry, + val category: SoundCategory, + val position: Vec3d, + val pitch: Float, + val volume: Float, + val seed: Long +) : FirmamentEvent.Cancellable() { + companion object : FirmamentEventBus() +} diff --git a/src/main/kotlin/events/TickEvent.kt b/src/main/kotlin/events/TickEvent.kt new file mode 100644 index 0000000..18007f8 --- /dev/null +++ b/src/main/kotlin/events/TickEvent.kt @@ -0,0 +1,7 @@ + + +package moe.nea.firmament.events + +data class TickEvent(val tickCount: Int) : FirmamentEvent() { + companion object : FirmamentEventBus() +} diff --git a/src/main/kotlin/events/TooltipEvent.kt b/src/main/kotlin/events/TooltipEvent.kt new file mode 100644 index 0000000..630ba56 --- /dev/null +++ b/src/main/kotlin/events/TooltipEvent.kt @@ -0,0 +1,17 @@ + + +package moe.nea.firmament.events + +import net.minecraft.client.gui.tooltip.Tooltip +import net.minecraft.entity.player.PlayerEntity +import net.minecraft.item.Item +import net.minecraft.item.ItemStack + +data class TooltipEvent( + val itemStack: ItemStack, + val tooltip: Tooltip, + val tooltipContext: Item.TooltipContext, + val player: PlayerEntity? +) : FirmamentEvent() { + companion object : FirmamentEventBus() +} diff --git a/src/main/kotlin/events/UseBlockEvent.kt b/src/main/kotlin/events/UseBlockEvent.kt new file mode 100644 index 0000000..8bbe0de --- /dev/null +++ b/src/main/kotlin/events/UseBlockEvent.kt @@ -0,0 +1,11 @@ + +package moe.nea.firmament.events + +import net.minecraft.entity.player.PlayerEntity +import net.minecraft.util.Hand +import net.minecraft.util.hit.BlockHitResult +import net.minecraft.world.World + +data class UseBlockEvent(val player: PlayerEntity, val world: World, val hand: Hand, val hitResult: BlockHitResult) : FirmamentEvent.Cancellable() { + companion object : FirmamentEventBus() +} diff --git a/src/main/kotlin/events/WorldKeyboardEvent.kt b/src/main/kotlin/events/WorldKeyboardEvent.kt new file mode 100644 index 0000000..e8566fd --- /dev/null +++ b/src/main/kotlin/events/WorldKeyboardEvent.kt @@ -0,0 +1,18 @@ + + +package moe.nea.firmament.events + +import net.minecraft.client.option.KeyBinding +import moe.nea.firmament.keybindings.IKeyBinding + +data class WorldKeyboardEvent(val keyCode: Int, val scanCode: Int, val modifiers: Int) : FirmamentEvent.Cancellable() { + companion object : FirmamentEventBus() + + fun matches(keyBinding: KeyBinding): Boolean { + return matches(IKeyBinding.minecraft(keyBinding)) + } + + fun matches(keyBinding: IKeyBinding): Boolean { + return keyBinding.matches(keyCode, scanCode, modifiers) + } +} diff --git a/src/main/kotlin/events/WorldReadyEvent.kt b/src/main/kotlin/events/WorldReadyEvent.kt new file mode 100644 index 0000000..2c76c44 --- /dev/null +++ b/src/main/kotlin/events/WorldReadyEvent.kt @@ -0,0 +1,7 @@ + + +package moe.nea.firmament.events + +class WorldReadyEvent : FirmamentEvent() { + companion object : FirmamentEventBus() +} diff --git a/src/main/kotlin/events/WorldRenderLastEvent.kt b/src/main/kotlin/events/WorldRenderLastEvent.kt new file mode 100644 index 0000000..21a670d --- /dev/null +++ b/src/main/kotlin/events/WorldRenderLastEvent.kt @@ -0,0 +1,27 @@ + + +package moe.nea.firmament.events + +import net.minecraft.client.render.Camera +import net.minecraft.client.render.GameRenderer +import net.minecraft.client.render.LightmapTextureManager +import net.minecraft.client.render.RenderTickCounter +import net.minecraft.client.render.VertexConsumerProvider +import net.minecraft.client.util.math.MatrixStack +import net.minecraft.util.math.Position +import net.minecraft.util.math.Vec3d + +/** + * This event is called after all world rendering is done, but before any GUI rendering (including hand) has been done. + */ +data class WorldRenderLastEvent( + val matrices: MatrixStack, + val tickCounter: RenderTickCounter, + val renderBlockOutline: Boolean, + val camera: Camera, + val gameRenderer: GameRenderer, + val lightmapTextureManager: LightmapTextureManager, + val vertexConsumers: VertexConsumerProvider.Immediate, +) : FirmamentEvent() { + companion object : FirmamentEventBus() +} diff --git a/src/main/kotlin/events/registration/ChatEvents.kt b/src/main/kotlin/events/registration/ChatEvents.kt new file mode 100644 index 0000000..4c1c63f --- /dev/null +++ b/src/main/kotlin/events/registration/ChatEvents.kt @@ -0,0 +1,54 @@ + + +package moe.nea.firmament.events.registration + +import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents +import net.fabricmc.fabric.api.event.player.AttackBlockCallback +import net.fabricmc.fabric.api.event.player.UseBlockCallback +import net.minecraft.text.Text +import net.minecraft.util.ActionResult +import moe.nea.firmament.events.AllowChatEvent +import moe.nea.firmament.events.AttackBlockEvent +import moe.nea.firmament.events.ModifyChatEvent +import moe.nea.firmament.events.ProcessChatEvent +import moe.nea.firmament.events.UseBlockEvent + +private var lastReceivedMessage: Text? = null + +fun registerFirmamentEvents() { + ClientReceiveMessageEvents.ALLOW_CHAT.register(ClientReceiveMessageEvents.AllowChat { message, signedMessage, sender, params, receptionTimestamp -> + lastReceivedMessage = message + !ProcessChatEvent.publish(ProcessChatEvent(message, false)).cancelled + && !AllowChatEvent.publish(AllowChatEvent(message)).cancelled + }) + ClientReceiveMessageEvents.ALLOW_GAME.register(ClientReceiveMessageEvents.AllowGame { message, overlay -> + lastReceivedMessage = message + overlay || (!ProcessChatEvent.publish(ProcessChatEvent(message, false)).cancelled && + !AllowChatEvent.publish(AllowChatEvent(message)).cancelled) + }) + ClientReceiveMessageEvents.MODIFY_GAME.register(ClientReceiveMessageEvents.ModifyGame { message, overlay -> + if (overlay) message + else ModifyChatEvent.publish(ModifyChatEvent(message)).replaceWith + }) + ClientReceiveMessageEvents.GAME_CANCELED.register(ClientReceiveMessageEvents.GameCanceled { message, overlay -> + if (!overlay && lastReceivedMessage !== message) { + ProcessChatEvent.publish(ProcessChatEvent(message, true)) + } + }) + ClientReceiveMessageEvents.CHAT_CANCELED.register(ClientReceiveMessageEvents.ChatCanceled { message, signedMessage, sender, params, receptionTimestamp -> + if (lastReceivedMessage !== message) { + ProcessChatEvent.publish(ProcessChatEvent(message, true)) + } + }) + + AttackBlockCallback.EVENT.register(AttackBlockCallback { player, world, hand, pos, direction -> + if (AttackBlockEvent.publish(AttackBlockEvent(player, world, hand, pos, direction)).cancelled) + ActionResult.CONSUME + else ActionResult.PASS + }) + UseBlockCallback.EVENT.register(UseBlockCallback { player, world, hand, hitResult -> + if (UseBlockEvent.publish(UseBlockEvent(player, world, hand, hitResult)).cancelled) + ActionResult.CONSUME + else ActionResult.PASS + }) +} diff --git a/src/main/kotlin/events/subscription/Subscription.kt b/src/main/kotlin/events/subscription/Subscription.kt new file mode 100644 index 0000000..83b91c9 --- /dev/null +++ b/src/main/kotlin/events/subscription/Subscription.kt @@ -0,0 +1,16 @@ + +package moe.nea.firmament.events.subscription + +import moe.nea.firmament.events.FirmamentEvent +import moe.nea.firmament.events.FirmamentEventBus +import moe.nea.firmament.features.FirmamentFeature + +interface SubscriptionOwner { + val delegateFeature: FirmamentFeature +} + +data class Subscription( + val owner: Any, + val invoke: (T) -> Unit, + val eventBus: FirmamentEventBus, +) diff --git a/src/main/kotlin/features/FeatureManager.kt b/src/main/kotlin/features/FeatureManager.kt new file mode 100644 index 0000000..19b91de --- /dev/null +++ b/src/main/kotlin/features/FeatureManager.kt @@ -0,0 +1,120 @@ + + +package moe.nea.firmament.features + +import kotlinx.serialization.Serializable +import kotlinx.serialization.serializer +import moe.nea.firmament.Firmament +import moe.nea.firmament.annotations.generated.AllSubscriptions +import moe.nea.firmament.events.FeaturesInitializedEvent +import moe.nea.firmament.events.FirmamentEvent +import moe.nea.firmament.events.subscription.Subscription +import moe.nea.firmament.features.chat.AutoCompletions +import moe.nea.firmament.features.chat.ChatLinks +import moe.nea.firmament.features.chat.QuickCommands +import moe.nea.firmament.features.debug.DebugView +import moe.nea.firmament.features.debug.DeveloperFeatures +import moe.nea.firmament.features.debug.MinorTrolling +import moe.nea.firmament.features.debug.PowerUserTools +import moe.nea.firmament.features.diana.DianaWaypoints +import moe.nea.firmament.features.events.anniversity.AnniversaryFeatures +import moe.nea.firmament.features.events.carnival.CarnivalFeatures +import moe.nea.firmament.features.fixes.CompatibliltyFeatures +import moe.nea.firmament.features.fixes.Fixes +import moe.nea.firmament.features.inventory.CraftingOverlay +import moe.nea.firmament.features.inventory.ItemRarityCosmetics +import moe.nea.firmament.features.inventory.PriceData +import moe.nea.firmament.features.inventory.SaveCursorPosition +import moe.nea.firmament.features.inventory.SlotLocking +import moe.nea.firmament.features.inventory.buttons.InventoryButtons +import moe.nea.firmament.features.inventory.storageoverlay.StorageOverlay +import moe.nea.firmament.features.mining.PickaxeAbility +import moe.nea.firmament.features.mining.PristineProfitTracker +import moe.nea.firmament.features.texturepack.CustomSkyBlockTextures +import moe.nea.firmament.features.world.FairySouls +import moe.nea.firmament.features.world.Waypoints +import moe.nea.firmament.util.data.DataHolder + +object FeatureManager : DataHolder(serializer(), "features", ::Config) { + @Serializable + data class Config( + val enabledFeatures: MutableMap = mutableMapOf() + ) + + private val features = mutableMapOf() + + val allFeatures: Collection get() = features.values + + private var hasAutoloaded = false + + init { + autoload() + } + + fun autoload() { + synchronized(this) { + if (hasAutoloaded) return + loadFeature(MinorTrolling) + loadFeature(FairySouls) + loadFeature(AutoCompletions) + // TODO: loadFeature(FishingWarning) + loadFeature(SlotLocking) + loadFeature(StorageOverlay) + loadFeature(PristineProfitTracker) + loadFeature(CraftingOverlay) + loadFeature(PowerUserTools) + loadFeature(Waypoints) + loadFeature(ChatLinks) + loadFeature(InventoryButtons) + loadFeature(CompatibliltyFeatures) + loadFeature(AnniversaryFeatures) + loadFeature(QuickCommands) + loadFeature(SaveCursorPosition) + loadFeature(CustomSkyBlockTextures) + loadFeature(PriceData) + loadFeature(Fixes) + loadFeature(DianaWaypoints) + loadFeature(ItemRarityCosmetics) + loadFeature(PickaxeAbility) + loadFeature(CarnivalFeatures) + if (Firmament.DEBUG) { + loadFeature(DeveloperFeatures) + loadFeature(DebugView) + } + allFeatures.forEach { it.config } + FeaturesInitializedEvent.publish(FeaturesInitializedEvent(allFeatures.toList())) + hasAutoloaded = true + } + } + + fun subscribeEvents() { + AllSubscriptions.provideSubscriptions { + subscribeSingleEvent(it) + } + } + + private fun subscribeSingleEvent(it: Subscription) { + it.eventBus.subscribe(false, it.invoke) + } + + fun loadFeature(feature: FirmamentFeature) { + synchronized(features) { + if (feature.identifier in features) { + Firmament.logger.error("Double registering feature ${feature.identifier}. Ignoring second instance $feature") + return + } + features[feature.identifier] = feature + feature.onLoad() + } + } + + fun isEnabled(identifier: String): Boolean? = + data.enabledFeatures[identifier] + + + fun setEnabled(identifier: String, value: Boolean) { + data.enabledFeatures[identifier] = value + markDirty() + } + +} diff --git a/src/main/kotlin/features/FirmamentFeature.kt b/src/main/kotlin/features/FirmamentFeature.kt new file mode 100644 index 0000000..2cfc4fd --- /dev/null +++ b/src/main/kotlin/features/FirmamentFeature.kt @@ -0,0 +1,23 @@ + + +package moe.nea.firmament.features + +import moe.nea.firmament.events.subscription.SubscriptionOwner +import moe.nea.firmament.gui.config.ManagedConfig + +// TODO: remove this entire feature system and revamp config +interface FirmamentFeature : SubscriptionOwner { + val identifier: String + val defaultEnabled: Boolean + get() = true + var isEnabled: Boolean + get() = FeatureManager.isEnabled(identifier) ?: defaultEnabled + set(value) { + FeatureManager.setEnabled(identifier, value) + } + override val delegateFeature: FirmamentFeature + get() = this + val config: ManagedConfig? get() = null + fun onLoad() {} + +} diff --git a/src/main/kotlin/features/chat/AutoCompletions.kt b/src/main/kotlin/features/chat/AutoCompletions.kt new file mode 100644 index 0000000..9144898 --- /dev/null +++ b/src/main/kotlin/features/chat/AutoCompletions.kt @@ -0,0 +1,57 @@ + + +package moe.nea.firmament.features.chat + +import com.mojang.brigadier.arguments.StringArgumentType.string +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.commands.get +import moe.nea.firmament.commands.suggestsList +import moe.nea.firmament.commands.thenArgument +import moe.nea.firmament.commands.thenExecute +import moe.nea.firmament.events.CommandEvent +import moe.nea.firmament.events.MaskCommands +import moe.nea.firmament.features.FirmamentFeature +import moe.nea.firmament.gui.config.ManagedConfig +import moe.nea.firmament.repo.RepoManager +import moe.nea.firmament.util.MC + +object AutoCompletions : FirmamentFeature { + + object TConfig : ManagedConfig(identifier) { + val provideWarpTabCompletion by toggle("warp-complete") { true } + val replaceWarpIsByWarpIsland by toggle("warp-is") { true } + } + + override val config: ManagedConfig? + get() = TConfig + override val identifier: String + get() = "auto-completions" + + @Subscribe + fun onMaskCommands(event: MaskCommands) { + if (TConfig.provideWarpTabCompletion) { + event.mask("warp") + } + } + + @Subscribe + fun onCommandEvent(event: CommandEvent) { + if (!TConfig.provideWarpTabCompletion) return + event.deleteCommand("warp") + event.register("warp") { + thenArgument("to", string()) { toArg -> + suggestsList { + RepoManager.neuRepo.constants?.islands?.warps?.flatMap { listOf(it.warp) + it.aliases } ?: listOf() + } + thenExecute { + val warpName = get(toArg) + if (warpName == "is" && TConfig.replaceWarpIsByWarpIsland) { + MC.sendServerCommand("warp island") + } else { + MC.sendServerCommand("warp $warpName") + } + } + } + } + } +} diff --git a/src/main/kotlin/features/chat/ChatLinks.kt b/src/main/kotlin/features/chat/ChatLinks.kt new file mode 100644 index 0000000..f2cb78a --- /dev/null +++ b/src/main/kotlin/features/chat/ChatLinks.kt @@ -0,0 +1,161 @@ + + +package moe.nea.firmament.features.chat + +import io.ktor.client.request.get +import io.ktor.client.statement.bodyAsChannel +import io.ktor.utils.io.jvm.javaio.toInputStream +import java.net.URL +import java.util.Collections +import moe.nea.jarvis.api.Point +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.async +import kotlin.math.min +import net.minecraft.client.gui.screen.ChatScreen +import net.minecraft.client.texture.NativeImage +import net.minecraft.client.texture.NativeImageBackedTexture +import net.minecraft.text.ClickEvent +import net.minecraft.text.HoverEvent +import net.minecraft.text.Style +import net.minecraft.text.Text +import net.minecraft.util.Formatting +import net.minecraft.util.Identifier +import moe.nea.firmament.Firmament +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.events.ModifyChatEvent +import moe.nea.firmament.events.ScreenRenderPostEvent +import moe.nea.firmament.features.FirmamentFeature +import moe.nea.firmament.gui.config.ManagedConfig +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.transformEachRecursively +import moe.nea.firmament.util.unformattedString + +object ChatLinks : FirmamentFeature { + override val identifier: String + get() = "chat-links" + + object TConfig : ManagedConfig(identifier) { + val enableLinks by toggle("links-enabled") { true } + val imageEnabled by toggle("image-enabled") { true } + val allowAllHosts by toggle("allow-all-hosts") { false } + val allowedHosts by string("allowed-hosts") { "cdn.discordapp.com,media.discordapp.com,media.discordapp.net,i.imgur.com" } + val actualAllowedHosts get() = allowedHosts.split(",").map { it.trim() } + val position by position("position", 16 * 20, 9 * 20) { Point(0.0, 0.0) } + } + + private fun isHostAllowed(host: String) = + TConfig.allowAllHosts || TConfig.actualAllowedHosts.any { it.equals(host, ignoreCase = true) } + + private fun isUrlAllowed(url: String) = isHostAllowed(url.removePrefix("https://").substringBefore("/")) + + override val config get() = TConfig + val urlRegex = "https://[^. ]+\\.[^ ]+(\\.?( |$))".toRegex() + + data class Image( + val texture: Identifier, + val width: Int, + val height: Int, + ) + + val imageCache: MutableMap> = + Collections.synchronizedMap(mutableMapOf>()) + + private fun tryCacheUrl(url: String) { + if (!isUrlAllowed(url)) { + return + } + if (url in imageCache) { + return + } + imageCache[url] = Firmament.coroutineScope.async { + try { + val response = Firmament.httpClient.get(URL(url)) + if (response.status.value == 200) { + val inputStream = response.bodyAsChannel().toInputStream(Firmament.globalJob) + val image = NativeImage.read(inputStream) + val texture = MC.textureManager.registerDynamicTexture( + "dynamic_image_preview", + NativeImageBackedTexture(image) + ) + Image(texture, image.width, image.height) + } else + null + } catch (exc: Exception) { + exc.printStackTrace() + null + } + } + } + + val imageExtensions = listOf("jpg", "png", "gif", "jpeg") + fun isImageUrl(url: String): Boolean { + return (url.substringAfterLast('.').lowercase() in imageExtensions) + } + + @Subscribe + @OptIn(ExperimentalCoroutinesApi::class) + fun onRender(it: ScreenRenderPostEvent) { + if (!TConfig.imageEnabled) return + if (it.screen !is ChatScreen) return + val hoveredComponent = + MC.inGameHud.chatHud.getTextStyleAt(it.mouseX.toDouble(), it.mouseY.toDouble()) ?: return + val hoverEvent = hoveredComponent.hoverEvent ?: return + val value = hoverEvent.getValue(HoverEvent.Action.SHOW_TEXT) ?: return + val url = urlRegex.matchEntire(value.unformattedString)?.groupValues?.get(0) ?: return + if (!isImageUrl(url)) return + val imageFuture = imageCache[url] ?: return + if (!imageFuture.isCompleted) return + val image = imageFuture.getCompleted() ?: return + it.drawContext.matrices.push() + val pos = TConfig.position + pos.applyTransformations(it.drawContext.matrices) + val scale = min(1F, min((9 * 20F) / image.height, (16 * 20F) / image.width)) + it.drawContext.matrices.scale(scale, scale, 1F) + it.drawContext.drawTexture( + image.texture, + 0, + 0, + 1F, + 1F, + image.width, + image.height, + image.width, + image.height, + ) + it.drawContext.matrices.pop() + } + + @Subscribe + fun onModifyChat(it: ModifyChatEvent) { + if (!TConfig.enableLinks) return + it.replaceWith = it.replaceWith.transformEachRecursively { child -> + val text = child.string + if ("://" !in text) return@transformEachRecursively child + val s = Text.empty().setStyle(child.style) + var index = 0 + while (index < text.length) { + val nextMatch = urlRegex.find(text, index) + if (nextMatch == null) { + s.append(Text.literal(text.substring(index, text.length))) + break + } + val range = nextMatch.groups[0]!!.range + val url = nextMatch.groupValues[0] + s.append(Text.literal(text.substring(index, range.first))) + s.append( + Text.literal(url).setStyle( + Style.EMPTY.withUnderline(true).withColor( + Formatting.AQUA + ).withHoverEvent(HoverEvent(HoverEvent.Action.SHOW_TEXT, Text.literal(url))) + .withClickEvent(ClickEvent(ClickEvent.Action.OPEN_URL, url)) + ) + ) + if (isImageUrl(url)) + tryCacheUrl(url) + index = range.last + 1 + } + s + } + } +} diff --git a/src/main/kotlin/features/chat/QuickCommands.kt b/src/main/kotlin/features/chat/QuickCommands.kt new file mode 100644 index 0000000..5944b92 --- /dev/null +++ b/src/main/kotlin/features/chat/QuickCommands.kt @@ -0,0 +1,100 @@ + + +package moe.nea.firmament.features.chat + +import com.mojang.brigadier.context.CommandContext +import net.minecraft.text.Text +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.commands.DefaultSource +import moe.nea.firmament.commands.RestArgumentType +import moe.nea.firmament.commands.get +import moe.nea.firmament.commands.thenArgument +import moe.nea.firmament.commands.thenExecute +import moe.nea.firmament.events.CommandEvent +import moe.nea.firmament.features.FirmamentFeature +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.SBData + +object QuickCommands : FirmamentFeature { + override val identifier: String + get() = "quick-commands" + + fun removePartialPrefix(text: String, prefix: String): String? { + var lf: String? = null + for (i in 1..prefix.length) { + if (text.startsWith(prefix.substring(0, i))) { + lf = text.substring(i) + } + } + return lf + } + + val kuudraLevelNames = listOf("NORMAL", "HOT", "BURNING", "FIERY", "INFERNAL") + val dungeonLevelNames = listOf("ONE", "TWO", "THREE", "FOUR", "FIVE", "SIX", "SEVEN") + + @Subscribe + fun onCommands(it: CommandEvent) { + it.register("join") { + thenArgument("what", RestArgumentType) { what -> + thenExecute { + val what = this[what] + if (!SBData.isOnSkyblock) { + MC.sendCommand("join $what") + return@thenExecute + } + val joinName = getNameForFloor(what.replace(" ", "").lowercase()) + if (joinName == null) { + source.sendFeedback(Text.stringifiedTranslatable("firmament.quick-commands.join.unknown", what)) + } else { + source.sendFeedback(Text.stringifiedTranslatable("firmament.quick-commands.join.success", + joinName)) + MC.sendCommand("joininstance $joinName") + } + } + } + thenExecute { + source.sendFeedback(Text.translatable("firmament.quick-commands.join.explain")) + } + } + } + + fun CommandContext.getNameForFloor(w: String): String? { + val kuudraLevel = removePartialPrefix(w, "kuudratier") ?: removePartialPrefix(w, "tier") + if (kuudraLevel != null) { + val l = kuudraLevel.toIntOrNull()?.let { it - 1 } ?: kuudraLevelNames.indexOfFirst { + it.startsWith( + kuudraLevel, + true + ) + } + if (l !in kuudraLevelNames.indices) { + source.sendFeedback(Text.stringifiedTranslatable("firmament.quick-commands.join.unknown-kuudra", + kuudraLevel)) + return null + } + return "KUUDRA_${kuudraLevelNames[l]}" + } + val masterLevel = removePartialPrefix(w, "master") + val normalLevel = + removePartialPrefix(w, "floor") ?: removePartialPrefix(w, "catacombs") ?: removePartialPrefix(w, "dungeons") + val dungeonLevel = masterLevel ?: normalLevel + if (dungeonLevel != null) { + val l = dungeonLevel.toIntOrNull()?.let { it - 1 } ?: dungeonLevelNames.indexOfFirst { + it.startsWith( + dungeonLevel, + true + ) + } + if (masterLevel == null && (l == -1 || null != removePartialPrefix(w, "entrance"))) { + return "CATACOMBS_ENTRANCE" + } + if (l !in dungeonLevelNames.indices) { + source.sendFeedback(Text.stringifiedTranslatable("firmament.quick-commands.join.unknown-catacombs", + kuudraLevel)) + return null + } + return "${if (masterLevel != null) "MASTER_" else ""}CATACOMBS_FLOOR_${dungeonLevelNames[l]}" + } + return null + } +} diff --git a/src/main/kotlin/features/debug/DebugLogger.kt b/src/main/kotlin/features/debug/DebugLogger.kt new file mode 100644 index 0000000..ab06030 --- /dev/null +++ b/src/main/kotlin/features/debug/DebugLogger.kt @@ -0,0 +1,13 @@ + +package moe.nea.firmament.features.debug + +import net.minecraft.text.Text +import moe.nea.firmament.util.MC + +class DebugLogger(val tag: String) { + fun isEnabled() = DeveloperFeatures.isEnabled // TODO: allow filtering by tag + fun log(text: () -> String) { + if (!isEnabled()) return + MC.sendChat(Text.literal(text())) + } +} diff --git a/src/main/kotlin/features/debug/DebugView.kt b/src/main/kotlin/features/debug/DebugView.kt new file mode 100644 index 0000000..7e1b8ec --- /dev/null +++ b/src/main/kotlin/features/debug/DebugView.kt @@ -0,0 +1,38 @@ + + +package moe.nea.firmament.features.debug + +import moe.nea.firmament.Firmament +import moe.nea.firmament.events.TickEvent +import moe.nea.firmament.features.FirmamentFeature +import moe.nea.firmament.util.TimeMark + +object DebugView : FirmamentFeature { + private data class StoredVariable( + val obj: T, + val timer: TimeMark, + ) + + private val storedVariables: MutableMap> = sortedMapOf() + override val identifier: String + get() = "debug-view" + override val defaultEnabled: Boolean + get() = Firmament.DEBUG + + fun showVariable(label: String, obj: T) { + synchronized(this) { + storedVariables[label] = StoredVariable(obj, TimeMark.now()) + } + } + + fun recalculateDebugWidget() { + } + + override fun onLoad() { + TickEvent.subscribe { + synchronized(this) { + recalculateDebugWidget() + } + } + } +} diff --git a/src/main/kotlin/features/debug/DeveloperFeatures.kt b/src/main/kotlin/features/debug/DeveloperFeatures.kt new file mode 100644 index 0000000..20c0cfd --- /dev/null +++ b/src/main/kotlin/features/debug/DeveloperFeatures.kt @@ -0,0 +1,55 @@ + + +package moe.nea.firmament.features.debug + +import java.nio.file.Path +import java.util.concurrent.CompletableFuture +import kotlin.io.path.absolute +import kotlin.io.path.exists +import net.minecraft.client.MinecraftClient +import net.minecraft.text.Text +import moe.nea.firmament.Firmament +import moe.nea.firmament.features.FirmamentFeature +import moe.nea.firmament.gui.config.ManagedConfig +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.TimeMark +import moe.nea.firmament.util.errorBoundary +import moe.nea.firmament.util.iterate + +object DeveloperFeatures : FirmamentFeature { + override val identifier: String + get() = "developer" + override val config: TConfig + get() = TConfig + override val defaultEnabled: Boolean + get() = Firmament.DEBUG + + val gradleDir = + Path.of(".").absolute() + .iterate { it.parent } + .find { it.resolve("settings.gradle.kts").exists() } + + object TConfig : ManagedConfig("developer") { + val autoRebuildResources by toggle("auto-rebuild") { false } + } + + @JvmStatic + fun hookOnBeforeResourceReload(client: MinecraftClient): CompletableFuture { + val reloadFuture = if (TConfig.autoRebuildResources && isEnabled && gradleDir != null) { + val builder = ProcessBuilder("./gradlew", ":processResources") + builder.directory(gradleDir.toFile()) + builder.inheritIO() + val process = builder.start() + MC.player?.sendMessage(Text.translatable("firmament.dev.resourcerebuild.start")) + val startTime = TimeMark.now() + process.toHandle().onExit().thenApply { + MC.player?.sendMessage(Text.stringifiedTranslatable("firmament.dev.resourcerebuild.done", startTime.passedTime())) + Unit + } + } else { + CompletableFuture.completedFuture(Unit) + } + return reloadFuture.thenCompose { client.reloadResources() } + } +} + diff --git a/src/main/kotlin/features/debug/MinorTrolling.kt b/src/main/kotlin/features/debug/MinorTrolling.kt new file mode 100644 index 0000000..32035a6 --- /dev/null +++ b/src/main/kotlin/features/debug/MinorTrolling.kt @@ -0,0 +1,27 @@ + + +package moe.nea.firmament.features.debug + +import net.minecraft.text.Text +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.events.ModifyChatEvent +import moe.nea.firmament.features.FirmamentFeature + + +// In memorian Dulkir +object MinorTrolling : FirmamentFeature { + override val identifier: String + get() = "minor-trolling" + + val trollers = listOf("nea89o", "lrg89") + val t = "From(?: \\[[^\\]]+])? ([^:]+): (.*)".toRegex() + + @Subscribe + fun onTroll(it: ModifyChatEvent) { + val m = t.matchEntire(it.unformattedString) ?: return + val (_, name, text) = m.groupValues + if (name !in trollers) return + if (!text.startsWith("c:")) return + it.replaceWith = Text.literal(text.substring(2).replace("&", "§")) + } +} diff --git a/src/main/kotlin/features/debug/PowerUserTools.kt b/src/main/kotlin/features/debug/PowerUserTools.kt new file mode 100644 index 0000000..7893eff --- /dev/null +++ b/src/main/kotlin/features/debug/PowerUserTools.kt @@ -0,0 +1,193 @@ + + +package moe.nea.firmament.features.debug + +import net.minecraft.block.SkullBlock +import net.minecraft.block.entity.SkullBlockEntity +import net.minecraft.component.DataComponentTypes +import net.minecraft.entity.Entity +import net.minecraft.entity.LivingEntity +import net.minecraft.item.ItemStack +import net.minecraft.item.Items +import net.minecraft.text.Text +import net.minecraft.util.hit.BlockHitResult +import net.minecraft.util.hit.EntityHitResult +import net.minecraft.util.hit.HitResult +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.events.CommandEvent +import moe.nea.firmament.events.CustomItemModelEvent +import moe.nea.firmament.events.HandledScreenKeyPressedEvent +import moe.nea.firmament.events.ItemTooltipEvent +import moe.nea.firmament.events.ScreenChangeEvent +import moe.nea.firmament.events.TickEvent +import moe.nea.firmament.events.WorldKeyboardEvent +import moe.nea.firmament.features.FirmamentFeature +import moe.nea.firmament.features.texturepack.CustomSkyBlockTextures +import moe.nea.firmament.gui.config.ManagedConfig +import moe.nea.firmament.mixins.accessor.AccessorHandledScreen +import moe.nea.firmament.util.ClipboardUtils +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.focusedItemStack +import moe.nea.firmament.util.skyBlockId + +object PowerUserTools : FirmamentFeature { + override val identifier: String + get() = "power-user" + + object TConfig : ManagedConfig(identifier) { + val showItemIds by toggle("show-item-id") { false } + val copyItemId by keyBindingWithDefaultUnbound("copy-item-id") + val copyTexturePackId by keyBindingWithDefaultUnbound("copy-texture-pack-id") + val copyNbtData by keyBindingWithDefaultUnbound("copy-nbt-data") + val copySkullTexture by keyBindingWithDefaultUnbound("copy-skull-texture") + val copyEntityData by keyBindingWithDefaultUnbound("entity-data") + } + + override val config + get() = TConfig + + var lastCopiedStack: Pair? = null + set(value) { + field = value + if (value != null) + lastCopiedStackViewTime = true + } + var lastCopiedStackViewTime = false + + override fun onLoad() { + TickEvent.subscribe { + if (!lastCopiedStackViewTime) + lastCopiedStack = null + lastCopiedStackViewTime = false + } + ScreenChangeEvent.subscribe { + lastCopiedStack = null + } + } + + fun debugFormat(itemStack: ItemStack): Text { + return Text.literal(itemStack.skyBlockId?.toString() ?: itemStack.toString()) + } + + @Subscribe + fun onEntityInfo(event: WorldKeyboardEvent) { + if (!event.matches(TConfig.copyEntityData)) return + val target = (MC.instance.crosshairTarget as? EntityHitResult)?.entity + if (target == null) { + MC.sendChat(Text.translatable("firmament.poweruser.entity.fail")) + return + } + showEntity(target) + } + + fun showEntity(target: Entity) { + MC.sendChat(Text.translatable("firmament.poweruser.entity.type", target.type)) + MC.sendChat(Text.translatable("firmament.poweruser.entity.name", target.name)) + MC.sendChat(Text.stringifiedTranslatable("firmament.poweruser.entity.position", target.pos)) + if (target is LivingEntity) { + MC.sendChat(Text.translatable("firmament.poweruser.entity.armor")) + for (armorItem in target.armorItems) { + MC.sendChat(Text.translatable("firmament.poweruser.entity.armor.item", debugFormat(armorItem))) + } + } + MC.sendChat(Text.stringifiedTranslatable("firmament.poweruser.entity.passengers", target.passengerList.size)) + target.passengerList.forEach { + showEntity(it) + } + } + + + @Subscribe + fun copyInventoryInfo(it: HandledScreenKeyPressedEvent) { + if (it.screen !is AccessorHandledScreen) return + val item = it.screen.focusedItemStack ?: return + if (it.matches(TConfig.copyItemId)) { + val sbId = item.skyBlockId + if (sbId == null) { + lastCopiedStack = Pair(item, Text.translatable("firmament.tooltip.copied.skyblockid.fail")) + return + } + ClipboardUtils.setTextContent(sbId.neuItem) + lastCopiedStack = + Pair(item, Text.stringifiedTranslatable("firmament.tooltip.copied.skyblockid", sbId.neuItem)) + } else if (it.matches(TConfig.copyTexturePackId)) { + val model = CustomItemModelEvent.getModelIdentifier(item) + if (model == null) { + lastCopiedStack = Pair(item, Text.translatable("firmament.tooltip.copied.modelid.fail")) + return + } + ClipboardUtils.setTextContent(model.toString()) + lastCopiedStack = + Pair(item, Text.stringifiedTranslatable("firmament.tooltip.copied.modelid", model.toString())) + } else if (it.matches(TConfig.copyNbtData)) { + // TODO: copy full nbt + val nbt = item.get(DataComponentTypes.CUSTOM_DATA)?.nbt?.toString() ?: "" + ClipboardUtils.setTextContent(nbt) + lastCopiedStack = Pair(item, Text.translatable("firmament.tooltip.copied.nbt")) + } else if (it.matches(TConfig.copySkullTexture)) { + if (item.item != Items.PLAYER_HEAD) { + lastCopiedStack = Pair(item, Text.translatable("firmament.tooltip.copied.skull-id.fail.no-skull")) + return + } + val profile = item.get(DataComponentTypes.PROFILE) + if (profile == null) { + lastCopiedStack = Pair(item, Text.translatable("firmament.tooltip.copied.skull-id.fail.no-profile")) + return + } + val skullTexture = CustomSkyBlockTextures.getSkullTexture(profile) + if (skullTexture == null) { + lastCopiedStack = Pair(item, Text.translatable("firmament.tooltip.copied.skull-id.fail.no-texture")) + return + } + ClipboardUtils.setTextContent(skullTexture.toString()) + lastCopiedStack = + Pair( + item, + Text.stringifiedTranslatable("firmament.tooltip.copied.skull-id", skullTexture.toString()) + ) + println("Copied skull id: $skullTexture") + } + } + + @Subscribe + fun onCopyWorldInfo(it: WorldKeyboardEvent) { + if (it.matches(TConfig.copySkullTexture)) { + val p = MC.camera ?: return + val blockHit = p.raycast(20.0, 0.0f, false) ?: return + if (blockHit.type != HitResult.Type.BLOCK || blockHit !is BlockHitResult) { + MC.sendChat(Text.translatable("firmament.tooltip.copied.skull.fail")) + return + } + val blockAt = p.world.getBlockState(blockHit.blockPos)?.block + val entity = p.world.getBlockEntity(blockHit.blockPos) + if (blockAt !is SkullBlock || entity !is SkullBlockEntity || entity.owner == null) { + MC.sendChat(Text.translatable("firmament.tooltip.copied.skull.fail")) + return + } + val id = CustomSkyBlockTextures.getSkullTexture(entity.owner!!) + if (id == null) { + MC.sendChat(Text.translatable("firmament.tooltip.copied.skull.fail")) + } else { + ClipboardUtils.setTextContent(id.toString()) + MC.sendChat(Text.stringifiedTranslatable("firmament.tooltip.copied.skull", id.toString())) + } + } + } + + @Subscribe + fun addItemId(it: ItemTooltipEvent) { + if (TConfig.showItemIds) { + val id = it.stack.skyBlockId ?: return + it.lines.add(Text.stringifiedTranslatable("firmament.tooltip.skyblockid", id.neuItem)) + } + val (item, text) = lastCopiedStack ?: return + if (!ItemStack.areEqual(item, it.stack)) { + lastCopiedStack = null + return + } + lastCopiedStackViewTime = true + it.lines.add(text) + } + + +} diff --git a/src/main/kotlin/features/diana/AncestralSpadeSolver.kt b/src/main/kotlin/features/diana/AncestralSpadeSolver.kt new file mode 100644 index 0000000..39ca6d3 --- /dev/null +++ b/src/main/kotlin/features/diana/AncestralSpadeSolver.kt @@ -0,0 +1,131 @@ + +package moe.nea.firmament.features.diana + +import kotlin.time.Duration.Companion.seconds +import net.minecraft.particle.ParticleTypes +import net.minecraft.sound.SoundEvents +import net.minecraft.util.math.Vec3d +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.events.ParticleSpawnEvent +import moe.nea.firmament.events.SoundReceiveEvent +import moe.nea.firmament.events.WorldKeyboardEvent +import moe.nea.firmament.events.WorldReadyEvent +import moe.nea.firmament.events.WorldRenderLastEvent +import moe.nea.firmament.events.subscription.SubscriptionOwner +import moe.nea.firmament.features.FirmamentFeature +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.SBData +import moe.nea.firmament.util.SkyBlockIsland +import moe.nea.firmament.util.SkyblockId +import moe.nea.firmament.util.TimeMark +import moe.nea.firmament.util.WarpUtil +import moe.nea.firmament.util.render.RenderInWorldContext +import moe.nea.firmament.util.skyBlockId + +object AncestralSpadeSolver : SubscriptionOwner { + var lastDing = TimeMark.farPast() + private set + private val pitches = mutableListOf() + val particlePositions = mutableListOf() + var nextGuess: Vec3d? = null + private set + + val ancestralSpadeId = SkyblockId("ANCESTRAL_SPADE") + private var lastTeleportAttempt = TimeMark.farPast() + + fun isEnabled() = + DianaWaypoints.TConfig.ancestralSpadeSolver + && SBData.skyblockLocation == SkyBlockIsland.HUB + && MC.player?.inventory?.containsAny { it.skyBlockId == ancestralSpadeId } == true // TODO: add a reactive property here + + @Subscribe + fun onKeyBind(event: WorldKeyboardEvent) { + if (!isEnabled()) return + if (!event.matches(DianaWaypoints.TConfig.ancestralSpadeTeleport)) return + + if (lastTeleportAttempt.passedTime() < 3.seconds) return + WarpUtil.teleportToNearestWarp(SkyBlockIsland.HUB, nextGuess ?: return) + lastTeleportAttempt = TimeMark.now() + } + + @Subscribe + fun onParticleSpawn(event: ParticleSpawnEvent) { + if (!isEnabled()) return + if (event.particleEffect != ParticleTypes.DRIPPING_LAVA) return + if (event.offset.x != 0.0F || event.offset.y != 0F || event.offset.z != 0F) + return + particlePositions.add(event.position) + if (particlePositions.size > 20) { + particlePositions.removeFirst() + } + } + + @Subscribe + fun onPlaySound(event: SoundReceiveEvent) { + if (!isEnabled()) return + if (!SoundEvents.BLOCK_NOTE_BLOCK_HARP.matchesId(event.sound.value().id)) return + + if (lastDing.passedTime() > 1.seconds) { + particlePositions.clear() + pitches.clear() + } + lastDing = TimeMark.now() + + pitches.add(event.pitch) + if (pitches.size > 20) { + pitches.removeFirst() + } + + if (particlePositions.size < 3) { + return + } + + val averagePitchDelta = + if (pitches.isEmpty()) return + else pitches + .zipWithNext { a, b -> b - a } + .average() + + val soundDistanceEstimate = (Math.E / averagePitchDelta) - particlePositions.first().distanceTo(event.position) + + if (soundDistanceEstimate > 1000) { + return + } + + val lastParticleDirection = particlePositions + .takeLast(3) + .let { (a, _, b) -> b.subtract(a) } + .normalize() + + nextGuess = event.position.add(lastParticleDirection.multiply(soundDistanceEstimate)) + } + + @Subscribe + fun onWorldRender(event: WorldRenderLastEvent) { + if (!isEnabled()) return + RenderInWorldContext.renderInWorld(event) { + nextGuess?.let { + color(1f, 1f, 0f, 0.5f) + tinyBlock(it, 1f) + color(1f, 1f, 0f, 1f) + tracer(it, lineWidth = 3f) + } + if (particlePositions.size > 2 && lastDing.passedTime() < 10.seconds && nextGuess != null) { + color(0f, 1f, 0f, 0.7f) + line(particlePositions) + } + } + } + + @Subscribe + fun onSwapWorld(event: WorldReadyEvent) { + nextGuess = null + particlePositions.clear() + pitches.clear() + lastDing = TimeMark.farPast() + } + + override val delegateFeature: FirmamentFeature + get() = DianaWaypoints + +} diff --git a/src/main/kotlin/features/diana/DianaWaypoints.kt b/src/main/kotlin/features/diana/DianaWaypoints.kt new file mode 100644 index 0000000..0a34eaa --- /dev/null +++ b/src/main/kotlin/features/diana/DianaWaypoints.kt @@ -0,0 +1,35 @@ + +package moe.nea.firmament.features.diana + +import moe.nea.firmament.events.AttackBlockEvent +import moe.nea.firmament.events.ParticleSpawnEvent +import moe.nea.firmament.events.ProcessChatEvent +import moe.nea.firmament.events.SoundReceiveEvent +import moe.nea.firmament.events.UseBlockEvent +import moe.nea.firmament.events.WorldKeyboardEvent +import moe.nea.firmament.events.WorldReadyEvent +import moe.nea.firmament.events.WorldRenderLastEvent +import moe.nea.firmament.features.FirmamentFeature +import moe.nea.firmament.gui.config.ManagedConfig + +object DianaWaypoints : FirmamentFeature { + override val identifier get() = "diana" + override val config get() = TConfig + + object TConfig : ManagedConfig(identifier) { + val ancestralSpadeSolver by toggle("ancestral-spade") { true } + val ancestralSpadeTeleport by keyBindingWithDefaultUnbound("ancestral-teleport") + val nearbyWaypoints by toggle("nearby-waypoints") { true } + } + + override fun onLoad() { + UseBlockEvent.subscribe { + NearbyBurrowsSolver.onBlockClick(it.hitResult.blockPos) + } + AttackBlockEvent.subscribe { + NearbyBurrowsSolver.onBlockClick(it.blockPos) + } + } +} + + diff --git a/src/main/kotlin/features/diana/NearbyBurrowsSolver.kt b/src/main/kotlin/features/diana/NearbyBurrowsSolver.kt new file mode 100644 index 0000000..7158bb9 --- /dev/null +++ b/src/main/kotlin/features/diana/NearbyBurrowsSolver.kt @@ -0,0 +1,144 @@ + +package moe.nea.firmament.features.diana + +import kotlin.time.Duration.Companion.seconds +import net.minecraft.particle.ParticleTypes +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.MathHelper +import net.minecraft.util.math.Position +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.events.ParticleSpawnEvent +import moe.nea.firmament.events.ProcessChatEvent +import moe.nea.firmament.events.WorldReadyEvent +import moe.nea.firmament.events.WorldRenderLastEvent +import moe.nea.firmament.events.subscription.SubscriptionOwner +import moe.nea.firmament.features.FirmamentFeature +import moe.nea.firmament.util.TimeMark +import moe.nea.firmament.util.mutableMapWithMaxSize +import moe.nea.firmament.util.render.RenderInWorldContext.Companion.renderInWorld + +object NearbyBurrowsSolver : SubscriptionOwner { + + + private val recentlyDugBurrows: MutableMap = mutableMapWithMaxSize(20) + private val recentEnchantParticles: MutableMap = mutableMapWithMaxSize(500) + private var lastBlockClick: BlockPos? = null + + enum class BurrowType { + START, MOB, TREASURE + } + + val burrows = mutableMapOf() + + @Subscribe + fun onChatEvent(event: ProcessChatEvent) { + val lastClickedBurrow = lastBlockClick ?: return + if (event.unformattedString.startsWith("You dug out a Griffin Burrow!") || + event.unformattedString.startsWith(" ☠ You were killed by") || + event.unformattedString.startsWith("You finished the Griffin burrow chain!") + ) { + markAsDug(lastClickedBurrow) + burrows.remove(lastClickedBurrow) + } + } + + + fun wasRecentlyDug(blockPos: BlockPos): Boolean { + val lastDigTime = recentlyDugBurrows[blockPos] ?: TimeMark.farPast() + return lastDigTime.passedTime() < 10.seconds + } + + fun markAsDug(blockPos: BlockPos) { + recentlyDugBurrows[blockPos] = TimeMark.now() + } + + fun wasRecentlyEnchanted(blockPos: BlockPos): Boolean { + val lastEnchantTime = recentEnchantParticles[blockPos] ?: TimeMark.farPast() + return lastEnchantTime.passedTime() < 4.seconds + } + + fun markAsEnchanted(blockPos: BlockPos) { + recentEnchantParticles[blockPos] = TimeMark.now() + } + + @Subscribe + fun onParticles(event: ParticleSpawnEvent) { + if (!DianaWaypoints.TConfig.nearbyWaypoints) return + + val position: BlockPos = event.position.toBlockPos().down() + + if (wasRecentlyDug(position)) return + + val isEven50Spread = (event.offset.x == 0.5f && event.offset.z == 0.5f) + + if (event.particleEffect.type == ParticleTypes.ENCHANT) { + if (event.count == 5 && event.speed == 0.05F && event.offset.y == 0.4F && isEven50Spread) { + markAsEnchanted(position) + } + return + } + + if (!wasRecentlyEnchanted(position)) return + + if (event.particleEffect.type == ParticleTypes.ENCHANTED_HIT + && event.count == 4 + && event.speed == 0.01F + && event.offset.y == 0.1f + && isEven50Spread + ) { + burrows[position] = BurrowType.START + } + if (event.particleEffect.type == ParticleTypes.CRIT + && event.count == 3 + && event.speed == 0.01F + && event.offset.y == 0.1F + && isEven50Spread + ) { + burrows[position] = BurrowType.MOB + } + if (event.particleEffect.type == ParticleTypes.DRIPPING_LAVA + && event.count == 2 + && event.speed == 0.01F + && event.offset.y == 0.1F + && event.offset.x == 0.35F && event.offset.z == 0.35f + ) { + burrows[position] = BurrowType.TREASURE + } + } + + @Subscribe + fun onRender(event: WorldRenderLastEvent) { + if (!DianaWaypoints.TConfig.nearbyWaypoints) return + renderInWorld(event) { + for ((location, burrow) in burrows) { + when (burrow) { + BurrowType.START -> color(.2f, .8f, .2f, 0.4f) + BurrowType.MOB -> color(0.3f, 0.4f, 0.9f, 0.4f) + BurrowType.TREASURE -> color(1f, 0.7f, 0.2f, 0.4f) + } + block(location) + } + } + } + + @Subscribe + fun onSwapWorld(worldReadyEvent: WorldReadyEvent) { + burrows.clear() + recentEnchantParticles.clear() + recentlyDugBurrows.clear() + lastBlockClick = null + } + + fun onBlockClick(blockPos: BlockPos) { + if (!DianaWaypoints.TConfig.nearbyWaypoints) return + burrows.remove(blockPos) + lastBlockClick = blockPos + } + + override val delegateFeature: FirmamentFeature + get() = DianaWaypoints +} + +fun Position.toBlockPos(): BlockPos { + return BlockPos(MathHelper.floor(x), MathHelper.floor(y), MathHelper.floor(z)) +} diff --git a/src/main/kotlin/features/events/anniversity/AnniversaryFeatures.kt b/src/main/kotlin/features/events/anniversity/AnniversaryFeatures.kt new file mode 100644 index 0000000..8926a95 --- /dev/null +++ b/src/main/kotlin/features/events/anniversity/AnniversaryFeatures.kt @@ -0,0 +1,224 @@ + +package moe.nea.firmament.features.events.anniversity + +import io.github.notenoughupdates.moulconfig.observer.ObservableList +import io.github.notenoughupdates.moulconfig.xml.Bind +import moe.nea.jarvis.api.Point +import kotlin.time.Duration.Companion.seconds +import net.minecraft.entity.passive.PigEntity +import net.minecraft.util.math.BlockPos +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.events.EntityInteractionEvent +import moe.nea.firmament.events.ProcessChatEvent +import moe.nea.firmament.events.TickEvent +import moe.nea.firmament.events.WorldReadyEvent +import moe.nea.firmament.features.FirmamentFeature +import moe.nea.firmament.gui.config.ManagedConfig +import moe.nea.firmament.gui.hud.MoulConfigHud +import moe.nea.firmament.rei.SBItemEntryDefinition +import moe.nea.firmament.repo.ItemNameLookup +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.SHORT_NUMBER_FORMAT +import moe.nea.firmament.util.SkyblockId +import moe.nea.firmament.util.TimeMark +import moe.nea.firmament.util.parseShortNumber +import moe.nea.firmament.util.useMatch + +object AnniversaryFeatures : FirmamentFeature { + override val identifier: String + get() = "anniversary" + + object TConfig : ManagedConfig(identifier) { + val enableShinyPigTracker by toggle("shiny-pigs") {true} + val trackPigCooldown by position("pig-hud", 200, 300) { Point(0.1, 0.2) } + } + + override val config: ManagedConfig? + get() = TConfig + + data class ClickedPig( + val clickedAt: TimeMark, + val startLocation: BlockPos, + val pigEntity: PigEntity + ) { + @Bind("timeLeft") + fun getTimeLeft(): Double = 1 - clickedAt.passedTime() / pigDuration + } + + val clickedPigs = ObservableList(mutableListOf()) + var lastClickedPig: PigEntity? = null + + val pigDuration = 90.seconds + + @Subscribe + fun onTick(event: TickEvent) { + clickedPigs.removeIf { it.clickedAt.passedTime() > pigDuration } + } + + val pattern = "SHINY! You extracted (?.*) from the piglet's orb!".toPattern() + + @Subscribe + fun onChat(event: ProcessChatEvent) { + if(!TConfig.enableShinyPigTracker)return + if (event.unformattedString == "Oink! Bring the pig back to the Shiny Orb!") { + val pig = lastClickedPig ?: return + // TODO: store proper location based on the orb location, maybe + val startLocation = pig.blockPos ?: return + clickedPigs.add(ClickedPig(TimeMark.now(), startLocation, pig)) + lastClickedPig = null + } + if (event.unformattedString == "SHINY! The orb is charged! Click on it for loot!") { + val player = MC.player ?: return + val lowest = + clickedPigs.minByOrNull { it.startLocation.getSquaredDistance(player.pos) } ?: return + clickedPigs.remove(lowest) + } + pattern.useMatch(event.unformattedString) { + val reward = group("reward") + val parsedReward = parseReward(reward) + addReward(parsedReward) + PigCooldown.rewards.atOnce { + PigCooldown.rewards.clear() + rewards.mapTo(PigCooldown.rewards) { PigCooldown.DisplayReward(it) } + } + } + } + + fun addReward(reward: Reward) { + val it = rewards.listIterator() + while (it.hasNext()) { + val merged = reward.mergeWith(it.next()) ?: continue + it.set(merged) + return + } + rewards.add(reward) + } + + val rewards = mutableListOf() + + fun ObservableList.atOnce(block: () -> Unit) { + val oldObserver = observer + observer = null + block() + observer = oldObserver + update() + } + + sealed interface Reward { + fun mergeWith(other: Reward): Reward? + data class EXP(val amount: Double, val skill: String) : Reward { + override fun mergeWith(other: Reward): Reward? { + if (other is EXP && other.skill == skill) + return EXP(amount + other.amount, skill) + return null + } + } + + data class Coins(val amount: Double) : Reward { + override fun mergeWith(other: Reward): Reward? { + if (other is Coins) + return Coins(other.amount + amount) + return null + } + } + + data class Items(val amount: Int, val item: SkyblockId) : Reward { + override fun mergeWith(other: Reward): Reward? { + if (other is Items && other.item == item) + return Items(amount + other.amount, item) + return null + } + } + + data class Unknown(val text: String) : Reward { + override fun mergeWith(other: Reward): Reward? { + return null + } + } + } + + val expReward = "\\+(?$SHORT_NUMBER_FORMAT) (?[^ ]+) XP".toPattern() + val coinReward = "\\+(?$SHORT_NUMBER_FORMAT) coins".toPattern() + val itemReward = "(?:(?[0-9]+)x )?(?.*)".toPattern() + fun parseReward(string: String): Reward { + expReward.useMatch(string) { + val exp = parseShortNumber(group("exp")) + val kind = group("kind") + return Reward.EXP(exp, kind) + } + coinReward.useMatch(string) { + val coins = parseShortNumber(group("amount")) + return Reward.Coins(coins) + } + itemReward.useMatch(string) { + val amount = group("amount")?.toIntOrNull() ?: 1 + val name = group("name") + val item = ItemNameLookup.guessItemByName(name, false) ?: return@useMatch + return Reward.Items(amount, item) + } + return Reward.Unknown(string) + } + + @Subscribe + fun onWorldClear(event: WorldReadyEvent) { + lastClickedPig = null + clickedPigs.clear() + } + + @Subscribe + fun onEntityClick(event: EntityInteractionEvent) { + if (event.entity is PigEntity) { + lastClickedPig = event.entity + } + } + + @Subscribe + fun init(event: WorldReadyEvent) { + PigCooldown.forceInit() + } + + object PigCooldown : MoulConfigHud("anniversary_pig", TConfig.trackPigCooldown) { + override fun shouldRender(): Boolean { + return clickedPigs.isNotEmpty() && TConfig.enableShinyPigTracker + } + + @Bind("pigs") + fun getPigs() = clickedPigs + + class DisplayReward(val backedBy: Reward) { + @Bind + fun count(): String { + return when (backedBy) { + is Reward.Coins -> backedBy.amount + is Reward.EXP -> backedBy.amount + is Reward.Items -> backedBy.amount + is Reward.Unknown -> 0 + }.toString() + } + + val itemStack = if (backedBy is Reward.Items) { + SBItemEntryDefinition.getEntry(backedBy.item, backedBy.amount) + } else { + SBItemEntryDefinition.getEntry(SkyblockId.NULL) + } + + @Bind + fun name(): String { + return when (backedBy) { + is Reward.Coins -> "Coins" + is Reward.EXP -> backedBy.skill + is Reward.Items -> itemStack.value.asItemStack().name.string + is Reward.Unknown -> backedBy.text + } + } + + @Bind + fun isKnown() = backedBy !is Reward.Unknown + } + + @get:Bind("rewards") + val rewards = ObservableList(mutableListOf()) + + } + +} diff --git a/src/main/kotlin/features/events/carnival/CarnivalFeatures.kt b/src/main/kotlin/features/events/carnival/CarnivalFeatures.kt new file mode 100644 index 0000000..1e6d97a --- /dev/null +++ b/src/main/kotlin/features/events/carnival/CarnivalFeatures.kt @@ -0,0 +1,17 @@ + +package moe.nea.firmament.features.events.carnival + +import moe.nea.firmament.features.FirmamentFeature +import moe.nea.firmament.gui.config.ManagedConfig + +object CarnivalFeatures : FirmamentFeature { + object TConfig : ManagedConfig(identifier) { + val enableBombSolver by toggle("bombs-solver") { true } + val displayTutorials by toggle("tutorials") { true } + } + + override val config: ManagedConfig? + get() = TConfig + override val identifier: String + get() = "carnival" +} diff --git a/src/main/kotlin/features/events/carnival/MinesweeperHelper.kt b/src/main/kotlin/features/events/carnival/MinesweeperHelper.kt new file mode 100644 index 0000000..06caf86 --- /dev/null +++ b/src/main/kotlin/features/events/carnival/MinesweeperHelper.kt @@ -0,0 +1,276 @@ + +package moe.nea.firmament.features.events.carnival + +import io.github.notenoughupdates.moulconfig.observer.ObservableList +import io.github.notenoughupdates.moulconfig.platform.ModernItemStack +import io.github.notenoughupdates.moulconfig.xml.Bind +import java.util.UUID +import net.minecraft.block.Blocks +import net.minecraft.item.Item +import net.minecraft.item.ItemStack +import net.minecraft.item.Items +import net.minecraft.text.ClickEvent +import net.minecraft.text.Text +import net.minecraft.util.math.BlockPos +import net.minecraft.world.WorldAccess +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.commands.thenExecute +import moe.nea.firmament.events.AttackBlockEvent +import moe.nea.firmament.events.CommandEvent +import moe.nea.firmament.events.EntityUpdateEvent +import moe.nea.firmament.events.ProcessChatEvent +import moe.nea.firmament.events.WorldReadyEvent +import moe.nea.firmament.events.WorldRenderLastEvent +import moe.nea.firmament.features.debug.DebugLogger +import moe.nea.firmament.util.LegacyFormattingCode +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.MoulConfigUtils +import moe.nea.firmament.util.ScreenUtil +import moe.nea.firmament.util.SkyblockId +import moe.nea.firmament.util.item.createSkullItem +import moe.nea.firmament.util.render.RenderInWorldContext +import moe.nea.firmament.util.setSkyBlockFirmamentUiId +import moe.nea.firmament.util.skyBlockId +import moe.nea.firmament.util.useMatch + +object MinesweeperHelper { + val sandBoxLow = BlockPos(-112, 72, -11) + val sandBoxHigh = BlockPos(-106, 72, -5) + val boardSize = Pair(sandBoxHigh.x - sandBoxLow.x, sandBoxHigh.z - sandBoxLow.z) + + val gameStartMessage = "[NPC] Carnival Pirateman: Good luck, matey!" + val gameEndMessage = "Fruit Digging" + val bombPattern = "MINES! There (are|is) (?[0-8]) bombs? hidden nearby\\.".toPattern() + val startGameQuestion = "[NPC] Carnival Pirateman: Would ye like to do some Fruit Digging?" + + + enum class Piece( + @get:Bind("fruitName") + val fruitName: String, + val points: Int, + val specialAbility: String, + val totalPerBoard: Int, + val textureHash: String, + val fruitColor: LegacyFormattingCode, + ) { + COCONUT("Coconut", + 200, + "Prevents a bomb from exploding next turn", + 3, + "10ceb1455b471d016a9f06d25f6e468df9fcf223e2c1e4795b16e84fcca264ee", + LegacyFormattingCode.DARK_PURPLE), + APPLE("Apple", + 100, + "Gains 100 points for each apple dug up", + 8, + "17ea278d6225c447c5943d652798d0bbbd1418434ce8c54c54fdac79994ddd6c", + LegacyFormattingCode.GREEN), + WATERMELON("Watermelon", + 100, + "Blows up an adjacent fruit for half the points", + 4, + "efe4ef83baf105e8dee6cf03dfe7407f1911b3b9952c891ae34139560f2931d6", + LegacyFormattingCode.DARK_BLUE), + DURIAN("Durian", + 800, + "Halves the points earned in the next turn", + 2, + "ac268d36c2c6047ffeec00124096376b56dbb4d756a55329363a1b27fcd659cd", + LegacyFormattingCode.DARK_PURPLE), + MANGO("Mango", + 300, + "Just an ordinary fruit", + 10, + "f363a62126a35537f8189343a22660de75e810c6ac004a7d3da65f1c040a839", + LegacyFormattingCode.GREEN), + DRAGON_FRUIT("Dragonfruit", + 1200, + "Halves the points earned in the next turn", + 1, + "3cc761bcb0579763d9b8ab6b7b96fa77eb6d9605a804d838fec39e7b25f95591", + LegacyFormattingCode.LIGHT_PURPLE), + POMEGRANATE("Pomegranate", + 200, + "Grants an extra 50% more points in the next turn", + 4, + "40824d18079042d5769f264f44394b95b9b99ce689688cc10c9eec3f882ccc08", + LegacyFormattingCode.DARK_BLUE), + CHERRY("Cherry", + 200, + "The second cherry grants 300 bonus points", + 2, + "c92b099a62cd2fbf8ada09dec145c75d7fda4dc57b968bea3a8fa11e37aa48b2", + LegacyFormattingCode.DARK_PURPLE), + BOMB("Bomb", + -1, + "Destroys nearby fruit", + 15, + "a76a2811d1e176a07b6d0a657b910f134896ce30850f6e80c7c83732d85381ea", + LegacyFormattingCode.DARK_RED), + RUM("Rum", + -1, + "Stops your dowsing ability for one turn", + 5, + "407b275d28b927b1bf7f6dd9f45fbdad2af8571c54c8f027d1bff6956fbf3c16", + LegacyFormattingCode.YELLOW), + ; + + val textureUrl = "http://textures.minecraft.net/texture/$textureHash" + val itemStack = createSkullItem(UUID.randomUUID(), textureUrl) + .setSkyBlockFirmamentUiId("MINESWEEPER_$name") + + @Bind + fun getIcon() = ModernItemStack.of(itemStack) + + @Bind + fun pieceLabel() = fruitColor.formattingCode + fruitName + + @Bind + fun boardLabel() = "§a$totalPerBoard§7/§rboard" + + @Bind("description") + fun getDescription() = buildString { + append(specialAbility) + if (points >= 0) { + append(" Default points: $points.") + } + } + } + + object TutorialScreen { + @get:Bind("pieces") + val pieces = ObservableList(Piece.entries.toList().reversed()) + + @get:Bind("modes") + val modes = ObservableList(DowsingMode.entries.toList()) + } + + enum class DowsingMode( + val itemType: Item, + @get:Bind("feature") + val feature: String, + @get:Bind("description") + val description: String, + ) { + MINES(Items.IRON_SHOVEL, "Bomb detection", "Tells you how many bombs are near the block"), + ANCHOR(Items.DIAMOND_SHOVEL, "Lowest fruit", "Shows you which block nearby contains the lowest scoring fruit"), + TREASURE(Items.GOLDEN_SHOVEL, "Highest fruit", "Tells you which kind of fruit is the highest scoring nearby"), + ; + + @Bind("itemType") + fun getItemStack() = ModernItemStack.of(ItemStack(itemType)) + + companion object { + val id = SkyblockId("CARNIVAL_SHOVEL") + fun fromItem(itemStack: ItemStack): DowsingMode? { + if (itemStack.skyBlockId != id) return null + return DowsingMode.entries.find { it.itemType == itemStack.item } + } + } + } + + data class BoardPosition( + val x: Int, + val y: Int + ) { + fun toBlockPos() = BlockPos(sandBoxLow.x + x, sandBoxLow.y, sandBoxLow.z + y) + + fun getBlock(world: WorldAccess) = world.getBlockState(toBlockPos()).block + fun isUnopened(world: WorldAccess) = getBlock(world) == Blocks.SAND + fun isOpened(world: WorldAccess) = getBlock(world) == Blocks.SANDSTONE + fun isScorched(world: WorldAccess) = getBlock(world) == Blocks.SANDSTONE_STAIRS + + companion object { + fun fromBlockPos(blockPos: BlockPos): BoardPosition? { + if (blockPos.y != sandBoxLow.y) return null + val x = blockPos.x - sandBoxLow.x + val y = blockPos.z - sandBoxLow.z + if (x < 0 || x >= boardSize.first) return null + if (y < 0 || y >= boardSize.second) return null + return BoardPosition(x, y) + } + } + } + + data class GameState( + val nearbyBombs: MutableMap = mutableMapOf(), + val knownBombPositions: MutableSet = mutableSetOf(), + var lastClickedPosition: BoardPosition? = null, + var lastDowsingMode: DowsingMode? = null, + ) + + var gameState: GameState? = null + val log = DebugLogger("minesweeper") + + @Subscribe + fun onCommand(event: CommandEvent.SubCommand) { + event.subcommand("minesweepertutorial") { + thenExecute { + ScreenUtil.setScreenLater(MoulConfigUtils.loadScreen("carnival/minesweeper_tutorial", + TutorialScreen, + null)) + } + } + } + + @Subscribe + fun onWorldChange(event: WorldReadyEvent) { + gameState = null + } + + @Subscribe + fun onChat(event: ProcessChatEvent) { + if (CarnivalFeatures.TConfig.displayTutorials && event.unformattedString == startGameQuestion) { + MC.sendChat(Text.translatable("firmament.carnival.tutorial.minesweeper").styled { + it.withClickEvent(ClickEvent(ClickEvent.Action.RUN_COMMAND, "/firm minesweepertutorial")) + }) + } + if (!CarnivalFeatures.TConfig.enableBombSolver) { + gameState = null // TODO: replace this which a watchable property + return + } + if (event.unformattedString == gameStartMessage) { + gameState = GameState() + log.log { "Game started" } + } + if (event.unformattedString.trim() == gameEndMessage) { + gameState = null // TODO: add a loot tracker maybe? probably not, i dont think people care + log.log { "Finished game" } + } + val gs = gameState ?: return + bombPattern.useMatch(event.unformattedString) { + val bombCount = group("bombCount").toInt() + log.log { "Marking ${gs.lastClickedPosition} as having $bombCount nearby" } + val pos = gs.lastClickedPosition ?: return + gs.nearbyBombs[pos] = bombCount + } + } + + @Subscribe + fun onMobChange(event: EntityUpdateEvent) { + val gs = gameState ?: return + if (event !is EntityUpdateEvent.TrackedDataUpdate) return + // TODO: listen to state + } + + @Subscribe + fun onBlockClick(event: AttackBlockEvent) { + val gs = gameState ?: return + val boardPosition = BoardPosition.fromBlockPos(event.blockPos) + log.log { "Breaking block at ${event.blockPos} ($boardPosition)" } + gs.lastClickedPosition = boardPosition + gs.lastDowsingMode = DowsingMode.fromItem(event.player.inventory.mainHandStack) + } + + @Subscribe + fun onRender(event: WorldRenderLastEvent) { + val gs = gameState ?: return + RenderInWorldContext.renderInWorld(event) { + for ((pos, bombCount) in gs.nearbyBombs) { + this.text(pos.toBlockPos().up().toCenterPos(), Text.literal("§a$bombCount \uD83D\uDCA3")) + } + } + } + + +} diff --git a/src/main/kotlin/features/fixes/CompatibliltyFeatures.kt b/src/main/kotlin/features/fixes/CompatibliltyFeatures.kt new file mode 100644 index 0000000..7c43cf6 --- /dev/null +++ b/src/main/kotlin/features/fixes/CompatibliltyFeatures.kt @@ -0,0 +1,51 @@ + + +package moe.nea.firmament.features.fixes + +import net.fabricmc.loader.api.FabricLoader +import net.superkat.explosiveenhancement.api.ExplosiveApi +import net.minecraft.particle.ParticleTypes +import net.minecraft.util.math.Vec3d +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.events.ParticleSpawnEvent +import moe.nea.firmament.features.FirmamentFeature +import moe.nea.firmament.gui.config.ManagedConfig +import moe.nea.firmament.util.MC + +object CompatibliltyFeatures : FirmamentFeature { + override val identifier: String + get() = "compatibility" + + object TConfig : ManagedConfig(identifier) { + val enhancedExplosions by toggle("explosion-enabled") { false } + val explosionSize by integer("explosion-power", 10, 50) { 1 } + } + + override val config: ManagedConfig? + get() = TConfig + + interface ExplosiveApiWrapper { + fun spawnParticle(vec3d: Vec3d, power: Float) + } + + class ExplosiveApiWrapperImpl : ExplosiveApiWrapper { + override fun spawnParticle(vec3d: Vec3d, power: Float) { + ExplosiveApi.spawnParticles(MC.world, vec3d.x, vec3d.y, vec3d.z, TConfig.explosionSize / 10F) + } + } + + val explosiveApiWrapper = if (FabricLoader.getInstance().isModLoaded("explosiveenhancement")) { + ExplosiveApiWrapperImpl() + } else null + + @Subscribe + fun onExplosion(it: ParticleSpawnEvent) { + if (TConfig.enhancedExplosions && + it.particleEffect.type == ParticleTypes.EXPLOSION_EMITTER && + explosiveApiWrapper != null + ) { + it.cancel() + explosiveApiWrapper.spawnParticle(it.position, 2F) + } + } +} diff --git a/src/main/kotlin/features/fixes/Fixes.kt b/src/main/kotlin/features/fixes/Fixes.kt new file mode 100644 index 0000000..d7b7a1c --- /dev/null +++ b/src/main/kotlin/features/fixes/Fixes.kt @@ -0,0 +1,71 @@ + + +package moe.nea.firmament.features.fixes + +import moe.nea.jarvis.api.Point +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable +import net.minecraft.client.MinecraftClient +import net.minecraft.client.option.KeyBinding +import net.minecraft.entity.player.PlayerEntity +import net.minecraft.text.Text +import net.minecraft.util.Arm +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.events.HudRenderEvent +import moe.nea.firmament.events.WorldKeyboardEvent +import moe.nea.firmament.features.FirmamentFeature +import moe.nea.firmament.gui.config.ManagedConfig +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.errorBoundary + +object Fixes : FirmamentFeature { + override val identifier: String + get() = "fixes" + + object TConfig : ManagedConfig(identifier) { + val fixUnsignedPlayerSkins by toggle("player-skins") { true } + var autoSprint by toggle("auto-sprint") { false } + val autoSprintKeyBinding by keyBindingWithDefaultUnbound("auto-sprint-keybinding") + val autoSprintHud by position("auto-sprint-hud", 80, 10) { Point(0.0, 1.0) } + val peekChat by keyBindingWithDefaultUnbound("peek-chat") + } + + override val config: ManagedConfig + get() = TConfig + + fun handleIsPressed( + keyBinding: KeyBinding, + cir: CallbackInfoReturnable + ) { + if (keyBinding === MinecraftClient.getInstance().options.sprintKey && TConfig.autoSprint && MC.player?.isSprinting != true) + cir.returnValue = true + } + + @Subscribe + fun onRenderHud(it: HudRenderEvent) { + if (!TConfig.autoSprintKeyBinding.isBound) return + it.context.matrices.push() + TConfig.autoSprintHud.applyTransformations(it.context.matrices) + it.context.drawText( + MC.font, Text.translatable( + if (TConfig.autoSprint) + "firmament.fixes.auto-sprint.on" + else if (MC.player?.isSprinting == true) + "firmament.fixes.auto-sprint.sprinting" + else + "firmament.fixes.auto-sprint.not-sprinting" + ), 0, 0, -1, false + ) + it.context.matrices.pop() + } + + @Subscribe + fun onWorldKeyboard(it: WorldKeyboardEvent) { + if (it.matches(TConfig.autoSprintKeyBinding)) { + TConfig.autoSprint = !TConfig.autoSprint + } + } + + fun shouldPeekChat(): Boolean { + return TConfig.peekChat.isPressed(atLeast = true) + } +} diff --git a/src/main/kotlin/features/inventory/CraftingOverlay.kt b/src/main/kotlin/features/inventory/CraftingOverlay.kt new file mode 100644 index 0000000..031ef78 --- /dev/null +++ b/src/main/kotlin/features/inventory/CraftingOverlay.kt @@ -0,0 +1,66 @@ + + +package moe.nea.firmament.features.inventory + +import net.minecraft.client.gui.screen.ingame.GenericContainerScreen +import net.minecraft.item.ItemStack +import net.minecraft.util.Formatting +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.events.SlotRenderEvents +import moe.nea.firmament.features.FirmamentFeature +import moe.nea.firmament.rei.FirmamentReiPlugin.Companion.asItemEntry +import moe.nea.firmament.rei.SBItemEntryDefinition +import moe.nea.firmament.rei.recipes.SBCraftingRecipe +import moe.nea.firmament.util.MC + +object CraftingOverlay : FirmamentFeature { + + private var screen: GenericContainerScreen? = null + private var recipe: SBCraftingRecipe? = null + private val craftingOverlayIndices = listOf( + 10, 11, 12, + 19, 20, 21, + 28, 29, 30, + ) + + + fun setOverlay(screen: GenericContainerScreen, recipe: SBCraftingRecipe) { + this.screen = screen + this.recipe = recipe + } + + override val identifier: String + get() = "crafting-overlay" + + @Subscribe + fun onSlotRender(event: SlotRenderEvents.After) { + val slot = event.slot + val recipe = this.recipe ?: return + if (slot.inventory != screen?.screenHandler?.inventory) return + val recipeIndex = craftingOverlayIndices.indexOf(slot.index) + if (recipeIndex < 0) return + val expectedItem = recipe.neuRecipe.inputs[recipeIndex] + val actualStack = slot.stack ?: ItemStack.EMPTY!! + val actualEntry = SBItemEntryDefinition.getEntry(actualStack).value + if ((actualEntry.skyblockId.neuItem != expectedItem.itemId || actualEntry.getStackSize() < expectedItem.amount) && expectedItem.amount.toInt() != 0) { + event.context.fill( + event.slot.x, + event.slot.y, + event.slot.x + 16, + event.slot.y + 16, + 0x80FF0000.toInt() + ) + } + if (!slot.hasStack()) { + val itemStack = SBItemEntryDefinition.getEntry(expectedItem).asItemEntry().value + event.context.drawItem(itemStack, event.slot.x, event.slot.y) + event.context.drawItemInSlot( + MC.font, + itemStack, + event.slot.x, + event.slot.y, + "${Formatting.RED}${expectedItem.amount.toInt()}" + ) + } + } +} diff --git a/src/main/kotlin/features/inventory/ItemRarityCosmetics.kt b/src/main/kotlin/features/inventory/ItemRarityCosmetics.kt new file mode 100644 index 0000000..566a813 --- /dev/null +++ b/src/main/kotlin/features/inventory/ItemRarityCosmetics.kt @@ -0,0 +1,85 @@ + + +package moe.nea.firmament.features.inventory + +import java.awt.Color +import net.minecraft.client.gui.DrawContext +import net.minecraft.item.ItemStack +import net.minecraft.util.Formatting +import net.minecraft.util.Identifier +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.events.HotbarItemRenderEvent +import moe.nea.firmament.events.SlotRenderEvents +import moe.nea.firmament.features.FirmamentFeature +import moe.nea.firmament.gui.config.ManagedConfig +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.item.loreAccordingToNbt +import moe.nea.firmament.util.lastNotNullOfOrNull +import moe.nea.firmament.util.memoize +import moe.nea.firmament.util.memoizeIdentity +import moe.nea.firmament.util.unformattedString + +object ItemRarityCosmetics : FirmamentFeature { + override val identifier: String + get() = "item-rarity-cosmetics" + + object TConfig : ManagedConfig(identifier) { + val showItemRarityBackground by toggle("background") { false } + val showItemRarityInHotbar by toggle("background-hotbar") { false } + } + + override val config: ManagedConfig + get() = TConfig + + private val rarityToColor = mapOf( + "UNCOMMON" to Formatting.GREEN, + "COMMON" to Formatting.WHITE, + "RARE" to Formatting.DARK_BLUE, + "EPIC" to Formatting.DARK_PURPLE, + "LEGENDARY" to Formatting.GOLD, + "LEGENJERRY" to Formatting.GOLD, + "MYTHIC" to Formatting.LIGHT_PURPLE, + "DIVINE" to Formatting.BLUE, + "SPECIAL" to Formatting.DARK_RED, + "SUPREME" to Formatting.DARK_RED, + ).mapValues { + val c = Color(it.value.colorValue!!) + Triple(c.red / 255F, c.green / 255F, c.blue / 255F) + } + + private fun getSkyblockRarity0(itemStack: ItemStack): Triple? { + return itemStack.loreAccordingToNbt.lastNotNullOfOrNull { + val entry = it.unformattedString + rarityToColor.entries.find { (k, v) -> k in entry }?.value + } + } + + val getSkyblockRarity = ::getSkyblockRarity0.memoizeIdentity(100) + + + fun drawItemStackRarity(drawContext: DrawContext, x: Int, y: Int, item: ItemStack) { + val (r, g, b) = getSkyblockRarity(item) ?: return + drawContext.drawSprite( + x, y, + 0, + 16, 16, + MC.guiAtlasManager.getSprite(Identifier.of("firmament:item_rarity_background")), + r, g, b, 1F + ) + } + + + @Subscribe + fun onRenderSlot(it: SlotRenderEvents.Before) { + if (!TConfig.showItemRarityBackground) return + val stack = it.slot.stack ?: return + drawItemStackRarity(it.context, it.slot.x, it.slot.y, stack) + } + + @Subscribe + fun onRenderHotbarItem(it: HotbarItemRenderEvent) { + if (!TConfig.showItemRarityInHotbar) return + val stack = it.item + drawItemStackRarity(it.context, it.x, it.y, stack) + } +} diff --git a/src/main/kotlin/features/inventory/PriceData.kt b/src/main/kotlin/features/inventory/PriceData.kt new file mode 100644 index 0000000..c61f8e8 --- /dev/null +++ b/src/main/kotlin/features/inventory/PriceData.kt @@ -0,0 +1,51 @@ + + +package moe.nea.firmament.features.inventory + +import net.minecraft.text.Text +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.events.ItemTooltipEvent +import moe.nea.firmament.features.FirmamentFeature +import moe.nea.firmament.gui.config.ManagedConfig +import moe.nea.firmament.repo.HypixelStaticData +import moe.nea.firmament.util.FirmFormatters +import moe.nea.firmament.util.skyBlockId + +object PriceData : FirmamentFeature { + override val identifier: String + get() = "price-data" + + object TConfig : ManagedConfig(identifier) { + val tooltipEnabled by toggle("enable-always") { true } + val enableKeybinding by keyBindingWithDefaultUnbound("enable-keybind") + } + + override val config get() = TConfig + + @Subscribe + fun onItemTooltip(it: ItemTooltipEvent) { + if (!TConfig.tooltipEnabled && !TConfig.enableKeybinding.isPressed()) { + return + } + val sbId = it.stack.skyBlockId + val bazaarData = HypixelStaticData.bazaarData[sbId] + val lowestBin = HypixelStaticData.lowestBin[sbId] + if (bazaarData != null) { + it.lines.add(Text.literal("")) + it.lines.add( + Text.stringifiedTranslatable("firmament.tooltip.bazaar.sell-order", + FirmFormatters.formatCommas(bazaarData.quickStatus.sellPrice, 1)) + ) + it.lines.add( + Text.stringifiedTranslatable("firmament.tooltip.bazaar.buy-order", + FirmFormatters.formatCommas(bazaarData.quickStatus.buyPrice, 1)) + ) + } else if (lowestBin != null) { + it.lines.add(Text.literal("")) + it.lines.add( + Text.stringifiedTranslatable("firmament.tooltip.ah.lowestbin", + FirmFormatters.formatCommas(lowestBin, 1)) + ) + } + } +} diff --git a/src/main/kotlin/features/inventory/SaveCursorPosition.kt b/src/main/kotlin/features/inventory/SaveCursorPosition.kt new file mode 100644 index 0000000..1c55753 --- /dev/null +++ b/src/main/kotlin/features/inventory/SaveCursorPosition.kt @@ -0,0 +1,66 @@ + + +package moe.nea.firmament.features.inventory + +import kotlin.math.absoluteValue +import kotlin.time.Duration.Companion.milliseconds +import net.minecraft.client.util.InputUtil +import moe.nea.firmament.features.FirmamentFeature +import moe.nea.firmament.gui.config.ManagedConfig +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.TimeMark +import moe.nea.firmament.util.assertNotNullOr + +object SaveCursorPosition : FirmamentFeature { + override val identifier: String + get() = "save-cursor-position" + + object TConfig : ManagedConfig(identifier) { + val enable by toggle("enable") { true } + val tolerance by duration("tolerance", 10.milliseconds, 5000.milliseconds) { 500.milliseconds } + } + + override val config: TConfig + get() = TConfig + + var savedPositionedP1: Pair? = null + var savedPosition: SavedPosition? = null + + data class SavedPosition( + val middle: Pair, + val cursor: Pair, + val savedAt: TimeMark = TimeMark.now() + ) + + @JvmStatic + fun saveCursorOriginal(positionedX: Double, positionedY: Double) { + savedPositionedP1 = Pair(positionedX, positionedY) + } + + @JvmStatic + fun loadCursor(middleX: Double, middleY: Double): Pair? { + if (!TConfig.enable) return null + val lastPosition = savedPosition?.takeIf { it.savedAt.passedTime() < TConfig.tolerance } + savedPosition = null + if (lastPosition != null && + (lastPosition.middle.first - middleX).absoluteValue < 1 && + (lastPosition.middle.second - middleY).absoluteValue < 1 + ) { + InputUtil.setCursorParameters( + MC.window.handle, + InputUtil.GLFW_CURSOR_NORMAL, + lastPosition.cursor.first, + lastPosition.cursor.second + ) + return lastPosition.cursor + } + return null + } + + @JvmStatic + fun saveCursorMiddle(middleX: Double, middleY: Double) { + if (!TConfig.enable) return + val cursorPos = assertNotNullOr(savedPositionedP1) { return } + savedPosition = SavedPosition(Pair(middleX, middleY), cursorPos) + } +} diff --git a/src/main/kotlin/features/inventory/SlotLocking.kt b/src/main/kotlin/features/inventory/SlotLocking.kt new file mode 100644 index 0000000..a50d8fb --- /dev/null +++ b/src/main/kotlin/features/inventory/SlotLocking.kt @@ -0,0 +1,203 @@ + + +@file:UseSerializers(DashlessUUIDSerializer::class) + +package moe.nea.firmament.features.inventory + +import com.mojang.blaze3d.systems.RenderSystem +import java.util.UUID +import org.lwjgl.glfw.GLFW +import kotlinx.serialization.Serializable +import kotlinx.serialization.UseSerializers +import kotlinx.serialization.serializer +import net.minecraft.client.gui.screen.ingame.HandledScreen +import net.minecraft.entity.player.PlayerInventory +import net.minecraft.screen.GenericContainerScreenHandler +import net.minecraft.screen.slot.SlotActionType +import net.minecraft.util.Identifier +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.events.HandledScreenKeyPressedEvent +import moe.nea.firmament.events.IsSlotProtectedEvent +import moe.nea.firmament.events.SlotRenderEvents +import moe.nea.firmament.features.FirmamentFeature +import moe.nea.firmament.gui.config.ManagedConfig +import moe.nea.firmament.keybindings.SavedKeyBinding +import moe.nea.firmament.mixins.accessor.AccessorHandledScreen +import moe.nea.firmament.util.CommonSoundEffects +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.SBData +import moe.nea.firmament.util.SkyBlockIsland +import moe.nea.firmament.util.data.ProfileSpecificDataHolder +import moe.nea.firmament.util.item.displayNameAccordingToNbt +import moe.nea.firmament.util.item.loreAccordingToNbt +import moe.nea.firmament.util.json.DashlessUUIDSerializer +import moe.nea.firmament.util.skyblockUUID +import moe.nea.firmament.util.unformattedString + +object SlotLocking : FirmamentFeature { + override val identifier: String + get() = "slot-locking" + + @Serializable + data class Data( + val lockedSlots: MutableSet = mutableSetOf(), + val lockedSlotsRift: MutableSet = mutableSetOf(), + + val lockedUUIDs: MutableSet = mutableSetOf(), + ) + + object TConfig : ManagedConfig(identifier) { + val lockSlot by keyBinding("lock") { GLFW.GLFW_KEY_L } + val lockUUID by keyBindingWithOutDefaultModifiers("lock-uuid") { + SavedKeyBinding(GLFW.GLFW_KEY_L, shift = true) + } + } + + override val config: TConfig + get() = TConfig + + object DConfig : ProfileSpecificDataHolder(serializer(), "locked-slots", ::Data) + + val lockedUUIDs get() = DConfig.data?.lockedUUIDs + + val lockedSlots + get() = when (SBData.skyblockLocation) { + SkyBlockIsland.RIFT -> DConfig.data?.lockedSlotsRift + null -> null + else -> DConfig.data?.lockedSlots + } + + fun isSalvageScreen(screen: HandledScreen<*>?): Boolean { + if (screen == null) return false + return screen.title.unformattedString.contains("Salvage Item") + } + + fun isTradeScreen(screen: HandledScreen<*>?): Boolean { + if (screen == null) return false + val handler = screen.screenHandler as? GenericContainerScreenHandler ?: return false + if (handler.inventory.size() < 9) return false + val middlePane = handler.inventory.getStack(handler.inventory.size() - 5) + if (middlePane == null) return false + return middlePane.displayNameAccordingToNbt?.unformattedString == "⇦ Your stuff" + } + + fun isNpcShop(screen: HandledScreen<*>?): Boolean { + if (screen == null) return false + val handler = screen.screenHandler as? GenericContainerScreenHandler ?: return false + if (handler.inventory.size() < 9) return false + val sellItem = handler.inventory.getStack(handler.inventory.size() - 5) + if (sellItem == null) return false + if (sellItem.displayNameAccordingToNbt?.unformattedString == "Sell Item") return true + val lore = sellItem.loreAccordingToNbt + return (lore.lastOrNull() ?: return false).unformattedString == "Click to buyback!" + } + + @Subscribe + fun onSalvageProtect(event: IsSlotProtectedEvent) { + if (event.slot == null) return + if (!event.slot.hasStack()) return + if (event.slot.stack.displayNameAccordingToNbt?.unformattedString != "Salvage Items") return + val inv = event.slot.inventory + var anyBlocked = false + for (i in 0 until event.slot.index) { + val stack = inv.getStack(i) + if (IsSlotProtectedEvent.shouldBlockInteraction(null, SlotActionType.THROW, stack)) + anyBlocked = true + } + if (anyBlocked) { + event.protectSilent() + } + } + + @Subscribe + fun onProtectUuidItems(event: IsSlotProtectedEvent) { + val doesNotDeleteItem = event.actionType == SlotActionType.SWAP + || event.actionType == SlotActionType.PICKUP + || event.actionType == SlotActionType.QUICK_MOVE + || event.actionType == SlotActionType.QUICK_CRAFT + || event.actionType == SlotActionType.CLONE + || event.actionType == SlotActionType.PICKUP_ALL + val isSellOrTradeScreen = + isNpcShop(MC.handledScreen) || isTradeScreen(MC.handledScreen) || isSalvageScreen(MC.handledScreen) + if ((!isSellOrTradeScreen || event.slot?.inventory !is PlayerInventory) + && doesNotDeleteItem + ) return + val stack = event.itemStack ?: return + val uuid = stack.skyblockUUID ?: return + if (uuid in (lockedUUIDs ?: return)) { + event.protect() + } + } + + @Subscribe + fun onProtectSlot(it: IsSlotProtectedEvent) { + if (it.slot != null && it.slot.inventory is PlayerInventory && it.slot.index in (lockedSlots ?: setOf())) { + it.protect() + } + } + + @Subscribe + fun onLockUUID(it: HandledScreenKeyPressedEvent) { + if (!it.matches(TConfig.lockUUID)) return + val inventory = MC.handledScreen ?: return + inventory as AccessorHandledScreen + + val slot = inventory.focusedSlot_Firmament ?: return + val stack = slot.stack ?: return + val uuid = stack.skyblockUUID ?: return + val lockedUUIDs = lockedUUIDs ?: return + if (uuid in lockedUUIDs) { + lockedUUIDs.remove(uuid) + } else { + lockedUUIDs.add(uuid) + } + DConfig.markDirty() + CommonSoundEffects.playSuccess() + it.cancel() + } + + @Subscribe + fun onLockSlot(it: HandledScreenKeyPressedEvent) { + if (!it.matches(TConfig.lockSlot)) return + val inventory = MC.handledScreen ?: return + inventory as AccessorHandledScreen + + val slot = inventory.focusedSlot_Firmament ?: return + val lockedSlots = lockedSlots ?: return + if (slot.inventory is PlayerInventory) { + if (slot.index in lockedSlots) { + lockedSlots.remove(slot.index) + } else { + lockedSlots.add(slot.index) + } + DConfig.markDirty() + CommonSoundEffects.playSuccess() + } + } + + @Subscribe + fun onRenderSlotOverlay(it: SlotRenderEvents.After) { + val isSlotLocked = it.slot.inventory is PlayerInventory && it.slot.index in (lockedSlots ?: setOf()) + val isUUIDLocked = (it.slot.stack?.skyblockUUID) in (lockedUUIDs ?: setOf()) + if (isSlotLocked || isUUIDLocked) { + RenderSystem.disableDepthTest() + it.context.drawSprite( + it.slot.x, it.slot.y, 0, + 16, 16, + MC.guiAtlasManager.getSprite( + when { + isSlotLocked -> + (Identifier.of("firmament:slot_locked")) + + isUUIDLocked -> + (Identifier.of("firmament:uuid_locked")) + + else -> + error("unreachable") + } + ) + ) + RenderSystem.enableDepthTest() + } + } +} diff --git a/src/main/kotlin/features/inventory/buttons/InventoryButton.kt b/src/main/kotlin/features/inventory/buttons/InventoryButton.kt new file mode 100644 index 0000000..539edf2 --- /dev/null +++ b/src/main/kotlin/features/inventory/buttons/InventoryButton.kt @@ -0,0 +1,85 @@ + + +package moe.nea.firmament.features.inventory.buttons + +import com.mojang.brigadier.StringReader +import me.shedaniel.math.Dimension +import me.shedaniel.math.Point +import me.shedaniel.math.Rectangle +import kotlinx.serialization.Serializable +import net.minecraft.client.gui.DrawContext +import net.minecraft.command.CommandRegistryAccess +import net.minecraft.command.argument.ItemStackArgumentType +import net.minecraft.item.ItemStack +import net.minecraft.resource.featuretoggle.FeatureFlags +import net.minecraft.util.Identifier +import moe.nea.firmament.repo.ItemCache.asItemStack +import moe.nea.firmament.repo.RepoManager +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.SkyblockId +import moe.nea.firmament.util.memoize + +@Serializable +data class InventoryButton( + var x: Int, + var y: Int, + var anchorRight: Boolean, + var anchorBottom: Boolean, + var icon: String? = "", + var command: String? = "", +) { + companion object { + val itemStackParser by lazy { + ItemStackArgumentType.itemStack(CommandRegistryAccess.of(MC.defaultRegistries, + FeatureFlags.VANILLA_FEATURES)) + } + val dimensions = Dimension(18, 18) + val getItemForName = ::getItemForName0.memoize(1024) + fun getItemForName0(icon: String): ItemStack { + val repoItem = RepoManager.getNEUItem(SkyblockId(icon)) + var itemStack = repoItem.asItemStack(idHint = SkyblockId(icon)) + if (repoItem == null) { + val giveSyntaxItem = if (icon.startsWith("/give") || icon.startsWith("give")) + icon.split(" ", limit = 3).getOrNull(2) ?: icon + else icon + val componentItem = + runCatching { + itemStackParser.parse(StringReader(giveSyntaxItem)).createStack(1, false) + }.getOrNull() + if (componentItem != null) + itemStack = componentItem + } + return itemStack + } + } + + fun render(context: DrawContext) { + context.drawSprite( + 0, + 0, + 0, + dimensions.width, + dimensions.height, + MC.guiAtlasManager.getSprite(Identifier.of("firmament:inventory_button_background")) + ) + context.drawItem(getItem(), 1, 1) + } + + fun isValid() = !icon.isNullOrBlank() && !command.isNullOrBlank() + + fun getPosition(guiRect: Rectangle): Point { + return Point( + (if (anchorRight) guiRect.maxX else guiRect.minX) + x, + (if (anchorBottom) guiRect.maxY else guiRect.minY) + y, + ) + } + + fun getBounds(guiRect: Rectangle): Rectangle { + return Rectangle(getPosition(guiRect), dimensions) + } + + fun getItem(): ItemStack { + return getItemForName(icon ?: "") + } + +} diff --git a/src/main/kotlin/features/inventory/buttons/InventoryButtonEditor.kt b/src/main/kotlin/features/inventory/buttons/InventoryButtonEditor.kt new file mode 100644 index 0000000..c57563e --- /dev/null +++ b/src/main/kotlin/features/inventory/buttons/InventoryButtonEditor.kt @@ -0,0 +1,184 @@ + + +package moe.nea.firmament.features.inventory.buttons + +import io.github.notenoughupdates.moulconfig.common.IItemStack +import io.github.notenoughupdates.moulconfig.platform.ModernItemStack +import io.github.notenoughupdates.moulconfig.xml.Bind +import me.shedaniel.math.Point +import me.shedaniel.math.Rectangle +import org.lwjgl.glfw.GLFW +import net.minecraft.client.gui.DrawContext +import net.minecraft.client.gui.widget.ButtonWidget +import net.minecraft.client.util.InputUtil +import net.minecraft.text.Text +import net.minecraft.util.math.MathHelper +import net.minecraft.util.math.Vec2f +import moe.nea.firmament.util.ClipboardUtils +import moe.nea.firmament.util.FragmentGuiScreen +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.MoulConfigUtils + +class InventoryButtonEditor( + val lastGuiRect: Rectangle, +) : FragmentGuiScreen() { + inner class Editor(val originalButton: InventoryButton) { + @field:Bind + var command: String = originalButton.command ?: "" + + @field:Bind + var icon: String = originalButton.icon ?: "" + + @Bind + fun getItemIcon(): IItemStack { + save() + return ModernItemStack.of(InventoryButton.getItemForName(icon)) + } + + @Bind + fun delete() { + buttons.removeIf { it === originalButton } + popup = null + } + + fun save() { + originalButton.icon = icon + originalButton.command = command + } + } + + var buttons: MutableList = + InventoryButtons.DConfig.data.buttons.map { it.copy() }.toMutableList() + + override fun close() { + InventoryButtons.DConfig.data.buttons = buttons + InventoryButtons.DConfig.markDirty() + super.close() + } + + override fun init() { + super.init() + addDrawableChild( + ButtonWidget.builder(Text.translatable("firmament.inventory-buttons.load-preset")) { + val t = ClipboardUtils.getTextContents() + val newButtons = InventoryButtonTemplates.loadTemplate(t) + if (newButtons != null) + buttons = newButtons.toMutableList() + } + .position(lastGuiRect.minX + 10, lastGuiRect.minY + 35) + .width(lastGuiRect.width - 20) + .build() + ) + addDrawableChild( + ButtonWidget.builder(Text.translatable("firmament.inventory-buttons.save-preset")) { + ClipboardUtils.setTextContent(InventoryButtonTemplates.saveTemplate(buttons)) + } + .position(lastGuiRect.minX + 10, lastGuiRect.minY + 60) + .width(lastGuiRect.width - 20) + .build() + ) + } + + override fun render(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) { + super.render(context, mouseX, mouseY, delta) + context.matrices.push() + context.matrices.translate(0f, 0f, -10f) + context.fill(lastGuiRect.minX, lastGuiRect.minY, lastGuiRect.maxX, lastGuiRect.maxY, -1) + context.setShaderColor(1f, 1f, 1f, 1f) + context.matrices.pop() + for (button in buttons) { + val buttonPosition = button.getBounds(lastGuiRect) + context.matrices.push() + context.matrices.translate(buttonPosition.minX.toFloat(), buttonPosition.minY.toFloat(), 0F) + button.render(context) + context.matrices.pop() + } + } + + override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean { + if (super.keyPressed(keyCode, scanCode, modifiers)) return true + if (keyCode == GLFW.GLFW_KEY_ESCAPE) { + close() + return true + } + return false + } + + override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean { + if (super.mouseReleased(mouseX, mouseY, button)) return true + val clickedButton = buttons.firstOrNull { it.getBounds(lastGuiRect).contains(Point(mouseX, mouseY)) } + if (clickedButton != null && !justPerformedAClickAction) { + createPopup(MoulConfigUtils.loadGui("button_editor_fragment", Editor(clickedButton)), Point(mouseX, mouseY)) + return true + } + justPerformedAClickAction = false + lastDraggedButton = null + return false + } + + override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean { + if (super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)) return true + + if (initialDragMousePosition.distanceSquared(Vec2f(mouseX.toFloat(), mouseY.toFloat())) >= 4 * 4) { + initialDragMousePosition = Vec2f(-10F, -10F) + lastDraggedButton?.let { dragging -> + justPerformedAClickAction = true + val (anchorRight, anchorBottom, offsetX, offsetY) = getCoordsForMouse(mouseX.toInt(), mouseY.toInt()) + ?: return true + dragging.x = offsetX + dragging.y = offsetY + dragging.anchorRight = anchorRight + dragging.anchorBottom = anchorBottom + } + } + return false + } + + var lastDraggedButton: InventoryButton? = null + var justPerformedAClickAction = false + var initialDragMousePosition = Vec2f(-10F, -10F) + + data class AnchoredCoords( + val anchorRight: Boolean, + val anchorBottom: Boolean, + val offsetX: Int, + val offsetY: Int, + ) + + fun getCoordsForMouse(mx: Int, my: Int): AnchoredCoords? { + if (lastGuiRect.contains(mx, my) || lastGuiRect.contains( + Point( + mx + InventoryButton.dimensions.width, + my + InventoryButton.dimensions.height, + ) + ) + ) return null + + val anchorRight = mx > lastGuiRect.maxX + val anchorBottom = my > lastGuiRect.maxY + var offsetX = mx - if (anchorRight) lastGuiRect.maxX else lastGuiRect.minX + var offsetY = my - if (anchorBottom) lastGuiRect.maxY else lastGuiRect.minY + if (InputUtil.isKeyPressed(MC.window.handle, InputUtil.GLFW_KEY_LEFT_SHIFT)) { + offsetX = MathHelper.floor(offsetX / 20F) * 20 + offsetY = MathHelper.floor(offsetY / 20F) * 20 + } + return AnchoredCoords(anchorRight, anchorBottom, offsetX, offsetY) + } + + override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean { + if (super.mouseClicked(mouseX, mouseY, button)) return true + val clickedButton = buttons.firstOrNull { it.getBounds(lastGuiRect).contains(Point(mouseX, mouseY)) } + if (clickedButton != null) { + lastDraggedButton = clickedButton + initialDragMousePosition = Vec2f(mouseX.toFloat(), mouseY.toFloat()) + return true + } + val mx = mouseX.toInt() + val my = mouseY.toInt() + val (anchorRight, anchorBottom, offsetX, offsetY) = getCoordsForMouse(mx, my) ?: return true + buttons.add(InventoryButton(offsetX, offsetY, anchorRight, anchorBottom, null, null)) + justPerformedAClickAction = true + return true + } + +} diff --git a/src/main/kotlin/features/inventory/buttons/InventoryButtonTemplates.kt b/src/main/kotlin/features/inventory/buttons/InventoryButtonTemplates.kt new file mode 100644 index 0000000..99b544b --- /dev/null +++ b/src/main/kotlin/features/inventory/buttons/InventoryButtonTemplates.kt @@ -0,0 +1,35 @@ + + +package moe.nea.firmament.features.inventory.buttons + +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import net.minecraft.text.Text +import moe.nea.firmament.Firmament +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.TemplateUtil + +object InventoryButtonTemplates { + + val legacyPrefix = "NEUBUTTONS/" + val modernPrefix = "MAYBEONEDAYIWILLHAVEMYOWNFORMAT" + + fun loadTemplate(t: String): List? { + val buttons = TemplateUtil.maybeDecodeTemplate>(legacyPrefix, t) ?: return null + return buttons.mapNotNull { + try { + Firmament.json.decodeFromString(it).also { + if (it.icon?.startsWith("extra:") == true || it.command?.any { it.isLowerCase() } == true) { + MC.sendChat(Text.translatable("firmament.inventory-buttons.import-failed")) + } + } + } catch (e: Exception) { + null + } + } + } + + fun saveTemplate(buttons: List): String { + return TemplateUtil.encodeTemplate(legacyPrefix, buttons.map { Firmament.json.encodeToString(it) }) + } +} diff --git a/src/main/kotlin/features/inventory/buttons/InventoryButtons.kt b/src/main/kotlin/features/inventory/buttons/InventoryButtons.kt new file mode 100644 index 0000000..fa90d21 --- /dev/null +++ b/src/main/kotlin/features/inventory/buttons/InventoryButtons.kt @@ -0,0 +1,88 @@ + + +package moe.nea.firmament.features.inventory.buttons + +import me.shedaniel.math.Rectangle +import kotlinx.serialization.Serializable +import kotlinx.serialization.serializer +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.events.HandledScreenClickEvent +import moe.nea.firmament.events.HandledScreenForegroundEvent +import moe.nea.firmament.events.HandledScreenPushREIEvent +import moe.nea.firmament.features.FirmamentFeature +import moe.nea.firmament.gui.config.ManagedConfig +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.ScreenUtil +import moe.nea.firmament.util.data.DataHolder +import moe.nea.firmament.util.getRectangle + +object InventoryButtons : FirmamentFeature { + override val identifier: String + get() = "inventory-buttons" + + object TConfig : ManagedConfig(identifier) { + val _openEditor by button("open-editor") { + openEditor() + } + } + + object DConfig : DataHolder(serializer(), identifier, ::Data) + + @Serializable + data class Data( + var buttons: MutableList = mutableListOf() + ) + + + override val config: ManagedConfig + get() = TConfig + + fun getValidButtons() = DConfig.data.buttons.asSequence().filter { it.isValid() } + + @Subscribe + fun onRectangles(it: HandledScreenPushREIEvent) { + val bounds = it.screen.getRectangle() + for (button in getValidButtons()) { + val buttonBounds = button.getBounds(bounds) + it.block(buttonBounds) + } + } + + @Subscribe + fun onClickScreen(it: HandledScreenClickEvent) { + val bounds = it.screen.getRectangle() + for (button in getValidButtons()) { + val buttonBounds = button.getBounds(bounds) + if (buttonBounds.contains(it.mouseX, it.mouseY)) { + MC.sendCommand(button.command!! /* non null invariant covered by getValidButtons */) + break + } + } + } + + @Subscribe + fun onRenderForeground(it: HandledScreenForegroundEvent) { + val bounds = it.screen.getRectangle() + for (button in getValidButtons()) { + val buttonBounds = button.getBounds(bounds) + it.context.matrices.push() + it.context.matrices.translate(buttonBounds.minX.toFloat(), buttonBounds.minY.toFloat(), 0F) + button.render(it.context) + it.context.matrices.pop() + } + lastRectangle = bounds + } + + var lastRectangle: Rectangle? = null + fun openEditor() { + ScreenUtil.setScreenLater( + InventoryButtonEditor( + lastRectangle ?: Rectangle( + MC.window.scaledWidth / 2 - 100, + MC.window.scaledHeight / 2 - 100, + 200, 200, + ) + ) + ) + } +} diff --git a/src/main/kotlin/features/inventory/storageoverlay/StorageBackingHandle.kt b/src/main/kotlin/features/inventory/storageoverlay/StorageBackingHandle.kt new file mode 100644 index 0000000..1015578 --- /dev/null +++ b/src/main/kotlin/features/inventory/storageoverlay/StorageBackingHandle.kt @@ -0,0 +1,53 @@ + + +package moe.nea.firmament.features.inventory.storageoverlay + +import net.minecraft.client.gui.screen.Screen +import net.minecraft.client.gui.screen.ingame.GenericContainerScreen +import net.minecraft.screen.GenericContainerScreenHandler +import moe.nea.firmament.util.ifMatches +import moe.nea.firmament.util.unformattedString + +/** + * A handle representing the state of the "server side" screens. + */ +sealed interface StorageBackingHandle { + + sealed interface HasBackingScreen { + val handler: GenericContainerScreenHandler + } + + /** + * The main storage overview is open. Clicking on a slot will open that page. This page is accessible via `/storage` + */ + data class Overview(override val handler: GenericContainerScreenHandler) : StorageBackingHandle, HasBackingScreen + + /** + * An individual storage page is open. This may be a backpack or an enderchest page. This page is accessible via + * the [Overview] or via `/ec ` for enderchest pages. + */ + data class Page(override val handler: GenericContainerScreenHandler, val storagePageSlot: StoragePageSlot) : + StorageBackingHandle, HasBackingScreen + + companion object { + private val enderChestName = "^Ender Chest \\(([1-9])/[1-9]\\)$".toRegex() + private val backPackName = "^.+Backpack \\(Slot #([0-9]+)\\)$".toRegex() + + /** + * Parse a screen into a [StorageBackingHandle]. If this returns null it means that the screen is not + * representable as a [StorageBackingHandle], meaning another screen is open, for example the enderchest icon + * selection screen. + */ + fun fromScreen(screen: Screen?): StorageBackingHandle? { + if (screen == null) return null + if (screen !is GenericContainerScreen) return null + val title = screen.title.unformattedString + if (title == "Storage") return Overview(screen.screenHandler) + return title.ifMatches(enderChestName) { + Page(screen.screenHandler, StoragePageSlot.ofEnderChestPage(it.groupValues[1].toInt())) + } ?: title.ifMatches(backPackName) { + Page(screen.screenHandler, StoragePageSlot.ofBackPackPage(it.groupValues[1].toInt())) + } + } + } +} diff --git a/src/main/kotlin/features/inventory/storageoverlay/StorageData.kt b/src/main/kotlin/features/inventory/storageoverlay/StorageData.kt new file mode 100644 index 0000000..7555c56 --- /dev/null +++ b/src/main/kotlin/features/inventory/storageoverlay/StorageData.kt @@ -0,0 +1,21 @@ + + +@file:UseSerializers(SortedMapSerializer::class) +package moe.nea.firmament.features.inventory.storageoverlay + +import java.util.SortedMap +import kotlinx.serialization.Serializable +import kotlinx.serialization.UseSerializers +import moe.nea.firmament.util.SortedMapSerializer + +@Serializable +data class StorageData( + val storageInventories: SortedMap = sortedMapOf() +) { + @Serializable + data class StorageInventory( + var title: String, + val slot: StoragePageSlot, + var inventory: VirtualInventory?, + ) +} diff --git a/src/main/kotlin/features/inventory/storageoverlay/StorageOverlay.kt b/src/main/kotlin/features/inventory/storageoverlay/StorageOverlay.kt new file mode 100644 index 0000000..b615c73 --- /dev/null +++ b/src/main/kotlin/features/inventory/storageoverlay/StorageOverlay.kt @@ -0,0 +1,154 @@ + + +package moe.nea.firmament.features.inventory.storageoverlay + +import java.util.SortedMap +import kotlinx.serialization.serializer +import net.minecraft.client.gui.screen.ingame.GenericContainerScreen +import net.minecraft.client.gui.screen.ingame.HandledScreen +import net.minecraft.entity.player.PlayerInventory +import net.minecraft.item.Items +import net.minecraft.network.packet.c2s.play.CloseHandledScreenC2SPacket +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.events.ScreenChangeEvent +import moe.nea.firmament.events.SlotClickEvent +import moe.nea.firmament.events.TickEvent +import moe.nea.firmament.features.FirmamentFeature +import moe.nea.firmament.gui.config.ManagedConfig +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.customgui.customGui +import moe.nea.firmament.util.data.ProfileSpecificDataHolder + +object StorageOverlay : FirmamentFeature { + + + object Data : ProfileSpecificDataHolder(serializer(), "storage-data", ::StorageData) + + override val identifier: String + get() = "storage-overlay" + + object TConfig : ManagedConfig(identifier) { + val alwaysReplace by toggle("always-replace") { true } + val columns by integer("rows", 1, 10) { 3 } + val scrollSpeed by integer("scroll-speed", 1, 50) { 10 } + val inverseScroll by toggle("inverse-scroll") { false } + val padding by integer("padding", 1, 20) { 5 } + val margin by integer("margin", 1, 60) { 20 } + } + + fun adjustScrollSpeed(amount: Double): Double { + return amount * TConfig.scrollSpeed * (if (TConfig.inverseScroll) 1 else -1) + } + + override val config: TConfig + get() = TConfig + + var lastStorageOverlay: StorageOverviewScreen? = null + var skipNextStorageOverlayBackflip = false + var currentHandler: StorageBackingHandle? = null + + @Subscribe + fun onTick(event: TickEvent) { + rememberContent(currentHandler ?: return) + } + + @Subscribe + fun onClick(event: SlotClickEvent) { + if (lastStorageOverlay != null && event.slot.inventory !is PlayerInventory && event.slot.index < 9 + && event.stack.item != Items.BLACK_STAINED_GLASS_PANE + ) { + skipNextStorageOverlayBackflip = true + } + } + + @Subscribe + fun onScreenChange(it: ScreenChangeEvent) { + if (it.old == null && it.new == null) return + val storageOverlayScreen = it.old as? StorageOverlayScreen + ?: ((it.old as? HandledScreen<*>)?.customGui as? StorageOverlayCustom)?.overview + var storageOverviewScreen = it.old as? StorageOverviewScreen + val screen = it.new as? GenericContainerScreen + val oldHandler = currentHandler + currentHandler = StorageBackingHandle.fromScreen(screen) + rememberContent(currentHandler) + if (storageOverviewScreen != null && oldHandler is StorageBackingHandle.HasBackingScreen) { + val player = MC.player + assert(player != null) + player?.networkHandler?.sendPacket(CloseHandledScreenC2SPacket(oldHandler.handler.syncId)) + if (player?.currentScreenHandler === oldHandler.handler) { + player.currentScreenHandler = player.playerScreenHandler + } + } + storageOverviewScreen = storageOverviewScreen ?: lastStorageOverlay + if (it.new == null && storageOverlayScreen != null && !storageOverlayScreen.isExiting) { + it.overrideScreen = storageOverlayScreen + return + } + if (storageOverviewScreen != null + && !storageOverviewScreen.isClosing + && (currentHandler is StorageBackingHandle.Overview || currentHandler == null) + ) { + if (skipNextStorageOverlayBackflip) { + skipNextStorageOverlayBackflip = false + } else { + it.overrideScreen = storageOverviewScreen + lastStorageOverlay = null + } + return + } + screen ?: return + screen.customGui = StorageOverlayCustom( + currentHandler ?: return, + screen, + storageOverlayScreen ?: (if (TConfig.alwaysReplace) StorageOverlayScreen() else return)) + } + + fun rememberContent(handler: StorageBackingHandle?) { + handler ?: return + // TODO: Make all of these functions work on deltas / updates instead of the entire contents + val data = Data.data?.storageInventories ?: return + when (handler) { + is StorageBackingHandle.Overview -> rememberStorageOverview(handler, data) + is StorageBackingHandle.Page -> rememberPage(handler, data) + } + Data.markDirty() + } + + private fun rememberStorageOverview( + handler: StorageBackingHandle.Overview, + data: SortedMap + ) { + for ((index, stack) in handler.handler.stacks.withIndex()) { + // Ignore unloaded item stacks + if (stack.isEmpty) continue + val slot = StoragePageSlot.fromOverviewSlotIndex(index) ?: continue + val isEmpty = stack.item in StorageOverviewScreen.emptyStorageSlotItems + if (slot in data) { + if (isEmpty) + data.remove(slot) + continue + } + if (!isEmpty) { + data[slot] = StorageData.StorageInventory(slot.defaultName(), slot, null) + } + } + } + + private fun rememberPage( + handler: StorageBackingHandle.Page, + data: SortedMap + ) { + // TODO: FIXME: FIXME NOW: Definitely don't copy all of this every tick into persistence + val newStacks = + VirtualInventory(handler.handler.stacks.take(handler.handler.rows * 9).drop(9).map { it.copy() }) + data.compute(handler.storagePageSlot) { slot, existingInventory -> + (existingInventory ?: StorageData.StorageInventory( + slot.defaultName(), + slot, + null + )).also { + it.inventory = newStacks + } + } + } +} diff --git a/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayCustom.kt b/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayCustom.kt new file mode 100644 index 0000000..d0d9114 --- /dev/null +++ b/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayCustom.kt @@ -0,0 +1,98 @@ + +package moe.nea.firmament.features.inventory.storageoverlay + +import me.shedaniel.math.Point +import me.shedaniel.math.Rectangle +import net.minecraft.client.MinecraftClient +import net.minecraft.client.gui.DrawContext +import net.minecraft.client.gui.screen.ingame.GenericContainerScreen +import net.minecraft.entity.player.PlayerInventory +import net.minecraft.screen.slot.Slot +import moe.nea.firmament.mixins.accessor.AccessorHandledScreen +import moe.nea.firmament.util.customgui.CustomGui + +class StorageOverlayCustom( + val handler: StorageBackingHandle, + val screen: GenericContainerScreen, + val overview: StorageOverlayScreen, +) : CustomGui() { + override fun onVoluntaryExit(): Boolean { + overview.isExiting = true + return super.onVoluntaryExit() + } + + override fun getBounds(): List { + return overview.getBounds() + } + + override fun afterSlotRender(context: DrawContext, slot: Slot) { + if (slot.inventory !is PlayerInventory) + context.disableScissor() + } + + override fun beforeSlotRender(context: DrawContext, slot: Slot) { + if (slot.inventory !is PlayerInventory) + overview.createScissors(context) + } + + override fun onInit() { + overview.init(MinecraftClient.getInstance(), screen.width, screen.height) + overview.init() + screen as AccessorHandledScreen + screen.x_Firmament = overview.measurements.x + screen.y_Firmament = overview.measurements.y + screen.backgroundWidth_Firmament = overview.measurements.totalWidth + screen.backgroundHeight_Firmament = overview.measurements.totalHeight + } + + override fun isPointOverSlot(slot: Slot, xOffset: Int, yOffset: Int, pointX: Double, pointY: Double): Boolean { + if (!super.isPointOverSlot(slot, xOffset, yOffset, pointX, pointY)) + return false + if (slot.inventory !is PlayerInventory) { + if (!overview.getScrollPanelInner().contains(pointX, pointY)) + return false + } + return true + } + + override fun shouldDrawForeground(): Boolean { + return false + } + + override fun mouseClick(mouseX: Double, mouseY: Double, button: Int): Boolean { + return overview.mouseClicked(mouseX, mouseY, button, (handler as? StorageBackingHandle.Page)?.storagePageSlot) + } + + override fun render(drawContext: DrawContext, delta: Float, mouseX: Int, mouseY: Int) { + overview.drawBackgrounds(drawContext) + overview.drawPages(drawContext, + mouseX, + mouseY, + delta, + (handler as? StorageBackingHandle.Page)?.storagePageSlot, + screen.screenHandler.slots.take(screen.screenHandler.rows * 9).drop(9), + Point((screen as AccessorHandledScreen).x_Firmament, screen.y_Firmament)) + overview.drawScrollBar(drawContext) + } + + override fun moveSlot(slot: Slot) { + val index = slot.index + if (index in 0..<36) { + val (x, y) = overview.getPlayerInventorySlotPosition(index) + slot.x = x - (screen as AccessorHandledScreen).x_Firmament + slot.y = y - screen.y_Firmament + } else { + slot.x = -100000 + slot.y = -100000 + } + } + + override fun mouseScrolled( + mouseX: Double, + mouseY: Double, + horizontalAmount: Double, + verticalAmount: Double + ): Boolean { + return overview.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount) + } +} diff --git a/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayScreen.kt b/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayScreen.kt new file mode 100644 index 0000000..13c6974 --- /dev/null +++ b/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayScreen.kt @@ -0,0 +1,296 @@ + +package moe.nea.firmament.features.inventory.storageoverlay + +import me.shedaniel.math.Point +import me.shedaniel.math.Rectangle +import net.minecraft.client.gui.DrawContext +import net.minecraft.client.gui.screen.Screen +import net.minecraft.screen.slot.Slot +import net.minecraft.text.Text +import net.minecraft.util.Identifier +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.events.CommandEvent +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.ScreenUtil +import moe.nea.firmament.util.assertTrueOr + +class StorageOverlayScreen : Screen(Text.literal("")) { + + companion object { + val PLAYER_WIDTH = 184 + val PLAYER_HEIGHT = 91 + val PLAYER_Y_INSET = 3 + val SLOT_SIZE = 18 + val PADDING = 10 + val PAGE_WIDTH = SLOT_SIZE * 9 + val HOTBAR_X = 12 + val HOTBAR_Y = 67 + val MAIN_INVENTORY_Y = 9 + val SCROLL_BAR_WIDTH = 8 + val SCROLL_BAR_HEIGHT = 16 + } + + var isExiting: Boolean = false + var scroll: Float = 0F + var pageWidthCount = StorageOverlay.TConfig.columns + + inner class Measurements { + val innerScrollPanelWidth = PAGE_WIDTH * pageWidthCount + (pageWidthCount - 1) * PADDING + val overviewWidth = innerScrollPanelWidth + 3 * PADDING + SCROLL_BAR_WIDTH + val x = width / 2 - overviewWidth / 2 + val overviewHeight = minOf(3 * 18 * 6, height - PLAYER_HEIGHT - minOf(80, height / 10)) + val innerScrollPanelHeight = overviewHeight - PADDING * 2 + val y = height / 2 - (overviewHeight + PLAYER_HEIGHT) / 2 + val playerX = width / 2 - PLAYER_WIDTH / 2 + val playerY = y + overviewHeight - PLAYER_Y_INSET + val totalWidth = overviewWidth + val totalHeight = overviewHeight - PLAYER_Y_INSET + PLAYER_HEIGHT + } + + var measurements = Measurements() + + var lastRenderedInnerHeight = 0 + public override fun init() { + super.init() + pageWidthCount = StorageOverlay.TConfig.columns + .coerceAtMost((width - PADDING) / (PAGE_WIDTH + PADDING)) + .coerceAtLeast(1) + measurements = Measurements() + } + + override fun mouseScrolled( + mouseX: Double, + mouseY: Double, + horizontalAmount: Double, + verticalAmount: Double + ): Boolean { + scroll = (scroll + StorageOverlay.adjustScrollSpeed(verticalAmount)).toFloat() + .coerceAtMost(getMaxScroll()) + .coerceAtLeast(0F) + return true + } + + fun getMaxScroll() = lastRenderedInnerHeight.toFloat() - getScrollPanelInner().height + + val playerInventorySprite = Identifier.of("firmament:storageoverlay/player_inventory") + val upperBackgroundSprite = Identifier.of("firmament:storageoverlay/upper_background") + val slotRowSprite = Identifier.of("firmament:storageoverlay/storage_row") + val scrollbarBackground = Identifier.of("firmament:storageoverlay/scroll_bar_background") + val scrollbarKnob = Identifier.of("firmament:storageoverlay/scroll_bar_knob") + + override fun close() { + isExiting = true + super.close() + } + + override fun render(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) { + super.render(context, mouseX, mouseY, delta) + drawBackgrounds(context) + drawPages(context, mouseX, mouseY, delta, null, null, Point()) + drawScrollBar(context) + drawPlayerInventory(context, mouseX, mouseY, delta) + } + + fun getScrollbarPercentage(): Float { + return scroll / getMaxScroll() + } + + fun drawScrollBar(context: DrawContext) { + val sbRect = getScrollBarRect() + context.drawGuiTexture( + scrollbarBackground, + sbRect.minX, sbRect.minY, + sbRect.width, sbRect.height, + ) + context.drawGuiTexture( + scrollbarKnob, + sbRect.minX, sbRect.minY + (getScrollbarPercentage() * (sbRect.height - SCROLL_BAR_HEIGHT)).toInt(), + SCROLL_BAR_WIDTH, SCROLL_BAR_HEIGHT + ) + } + + fun drawBackgrounds(context: DrawContext) { + context.drawGuiTexture(upperBackgroundSprite, + measurements.x, + measurements.y, + 0, + measurements.overviewWidth, + measurements.overviewHeight) + context.drawGuiTexture(playerInventorySprite, + measurements.playerX, + measurements.playerY, + 0, + PLAYER_WIDTH, + PLAYER_HEIGHT) + } + + fun getPlayerInventorySlotPosition(int: Int): Pair { + if (int < 9) { + return Pair(measurements.playerX + int * SLOT_SIZE + HOTBAR_X, HOTBAR_Y + measurements.playerY) + } + return Pair( + measurements.playerX + (int % 9) * SLOT_SIZE + HOTBAR_X, + measurements.playerY + (int / 9 - 1) * SLOT_SIZE + MAIN_INVENTORY_Y + ) + } + + fun drawPlayerInventory(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) { + val items = MC.player?.inventory?.main ?: return + items.withIndex().forEach { (index, item) -> + val (x, y) = getPlayerInventorySlotPosition(index) + context.drawItem(item, x, y, 0) + context.drawItemInSlot(textRenderer, item, x, y) + } + } + + fun getScrollBarRect(): Rectangle { + return Rectangle(measurements.x + PADDING + measurements.innerScrollPanelWidth + PADDING, + measurements.y + PADDING, + SCROLL_BAR_WIDTH, + measurements.innerScrollPanelHeight) + } + + fun getScrollPanelInner(): Rectangle { + return Rectangle(measurements.x + PADDING, + measurements.y + PADDING, + measurements.innerScrollPanelWidth, + measurements.innerScrollPanelHeight) + } + + fun createScissors(context: DrawContext) { + val rect = getScrollPanelInner() + context.enableScissor( + rect.minX, rect.minY, + rect.maxX, rect.maxY + ) + } + + fun drawPages( + context: DrawContext, mouseX: Int, mouseY: Int, delta: Float, + excluding: StoragePageSlot?, + slots: List?, + slotOffset: Point + ) { + createScissors(context) + val data = StorageOverlay.Data.data ?: StorageData() + layoutedForEach(data) { rect, page, inventory -> + drawPage(context, + rect.x, + rect.y, + page, inventory, + if (excluding == page) slots else null, + slotOffset + ) + } + context.disableScissor() + } + + override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean { + return mouseClicked(mouseX, mouseY, button, null) + } + + fun mouseClicked(mouseX: Double, mouseY: Double, button: Int, activePage: StoragePageSlot?): Boolean { + if (getScrollPanelInner().contains(mouseX, mouseY)) { + val data = StorageOverlay.Data.data ?: StorageData() + layoutedForEach(data) { rect, page, _ -> + if (rect.contains(mouseX, mouseY) && activePage != page && button == 0) { + page.navigateTo() + return true + } + } + return false + } + val sbRect = getScrollBarRect() + if (sbRect.contains(mouseX, mouseY)) { + // TODO: support dragging of the mouse and such + val percentage = (mouseY - sbRect.getY()) / sbRect.getHeight() + scroll = (getMaxScroll() * percentage).toFloat() + mouseScrolled(0.0, 0.0, 0.0, 0.0) + return true + } + return false + } + + private inline fun layoutedForEach( + data: StorageData, + func: ( + rectangle: Rectangle, + page: StoragePageSlot, inventory: StorageData.StorageInventory, + ) -> Unit + ) { + var yOffset = -scroll.toInt() + var xOffset = 0 + var maxHeight = 0 + for ((page, inventory) in data.storageInventories.entries) { + val currentHeight = inventory.inventory?.let { it.rows * SLOT_SIZE + 4 + textRenderer.fontHeight } + ?: 18 + maxHeight = maxOf(maxHeight, currentHeight) + val rect = Rectangle( + measurements.x + PADDING + (PAGE_WIDTH + PADDING) * xOffset, + yOffset + measurements.y + PADDING, + PAGE_WIDTH, + currentHeight + ) + func(rect, page, inventory) + xOffset++ + if (xOffset >= pageWidthCount) { + yOffset += maxHeight + xOffset = 0 + maxHeight = 0 + } + } + lastRenderedInnerHeight = maxHeight + yOffset + scroll.toInt() + } + + fun drawPage( + context: DrawContext, + x: Int, + y: Int, + page: StoragePageSlot, + inventory: StorageData.StorageInventory, + slots: List?, + slotOffset: Point, + ): Int { + val inv = inventory.inventory + if (inv == null) { + context.drawGuiTexture(upperBackgroundSprite, x, y, PAGE_WIDTH, 18) + context.drawText(textRenderer, + Text.literal("TODO: open this page"), + x + 4, + y + 4, + -1, + true) + return 18 + } + assertTrueOr(slots == null || slots.size == inv.stacks.size) { return 0 } + val name = page.defaultName() + context.drawText(textRenderer, Text.literal(name), x + 4, y + 2, + if (slots == null) 0xFFFFFFFF.toInt() else 0xFFFFFF00.toInt(), true) + context.drawGuiTexture(slotRowSprite, x, y + 4 + textRenderer.fontHeight, PAGE_WIDTH, inv.rows * SLOT_SIZE) + inv.stacks.forEachIndexed { index, stack -> + val slotX = (index % 9) * SLOT_SIZE + x + 1 + val slotY = (index / 9) * SLOT_SIZE + y + 4 + textRenderer.fontHeight + 1 + if (slots == null) { + context.drawItem(stack, slotX, slotY) + context.drawItemInSlot(textRenderer, stack, slotX, slotY) + } else { + val slot = slots[index] + slot.x = slotX - slotOffset.x + slot.y = slotY - slotOffset.y + } + } + return inv.rows * SLOT_SIZE + 4 + textRenderer.fontHeight + } + + fun getBounds(): List { + return listOf( + Rectangle(measurements.x, + measurements.y, + measurements.overviewWidth, + measurements.overviewHeight), + Rectangle(measurements.playerX, + measurements.playerY, + PLAYER_WIDTH, + PLAYER_HEIGHT)) + } +} diff --git a/src/main/kotlin/features/inventory/storageoverlay/StorageOverviewScreen.kt b/src/main/kotlin/features/inventory/storageoverlay/StorageOverviewScreen.kt new file mode 100644 index 0000000..2cbd54e --- /dev/null +++ b/src/main/kotlin/features/inventory/storageoverlay/StorageOverviewScreen.kt @@ -0,0 +1,123 @@ + + +package moe.nea.firmament.features.inventory.storageoverlay + +import org.lwjgl.glfw.GLFW +import kotlin.math.max +import net.minecraft.block.Blocks +import net.minecraft.client.gui.DrawContext +import net.minecraft.client.gui.screen.Screen +import net.minecraft.item.Item +import net.minecraft.item.Items +import net.minecraft.text.Text +import net.minecraft.util.DyeColor +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.toShedaniel + +class StorageOverviewScreen() : Screen(Text.empty()) { + companion object { + val emptyStorageSlotItems = listOf( + Blocks.RED_STAINED_GLASS_PANE.asItem(), + Blocks.BROWN_STAINED_GLASS_PANE.asItem(), + Items.GRAY_DYE + ) + val pageWidth get() = 19 * 9 + } + + val content = StorageOverlay.Data.data ?: StorageData() + var isClosing = false + + var scroll = 0 + var lastRenderedHeight = 0 + + override fun render(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) { + super.render(context, mouseX, mouseY, delta) + context.fill(0, 0, width, height, 0x90000000.toInt()) + layoutedForEach { (key, value), offsetX, offsetY -> + context.matrices.push() + context.matrices.translate(offsetX.toFloat(), offsetY.toFloat(), 0F) + renderStoragePage(context, value, mouseX - offsetX, mouseY - offsetY) + context.matrices.pop() + } + } + + inline fun layoutedForEach(onEach: (data: Pair, offsetX: Int, offsetY: Int) -> Unit) { + var offsetY = 0 + var currentMaxHeight = StorageOverlay.config.margin - StorageOverlay.config.padding - scroll + var totalHeight = -currentMaxHeight + content.storageInventories.onEachIndexed { index, (key, value) -> + val pageX = (index % StorageOverlay.config.columns) + if (pageX == 0) { + currentMaxHeight += StorageOverlay.config.padding + offsetY += currentMaxHeight + totalHeight += currentMaxHeight + currentMaxHeight = 0 + } + val xPosition = + width / 2 - (StorageOverlay.config.columns * (pageWidth + StorageOverlay.config.padding) - StorageOverlay.config.padding) / 2 + pageX * (pageWidth + StorageOverlay.config.padding) + onEach(Pair(key, value), xPosition, offsetY) + val height = getStorePageHeight(value) + currentMaxHeight = max(currentMaxHeight, height) + } + lastRenderedHeight = totalHeight + currentMaxHeight + } + + override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean { + layoutedForEach { (k, p), x, y -> + val rx = mouseX - x + val ry = mouseY - y + if (rx in (0.0..pageWidth.toDouble()) && ry in (0.0..getStorePageHeight(p).toDouble())) { + close() + StorageOverlay.lastStorageOverlay = this + k.navigateTo() + return true + } + } + return super.mouseClicked(mouseX, mouseY, button) + } + + fun getStorePageHeight(page: StorageData.StorageInventory): Int { + return page.inventory?.rows?.let { it * 19 + MC.font.fontHeight + 2 } ?: 60 + } + + override fun mouseScrolled( + mouseX: Double, + mouseY: Double, + horizontalAmount: Double, + verticalAmount: Double + ): Boolean { + scroll = + (scroll + StorageOverlay.adjustScrollSpeed(verticalAmount)).toInt() + .coerceAtMost(lastRenderedHeight - height + 2 * StorageOverlay.config.margin).coerceAtLeast(0) + return true + } + + private fun renderStoragePage(context: DrawContext, page: StorageData.StorageInventory, mouseX: Int, mouseY: Int) { + context.drawText(MC.font, page.title, 2, 2, -1, true) + val inventory = page.inventory + if (inventory == null) { + // TODO: Missing texture + context.fill(0, 0, pageWidth, 60, DyeColor.RED.toShedaniel().darker(4.0).color) + context.drawCenteredTextWithShadow(MC.font, Text.literal("Not loaded yet"), pageWidth / 2, 30, -1) + return + } + + for ((index, stack) in inventory.stacks.withIndex()) { + val x = (index % 9) * 19 + val y = (index / 9) * 19 + MC.font.fontHeight + 2 + if (((mouseX - x) in 0 until 18) && ((mouseY - y) in 0 until 18)) { + context.fill(x, y, x + 18, y + 18, 0x80808080.toInt()) + } else { + context.fill(x, y, x + 18, y + 18, 0x40808080.toInt()) + } + context.drawItem(stack, x + 1, y + 1) + context.drawItemInSlot(MC.font, stack, x + 1, y + 1) + } + } + + override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean { + if (keyCode == GLFW.GLFW_KEY_ESCAPE) + isClosing = true + return super.keyPressed(keyCode, scanCode, modifiers) + } +} diff --git a/src/main/kotlin/features/inventory/storageoverlay/StoragePageSlot.kt b/src/main/kotlin/features/inventory/storageoverlay/StoragePageSlot.kt new file mode 100644 index 0000000..9259415 --- /dev/null +++ b/src/main/kotlin/features/inventory/storageoverlay/StoragePageSlot.kt @@ -0,0 +1,66 @@ + + +package moe.nea.firmament.features.inventory.storageoverlay + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import moe.nea.firmament.util.MC + +@Serializable(with = StoragePageSlot.Serializer::class) +data class StoragePageSlot(val index: Int) : Comparable { + object Serializer : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("StoragePageSlot", PrimitiveKind.INT) + + override fun deserialize(decoder: Decoder): StoragePageSlot { + return StoragePageSlot(decoder.decodeInt()) + } + + override fun serialize(encoder: Encoder, value: StoragePageSlot) { + encoder.encodeInt(value.index) + } + } + + init { + assert(index in 0 until (3 * 9)) + } + + val isEnderChest get() = index < 9 + val isBackPack get() = !isEnderChest + val slotIndexInOverviewPage get() = if (isEnderChest) index + 9 else index + 18 + fun defaultName(): String = if (isEnderChest) "Ender Chest #${index + 1}" else "Backpack #${index - 9 + 1}" + + fun navigateTo() { + if (isBackPack) { + MC.sendCommand("backpack ${index - 9 + 1}") + } else { + MC.sendCommand("enderchest ${index + 1}") + } + } + + companion object { + fun fromOverviewSlotIndex(slot: Int): StoragePageSlot? { + if (slot in 9 until 18) return StoragePageSlot(slot - 9) + if (slot in 27 until 45) return StoragePageSlot(slot - 27 + 9) + return null + } + + fun ofEnderChestPage(slot: Int): StoragePageSlot { + assert(slot in 1..9) + return StoragePageSlot(slot - 1) + } + + fun ofBackPackPage(slot: Int): StoragePageSlot { + assert(slot in 1..18) + return StoragePageSlot(slot - 1 + 9) + } + } + + override fun compareTo(other: StoragePageSlot): Int { + return this.index - other.index + } +} diff --git a/src/main/kotlin/features/inventory/storageoverlay/VirtualInventory.kt b/src/main/kotlin/features/inventory/storageoverlay/VirtualInventory.kt new file mode 100644 index 0000000..e07df8a --- /dev/null +++ b/src/main/kotlin/features/inventory/storageoverlay/VirtualInventory.kt @@ -0,0 +1,65 @@ + + +package moe.nea.firmament.features.inventory.storageoverlay + +import io.ktor.util.decodeBase64Bytes +import io.ktor.util.encodeBase64 +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import net.minecraft.item.ItemStack +import net.minecraft.nbt.NbtCompound +import net.minecraft.nbt.NbtIo +import net.minecraft.nbt.NbtList +import net.minecraft.nbt.NbtOps +import net.minecraft.nbt.NbtSizeTracker + +@Serializable(with = VirtualInventory.Serializer::class) +data class VirtualInventory( + val stacks: List +) { + val rows = stacks.size / 9 + + init { + assert(stacks.size % 9 == 0) + assert(stacks.size / 9 in 1..5) + } + + + object Serializer : KSerializer { + const val INVENTORY = "INVENTORY" + override val descriptor: SerialDescriptor + get() = PrimitiveSerialDescriptor("VirtualInventory", PrimitiveKind.STRING) + + override fun deserialize(decoder: Decoder): VirtualInventory { + val s = decoder.decodeString() + val n = NbtIo.readCompressed(ByteArrayInputStream(s.decodeBase64Bytes()), NbtSizeTracker.of(100_000_000)) + val items = n.getList(INVENTORY, NbtCompound.COMPOUND_TYPE.toInt()) + return VirtualInventory(items.map { + it as NbtCompound + if (it.isEmpty) ItemStack.EMPTY + else runCatching { + ItemStack.CODEC.parse(NbtOps.INSTANCE, it).orThrow + }.getOrElse { ItemStack.EMPTY } + }) + } + + override fun serialize(encoder: Encoder, value: VirtualInventory) { + val list = NbtList() + value.stacks.forEach { + if (it.isEmpty) list.add(NbtCompound()) + else list.add(runCatching { ItemStack.CODEC.encode(it, NbtOps.INSTANCE, NbtCompound()).orThrow } + .getOrElse { NbtCompound() }) + } + val baos = ByteArrayOutputStream() + NbtIo.writeCompressed(NbtCompound().also { it.put(INVENTORY, list) }, baos) + encoder.encodeString(baos.toByteArray().encodeBase64()) + } + } +} diff --git a/src/main/kotlin/features/mining/Histogram.kt b/src/main/kotlin/features/mining/Histogram.kt new file mode 100644 index 0000000..ed48437 --- /dev/null +++ b/src/main/kotlin/features/mining/Histogram.kt @@ -0,0 +1,81 @@ + +package moe.nea.firmament.features.mining + +import java.util.* +import kotlin.time.Duration +import moe.nea.firmament.util.TimeMark + +class Histogram( + val maxSize: Int, + val maxDuration: Duration, +) { + + data class OrderedTimestamp(val timestamp: TimeMark, val order: Int) : Comparable { + override fun compareTo(other: OrderedTimestamp): Int { + val o = timestamp.compareTo(other.timestamp) + if (o != 0) return o + return order.compareTo(other.order) + } + } + + val size: Int get() = dataPoints.size + private val dataPoints: NavigableMap = TreeMap() + + private var order = Int.MIN_VALUE + + fun record(entry: T, timestamp: TimeMark = TimeMark.now()) { + dataPoints[OrderedTimestamp(timestamp, order++)] = entry + trim() + } + + fun oldestUpdate(): TimeMark { + trim() + return if (dataPoints.isEmpty()) TimeMark.now() else dataPoints.firstKey().timestamp + } + + fun latestUpdate(): TimeMark { + trim() + return if (dataPoints.isEmpty()) TimeMark.farPast() else dataPoints.lastKey().timestamp + } + + fun averagePer(valueExtractor: (T) -> Double, perDuration: Duration): Double? { + return aggregate( + seed = 0.0, + operator = { accumulator, entry, _ -> accumulator + valueExtractor(entry) }, + finish = { sum, beginning, end -> + val timespan = end - beginning + if (timespan > perDuration) + sum / (timespan / perDuration) + else null + }) + } + + fun aggregate( + seed: V, + operator: (V, T, TimeMark) -> V, + finish: (V, TimeMark, TimeMark) -> R + ): R? { + trim() + var accumulator = seed + var min: TimeMark? = null + var max: TimeMark? = null + dataPoints.forEach { (key, value) -> + max = key.timestamp + if (min == null) + min = key.timestamp + accumulator = operator(accumulator, value, key.timestamp) + } + if (min == null) + return null + return finish(accumulator, min!!, max!!) + } + + private fun trim() { + while (maxSize < dataPoints.size) { + dataPoints.pollFirstEntry() + } + dataPoints.headMap(OrderedTimestamp(TimeMark.ago(maxDuration), Int.MAX_VALUE)).clear() + } + + +} diff --git a/src/main/kotlin/features/mining/PickaxeAbility.kt b/src/main/kotlin/features/mining/PickaxeAbility.kt new file mode 100644 index 0000000..7879f2d --- /dev/null +++ b/src/main/kotlin/features/mining/PickaxeAbility.kt @@ -0,0 +1,176 @@ + +package moe.nea.firmament.features.mining + +import java.util.regex.Pattern +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds +import net.minecraft.item.ItemStack +import net.minecraft.util.DyeColor +import net.minecraft.util.Hand +import net.minecraft.util.Identifier +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.events.HudRenderEvent +import moe.nea.firmament.events.ProcessChatEvent +import moe.nea.firmament.events.SlotClickEvent +import moe.nea.firmament.events.WorldReadyEvent +import moe.nea.firmament.features.FirmamentFeature +import moe.nea.firmament.gui.config.ManagedConfig +import moe.nea.firmament.util.DurabilityBarEvent +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.SHORT_NUMBER_FORMAT +import moe.nea.firmament.util.TIME_PATTERN +import moe.nea.firmament.util.TimeMark +import moe.nea.firmament.util.extraAttributes +import moe.nea.firmament.util.item.displayNameAccordingToNbt +import moe.nea.firmament.util.item.loreAccordingToNbt +import moe.nea.firmament.util.parseShortNumber +import moe.nea.firmament.util.parseTimePattern +import moe.nea.firmament.util.render.RenderCircleProgress +import moe.nea.firmament.util.render.lerp +import moe.nea.firmament.util.toShedaniel +import moe.nea.firmament.util.unformattedString +import moe.nea.firmament.util.useMatch + +object PickaxeAbility : FirmamentFeature { + override val identifier: String + get() = "pickaxe-info" + + + object TConfig : ManagedConfig(identifier) { + val cooldownEnabled by toggle("ability-cooldown") { true } + val cooldownScale by integer("ability-scale", 16, 64) { 16 } + val drillFuelBar by toggle("fuel-bar") { true } + } + + var lobbyJoinTime = TimeMark.farPast() + var lastUsage = mutableMapOf() + var abilityOverride: String? = null + var defaultAbilityDurations = mutableMapOf( + "Mining Speed Boost" to 120.seconds, + "Pickobulus" to 110.seconds, + "Gemstone Infusion" to 140.seconds, + "Hazardous Miner" to 140.seconds, + "Maniac Miner" to 59.seconds, + "Vein Seeker" to 60.seconds + ) + + override val config: ManagedConfig + get() = TConfig + + fun getCooldownPercentage(name: String, cooldown: Duration): Double { + val sinceLastUsage = lastUsage[name]?.passedTime() ?: Duration.INFINITE + if (sinceLastUsage < cooldown) + return sinceLastUsage / cooldown + val sinceLobbyJoin = lobbyJoinTime.passedTime() + val halfCooldown = cooldown / 2 + if (sinceLobbyJoin < halfCooldown) { + return (sinceLobbyJoin / halfCooldown) + } + return 1.0 + } + + @Subscribe + fun onSlotClick(it: SlotClickEvent) { + if (MC.screen?.title?.unformattedString == "Heart of the Mountain") { + val name = it.stack.displayNameAccordingToNbt?.unformattedString ?: return + val cooldown = it.stack.loreAccordingToNbt.firstNotNullOfOrNull { + cooldownPattern.useMatch(it.unformattedString) { + parseTimePattern(group("cooldown")) + } + } ?: return + defaultAbilityDurations[name] = cooldown + } + } + + @Subscribe + fun onDurabilityBar(it: DurabilityBarEvent) { + if (!TConfig.drillFuelBar) return + val lore = it.item.loreAccordingToNbt + if (lore.lastOrNull()?.unformattedString?.contains("DRILL") != true) return + val maxFuel = lore.firstNotNullOfOrNull { + fuelPattern.useMatch(it.unformattedString) { + parseShortNumber(group("maxFuel")) + } + } ?: return + val extra = it.item.extraAttributes + if (!extra.contains("drill_fuel")) return + val fuel = extra.getInt("drill_fuel") + val percentage = fuel / maxFuel.toFloat() + it.barOverride = DurabilityBarEvent.DurabilityBar( + lerp( + DyeColor.RED.toShedaniel(), + DyeColor.GREEN.toShedaniel(), + percentage + ), percentage + ) + } + + @Subscribe + fun onChatMessage(it: ProcessChatEvent) { + abilityUsePattern.useMatch(it.unformattedString) { + lastUsage[group("name")] = TimeMark.now() + } + abilitySwitchPattern.useMatch(it.unformattedString) { + abilityOverride = group("ability") + } + } + + @Subscribe + fun onWorldReady(event: WorldReadyEvent) { + lastUsage.clear() + lobbyJoinTime = TimeMark.now() + abilityOverride = null + } + + val abilityUsePattern = Pattern.compile("You used your (?.*) Pickaxe Ability!") + val fuelPattern = Pattern.compile("Fuel: .*/(?$SHORT_NUMBER_FORMAT)") + + data class PickaxeAbilityData( + val name: String, + val cooldown: Duration, + ) + + fun getCooldownFromLore(itemStack: ItemStack): PickaxeAbilityData? { + val lore = itemStack.loreAccordingToNbt + if (!lore.any { it.unformattedString.contains("Breaking Power") == true }) + return null + val cooldown = lore.firstNotNullOfOrNull { + cooldownPattern.useMatch(it.unformattedString) { + parseTimePattern(group("cooldown")) + } + } ?: return null + val name = lore.firstNotNullOfOrNull { + abilityPattern.useMatch(it.unformattedString) { + group("name") + } + } ?: return null + return PickaxeAbilityData(name, cooldown) + } + + + val cooldownPattern = Pattern.compile("Cooldown: (?$TIME_PATTERN)") + val abilityPattern = Pattern.compile("Ability: (?.*) {2}RIGHT CLICK") + val abilitySwitchPattern = + Pattern.compile("You selected (?.*) as your Pickaxe Ability\\. This ability will apply to all of your pickaxes!") + + + @Subscribe + fun renderHud(event: HudRenderEvent) { + if (!TConfig.cooldownEnabled) return + var ability = getCooldownFromLore(MC.player?.getStackInHand(Hand.MAIN_HAND) ?: return) ?: return + defaultAbilityDurations[ability.name] = ability.cooldown + val ao = abilityOverride + if (ao != ability.name && ao != null) { + ability = PickaxeAbilityData(ao, defaultAbilityDurations[ao] ?: 120.seconds) + } + event.context.matrices.push() + event.context.matrices.translate(MC.window.scaledWidth / 2F, MC.window.scaledHeight / 2F, 0F) + event.context.matrices.scale(TConfig.cooldownScale.toFloat(), TConfig.cooldownScale.toFloat(), 1F) + RenderCircleProgress.renderCircle( + event.context, Identifier.of("firmament", "textures/gui/circle.png"), + getCooldownPercentage(ability.name, ability.cooldown).toFloat(), + 0f, 1f, 0f, 1f + ) + event.context.matrices.pop() + } +} diff --git a/src/main/kotlin/features/mining/PristineProfitTracker.kt b/src/main/kotlin/features/mining/PristineProfitTracker.kt new file mode 100644 index 0000000..f1bc7e5 --- /dev/null +++ b/src/main/kotlin/features/mining/PristineProfitTracker.kt @@ -0,0 +1,133 @@ + +package moe.nea.firmament.features.mining + +import io.github.notenoughupdates.moulconfig.xml.Bind +import moe.nea.jarvis.api.Point +import kotlinx.serialization.Serializable +import kotlinx.serialization.serializer +import kotlin.time.Duration.Companion.seconds +import net.minecraft.text.Text +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.events.ProcessChatEvent +import moe.nea.firmament.features.FirmamentFeature +import moe.nea.firmament.gui.config.ManagedConfig +import moe.nea.firmament.gui.hud.MoulConfigHud +import moe.nea.firmament.util.BazaarPriceStrategy +import moe.nea.firmament.util.FirmFormatters.formatCommas +import moe.nea.firmament.util.SkyblockId +import moe.nea.firmament.util.data.ProfileSpecificDataHolder +import moe.nea.firmament.util.formattedString +import moe.nea.firmament.util.parseIntWithComma +import moe.nea.firmament.util.useMatch + +object PristineProfitTracker : FirmamentFeature { + override val identifier: String + get() = "pristine-profit" + + enum class GemstoneKind( + val label: String, + val flawedId: SkyblockId, + ) { + SAPPHIRE("Sapphire", SkyblockId("FLAWED_SAPPHIRE_GEM")), + RUBY("Ruby", SkyblockId("FLAWED_RUBY_GEM")), + AMETHYST("Amethyst", SkyblockId("FLAWED_AMETHYST_GEM")), + AMBER("Amber", SkyblockId("FLAWED_AMBER_GEM")), + TOPAZ("Topaz", SkyblockId("FLAWED_TOPAZ_GEM")), + JADE("Jade", SkyblockId("FLAWED_JADE_GEM")), + JASPER("Jasper", SkyblockId("FLAWED_JASPER_GEM")), + OPAL("Opal", SkyblockId("FLAWED_OPAL_GEM")), + } + + @Serializable + data class Data( + var maxMoneyPerSecond: Double = 1.0, + var maxCollectionPerSecond: Double = 1.0, + ) + + object DConfig : ProfileSpecificDataHolder(serializer(), identifier, ::Data) + + override val config: ManagedConfig? + get() = TConfig + + object TConfig : ManagedConfig(identifier) { + val timeout by duration("timeout", 0.seconds, 120.seconds) { 30.seconds } + val gui by position("position", 80, 30) { Point(0.05, 0.2) } + } + + val sellingStrategy = BazaarPriceStrategy.SELL_ORDER + + val pristineRegex = + "PRISTINE! You found . Flawed (?${ + GemstoneKind.entries.joinToString("|") { it.label } + }) Gemstone x(?[0-9,]+)!".toPattern() + + val collectionHistogram = Histogram(10000, 180.seconds) + val moneyHistogram = Histogram(10000, 180.seconds) + + object ProfitHud : MoulConfigHud("pristine_profit", TConfig.gui) { + @field:Bind + var moneyCurrent: Double = 0.0 + + @field:Bind + var moneyMax: Double = 1.0 + + @field:Bind + var moneyText = "" + + @field:Bind + var collectionCurrent = 0.0 + + @field:Bind + var collectionMax = 1.0 + + @field:Bind + var collectionText = "" + override fun shouldRender(): Boolean = collectionHistogram.latestUpdate().passedTime() < TConfig.timeout + } + + val SECONDS_PER_HOUR = 3600 + val ROUGHS_PER_FLAWED = 80 + + fun updateUi() { + val collectionPerSecond = collectionHistogram.averagePer({ it }, 1.seconds) + val moneyPerSecond = moneyHistogram.averagePer({ it }, 1.seconds) + if (collectionPerSecond == null || moneyPerSecond == null) return + ProfitHud.collectionCurrent = collectionPerSecond + ProfitHud.collectionText = Text.stringifiedTranslatable("firmament.pristine-profit.collection", + formatCommas(collectionPerSecond * SECONDS_PER_HOUR, + 1)).formattedString() + ProfitHud.moneyCurrent = moneyPerSecond + ProfitHud.moneyText = Text.stringifiedTranslatable("firmament.pristine-profit.money", + formatCommas(moneyPerSecond * SECONDS_PER_HOUR, 1)) + .formattedString() + val data = DConfig.data + if (data != null) { + if (data.maxCollectionPerSecond < collectionPerSecond && collectionHistogram.oldestUpdate() + .passedTime() > 30.seconds + ) { + data.maxCollectionPerSecond = collectionPerSecond + DConfig.markDirty() + } + if (data.maxMoneyPerSecond < moneyPerSecond && moneyHistogram.oldestUpdate().passedTime() > 30.seconds) { + data.maxMoneyPerSecond = moneyPerSecond + DConfig.markDirty() + } + ProfitHud.collectionMax = maxOf(data.maxCollectionPerSecond, collectionPerSecond) + ProfitHud.moneyMax = maxOf(data.maxMoneyPerSecond, moneyPerSecond) + } + } + + + @Subscribe + fun onMessage(it: ProcessChatEvent) { + pristineRegex.useMatch(it.unformattedString) { + val gemstoneKind = GemstoneKind.valueOf(group("kind").uppercase()) + val flawedCount = parseIntWithComma(group("count")) + val moneyAmount = sellingStrategy.getSellPrice(gemstoneKind.flawedId) * flawedCount + moneyHistogram.record(moneyAmount) + val collectionAmount = flawedCount * ROUGHS_PER_FLAWED + collectionHistogram.record(collectionAmount.toDouble()) + updateUi() + } + } +} diff --git a/src/main/kotlin/features/notifications/Notifications.kt b/src/main/kotlin/features/notifications/Notifications.kt new file mode 100644 index 0000000..8d912d1 --- /dev/null +++ b/src/main/kotlin/features/notifications/Notifications.kt @@ -0,0 +1,7 @@ + +package moe.nea.firmament.features.notifications + +import moe.nea.firmament.features.FirmamentFeature + +object Notifications { +} diff --git a/src/main/kotlin/features/texturepack/AlwaysPredicate.kt b/src/main/kotlin/features/texturepack/AlwaysPredicate.kt new file mode 100644 index 0000000..4dd28df --- /dev/null +++ b/src/main/kotlin/features/texturepack/AlwaysPredicate.kt @@ -0,0 +1,17 @@ + +package moe.nea.firmament.features.texturepack + +import com.google.gson.JsonElement +import net.minecraft.item.ItemStack + +object AlwaysPredicate : FirmamentModelPredicate { + override fun test(stack: ItemStack): Boolean { + return true + } + + object Parser : FirmamentModelPredicateParser { + override fun parse(jsonElement: JsonElement): FirmamentModelPredicate { + return AlwaysPredicate + } + } +} diff --git a/src/main/kotlin/features/texturepack/AndPredicate.kt b/src/main/kotlin/features/texturepack/AndPredicate.kt new file mode 100644 index 0000000..55a4f32 --- /dev/null +++ b/src/main/kotlin/features/texturepack/AndPredicate.kt @@ -0,0 +1,26 @@ + +package moe.nea.firmament.features.texturepack + +import com.google.gson.JsonArray +import com.google.gson.JsonElement +import com.google.gson.JsonObject +import net.minecraft.item.ItemStack + +class AndPredicate(val children: Array) : FirmamentModelPredicate { + override fun test(stack: ItemStack): Boolean { + return children.all { it.test(stack) } + } + + object Parser : FirmamentModelPredicateParser { + override fun parse(jsonElement: JsonElement): FirmamentModelPredicate { + val children = + (jsonElement as JsonArray) + .flatMap { + CustomModelOverrideParser.parsePredicates(it as JsonObject) + } + .toTypedArray() + return AndPredicate(children) + } + + } +} diff --git a/src/main/kotlin/features/texturepack/BakedModelExtra.kt b/src/main/kotlin/features/texturepack/BakedModelExtra.kt new file mode 100644 index 0000000..ae1f6d5 --- /dev/null +++ b/src/main/kotlin/features/texturepack/BakedModelExtra.kt @@ -0,0 +1,9 @@ + +package moe.nea.firmament.features.texturepack + +import net.minecraft.client.render.model.BakedModel + +interface BakedModelExtra { + fun getHeadModel_firmament(): BakedModel? + fun setHeadModel_firmament(headModel: BakedModel?) +} diff --git a/src/main/kotlin/features/texturepack/BakedOverrideData.kt b/src/main/kotlin/features/texturepack/BakedOverrideData.kt new file mode 100644 index 0000000..c012883 --- /dev/null +++ b/src/main/kotlin/features/texturepack/BakedOverrideData.kt @@ -0,0 +1,8 @@ + +package moe.nea.firmament.features.texturepack + +interface BakedOverrideData { + fun getFirmamentOverrides(): Array? + fun setFirmamentOverrides(overrides: Array?) + +} diff --git a/src/main/kotlin/features/texturepack/CustomBlockTextures.kt b/src/main/kotlin/features/texturepack/CustomBlockTextures.kt new file mode 100644 index 0000000..0f2c2e6 --- /dev/null +++ b/src/main/kotlin/features/texturepack/CustomBlockTextures.kt @@ -0,0 +1,295 @@ +@file:UseSerializers(BlockPosSerializer::class, IdentifierSerializer::class) + +package moe.nea.firmament.features.texturepack + +import java.util.concurrent.CompletableFuture +import net.fabricmc.loader.api.FabricLoader +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient +import kotlinx.serialization.UseSerializers +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.json.JsonDecoder +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.serializer +import kotlin.jvm.optionals.getOrNull +import net.minecraft.block.Block +import net.minecraft.block.BlockState +import net.minecraft.client.render.model.BakedModel +import net.minecraft.client.util.ModelIdentifier +import net.minecraft.registry.RegistryKey +import net.minecraft.registry.RegistryKeys +import net.minecraft.resource.ResourceManager +import net.minecraft.resource.SinglePreparationResourceReloader +import net.minecraft.util.Identifier +import net.minecraft.util.math.BlockPos +import net.minecraft.util.profiler.Profiler +import moe.nea.firmament.Firmament +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.events.BakeExtraModelsEvent +import moe.nea.firmament.events.EarlyResourceReloadEvent +import moe.nea.firmament.events.FinalizeResourceManagerEvent +import moe.nea.firmament.events.SkyblockServerUpdateEvent +import moe.nea.firmament.features.texturepack.CustomGlobalTextures.logger +import moe.nea.firmament.util.IdentifierSerializer +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.SBData +import moe.nea.firmament.util.SkyBlockIsland +import moe.nea.firmament.util.json.BlockPosSerializer +import moe.nea.firmament.util.json.SingletonSerializableList + + +object CustomBlockTextures { + @Serializable + data class CustomBlockOverride( + val modes: @Serializable(SingletonSerializableList::class) List, + val area: List? = null, + val replacements: Map, + ) + + @Serializable(with = Replacement.Serializer::class) + data class Replacement( + val block: Identifier, + val sound: Identifier?, + ) { + + @Transient + val blockModelIdentifier get() = ModelIdentifier(block.withPrefixedPath("block/"), "firmament") + + @Transient + val bakedModel: BakedModel by lazy(LazyThreadSafetyMode.NONE) { + MC.instance.bakedModelManager.getModel(blockModelIdentifier) + } + + @OptIn(ExperimentalSerializationApi::class) + @kotlinx.serialization.Serializer(Replacement::class) + object DefaultSerializer : KSerializer + + object Serializer : KSerializer { + val delegate = serializer() + override val descriptor: SerialDescriptor + get() = delegate.descriptor + + override fun deserialize(decoder: Decoder): Replacement { + val jsonElement = decoder.decodeSerializableValue(delegate) + if (jsonElement is JsonPrimitive) { + require(jsonElement.isString) + return Replacement(Identifier.tryParse(jsonElement.content)!!, null) + } + return (decoder as JsonDecoder).json.decodeFromJsonElement(DefaultSerializer, jsonElement) + } + + override fun serialize(encoder: Encoder, value: Replacement) { + encoder.encodeSerializableValue(DefaultSerializer, value) + } + } + } + + @Serializable + data class Area( + val min: BlockPos, + val max: BlockPos, + ) { + @Transient + val realMin = BlockPos( + minOf(min.x, max.x), + minOf(min.y, max.y), + minOf(min.z, max.z), + ) + + @Transient + val realMax = BlockPos( + maxOf(min.x, max.x), + maxOf(min.y, max.y), + maxOf(min.z, max.z), + ) + + fun roughJoin(other: Area): Area { + return Area( + BlockPos( + minOf(realMin.x, other.realMin.x), + minOf(realMin.y, other.realMin.y), + minOf(realMin.z, other.realMin.z), + ), + BlockPos( + maxOf(realMax.x, other.realMax.x), + maxOf(realMax.y, other.realMax.y), + maxOf(realMax.z, other.realMax.z), + ) + ) + } + + fun contains(blockPos: BlockPos): Boolean { + return (blockPos.x in realMin.x..realMax.x) && + (blockPos.y in realMin.y..realMax.y) && + (blockPos.z in realMin.z..realMax.z) + } + } + + data class LocationReplacements( + val lookup: Map> + ) + + data class BlockReplacement( + val checks: List?, + val replacement: Replacement, + ) { + val roughCheck by lazy(LazyThreadSafetyMode.NONE) { + if (checks == null || checks.size < 3) return@lazy null + checks.reduce { acc, next -> acc.roughJoin(next) } + } + } + + data class BakedReplacements(val data: Map) + + var allLocationReplacements: BakedReplacements = BakedReplacements(mapOf()) + var currentIslandReplacements: LocationReplacements? = null + + fun refreshReplacements() { + val location = SBData.skyblockLocation + val replacements = + if (CustomSkyBlockTextures.TConfig.enableBlockOverrides) location?.let(allLocationReplacements.data::get) + else null + val lastReplacements = currentIslandReplacements + currentIslandReplacements = replacements + if (lastReplacements != replacements) { + MC.nextTick { + MC.worldRenderer.chunks?.chunks?.forEach { + // false schedules rebuilds outside a 27 block radius to happen async + it.scheduleRebuild(false) + } + sodiumReloadTask?.run() + } + } + } + + private val sodiumReloadTask = runCatching { + Class.forName("moe.nea.firmament.compat.sodium.SodiumChunkReloader").getConstructor().newInstance() as Runnable + }.getOrElse { + if (FabricLoader.getInstance().isModLoaded("sodium")) + logger.error("Could not create sodium chunk reloader") + null + } + + + fun matchesPosition(replacement: BlockReplacement, blockPos: BlockPos?): Boolean { + if (blockPos == null) return true + val rc = replacement.roughCheck + if (rc != null && !rc.contains(blockPos)) return false + val areas = replacement.checks + if (areas != null && !areas.any { it.contains(blockPos) }) return false + return true + } + + @JvmStatic + fun getReplacementModel(block: BlockState, blockPos: BlockPos?): BakedModel? { + return getReplacement(block, blockPos)?.bakedModel + } + + @JvmStatic + fun getReplacement(block: BlockState, blockPos: BlockPos?): Replacement? { + if (isInFallback() && blockPos == null) return null + val replacements = currentIslandReplacements?.lookup?.get(block.block) ?: return null + for (replacement in replacements) { + if (replacement.checks == null || matchesPosition(replacement, blockPos)) + return replacement.replacement + } + return null + } + + + @Subscribe + fun onLocation(event: SkyblockServerUpdateEvent) { + refreshReplacements() + } + + @Volatile + var preparationFuture: CompletableFuture = CompletableFuture.completedFuture(BakedReplacements( + mapOf())) + + val insideFallbackCall = ThreadLocal.withInitial { 0 } + + @JvmStatic + fun enterFallbackCall() { + insideFallbackCall.set(insideFallbackCall.get() + 1) + } + + fun isInFallback() = insideFallbackCall.get() > 0 + + @JvmStatic + fun exitFallbackCall() { + insideFallbackCall.set(insideFallbackCall.get() - 1) + } + + @Subscribe + fun onEarlyReload(event: EarlyResourceReloadEvent) { + preparationFuture = CompletableFuture + .supplyAsync( + { prepare(event.resourceManager) }, event.preparationExecutor) + } + + @Subscribe + fun bakeExtraModels(event: BakeExtraModelsEvent) { + preparationFuture.join().data.values + .flatMap { it.lookup.values } + .flatten() + .mapTo(mutableSetOf()) { it.replacement.blockModelIdentifier } + .forEach { event.addNonItemModel(it) } + } + + private fun prepare(manager: ResourceManager): BakedReplacements { + val resources = manager.findResources("overrides/blocks") { + it.namespace == "firmskyblock" && it.path.endsWith(".json") + } + val map = mutableMapOf>>() + for ((file, resource) in resources) { + val json = + Firmament.tryDecodeJsonFromStream(resource.inputStream) + .getOrElse { ex -> + logger.error("Failed to load block texture override at $file", ex) + continue + } + for (mode in json.modes) { + val island = SkyBlockIsland.forMode(mode) + val islandMpa = map.getOrPut(island, ::mutableMapOf) + for ((blockId, replacement) in json.replacements) { + val block = MC.defaultRegistries.getWrapperOrThrow(RegistryKeys.BLOCK) + .getOptional(RegistryKey.of(RegistryKeys.BLOCK, blockId)) + .getOrNull() + if (block == null) { + logger.error("Failed to load block texture override at ${file}: unknown block '$blockId'") + continue + } + val replacements = islandMpa.getOrPut(block.value(), ::mutableListOf) + replacements.add(BlockReplacement(json.area, replacement)) + } + } + } + + return BakedReplacements(map.mapValues { LocationReplacements(it.value) }) + } + + @JvmStatic + fun patchIndigo(orig: BakedModel, pos: BlockPos, state: BlockState): BakedModel { + return getReplacementModel(state, pos) ?: orig + } + + @Subscribe + fun onStart(event: FinalizeResourceManagerEvent) { + event.resourceManager.registerReloader(object : + SinglePreparationResourceReloader() { + override fun prepare(manager: ResourceManager, profiler: Profiler): BakedReplacements { + return preparationFuture.join() + } + + override fun apply(prepared: BakedReplacements, manager: ResourceManager, profiler: Profiler?) { + allLocationReplacements = prepared + refreshReplacements() + } + }) + } +} diff --git a/src/main/kotlin/features/texturepack/CustomGlobalArmorOverrides.kt b/src/main/kotlin/features/texturepack/CustomGlobalArmorOverrides.kt new file mode 100644 index 0000000..23577ee --- /dev/null +++ b/src/main/kotlin/features/texturepack/CustomGlobalArmorOverrides.kt @@ -0,0 +1,106 @@ + +@file:UseSerializers(IdentifierSerializer::class) + +package moe.nea.firmament.features.texturepack + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient +import kotlinx.serialization.UseSerializers +import net.minecraft.item.ArmorMaterial +import net.minecraft.item.ItemStack +import net.minecraft.resource.ResourceManager +import net.minecraft.resource.SinglePreparationResourceReloader +import net.minecraft.util.Identifier +import net.minecraft.util.profiler.Profiler +import moe.nea.firmament.Firmament +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.events.FinalizeResourceManagerEvent +import moe.nea.firmament.events.subscription.SubscriptionOwner +import moe.nea.firmament.features.FirmamentFeature +import moe.nea.firmament.features.texturepack.CustomGlobalTextures.logger +import moe.nea.firmament.util.IdentifierSerializer +import moe.nea.firmament.util.IdentityCharacteristics +import moe.nea.firmament.util.computeNullableFunction +import moe.nea.firmament.util.skyBlockId + +object CustomGlobalArmorOverrides : SubscriptionOwner { + @Serializable + data class ArmorOverride( + @SerialName("item_ids") + val itemIds: List, + val layers: List, + val overrides: List = listOf(), + ) { + @Transient + val bakedLayers = bakeLayers(layers) + } + + fun bakeLayers(layers: List): List { + return layers.map { ArmorMaterial.Layer(it.identifier, it.suffix, it.tint) } + } + + @Serializable + data class ArmorOverrideLayer( + val tint: Boolean = false, + val identifier: Identifier, + val suffix: String = "", + ) + + @Serializable + data class ArmorOverrideOverride( + val predicate: FirmamentModelPredicate, + val layers: List, + ) { + @Transient + val bakedLayers = bakeLayers(layers) + } + + override val delegateFeature: FirmamentFeature + get() = CustomSkyBlockTextures + + val overrideCache = mutableMapOf, Any>() + + @JvmStatic + fun overrideArmor(stack: ItemStack): List? { + if (!CustomSkyBlockTextures.TConfig.enableArmorOverrides) return null + return overrideCache.computeNullableFunction(IdentityCharacteristics(stack)) { + val id = stack.skyBlockId ?: return@computeNullableFunction null + val override = overrides[id.neuItem] ?: return@computeNullableFunction null + for (suboverride in override.overrides) { + if (suboverride.predicate.test(stack)) { + return@computeNullableFunction suboverride.bakedLayers + } + } + return@computeNullableFunction override.bakedLayers + } + } + + var overrides: Map = mapOf() + + @Subscribe + fun onStart(event: FinalizeResourceManagerEvent) { + event.resourceManager.registerReloader(object : + SinglePreparationResourceReloader>() { + override fun prepare(manager: ResourceManager, profiler: Profiler): Map { + val overrideFiles = manager.findResources("overrides/armor_models") { + it.namespace == "firmskyblock" && it.path.endsWith(".json") + } + val overrides = overrideFiles.mapNotNull { + Firmament.tryDecodeJsonFromStream(it.value.inputStream).getOrElse { ex -> + logger.error("Failed to load armor texture override at ${it.key}", ex) + null + } + } + val associatedMap = overrides.flatMap { obj -> obj.itemIds.map { it to obj } } + .toMap() + return associatedMap + } + + override fun apply(prepared: Map, manager: ResourceManager, profiler: Profiler) { + overrides = prepared + } + }) + } + +} diff --git a/src/main/kotlin/features/texturepack/CustomGlobalTextures.kt b/src/main/kotlin/features/texturepack/CustomGlobalTextures.kt new file mode 100644 index 0000000..d64c844 --- /dev/null +++ b/src/main/kotlin/features/texturepack/CustomGlobalTextures.kt @@ -0,0 +1,167 @@ + +@file:UseSerializers(IdentifierSerializer::class, CustomModelOverrideParser.FirmamentRootPredicateSerializer::class) + +package moe.nea.firmament.features.texturepack + + +import java.util.concurrent.CompletableFuture +import org.slf4j.LoggerFactory +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable +import kotlinx.serialization.Serializable +import kotlinx.serialization.UseSerializers +import kotlin.jvm.optionals.getOrNull +import net.minecraft.client.render.item.ItemModels +import net.minecraft.client.render.model.BakedModel +import net.minecraft.client.util.ModelIdentifier +import net.minecraft.item.ItemStack +import net.minecraft.resource.ResourceManager +import net.minecraft.resource.SinglePreparationResourceReloader +import net.minecraft.text.Text +import net.minecraft.util.Identifier +import net.minecraft.util.profiler.Profiler +import moe.nea.firmament.Firmament +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.events.BakeExtraModelsEvent +import moe.nea.firmament.events.EarlyResourceReloadEvent +import moe.nea.firmament.events.FinalizeResourceManagerEvent +import moe.nea.firmament.events.ScreenChangeEvent +import moe.nea.firmament.events.subscription.SubscriptionOwner +import moe.nea.firmament.features.FirmamentFeature +import moe.nea.firmament.util.IdentifierSerializer +import moe.nea.firmament.util.IdentityCharacteristics +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.computeNullableFunction +import moe.nea.firmament.util.json.SingletonSerializableList +import moe.nea.firmament.util.runNull + +object CustomGlobalTextures : SinglePreparationResourceReloader(), + SubscriptionOwner { + override val delegateFeature: FirmamentFeature + get() = CustomSkyBlockTextures + + class CustomGuiTextureOverride( + val classes: List + ) + + @Serializable + data class GlobalItemOverride( + val screen: @Serializable(SingletonSerializableList::class) List, + val model: Identifier, + val predicate: FirmamentModelPredicate, + ) + + @Serializable + data class ScreenFilter( + val title: StringMatcher, + ) + + data class ItemOverrideCollection( + val screenFilter: ScreenFilter, + val overrides: List, + ) + + @Subscribe + fun onStart(event: FinalizeResourceManagerEvent) { + MC.resourceManager.registerReloader(this) + } + + @Subscribe + fun onEarlyReload(event: EarlyResourceReloadEvent) { + preparationFuture = CompletableFuture + .supplyAsync( + { + prepare(event.resourceManager) + }, event.preparationExecutor) + } + + @Subscribe + fun onBakeModels(event: BakeExtraModelsEvent) { + for (guiClassOverride in preparationFuture.join().classes) { + for (override in guiClassOverride.overrides) { + event.addItemModel(ModelIdentifier(override.model, "inventory")) + } + } + } + + @Volatile + var preparationFuture: CompletableFuture = CompletableFuture.completedFuture( + CustomGuiTextureOverride(listOf())) + + override fun prepare(manager: ResourceManager?, profiler: Profiler?): CustomGuiTextureOverride { + return preparationFuture.join() + } + + override fun apply(prepared: CustomGuiTextureOverride, manager: ResourceManager?, profiler: Profiler?) { + this.guiClassOverrides = prepared + } + + val logger = LoggerFactory.getLogger(CustomGlobalTextures::class.java) + fun prepare(manager: ResourceManager): CustomGuiTextureOverride { + val overrideResources = + manager.findResources("overrides/item") { it.namespace == "firmskyblock" && it.path.endsWith(".json") } + .mapNotNull { + Firmament.tryDecodeJsonFromStream(it.value.inputStream).getOrElse { ex -> + logger.error("Failed to load global item override at ${it.key}", ex) + null + } + } + + val byGuiClass = overrideResources.flatMap { override -> override.screen.toSet().map { it to override } } + .groupBy { it.first } + val guiClasses = byGuiClass.entries + .mapNotNull { + val key = it.key + val guiClassResource = + manager.getResource(Identifier.of(key.namespace, "filters/screen/${key.path}.json")) + .getOrNull() + ?: return@mapNotNull runNull { + logger.error("Failed to locate screen filter at $key") + } + val screenFilter = + Firmament.tryDecodeJsonFromStream(guiClassResource.inputStream) + .getOrElse { ex -> + logger.error("Failed to load screen filter at $key", ex) + return@mapNotNull null + } + ItemOverrideCollection(screenFilter, it.value.map { it.second }) + } + logger.info("Loaded ${overrideResources.size} global item overrides") + return CustomGuiTextureOverride(guiClasses) + } + + var guiClassOverrides = CustomGuiTextureOverride(listOf()) + + var matchingOverrides: Set = setOf() + + @Subscribe + fun onOpenGui(event: ScreenChangeEvent) { + val newTitle = event.new?.title ?: Text.empty() + matchingOverrides = guiClassOverrides.classes + .filterTo(mutableSetOf()) { it.screenFilter.title.matches(newTitle) } + } + + val overrideCache = mutableMapOf, Any>() + + @JvmStatic + fun replaceGlobalModel( + models: ItemModels, + stack: ItemStack, + cir: CallbackInfoReturnable + ) { + val value = overrideCache.computeNullableFunction(IdentityCharacteristics(stack)) { + for (guiClassOverride in matchingOverrides) { + for (override in guiClassOverride.overrides) { + if (override.predicate.test(stack)) { + return@computeNullableFunction models.modelManager.getModel( + ModelIdentifier(override.model, "inventory")) + } + } + } + null + } + if (value != null) + cir.returnValue = value + } + + +} diff --git a/src/main/kotlin/features/texturepack/CustomModelOverrideParser.kt b/src/main/kotlin/features/texturepack/CustomModelOverrideParser.kt new file mode 100644 index 0000000..a4e7c02 --- /dev/null +++ b/src/main/kotlin/features/texturepack/CustomModelOverrideParser.kt @@ -0,0 +1,74 @@ + +package moe.nea.firmament.features.texturepack + +import com.google.gson.JsonObject +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import net.minecraft.item.ItemStack +import net.minecraft.util.Identifier + +object CustomModelOverrideParser { + object FirmamentRootPredicateSerializer : KSerializer { + val delegateSerializer = kotlinx.serialization.json.JsonObject.serializer() + override val descriptor: SerialDescriptor + get() = SerialDescriptor("FirmamentModelRootPredicate", delegateSerializer.descriptor) + + override fun deserialize(decoder: Decoder): FirmamentModelPredicate { + val json = decoder.decodeSerializableValue(delegateSerializer).intoGson() as JsonObject + return AndPredicate(parsePredicates(json).toTypedArray()) + } + + override fun serialize(encoder: Encoder, value: FirmamentModelPredicate) { + TODO("Cannot serialize firmament predicates") + } + } + + val predicateParsers = mutableMapOf() + + + fun registerPredicateParser(name: String, parser: FirmamentModelPredicateParser) { + predicateParsers[Identifier.of("firmament", name)] = parser + } + + init { + registerPredicateParser("display_name", DisplayNamePredicate.Parser) + registerPredicateParser("lore", LorePredicate.Parser) + registerPredicateParser("all", AndPredicate.Parser) + registerPredicateParser("any", OrPredicate.Parser) + registerPredicateParser("not", NotPredicate.Parser) + registerPredicateParser("item", ItemPredicate.Parser) + registerPredicateParser("extra_attributes", ExtraAttributesPredicate.Parser) + registerPredicateParser("pet", PetPredicate.Parser) + } + + private val neverPredicate = listOf( + object : FirmamentModelPredicate { + override fun test(stack: ItemStack): Boolean { + return false + } + } + ) + + fun parsePredicates(predicates: JsonObject): List { + val parsedPredicates = mutableListOf() + for (predicateName in predicates.keySet()) { + if (!predicateName.startsWith("firmament:")) continue + val identifier = Identifier.of(predicateName) + val parser = predicateParsers[identifier] ?: return neverPredicate + val parsedPredicate = parser.parse(predicates[predicateName]) ?: return neverPredicate + parsedPredicates.add(parsedPredicate) + } + return parsedPredicates + } + + @JvmStatic + fun parseCustomModelOverrides(jsonObject: JsonObject): Array? { + val predicates = (jsonObject["predicate"] as? JsonObject) ?: return null + val parsedPredicates = parsePredicates(predicates) + if (parsedPredicates.isEmpty()) + return null + return parsedPredicates.toTypedArray() + } +} diff --git a/src/main/kotlin/features/texturepack/CustomSkyBlockTextures.kt b/src/main/kotlin/features/texturepack/CustomSkyBlockTextures.kt new file mode 100644 index 0000000..dec6046 --- /dev/null +++ b/src/main/kotlin/features/texturepack/CustomSkyBlockTextures.kt @@ -0,0 +1,114 @@ +package moe.nea.firmament.features.texturepack + +import com.mojang.authlib.minecraft.MinecraftProfileTexture +import com.mojang.authlib.properties.Property +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable +import net.minecraft.block.SkullBlock +import net.minecraft.client.MinecraftClient +import net.minecraft.client.render.RenderLayer +import net.minecraft.client.util.ModelIdentifier +import net.minecraft.component.type.ProfileComponent +import net.minecraft.util.Identifier +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.events.BakeExtraModelsEvent +import moe.nea.firmament.events.CustomItemModelEvent +import moe.nea.firmament.events.TickEvent +import moe.nea.firmament.features.FirmamentFeature +import moe.nea.firmament.gui.config.ManagedConfig +import moe.nea.firmament.util.IdentityCharacteristics +import moe.nea.firmament.util.item.decodeProfileTextureProperty +import moe.nea.firmament.util.skyBlockId + +object CustomSkyBlockTextures : FirmamentFeature { + override val identifier: String + get() = "custom-skyblock-textures" + + object TConfig : ManagedConfig(identifier) { + val enabled by toggle("enabled") { true } + val skullsEnabled by toggle("skulls-enabled") { true } + val cacheDuration by integer("cache-duration", 0, 20) { 1 } + val enableModelOverrides by toggle("model-overrides") { true } + val enableArmorOverrides by toggle("armor-overrides") { true } + val enableBlockOverrides by toggle("block-overrides") { true } + } + + override val config: ManagedConfig + get() = TConfig + + @Subscribe + fun onTick(it: TickEvent) { + if (TConfig.cacheDuration < 1 || it.tickCount % TConfig.cacheDuration == 0) { + // TODO: unify all of those caches somehow + CustomItemModelEvent.clearCache() + skullTextureCache.clear() + CustomGlobalTextures.overrideCache.clear() + CustomGlobalArmorOverrides.overrideCache.clear() + } + } + + @Subscribe + fun bakeCustomFirmModels(event: BakeExtraModelsEvent) { + val resources = + MinecraftClient.getInstance().resourceManager.findResources("models/item" + ) { it: Identifier -> + "firmskyblock" == it.namespace && it.path + .endsWith(".json") + } + for (identifier in resources.keys) { + val modelId = ModelIdentifier.ofInventoryVariant( + Identifier.of( + "firmskyblock", + identifier.path.substring( + "models/item/".length, + identifier.path.length - ".json".length), + )) + event.addItemModel(modelId) + } + } + + @Subscribe + fun onCustomModelId(it: CustomItemModelEvent) { + if (!TConfig.enabled) return + val id = it.itemStack.skyBlockId ?: return + it.overrideModel = ModelIdentifier.ofInventoryVariant(Identifier.of("firmskyblock", id.identifier.path)) + } + + private val skullTextureCache = mutableMapOf, Any>() + private val sentinelPresentInvalid = Object() + + private val mcUrlRegex = "https?://textures.minecraft.net/texture/([a-fA-F0-9]+)".toRegex() + + fun getSkullId(textureProperty: Property): String? { + val texture = decodeProfileTextureProperty(textureProperty) ?: return null + val textureUrl = + texture.textures[MinecraftProfileTexture.Type.SKIN]?.url ?: return null + val mcUrlData = mcUrlRegex.matchEntire(textureUrl) ?: return null + return mcUrlData.groupValues[1] + } + + fun getSkullTexture(profile: ProfileComponent): Identifier? { + val id = getSkullId(profile.properties["textures"].firstOrNull() ?: return null) ?: return null + return Identifier.of("firmskyblock", "textures/placedskull/$id.png") + } + + fun modifySkullTexture( + type: SkullBlock.SkullType?, + component: ProfileComponent?, + cir: CallbackInfoReturnable + ) { + if (type != SkullBlock.Type.PLAYER) return + if (!TConfig.skullsEnabled) return + if (component == null) return + val ic = IdentityCharacteristics(component) + + val n = skullTextureCache.getOrPut(ic) { + val id = getSkullTexture(component) ?: return@getOrPut sentinelPresentInvalid + if (!MinecraftClient.getInstance().resourceManager.getResource(id).isPresent) { + return@getOrPut sentinelPresentInvalid + } + return@getOrPut id + } + if (n === sentinelPresentInvalid) return + cir.returnValue = RenderLayer.getEntityTranslucent(n as Identifier) + } +} diff --git a/src/main/kotlin/features/texturepack/DisplayNamePredicate.kt b/src/main/kotlin/features/texturepack/DisplayNamePredicate.kt new file mode 100644 index 0000000..c89931e --- /dev/null +++ b/src/main/kotlin/features/texturepack/DisplayNamePredicate.kt @@ -0,0 +1,22 @@ + +package moe.nea.firmament.features.texturepack + +import com.google.gson.JsonElement +import net.minecraft.item.ItemStack +import net.minecraft.nbt.NbtElement +import net.minecraft.nbt.NbtString +import moe.nea.firmament.util.item.displayNameAccordingToNbt +import moe.nea.firmament.util.item.loreAccordingToNbt + +data class DisplayNamePredicate(val stringMatcher: StringMatcher) : FirmamentModelPredicate { + override fun test(stack: ItemStack): Boolean { + val display = stack.displayNameAccordingToNbt + return stringMatcher.matches(display) + } + + object Parser : FirmamentModelPredicateParser { + override fun parse(jsonElement: JsonElement): FirmamentModelPredicate { + return DisplayNamePredicate(StringMatcher.parse(jsonElement)) + } + } +} diff --git a/src/main/kotlin/features/texturepack/ExtraAttributesPredicate.kt b/src/main/kotlin/features/texturepack/ExtraAttributesPredicate.kt new file mode 100644 index 0000000..4114f45 --- /dev/null +++ b/src/main/kotlin/features/texturepack/ExtraAttributesPredicate.kt @@ -0,0 +1,268 @@ + +package moe.nea.firmament.features.texturepack + +import com.google.gson.JsonArray +import com.google.gson.JsonElement +import com.google.gson.JsonObject +import com.google.gson.JsonPrimitive +import net.minecraft.item.ItemStack +import net.minecraft.nbt.NbtByte +import net.minecraft.nbt.NbtCompound +import net.minecraft.nbt.NbtDouble +import net.minecraft.nbt.NbtElement +import net.minecraft.nbt.NbtFloat +import net.minecraft.nbt.NbtInt +import net.minecraft.nbt.NbtList +import net.minecraft.nbt.NbtLong +import net.minecraft.nbt.NbtShort +import net.minecraft.nbt.NbtString +import moe.nea.firmament.util.extraAttributes + +fun interface NbtMatcher { + fun matches(nbt: NbtElement): Boolean + + object Parser { + fun parse(jsonElement: JsonElement): NbtMatcher? { + if (jsonElement is JsonPrimitive) { + if (jsonElement.isString) { + val string = jsonElement.asString + return MatchStringExact(string) + } + if (jsonElement.isNumber) { + return MatchNumberExact(jsonElement.asLong) //TODO: parse generic number + } + } + if (jsonElement is JsonObject) { + var encounteredParser: NbtMatcher? = null + for (entry in ExclusiveParserType.entries) { + val data = jsonElement[entry.key] ?: continue + if (encounteredParser != null) { + // TODO: warn + return null + } + encounteredParser = entry.parse(data) ?: return null + } + return encounteredParser + } + return null + } + + enum class ExclusiveParserType(val key: String) { + STRING("string") { + override fun parse(element: JsonElement): NbtMatcher? { + return MatchString(StringMatcher.parse(element)) + } + }, + INT("int") { + override fun parse(element: JsonElement): NbtMatcher? { + return parseGenericNumber(element, + { it.asInt }, + { (it as? NbtInt)?.intValue() }, + { a, b -> + if (a == b) Comparison.EQUAL + else if (a < b) Comparison.LESS_THAN + else Comparison.GREATER + }) + } + }, + FLOAT("float") { + override fun parse(element: JsonElement): NbtMatcher? { + return parseGenericNumber(element, + { it.asFloat }, + { (it as? NbtFloat)?.floatValue() }, + { a, b -> + if (a == b) Comparison.EQUAL + else if (a < b) Comparison.LESS_THAN + else Comparison.GREATER + }) + } + }, + DOUBLE("double") { + override fun parse(element: JsonElement): NbtMatcher? { + return parseGenericNumber(element, + { it.asDouble }, + { (it as? NbtDouble)?.doubleValue() }, + { a, b -> + if (a == b) Comparison.EQUAL + else if (a < b) Comparison.LESS_THAN + else Comparison.GREATER + }) + } + }, + LONG("long") { + override fun parse(element: JsonElement): NbtMatcher? { + return parseGenericNumber(element, + { it.asLong }, + { (it as? NbtLong)?.longValue() }, + { a, b -> + if (a == b) Comparison.EQUAL + else if (a < b) Comparison.LESS_THAN + else Comparison.GREATER + }) + } + }, + SHORT("short") { + override fun parse(element: JsonElement): NbtMatcher? { + return parseGenericNumber(element, + { it.asShort }, + { (it as? NbtShort)?.shortValue() }, + { a, b -> + if (a == b) Comparison.EQUAL + else if (a < b) Comparison.LESS_THAN + else Comparison.GREATER + }) + } + }, + BYTE("byte") { + override fun parse(element: JsonElement): NbtMatcher? { + return parseGenericNumber(element, + { it.asByte }, + { (it as? NbtByte)?.byteValue() }, + { a, b -> + if (a == b) Comparison.EQUAL + else if (a < b) Comparison.LESS_THAN + else Comparison.GREATER + }) + } + }, + ; + + abstract fun parse(element: JsonElement): NbtMatcher? + } + + enum class Comparison { + LESS_THAN, EQUAL, GREATER + } + + inline fun parseGenericNumber( + jsonElement: JsonElement, + primitiveExtractor: (JsonPrimitive) -> T?, + crossinline nbtExtractor: (NbtElement) -> T?, + crossinline compare: (T, T) -> Comparison + ): NbtMatcher? { + if (jsonElement is JsonPrimitive) { + val expected = primitiveExtractor(jsonElement) ?: return null + return NbtMatcher { + val actual = nbtExtractor(it) ?: return@NbtMatcher false + compare(actual, expected) == Comparison.EQUAL + } + } + if (jsonElement is JsonObject) { + val minElement = jsonElement.getAsJsonPrimitive("min") + val min = if (minElement != null) primitiveExtractor(minElement) ?: return null else null + val minExclusive = jsonElement.get("minExclusive")?.asBoolean ?: false + val maxElement = jsonElement.getAsJsonPrimitive("max") + val max = if (maxElement != null) primitiveExtractor(maxElement) ?: return null else null + val maxExclusive = jsonElement.get("maxExclusive")?.asBoolean ?: true + if (min == null && max == null) return null + return NbtMatcher { + val actual = nbtExtractor(it) ?: return@NbtMatcher false + if (max != null) { + val comp = compare(actual, max) + if (comp == Comparison.GREATER) return@NbtMatcher false + if (comp == Comparison.EQUAL && maxExclusive) return@NbtMatcher false + } + if (min != null) { + val comp = compare(actual, min) + if (comp == Comparison.LESS_THAN) return@NbtMatcher false + if (comp == Comparison.EQUAL && minExclusive) return@NbtMatcher false + } + return@NbtMatcher true + } + } + return null + + } + } + + class MatchNumberExact(val number: Long) : NbtMatcher { + override fun matches(nbt: NbtElement): Boolean { + return when (nbt) { + is NbtByte -> nbt.byteValue().toLong() == number + is NbtInt -> nbt.intValue().toLong() == number + is NbtShort -> nbt.shortValue().toLong() == number + is NbtLong -> nbt.longValue().toLong() == number + else -> false + } + } + + } + + class MatchStringExact(val string: String) : NbtMatcher { + override fun matches(nbt: NbtElement): Boolean { + return nbt is NbtString && nbt.asString() == string + } + + override fun toString(): String { + return "MatchNbtStringExactly($string)" + } + } + + class MatchString(val string: StringMatcher) : NbtMatcher { + override fun matches(nbt: NbtElement): Boolean { + return nbt is NbtString && string.matches(nbt.asString()) + } + + override fun toString(): String { + return "MatchNbtString($string)" + } + } +} + +data class ExtraAttributesPredicate( + val path: NbtPrism, + val matcher: NbtMatcher, +) : FirmamentModelPredicate { + + object Parser : FirmamentModelPredicateParser { + override fun parse(jsonElement: JsonElement): FirmamentModelPredicate? { + if (jsonElement !is JsonObject) return null + val path = jsonElement.get("path") ?: return null + val pathSegments = if (path is JsonArray) { + path.map { (it as JsonPrimitive).asString } + } else if (path is JsonPrimitive && path.isString) { + path.asString.split(".") + } else return null + val matcher = NbtMatcher.Parser.parse(jsonElement.get("match") ?: jsonElement) + ?: return null + return ExtraAttributesPredicate(NbtPrism(pathSegments), matcher) + } + } + + override fun test(stack: ItemStack): Boolean { + return path.access(stack.extraAttributes) + .any { matcher.matches(it) } + } +} + +class NbtPrism(val path: List) { + override fun toString(): String { + return "Prism($path)" + } + fun access(root: NbtElement): Collection { + var rootSet = mutableListOf(root) + var switch = mutableListOf() + for (pathSegment in path) { + if (pathSegment == ".") continue + for (element in rootSet) { + if (element is NbtList) { + if (pathSegment == "*") + switch.addAll(element) + val index = pathSegment.toIntOrNull() ?: continue + if (index !in element.indices) continue + switch.add(element[index]) + } + if (element is NbtCompound) { + if (pathSegment == "*") + element.keys.mapTo(switch) { element.get(it)!! } + switch.add(element.get(pathSegment) ?: continue) + } + } + val temp = switch + switch = rootSet + rootSet = temp + switch.clear() + } + return rootSet + } +} diff --git a/src/main/kotlin/features/texturepack/FirmamentModelPredicate.kt b/src/main/kotlin/features/texturepack/FirmamentModelPredicate.kt new file mode 100644 index 0000000..d11fec0 --- /dev/null +++ b/src/main/kotlin/features/texturepack/FirmamentModelPredicate.kt @@ -0,0 +1,8 @@ + +package moe.nea.firmament.features.texturepack + +import net.minecraft.item.ItemStack + +interface FirmamentModelPredicate { + fun test(stack: ItemStack): Boolean +} diff --git a/src/main/kotlin/features/texturepack/FirmamentModelPredicateParser.kt b/src/main/kotlin/features/texturepack/FirmamentModelPredicateParser.kt new file mode 100644 index 0000000..3ed0c67 --- /dev/null +++ b/src/main/kotlin/features/texturepack/FirmamentModelPredicateParser.kt @@ -0,0 +1,8 @@ + +package moe.nea.firmament.features.texturepack + +import com.google.gson.JsonElement + +interface FirmamentModelPredicateParser { + fun parse(jsonElement: JsonElement): FirmamentModelPredicate? +} diff --git a/src/main/kotlin/features/texturepack/ItemPredicate.kt b/src/main/kotlin/features/texturepack/ItemPredicate.kt new file mode 100644 index 0000000..4302b53 --- /dev/null +++ b/src/main/kotlin/features/texturepack/ItemPredicate.kt @@ -0,0 +1,32 @@ + +package moe.nea.firmament.features.texturepack + +import com.google.gson.JsonElement +import com.google.gson.JsonPrimitive +import kotlin.jvm.optionals.getOrNull +import net.minecraft.item.Item +import net.minecraft.item.ItemStack +import net.minecraft.registry.RegistryKey +import net.minecraft.registry.RegistryKeys +import net.minecraft.util.Identifier +import moe.nea.firmament.util.MC + +class ItemPredicate( + val item: Item +) : FirmamentModelPredicate { + override fun test(stack: ItemStack): Boolean { + return stack.item == item + } + + object Parser : FirmamentModelPredicateParser { + override fun parse(jsonElement: JsonElement): ItemPredicate? { + if (jsonElement is JsonPrimitive && jsonElement.isString) { + val itemKey = RegistryKey.of(RegistryKeys.ITEM, + Identifier.tryParse(jsonElement.asString) + ?: return null) + return ItemPredicate(MC.defaultItems.getOptional(itemKey).getOrNull()?.value() ?: return null) + } + return null + } + } +} diff --git a/src/main/kotlin/features/texturepack/JsonUnbakedModelFirmExtra.kt b/src/main/kotlin/features/texturepack/JsonUnbakedModelFirmExtra.kt new file mode 100644 index 0000000..ab9e27d --- /dev/null +++ b/src/main/kotlin/features/texturepack/JsonUnbakedModelFirmExtra.kt @@ -0,0 +1,10 @@ + +package moe.nea.firmament.features.texturepack + +import net.minecraft.util.Identifier + +interface JsonUnbakedModelFirmExtra { + + fun setHeadModel_firmament(identifier: Identifier?) + fun getHeadModel_firmament(): Identifier? +} diff --git a/src/main/kotlin/features/texturepack/LorePredicate.kt b/src/main/kotlin/features/texturepack/LorePredicate.kt new file mode 100644 index 0000000..13e3974 --- /dev/null +++ b/src/main/kotlin/features/texturepack/LorePredicate.kt @@ -0,0 +1,19 @@ + +package moe.nea.firmament.features.texturepack + +import com.google.gson.JsonElement +import net.minecraft.item.ItemStack +import moe.nea.firmament.util.item.loreAccordingToNbt + +class LorePredicate(val matcher: StringMatcher) : FirmamentModelPredicate { + object Parser : FirmamentModelPredicateParser { + override fun parse(jsonElement: JsonElement): FirmamentModelPredicate { + return LorePredicate(StringMatcher.parse(jsonElement)) + } + } + + override fun test(stack: ItemStack): Boolean { + val lore = stack.loreAccordingToNbt + return lore.any { matcher.matches(it) } + } +} diff --git a/src/main/kotlin/features/texturepack/ModelOverrideData.kt b/src/main/kotlin/features/texturepack/ModelOverrideData.kt new file mode 100644 index 0000000..1585bd7 --- /dev/null +++ b/src/main/kotlin/features/texturepack/ModelOverrideData.kt @@ -0,0 +1,7 @@ + +package moe.nea.firmament.features.texturepack + +interface ModelOverrideData { + fun getFirmamentOverrides(): Array? + fun setFirmamentOverrides(overrides: Array?) +} diff --git a/src/main/kotlin/features/texturepack/ModelOverrideFilterSet.kt b/src/main/kotlin/features/texturepack/ModelOverrideFilterSet.kt new file mode 100644 index 0000000..4ef8d06 --- /dev/null +++ b/src/main/kotlin/features/texturepack/ModelOverrideFilterSet.kt @@ -0,0 +1,19 @@ + +package moe.nea.firmament.features.texturepack + +import com.google.gson.JsonElement +import moe.nea.firmament.util.filter.IteratorFilterSet + +class ModelOverrideFilterSet(original: java.util.Set>) : + IteratorFilterSet>(original) { + companion object { + @JvmStatic + fun createFilterSet(set: java.util.Set<*>): java.util.Set<*> { + return ModelOverrideFilterSet(set as java.util.Set>) as java.util.Set<*> + } + } + + override fun shouldKeepElement(element: Map.Entry): Boolean { + return !element.key.startsWith("firmament:") + } +} diff --git a/src/main/kotlin/features/texturepack/NotPredicate.kt b/src/main/kotlin/features/texturepack/NotPredicate.kt new file mode 100644 index 0000000..ecd67c3 --- /dev/null +++ b/src/main/kotlin/features/texturepack/NotPredicate.kt @@ -0,0 +1,18 @@ + +package moe.nea.firmament.features.texturepack + +import com.google.gson.JsonElement +import com.google.gson.JsonObject +import net.minecraft.item.ItemStack + +class NotPredicate(val children: Array) : FirmamentModelPredicate { + override fun test(stack: ItemStack): Boolean { + return children.none { it.test(stack) } + } + + object Parser : FirmamentModelPredicateParser { + override fun parse(jsonElement: JsonElement): FirmamentModelPredicate { + return NotPredicate(CustomModelOverrideParser.parsePredicates(jsonElement as JsonObject).toTypedArray()) + } + } +} diff --git a/src/main/kotlin/features/texturepack/NumberMatcher.kt b/src/main/kotlin/features/texturepack/NumberMatcher.kt new file mode 100644 index 0000000..7e6665f --- /dev/null +++ b/src/main/kotlin/features/texturepack/NumberMatcher.kt @@ -0,0 +1,125 @@ + +package moe.nea.firmament.features.texturepack + +import com.google.gson.JsonElement +import com.google.gson.JsonPrimitive +import moe.nea.firmament.util.useMatch + +abstract class NumberMatcher { + abstract fun test(number: Number): Boolean + + + companion object { + fun parse(jsonElement: JsonElement): NumberMatcher? { + if (jsonElement is JsonPrimitive) { + if (jsonElement.isString) { + val string = jsonElement.asString + return parseRange(string) ?: parseOperator(string) + } + if (jsonElement.isNumber) { + val number = jsonElement.asNumber + val hasDecimals = (number.toString().contains(".")) + return MatchNumberExact(if (hasDecimals) number.toLong() else number.toDouble()) + } + } + return null + } + + private val intervalSpec = + "(?[\\[\\(])(?[0-9.]+)?,(?[0-9.]+)?(?[\\]\\)])" + .toPattern() + + fun parseRange(string: String): RangeMatcher? { + intervalSpec.useMatch(string) { + // Open in the set-theory sense, meaning does not include its end. + val beginningOpen = group("beginningOpen") == "(" + val endingOpen = group("endingOpen") == ")" + val beginning = group("beginning")?.toDouble() + val ending = group("ending")?.toDouble() + return RangeMatcher(beginning, !beginningOpen, ending, !endingOpen) + } + return null + } + + enum class Operator(val operator: String) { + LESS("<") { + override fun matches(comparisonResult: Int): Boolean { + return comparisonResult < 0 + } + }, + LESS_EQUALS("<=") { + override fun matches(comparisonResult: Int): Boolean { + return comparisonResult <= 0 + } + }, + GREATER(">") { + override fun matches(comparisonResult: Int): Boolean { + return comparisonResult > 0 + } + }, + GREATER_EQUALS(">=") { + override fun matches(comparisonResult: Int): Boolean { + return comparisonResult >= 0 + } + }, + ; + + abstract fun matches(comparisonResult: Int): Boolean + } + + private val operatorPattern = "(?${Operator.entries.joinToString("|") {it.operator}})(?[0-9.]+)".toPattern() + + fun parseOperator(string: String): OperatorMatcher? { + operatorPattern.useMatch(string) { + val operatorName = group("operator") + val operator = Operator.entries.find { it.operator == operatorName }!! + val value = group("value").toDouble() + return OperatorMatcher(operator, value) + } + return null + } + + data class OperatorMatcher(val operator: Operator, val value: Double) : NumberMatcher() { + override fun test(number: Number): Boolean { + return operator.matches(number.toDouble().compareTo(value)) + } + } + + + data class MatchNumberExact(val number: Number) : NumberMatcher() { + override fun test(number: Number): Boolean { + return when (this.number) { + is Double -> number.toDouble() == this.number.toDouble() + else -> number.toLong() == this.number.toLong() + } + } + } + + data class RangeMatcher( + val beginning: Double?, + val beginningInclusive: Boolean, + val ending: Double?, + val endingInclusive: Boolean, + ) : NumberMatcher() { + override fun test(number: Number): Boolean { + val value = number.toDouble() + if (beginning != null) { + if (beginningInclusive) { + if (value < beginning) return false + } else { + if (value <= beginning) return false + } + } + if (ending != null) { + if (endingInclusive) { + if (value > ending) return false + } else { + if (value >= ending) return false + } + } + return true + } + } + } + +} diff --git a/src/main/kotlin/features/texturepack/OrPredicate.kt b/src/main/kotlin/features/texturepack/OrPredicate.kt new file mode 100644 index 0000000..32f556b --- /dev/null +++ b/src/main/kotlin/features/texturepack/OrPredicate.kt @@ -0,0 +1,26 @@ + +package moe.nea.firmament.features.texturepack + +import com.google.gson.JsonArray +import com.google.gson.JsonElement +import com.google.gson.JsonObject +import net.minecraft.item.ItemStack + +class OrPredicate(val children: Array) : FirmamentModelPredicate { + override fun test(stack: ItemStack): Boolean { + return children.any { it.test(stack) } + } + + object Parser : FirmamentModelPredicateParser { + override fun parse(jsonElement: JsonElement): FirmamentModelPredicate { + val children = + (jsonElement as JsonArray) + .flatMap { + CustomModelOverrideParser.parsePredicates(it as JsonObject) + } + .toTypedArray() + return OrPredicate(children) + } + + } +} diff --git a/src/main/kotlin/features/texturepack/PetPredicate.kt b/src/main/kotlin/features/texturepack/PetPredicate.kt new file mode 100644 index 0000000..5e5d750 --- /dev/null +++ b/src/main/kotlin/features/texturepack/PetPredicate.kt @@ -0,0 +1,66 @@ + +package moe.nea.firmament.features.texturepack + +import com.google.gson.JsonElement +import com.google.gson.JsonObject +import net.minecraft.item.ItemStack +import moe.nea.firmament.repo.ExpLadders +import moe.nea.firmament.util.petData + +class PetPredicate( + val petId: StringMatcher?, + val tier: RarityMatcher?, + val exp: NumberMatcher?, + val candyUsed: NumberMatcher?, + val level: NumberMatcher?, +) : FirmamentModelPredicate { + + override fun test(stack: ItemStack): Boolean { + val petData = stack.petData ?: return false + if (petId != null) { + if (!petId.matches(petData.type)) return false + } + if (exp != null) { + if (!exp.test(petData.exp)) return false + } + if (candyUsed != null) { + if (!candyUsed.test(petData.candyUsed)) return false + } + if (tier != null) { + if (!tier.match(petData.tier)) return false + } + val levelData by lazy(LazyThreadSafetyMode.NONE) { + ExpLadders.getExpLadder(petData.type, petData.tier) + .getPetLevel(petData.exp) + } + if (level != null) { + if (!level.test(levelData.currentLevel)) return false + } + return true + } + + object Parser : FirmamentModelPredicateParser { + override fun parse(jsonElement: JsonElement): FirmamentModelPredicate? { + if (jsonElement.isJsonPrimitive) { + return PetPredicate(StringMatcher.Equals(jsonElement.asString, false), null, null, null, null) + } + if (jsonElement !is JsonObject) return null + val idMatcher = jsonElement["id"]?.let(StringMatcher::parse) + val expMatcher = jsonElement["exp"]?.let(NumberMatcher::parse) + val levelMatcher = jsonElement["level"]?.let(NumberMatcher::parse) + val candyMatcher = jsonElement["candyUsed"]?.let(NumberMatcher::parse) + val tierMatcher = jsonElement["tier"]?.let(RarityMatcher::parse) + return PetPredicate( + idMatcher, + tierMatcher, + expMatcher, + candyMatcher, + levelMatcher, + ) + } + } + + override fun toString(): String { + return super.toString() + } +} diff --git a/src/main/kotlin/features/texturepack/RarityMatcher.kt b/src/main/kotlin/features/texturepack/RarityMatcher.kt new file mode 100644 index 0000000..634a171 --- /dev/null +++ b/src/main/kotlin/features/texturepack/RarityMatcher.kt @@ -0,0 +1,69 @@ + +package moe.nea.firmament.features.texturepack + +import com.google.gson.JsonElement +import io.github.moulberry.repo.data.Rarity +import moe.nea.firmament.util.useMatch + +abstract class RarityMatcher { + abstract fun match(rarity: Rarity): Boolean + + companion object { + fun parse(jsonElement: JsonElement): RarityMatcher { + val string = jsonElement.asString + val range = parseRange(string) + if (range != null) return range + return Exact(Rarity.valueOf(string)) + } + + private val allRarities = Rarity.entries.joinToString("|", "(?:", ")") + private val intervalSpec = + "(?[\\[\\(])(?$allRarities)?,(?$allRarities)?(?[\\]\\)])" + .toPattern() + + fun parseRange(string: String): RangeMatcher? { + intervalSpec.useMatch(string) { + // Open in the set-theory sense, meaning does not include its end. + val beginningOpen = group("beginningOpen") == "(" + val endingOpen = group("endingOpen") == ")" + val beginning = group("beginning")?.let(Rarity::valueOf) + val ending = group("ending")?.let(Rarity::valueOf) + return RangeMatcher(beginning, !beginningOpen, ending, !endingOpen) + } + return null + } + + } + + data class Exact(val expected: Rarity) : RarityMatcher() { + override fun match(rarity: Rarity): Boolean { + return rarity == expected + } + } + + data class RangeMatcher( + val beginning: Rarity?, + val beginningInclusive: Boolean, + val ending: Rarity?, + val endingInclusive: Boolean, + ) : RarityMatcher() { + override fun match(rarity: Rarity): Boolean { + if (beginning != null) { + if (beginningInclusive) { + if (rarity < beginning) return false + } else { + if (rarity <= beginning) return false + } + } + if (ending != null) { + if (endingInclusive) { + if (rarity > ending) return false + } else { + if (rarity >= ending) return false + } + } + return true + } + } + +} diff --git a/src/main/kotlin/features/texturepack/StringMatcher.kt b/src/main/kotlin/features/texturepack/StringMatcher.kt new file mode 100644 index 0000000..5eb86ac --- /dev/null +++ b/src/main/kotlin/features/texturepack/StringMatcher.kt @@ -0,0 +1,159 @@ + +package moe.nea.firmament.features.texturepack + +import com.google.gson.JsonArray +import com.google.gson.JsonElement +import com.google.gson.JsonNull +import com.google.gson.JsonObject +import com.google.gson.JsonPrimitive +import com.google.gson.internal.LazilyParsedNumber +import java.util.function.Predicate +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import net.minecraft.nbt.NbtString +import net.minecraft.text.Text +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.removeColorCodes + +@Serializable(with = StringMatcher.Serializer::class) +interface StringMatcher { + fun matches(string: String): Boolean + fun matches(text: Text): Boolean { + return matches(text.string) + } + + fun matches(nbt: NbtString): Boolean { + val string = nbt.asString() + val jsonStart = string.indexOf('{') + val stringStart = string.indexOf('"') + val isString = stringStart >= 0 && string.subSequence(0, stringStart).isBlank() + val isJson = jsonStart >= 0 && string.subSequence(0, jsonStart).isBlank() + if (isString || isJson) + return matches(Text.Serialization.fromJson(string, MC.defaultRegistries) ?: return false) + return matches(string) + } + + class Equals(input: String, val stripColorCodes: Boolean) : StringMatcher { + private val expected = if (stripColorCodes) input.removeColorCodes() else input + override fun matches(string: String): Boolean { + return expected == (if (stripColorCodes) string.removeColorCodes() else string) + } + + override fun toString(): String { + return "Equals($expected, stripColorCodes = $stripColorCodes)" + } + } + + class Pattern(val patternWithColorCodes: String, val stripColorCodes: Boolean) : StringMatcher { + private val regex: Predicate = patternWithColorCodes.toPattern().asMatchPredicate() + override fun matches(string: String): Boolean { + return regex.test(if (stripColorCodes) string.removeColorCodes() else string) + } + + override fun toString(): String { + return "Pattern($patternWithColorCodes, stripColorCodes = $stripColorCodes)" + } + } + + object Serializer : KSerializer { + val delegateSerializer = kotlinx.serialization.json.JsonElement.serializer() + override val descriptor: SerialDescriptor + get() = SerialDescriptor("StringMatcher", delegateSerializer.descriptor) + + override fun deserialize(decoder: Decoder): StringMatcher { + val delegate = decoder.decodeSerializableValue(delegateSerializer) + val gsonDelegate = delegate.intoGson() + return parse(gsonDelegate) + } + + override fun serialize(encoder: Encoder, value: StringMatcher) { + encoder.encodeSerializableValue(delegateSerializer, Companion.serialize(value).intoKotlinJson()) + } + + } + + companion object { + fun serialize(stringMatcher: StringMatcher): JsonElement { + TODO("Cannot serialize string matchers rn") + } + + fun parse(jsonElement: JsonElement): StringMatcher { + if (jsonElement is JsonPrimitive) { + return Equals(jsonElement.asString, true) + } + if (jsonElement is JsonObject) { + val regex = jsonElement["regex"] as JsonPrimitive? + val text = jsonElement["equals"] as JsonPrimitive? + val shouldStripColor = when (val color = (jsonElement["color"] as JsonPrimitive?)?.asString) { + "preserve" -> false + "strip", null -> true + else -> error("Unknown color preservation mode: $color") + } + if ((regex == null) == (text == null)) error("Could not parse $jsonElement as string matcher") + if (regex != null) + return Pattern(regex.asString, shouldStripColor) + if (text != null) + return Equals(text.asString, shouldStripColor) + } + error("Could not parse $jsonElement as a string matcher") + } + } +} + +fun JsonElement.intoKotlinJson(): kotlinx.serialization.json.JsonElement { + when (this) { + is JsonNull -> return kotlinx.serialization.json.JsonNull + is JsonObject -> { + return kotlinx.serialization.json.JsonObject(this.entrySet() + .associate { it.key to it.value.intoKotlinJson() }) + } + + is JsonArray -> { + return kotlinx.serialization.json.JsonArray(this.map { it.intoKotlinJson() }) + } + + is JsonPrimitive -> { + if (this.isString) + return kotlinx.serialization.json.JsonPrimitive(this.asString) + if (this.isBoolean) + return kotlinx.serialization.json.JsonPrimitive(this.asBoolean) + return kotlinx.serialization.json.JsonPrimitive(this.asNumber) + } + + else -> error("Unknown json variant $this") + } +} + +fun kotlinx.serialization.json.JsonElement.intoGson(): JsonElement { + when (this) { + is kotlinx.serialization.json.JsonNull -> return JsonNull.INSTANCE + is kotlinx.serialization.json.JsonPrimitive -> { + if (this.isString) + return JsonPrimitive(this.content) + if (this.content == "true") + return JsonPrimitive(true) + if (this.content == "false") + return JsonPrimitive(false) + return JsonPrimitive(LazilyParsedNumber(this.content)) + } + + is kotlinx.serialization.json.JsonObject -> { + val obj = JsonObject() + for ((k, v) in this) { + obj.add(k, v.intoGson()) + } + return obj + } + + is kotlinx.serialization.json.JsonArray -> { + val arr = JsonArray() + for (v in this) { + arr.add(v.intoGson()) + } + return arr + } + } +} diff --git a/src/main/kotlin/features/world/FairySouls.kt b/src/main/kotlin/features/world/FairySouls.kt new file mode 100644 index 0000000..8a8291a --- /dev/null +++ b/src/main/kotlin/features/world/FairySouls.kt @@ -0,0 +1,131 @@ + + +package moe.nea.firmament.features.world + +import io.github.moulberry.repo.data.Coordinate +import kotlinx.serialization.Serializable +import kotlinx.serialization.serializer +import net.minecraft.text.Text +import net.minecraft.util.math.Vec3d +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.events.ProcessChatEvent +import moe.nea.firmament.events.SkyblockServerUpdateEvent +import moe.nea.firmament.events.WorldRenderLastEvent +import moe.nea.firmament.features.FirmamentFeature +import moe.nea.firmament.gui.config.ManagedConfig +import moe.nea.firmament.repo.RepoManager +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.SBData +import moe.nea.firmament.util.SkyBlockIsland +import moe.nea.firmament.util.blockPos +import moe.nea.firmament.util.data.ProfileSpecificDataHolder +import moe.nea.firmament.util.render.RenderInWorldContext +import moe.nea.firmament.util.render.RenderInWorldContext.Companion.renderInWorld +import moe.nea.firmament.util.unformattedString + + +object FairySouls : FirmamentFeature { + + + @Serializable + data class Data( + val foundSouls: MutableMap> = mutableMapOf() + ) + + override val config: ManagedConfig + get() = TConfig + + object DConfig : ProfileSpecificDataHolder(serializer(), "found-fairysouls", ::Data) + + + object TConfig : ManagedConfig("fairy-souls") { + val displaySouls by toggle("show") { false } + val resetSouls by button("reset") { + DConfig.data?.foundSouls?.clear() != null + updateMissingSouls() + } + } + + + override val identifier: String get() = "fairy-souls" + + val playerReach = 5 + val playerReachSquared = playerReach * playerReach + + var currentLocationName: SkyBlockIsland? = null + var currentLocationSouls: List = emptyList() + var currentMissingSouls: List = emptyList() + + fun updateMissingSouls() { + currentMissingSouls = emptyList() + val c = DConfig.data ?: return + val fi = c.foundSouls[currentLocationName] ?: setOf() + val cms = currentLocationSouls.toMutableList() + fi.asSequence().sortedDescending().filter { it in cms.indices }.forEach { cms.removeAt(it) } + currentMissingSouls = cms + } + + fun updateWorldSouls() { + currentLocationSouls = emptyList() + val loc = currentLocationName ?: return + currentLocationSouls = RepoManager.neuRepo.constants.fairySouls.soulLocations[loc.locrawMode] ?: return + } + + fun findNearestClickableSoul(): Coordinate? { + val player = MC.player ?: return null + val pos = player.pos + val location = SBData.skyblockLocation ?: return null + val soulLocations: List = + RepoManager.neuRepo.constants.fairySouls.soulLocations[location.locrawMode] ?: return null + return soulLocations + .map { it to it.blockPos.getSquaredDistance(pos) } + .filter { it.second < playerReachSquared } + .minByOrNull { it.second } + ?.first + } + + private fun markNearestSoul() { + val nearestSoul = findNearestClickableSoul() ?: return + val c = DConfig.data ?: return + val loc = currentLocationName ?: return + val idx = currentLocationSouls.indexOf(nearestSoul) + c.foundSouls.computeIfAbsent(loc) { mutableSetOf() }.add(idx) + DConfig.markDirty() + updateMissingSouls() + } + + @Subscribe + fun onWorldRender(it: WorldRenderLastEvent) { + if (!TConfig.displaySouls) return + renderInWorld(it) { + color(1F, 1F, 0F, 0.8F) + currentMissingSouls.forEach { + block(it.blockPos) + } + color(1f, 0f, 1f, 1f) + currentLocationSouls.forEach { + wireframeCube(it.blockPos) + } + } + } + + @Subscribe + fun onProcessChat(it: ProcessChatEvent) { + when (it.text.unformattedString) { + "You have already found that Fairy Soul!" -> { + markNearestSoul() + } + + "SOUL! You found a Fairy Soul!" -> { + markNearestSoul() + } + } + } + + @Subscribe + fun onLocationChange(it: SkyblockServerUpdateEvent) { + currentLocationName = it.newLocraw?.skyblockLocation + updateWorldSouls() + updateMissingSouls() + } +} diff --git a/src/main/kotlin/features/world/NPCWaypoints.kt b/src/main/kotlin/features/world/NPCWaypoints.kt new file mode 100644 index 0000000..592b8fa --- /dev/null +++ b/src/main/kotlin/features/world/NPCWaypoints.kt @@ -0,0 +1,40 @@ +package moe.nea.firmament.features.world + +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.commands.thenExecute +import moe.nea.firmament.events.CommandEvent +import moe.nea.firmament.events.ReloadRegistrationEvent +import moe.nea.firmament.util.MoulConfigUtils +import moe.nea.firmament.util.ScreenUtil + +object NPCWaypoints { + + var allNpcWaypoints = listOf() + + @Subscribe + fun onRepoReloadRegistration(event: ReloadRegistrationEvent) { + event.repo.registerReloadListener { + allNpcWaypoints = it.items.items.values + .asSequence() + .filter { !it.island.isNullOrBlank() } + .map { + NavigableWaypoint.NPCWaypoint(it) + } + .toList() + } + } + + @Subscribe + fun onOpenGui(event: CommandEvent.SubCommand) { + event.subcommand("npcs") { + thenExecute { + ScreenUtil.setScreenLater(MoulConfigUtils.loadScreen( + "npc_waypoints", + NpcWaypointGui(allNpcWaypoints), + null)) + } + } + } + + +} diff --git a/src/main/kotlin/features/world/NavigableWaypoint.kt b/src/main/kotlin/features/world/NavigableWaypoint.kt new file mode 100644 index 0000000..28a517f --- /dev/null +++ b/src/main/kotlin/features/world/NavigableWaypoint.kt @@ -0,0 +1,22 @@ +package moe.nea.firmament.features.world + +import io.github.moulberry.repo.data.NEUItem +import net.minecraft.util.math.BlockPos +import moe.nea.firmament.util.SkyBlockIsland + +abstract class NavigableWaypoint { + abstract val name: String + abstract val position: BlockPos + abstract val island: SkyBlockIsland + + data class NPCWaypoint( + val item: NEUItem, + ) : NavigableWaypoint() { + override val name: String + get() = item.displayName + override val position: BlockPos + get() = BlockPos(item.x, item.y, item.z) + override val island: SkyBlockIsland + get() = SkyBlockIsland.forMode(item.island) + } +} diff --git a/src/main/kotlin/features/world/NavigationHelper.kt b/src/main/kotlin/features/world/NavigationHelper.kt new file mode 100644 index 0000000..acdfb86 --- /dev/null +++ b/src/main/kotlin/features/world/NavigationHelper.kt @@ -0,0 +1,121 @@ +package moe.nea.firmament.features.world + +import io.github.moulberry.repo.constants.Islands +import net.minecraft.text.Text +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Position +import net.minecraft.util.math.Vec3i +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.events.SkyblockServerUpdateEvent +import moe.nea.firmament.events.TickEvent +import moe.nea.firmament.events.WorldRenderLastEvent +import moe.nea.firmament.repo.RepoManager +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.SBData +import moe.nea.firmament.util.SkyBlockIsland +import moe.nea.firmament.util.WarpUtil +import moe.nea.firmament.util.render.RenderInWorldContext + +object NavigationHelper { + var targetWaypoint: NavigableWaypoint? = null + set(value) { + field = value + recalculateRoute() + } + + var nextTeleporter: Islands.Teleporter? = null + private set + + val Islands.Teleporter.toIsland get() = SkyBlockIsland.forMode(this.getTo()) + val Islands.Teleporter.fromIsland get() = SkyBlockIsland.forMode(this.getFrom()) + val Islands.Teleporter.blockPos get() = BlockPos(x.toInt(), y.toInt(), z.toInt()) + + @Subscribe + fun onWorldSwitch(event: SkyblockServerUpdateEvent) { + recalculateRoute() + } + + fun recalculateRoute() { + val tp = targetWaypoint + val currentIsland = SBData.skyblockLocation + if (tp == null || currentIsland == null) { + nextTeleporter = null + return + } + val route = findRoute(currentIsland, tp.island, mutableSetOf()) + nextTeleporter = route?.get(0) + } + + private fun findRoute( + fromIsland: SkyBlockIsland, + targetIsland: SkyBlockIsland, + visitedIslands: MutableSet + ): MutableList? { + var shortestChain: MutableList? = null + for (it in RepoManager.neuRepo.constants.islands.teleporters) { + if (it.toIsland in visitedIslands) continue + if (it.fromIsland != fromIsland) continue + if (it.toIsland == targetIsland) return mutableListOf(it) + visitedIslands.add(fromIsland) + val nextRoute = findRoute(it.toIsland, targetIsland, visitedIslands) ?: continue + nextRoute.add(0, it) + if (shortestChain == null || shortestChain.size > nextRoute.size) { + shortestChain = nextRoute + } + visitedIslands.remove(fromIsland) + } + return shortestChain + } + + + @Subscribe + fun onMovement(event: TickEvent) { // TODO: add a movement tick event maybe? + val tp = targetWaypoint ?: return + val p = MC.player ?: return + if (p.squaredDistanceTo(tp.position.toCenterPos()) < 5 * 5) { + targetWaypoint = null + } + } + + @Subscribe + fun drawWaypoint(event: WorldRenderLastEvent) { + val tp = targetWaypoint ?: return + val nt = nextTeleporter + RenderInWorldContext.renderInWorld(event) { + if (nt != null) { + waypoint(nt.blockPos, + Text.literal("Teleporter to " + nt.toIsland.userFriendlyName), + Text.literal("(towards " + tp.name + "§f)")) + } else if (tp.island == SBData.skyblockLocation) { + waypoint(tp.position, + Text.literal(tp.name)) + } + } + } + + fun tryWarpNear() { + val tp = targetWaypoint + if (tp == null) { + MC.sendChat(Text.literal("Could not find a waypoint to warp you to. Select one first.")) + return + } + WarpUtil.teleportToNearestWarp(tp.island, tp.position.asPositionView()) + } + +} + +fun Vec3i.asPositionView(): Position { + return object : Position { + override fun getX(): Double { + return this@asPositionView.x.toDouble() + } + + override fun getY(): Double { + return this@asPositionView.y.toDouble() + } + + override fun getZ(): Double { + return this@asPositionView.z.toDouble() + } + } +} diff --git a/src/main/kotlin/features/world/NpcWaypointGui.kt b/src/main/kotlin/features/world/NpcWaypointGui.kt new file mode 100644 index 0000000..6146e50 --- /dev/null +++ b/src/main/kotlin/features/world/NpcWaypointGui.kt @@ -0,0 +1,68 @@ +package moe.nea.firmament.features.world + +import io.github.notenoughupdates.moulconfig.observer.ObservableList +import io.github.notenoughupdates.moulconfig.xml.Bind +import moe.nea.firmament.features.events.anniversity.AnniversaryFeatures.atOnce +import moe.nea.firmament.keybindings.SavedKeyBinding + +class NpcWaypointGui( + val allWaypoints: List, +) { + + data class NavigableWaypointW(val waypoint: NavigableWaypoint) { + @Bind + fun name() = waypoint.name + + @Bind + fun isSelected() = NavigationHelper.targetWaypoint == waypoint + + @Bind + fun click() { + if (SavedKeyBinding.isShiftDown()) { + NavigationHelper.targetWaypoint = waypoint + NavigationHelper.tryWarpNear() + } else if (isSelected()) { + NavigationHelper.targetWaypoint = null + } else { + NavigationHelper.targetWaypoint = waypoint + } + } + } + + @JvmField + @field:Bind + var search: String = "" + var lastSearch: String? = null + + @Bind("results") + fun results(): ObservableList { + return results + } + + @Bind + fun tick() { + if (search != lastSearch) { + updateSearch() + lastSearch = search + } + } + + val results: ObservableList = ObservableList(mutableListOf()) + + fun updateSearch() { + val split = search.split(" +".toRegex()) + results.atOnce { + results.clear() + allWaypoints.filter { waypoint -> + if (search.isBlank()) { + true + } else { + split.all { waypoint.name.contains(it, ignoreCase = true) } + } + }.mapTo(results) { + NavigableWaypointW(it) + } + } + } + +} diff --git a/src/main/kotlin/features/world/Waypoints.kt b/src/main/kotlin/features/world/Waypoints.kt new file mode 100644 index 0000000..91a06da --- /dev/null +++ b/src/main/kotlin/features/world/Waypoints.kt @@ -0,0 +1,297 @@ + + +package moe.nea.firmament.features.world + +import com.mojang.brigadier.arguments.IntegerArgumentType +import me.shedaniel.math.Color +import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource +import kotlinx.serialization.Serializable +import kotlin.collections.component1 +import kotlin.collections.component2 +import kotlin.collections.set +import kotlin.time.Duration.Companion.hours +import kotlin.time.Duration.Companion.seconds +import net.minecraft.command.argument.BlockPosArgumentType +import net.minecraft.server.command.ServerCommandSource +import net.minecraft.text.Text +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Vec3d +import moe.nea.firmament.Firmament +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.commands.get +import moe.nea.firmament.commands.thenArgument +import moe.nea.firmament.commands.thenExecute +import moe.nea.firmament.commands.thenLiteral +import moe.nea.firmament.events.CommandEvent +import moe.nea.firmament.events.ProcessChatEvent +import moe.nea.firmament.events.TickEvent +import moe.nea.firmament.events.WorldReadyEvent +import moe.nea.firmament.events.WorldRenderLastEvent +import moe.nea.firmament.features.FirmamentFeature +import moe.nea.firmament.gui.config.ManagedConfig +import moe.nea.firmament.util.ClipboardUtils +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.TimeMark +import moe.nea.firmament.util.render.RenderInWorldContext + +object Waypoints : FirmamentFeature { + override val identifier: String + get() = "waypoints" + + object TConfig : ManagedConfig(identifier) { + val tempWaypointDuration by duration("temp-waypoint-duration", 0.seconds, 1.hours) { 30.seconds } + val showIndex by toggle("show-index") { true } + val skipToNearest by toggle("skip-to-nearest") { false } + // TODO: look ahead size + } + + data class TemporaryWaypoint( + val pos: BlockPos, + val postedAt: TimeMark, + ) + + override val config get() = TConfig + + val temporaryPlayerWaypointList = mutableMapOf() + val temporaryPlayerWaypointMatcher = "(?i)x: (-?[0-9]+),? y: (-?[0-9]+),? z: (-?[0-9]+)".toPattern() + + val waypoints = mutableListOf() + var ordered = false + var orderedIndex = 0 + + @Serializable + data class ColeWeightWaypoint( + val x: Int, + val y: Int, + val z: Int, + val r: Int = 0, + val g: Int = 0, + val b: Int = 0, + ) + + @Subscribe + fun onRenderOrderedWaypoints(event: WorldRenderLastEvent) { + if (waypoints.isEmpty()) return + RenderInWorldContext.renderInWorld(event) { + if (!ordered) { + waypoints.withIndex().forEach { + color(0f, 0.3f, 0.7f, 0.5f) + block(it.value) + color(1f, 1f, 1f, 1f) + if (TConfig.showIndex) + withFacingThePlayer(it.value.toCenterPos()) { + text(Text.literal(it.index.toString())) + } + } + } else { + orderedIndex %= waypoints.size + val firstColor = Color.ofRGBA(0, 200, 40, 180) + color(firstColor) + tracer(waypoints[orderedIndex].toCenterPos(), lineWidth = 3f) + waypoints.withIndex().toList() + .wrappingWindow(orderedIndex, 3) + .zip( + listOf( + firstColor, + Color.ofRGBA(180, 200, 40, 150), + Color.ofRGBA(180, 80, 20, 140), + ) + ) + .reversed() + .forEach { (waypoint, col) -> + val (index, pos) = waypoint + color(col) + block(pos) + color(1f, 1f, 1f, 1f) + if (TConfig.showIndex) + withFacingThePlayer(pos.toCenterPos()) { + text(Text.literal(index.toString())) + } + } + } + } + } + + @Subscribe + fun onTick(event: TickEvent) { + if (waypoints.isEmpty() || !ordered) return + orderedIndex %= waypoints.size + val p = MC.player?.pos ?: return + if (TConfig.skipToNearest) { + orderedIndex = + (waypoints.withIndex().minBy { it.value.getSquaredDistance(p) }.index + 1) % waypoints.size + } else { + if (waypoints[orderedIndex].isWithinDistance(p, 3.0)) { + orderedIndex = (orderedIndex + 1) % waypoints.size + } + } + } + + @Subscribe + fun onProcessChat(it: ProcessChatEvent) { + val matcher = temporaryPlayerWaypointMatcher.matcher(it.unformattedString) + if (it.nameHeuristic != null && TConfig.tempWaypointDuration > 0.seconds && matcher.find()) { + temporaryPlayerWaypointList[it.nameHeuristic] = TemporaryWaypoint( + BlockPos( + matcher.group(1).toInt(), + matcher.group(2).toInt(), + matcher.group(3).toInt(), + ), + TimeMark.now() + ) + } + } + + @Subscribe + fun onCommand(event: CommandEvent.SubCommand) { + event.subcommand("waypoint") { + thenArgument("pos", BlockPosArgumentType.blockPos()) { pos -> + thenExecute { + val position = pos.get(this).toAbsoluteBlockPos(source.asFakeServer()) + waypoints.add(position) + source.sendFeedback( + Text.stringifiedTranslatable( + "firmament.command.waypoint.added", + position.x, + position.y, + position.z + ) + ) + } + } + } + event.subcommand("waypoints") { + thenLiteral("clear") { + thenExecute { + waypoints.clear() + source.sendFeedback(Text.translatable("firmament.command.waypoint.clear")) + } + } + thenLiteral("toggleordered") { + thenExecute { + ordered = !ordered + if (ordered) { + val p = MC.player?.pos ?: Vec3d.ZERO + orderedIndex = + waypoints.withIndex().minByOrNull { it.value.getSquaredDistance(p) }?.index ?: 0 + } + source.sendFeedback(Text.translatable("firmament.command.waypoint.ordered.toggle.$ordered")) + } + } + thenLiteral("skip") { + thenExecute { + if (ordered && waypoints.isNotEmpty()) { + orderedIndex = (orderedIndex + 1) % waypoints.size + source.sendFeedback(Text.translatable("firmament.command.waypoint.skip")) + } else { + source.sendError(Text.translatable("firmament.command.waypoint.skip.error")) + } + } + } + thenLiteral("remove") { + thenArgument("index", IntegerArgumentType.integer(0)) { indexArg -> + thenExecute { + val index = get(indexArg) + if (index in waypoints.indices) { + waypoints.removeAt(index) + source.sendFeedback(Text.stringifiedTranslatable( + "firmament.command.waypoint.remove", + index)) + } else { + source.sendError(Text.stringifiedTranslatable("firmament.command.waypoint.remove.error")) + } + } + } + } + thenLiteral("import") { + thenExecute { + val contents = ClipboardUtils.getTextContents() + val data = try { + Firmament.json.decodeFromString>(contents) + } catch (ex: Exception) { + Firmament.logger.error("Could not load waypoints from clipboard", ex) + source.sendError(Text.translatable("firmament.command.waypoint.import.error")) + return@thenExecute + } + waypoints.clear() + data.mapTo(waypoints) { BlockPos(it.x, it.y, it.z) } + source.sendFeedback( + Text.stringifiedTranslatable( + "firmament.command.waypoint.import", + data.size + ) + ) + } + } + } + } + + @Subscribe + fun onRenderTemporaryWaypoints(event: WorldRenderLastEvent) { + temporaryPlayerWaypointList.entries.removeIf { it.value.postedAt.passedTime() > TConfig.tempWaypointDuration } + if (temporaryPlayerWaypointList.isEmpty()) return + RenderInWorldContext.renderInWorld(event) { + color(1f, 1f, 0f, 1f) + temporaryPlayerWaypointList.forEach { (player, waypoint) -> + block(waypoint.pos) + } + color(1f, 1f, 1f, 1f) + temporaryPlayerWaypointList.forEach { (player, waypoint) -> + val skin = + MC.networkHandler?.listedPlayerListEntries?.find { it.profile.name == player } + ?.skinTextures + ?.texture + withFacingThePlayer(waypoint.pos.toCenterPos()) { + waypoint(waypoint.pos, Text.stringifiedTranslatable("firmament.waypoint.temporary", player)) + if (skin != null) { + matrixStack.translate(0F, -20F, 0F) + // Head front + texture( + skin, 16, 16, + 1 / 8f, 1 / 8f, + 2 / 8f, 2 / 8f, + ) + // Head overlay + texture( + skin, 16, 16, + 5 / 8f, 1 / 8f, + 6 / 8f, 2 / 8f, + ) + } + } + } + } + } + + @Subscribe + fun onWorldReady(event: WorldReadyEvent) { + temporaryPlayerWaypointList.clear() + } +} + +fun List.wrappingWindow(startIndex: Int, windowSize: Int): List { + val result = ArrayList(windowSize) + if (startIndex + windowSize < size) { + result.addAll(subList(startIndex, startIndex + windowSize)) + } else { + result.addAll(subList(startIndex, size)) + result.addAll(subList(0, minOf(windowSize - (size - startIndex), startIndex))) + } + return result +} + + +fun FabricClientCommandSource.asFakeServer(): ServerCommandSource { + val source = this + return ServerCommandSource( + source.player, + source.position, + source.rotation, + null, + 0, + "FakeServerCommandSource", + Text.literal("FakeServerCommandSource"), + null, + source.player + ) +} diff --git a/src/main/kotlin/gui/BarComponent.kt b/src/main/kotlin/gui/BarComponent.kt new file mode 100644 index 0000000..8ef0753 --- /dev/null +++ b/src/main/kotlin/gui/BarComponent.kt @@ -0,0 +1,125 @@ + +package moe.nea.firmament.gui + +import com.mojang.blaze3d.systems.RenderSystem +import io.github.notenoughupdates.moulconfig.common.MyResourceLocation +import io.github.notenoughupdates.moulconfig.common.RenderContext +import io.github.notenoughupdates.moulconfig.gui.GuiComponent +import io.github.notenoughupdates.moulconfig.gui.GuiImmediateContext +import io.github.notenoughupdates.moulconfig.observer.GetSetter +import io.github.notenoughupdates.moulconfig.platform.ModernRenderContext +import me.shedaniel.math.Color +import net.minecraft.client.gui.DrawContext +import net.minecraft.util.Identifier +import moe.nea.firmament.Firmament + +class BarComponent( + val progress: GetSetter, val total: GetSetter, + val fillColor: Color, + val emptyColor: Color, +) : GuiComponent() { + override fun getWidth(): Int { + return 80 + } + + override fun getHeight(): Int { + return 8 + } + + data class Texture( + val identifier: Identifier, + val u1: Float, val v1: Float, + val u2: Float, val v2: Float, + ) { + fun draw(context: DrawContext, x: Int, y: Int, width: Int, height: Int, color: Color) { + context.drawTexturedQuad( + identifier, + x, y, x + width, x + height, 0, + u1, u2, v1, v2, + color.red / 255F, + color.green / 255F, + color.blue / 255F, + color.alpha / 255F, + ) + } + } + + companion object { + val resource = Firmament.identifier("textures/gui/bar.png") + val left = Texture(resource, 0 / 64F, 0 / 64F, 4 / 64F, 8 / 64F) + val middle = Texture(resource, 4 / 64F, 0 / 64F, 8 / 64F, 8 / 64F) + val right = Texture(resource, 8 / 64F, 0 / 64F, 12 / 64F, 8 / 64F) + val segmentOverlay = Texture(resource, 12 / 64F, 0 / 64F, 15 / 64F, 8 / 64F) + } + + private fun drawSection( + context: DrawContext, + texture: Texture, + x: Int, + y: Int, + width: Int, + sectionStart: Double, + sectionEnd: Double + ) { + if (sectionEnd < progress.get() && width == 4) { + texture.draw(context, x, y, 4, 8, fillColor) + return + } + if (sectionStart > progress.get() && width == 4) { + texture.draw(context, x, y, 4, 8, emptyColor) + return + } + val increasePerPixel = (sectionEnd - sectionStart) / width + var valueAtPixel = sectionStart + for (i in (0 until width)) { + val newTex = + Texture(texture.identifier, texture.u1 + i / 64F, texture.v1, texture.u1 + (i + 1) / 64F, texture.v2) + newTex.draw( + context, x + i, y, 1, 8, + if (valueAtPixel < progress.get()) fillColor else emptyColor + ) + valueAtPixel += increasePerPixel + } + } + + override fun render(context: GuiImmediateContext) { + val renderContext = (context.renderContext as ModernRenderContext).drawContext + var i = 0 + val x = 0 + val y = 0 + while (i < context.width - 4) { + drawSection( + renderContext, + if (i == 0) left else middle, + x + i, y, + (context.width - (i + 4)).coerceAtMost(4), + i * total.get() / context.width, (i + 4) * total.get() / context.width + ) + i += 4 + } + drawSection( + renderContext, + right, + x + context.width - 4, + y, + 4, + (context.width - 4) * total.get() / context.width, + total.get() + ) + RenderSystem.setShaderColor(1F, 1F, 1F, 1F) + + } + +} + +fun Identifier.toMoulConfig(): MyResourceLocation { + return MyResourceLocation(this.namespace, this.path) +} + +fun RenderContext.color(color: Color) { + color(color.red, color.green, color.blue, color.alpha) +} + +fun RenderContext.color(red: Int, green: Int, blue: Int, alpha: Int) { + color(red / 255f, green / 255f, blue / 255f, alpha / 255f) +} diff --git a/src/main/kotlin/gui/FirmButtonComponent.kt b/src/main/kotlin/gui/FirmButtonComponent.kt new file mode 100644 index 0000000..82e5b05 --- /dev/null +++ b/src/main/kotlin/gui/FirmButtonComponent.kt @@ -0,0 +1,81 @@ + +package moe.nea.firmament.gui + +import io.github.notenoughupdates.moulconfig.common.MyResourceLocation +import io.github.notenoughupdates.moulconfig.deps.libninepatch.NinePatch +import io.github.notenoughupdates.moulconfig.gui.GuiComponent +import io.github.notenoughupdates.moulconfig.gui.GuiImmediateContext +import io.github.notenoughupdates.moulconfig.gui.MouseEvent +import io.github.notenoughupdates.moulconfig.gui.component.PanelComponent +import io.github.notenoughupdates.moulconfig.observer.GetSetter + + +open class FirmButtonComponent( + child: GuiComponent, + val isEnabled: GetSetter = GetSetter.constant(true), + val noBackground: Boolean = false, + val action: Runnable, +) : PanelComponent(child, if (noBackground) 0 else 2, DefaultBackgroundRenderer.TRANSPARENT) { + + /* TODO: make use of vanillas built in nine slicer */ + val hoveredBg = + NinePatch.builder(MyResourceLocation("minecraft", "textures/gui/sprites/widget/button_highlighted.png")) + .cornerSize(5) + .cornerUv(5 / 200F, 5 / 20F) + .mode(NinePatch.Mode.STRETCHING) + .build() + val unhoveredBg = NinePatch.builder(MyResourceLocation("minecraft", "textures/gui/sprites/widget/button.png")) + .cornerSize(5) + .cornerUv(5 / 200F, 5 / 20F) + .mode(NinePatch.Mode.STRETCHING) + .build() + val disabledBg = + NinePatch.builder(MyResourceLocation("minecraft", "textures/gui/sprites/widget/button_disabled.png")) + .cornerSize(5) + .cornerUv(5 / 200F, 5 / 20F) + .mode(NinePatch.Mode.STRETCHING) + .build() + val activeBg = NinePatch.builder(MyResourceLocation("firmament", "textures/gui/sprites/widget/button_active.png")) + .cornerSize(5) + .cornerUv(5 / 200F, 5 / 20F) + .mode(NinePatch.Mode.STRETCHING) + .build() + var isClicking = false + override fun mouseEvent(mouseEvent: MouseEvent, context: GuiImmediateContext): Boolean { + if (!isEnabled.get()) return false + if (isClicking) { + if (mouseEvent is MouseEvent.Click && !mouseEvent.mouseState && mouseEvent.mouseButton == 0) { + isClicking = false + if (context.isHovered) { + action.run() + } + return true + } + } + if (!context.isHovered) return false + if (mouseEvent !is MouseEvent.Click) return false + if (mouseEvent.mouseState && mouseEvent.mouseButton == 0) { + requestFocus() + isClicking = true + return true + } + return false + } + + open fun getBackground(context: GuiImmediateContext): NinePatch = + if (!isEnabled.get()) disabledBg + else if (context.isHovered || isClicking) hoveredBg + else unhoveredBg + + override fun render(context: GuiImmediateContext) { + context.renderContext.pushMatrix() + if (!noBackground) + context.renderContext.drawNinePatch( + getBackground(context), + 0f, 0f, context.width, context.height + ) + context.renderContext.translate(insets.toFloat(), insets.toFloat(), 0f) + element.render(getChildContext(context)) + context.renderContext.popMatrix() + } +} diff --git a/src/main/kotlin/gui/FirmHoverComponent.kt b/src/main/kotlin/gui/FirmHoverComponent.kt new file mode 100644 index 0000000..b1792ce --- /dev/null +++ b/src/main/kotlin/gui/FirmHoverComponent.kt @@ -0,0 +1,59 @@ +package moe.nea.firmament.gui + +import io.github.notenoughupdates.moulconfig.gui.GuiComponent +import io.github.notenoughupdates.moulconfig.gui.GuiImmediateContext +import io.github.notenoughupdates.moulconfig.gui.KeyboardEvent +import io.github.notenoughupdates.moulconfig.gui.MouseEvent +import java.util.function.BiFunction +import java.util.function.Supplier +import kotlin.time.Duration +import moe.nea.firmament.util.TimeMark + +class FirmHoverComponent( + val child: GuiComponent, + val hoverLines: Supplier>, + val hoverDelay: Duration, +) : GuiComponent() { + override fun getWidth(): Int { + return child.width + } + + override fun getHeight(): Int { + return child.height + } + + override fun foldChildren( + initial: T, + visitor: BiFunction + ): T { + return visitor.apply(child, initial) + } + + override fun render(context: GuiImmediateContext) { + if (context.isHovered && (permaHover || lastMouseMove.passedTime() > hoverDelay)) { + context.renderContext.scheduleDrawTooltip(hoverLines.get()) + permaHover = true + } else { + permaHover = false + } + if (!context.isHovered) { + lastMouseMove = TimeMark.now() + } + child.render(context) + + } + + var permaHover = false + var lastMouseMove = TimeMark.farPast() + + override fun mouseEvent(mouseEvent: MouseEvent, context: GuiImmediateContext): Boolean { + if (mouseEvent is MouseEvent.Move) { + lastMouseMove = TimeMark.now() + } + return child.mouseEvent(mouseEvent, context) + } + + override fun keyboardEvent(event: KeyboardEvent, context: GuiImmediateContext): Boolean { + return child.keyboardEvent(event, context) + } +} diff --git a/src/main/kotlin/gui/FixedComponent.kt b/src/main/kotlin/gui/FixedComponent.kt new file mode 100644 index 0000000..ae1da2d --- /dev/null +++ b/src/main/kotlin/gui/FixedComponent.kt @@ -0,0 +1,38 @@ + +package moe.nea.firmament.gui + +import io.github.notenoughupdates.moulconfig.gui.GuiComponent +import io.github.notenoughupdates.moulconfig.gui.GuiImmediateContext +import io.github.notenoughupdates.moulconfig.gui.KeyboardEvent +import io.github.notenoughupdates.moulconfig.gui.MouseEvent +import io.github.notenoughupdates.moulconfig.observer.GetSetter +import java.util.function.BiFunction + +class FixedComponent( + val fixedWidth: GetSetter?, + val fixedHeight: GetSetter?, + val component: GuiComponent, +) : GuiComponent() { + override fun getWidth(): Int = fixedWidth?.get() ?: component.width + + override fun getHeight(): Int = fixedHeight?.get() ?: component.height + + override fun foldChildren(initial: T, visitor: BiFunction): T { + return visitor.apply(component, initial) + } + + fun fixContext(context: GuiImmediateContext): GuiImmediateContext = + context.translated(0, 0, width, height) + + override fun render(context: GuiImmediateContext) { + component.render(fixContext(context)) + } + + override fun mouseEvent(mouseEvent: MouseEvent, context: GuiImmediateContext): Boolean { + return component.mouseEvent(mouseEvent, fixContext(context)) + } + + override fun keyboardEvent(event: KeyboardEvent, context: GuiImmediateContext): Boolean { + return component.keyboardEvent(event, fixContext(context)) + } +} diff --git a/src/main/kotlin/gui/ImageComponent.kt b/src/main/kotlin/gui/ImageComponent.kt new file mode 100644 index 0000000..bba7dee --- /dev/null +++ b/src/main/kotlin/gui/ImageComponent.kt @@ -0,0 +1,33 @@ +package moe.nea.firmament.gui + +import io.github.notenoughupdates.moulconfig.common.MyResourceLocation +import io.github.notenoughupdates.moulconfig.gui.GuiComponent +import io.github.notenoughupdates.moulconfig.gui.GuiImmediateContext +import java.util.function.Supplier + +class ImageComponent( + private val width: Int, + private val height: Int, + val resourceLocation: Supplier, + val u1: Float, + val u2: Float, + val v1: Float, + val v2: Float, +) : GuiComponent() { + override fun getWidth(): Int { + return width + } + + override fun getHeight(): Int { + return height + } + + override fun render(context: GuiImmediateContext) { + context.renderContext.bindTexture(resourceLocation.get()) + context.renderContext.drawTexturedRect( + 0f, 0f, + context.width.toFloat(), context.height.toFloat(), + u1, v1, u2, v2 + ) + } +} diff --git a/src/main/kotlin/gui/TickComponent.kt b/src/main/kotlin/gui/TickComponent.kt new file mode 100644 index 0000000..d1879b1 --- /dev/null +++ b/src/main/kotlin/gui/TickComponent.kt @@ -0,0 +1,18 @@ +package moe.nea.firmament.gui + +import io.github.notenoughupdates.moulconfig.gui.GuiComponent +import io.github.notenoughupdates.moulconfig.gui.GuiImmediateContext + +class TickComponent(val onTick: Runnable) : GuiComponent() { + override fun getWidth(): Int { + return 0 + } + + override fun getHeight(): Int { + return 0 + } + + override fun render(context: GuiImmediateContext) { + onTick.run() + } +} diff --git a/src/main/kotlin/gui/config/AllConfigsGui.kt b/src/main/kotlin/gui/config/AllConfigsGui.kt new file mode 100644 index 0000000..4f7731c --- /dev/null +++ b/src/main/kotlin/gui/config/AllConfigsGui.kt @@ -0,0 +1,46 @@ + + +package moe.nea.firmament.gui.config + +import io.github.notenoughupdates.moulconfig.observer.ObservableList +import io.github.notenoughupdates.moulconfig.xml.Bind +import net.minecraft.client.gui.screen.Screen +import net.minecraft.text.Text +import moe.nea.firmament.features.FeatureManager +import moe.nea.firmament.repo.RepoManager +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.MoulConfigUtils +import moe.nea.firmament.util.ScreenUtil.setScreenLater + +object AllConfigsGui { + + val allConfigs + get() = listOf( + RepoManager.Config + ) + FeatureManager.allFeatures.mapNotNull { it.config } + + fun List.toObservableList(): ObservableList = ObservableList(this) + + class MainMapping(val allConfigs: List) { + @get:Bind("configs") + val configs = allConfigs.map { EntryMapping(it) }.toObservableList() + + class EntryMapping(val config: ManagedConfig) { + @Bind + fun name() = Text.translatable("firmament.config.${config.name}").string + + @Bind + fun openEditor() { + config.showConfigEditor(MC.screen) + } + } + } + + fun makeScreen(parent: Screen? = null): Screen { + return MoulConfigUtils.loadScreen("config/main", MainMapping(allConfigs), parent) + } + + fun showAllGuis() { + setScreenLater(makeScreen()) + } +} diff --git a/src/main/kotlin/gui/config/BooleanHandler.kt b/src/main/kotlin/gui/config/BooleanHandler.kt new file mode 100644 index 0000000..8592777 --- /dev/null +++ b/src/main/kotlin/gui/config/BooleanHandler.kt @@ -0,0 +1,37 @@ + + +package moe.nea.firmament.gui.config + +import io.github.notenoughupdates.moulconfig.gui.component.CenterComponent +import io.github.notenoughupdates.moulconfig.gui.component.SwitchComponent +import io.github.notenoughupdates.moulconfig.observer.GetSetter +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.boolean +import kotlinx.serialization.json.jsonPrimitive + +class BooleanHandler(val config: ManagedConfig) : ManagedConfig.OptionHandler { + override fun toJson(element: Boolean): JsonElement? { + return JsonPrimitive(element) + } + + override fun fromJson(element: JsonElement): Boolean { + return element.jsonPrimitive.boolean + } + + override fun emitGuiElements(opt: ManagedOption, guiAppender: GuiAppender) { + guiAppender.appendLabeledRow( + opt.labelText, + CenterComponent(SwitchComponent(object : GetSetter { + override fun get(): Boolean { + return opt.get() + } + + override fun set(newValue: Boolean) { + opt.set(newValue) + config.save() + } + }, 200) + )) + } +} diff --git a/src/main/kotlin/gui/config/ClickHandler.kt b/src/main/kotlin/gui/config/ClickHandler.kt new file mode 100644 index 0000000..fa1c621 --- /dev/null +++ b/src/main/kotlin/gui/config/ClickHandler.kt @@ -0,0 +1,24 @@ + + +package moe.nea.firmament.gui.config + +import io.github.notenoughupdates.moulconfig.gui.component.TextComponent +import kotlinx.serialization.json.JsonElement +import moe.nea.firmament.gui.FirmButtonComponent + +class ClickHandler(val config: ManagedConfig, val runnable: () -> Unit) : ManagedConfig.OptionHandler { + override fun toJson(element: Unit): JsonElement? { + return null + } + + override fun fromJson(element: JsonElement) {} + + override fun emitGuiElements(opt: ManagedOption, guiAppender: GuiAppender) { + guiAppender.appendLabeledRow( + opt.labelText, + FirmButtonComponent( + TextComponent(opt.labelText.string), + action = runnable), + ) + } +} diff --git a/src/main/kotlin/gui/config/DurationHandler.kt b/src/main/kotlin/gui/config/DurationHandler.kt new file mode 100644 index 0000000..8d485b1 --- /dev/null +++ b/src/main/kotlin/gui/config/DurationHandler.kt @@ -0,0 +1,58 @@ + + +package moe.nea.firmament.gui.config + +import io.github.notenoughupdates.moulconfig.common.IMinecraft +import io.github.notenoughupdates.moulconfig.gui.component.RowComponent +import io.github.notenoughupdates.moulconfig.gui.component.SliderComponent +import io.github.notenoughupdates.moulconfig.gui.component.TextComponent +import io.github.notenoughupdates.moulconfig.observer.GetSetter +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.long +import kotlin.time.Duration +import kotlin.time.DurationUnit +import kotlin.time.toDuration +import net.minecraft.text.Text +import moe.nea.firmament.util.FirmFormatters + +class DurationHandler(val config: ManagedConfig, val min: Duration, val max: Duration) : + ManagedConfig.OptionHandler { + override fun toJson(element: Duration): JsonElement? { + return JsonPrimitive(element.inWholeMilliseconds) + } + + override fun fromJson(element: JsonElement): Duration { + return element.jsonPrimitive.long.toDuration(DurationUnit.MILLISECONDS) + } + + override fun emitGuiElements(opt: ManagedOption, guiAppender: GuiAppender) { + guiAppender.appendLabeledRow( + opt.labelText, + RowComponent( + TextComponent(IMinecraft.instance.defaultFontRenderer, + { FirmFormatters.formatTimespan(opt.value) }, + 40, + TextComponent.TextAlignment.CENTER, + true, + false), + SliderComponent( + object : GetSetter { + override fun get(): Float { + return opt.value.toDouble(DurationUnit.SECONDS).toFloat() + } + + override fun set(newValue: Float) { + opt.value = newValue.toDouble().toDuration(DurationUnit.SECONDS) + } + }, + min.toDouble(DurationUnit.SECONDS).toFloat(), + max.toDouble(DurationUnit.SECONDS).toFloat(), + 0.1F, + 130 + ) + )) + } + +} diff --git a/src/main/kotlin/gui/config/GuiAppender.kt b/src/main/kotlin/gui/config/GuiAppender.kt new file mode 100644 index 0000000..329319d --- /dev/null +++ b/src/main/kotlin/gui/config/GuiAppender.kt @@ -0,0 +1,40 @@ + + +package moe.nea.firmament.gui.config + +import io.github.notenoughupdates.moulconfig.gui.GuiComponent +import io.github.notenoughupdates.moulconfig.gui.component.RowComponent +import io.github.notenoughupdates.moulconfig.gui.component.TextComponent +import io.github.notenoughupdates.moulconfig.observer.GetSetter +import net.minecraft.client.gui.screen.Screen +import net.minecraft.text.Text +import moe.nea.firmament.gui.FixedComponent + +class GuiAppender(val width: Int, val screenAccessor: () -> Screen) { + val panel = mutableListOf() + internal val reloadables = mutableListOf<(() -> Unit)>() + + fun onReload(reloadable: () -> Unit) { + reloadables.add(reloadable) + } + + fun appendLabeledRow(label: Text, right: GuiComponent) { + appendSplitRow( + TextComponent(label.string), + right + ) + } + + fun appendSplitRow(left: GuiComponent, right: GuiComponent) { + // TODO: make this more dynamic + // i could just make a component that allows for using half the available size + appendFullRow(RowComponent( + FixedComponent(GetSetter.constant(width / 2), null, left), + FixedComponent(GetSetter.constant(width / 2), null, right), + )) + } + + fun appendFullRow(widget: GuiComponent) { + panel.add(widget) + } +} diff --git a/src/main/kotlin/gui/config/HudMetaHandler.kt b/src/main/kotlin/gui/config/HudMetaHandler.kt new file mode 100644 index 0000000..35c9d51 --- /dev/null +++ b/src/main/kotlin/gui/config/HudMetaHandler.kt @@ -0,0 +1,39 @@ + + +package moe.nea.firmament.gui.config + +import io.github.notenoughupdates.moulconfig.gui.component.TextComponent +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.decodeFromJsonElement +import kotlinx.serialization.json.encodeToJsonElement +import net.minecraft.text.MutableText +import net.minecraft.text.Text +import moe.nea.firmament.gui.FirmButtonComponent +import moe.nea.firmament.jarvis.JarvisIntegration +import moe.nea.firmament.util.MC + +class HudMetaHandler(val config: ManagedConfig, val label: MutableText, val width: Int, val height: Int) : + ManagedConfig.OptionHandler { + override fun toJson(element: HudMeta): JsonElement? { + return Json.encodeToJsonElement(element.position) + } + + override fun fromJson(element: JsonElement): HudMeta { + return HudMeta(Json.decodeFromJsonElement(element), label, width, height) + } + + override fun emitGuiElements(opt: ManagedOption, guiAppender: GuiAppender) { + guiAppender.appendLabeledRow( + opt.labelText, + FirmButtonComponent( + TextComponent( + Text.stringifiedTranslatable("firmament.hud.edit", label).string), + ) { + MC.screen = JarvisIntegration.jarvis.getHudEditor( + guiAppender.screenAccessor.invoke(), + listOf(opt.value) + ) + }) + } +} diff --git a/src/main/kotlin/gui/config/IntegerHandler.kt b/src/main/kotlin/gui/config/IntegerHandler.kt new file mode 100644 index 0000000..31ce90f --- /dev/null +++ b/src/main/kotlin/gui/config/IntegerHandler.kt @@ -0,0 +1,54 @@ + + +package moe.nea.firmament.gui.config + +import io.github.notenoughupdates.moulconfig.common.IMinecraft +import io.github.notenoughupdates.moulconfig.gui.component.RowComponent +import io.github.notenoughupdates.moulconfig.gui.component.SliderComponent +import io.github.notenoughupdates.moulconfig.gui.component.TextComponent +import io.github.notenoughupdates.moulconfig.observer.GetSetter +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.int +import kotlinx.serialization.json.jsonPrimitive +import moe.nea.firmament.util.FirmFormatters + +class IntegerHandler(val config: ManagedConfig, val min: Int, val max: Int) : ManagedConfig.OptionHandler { + override fun toJson(element: Int): JsonElement? { + return JsonPrimitive(element) + } + + override fun fromJson(element: JsonElement): Int { + return element.jsonPrimitive.int + } + + override fun emitGuiElements(opt: ManagedOption, guiAppender: GuiAppender) { + guiAppender.appendLabeledRow( + opt.labelText, + RowComponent( + TextComponent(IMinecraft.instance.defaultFontRenderer, + { FirmFormatters.formatCommas(opt.value, 0) }, + 40, + TextComponent.TextAlignment.CENTER, + true, + false), + SliderComponent( + object : GetSetter { + override fun get(): Float { + return opt.value.toFloat() + } + + override fun set(newValue: Float) { + opt.value = newValue.toInt() + } + }, + min.toFloat(), + max.toFloat(), + 0.1F, + 130 + ) + )) + + } + +} diff --git a/src/main/kotlin/gui/config/JAnyHud.kt b/src/main/kotlin/gui/config/JAnyHud.kt new file mode 100644 index 0000000..35c4eb2 --- /dev/null +++ b/src/main/kotlin/gui/config/JAnyHud.kt @@ -0,0 +1,48 @@ + + +package moe.nea.firmament.gui.config + +import moe.nea.jarvis.api.JarvisHud +import moe.nea.jarvis.api.JarvisScalable +import kotlinx.serialization.Serializable +import net.minecraft.text.Text + +@Serializable +data class HudPosition( + var x: Double, + var y: Double, + var scale: Float, +) + + +data class HudMeta( + val position: HudPosition, + private val label: Text, + private val width: Int, + private val height: Int, +) : JarvisScalable, JarvisHud { + override fun getX(): Double = position.x + + override fun setX(newX: Double) { + position.x = newX + } + + override fun getY(): Double = position.y + + override fun setY(newY: Double) { + position.y = newY + } + + override fun getLabel(): Text = label + + override fun getWidth(): Int = width + + override fun getHeight(): Int = height + + override fun getScale(): Float = position.scale + + override fun setScale(newScale: Float) { + position.scale = newScale + } + +} diff --git a/src/main/kotlin/gui/config/KeyBindingHandler.kt b/src/main/kotlin/gui/config/KeyBindingHandler.kt new file mode 100644 index 0000000..c389cc9 --- /dev/null +++ b/src/main/kotlin/gui/config/KeyBindingHandler.kt @@ -0,0 +1,149 @@ + + +package moe.nea.firmament.gui.config + +import io.github.notenoughupdates.moulconfig.common.IMinecraft +import io.github.notenoughupdates.moulconfig.common.MyResourceLocation +import io.github.notenoughupdates.moulconfig.deps.libninepatch.NinePatch +import io.github.notenoughupdates.moulconfig.gui.GuiImmediateContext +import io.github.notenoughupdates.moulconfig.gui.KeyboardEvent +import io.github.notenoughupdates.moulconfig.gui.component.TextComponent +import org.lwjgl.glfw.GLFW +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.decodeFromJsonElement +import kotlinx.serialization.json.encodeToJsonElement +import net.minecraft.text.Text +import net.minecraft.util.Formatting +import moe.nea.firmament.gui.FirmButtonComponent +import moe.nea.firmament.keybindings.FirmamentKeyBindings +import moe.nea.firmament.keybindings.SavedKeyBinding + +class KeyBindingHandler(val name: String, val managedConfig: ManagedConfig) : + ManagedConfig.OptionHandler { + + override fun initOption(opt: ManagedOption) { + FirmamentKeyBindings.registerKeyBinding(name, opt) + } + + override fun toJson(element: SavedKeyBinding): JsonElement? { + return Json.encodeToJsonElement(element) + } + + override fun fromJson(element: JsonElement): SavedKeyBinding { + return Json.decodeFromJsonElement(element) + } + + override fun emitGuiElements(opt: ManagedOption, guiAppender: GuiAppender) { + var editing = false + var lastPressed = 0 + var lastPressedNonModifier = 0 + var label: String = "" + var button: FirmButtonComponent? = null + fun updateLabel() { + var stroke = opt.value.format() + if (editing) { + stroke = Text.literal("") + val (shift, alt, ctrl) = SavedKeyBinding.getMods(SavedKeyBinding.getModInt()) + if (shift) { + stroke.append("SHIFT + ") + } + if (alt) { + stroke.append("ALT + ") + } + if (ctrl) { + stroke.append("CTRL + ") + } + stroke.append("???") + stroke.styled { it.withColor(Formatting.YELLOW) } + } + label = (stroke).string + managedConfig.save() + } + button = object : FirmButtonComponent( + TextComponent( + IMinecraft.instance.defaultFontRenderer, + { label }, + 130, + TextComponent.TextAlignment.LEFT, + false, + false + ), action = { + if (editing) { + button!!.blur() + } else { + editing = true + button!!.requestFocus() + updateLabel() + } + }) { + override fun keyboardEvent(event: KeyboardEvent, context: GuiImmediateContext): Boolean { + if (event is KeyboardEvent.KeyPressed) { + return if (event.pressed) onKeyPressed(event.keycode, SavedKeyBinding.getModInt()) + else onKeyReleased(event.keycode, SavedKeyBinding.getModInt()) + } + return super.keyboardEvent(event, context) + } + + override fun getBackground(context: GuiImmediateContext): NinePatch { + if (editing) return activeBg + return super.getBackground(context) + } + + fun onKeyPressed(ch: Int, modifiers: Int): Boolean { + if (!editing) { + return false + } + if (ch == GLFW.GLFW_KEY_ESCAPE) { + lastPressedNonModifier = 0 + editing = false + lastPressed = 0 + opt.value = SavedKeyBinding(GLFW.GLFW_KEY_UNKNOWN) + updateLabel() + blur() + return true + } + if (ch == GLFW.GLFW_KEY_LEFT_SHIFT || ch == GLFW.GLFW_KEY_RIGHT_SHIFT + || ch == GLFW.GLFW_KEY_LEFT_ALT || ch == GLFW.GLFW_KEY_RIGHT_ALT + || ch == GLFW.GLFW_KEY_LEFT_CONTROL || ch == GLFW.GLFW_KEY_RIGHT_CONTROL + ) { + lastPressed = ch + } else { + opt.value = SavedKeyBinding( + ch, modifiers + ) + editing = false + blur() + lastPressed = 0 + lastPressedNonModifier = 0 + } + updateLabel() + return true + } + + override fun onLostFocus() { + lastPressedNonModifier = 0 + editing = false + lastPressed = 0 + updateLabel() + } + + fun onKeyReleased(ch: Int, modifiers: Int): Boolean { + if (!editing) + return false + if (lastPressedNonModifier == ch || (lastPressedNonModifier == 0 && ch == lastPressed)) { + opt.value = SavedKeyBinding(ch, modifiers) + editing = false + blur() + lastPressed = 0 + lastPressedNonModifier = 0 + } + updateLabel() + return true + } + } + updateLabel() + guiAppender.appendLabeledRow(opt.labelText, button) + } + +} diff --git a/src/main/kotlin/gui/config/ManagedConfig.kt b/src/main/kotlin/gui/config/ManagedConfig.kt new file mode 100644 index 0000000..aa6e3c8 --- /dev/null +++ b/src/main/kotlin/gui/config/ManagedConfig.kt @@ -0,0 +1,181 @@ + + +package moe.nea.firmament.gui.config + +import io.github.notenoughupdates.moulconfig.gui.CloseEventListener +import io.github.notenoughupdates.moulconfig.gui.GuiComponentWrapper +import io.github.notenoughupdates.moulconfig.gui.GuiContext +import io.github.notenoughupdates.moulconfig.gui.component.CenterComponent +import io.github.notenoughupdates.moulconfig.gui.component.ColumnComponent +import io.github.notenoughupdates.moulconfig.gui.component.PanelComponent +import io.github.notenoughupdates.moulconfig.gui.component.RowComponent +import io.github.notenoughupdates.moulconfig.gui.component.ScrollPanelComponent +import io.github.notenoughupdates.moulconfig.gui.component.TextComponent +import moe.nea.jarvis.api.Point +import org.lwjgl.glfw.GLFW +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonObject +import kotlin.io.path.createDirectories +import kotlin.io.path.readText +import kotlin.io.path.writeText +import kotlin.time.Duration +import net.minecraft.client.gui.screen.Screen +import net.minecraft.text.Text +import moe.nea.firmament.Firmament +import moe.nea.firmament.gui.FirmButtonComponent +import moe.nea.firmament.keybindings.SavedKeyBinding +import moe.nea.firmament.util.ScreenUtil.setScreenLater + +abstract class ManagedConfig(override val name: String) : ManagedConfigElement() { + + interface OptionHandler { + fun initOption(opt: ManagedOption) {} + fun toJson(element: T): JsonElement? + fun fromJson(element: JsonElement): T + fun emitGuiElements(opt: ManagedOption, guiAppender: GuiAppender) + } + + val file = Firmament.CONFIG_DIR.resolve("$name.json") + val data: JsonObject by lazy { + try { + Firmament.json.decodeFromString( + file.readText() + ) + } catch (e: Exception) { + Firmament.logger.info("Could not read config $name. Loading empty config.") + JsonObject(mutableMapOf()) + } + } + + fun save() { + val data = JsonObject(allOptions.mapNotNull { (key, value) -> + value.toJson()?.let { + key to it + } + }.toMap()) + file.parent.createDirectories() + file.writeText(Firmament.json.encodeToString(data)) + } + + + val allOptions = mutableMapOf>() + val sortedOptions = mutableListOf>() + + private var latestGuiAppender: GuiAppender? = null + + protected fun option( + propertyName: String, + default: () -> T, + handler: OptionHandler + ): ManagedOption { + if (propertyName in allOptions) error("Cannot register the same name twice") + return ManagedOption(this, propertyName, default, handler).also { + it.handler.initOption(it) + it.load(data) + allOptions[propertyName] = it + sortedOptions.add(it) + } + } + + protected fun toggle(propertyName: String, default: () -> Boolean): ManagedOption { + return option(propertyName, default, BooleanHandler(this)) + } + + protected fun duration( + propertyName: String, + min: Duration, + max: Duration, + default: () -> Duration, + ): ManagedOption { + return option(propertyName, default, DurationHandler(this, min, max)) + } + + + protected fun position( + propertyName: String, + width: Int, + height: Int, + default: () -> Point, + ): ManagedOption { + val label = Text.translatable("firmament.config.${name}.${propertyName}") + return option(propertyName, { + val p = default() + HudMeta(HudPosition(p.x, p.y, 1F), label, width, height) + }, HudMetaHandler(this, label, width, height)) + } + + protected fun keyBinding( + propertyName: String, + default: () -> Int, + ): ManagedOption = keyBindingWithOutDefaultModifiers(propertyName) { SavedKeyBinding(default()) } + + protected fun keyBindingWithOutDefaultModifiers( + propertyName: String, + default: () -> SavedKeyBinding, + ): ManagedOption { + return option(propertyName, default, KeyBindingHandler("firmament.config.${name}.${propertyName}", this)) + } + + protected fun keyBindingWithDefaultUnbound( + propertyName: String, + ): ManagedOption { + return keyBindingWithOutDefaultModifiers(propertyName) { SavedKeyBinding(GLFW.GLFW_KEY_UNKNOWN) } + } + + protected fun integer( + propertyName: String, + min: Int, + max: Int, + default: () -> Int, + ): ManagedOption { + return option(propertyName, default, IntegerHandler(this, min, max)) + } + + protected fun button(propertyName: String, runnable: () -> Unit): ManagedOption { + return option(propertyName, { }, ClickHandler(this, runnable)) + } + + protected fun string(propertyName: String, default: () -> String): ManagedOption { + return option(propertyName, default, StringHandler(this)) + } + + + fun reloadGui() { + latestGuiAppender?.reloadables?.forEach { it() } + } + + val labelText = Text.translatable("firmament.config.${name}") + + fun getConfigEditor(parent: Screen? = null): Screen { + var screen: Screen? = null + val guiapp = GuiAppender(400) { requireNotNull(screen) { "Screen Accessor called too early" } } + latestGuiAppender = guiapp + guiapp.appendFullRow(RowComponent( + FirmButtonComponent(TextComponent("←")) { + if (parent != null) { + save() + setScreenLater(parent) + } else { + AllConfigsGui.showAllGuis() + } + } + )) + sortedOptions.forEach { it.appendToGui(guiapp) } + guiapp.reloadables.forEach { it() } + val component = CenterComponent(PanelComponent(ScrollPanelComponent(400, 300, ColumnComponent(guiapp.panel)), 10, PanelComponent.DefaultBackgroundRenderer.VANILLA)) + screen = object : GuiComponentWrapper(GuiContext(component)) { + override fun close() { + if (context.onBeforeClose() == CloseEventListener.CloseAction.NO_OBJECTIONS_TO_CLOSE) { + client!!.setScreen(parent) + } + } + } + return screen + } + + fun showConfigEditor(parent: Screen? = null) { + setScreenLater(getConfigEditor(parent)) + } + +} diff --git a/src/main/kotlin/gui/config/ManagedConfigElement.kt b/src/main/kotlin/gui/config/ManagedConfigElement.kt new file mode 100644 index 0000000..28cd6b8 --- /dev/null +++ b/src/main/kotlin/gui/config/ManagedConfigElement.kt @@ -0,0 +1,8 @@ + + +package moe.nea.firmament.gui.config + +abstract class ManagedConfigElement { + abstract val name: String + +} diff --git a/src/main/kotlin/gui/config/ManagedOption.kt b/src/main/kotlin/gui/config/ManagedOption.kt new file mode 100644 index 0000000..b7264e8 --- /dev/null +++ b/src/main/kotlin/gui/config/ManagedOption.kt @@ -0,0 +1,62 @@ + + +package moe.nea.firmament.gui.config + +import io.github.notenoughupdates.moulconfig.observer.GetSetter +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonObject +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty +import net.minecraft.text.Text +import moe.nea.firmament.Firmament + +class ManagedOption( + val element: ManagedConfigElement, + val propertyName: String, + val default: () -> T, + val handler: ManagedConfig.OptionHandler +) : ReadWriteProperty, GetSetter { + override fun set(newValue: T) { + this.value = newValue + } + + override fun get(): T { + return this.value + } + + val rawLabelText = "firmament.config.${element.name}.${propertyName}" + val labelText = Text.translatable(rawLabelText) + + lateinit var value: T + + override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { + this.value = value + } + + override fun getValue(thisRef: Any?, property: KProperty<*>): T { + return value + } + + fun load(root: JsonElement) { + if (root is JsonObject && root.containsKey(propertyName)) { + try { + value = handler.fromJson(root[propertyName]!!) + return + } catch (e: Exception) { + Firmament.logger.error( + "Exception during loading of config file ${element.name}. This will reset this config.", + e + ) + } + } + value = default() + } + + fun toJson(): JsonElement? { + return handler.toJson(value) + } + + fun appendToGui(guiapp: GuiAppender) { + handler.emitGuiElements(this, guiapp) + } +} diff --git a/src/main/kotlin/gui/config/StringHandler.kt b/src/main/kotlin/gui/config/StringHandler.kt new file mode 100644 index 0000000..a326abb --- /dev/null +++ b/src/main/kotlin/gui/config/StringHandler.kt @@ -0,0 +1,36 @@ + + +package moe.nea.firmament.gui.config + +import io.github.notenoughupdates.moulconfig.gui.component.TextFieldComponent +import io.github.notenoughupdates.moulconfig.observer.GetSetter +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.jsonPrimitive +import net.minecraft.text.Text + +class StringHandler(val config: ManagedConfig) : ManagedConfig.OptionHandler { + override fun toJson(element: String): JsonElement? { + return JsonPrimitive(element) + } + + override fun fromJson(element: JsonElement): String { + return element.jsonPrimitive.content + } + + override fun emitGuiElements(opt: ManagedOption, guiAppender: GuiAppender) { + guiAppender.appendLabeledRow( + opt.labelText, + TextFieldComponent( + object : GetSetter by opt { + override fun set(newValue: String) { + opt.set(newValue) + config.save() + } + }, + 130, + suggestion = Text.translatableWithFallback(opt.rawLabelText + ".hint", "").string + ), + ) + } +} diff --git a/src/main/kotlin/gui/entity/EntityModifier.kt b/src/main/kotlin/gui/entity/EntityModifier.kt new file mode 100644 index 0000000..9623070 --- /dev/null +++ b/src/main/kotlin/gui/entity/EntityModifier.kt @@ -0,0 +1,9 @@ + +package moe.nea.firmament.gui.entity + +import com.google.gson.JsonObject +import net.minecraft.entity.LivingEntity + +fun interface EntityModifier { + fun apply(entity: LivingEntity, info: JsonObject): LivingEntity +} diff --git a/src/main/kotlin/gui/entity/EntityRenderer.kt b/src/main/kotlin/gui/entity/EntityRenderer.kt new file mode 100644 index 0000000..8c7428d --- /dev/null +++ b/src/main/kotlin/gui/entity/EntityRenderer.kt @@ -0,0 +1,197 @@ + +package moe.nea.firmament.gui.entity + +import com.google.gson.Gson +import com.google.gson.JsonArray +import com.google.gson.JsonObject +import org.apache.logging.log4j.LogManager +import org.joml.Quaternionf +import org.joml.Vector3f +import kotlin.math.atan +import net.minecraft.client.gui.DrawContext +import net.minecraft.client.gui.screen.ingame.InventoryScreen +import net.minecraft.entity.Entity +import net.minecraft.entity.EntityType +import net.minecraft.entity.LivingEntity +import net.minecraft.util.Identifier +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.assertNotNullOr +import moe.nea.firmament.util.iterate +import moe.nea.firmament.util.openFirmamentResource +import moe.nea.firmament.util.render.enableScissorWithTranslation + +object EntityRenderer { + val fakeWorld = FakeWorld() + private fun t(entityType: EntityType): () -> T { + return { entityType.create(fakeWorld)!! } + } + + val entityIds: Map LivingEntity> = mapOf( + "Zombie" to t(EntityType.ZOMBIE), + "Chicken" to t(EntityType.CHICKEN), + "Slime" to t(EntityType.SLIME), + "Wolf" to t(EntityType.WOLF), + "Skeleton" to t(EntityType.SKELETON), + "Creeper" to t(EntityType.CREEPER), + "Ocelot" to t(EntityType.OCELOT), + "Blaze" to t(EntityType.BLAZE), + "Rabbit" to t(EntityType.RABBIT), + "Sheep" to t(EntityType.SHEEP), + "Horse" to t(EntityType.HORSE), + "Eisengolem" to t(EntityType.IRON_GOLEM), + "Silverfish" to t(EntityType.SILVERFISH), + "Witch" to t(EntityType.WITCH), + "Endermite" to t(EntityType.ENDERMITE), + "Snowman" to t(EntityType.SNOW_GOLEM), + "Villager" to t(EntityType.VILLAGER), + "Guardian" to t(EntityType.GUARDIAN), + "ArmorStand" to t(EntityType.ARMOR_STAND), + "Squid" to t(EntityType.SQUID), + "Bat" to t(EntityType.BAT), + "Spider" to t(EntityType.SPIDER), + "CaveSpider" to t(EntityType.CAVE_SPIDER), + "Pigman" to t(EntityType.ZOMBIFIED_PIGLIN), + "Ghast" to t(EntityType.GHAST), + "MagmaCube" to t(EntityType.MAGMA_CUBE), + "Wither" to t(EntityType.WITHER), + "Enderman" to t(EntityType.ENDERMAN), + "Mooshroom" to t(EntityType.MOOSHROOM), + "WitherSkeleton" to t(EntityType.WITHER_SKELETON), + "Cow" to t(EntityType.COW), + "Dragon" to t(EntityType.ENDER_DRAGON), + "Player" to { makeGuiPlayer(fakeWorld) }, + "Pig" to t(EntityType.PIG), + "Giant" to t(EntityType.GIANT), + ) + val entityModifiers: Map = mapOf( + "playerdata" to ModifyPlayerSkin, + "equipment" to ModifyEquipment, + "riding" to ModifyRiding, + "charged" to ModifyCharged, + "witherdata" to ModifyWither, + "invisible" to ModifyInvisible, + "age" to ModifyAge, + "horse" to ModifyHorse, + "name" to ModifyName, + ) + + val logger = LogManager.getLogger("Firmament.Entity") + fun applyModifiers(entityId: String, modifiers: List): LivingEntity? { + val entityType = assertNotNullOr(entityIds[entityId]) { + logger.error("Could not create entity with id $entityId") + return null + } + var entity = entityType() + for (modifierJson in modifiers) { + val modifier = assertNotNullOr(modifierJson["type"]?.asString?.let(entityModifiers::get)) { + logger.error("Unknown modifier $modifierJson") + return null + } + entity = modifier.apply(entity, modifierJson) + } + return entity + } + + fun constructEntity(info: JsonObject): LivingEntity? { + val modifiers = (info["modifiers"] as JsonArray?)?.map { it.asJsonObject } ?: emptyList() + val entityType = assertNotNullOr(info["entity"]?.asString) { + logger.error("Missing entity type on entity object") + return null + } + return applyModifiers(entityType, modifiers) + } + + private val gson = Gson() + fun constructEntity(location: Identifier): LivingEntity? { + return constructEntity( + gson.fromJson( + location.openFirmamentResource().bufferedReader(), JsonObject::class.java + ) + ) + } + + fun renderEntity( + entity: LivingEntity, + renderContext: DrawContext, + posX: Int, + posY: Int, + mouseX: Float, + mouseY: Float + ) { + var bottomOffset = 0.0F + var currentEntity = entity + val maxSize = entity.iterate { it.firstPassenger as? LivingEntity } + .map { it.height } + .sum() + while (true) { + currentEntity.age = MC.player?.age ?: 0 + drawEntity( + renderContext, + posX, + posY, + posX + 50, + posY + 80, + minOf(2F / maxSize, 1F) * 30, + -bottomOffset, + mouseX, + mouseY, + currentEntity + ) + val next = currentEntity.firstPassenger as? LivingEntity ?: break + bottomOffset += currentEntity.getPassengerRidingPos(next).y.toFloat() * 0.75F + currentEntity = next + } + } + + + fun drawEntity( + context: DrawContext, + x1: Int, + y1: Int, + x2: Int, + y2: Int, + size: Float, + bottomOffset: Float, + mouseX: Float, + mouseY: Float, + entity: LivingEntity + ) { + context.enableScissorWithTranslation(x1.toFloat(), y1.toFloat(), x2.toFloat(), y2.toFloat()) + val centerX = (x1 + x2) / 2f + val centerY = (y1 + y2) / 2f + val targetYaw = atan(((centerX - mouseX) / 40.0f).toDouble()).toFloat() + val targetPitch = atan(((centerY - mouseY) / 40.0f).toDouble()).toFloat() + val rotateToFaceTheFront = Quaternionf().rotateZ(Math.PI.toFloat()) + val rotateToFaceTheCamera = Quaternionf().rotateX(targetPitch * 20.0f * (Math.PI.toFloat() / 180)) + rotateToFaceTheFront.mul(rotateToFaceTheCamera) + val oldBodyYaw = entity.bodyYaw + val oldYaw = entity.yaw + val oldPitch = entity.pitch + val oldPrevHeadYaw = entity.prevHeadYaw + val oldHeadYaw = entity.headYaw + entity.bodyYaw = 180.0f + targetYaw * 20.0f + entity.yaw = 180.0f + targetYaw * 40.0f + entity.pitch = -targetPitch * 20.0f + entity.headYaw = entity.yaw + entity.prevHeadYaw = entity.yaw + val vector3f = Vector3f(0.0f, entity.height / 2.0f + bottomOffset, 0.0f) + InventoryScreen.drawEntity( + context, + centerX, + centerY, + size, + vector3f, + rotateToFaceTheFront, + rotateToFaceTheCamera, + entity + ) + entity.bodyYaw = oldBodyYaw + entity.yaw = oldYaw + entity.pitch = oldPitch + entity.prevHeadYaw = oldPrevHeadYaw + entity.headYaw = oldHeadYaw + context.disableScissor() + } + + +} diff --git a/src/main/kotlin/gui/entity/EntityWidget.kt b/src/main/kotlin/gui/entity/EntityWidget.kt new file mode 100644 index 0000000..2e49072 --- /dev/null +++ b/src/main/kotlin/gui/entity/EntityWidget.kt @@ -0,0 +1,35 @@ + +package moe.nea.firmament.gui.entity + +import me.shedaniel.math.Dimension +import me.shedaniel.math.Point +import me.shedaniel.math.Rectangle +import me.shedaniel.rei.api.client.gui.widgets.WidgetWithBounds +import net.minecraft.client.gui.DrawContext +import net.minecraft.client.gui.Element +import net.minecraft.entity.LivingEntity + +class EntityWidget(val entity: LivingEntity, val point: Point) : WidgetWithBounds() { + override fun children(): List { + return emptyList() + } + + var hasErrored = false + + override fun render(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) { + try { + if (!hasErrored) + EntityRenderer.renderEntity(entity, context, point.x, point.y, mouseX.toFloat(), mouseY.toFloat()) + } catch (ex: Exception) { + EntityRenderer.logger.error("Failed to render constructed entity: $entity", ex) + hasErrored = true + } + if (hasErrored) { + context.fill(point.x, point.y, point.x + 50, point.y + 80, 0xFFAA2222.toInt()) + } + } + + override fun getBounds(): Rectangle { + return Rectangle(point, Dimension(50, 80)) + } +} diff --git a/src/main/kotlin/gui/entity/FakeWorld.kt b/src/main/kotlin/gui/entity/FakeWorld.kt new file mode 100644 index 0000000..f354d5a --- /dev/null +++ b/src/main/kotlin/gui/entity/FakeWorld.kt @@ -0,0 +1,488 @@ + +package moe.nea.firmament.gui.entity + +import com.mojang.datafixers.util.Pair +import com.mojang.serialization.Lifecycle +import java.util.* +import java.util.function.BooleanSupplier +import java.util.function.Consumer +import java.util.stream.Stream +import kotlin.jvm.optionals.getOrNull +import kotlin.streams.asSequence +import net.minecraft.block.Block +import net.minecraft.block.BlockState +import net.minecraft.component.type.MapIdComponent +import net.minecraft.entity.Entity +import net.minecraft.entity.player.PlayerEntity +import net.minecraft.fluid.Fluid +import net.minecraft.item.map.MapState +import net.minecraft.recipe.BrewingRecipeRegistry +import net.minecraft.recipe.Ingredient +import net.minecraft.recipe.RecipeManager +import net.minecraft.registry.BuiltinRegistries +import net.minecraft.registry.DynamicRegistryManager +import net.minecraft.registry.Registry +import net.minecraft.registry.RegistryKey +import net.minecraft.registry.RegistryKeys +import net.minecraft.registry.RegistryWrapper +import net.minecraft.registry.entry.RegistryEntry +import net.minecraft.registry.entry.RegistryEntryInfo +import net.minecraft.registry.entry.RegistryEntryList +import net.minecraft.registry.entry.RegistryEntryOwner +import net.minecraft.registry.tag.TagKey +import net.minecraft.resource.featuretoggle.FeatureFlags +import net.minecraft.resource.featuretoggle.FeatureSet +import net.minecraft.scoreboard.Scoreboard +import net.minecraft.sound.SoundCategory +import net.minecraft.sound.SoundEvent +import net.minecraft.util.Identifier +import net.minecraft.util.TypeFilter +import net.minecraft.util.function.LazyIterationConsumer +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Box +import net.minecraft.util.math.ChunkPos +import net.minecraft.util.math.Direction +import net.minecraft.util.math.Vec3d +import net.minecraft.util.math.random.Random +import net.minecraft.util.profiler.DummyProfiler +import net.minecraft.world.BlockView +import net.minecraft.world.Difficulty +import net.minecraft.world.GameRules +import net.minecraft.world.MutableWorldProperties +import net.minecraft.world.World +import net.minecraft.world.biome.Biome +import net.minecraft.world.biome.BiomeKeys +import net.minecraft.world.chunk.Chunk +import net.minecraft.world.chunk.ChunkManager +import net.minecraft.world.chunk.ChunkStatus +import net.minecraft.world.chunk.EmptyChunk +import net.minecraft.world.chunk.light.LightingProvider +import net.minecraft.world.entity.EntityLookup +import net.minecraft.world.event.GameEvent +import net.minecraft.world.tick.OrderedTick +import net.minecraft.world.tick.QueryableTickScheduler +import net.minecraft.world.tick.TickManager + +fun makeRegistry(registryWrapper: RegistryWrapper.Impl, key: RegistryKey>): Registry { + val inverseLookup = registryWrapper.streamEntries() + .asSequence().map { it.value() to it.registryKey() } + .toMap() + val idLookup = registryWrapper.streamEntries() + .asSequence() + .map { it.registryKey() } + .withIndex() + .associate { it.value to it.index } + val map = registryWrapper.streamEntries().asSequence().map { it.registryKey() to it.value() }.toMap(mutableMapOf()) + val inverseIdLookup = idLookup.asIterable().associate { (k, v) -> v to k } + return object : Registry { + override fun get(key: RegistryKey?): T? { + return registryWrapper.getOptional(key).getOrNull()?.value() + } + + override fun iterator(): MutableIterator { + return object : MutableIterator { + val iterator = registryWrapper.streamEntries().iterator() + override fun hasNext(): Boolean { + return iterator.hasNext() + } + + override fun next(): T { + return iterator.next().value() + } + + override fun remove() { + TODO("Not yet implemented") + } + } + } + + override fun getRawId(value: T?): Int { + return idLookup[inverseLookup[value ?: return -1] ?: return -1] ?: return -1 + } + + override fun get(id: Identifier?): T? { + return get(RegistryKey.of(key, id)) + } + + override fun get(index: Int): T? { + return get(inverseIdLookup[index] ?: return null) + } + + override fun size(): Int { + return idLookup.size + } + + override fun getKey(): RegistryKey> { + return key + } + + override fun getEntryInfo(key: RegistryKey?): Optional { + TODO("Not yet implemented") + } + + override fun getLifecycle(): Lifecycle { + return Lifecycle.stable() + } + + override fun getDefaultEntry(): Optional> { + return Optional.empty() + } + + override fun getIds(): MutableSet { + return idLookup.keys.mapTo(mutableSetOf()) { it.value } + } + + override fun getEntrySet(): MutableSet, T>> { + return map.entries + } + + override fun getKeys(): MutableSet> { + return map.keys + } + + override fun getRandom(random: Random?): Optional> { + return registryWrapper.streamEntries().findFirst() + } + + override fun containsId(id: Identifier?): Boolean { + return idLookup.containsKey(RegistryKey.of(key, id ?: return false)) + } + + override fun freeze(): Registry { + return this + } + + override fun getEntry(rawId: Int): Optional> { + val x = inverseIdLookup[rawId] ?: return Optional.empty() + return Optional.of(RegistryEntry.Reference.standAlone(registryWrapper, x)) + } + + override fun streamEntries(): Stream> { + return registryWrapper.streamEntries() + } + + override fun streamTagsAndEntries(): Stream, RegistryEntryList.Named>> { + return streamTags().map { Pair(it, getOrCreateEntryList(it)) } + } + + override fun streamTags(): Stream> { + return registryWrapper.streamTagKeys() + } + + override fun clearTags() { + } + + override fun getEntryOwner(): RegistryEntryOwner { + return registryWrapper + } + + override fun getReadOnlyWrapper(): RegistryWrapper.Impl { + return registryWrapper + } + + override fun populateTags(tagEntries: MutableMap, MutableList>>?) { + } + + override fun getOrCreateEntryList(tag: TagKey?): RegistryEntryList.Named { + return getEntryList(tag).orElseGet { RegistryEntryList.of(registryWrapper, tag) } + } + + override fun getEntryList(tag: TagKey?): Optional> { + return registryWrapper.getOptional(tag ?: return Optional.empty()) + } + + override fun getEntry(value: T): RegistryEntry { + return registryWrapper.getOptional(inverseLookup[value]!!).get() + } + + override fun getEntry(key: RegistryKey?): Optional> { + return registryWrapper.getOptional(key ?: return Optional.empty()) + } + + override fun getEntry(id: Identifier?): Optional> { + TODO("Not yet implemented") + } + + override fun createEntry(value: T): RegistryEntry.Reference { + TODO("Not yet implemented") + } + + override fun contains(key: RegistryKey?): Boolean { + return getEntry(key).isPresent + } + + override fun getId(value: T): Identifier? { + return (inverseLookup[value] ?: return null).value + } + + override fun getKey(entry: T): Optional> { + return Optional.ofNullable(inverseLookup[entry ?: return Optional.empty()]) + } + } +} + +fun createDynamicRegistry(): DynamicRegistryManager.Immutable { + val wrapperLookup = BuiltinRegistries.createWrapperLookup() + return object : DynamicRegistryManager.Immutable { + override fun getOptional(key: RegistryKey>): Optional> { + val lookup = wrapperLookup.getOptionalWrapper(key).getOrNull() ?: return Optional.empty() + val registry = makeRegistry(lookup, key as RegistryKey>) + return Optional.of(registry) + } + + fun entry(reg: RegistryKey>): DynamicRegistryManager.Entry { + return DynamicRegistryManager.Entry(reg, getOptional(reg).get()) + } + + override fun streamAllRegistries(): Stream> { + return wrapperLookup.streamAllRegistryKeys() + .map { entry(it as RegistryKey>) } + } + } +} + +class FakeWorld( + registries: DynamicRegistryManager.Immutable = createDynamicRegistry(), +) : World( + Properties, + RegistryKey.of(RegistryKeys.WORLD, Identifier.of("firmament", "fakeworld")), + registries, + registries[RegistryKeys.DIMENSION_TYPE].entryOf( + RegistryKey.of( + RegistryKeys.DIMENSION_TYPE, + Identifier.of("minecraft", "overworld") + ) + ), + { DummyProfiler.INSTANCE }, + true, + false, + 0, 0 +) { + object Properties : MutableWorldProperties { + override fun getSpawnPos(): BlockPos { + return BlockPos.ORIGIN + } + + override fun getSpawnAngle(): Float { + return 0F + } + + override fun getTime(): Long { + return 0 + } + + override fun getTimeOfDay(): Long { + return 0 + } + + override fun isThundering(): Boolean { + return false + } + + override fun isRaining(): Boolean { + return false + } + + override fun setRaining(raining: Boolean) { + } + + override fun isHardcore(): Boolean { + return false + } + + override fun getGameRules(): GameRules { + return GameRules() + } + + override fun getDifficulty(): Difficulty { + return Difficulty.HARD + } + + override fun isDifficultyLocked(): Boolean { + return false + } + + override fun setSpawnPos(pos: BlockPos?, angle: Float) {} + } + + override fun getPlayers(): List { + return emptyList() + } + + override fun getBrightness(direction: Direction?, shaded: Boolean): Float { + return 1f + } + + override fun getGeneratorStoredBiome(biomeX: Int, biomeY: Int, biomeZ: Int): RegistryEntry { + return registryManager.get(RegistryKeys.BIOME).entryOf(BiomeKeys.PLAINS) + } + + override fun getEnabledFeatures(): FeatureSet { + return FeatureFlags.VANILLA_FEATURES + } + + class FakeTickScheduler : QueryableTickScheduler { + override fun scheduleTick(orderedTick: OrderedTick?) { + } + + override fun isQueued(pos: BlockPos?, type: T): Boolean { + return true + } + + override fun getTickCount(): Int { + return 0 + } + + override fun isTicking(pos: BlockPos?, type: T): Boolean { + return true + } + + } + + override fun getBlockTickScheduler(): QueryableTickScheduler { + return FakeTickScheduler() + } + + override fun getFluidTickScheduler(): QueryableTickScheduler { + return FakeTickScheduler() + } + + + class FakeChunkManager(val world: FakeWorld) : ChunkManager() { + override fun getChunk(x: Int, z: Int, leastStatus: ChunkStatus?, create: Boolean): Chunk { + return EmptyChunk( + world, + ChunkPos(x, z), + world.registryManager.get(RegistryKeys.BIOME).entryOf(BiomeKeys.PLAINS) + ) + } + + override fun getWorld(): BlockView { + return world + } + + override fun tick(shouldKeepTicking: BooleanSupplier?, tickChunks: Boolean) { + } + + override fun getDebugString(): String { + return "FakeChunkManager" + } + + override fun getLoadedChunkCount(): Int { + return 0 + } + + override fun getLightingProvider(): LightingProvider { + return FakeLightingProvider(this) + } + } + + class FakeLightingProvider(chunkManager: FakeChunkManager) : LightingProvider(chunkManager, false, false) + + override fun getChunkManager(): ChunkManager { + return FakeChunkManager(this) + } + + override fun playSound( + source: PlayerEntity?, + x: Double, + y: Double, + z: Double, + sound: RegistryEntry?, + category: SoundCategory?, + volume: Float, + pitch: Float, + seed: Long + ) { + } + + override fun syncWorldEvent(player: PlayerEntity?, eventId: Int, pos: BlockPos?, data: Int) { + } + + override fun emitGameEvent(event: RegistryEntry?, emitterPos: Vec3d?, emitter: GameEvent.Emitter?) { + } + + override fun updateListeners(pos: BlockPos?, oldState: BlockState?, newState: BlockState?, flags: Int) { + } + + override fun playSoundFromEntity( + source: PlayerEntity?, + entity: Entity?, + sound: RegistryEntry?, + category: SoundCategory?, + volume: Float, + pitch: Float, + seed: Long + ) { + } + + override fun asString(): String { + return "FakeWorld" + } + + override fun getEntityById(id: Int): Entity? { + return null + } + + override fun getTickManager(): TickManager { + return TickManager() + } + + override fun getMapState(id: MapIdComponent?): MapState? { + return null + } + + override fun putMapState(id: MapIdComponent?, state: MapState?) { + } + + override fun increaseAndGetMapId(): MapIdComponent { + return MapIdComponent(0) + } + + override fun setBlockBreakingInfo(entityId: Int, pos: BlockPos?, progress: Int) { + } + + override fun getScoreboard(): Scoreboard { + return Scoreboard() + } + + override fun getRecipeManager(): RecipeManager { + return RecipeManager(registryManager) + } + + object FakeEntityLookup : EntityLookup { + override fun get(id: Int): Entity? { + return null + } + + override fun get(uuid: UUID?): Entity? { + return null + } + + override fun iterate(): MutableIterable { + return mutableListOf() + } + + override fun forEachIntersects( + filter: TypeFilter?, + box: Box?, + consumer: LazyIterationConsumer? + ) { + } + + override fun forEachIntersects(box: Box?, action: Consumer?) { + } + + override fun forEach(filter: TypeFilter?, consumer: LazyIterationConsumer?) { + } + + } + + override fun getEntityLookup(): EntityLookup { + return FakeEntityLookup + } + + override fun getBrewingRecipeRegistry(): BrewingRecipeRegistry { + return BrewingRecipeRegistry.EMPTY + } +} diff --git a/src/main/kotlin/gui/entity/GuiPlayer.kt b/src/main/kotlin/gui/entity/GuiPlayer.kt new file mode 100644 index 0000000..d00b44d --- /dev/null +++ b/src/main/kotlin/gui/entity/GuiPlayer.kt @@ -0,0 +1,54 @@ + +package moe.nea.firmament.gui.entity + +import com.mojang.authlib.GameProfile +import java.util.* +import net.minecraft.client.network.AbstractClientPlayerEntity +import net.minecraft.client.util.DefaultSkinHelper +import net.minecraft.client.util.SkinTextures +import net.minecraft.client.util.SkinTextures.Model +import net.minecraft.client.world.ClientWorld +import net.minecraft.util.Identifier +import net.minecraft.util.math.BlockPos +import net.minecraft.world.World + +/** + * @see moe.nea.firmament.init.EarlyRiser + */ +fun makeGuiPlayer(world: FakeWorld): GuiPlayer { + val constructor = GuiPlayer::class.java.getDeclaredConstructor( + World::class.java, + BlockPos::class.java, + Float::class.javaPrimitiveType, + GameProfile::class.java + ) + return constructor.newInstance(world, BlockPos.ORIGIN, 0F, GameProfile(UUID.randomUUID(), "Linnea")) +} + +class GuiPlayer(world: ClientWorld?, profile: GameProfile?) : AbstractClientPlayerEntity(world, profile) { + override fun isSpectator(): Boolean { + return false + } + + override fun isCreative(): Boolean { + return false + } + + override fun shouldRenderName(): Boolean { + return false + } + + var skinTexture: Identifier = DefaultSkinHelper.getSkinTextures(this.getUuid()).texture + var capeTexture: Identifier? = null + var model: Model = Model.WIDE + override fun getSkinTextures(): SkinTextures { + return SkinTextures( + skinTexture, + null, + capeTexture, + null, + model, + true + ) + } +} diff --git a/src/main/kotlin/gui/entity/ModifyAge.kt b/src/main/kotlin/gui/entity/ModifyAge.kt new file mode 100644 index 0000000..a65c368 --- /dev/null +++ b/src/main/kotlin/gui/entity/ModifyAge.kt @@ -0,0 +1,25 @@ + +package moe.nea.firmament.gui.entity + +import com.google.gson.JsonObject +import net.minecraft.entity.LivingEntity +import net.minecraft.entity.decoration.ArmorStandEntity +import net.minecraft.entity.mob.ZombieEntity +import net.minecraft.entity.passive.PassiveEntity + +object ModifyAge : EntityModifier { + override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity { + val isBaby = info["baby"]?.asBoolean ?: false + if (entity is PassiveEntity) { + entity.breedingAge = if (isBaby) -1 else 1 + } else if (entity is ZombieEntity) { + entity.isBaby = isBaby + } else if (entity is ArmorStandEntity) { + entity.isSmall = isBaby + } else { + error("Cannot set age for $entity") + } + return entity + } + +} diff --git a/src/main/kotlin/gui/entity/ModifyCharged.kt b/src/main/kotlin/gui/entity/ModifyCharged.kt new file mode 100644 index 0000000..d22f6e3 --- /dev/null +++ b/src/main/kotlin/gui/entity/ModifyCharged.kt @@ -0,0 +1,14 @@ + +package moe.nea.firmament.gui.entity + +import com.google.gson.JsonObject +import net.minecraft.entity.LivingEntity +import net.minecraft.entity.mob.CreeperEntity + +object ModifyCharged : EntityModifier { + override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity { + require(entity is CreeperEntity) + entity.dataTracker.set(CreeperEntity.CHARGED, true) + return entity + } +} diff --git a/src/main/kotlin/gui/entity/ModifyEquipment.kt b/src/main/kotlin/gui/entity/ModifyEquipment.kt new file mode 100644 index 0000000..73e450e --- /dev/null +++ b/src/main/kotlin/gui/entity/ModifyEquipment.kt @@ -0,0 +1,55 @@ + +package moe.nea.firmament.gui.entity + +import com.google.gson.JsonObject +import net.minecraft.component.DataComponentTypes +import net.minecraft.component.type.DyedColorComponent +import net.minecraft.entity.EquipmentSlot +import net.minecraft.entity.LivingEntity +import net.minecraft.item.ArmorItem +import net.minecraft.item.Item +import net.minecraft.item.ItemStack +import net.minecraft.item.Items +import moe.nea.firmament.rei.SBItemStack +import moe.nea.firmament.util.SkyblockId +import moe.nea.firmament.util.item.setEncodedSkullOwner +import moe.nea.firmament.util.item.zeroUUID + +object ModifyEquipment : EntityModifier { + val names = mapOf( + "hand" to EquipmentSlot.MAINHAND, + "helmet" to EquipmentSlot.HEAD, + "chestplate" to EquipmentSlot.CHEST, + "leggings" to EquipmentSlot.LEGS, + "feet" to EquipmentSlot.FEET, + ) + + override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity { + names.forEach { (key, slot) -> + info[key]?.let { + entity.equipStack(slot, createItem(it.asString)) + } + } + return entity + } + + private fun createItem(item: String): ItemStack { + val split = item.split("#") + if (split.size != 2) return SBItemStack(SkyblockId(item)).asImmutableItemStack() + val (type, data) = split + return when (type) { + "SKULL" -> ItemStack(Items.PLAYER_HEAD).also { it.setEncodedSkullOwner(zeroUUID, data) } + "LEATHER_LEGGINGS" -> coloredLeatherArmor(Items.LEATHER_LEGGINGS, data) + "LEATHER_BOOTS" -> coloredLeatherArmor(Items.LEATHER_BOOTS, data) + "LEATHER_HELMET" -> coloredLeatherArmor(Items.LEATHER_HELMET, data) + "LEATHER_CHESTPLATE" -> coloredLeatherArmor(Items.LEATHER_CHESTPLATE, data) + else -> error("Unknown leather piece: $type") + } + } + + private fun coloredLeatherArmor(leatherArmor: Item, data: String): ItemStack { + val stack = ItemStack(leatherArmor) + stack.set(DataComponentTypes.DYED_COLOR, DyedColorComponent(data.toInt(16), false)) + return stack + } +} diff --git a/src/main/kotlin/gui/entity/ModifyHorse.kt b/src/main/kotlin/gui/entity/ModifyHorse.kt new file mode 100644 index 0000000..8ac011b --- /dev/null +++ b/src/main/kotlin/gui/entity/ModifyHorse.kt @@ -0,0 +1,61 @@ + +package moe.nea.firmament.gui.entity + +import com.google.gson.JsonNull +import com.google.gson.JsonObject +import kotlin.experimental.and +import kotlin.experimental.inv +import kotlin.experimental.or +import net.minecraft.entity.EntityType +import net.minecraft.entity.LivingEntity +import net.minecraft.entity.passive.AbstractHorseEntity +import net.minecraft.item.ItemStack +import net.minecraft.item.Items +import moe.nea.firmament.gui.entity.EntityRenderer.fakeWorld + +object ModifyHorse : EntityModifier { + override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity { + require(entity is AbstractHorseEntity) + var entity: AbstractHorseEntity = entity + info["kind"]?.let { + entity = when (it.asString) { + "skeleton" -> EntityType.SKELETON_HORSE.create(fakeWorld)!! + "zombie" -> EntityType.ZOMBIE_HORSE.create(fakeWorld)!! + "mule" -> EntityType.MULE.create(fakeWorld)!! + "donkey" -> EntityType.DONKEY.create(fakeWorld)!! + "horse" -> EntityType.HORSE.create(fakeWorld)!! + else -> error("Unknown horse kind $it") + } + } + info["armor"]?.let { + if (it is JsonNull) { + entity.setHorseArmor(ItemStack.EMPTY) + } else { + when (it.asString) { + "iron" -> entity.setHorseArmor(ItemStack(Items.IRON_HORSE_ARMOR)) + "golden" -> entity.setHorseArmor(ItemStack(Items.GOLDEN_HORSE_ARMOR)) + "diamond" -> entity.setHorseArmor(ItemStack(Items.DIAMOND_HORSE_ARMOR)) + else -> error("Unknown horse armor $it") + } + } + } + info["saddled"]?.let { + entity.setIsSaddled(it.asBoolean) + } + return entity + } + +} + +fun AbstractHorseEntity.setIsSaddled(shouldBeSaddled: Boolean) { + val oldFlag = dataTracker.get(AbstractHorseEntity.HORSE_FLAGS) + dataTracker.set( + AbstractHorseEntity.HORSE_FLAGS, + if (shouldBeSaddled) oldFlag or AbstractHorseEntity.SADDLED_FLAG.toByte() + else oldFlag and AbstractHorseEntity.SADDLED_FLAG.toByte().inv() + ) +} + +fun AbstractHorseEntity.setHorseArmor(itemStack: ItemStack) { + items.setStack(1, itemStack) +} diff --git a/src/main/kotlin/gui/entity/ModifyInvisible.kt b/src/main/kotlin/gui/entity/ModifyInvisible.kt new file mode 100644 index 0000000..8d36991 --- /dev/null +++ b/src/main/kotlin/gui/entity/ModifyInvisible.kt @@ -0,0 +1,13 @@ + +package moe.nea.firmament.gui.entity + +import com.google.gson.JsonObject +import net.minecraft.entity.LivingEntity + +object ModifyInvisible : EntityModifier { + override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity { + entity.isInvisible = info.get("invisible")?.asBoolean ?: true + return entity + } + +} diff --git a/src/main/kotlin/gui/entity/ModifyName.kt b/src/main/kotlin/gui/entity/ModifyName.kt new file mode 100644 index 0000000..a03da96 --- /dev/null +++ b/src/main/kotlin/gui/entity/ModifyName.kt @@ -0,0 +1,14 @@ + +package moe.nea.firmament.gui.entity + +import com.google.gson.JsonObject +import net.minecraft.entity.LivingEntity +import net.minecraft.text.Text + +object ModifyName : EntityModifier { + override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity { + entity.customName = Text.literal(info.get("name").asString) + return entity + } + +} diff --git a/src/main/kotlin/gui/entity/ModifyPlayerSkin.kt b/src/main/kotlin/gui/entity/ModifyPlayerSkin.kt new file mode 100644 index 0000000..28f0070 --- /dev/null +++ b/src/main/kotlin/gui/entity/ModifyPlayerSkin.kt @@ -0,0 +1,47 @@ + +package moe.nea.firmament.gui.entity + +import com.google.gson.JsonObject +import com.google.gson.JsonPrimitive +import kotlin.experimental.and +import kotlin.experimental.or +import net.minecraft.client.util.SkinTextures +import net.minecraft.entity.LivingEntity +import net.minecraft.entity.player.PlayerEntity +import net.minecraft.entity.player.PlayerModelPart +import net.minecraft.util.Identifier + +object ModifyPlayerSkin : EntityModifier { + val playerModelPartIndex = PlayerModelPart.entries.associateBy { it.getName() } + override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity { + require(entity is GuiPlayer) + info["cape"]?.let { + entity.capeTexture = Identifier.of(it.asString) + } + info["skin"]?.let { + entity.skinTexture = Identifier.of(it.asString) + } + info["slim"]?.let { + entity.model = if (it.asBoolean) SkinTextures.Model.SLIM else SkinTextures.Model.WIDE + } + info["parts"]?.let { + var trackedData = entity.dataTracker.get(PlayerEntity.PLAYER_MODEL_PARTS) + if (it is JsonPrimitive && it.isBoolean) { + trackedData = (if (it.asBoolean) -1 else 0).toByte() + } else { + val obj = it.asJsonObject + for ((k, v) in obj.entrySet()) { + val part = playerModelPartIndex[k]!! + trackedData = if (v.asBoolean) { + trackedData and (part.bitFlag.inv().toByte()) + } else { + trackedData or (part.bitFlag.toByte()) + } + } + } + entity.dataTracker.set(PlayerEntity.PLAYER_MODEL_PARTS, trackedData) + } + return entity + } + +} diff --git a/src/main/kotlin/gui/entity/ModifyRiding.kt b/src/main/kotlin/gui/entity/ModifyRiding.kt new file mode 100644 index 0000000..5c4c78d --- /dev/null +++ b/src/main/kotlin/gui/entity/ModifyRiding.kt @@ -0,0 +1,15 @@ + +package moe.nea.firmament.gui.entity + +import com.google.gson.JsonObject +import net.minecraft.entity.LivingEntity + +object ModifyRiding : EntityModifier { + override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity { + val newEntity = EntityRenderer.constructEntity(info) + require(newEntity != null) + newEntity.startRiding(entity, true) + return entity + } + +} diff --git a/src/main/kotlin/gui/entity/ModifyWither.kt b/src/main/kotlin/gui/entity/ModifyWither.kt new file mode 100644 index 0000000..6083d88 --- /dev/null +++ b/src/main/kotlin/gui/entity/ModifyWither.kt @@ -0,0 +1,20 @@ + +package moe.nea.firmament.gui.entity + +import com.google.gson.JsonObject +import net.minecraft.entity.LivingEntity +import net.minecraft.entity.boss.WitherEntity + +object ModifyWither : EntityModifier { + override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity { + require(entity is WitherEntity) + info["tiny"]?.let { + entity.setInvulTimer(if (it.asBoolean) 800 else 0) + } + info["armored"]?.let { + entity.health = if (it.asBoolean) 1F else entity.maxHealth + } + return entity + } + +} diff --git a/src/main/kotlin/gui/hud/MoulConfigHud.kt b/src/main/kotlin/gui/hud/MoulConfigHud.kt new file mode 100644 index 0000000..e77d9af --- /dev/null +++ b/src/main/kotlin/gui/hud/MoulConfigHud.kt @@ -0,0 +1,66 @@ + +package moe.nea.firmament.gui.hud + +import io.github.notenoughupdates.moulconfig.gui.GuiComponentWrapper +import io.github.notenoughupdates.moulconfig.gui.GuiContext +import io.github.notenoughupdates.moulconfig.gui.component.TextComponent +import net.minecraft.resource.ResourceManager +import net.minecraft.resource.SynchronousResourceReloader +import moe.nea.firmament.events.FinalizeResourceManagerEvent +import moe.nea.firmament.events.HudRenderEvent +import moe.nea.firmament.gui.config.HudMeta +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.MoulConfigUtils + +abstract class MoulConfigHud( + val name: String, + val hudMeta: HudMeta, +) { + companion object { + private val componentWrapper by lazy { + object : GuiComponentWrapper(GuiContext(TextComponent("§cERROR"))) { + init { + this.client = MC.instance + } + } + } + } + + private var fragment: GuiContext? = null + + fun forceInit() { + } + + open fun shouldRender(): Boolean { + return true + } + + init { + require(name.matches("^[a-z_/]+$".toRegex())) + HudRenderEvent.subscribe { + if (!shouldRender()) return@subscribe + val renderContext = componentWrapper.createContext(it.context) + if (fragment == null) + loadFragment() + it.context.matrices.push() + hudMeta.applyTransformations(it.context.matrices) + val renderContextTranslated = + renderContext.translated(hudMeta.absoluteX, hudMeta.absoluteY, hudMeta.width, hudMeta.height) + .scaled(hudMeta.scale) + fragment!!.root.render(renderContextTranslated) + it.context.matrices.pop() + } + FinalizeResourceManagerEvent.subscribe { + MC.resourceManager.registerReloader(object : SynchronousResourceReloader { + override fun reload(manager: ResourceManager?) { + fragment = null + } + }) + } + } + + fun loadFragment() { + fragment = MoulConfigUtils.loadGui(name, this) + } + +} diff --git a/src/main/kotlin/jarvis/JarvisIntegration.kt b/src/main/kotlin/jarvis/JarvisIntegration.kt new file mode 100644 index 0000000..96f47f7 --- /dev/null +++ b/src/main/kotlin/jarvis/JarvisIntegration.kt @@ -0,0 +1,64 @@ + + +package moe.nea.firmament.jarvis + +import moe.nea.jarvis.api.Jarvis +import moe.nea.jarvis.api.JarvisConfigOption +import moe.nea.jarvis.api.JarvisHud +import moe.nea.jarvis.api.JarvisPlugin +import net.minecraft.client.gui.screen.Screen +import net.minecraft.text.Text +import moe.nea.firmament.Firmament +import moe.nea.firmament.features.FeatureManager +import moe.nea.firmament.gui.config.HudMeta +import moe.nea.firmament.gui.config.HudMetaHandler +import moe.nea.firmament.repo.RepoManager + +class JarvisIntegration : JarvisPlugin { + override fun getModId(): String = + Firmament.MOD_ID + + companion object { + lateinit var jarvis: Jarvis + } + + override fun onInitialize(jarvis: Jarvis) { + Companion.jarvis = jarvis + } + + val configs + get() = listOf( + RepoManager.Config + ) + FeatureManager.allFeatures.mapNotNull { it.config } + + + override fun getAllHuds(): List { + return configs.flatMap { config -> + config.sortedOptions.mapNotNull { if (it.handler is HudMetaHandler) it.value as HudMeta else null } + } + } + + override fun onHudEditorClosed() { + configs.forEach { it.save() } + } + + override fun getAllConfigOptions(): List { + return configs.flatMap { config -> + config.sortedOptions.map { + object : JarvisConfigOption { + override fun title(): Text { + return it.labelText + } + + override fun description(): List { + return emptyList() + } + + override fun jumpTo(parentScreen: Screen?): Screen { + return config.getConfigEditor(parentScreen) + } + } + } + } + } +} diff --git a/src/main/kotlin/keybindings/FirmamentKeyBindings.kt b/src/main/kotlin/keybindings/FirmamentKeyBindings.kt new file mode 100644 index 0000000..e2bed8d --- /dev/null +++ b/src/main/kotlin/keybindings/FirmamentKeyBindings.kt @@ -0,0 +1,26 @@ + + +package moe.nea.firmament.keybindings + +import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper +import net.minecraft.client.option.KeyBinding +import net.minecraft.client.util.InputUtil +import moe.nea.firmament.gui.config.KeyBindingHandler +import moe.nea.firmament.gui.config.ManagedOption + +object FirmamentKeyBindings { + fun registerKeyBinding(name: String, config: ManagedOption) { + val vanillaKeyBinding = KeyBindingHelper.registerKeyBinding( + KeyBinding( + name, + InputUtil.Type.KEYSYM, + -1, + "firmament.key.category" + ) + ) + keyBindings[vanillaKeyBinding] = config + } + + val keyBindings = mutableMapOf>() + +} diff --git a/src/main/kotlin/keybindings/IKeyBinding.kt b/src/main/kotlin/keybindings/IKeyBinding.kt new file mode 100644 index 0000000..1975361 --- /dev/null +++ b/src/main/kotlin/keybindings/IKeyBinding.kt @@ -0,0 +1,29 @@ + + +package moe.nea.firmament.keybindings + +import net.minecraft.client.option.KeyBinding + +interface IKeyBinding { + fun matches(keyCode: Int, scanCode: Int, modifiers: Int): Boolean + + fun withModifiers(wantedModifiers: Int): IKeyBinding { + val old = this + return object : IKeyBinding { + override fun matches(keyCode: Int, scanCode: Int, modifiers: Int): Boolean { + return old.matches(keyCode, scanCode, modifiers) && (modifiers and wantedModifiers) == wantedModifiers + } + } + } + + companion object { + fun minecraft(keyBinding: KeyBinding) = object : IKeyBinding { + override fun matches(keyCode: Int, scanCode: Int, modifiers: Int) = + keyBinding.matchesKey(keyCode, scanCode) + } + + fun ofKeyCode(wantedKeyCode: Int) = object : IKeyBinding { + override fun matches(keyCode: Int, scanCode: Int, modifiers: Int): Boolean = keyCode == wantedKeyCode + } + } +} diff --git a/src/main/kotlin/keybindings/SavedKeyBinding.kt b/src/main/kotlin/keybindings/SavedKeyBinding.kt new file mode 100644 index 0000000..8607fd0 --- /dev/null +++ b/src/main/kotlin/keybindings/SavedKeyBinding.kt @@ -0,0 +1,106 @@ + + +package moe.nea.firmament.keybindings + +import org.lwjgl.glfw.GLFW +import kotlinx.serialization.Serializable +import net.minecraft.client.MinecraftClient +import net.minecraft.client.util.InputUtil +import net.minecraft.text.Text +import moe.nea.firmament.util.MC + +@Serializable +data class SavedKeyBinding( + val keyCode: Int, + val shift: Boolean = false, + val ctrl: Boolean = false, + val alt: Boolean = false, +) : IKeyBinding { + val isBound: Boolean get() = keyCode != GLFW.GLFW_KEY_UNKNOWN + + constructor(keyCode: Int, mods: Triple) : this( + keyCode, + mods.first && keyCode != GLFW.GLFW_KEY_LEFT_SHIFT && keyCode != GLFW.GLFW_KEY_RIGHT_SHIFT, + mods.second && keyCode != GLFW.GLFW_KEY_LEFT_CONTROL && keyCode != GLFW.GLFW_KEY_RIGHT_CONTROL, + mods.third && keyCode != GLFW.GLFW_KEY_LEFT_ALT && keyCode != GLFW.GLFW_KEY_RIGHT_ALT, + ) + + constructor(keyCode: Int, mods: Int) : this(keyCode, getMods(mods)) + + companion object { + fun getMods(modifiers: Int): Triple { + return Triple( + modifiers and GLFW.GLFW_MOD_SHIFT != 0, + modifiers and GLFW.GLFW_MOD_CONTROL != 0, + modifiers and GLFW.GLFW_MOD_ALT != 0 + ) + } + + fun getModInt(): Int { + val h = MC.window.handle + val ctrl = if (MinecraftClient.IS_SYSTEM_MAC) { + InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_LEFT_SUPER) + || InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_RIGHT_SUPER) + } else InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_LEFT_CONTROL) + || InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_RIGHT_CONTROL) + val shift = isShiftDown() + val alt = InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_LEFT_ALT) + || InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_RIGHT_ALT) + var mods = 0 + if (ctrl) mods = mods or GLFW.GLFW_MOD_CONTROL + if (shift) mods = mods or GLFW.GLFW_MOD_SHIFT + if (alt) mods = mods or GLFW.GLFW_MOD_ALT + return mods + } + + private val h get() = MC.window.handle + fun isShiftDown() = InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_LEFT_SHIFT) + || InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_RIGHT_SHIFT) + + } + + fun isPressed(atLeast: Boolean = false): Boolean { + if (!isBound) return false + val h = MC.window.handle + if (!InputUtil.isKeyPressed(h, keyCode)) return false + + val ctrl = if (MinecraftClient.IS_SYSTEM_MAC) { + InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_LEFT_SUPER) + || InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_RIGHT_SUPER) + } else InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_LEFT_CONTROL) + || InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_RIGHT_CONTROL) + val shift = isShiftDown() + val alt = InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_LEFT_ALT) + || InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_RIGHT_ALT) + if (atLeast) + return (ctrl >= this.ctrl) && + (alt >= this.alt) && + (shift >= this.shift) + + return (ctrl == this.ctrl) && + (alt == this.alt) && + (shift == this.shift) + } + + override fun matches(keyCode: Int, scanCode: Int, modifiers: Int): Boolean { + if (this.keyCode == GLFW.GLFW_KEY_UNKNOWN) return false + return keyCode == this.keyCode && getMods(modifiers) == Triple(shift, ctrl, alt) + } + + fun format(): Text { + val stroke = Text.literal("") + if (ctrl) { + stroke.append("CTRL + ") + } + if (alt) { + stroke.append("ALT + ") + } + if (shift) { + stroke.append("SHIFT + ") // TODO: translations? + } + + stroke.append(InputUtil.Type.KEYSYM.createFromCode(keyCode).localizedText) + return stroke + } + +} diff --git a/src/main/kotlin/modmenu/FirmamentModMenuPlugin.kt b/src/main/kotlin/modmenu/FirmamentModMenuPlugin.kt new file mode 100644 index 0000000..f889bf3 --- /dev/null +++ b/src/main/kotlin/modmenu/FirmamentModMenuPlugin.kt @@ -0,0 +1,14 @@ + + +package moe.nea.firmament.modmenu + +import com.terraformersmc.modmenu.api.ConfigScreenFactory +import com.terraformersmc.modmenu.api.ModMenuApi +import moe.nea.firmament.gui.config.AllConfigsGui + +class FirmamentModMenuPlugin : ModMenuApi { + override fun getModConfigScreenFactory(): ConfigScreenFactory<*> { + return ConfigScreenFactory { AllConfigsGui.makeScreen(it) } + } +} + diff --git a/src/main/kotlin/moe/nea/firmament/Firmament.kt b/src/main/kotlin/moe/nea/firmament/Firmament.kt deleted file mode 100644 index c1801f4..0000000 --- a/src/main/kotlin/moe/nea/firmament/Firmament.kt +++ /dev/null @@ -1,148 +0,0 @@ - - -package moe.nea.firmament - -import com.mojang.brigadier.CommandDispatcher -import io.ktor.client.HttpClient -import io.ktor.client.plugins.UserAgent -import io.ktor.client.plugins.cache.HttpCache -import io.ktor.client.plugins.compression.ContentEncoding -import io.ktor.client.plugins.contentnegotiation.ContentNegotiation -import io.ktor.client.plugins.logging.LogLevel -import io.ktor.client.plugins.logging.Logging -import io.ktor.serialization.kotlinx.json.json -import java.io.InputStream -import java.nio.file.Files -import java.nio.file.Path -import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback -import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource -import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents -import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents -import net.fabricmc.fabric.api.client.item.v1.ItemTooltipCallback -import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents -import net.fabricmc.loader.api.FabricLoader -import net.fabricmc.loader.api.Version -import net.fabricmc.loader.api.metadata.ModMetadata -import org.apache.logging.log4j.LogManager -import org.apache.logging.log4j.Logger -import kotlinx.coroutines.CoroutineName -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.plus -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.decodeFromStream -import kotlin.coroutines.EmptyCoroutineContext -import net.minecraft.client.render.chunk.SectionBuilder -import net.minecraft.command.CommandRegistryAccess -import net.minecraft.util.Identifier -import moe.nea.firmament.commands.registerFirmamentCommand -import moe.nea.firmament.events.ClientStartedEvent -import moe.nea.firmament.events.CommandEvent -import moe.nea.firmament.events.ItemTooltipEvent -import moe.nea.firmament.events.ScreenRenderPostEvent -import moe.nea.firmament.events.TickEvent -import moe.nea.firmament.events.registration.registerFirmamentEvents -import moe.nea.firmament.features.FeatureManager -import moe.nea.firmament.repo.HypixelStaticData -import moe.nea.firmament.repo.RepoManager -import moe.nea.firmament.util.MC -import moe.nea.firmament.util.SBData -import moe.nea.firmament.util.data.IDataHolder - -object Firmament { - const val MOD_ID = "firmament" - - val DEBUG = System.getProperty("firmament.debug") == "true" - val DATA_DIR: Path = Path.of(".firmament").also { Files.createDirectories(it) } - val CONFIG_DIR: Path = Path.of("config/firmament").also { Files.createDirectories(it) } - val logger: Logger = LogManager.getLogger("Firmament") - private val metadata: ModMetadata by lazy { - FabricLoader.getInstance().getModContainer(MOD_ID).orElseThrow().metadata - } - val version: Version by lazy { metadata.version } - - val json = Json { - prettyPrint = DEBUG - isLenient = true - ignoreUnknownKeys = true - encodeDefaults = true - } - - val httpClient by lazy { - HttpClient { - install(ContentNegotiation) { - json(json) - } - install(ContentEncoding) { - gzip() - deflate() - } - install(UserAgent) { - agent = "Firmament/$version" - } - if (DEBUG) - install(Logging) { - level = LogLevel.INFO - } - install(HttpCache) - } - } - - val globalJob = Job() - val coroutineScope = - CoroutineScope(EmptyCoroutineContext + CoroutineName("Firmament")) + SupervisorJob(globalJob) - - private fun registerCommands( - dispatcher: CommandDispatcher, - @Suppress("UNUSED_PARAMETER") - ctx: CommandRegistryAccess - ) { - registerFirmamentCommand(dispatcher) - CommandEvent.publish(CommandEvent(dispatcher, ctx, MC.networkHandler?.commandDispatcher)) - } - - @JvmStatic - fun onInitialize() { - } - - @JvmStatic - fun onClientInitialize() { - FeatureManager.subscribeEvents() - var tick = 0 - ClientTickEvents.END_CLIENT_TICK.register(ClientTickEvents.EndTick { instance -> - TickEvent.publish(TickEvent(tick++)) - }) - IDataHolder.registerEvents() - RepoManager.initialize() - SBData.init() - FeatureManager.autoload() - HypixelStaticData.spawnDataCollectionLoop() - ClientCommandRegistrationCallback.EVENT.register(this::registerCommands) - ClientLifecycleEvents.CLIENT_STARTED.register(ClientLifecycleEvents.ClientStarted { - ClientStartedEvent.publish(ClientStartedEvent()) - }) - ClientLifecycleEvents.CLIENT_STOPPING.register(ClientLifecycleEvents.ClientStopping { - logger.info("Shutting down Firmament coroutines") - globalJob.cancel() - }) - registerFirmamentEvents() - ItemTooltipCallback.EVENT.register { stack, context, type, lines -> - ItemTooltipEvent.publish(ItemTooltipEvent(stack, context, type, lines)) - } - ScreenEvents.AFTER_INIT.register(ScreenEvents.AfterInit { client, screen, scaledWidth, scaledHeight -> - ScreenEvents.afterRender(screen) - .register(ScreenEvents.AfterRender { screen, drawContext, mouseX, mouseY, tickDelta -> - ScreenRenderPostEvent.publish(ScreenRenderPostEvent(screen, mouseX, mouseY, tickDelta, drawContext)) - }) - }) - } - - - fun identifier(path: String) = Identifier.of(MOD_ID, path) - inline fun tryDecodeJsonFromStream(inputStream: InputStream): Result { - return runCatching { - json.decodeFromStream(inputStream) - } - } -} diff --git a/src/main/kotlin/moe/nea/firmament/apis/Profiles.kt b/src/main/kotlin/moe/nea/firmament/apis/Profiles.kt deleted file mode 100644 index 789364a..0000000 --- a/src/main/kotlin/moe/nea/firmament/apis/Profiles.kt +++ /dev/null @@ -1,194 +0,0 @@ - - -@file:UseSerializers(DashlessUUIDSerializer::class, InstantAsLongSerializer::class) - -package moe.nea.firmament.apis - -import io.github.moulberry.repo.constants.Leveling -import io.github.moulberry.repo.data.Rarity -import kotlinx.datetime.Instant -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.UseSerializers -import moe.nea.firmament.repo.RepoManager -import moe.nea.firmament.util.LegacyFormattingCode -import moe.nea.firmament.util.SkyblockId -import moe.nea.firmament.util.assertNotNullOr -import moe.nea.firmament.util.json.DashlessUUIDSerializer -import moe.nea.firmament.util.json.InstantAsLongSerializer -import net.minecraft.util.DyeColor -import net.minecraft.util.Formatting -import java.util.* -import kotlin.reflect.KProperty1 - - -@Serializable -data class CollectionSkillData( - val items: Map -) - -@Serializable -data class CollectionResponse( - val success: Boolean, - val collections: Map -) - -@Serializable -data class CollectionInfo( - val name: String, - val maxTiers: Int, - val tiers: List -) - -@Serializable -data class CollectionTier( - val tier: Int, - val amountRequired: Long, - val unlocks: List, -) - - -@Serializable -data class Profiles( - val success: Boolean, - val profiles: List? -) - -@Serializable -data class Profile( - @SerialName("profile_id") - val profileId: UUID, - @SerialName("cute_name") - val cuteName: String, - val selected: Boolean = false, - val members: Map, -) - -enum class Skill(val accessor: KProperty1, val color: DyeColor, val icon: SkyblockId) { - FARMING(Member::experienceSkillFarming, DyeColor.YELLOW, SkyblockId("ROOKIE_HOE")), - FORAGING(Member::experienceSkillForaging, DyeColor.BROWN, SkyblockId("TREECAPITATOR_AXE")), - MINING(Member::experienceSkillMining, DyeColor.LIGHT_GRAY, SkyblockId("DIAMOND_PICKAXE")), - ALCHEMY(Member::experienceSkillAlchemy, DyeColor.PURPLE, SkyblockId("BREWING_STAND")), - TAMING(Member::experienceSkillTaming, DyeColor.GREEN, SkyblockId("SUPER_EGG")), - FISHING(Member::experienceSkillFishing, DyeColor.BLUE, SkyblockId("FARMER_ROD")), - RUNECRAFTING(Member::experienceSkillRunecrafting, DyeColor.PINK, SkyblockId("MUSIC_RUNE;1")), - CARPENTRY(Member::experienceSkillCarpentry, DyeColor.ORANGE, SkyblockId("WORKBENCH")), - COMBAT(Member::experienceSkillCombat, DyeColor.RED, SkyblockId("UNDEAD_SWORD")), - SOCIAL(Member::experienceSkillSocial, DyeColor.WHITE, SkyblockId("EGG_HUNT")), - ENCHANTING(Member::experienceSkillEnchanting, DyeColor.MAGENTA, SkyblockId("ENCHANTMENT_TABLE")), - ; - - fun getMaximumLevel(leveling: Leveling) = assertNotNullOr(leveling.maximumLevels[name.lowercase()]) { 50 } - - fun getLadder(leveling: Leveling): List { - if (this == SOCIAL) return leveling.socialExperienceRequiredPerLevel - if (this == RUNECRAFTING) return leveling.runecraftingExperienceRequiredPerLevel - return leveling.skillExperienceRequiredPerLevel - } -} - -enum class CollectionCategory(val skill: Skill?, val color: DyeColor, val icon: SkyblockId) { - FARMING(Skill.FARMING, DyeColor.YELLOW, SkyblockId("ROOKIE_HOE")), - FORAGING(Skill.FORAGING, DyeColor.BROWN, SkyblockId("TREECAPITATOR_AXE")), - MINING(Skill.MINING, DyeColor.LIGHT_GRAY, SkyblockId("DIAMOND_PICKAXE")), - FISHING(Skill.FISHING, DyeColor.BLUE, SkyblockId("FARMER_ROD")), - COMBAT(Skill.COMBAT, DyeColor.RED, SkyblockId("UNDEAD_SWORD")), - RIFT(null, DyeColor.PURPLE, SkyblockId("SKYBLOCK_MOTE")), -} - -@Serializable -@JvmInline -value class CollectionType(val string: String) { - val skyblockId get() = SkyblockId(string.replace(":", "-").replace("MUSHROOM_COLLECTION", "HUGE_MUSHROOM_2")) -} - -@Serializable -data class Member( - val pets: List = listOf(), - @SerialName("coop_invitation") - val coopInvitation: CoopInvitation? = null, - @SerialName("experience_skill_farming") - val experienceSkillFarming: Double = 0.0, - @SerialName("experience_skill_alchemy") - val experienceSkillAlchemy: Double = 0.0, - @SerialName("experience_skill_combat") - val experienceSkillCombat: Double = 0.0, - @SerialName("experience_skill_taming") - val experienceSkillTaming: Double = 0.0, - @SerialName("experience_skill_social2") - val experienceSkillSocial: Double = 0.0, - @SerialName("experience_skill_enchanting") - val experienceSkillEnchanting: Double = 0.0, - @SerialName("experience_skill_fishing") - val experienceSkillFishing: Double = 0.0, - @SerialName("experience_skill_foraging") - val experienceSkillForaging: Double = 0.0, - @SerialName("experience_skill_mining") - val experienceSkillMining: Double = 0.0, - @SerialName("experience_skill_runecrafting") - val experienceSkillRunecrafting: Double = 0.0, - @SerialName("experience_skill_carpentry") - val experienceSkillCarpentry: Double = 0.0, - val collection: Map = mapOf() -) - -@Serializable -data class CoopInvitation( - val timestamp: Instant, - @SerialName("invited_by") - val invitedBy: UUID? = null, - val confirmed: Boolean, -) - -@JvmInline -@Serializable -value class PetType(val name: String) - -@Serializable -data class Pet( - val uuid: UUID? = null, - val type: PetType, - val exp: Double = 0.0, - val active: Boolean = false, - val tier: Rarity, - val candyUsed: Int = 0, - val heldItem: String? = null, - val skin: String? = null, -) { - val itemId get() = SkyblockId("${type.name};${tier.ordinal}") -} - -@Serializable -data class PlayerResponse( - val success: Boolean, - val player: PlayerData, -) - -@Serializable -data class PlayerData( - val uuid: UUID, - val firstLogin: Instant, - val lastLogin: Instant? = null, - @SerialName("playername") - val playerName: String, - val achievementsOneTime: List = listOf(), - @SerialName("newPackageRank") - val packageRank: String? = null, - val monthlyPackageRank: String? = null, - val rankPlusColor: String = "GOLD" -) { - val rankPlusDyeColor = LegacyFormattingCode.values().find { it.name == rankPlusColor } ?: LegacyFormattingCode.GOLD - val rankData get() = RepoManager.neuRepo.constants.misc.ranks[if (monthlyPackageRank == "NONE" || monthlyPackageRank == null) packageRank else monthlyPackageRank] - fun getDisplayName(name: String = playerName) = rankData?.let { - ("§${it.color}[${it.tag}${rankPlusDyeColor.modern}" + - "${it.plus ?: ""}§${it.color}] $name") - } ?: "${Formatting.GRAY}$name" - - -} - -@Serializable -data class AshconNameLookup( - val username: String, - val uuid: UUID, -) diff --git a/src/main/kotlin/moe/nea/firmament/apis/Routes.kt b/src/main/kotlin/moe/nea/firmament/apis/Routes.kt deleted file mode 100644 index bf55a2d..0000000 --- a/src/main/kotlin/moe/nea/firmament/apis/Routes.kt +++ /dev/null @@ -1,95 +0,0 @@ - - -package moe.nea.firmament.apis - -import io.ktor.client.call.* -import io.ktor.client.request.* -import io.ktor.http.* -import io.ktor.util.* -import java.util.* -import kotlinx.coroutines.Deferred -import kotlinx.coroutines.async -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import kotlin.collections.MutableMap -import kotlin.collections.listOf -import kotlin.collections.mutableMapOf -import kotlin.collections.set -import moe.nea.firmament.Firmament -import moe.nea.firmament.util.MinecraftDispatcher - -object Routes { - private val nameToUUID: MutableMap> = CaseInsensitiveMap() - private val profiles: MutableMap> = mutableMapOf() - private val accounts: MutableMap> = mutableMapOf() - private val UUIDToName: MutableMap> = mutableMapOf() - - suspend fun getPlayerNameForUUID(uuid: UUID): String? { - return withContext(MinecraftDispatcher) { - UUIDToName.computeIfAbsent(uuid) { - async(Firmament.coroutineScope.coroutineContext) { - val response = Firmament.httpClient.get("https://api.ashcon.app/mojang/v2/user/$uuid") - if (!response.status.isSuccess()) return@async null - val data = response.body() - launch(MinecraftDispatcher) { - nameToUUID[data.username] = async { data.uuid } - } - data.username - } - } - }.await() - } - - suspend fun getUUIDForPlayerName(name: String): UUID? { - return withContext(MinecraftDispatcher) { - nameToUUID.computeIfAbsent(name) { - async(Firmament.coroutineScope.coroutineContext) { - val response = Firmament.httpClient.get("https://api.ashcon.app/mojang/v2/user/$name") - if (!response.status.isSuccess()) return@async null - val data = response.body() - launch(MinecraftDispatcher) { - UUIDToName[data.uuid] = async { data.username } - } - data.uuid - } - } - }.await() - } - - suspend fun getAccountData(uuid: UUID): PlayerData? { - return withContext(MinecraftDispatcher) { - accounts.computeIfAbsent(uuid) { - async(Firmament.coroutineScope.coroutineContext) { - val response = UrsaManager.request(listOf("v1", "hypixel","player", uuid.toString())) - if (!response.status.isSuccess()) { - launch(MinecraftDispatcher) { - @Suppress("DeferredResultUnused") - accounts.remove(uuid) - } - return@async null - } - response.body().player - } - } - }.await() - } - - suspend fun getProfiles(uuid: UUID): Profiles? { - return withContext(MinecraftDispatcher) { - profiles.computeIfAbsent(uuid) { - async(Firmament.coroutineScope.coroutineContext) { - val response = UrsaManager.request(listOf("v1", "hypixel","profiles", uuid.toString())) - if (!response.status.isSuccess()) { - launch(MinecraftDispatcher) { - @Suppress("DeferredResultUnused") - profiles.remove(uuid) - } - return@async null - } - response.body() - } - } - }.await() - } - -} diff --git a/src/main/kotlin/moe/nea/firmament/apis/UrsaManager.kt b/src/main/kotlin/moe/nea/firmament/apis/UrsaManager.kt deleted file mode 100644 index 13f7aef..0000000 --- a/src/main/kotlin/moe/nea/firmament/apis/UrsaManager.kt +++ /dev/null @@ -1,72 +0,0 @@ - - -package moe.nea.firmament.apis - -import io.ktor.client.request.* -import io.ktor.client.statement.* -import io.ktor.http.* -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.withContext -import moe.nea.firmament.Firmament -import net.minecraft.client.MinecraftClient -import java.time.Duration -import java.time.Instant -import java.util.* - -object UrsaManager { - private data class Token( - val validUntil: Instant, - val token: String, - val obtainedFrom: String, - ) { - fun isValid(host: String) = Instant.now().plusSeconds(60) < validUntil && obtainedFrom == host - } - - private var currentToken: Token? = null - private val lock = Mutex() - private fun getToken(host: String) = currentToken?.takeIf { it.isValid(host) } - - suspend fun request(path: List): HttpResponse { - var didLock = false - try { - val host = "ursa.notenoughupdates.org" - var token = getToken(host) - if (token == null) { - lock.lock() - didLock = true - token = getToken(host) - } - val response = Firmament.httpClient.get { - url { - this.host = host - appendPathSegments(path, encodeSlash = true) - } - if (token == null) { - withContext(Dispatchers.IO) { - val mc = MinecraftClient.getInstance() - val serverId = UUID.randomUUID().toString() - mc.sessionService.joinServer(mc.session.uuidOrNull, mc.session.accessToken, serverId) - header("x-ursa-username", mc.session.username) - header("x-ursa-serverid", serverId) - } - } else { - header("x-ursa-token", token.token) - } - } - val savedToken = response.headers["x-ursa-token"] - if (savedToken != null) { - val validUntil = response.headers["x-ursa-expires"]?.toLongOrNull()?.let { Instant.ofEpochMilli(it) } - ?: (Instant.now() + Duration.ofMinutes(55)) - currentToken = Token(validUntil, savedToken, host) - } - if (response.status.value != 200) { - Firmament.logger.error("Failed to contact ursa minor: ${response.bodyAsText()}") - } - return response - } finally { - if (didLock) - lock.unlock() - } - } -} diff --git a/src/main/kotlin/moe/nea/firmament/commands/CaseInsensitiveLiteralCommandNode.kt b/src/main/kotlin/moe/nea/firmament/commands/CaseInsensitiveLiteralCommandNode.kt deleted file mode 100644 index 10772b0..0000000 --- a/src/main/kotlin/moe/nea/firmament/commands/CaseInsensitiveLiteralCommandNode.kt +++ /dev/null @@ -1,75 +0,0 @@ - - -package moe.nea.firmament.commands - -import com.mojang.brigadier.Command -import com.mojang.brigadier.RedirectModifier -import com.mojang.brigadier.StringReader -import com.mojang.brigadier.builder.LiteralArgumentBuilder -import com.mojang.brigadier.context.CommandContextBuilder -import com.mojang.brigadier.context.StringRange -import com.mojang.brigadier.exceptions.CommandSyntaxException -import com.mojang.brigadier.tree.CommandNode -import com.mojang.brigadier.tree.LiteralCommandNode -import java.util.function.Predicate - -class CaseInsensitiveLiteralCommandNode( - literal: String, command: Command?, requirement: Predicate?, - redirect: CommandNode?, modifier: RedirectModifier?, forks: Boolean -) : LiteralCommandNode( - literal.lowercase(), command, requirement, redirect, modifier, forks -) { - class Builder(literal: String) : LiteralArgumentBuilder(literal) { - override fun build(): LiteralCommandNode { - val result = CaseInsensitiveLiteralCommandNode( - literal, - command, requirement, redirect, redirectModifier, isFork - ) - for (argument in arguments) { - result.addChild(argument) - } - return result - } - } - - override fun createBuilder(): LiteralArgumentBuilder { - return Builder(literal).also { - it.requires(requirement) - it.forward(redirect, redirectModifier, isFork) - if (command != null) - it.executes(command) - } - } - - override fun parse(reader: StringReader, contextBuilder: CommandContextBuilder) { - val start = reader.cursor - val end = parse0(reader) - if (end > -1) { - contextBuilder.withNode(this, StringRange.between(start, end)) - return - } - - throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.literalIncorrect().createWithContext(reader, literal) - } - - override fun toString(): String { - return "" - } - - private fun parse0(reader: StringReader): Int { - val start = reader.cursor - if (reader.canRead(literal.length)) { - val end = start + literal.length - if (reader.string.substring(start, end).equals(literal, true)) { - reader.cursor = end - if (!reader.canRead() || reader.peek() == ' ') { - return end - } else { - reader.cursor = start - } - } - } - return -1 - } - -} diff --git a/src/main/kotlin/moe/nea/firmament/commands/RestArgumentType.kt b/src/main/kotlin/moe/nea/firmament/commands/RestArgumentType.kt deleted file mode 100644 index 361907f..0000000 --- a/src/main/kotlin/moe/nea/firmament/commands/RestArgumentType.kt +++ /dev/null @@ -1,15 +0,0 @@ - - -package moe.nea.firmament.commands - -import com.mojang.brigadier.StringReader -import com.mojang.brigadier.arguments.ArgumentType - -object RestArgumentType : ArgumentType { - override fun parse(reader: StringReader): String { - val remaining = reader.remaining - reader.cursor += remaining.length - return remaining - } - -} diff --git a/src/main/kotlin/moe/nea/firmament/commands/dsl.kt b/src/main/kotlin/moe/nea/firmament/commands/dsl.kt deleted file mode 100644 index d1f0d8c..0000000 --- a/src/main/kotlin/moe/nea/firmament/commands/dsl.kt +++ /dev/null @@ -1,118 +0,0 @@ - - -package moe.nea.firmament.commands - -import com.mojang.brigadier.arguments.ArgumentType -import com.mojang.brigadier.builder.ArgumentBuilder -import com.mojang.brigadier.builder.RequiredArgumentBuilder -import com.mojang.brigadier.context.CommandContext -import com.mojang.brigadier.suggestion.SuggestionProvider -import kotlinx.coroutines.launch -import moe.nea.firmament.Firmament -import moe.nea.firmament.util.MinecraftDispatcher -import moe.nea.firmament.util.iterate -import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource -import java.lang.reflect.ParameterizedType -import java.lang.reflect.Type -import java.lang.reflect.TypeVariable - - -typealias DefaultSource = FabricClientCommandSource - - -inline val > T.context get() = this -operator fun > C.get(arg: TypeSafeArg): T { - return arg.get(this) -} - -fun literal( - name: String, - block: CaseInsensitiveLiteralCommandNode.Builder.() -> Unit -): CaseInsensitiveLiteralCommandNode.Builder = - CaseInsensitiveLiteralCommandNode.Builder(name).also(block) - - -private fun normalizeGeneric(argument: Type): Class<*> { - return when (argument) { - is Class<*> -> argument - is TypeVariable<*> -> normalizeGeneric(argument.bounds[0]) - is ParameterizedType -> normalizeGeneric(argument.rawType) - else -> Any::class.java - } -} - -data class TypeSafeArg(val name: String, val argument: ArgumentType) { - val argClass by lazy { - argument.javaClass - .iterate>> { - it.superclass - } - .flatMap { - it.genericInterfaces.toList() - } - .filterIsInstance() - .find { it.rawType == ArgumentType::class.java }!! - .let { normalizeGeneric(it.actualTypeArguments[0]) } - } - - @JvmName("getWithThis") - fun CommandContext.get(): T = - get(this) - - - fun get(ctx: CommandContext): T { - try { - return ctx.getArgument(name, argClass) as T - } catch (e: Exception) { - if (ctx.child != null) { - return get(ctx.child) - } - throw e - } - } -} - - -fun argument( - name: String, - argument: ArgumentType, - block: RequiredArgumentBuilder.(TypeSafeArg) -> Unit -): RequiredArgumentBuilder = - RequiredArgumentBuilder.argument(name, argument).also { block(it, TypeSafeArg(name, argument)) } - -fun , AT : Any> T.thenArgument( - name: String, - argument: ArgumentType, - block: RequiredArgumentBuilder.(TypeSafeArg) -> Unit -): T = then(argument(name, argument, block)) - -fun > T.suggestsList(provider: CommandContext.() -> Iterable) { - suggests(SuggestionProvider { context, builder -> - provider(context) - .asSequence() - .filter { it.startsWith(builder.remaining, ignoreCase = true) } - .forEach { - builder.suggest(it) - } - builder.buildFuture() - }) -} - -fun > T.thenLiteral( - name: String, - block: CaseInsensitiveLiteralCommandNode.Builder.() -> Unit -): T = - then(literal(name, block)) - -fun > T.then(node: ArgumentBuilder, block: T.() -> Unit): T = - then(node).also(block) - -fun > T.thenExecute(block: suspend CommandContext.() -> Unit): T = - executes { - Firmament.coroutineScope.launch(MinecraftDispatcher) { - block(it) - } - 1 - } - - diff --git a/src/main/kotlin/moe/nea/firmament/commands/rome.kt b/src/main/kotlin/moe/nea/firmament/commands/rome.kt deleted file mode 100644 index 015512d..0000000 --- a/src/main/kotlin/moe/nea/firmament/commands/rome.kt +++ /dev/null @@ -1,230 +0,0 @@ - - -package moe.nea.firmament.commands - -import com.mojang.brigadier.CommandDispatcher -import com.mojang.brigadier.arguments.StringArgumentType.string -import io.ktor.client.statement.bodyAsText -import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource -import net.minecraft.text.Text -import moe.nea.firmament.apis.UrsaManager -import moe.nea.firmament.events.CommandEvent -import moe.nea.firmament.features.debug.PowerUserTools -import moe.nea.firmament.features.inventory.buttons.InventoryButtons -import moe.nea.firmament.features.inventory.storageoverlay.StorageOverlayScreen -import moe.nea.firmament.features.inventory.storageoverlay.StorageOverviewScreen -import moe.nea.firmament.gui.config.AllConfigsGui -import moe.nea.firmament.gui.config.BooleanHandler -import moe.nea.firmament.gui.config.ManagedOption -import moe.nea.firmament.repo.HypixelStaticData -import moe.nea.firmament.repo.RepoManager -import moe.nea.firmament.util.FirmFormatters -import moe.nea.firmament.util.MC -import moe.nea.firmament.util.SBData -import moe.nea.firmament.util.ScreenUtil -import moe.nea.firmament.util.SkyblockId - - -fun firmamentCommand() = literal("firmament") { - thenLiteral("config") { - thenExecute { - AllConfigsGui.showAllGuis() - } - thenLiteral("toggle") { - thenArgument("config", string()) { config -> - suggestsList { - AllConfigsGui.allConfigs.asSequence().map { it.name }.asIterable() - } - thenArgument("property", string()) { property -> - suggestsList { - (AllConfigsGui.allConfigs.find { it.name == this[config] } ?: return@suggestsList listOf()) - .allOptions.entries.asSequence().filter { it.value.handler is BooleanHandler } - .map { it.key } - .asIterable() - } - thenExecute { - val config = this[config] - val property = this[property] - - val configObj = AllConfigsGui.allConfigs.find { it.name == config } - if (configObj == null) { - source.sendFeedback( - Text.stringifiedTranslatable( - "firmament.command.toggle.no-config-found", - config - ) - ) - return@thenExecute - } - val propertyObj = configObj.allOptions[property] - if (propertyObj == null) { - source.sendFeedback( - Text.stringifiedTranslatable("firmament.command.toggle.no-property-found", property) - ) - return@thenExecute - } - if (propertyObj.handler !is BooleanHandler) { - source.sendFeedback( - Text.stringifiedTranslatable("firmament.command.toggle.not-a-toggle", property) - ) - return@thenExecute - } - propertyObj as ManagedOption - propertyObj.value = !propertyObj.value - configObj.save() - source.sendFeedback( - Text.stringifiedTranslatable( - "firmament.command.toggle.toggled", configObj.labelText, - propertyObj.labelText, - Text.translatable("firmament.toggle.${propertyObj.value}") - ) - ) - } - } - } - } - } - thenLiteral("buttons") { - thenExecute { - InventoryButtons.openEditor() - } - } - thenLiteral("sendcoords") { - thenExecute { - val p = MC.player ?: return@thenExecute - MC.sendServerChat("x: ${p.blockX}, y: ${p.blockY}, z: ${p.blockZ}") - } - thenArgument("rest", RestArgumentType) { rest -> - thenExecute { - val p = MC.player ?: return@thenExecute - MC.sendServerChat("x: ${p.blockX}, y: ${p.blockY}, z: ${p.blockZ} ${this[rest]}") - } - } - } - thenLiteral("storageoverview") { - thenExecute { - ScreenUtil.setScreenLater(StorageOverviewScreen()) - MC.player?.networkHandler?.sendChatCommand("storage") - } - } - thenLiteral("storage") { - thenExecute { - ScreenUtil.setScreenLater(StorageOverlayScreen()) - MC.player?.networkHandler?.sendChatCommand("storage") - } - } - thenLiteral("repo") { - thenLiteral("reload") { - thenLiteral("fetch") { - thenExecute { - source.sendFeedback(Text.translatable("firmament.repo.reload.network")) // TODO better reporting - RepoManager.launchAsyncUpdate() - } - } - thenExecute { - source.sendFeedback(Text.translatable("firmament.repo.reload.disk")) - RepoManager.reload() - } - } - } - thenLiteral("price") { - thenArgument("item", string()) { item -> - suggestsList { RepoManager.neuRepo.items.items.keys } - thenExecute { - val itemName = SkyblockId(get(item)) - source.sendFeedback(Text.stringifiedTranslatable("firmament.price", itemName.neuItem)) - val bazaarData = HypixelStaticData.bazaarData[itemName] - if (bazaarData != null) { - source.sendFeedback(Text.translatable("firmament.price.bazaar")) - source.sendFeedback( - Text.stringifiedTranslatable("firmament.price.bazaar.productid", bazaarData.productId.bazaarId) - ) - source.sendFeedback( - Text.stringifiedTranslatable( - "firmament.price.bazaar.buy.price", - FirmFormatters.formatCommas(bazaarData.quickStatus.buyPrice, 1) - ) - ) - source.sendFeedback( - Text.stringifiedTranslatable( - "firmament.price.bazaar.buy.order", - bazaarData.quickStatus.buyOrders - ) - ) - source.sendFeedback( - Text.stringifiedTranslatable( - "firmament.price.bazaar.sell.price", - FirmFormatters.formatCommas(bazaarData.quickStatus.sellPrice, 1) - ) - ) - source.sendFeedback( - Text.stringifiedTranslatable( - "firmament.price.bazaar.sell.order", - bazaarData.quickStatus.sellOrders - ) - ) - } - val lowestBin = HypixelStaticData.lowestBin[itemName] - if (lowestBin != null) { - source.sendFeedback( - Text.stringifiedTranslatable( - "firmament.price.lowestbin", - FirmFormatters.formatCommas(lowestBin, 1) - ) - ) - } - } - } - } - thenLiteral("dev") { - thenLiteral("simulate") { - thenArgument("message", RestArgumentType) { message -> - thenExecute { - MC.instance.messageHandler.onGameMessage(Text.literal(get(message)), false) - } - } - } - thenLiteral("sbdata") { - thenExecute { - source.sendFeedback(Text.stringifiedTranslatable("firmament.sbinfo.profile", SBData.profileId)) - val locrawInfo = SBData.locraw - if (locrawInfo == null) { - source.sendFeedback(Text.translatable("firmament.sbinfo.nolocraw")) - } else { - source.sendFeedback(Text.stringifiedTranslatable("firmament.sbinfo.server", locrawInfo.server)) - source.sendFeedback(Text.stringifiedTranslatable("firmament.sbinfo.gametype", locrawInfo.gametype)) - source.sendFeedback(Text.stringifiedTranslatable("firmament.sbinfo.mode", locrawInfo.mode)) - source.sendFeedback(Text.stringifiedTranslatable("firmament.sbinfo.map", locrawInfo.map)) - } - } - } - thenLiteral("copyEntities") { - thenExecute { - val player = MC.player ?: return@thenExecute - player.world.getOtherEntities(player, player.boundingBox.expand(12.0)).forEach(PowerUserTools::showEntity) - } - } - thenLiteral("callUrsa") { - thenArgument("path", string()) { path -> - thenExecute { - source.sendFeedback(Text.translatable("firmament.ursa.debugrequest.start")) - val text = UrsaManager.request(this[path].split("/")).bodyAsText() - source.sendFeedback(Text.stringifiedTranslatable("firmament.ursa.debugrequest.result", text)) - } - } - } - } - CommandEvent.SubCommand.publish(CommandEvent.SubCommand(this@literal)) -} - - -fun registerFirmamentCommand(dispatcher: CommandDispatcher) { - val firmament = dispatcher.register(firmamentCommand()) - dispatcher.register(literal("firm") { - redirect(firmament) - }) -} - - - - diff --git a/src/main/kotlin/moe/nea/firmament/compat/SodiumChunkReloader.kt b/src/main/kotlin/moe/nea/firmament/compat/SodiumChunkReloader.kt deleted file mode 100644 index 4bb231a..0000000 --- a/src/main/kotlin/moe/nea/firmament/compat/SodiumChunkReloader.kt +++ /dev/null @@ -1,12 +0,0 @@ -package moe.nea.firmament.compat - -import me.jellysquid.mods.sodium.client.render.SodiumWorldRenderer -import moe.nea.firmament.mixins.accessor.sodium.AccessorSodiumWorldRenderer - -class SodiumChunkReloader : Runnable { - override fun run() { - (SodiumWorldRenderer.instanceNullable() as AccessorSodiumWorldRenderer) - .renderSectionManager_firmament - .updateChunks(false) - } -} diff --git a/src/main/kotlin/moe/nea/firmament/events/AllowChatEvent.kt b/src/main/kotlin/moe/nea/firmament/events/AllowChatEvent.kt deleted file mode 100644 index 3069843..0000000 --- a/src/main/kotlin/moe/nea/firmament/events/AllowChatEvent.kt +++ /dev/null @@ -1,16 +0,0 @@ - - -package moe.nea.firmament.events - -import moe.nea.firmament.util.unformattedString -import net.minecraft.text.Text - -/** - * Filter whether the user should see a chat message altogether. May or may not be called for every chat packet sent by - * the server. When that quality is desired, consider [ProcessChatEvent] instead. - */ -data class AllowChatEvent(val text: Text) : FirmamentEvent.Cancellable() { - val unformattedString = text.unformattedString - - companion object : FirmamentEventBus() -} diff --git a/src/main/kotlin/moe/nea/firmament/events/AttackBlockEvent.kt b/src/main/kotlin/moe/nea/firmament/events/AttackBlockEvent.kt deleted file mode 100644 index bbaa81d..0000000 --- a/src/main/kotlin/moe/nea/firmament/events/AttackBlockEvent.kt +++ /dev/null @@ -1,18 +0,0 @@ - -package moe.nea.firmament.events - -import net.minecraft.entity.player.PlayerEntity -import net.minecraft.util.Hand -import net.minecraft.util.math.BlockPos -import net.minecraft.util.math.Direction -import net.minecraft.world.World - -data class AttackBlockEvent( - val player: PlayerEntity, - val world: World, - val hand: Hand, - val blockPos: BlockPos, - val direction: Direction -) : FirmamentEvent.Cancellable() { - companion object : FirmamentEventBus() -} diff --git a/src/main/kotlin/moe/nea/firmament/events/BakeExtraModelsEvent.kt b/src/main/kotlin/moe/nea/firmament/events/BakeExtraModelsEvent.kt deleted file mode 100644 index f75bedc..0000000 --- a/src/main/kotlin/moe/nea/firmament/events/BakeExtraModelsEvent.kt +++ /dev/null @@ -1,21 +0,0 @@ - -package moe.nea.firmament.events - -import java.util.function.Consumer -import net.minecraft.client.util.ModelIdentifier - -class BakeExtraModelsEvent( - private val addItemModel: Consumer, - private val addAnyModel: Consumer, -) : FirmamentEvent() { - - fun addNonItemModel(modelIdentifier: ModelIdentifier) { - this.addAnyModel.accept(modelIdentifier) - } - - fun addItemModel(modelIdentifier: ModelIdentifier) { - this.addItemModel.accept(modelIdentifier) - } - - companion object : FirmamentEventBus() -} diff --git a/src/main/kotlin/moe/nea/firmament/events/ClientStartedEvent.kt b/src/main/kotlin/moe/nea/firmament/events/ClientStartedEvent.kt deleted file mode 100644 index 637916d..0000000 --- a/src/main/kotlin/moe/nea/firmament/events/ClientStartedEvent.kt +++ /dev/null @@ -1,6 +0,0 @@ - -package moe.nea.firmament.events - -class ClientStartedEvent : FirmamentEvent() { - companion object : FirmamentEventBus() -} diff --git a/src/main/kotlin/moe/nea/firmament/events/CommandEvent.kt b/src/main/kotlin/moe/nea/firmament/events/CommandEvent.kt deleted file mode 100644 index cc9cf45..0000000 --- a/src/main/kotlin/moe/nea/firmament/events/CommandEvent.kt +++ /dev/null @@ -1,45 +0,0 @@ - - -package moe.nea.firmament.events - -import com.mojang.brigadier.CommandDispatcher -import com.mojang.brigadier.tree.LiteralCommandNode -import net.minecraft.command.CommandRegistryAccess -import moe.nea.firmament.commands.CaseInsensitiveLiteralCommandNode -import moe.nea.firmament.commands.DefaultSource -import moe.nea.firmament.commands.literal -import moe.nea.firmament.commands.thenLiteral - -data class CommandEvent( - val dispatcher: CommandDispatcher, - val ctx: CommandRegistryAccess, - val serverCommands: CommandDispatcher<*>?, -) : FirmamentEvent() { - companion object : FirmamentEventBus() - - /** - * Register subcommands to `/firm`. For new top level commands use [CommandEvent]. Cannot be used to register - * subcommands to other commands. - */ - data class SubCommand( - val builder: CaseInsensitiveLiteralCommandNode.Builder, - ) : FirmamentEvent() { - companion object : FirmamentEventBus() - - fun subcommand(name: String, block: CaseInsensitiveLiteralCommandNode.Builder.() -> Unit) { - builder.thenLiteral(name, block) - } - } - - fun deleteCommand(name: String) { - dispatcher.root.children.removeIf { it.name.equals(name, ignoreCase = false) } - serverCommands?.root?.children?.removeIf { it.name.equals(name, ignoreCase = false) } - } - - fun register( - name: String, - block: CaseInsensitiveLiteralCommandNode.Builder.() -> Unit - ): LiteralCommandNode { - return dispatcher.register(literal(name, block)) - } -} diff --git a/src/main/kotlin/moe/nea/firmament/events/CustomItemModelEvent.kt b/src/main/kotlin/moe/nea/firmament/events/CustomItemModelEvent.kt deleted file mode 100644 index 27524a9..0000000 --- a/src/main/kotlin/moe/nea/firmament/events/CustomItemModelEvent.kt +++ /dev/null @@ -1,43 +0,0 @@ - - -package moe.nea.firmament.events - -import java.util.* -import net.minecraft.client.render.model.BakedModel -import net.minecraft.client.render.model.BakedModelManager -import net.minecraft.client.util.ModelIdentifier -import net.minecraft.item.ItemStack - -data class CustomItemModelEvent( - val itemStack: ItemStack, - var overrideModel: ModelIdentifier? = null, -) : FirmamentEvent() { - companion object : FirmamentEventBus() { - private val cache = IdentityHashMap() - private val sentinelNull = Object() - - fun clearCache() { - cache.clear() - } - - @JvmStatic - fun getModelIdentifier(itemStack: ItemStack?): ModelIdentifier? { - if (itemStack == null) return null - return publish(CustomItemModelEvent(itemStack)).overrideModel - } - - @JvmStatic - fun getModel(itemStack: ItemStack?, thing: BakedModelManager): BakedModel? { - if (itemStack == null) return null - val cachedValue = cache.getOrPut(itemStack) { - val modelId = getModelIdentifier(itemStack) ?: return@getOrPut sentinelNull - val bakedModel = thing.getModel(modelId) - if (bakedModel === thing.missingModel) return@getOrPut sentinelNull - bakedModel - } - if (cachedValue === sentinelNull) - return null - return cachedValue as BakedModel - } - } -} diff --git a/src/main/kotlin/moe/nea/firmament/events/EarlyResourceReloadEvent.kt b/src/main/kotlin/moe/nea/firmament/events/EarlyResourceReloadEvent.kt deleted file mode 100644 index ec8377a..0000000 --- a/src/main/kotlin/moe/nea/firmament/events/EarlyResourceReloadEvent.kt +++ /dev/null @@ -1,10 +0,0 @@ - -package moe.nea.firmament.events - -import java.util.concurrent.Executor -import net.minecraft.resource.ResourceManager - -data class EarlyResourceReloadEvent(val resourceManager: ResourceManager, val preparationExecutor: Executor) : - FirmamentEvent() { - companion object : FirmamentEventBus() -} diff --git a/src/main/kotlin/moe/nea/firmament/events/EntityDespawnEvent.kt b/src/main/kotlin/moe/nea/firmament/events/EntityDespawnEvent.kt deleted file mode 100644 index 93dc477..0000000 --- a/src/main/kotlin/moe/nea/firmament/events/EntityDespawnEvent.kt +++ /dev/null @@ -1,11 +0,0 @@ - -package moe.nea.firmament.events - -import net.minecraft.entity.Entity - -data class EntityDespawnEvent( - val entity: Entity?, val entityId: Int, - val reason: Entity.RemovalReason, -) : FirmamentEvent() { - companion object: FirmamentEventBus() -} diff --git a/src/main/kotlin/moe/nea/firmament/events/EntityInteractionEvent.kt b/src/main/kotlin/moe/nea/firmament/events/EntityInteractionEvent.kt deleted file mode 100644 index 123ea39..0000000 --- a/src/main/kotlin/moe/nea/firmament/events/EntityInteractionEvent.kt +++ /dev/null @@ -1,29 +0,0 @@ - -package moe.nea.firmament.events - -import net.minecraft.entity.Entity -import net.minecraft.util.Hand - -data class EntityInteractionEvent( - val kind: InteractionKind, - val entity: Entity, - val hand: Hand, -) : FirmamentEvent() { - companion object : FirmamentEventBus() - enum class InteractionKind { - /** - * Is sent when left-clicking an entity - */ - ATTACK, - - /** - * Is a fallback when [INTERACT_AT_LOCATION] fails - */ - INTERACT, - - /** - * Is tried first on right click - */ - INTERACT_AT_LOCATION, - } -} diff --git a/src/main/kotlin/moe/nea/firmament/events/EntityUpdateEvent.kt b/src/main/kotlin/moe/nea/firmament/events/EntityUpdateEvent.kt deleted file mode 100644 index d091984..0000000 --- a/src/main/kotlin/moe/nea/firmament/events/EntityUpdateEvent.kt +++ /dev/null @@ -1,31 +0,0 @@ - -package moe.nea.firmament.events - -import net.minecraft.entity.Entity -import net.minecraft.entity.LivingEntity -import net.minecraft.entity.data.DataTracker -import net.minecraft.network.packet.s2c.play.EntityAttributesS2CPacket - -/** - * This event is fired when some entity properties are updated. - * It is not fired for common changes like position, but is for less common ones, - * like health, tracked data, names, equipment. It is always fired - * *after* the values have been applied to the entity. - */ -sealed class EntityUpdateEvent : FirmamentEvent() { - companion object : FirmamentEventBus() - - abstract val entity: Entity - - data class AttributeUpdate( - override val entity: LivingEntity, - val attributes: List, - ) : EntityUpdateEvent() - - data class TrackedDataUpdate( - override val entity: Entity, - val trackedValues: List>, - ) : EntityUpdateEvent() - -// TODO: onEntityPassengersSet, onEntityAttach?, onEntityEquipmentUpdate, onEntityStatusEffect -} diff --git a/src/main/kotlin/moe/nea/firmament/events/FeaturesInitializedEvent.kt b/src/main/kotlin/moe/nea/firmament/events/FeaturesInitializedEvent.kt deleted file mode 100644 index ad2ad8a..0000000 --- a/src/main/kotlin/moe/nea/firmament/events/FeaturesInitializedEvent.kt +++ /dev/null @@ -1,8 +0,0 @@ - -package moe.nea.firmament.events - -import moe.nea.firmament.features.FirmamentFeature - -data class FeaturesInitializedEvent(val features: List) : FirmamentEvent() { - companion object : FirmamentEventBus() -} diff --git a/src/main/kotlin/moe/nea/firmament/events/FinalizeResourceManagerEvent.kt b/src/main/kotlin/moe/nea/firmament/events/FinalizeResourceManagerEvent.kt deleted file mode 100644 index c43ad3b..0000000 --- a/src/main/kotlin/moe/nea/firmament/events/FinalizeResourceManagerEvent.kt +++ /dev/null @@ -1,10 +0,0 @@ - -package moe.nea.firmament.events - -import net.minecraft.resource.ReloadableResourceManagerImpl - -data class FinalizeResourceManagerEvent( - val resourceManager: ReloadableResourceManagerImpl, -) : FirmamentEvent() { - companion object : FirmamentEventBus() -} diff --git a/src/main/kotlin/moe/nea/firmament/events/FirmamentEvent.kt b/src/main/kotlin/moe/nea/firmament/events/FirmamentEvent.kt deleted file mode 100644 index 1a93ef5..0000000 --- a/src/main/kotlin/moe/nea/firmament/events/FirmamentEvent.kt +++ /dev/null @@ -1,38 +0,0 @@ - - -package moe.nea.firmament.events - -/** - * An event that can be fired by a [FirmamentEventBus]. - * - * Typically, that event bus is implemented as a companion object - * - * ``` - * class SomeEvent : FirmamentEvent() { - * companion object : FirmamentEventBus() - * } - * ``` - */ -abstract class FirmamentEvent { - /** - * A [FirmamentEvent] that can be [cancelled] - */ - abstract class Cancellable : FirmamentEvent() { - /** - * Cancels this is event. - * - * @see cancelled - */ - fun cancel() { - cancelled = true - } - - /** - * Whether this event is cancelled. - * - * Cancelled events will bypass handlers unless otherwise specified and will prevent the action that this - * event was originally fired for. - */ - var cancelled: Boolean = false - } -} diff --git a/src/main/kotlin/moe/nea/firmament/events/FirmamentEventBus.kt b/src/main/kotlin/moe/nea/firmament/events/FirmamentEventBus.kt deleted file mode 100644 index ee9e6c8..0000000 --- a/src/main/kotlin/moe/nea/firmament/events/FirmamentEventBus.kt +++ /dev/null @@ -1,52 +0,0 @@ - - -package moe.nea.firmament.events - -import java.util.concurrent.CopyOnWriteArrayList -import moe.nea.firmament.Firmament -import moe.nea.firmament.util.MC - -/** - * A pubsub event bus. - * - * [subscribe] to events [publish]ed on this event bus. - * Subscriptions may not necessarily be delivered in the order of registering. - */ -open class FirmamentEventBus { - data class Handler( - val invocation: (T) -> Unit, val receivesCancelled: Boolean, - var knownErrors: MutableSet> = mutableSetOf(), - ) - - private val toHandle: MutableList> = CopyOnWriteArrayList() - fun subscribe(handle: (T) -> Unit) { - subscribe(false, handle) - } - - fun subscribe(receivesCancelled: Boolean, handle: (T) -> Unit) { - toHandle.add(Handler(handle, receivesCancelled)) - } - - fun publish(event: T): T { - for (function in toHandle) { - if (function.receivesCancelled || event !is FirmamentEvent.Cancellable || !event.cancelled) { - try { - function.invocation(event) - } catch (e: Exception) { - val klass = e.javaClass - if (!function.knownErrors.contains(klass) || Firmament.DEBUG) { - function.knownErrors.add(klass) - Firmament.logger.error("Caught exception during processing event $event by $function", e) - } - } - } - } - return event - } - - fun publishSync(event: T) { - MC.onMainThread { - publish(event) - } - } -} diff --git a/src/main/kotlin/moe/nea/firmament/events/HandledScreenClickEvent.kt b/src/main/kotlin/moe/nea/firmament/events/HandledScreenClickEvent.kt deleted file mode 100644 index 4c3003c..0000000 --- a/src/main/kotlin/moe/nea/firmament/events/HandledScreenClickEvent.kt +++ /dev/null @@ -1,10 +0,0 @@ - - -package moe.nea.firmament.events - -import net.minecraft.client.gui.screen.ingame.HandledScreen - -data class HandledScreenClickEvent(val screen: HandledScreen<*>, val mouseX: Double, val mouseY: Double, val button: Int) : - FirmamentEvent.Cancellable() { - companion object : FirmamentEventBus() -} diff --git a/src/main/kotlin/moe/nea/firmament/events/HandledScreenForegroundEvent.kt b/src/main/kotlin/moe/nea/firmament/events/HandledScreenForegroundEvent.kt deleted file mode 100644 index f16d30e..0000000 --- a/src/main/kotlin/moe/nea/firmament/events/HandledScreenForegroundEvent.kt +++ /dev/null @@ -1,16 +0,0 @@ - - -package moe.nea.firmament.events - -import net.minecraft.client.gui.DrawContext -import net.minecraft.client.gui.screen.ingame.HandledScreen - -data class HandledScreenForegroundEvent( - val screen: HandledScreen<*>, - val context: DrawContext, - val mouseX: Int, - val mouseY: Int, - val delta: Float -) : FirmamentEvent() { - companion object : FirmamentEventBus() -} diff --git a/src/main/kotlin/moe/nea/firmament/events/HandledScreenKeyPressedEvent.kt b/src/main/kotlin/moe/nea/firmament/events/HandledScreenKeyPressedEvent.kt deleted file mode 100644 index 7ec2abb..0000000 --- a/src/main/kotlin/moe/nea/firmament/events/HandledScreenKeyPressedEvent.kt +++ /dev/null @@ -1,24 +0,0 @@ - - -package moe.nea.firmament.events - -import net.minecraft.client.gui.screen.ingame.HandledScreen -import net.minecraft.client.option.KeyBinding -import moe.nea.firmament.keybindings.IKeyBinding - -data class HandledScreenKeyPressedEvent( - val screen: HandledScreen<*>, - val keyCode: Int, - val scanCode: Int, - val modifiers: Int -) : FirmamentEvent.Cancellable() { - companion object : FirmamentEventBus() - - fun matches(keyBinding: KeyBinding): Boolean { - return matches(IKeyBinding.minecraft(keyBinding)) - } - - fun matches(keyBinding: IKeyBinding): Boolean { - return keyBinding.matches(keyCode, scanCode, modifiers) - } -} diff --git a/src/main/kotlin/moe/nea/firmament/events/HandledScreenPushREIEvent.kt b/src/main/kotlin/moe/nea/firmament/events/HandledScreenPushREIEvent.kt deleted file mode 100644 index 1bb495a..0000000 --- a/src/main/kotlin/moe/nea/firmament/events/HandledScreenPushREIEvent.kt +++ /dev/null @@ -1,18 +0,0 @@ - - -package moe.nea.firmament.events - -import me.shedaniel.math.Rectangle -import net.minecraft.client.gui.screen.ingame.HandledScreen - -data class HandledScreenPushREIEvent( - val screen: HandledScreen<*>, - val rectangles: MutableList = mutableListOf() -) : FirmamentEvent() { - - fun block(rectangle: Rectangle) { - rectangles.add(rectangle) - } - - companion object : FirmamentEventBus() -} diff --git a/src/main/kotlin/moe/nea/firmament/events/HotbarItemRenderEvent.kt b/src/main/kotlin/moe/nea/firmament/events/HotbarItemRenderEvent.kt deleted file mode 100644 index a1940e6..0000000 --- a/src/main/kotlin/moe/nea/firmament/events/HotbarItemRenderEvent.kt +++ /dev/null @@ -1,17 +0,0 @@ - - -package moe.nea.firmament.events - -import net.minecraft.client.gui.DrawContext -import net.minecraft.client.render.RenderTickCounter -import net.minecraft.item.ItemStack - -data class HotbarItemRenderEvent( - val item: ItemStack, - val context: DrawContext, - val x: Int, - val y: Int, - val tickDelta: RenderTickCounter, -) : FirmamentEvent() { - companion object : FirmamentEventBus() -} diff --git a/src/main/kotlin/moe/nea/firmament/events/HudRenderEvent.kt b/src/main/kotlin/moe/nea/firmament/events/HudRenderEvent.kt deleted file mode 100644 index 555b3c8..0000000 --- a/src/main/kotlin/moe/nea/firmament/events/HudRenderEvent.kt +++ /dev/null @@ -1,13 +0,0 @@ - - -package moe.nea.firmament.events - -import net.minecraft.client.gui.DrawContext -import net.minecraft.client.render.RenderTickCounter - -/** - * Called when hud elements should be rendered, before the screen, but after the world. - */ -data class HudRenderEvent(val context: DrawContext, val tickDelta: RenderTickCounter) : FirmamentEvent() { - companion object : FirmamentEventBus() -} diff --git a/src/main/kotlin/moe/nea/firmament/events/IsSlotProtectedEvent.kt b/src/main/kotlin/moe/nea/firmament/events/IsSlotProtectedEvent.kt deleted file mode 100644 index cd431f7..0000000 --- a/src/main/kotlin/moe/nea/firmament/events/IsSlotProtectedEvent.kt +++ /dev/null @@ -1,46 +0,0 @@ - - -package moe.nea.firmament.events - -import net.minecraft.item.ItemStack -import net.minecraft.screen.slot.Slot -import net.minecraft.screen.slot.SlotActionType -import net.minecraft.text.Text -import moe.nea.firmament.util.CommonSoundEffects -import moe.nea.firmament.util.MC - -data class IsSlotProtectedEvent( - val slot: Slot?, - val actionType: SlotActionType, - var isProtected: Boolean, - val itemStackOverride: ItemStack?, - var silent: Boolean = false, -) : FirmamentEvent() { - val itemStack get() = itemStackOverride ?: slot!!.stack - - fun protect() { - isProtected = true - } - - fun protectSilent() { - if (!isProtected) { - silent = true - } - isProtected = true - } - - companion object : FirmamentEventBus() { - @JvmStatic - @JvmOverloads - fun shouldBlockInteraction(slot: Slot?, action: SlotActionType, itemStackOverride: ItemStack? = null): Boolean { - if (slot == null && itemStackOverride == null) return false - val event = IsSlotProtectedEvent(slot, action, false, itemStackOverride) - publish(event) - if (event.isProtected && !event.silent) { - MC.player?.sendMessage(Text.translatable("firmament.protectitem").append(event.itemStack.name)) - CommonSoundEffects.playFailure() - } - return event.isProtected - } - } -} diff --git a/src/main/kotlin/moe/nea/firmament/events/ItemTooltipEvent.kt b/src/main/kotlin/moe/nea/firmament/events/ItemTooltipEvent.kt deleted file mode 100644 index d86e06f..0000000 --- a/src/main/kotlin/moe/nea/firmament/events/ItemTooltipEvent.kt +++ /dev/null @@ -1,14 +0,0 @@ - - -package moe.nea.firmament.events - -import net.minecraft.item.Item.TooltipContext -import net.minecraft.item.ItemStack -import net.minecraft.item.tooltip.TooltipType -import net.minecraft.text.Text - -data class ItemTooltipEvent( - val stack: ItemStack, val context: TooltipContext, val type: TooltipType, val lines: MutableList -) : FirmamentEvent() { - companion object : FirmamentEventBus() -} diff --git a/src/main/kotlin/moe/nea/firmament/events/MaskCommands.kt b/src/main/kotlin/moe/nea/firmament/events/MaskCommands.kt deleted file mode 100644 index 35aade0..0000000 --- a/src/main/kotlin/moe/nea/firmament/events/MaskCommands.kt +++ /dev/null @@ -1,13 +0,0 @@ - - -package moe.nea.firmament.events - -import com.mojang.brigadier.CommandDispatcher - -data class MaskCommands(val dispatcher: CommandDispatcher<*>) : FirmamentEvent() { - companion object : FirmamentEventBus() - - fun mask(name: String) { - dispatcher.root.children.removeIf { it.name.equals(name, ignoreCase = true) } - } -} diff --git a/src/main/kotlin/moe/nea/firmament/events/ModifyChatEvent.kt b/src/main/kotlin/moe/nea/firmament/events/ModifyChatEvent.kt deleted file mode 100644 index a5868e8..0000000 --- a/src/main/kotlin/moe/nea/firmament/events/ModifyChatEvent.kt +++ /dev/null @@ -1,21 +0,0 @@ - - -package moe.nea.firmament.events - -import moe.nea.firmament.util.unformattedString -import net.minecraft.text.Text - -/** - * Allow modification of a chat message before it is sent off to the user. Intended for display purposes. - */ -data class ModifyChatEvent(val originalText: Text) : FirmamentEvent() { - var unformattedString = originalText.unformattedString - private set - var replaceWith: Text = originalText - set(value) { - field = value - unformattedString = value.unformattedString - } - - companion object : FirmamentEventBus() -} diff --git a/src/main/kotlin/moe/nea/firmament/events/OutgoingPacketEvent.kt b/src/main/kotlin/moe/nea/firmament/events/OutgoingPacketEvent.kt deleted file mode 100644 index 93890ea..0000000 --- a/src/main/kotlin/moe/nea/firmament/events/OutgoingPacketEvent.kt +++ /dev/null @@ -1,9 +0,0 @@ - - -package moe.nea.firmament.events - -import net.minecraft.network.packet.Packet - -data class OutgoingPacketEvent(val packet: Packet<*>) : FirmamentEvent.Cancellable() { - companion object : FirmamentEventBus() -} diff --git a/src/main/kotlin/moe/nea/firmament/events/ParticleSpawnEvent.kt b/src/main/kotlin/moe/nea/firmament/events/ParticleSpawnEvent.kt deleted file mode 100644 index 9359e4b..0000000 --- a/src/main/kotlin/moe/nea/firmament/events/ParticleSpawnEvent.kt +++ /dev/null @@ -1,18 +0,0 @@ - - -package moe.nea.firmament.events - -import org.joml.Vector3f -import net.minecraft.particle.ParticleEffect -import net.minecraft.util.math.Vec3d - -data class ParticleSpawnEvent( - val particleEffect: ParticleEffect, - val position: Vec3d, - val offset: Vector3f, - val longDistance: Boolean, - val count: Int, - val speed: Float, -) : FirmamentEvent.Cancellable() { - companion object : FirmamentEventBus() -} diff --git a/src/main/kotlin/moe/nea/firmament/events/PlayerInventoryUpdate.kt b/src/main/kotlin/moe/nea/firmament/events/PlayerInventoryUpdate.kt deleted file mode 100644 index 6e8203a..0000000 --- a/src/main/kotlin/moe/nea/firmament/events/PlayerInventoryUpdate.kt +++ /dev/null @@ -1,11 +0,0 @@ - -package moe.nea.firmament.events - -import net.minecraft.item.ItemStack - -sealed class PlayerInventoryUpdate : FirmamentEvent() { - companion object : FirmamentEventBus() - data class Single(val slot: Int, val stack: ItemStack) : PlayerInventoryUpdate() - data class Multi(val contents: List) : PlayerInventoryUpdate() - -} diff --git a/src/main/kotlin/moe/nea/firmament/events/ProcessChatEvent.kt b/src/main/kotlin/moe/nea/firmament/events/ProcessChatEvent.kt deleted file mode 100644 index 76c0b27..0000000 --- a/src/main/kotlin/moe/nea/firmament/events/ProcessChatEvent.kt +++ /dev/null @@ -1,28 +0,0 @@ - - -package moe.nea.firmament.events - -import net.minecraft.text.Text -import moe.nea.firmament.util.unformattedString - -/** - * Behaves like [AllowChatEvent], but is triggered even when cancelled by other mods. Intended for data collection. - * Make sure to subscribe to cancellable events as well when using. - */ -data class ProcessChatEvent(val text: Text, val wasExternallyCancelled: Boolean) : FirmamentEvent.Cancellable() { - val unformattedString = text.unformattedString - - val nameHeuristic: String? = run { - val firstColon = unformattedString.indexOf(':') - if (firstColon < 0) return@run null - val firstSpace = unformattedString.lastIndexOf(' ', firstColon) - unformattedString.substring(firstSpace + 1 until firstColon).takeIf { it.isNotEmpty() } - } - - init { - if (wasExternallyCancelled) - cancelled = true - } - - companion object : FirmamentEventBus() -} diff --git a/src/main/kotlin/moe/nea/firmament/events/ReloadRegistrationEvent.kt b/src/main/kotlin/moe/nea/firmament/events/ReloadRegistrationEvent.kt deleted file mode 100644 index 4c3083e..0000000 --- a/src/main/kotlin/moe/nea/firmament/events/ReloadRegistrationEvent.kt +++ /dev/null @@ -1,7 +0,0 @@ -package moe.nea.firmament.events - -import io.github.moulberry.repo.NEURepository - -data class ReloadRegistrationEvent(val repo: NEURepository) : FirmamentEvent() { - companion object : FirmamentEventBus() -} diff --git a/src/main/kotlin/moe/nea/firmament/events/ScreenChangeEvent.kt b/src/main/kotlin/moe/nea/firmament/events/ScreenChangeEvent.kt deleted file mode 100644 index 489e487..0000000 --- a/src/main/kotlin/moe/nea/firmament/events/ScreenChangeEvent.kt +++ /dev/null @@ -1,10 +0,0 @@ - - -package moe.nea.firmament.events - -import net.minecraft.client.gui.screen.Screen - -data class ScreenChangeEvent(val old: Screen?, val new: Screen?) : FirmamentEvent.Cancellable() { - var overrideScreen: Screen? = null - companion object : FirmamentEventBus() -} diff --git a/src/main/kotlin/moe/nea/firmament/events/ScreenRenderPostEvent.kt b/src/main/kotlin/moe/nea/firmament/events/ScreenRenderPostEvent.kt deleted file mode 100644 index 79f4913..0000000 --- a/src/main/kotlin/moe/nea/firmament/events/ScreenRenderPostEvent.kt +++ /dev/null @@ -1,16 +0,0 @@ - - -package moe.nea.firmament.events - -import net.minecraft.client.gui.DrawContext -import net.minecraft.client.gui.screen.Screen - -data class ScreenRenderPostEvent( - val screen: Screen, - val mouseX: Int, - val mouseY: Int, - val tickDelta: Float, - val drawContext: DrawContext -) : FirmamentEvent() { - companion object : FirmamentEventBus() -} diff --git a/src/main/kotlin/moe/nea/firmament/events/ServerConnectedEvent.kt b/src/main/kotlin/moe/nea/firmament/events/ServerConnectedEvent.kt deleted file mode 100644 index 26897f2..0000000 --- a/src/main/kotlin/moe/nea/firmament/events/ServerConnectedEvent.kt +++ /dev/null @@ -1,18 +0,0 @@ -package moe.nea.firmament.events - -import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents -import net.minecraft.client.MinecraftClient -import net.minecraft.client.network.ClientPlayNetworkHandler -import net.minecraft.network.ClientConnection - -data class ServerConnectedEvent( - val connection: ClientConnection -) : FirmamentEvent() { - companion object : FirmamentEventBus() { - init { - ClientPlayConnectionEvents.INIT.register(ClientPlayConnectionEvents.Init { clientPlayNetworkHandler: ClientPlayNetworkHandler, minecraftClient: MinecraftClient -> - publishSync(ServerConnectedEvent(clientPlayNetworkHandler.connection)) - }) - } - } -} diff --git a/src/main/kotlin/moe/nea/firmament/events/SkyblockServerUpdateEvent.kt b/src/main/kotlin/moe/nea/firmament/events/SkyblockServerUpdateEvent.kt deleted file mode 100644 index 0bc5143..0000000 --- a/src/main/kotlin/moe/nea/firmament/events/SkyblockServerUpdateEvent.kt +++ /dev/null @@ -1,15 +0,0 @@ - - -package moe.nea.firmament.events - -import moe.nea.firmament.util.Locraw - -/** - * This event gets published whenever `/locraw` is queried and HyPixel returns a location different to the old one. - * - * **N.B.:** This event may get fired multiple times while on the server (for example, first to null, then to the - * correct location). - */ -data class SkyblockServerUpdateEvent(val oldLocraw: Locraw?, val newLocraw: Locraw?) : FirmamentEvent() { - companion object : FirmamentEventBus() -} diff --git a/src/main/kotlin/moe/nea/firmament/events/SlotClickEvent.kt b/src/main/kotlin/moe/nea/firmament/events/SlotClickEvent.kt deleted file mode 100644 index d4abfb0..0000000 --- a/src/main/kotlin/moe/nea/firmament/events/SlotClickEvent.kt +++ /dev/null @@ -1,15 +0,0 @@ - -package moe.nea.firmament.events - -import net.minecraft.item.ItemStack -import net.minecraft.screen.slot.Slot -import net.minecraft.screen.slot.SlotActionType - -data class SlotClickEvent( - val slot: Slot, - val stack: ItemStack, - val button: Int, - val actionType: SlotActionType, -) : FirmamentEvent() { - companion object : FirmamentEventBus() -} diff --git a/src/main/kotlin/moe/nea/firmament/events/SlotRenderEvents.kt b/src/main/kotlin/moe/nea/firmament/events/SlotRenderEvents.kt deleted file mode 100644 index 8352581..0000000 --- a/src/main/kotlin/moe/nea/firmament/events/SlotRenderEvents.kt +++ /dev/null @@ -1,34 +0,0 @@ - - -package moe.nea.firmament.events - -import net.minecraft.client.gui.DrawContext -import net.minecraft.screen.slot.Slot - -interface SlotRenderEvents { - val context: DrawContext - val slot: Slot - val mouseX: Int - val mouseY: Int - val delta: Float - - data class Before( - override val context: DrawContext, override val slot: Slot, - override val mouseX: Int, - override val mouseY: Int, - override val delta: Float - ) : FirmamentEvent(), - SlotRenderEvents { - companion object : FirmamentEventBus() - } - - data class After( - override val context: DrawContext, override val slot: Slot, - override val mouseX: Int, - override val mouseY: Int, - override val delta: Float - ) : FirmamentEvent(), - SlotRenderEvents { - companion object : FirmamentEventBus() - } -} diff --git a/src/main/kotlin/moe/nea/firmament/events/SoundReceiveEvent.kt b/src/main/kotlin/moe/nea/firmament/events/SoundReceiveEvent.kt deleted file mode 100644 index d1b85b6..0000000 --- a/src/main/kotlin/moe/nea/firmament/events/SoundReceiveEvent.kt +++ /dev/null @@ -1,18 +0,0 @@ - -package moe.nea.firmament.events - -import net.minecraft.registry.entry.RegistryEntry -import net.minecraft.sound.SoundCategory -import net.minecraft.sound.SoundEvent -import net.minecraft.util.math.Vec3d - -data class SoundReceiveEvent( - val sound: RegistryEntry, - val category: SoundCategory, - val position: Vec3d, - val pitch: Float, - val volume: Float, - val seed: Long -) : FirmamentEvent.Cancellable() { - companion object : FirmamentEventBus() -} diff --git a/src/main/kotlin/moe/nea/firmament/events/TickEvent.kt b/src/main/kotlin/moe/nea/firmament/events/TickEvent.kt deleted file mode 100644 index 18007f8..0000000 --- a/src/main/kotlin/moe/nea/firmament/events/TickEvent.kt +++ /dev/null @@ -1,7 +0,0 @@ - - -package moe.nea.firmament.events - -data class TickEvent(val tickCount: Int) : FirmamentEvent() { - companion object : FirmamentEventBus() -} diff --git a/src/main/kotlin/moe/nea/firmament/events/TooltipEvent.kt b/src/main/kotlin/moe/nea/firmament/events/TooltipEvent.kt deleted file mode 100644 index 630ba56..0000000 --- a/src/main/kotlin/moe/nea/firmament/events/TooltipEvent.kt +++ /dev/null @@ -1,17 +0,0 @@ - - -package moe.nea.firmament.events - -import net.minecraft.client.gui.tooltip.Tooltip -import net.minecraft.entity.player.PlayerEntity -import net.minecraft.item.Item -import net.minecraft.item.ItemStack - -data class TooltipEvent( - val itemStack: ItemStack, - val tooltip: Tooltip, - val tooltipContext: Item.TooltipContext, - val player: PlayerEntity? -) : FirmamentEvent() { - companion object : FirmamentEventBus() -} diff --git a/src/main/kotlin/moe/nea/firmament/events/UseBlockEvent.kt b/src/main/kotlin/moe/nea/firmament/events/UseBlockEvent.kt deleted file mode 100644 index 8bbe0de..0000000 --- a/src/main/kotlin/moe/nea/firmament/events/UseBlockEvent.kt +++ /dev/null @@ -1,11 +0,0 @@ - -package moe.nea.firmament.events - -import net.minecraft.entity.player.PlayerEntity -import net.minecraft.util.Hand -import net.minecraft.util.hit.BlockHitResult -import net.minecraft.world.World - -data class UseBlockEvent(val player: PlayerEntity, val world: World, val hand: Hand, val hitResult: BlockHitResult) : FirmamentEvent.Cancellable() { - companion object : FirmamentEventBus() -} diff --git a/src/main/kotlin/moe/nea/firmament/events/WorldKeyboardEvent.kt b/src/main/kotlin/moe/nea/firmament/events/WorldKeyboardEvent.kt deleted file mode 100644 index e8566fd..0000000 --- a/src/main/kotlin/moe/nea/firmament/events/WorldKeyboardEvent.kt +++ /dev/null @@ -1,18 +0,0 @@ - - -package moe.nea.firmament.events - -import net.minecraft.client.option.KeyBinding -import moe.nea.firmament.keybindings.IKeyBinding - -data class WorldKeyboardEvent(val keyCode: Int, val scanCode: Int, val modifiers: Int) : FirmamentEvent.Cancellable() { - companion object : FirmamentEventBus() - - fun matches(keyBinding: KeyBinding): Boolean { - return matches(IKeyBinding.minecraft(keyBinding)) - } - - fun matches(keyBinding: IKeyBinding): Boolean { - return keyBinding.matches(keyCode, scanCode, modifiers) - } -} diff --git a/src/main/kotlin/moe/nea/firmament/events/WorldReadyEvent.kt b/src/main/kotlin/moe/nea/firmament/events/WorldReadyEvent.kt deleted file mode 100644 index 2c76c44..0000000 --- a/src/main/kotlin/moe/nea/firmament/events/WorldReadyEvent.kt +++ /dev/null @@ -1,7 +0,0 @@ - - -package moe.nea.firmament.events - -class WorldReadyEvent : FirmamentEvent() { - companion object : FirmamentEventBus() -} diff --git a/src/main/kotlin/moe/nea/firmament/events/WorldRenderLastEvent.kt b/src/main/kotlin/moe/nea/firmament/events/WorldRenderLastEvent.kt deleted file mode 100644 index 21a670d..0000000 --- a/src/main/kotlin/moe/nea/firmament/events/WorldRenderLastEvent.kt +++ /dev/null @@ -1,27 +0,0 @@ - - -package moe.nea.firmament.events - -import net.minecraft.client.render.Camera -import net.minecraft.client.render.GameRenderer -import net.minecraft.client.render.LightmapTextureManager -import net.minecraft.client.render.RenderTickCounter -import net.minecraft.client.render.VertexConsumerProvider -import net.minecraft.client.util.math.MatrixStack -import net.minecraft.util.math.Position -import net.minecraft.util.math.Vec3d - -/** - * This event is called after all world rendering is done, but before any GUI rendering (including hand) has been done. - */ -data class WorldRenderLastEvent( - val matrices: MatrixStack, - val tickCounter: RenderTickCounter, - val renderBlockOutline: Boolean, - val camera: Camera, - val gameRenderer: GameRenderer, - val lightmapTextureManager: LightmapTextureManager, - val vertexConsumers: VertexConsumerProvider.Immediate, -) : FirmamentEvent() { - companion object : FirmamentEventBus() -} diff --git a/src/main/kotlin/moe/nea/firmament/events/registration/ChatEvents.kt b/src/main/kotlin/moe/nea/firmament/events/registration/ChatEvents.kt deleted file mode 100644 index 4c1c63f..0000000 --- a/src/main/kotlin/moe/nea/firmament/events/registration/ChatEvents.kt +++ /dev/null @@ -1,54 +0,0 @@ - - -package moe.nea.firmament.events.registration - -import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents -import net.fabricmc.fabric.api.event.player.AttackBlockCallback -import net.fabricmc.fabric.api.event.player.UseBlockCallback -import net.minecraft.text.Text -import net.minecraft.util.ActionResult -import moe.nea.firmament.events.AllowChatEvent -import moe.nea.firmament.events.AttackBlockEvent -import moe.nea.firmament.events.ModifyChatEvent -import moe.nea.firmament.events.ProcessChatEvent -import moe.nea.firmament.events.UseBlockEvent - -private var lastReceivedMessage: Text? = null - -fun registerFirmamentEvents() { - ClientReceiveMessageEvents.ALLOW_CHAT.register(ClientReceiveMessageEvents.AllowChat { message, signedMessage, sender, params, receptionTimestamp -> - lastReceivedMessage = message - !ProcessChatEvent.publish(ProcessChatEvent(message, false)).cancelled - && !AllowChatEvent.publish(AllowChatEvent(message)).cancelled - }) - ClientReceiveMessageEvents.ALLOW_GAME.register(ClientReceiveMessageEvents.AllowGame { message, overlay -> - lastReceivedMessage = message - overlay || (!ProcessChatEvent.publish(ProcessChatEvent(message, false)).cancelled && - !AllowChatEvent.publish(AllowChatEvent(message)).cancelled) - }) - ClientReceiveMessageEvents.MODIFY_GAME.register(ClientReceiveMessageEvents.ModifyGame { message, overlay -> - if (overlay) message - else ModifyChatEvent.publish(ModifyChatEvent(message)).replaceWith - }) - ClientReceiveMessageEvents.GAME_CANCELED.register(ClientReceiveMessageEvents.GameCanceled { message, overlay -> - if (!overlay && lastReceivedMessage !== message) { - ProcessChatEvent.publish(ProcessChatEvent(message, true)) - } - }) - ClientReceiveMessageEvents.CHAT_CANCELED.register(ClientReceiveMessageEvents.ChatCanceled { message, signedMessage, sender, params, receptionTimestamp -> - if (lastReceivedMessage !== message) { - ProcessChatEvent.publish(ProcessChatEvent(message, true)) - } - }) - - AttackBlockCallback.EVENT.register(AttackBlockCallback { player, world, hand, pos, direction -> - if (AttackBlockEvent.publish(AttackBlockEvent(player, world, hand, pos, direction)).cancelled) - ActionResult.CONSUME - else ActionResult.PASS - }) - UseBlockCallback.EVENT.register(UseBlockCallback { player, world, hand, hitResult -> - if (UseBlockEvent.publish(UseBlockEvent(player, world, hand, hitResult)).cancelled) - ActionResult.CONSUME - else ActionResult.PASS - }) -} diff --git a/src/main/kotlin/moe/nea/firmament/events/subscription/Subscription.kt b/src/main/kotlin/moe/nea/firmament/events/subscription/Subscription.kt deleted file mode 100644 index 83b91c9..0000000 --- a/src/main/kotlin/moe/nea/firmament/events/subscription/Subscription.kt +++ /dev/null @@ -1,16 +0,0 @@ - -package moe.nea.firmament.events.subscription - -import moe.nea.firmament.events.FirmamentEvent -import moe.nea.firmament.events.FirmamentEventBus -import moe.nea.firmament.features.FirmamentFeature - -interface SubscriptionOwner { - val delegateFeature: FirmamentFeature -} - -data class Subscription( - val owner: Any, - val invoke: (T) -> Unit, - val eventBus: FirmamentEventBus, -) diff --git a/src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt b/src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt deleted file mode 100644 index 19b91de..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt +++ /dev/null @@ -1,120 +0,0 @@ - - -package moe.nea.firmament.features - -import kotlinx.serialization.Serializable -import kotlinx.serialization.serializer -import moe.nea.firmament.Firmament -import moe.nea.firmament.annotations.generated.AllSubscriptions -import moe.nea.firmament.events.FeaturesInitializedEvent -import moe.nea.firmament.events.FirmamentEvent -import moe.nea.firmament.events.subscription.Subscription -import moe.nea.firmament.features.chat.AutoCompletions -import moe.nea.firmament.features.chat.ChatLinks -import moe.nea.firmament.features.chat.QuickCommands -import moe.nea.firmament.features.debug.DebugView -import moe.nea.firmament.features.debug.DeveloperFeatures -import moe.nea.firmament.features.debug.MinorTrolling -import moe.nea.firmament.features.debug.PowerUserTools -import moe.nea.firmament.features.diana.DianaWaypoints -import moe.nea.firmament.features.events.anniversity.AnniversaryFeatures -import moe.nea.firmament.features.events.carnival.CarnivalFeatures -import moe.nea.firmament.features.fixes.CompatibliltyFeatures -import moe.nea.firmament.features.fixes.Fixes -import moe.nea.firmament.features.inventory.CraftingOverlay -import moe.nea.firmament.features.inventory.ItemRarityCosmetics -import moe.nea.firmament.features.inventory.PriceData -import moe.nea.firmament.features.inventory.SaveCursorPosition -import moe.nea.firmament.features.inventory.SlotLocking -import moe.nea.firmament.features.inventory.buttons.InventoryButtons -import moe.nea.firmament.features.inventory.storageoverlay.StorageOverlay -import moe.nea.firmament.features.mining.PickaxeAbility -import moe.nea.firmament.features.mining.PristineProfitTracker -import moe.nea.firmament.features.texturepack.CustomSkyBlockTextures -import moe.nea.firmament.features.world.FairySouls -import moe.nea.firmament.features.world.Waypoints -import moe.nea.firmament.util.data.DataHolder - -object FeatureManager : DataHolder(serializer(), "features", ::Config) { - @Serializable - data class Config( - val enabledFeatures: MutableMap = mutableMapOf() - ) - - private val features = mutableMapOf() - - val allFeatures: Collection get() = features.values - - private var hasAutoloaded = false - - init { - autoload() - } - - fun autoload() { - synchronized(this) { - if (hasAutoloaded) return - loadFeature(MinorTrolling) - loadFeature(FairySouls) - loadFeature(AutoCompletions) - // TODO: loadFeature(FishingWarning) - loadFeature(SlotLocking) - loadFeature(StorageOverlay) - loadFeature(PristineProfitTracker) - loadFeature(CraftingOverlay) - loadFeature(PowerUserTools) - loadFeature(Waypoints) - loadFeature(ChatLinks) - loadFeature(InventoryButtons) - loadFeature(CompatibliltyFeatures) - loadFeature(AnniversaryFeatures) - loadFeature(QuickCommands) - loadFeature(SaveCursorPosition) - loadFeature(CustomSkyBlockTextures) - loadFeature(PriceData) - loadFeature(Fixes) - loadFeature(DianaWaypoints) - loadFeature(ItemRarityCosmetics) - loadFeature(PickaxeAbility) - loadFeature(CarnivalFeatures) - if (Firmament.DEBUG) { - loadFeature(DeveloperFeatures) - loadFeature(DebugView) - } - allFeatures.forEach { it.config } - FeaturesInitializedEvent.publish(FeaturesInitializedEvent(allFeatures.toList())) - hasAutoloaded = true - } - } - - fun subscribeEvents() { - AllSubscriptions.provideSubscriptions { - subscribeSingleEvent(it) - } - } - - private fun subscribeSingleEvent(it: Subscription) { - it.eventBus.subscribe(false, it.invoke) - } - - fun loadFeature(feature: FirmamentFeature) { - synchronized(features) { - if (feature.identifier in features) { - Firmament.logger.error("Double registering feature ${feature.identifier}. Ignoring second instance $feature") - return - } - features[feature.identifier] = feature - feature.onLoad() - } - } - - fun isEnabled(identifier: String): Boolean? = - data.enabledFeatures[identifier] - - - fun setEnabled(identifier: String, value: Boolean) { - data.enabledFeatures[identifier] = value - markDirty() - } - -} diff --git a/src/main/kotlin/moe/nea/firmament/features/FirmamentFeature.kt b/src/main/kotlin/moe/nea/firmament/features/FirmamentFeature.kt deleted file mode 100644 index 2cfc4fd..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/FirmamentFeature.kt +++ /dev/null @@ -1,23 +0,0 @@ - - -package moe.nea.firmament.features - -import moe.nea.firmament.events.subscription.SubscriptionOwner -import moe.nea.firmament.gui.config.ManagedConfig - -// TODO: remove this entire feature system and revamp config -interface FirmamentFeature : SubscriptionOwner { - val identifier: String - val defaultEnabled: Boolean - get() = true - var isEnabled: Boolean - get() = FeatureManager.isEnabled(identifier) ?: defaultEnabled - set(value) { - FeatureManager.setEnabled(identifier, value) - } - override val delegateFeature: FirmamentFeature - get() = this - val config: ManagedConfig? get() = null - fun onLoad() {} - -} diff --git a/src/main/kotlin/moe/nea/firmament/features/chat/AutoCompletions.kt b/src/main/kotlin/moe/nea/firmament/features/chat/AutoCompletions.kt deleted file mode 100644 index 9144898..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/chat/AutoCompletions.kt +++ /dev/null @@ -1,57 +0,0 @@ - - -package moe.nea.firmament.features.chat - -import com.mojang.brigadier.arguments.StringArgumentType.string -import moe.nea.firmament.annotations.Subscribe -import moe.nea.firmament.commands.get -import moe.nea.firmament.commands.suggestsList -import moe.nea.firmament.commands.thenArgument -import moe.nea.firmament.commands.thenExecute -import moe.nea.firmament.events.CommandEvent -import moe.nea.firmament.events.MaskCommands -import moe.nea.firmament.features.FirmamentFeature -import moe.nea.firmament.gui.config.ManagedConfig -import moe.nea.firmament.repo.RepoManager -import moe.nea.firmament.util.MC - -object AutoCompletions : FirmamentFeature { - - object TConfig : ManagedConfig(identifier) { - val provideWarpTabCompletion by toggle("warp-complete") { true } - val replaceWarpIsByWarpIsland by toggle("warp-is") { true } - } - - override val config: ManagedConfig? - get() = TConfig - override val identifier: String - get() = "auto-completions" - - @Subscribe - fun onMaskCommands(event: MaskCommands) { - if (TConfig.provideWarpTabCompletion) { - event.mask("warp") - } - } - - @Subscribe - fun onCommandEvent(event: CommandEvent) { - if (!TConfig.provideWarpTabCompletion) return - event.deleteCommand("warp") - event.register("warp") { - thenArgument("to", string()) { toArg -> - suggestsList { - RepoManager.neuRepo.constants?.islands?.warps?.flatMap { listOf(it.warp) + it.aliases } ?: listOf() - } - thenExecute { - val warpName = get(toArg) - if (warpName == "is" && TConfig.replaceWarpIsByWarpIsland) { - MC.sendServerCommand("warp island") - } else { - MC.sendServerCommand("warp $warpName") - } - } - } - } - } -} diff --git a/src/main/kotlin/moe/nea/firmament/features/chat/ChatLinks.kt b/src/main/kotlin/moe/nea/firmament/features/chat/ChatLinks.kt deleted file mode 100644 index f2cb78a..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/chat/ChatLinks.kt +++ /dev/null @@ -1,161 +0,0 @@ - - -package moe.nea.firmament.features.chat - -import io.ktor.client.request.get -import io.ktor.client.statement.bodyAsChannel -import io.ktor.utils.io.jvm.javaio.toInputStream -import java.net.URL -import java.util.Collections -import moe.nea.jarvis.api.Point -import kotlinx.coroutines.Deferred -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.async -import kotlin.math.min -import net.minecraft.client.gui.screen.ChatScreen -import net.minecraft.client.texture.NativeImage -import net.minecraft.client.texture.NativeImageBackedTexture -import net.minecraft.text.ClickEvent -import net.minecraft.text.HoverEvent -import net.minecraft.text.Style -import net.minecraft.text.Text -import net.minecraft.util.Formatting -import net.minecraft.util.Identifier -import moe.nea.firmament.Firmament -import moe.nea.firmament.annotations.Subscribe -import moe.nea.firmament.events.ModifyChatEvent -import moe.nea.firmament.events.ScreenRenderPostEvent -import moe.nea.firmament.features.FirmamentFeature -import moe.nea.firmament.gui.config.ManagedConfig -import moe.nea.firmament.util.MC -import moe.nea.firmament.util.transformEachRecursively -import moe.nea.firmament.util.unformattedString - -object ChatLinks : FirmamentFeature { - override val identifier: String - get() = "chat-links" - - object TConfig : ManagedConfig(identifier) { - val enableLinks by toggle("links-enabled") { true } - val imageEnabled by toggle("image-enabled") { true } - val allowAllHosts by toggle("allow-all-hosts") { false } - val allowedHosts by string("allowed-hosts") { "cdn.discordapp.com,media.discordapp.com,media.discordapp.net,i.imgur.com" } - val actualAllowedHosts get() = allowedHosts.split(",").map { it.trim() } - val position by position("position", 16 * 20, 9 * 20) { Point(0.0, 0.0) } - } - - private fun isHostAllowed(host: String) = - TConfig.allowAllHosts || TConfig.actualAllowedHosts.any { it.equals(host, ignoreCase = true) } - - private fun isUrlAllowed(url: String) = isHostAllowed(url.removePrefix("https://").substringBefore("/")) - - override val config get() = TConfig - val urlRegex = "https://[^. ]+\\.[^ ]+(\\.?( |$))".toRegex() - - data class Image( - val texture: Identifier, - val width: Int, - val height: Int, - ) - - val imageCache: MutableMap> = - Collections.synchronizedMap(mutableMapOf>()) - - private fun tryCacheUrl(url: String) { - if (!isUrlAllowed(url)) { - return - } - if (url in imageCache) { - return - } - imageCache[url] = Firmament.coroutineScope.async { - try { - val response = Firmament.httpClient.get(URL(url)) - if (response.status.value == 200) { - val inputStream = response.bodyAsChannel().toInputStream(Firmament.globalJob) - val image = NativeImage.read(inputStream) - val texture = MC.textureManager.registerDynamicTexture( - "dynamic_image_preview", - NativeImageBackedTexture(image) - ) - Image(texture, image.width, image.height) - } else - null - } catch (exc: Exception) { - exc.printStackTrace() - null - } - } - } - - val imageExtensions = listOf("jpg", "png", "gif", "jpeg") - fun isImageUrl(url: String): Boolean { - return (url.substringAfterLast('.').lowercase() in imageExtensions) - } - - @Subscribe - @OptIn(ExperimentalCoroutinesApi::class) - fun onRender(it: ScreenRenderPostEvent) { - if (!TConfig.imageEnabled) return - if (it.screen !is ChatScreen) return - val hoveredComponent = - MC.inGameHud.chatHud.getTextStyleAt(it.mouseX.toDouble(), it.mouseY.toDouble()) ?: return - val hoverEvent = hoveredComponent.hoverEvent ?: return - val value = hoverEvent.getValue(HoverEvent.Action.SHOW_TEXT) ?: return - val url = urlRegex.matchEntire(value.unformattedString)?.groupValues?.get(0) ?: return - if (!isImageUrl(url)) return - val imageFuture = imageCache[url] ?: return - if (!imageFuture.isCompleted) return - val image = imageFuture.getCompleted() ?: return - it.drawContext.matrices.push() - val pos = TConfig.position - pos.applyTransformations(it.drawContext.matrices) - val scale = min(1F, min((9 * 20F) / image.height, (16 * 20F) / image.width)) - it.drawContext.matrices.scale(scale, scale, 1F) - it.drawContext.drawTexture( - image.texture, - 0, - 0, - 1F, - 1F, - image.width, - image.height, - image.width, - image.height, - ) - it.drawContext.matrices.pop() - } - - @Subscribe - fun onModifyChat(it: ModifyChatEvent) { - if (!TConfig.enableLinks) return - it.replaceWith = it.replaceWith.transformEachRecursively { child -> - val text = child.string - if ("://" !in text) return@transformEachRecursively child - val s = Text.empty().setStyle(child.style) - var index = 0 - while (index < text.length) { - val nextMatch = urlRegex.find(text, index) - if (nextMatch == null) { - s.append(Text.literal(text.substring(index, text.length))) - break - } - val range = nextMatch.groups[0]!!.range - val url = nextMatch.groupValues[0] - s.append(Text.literal(text.substring(index, range.first))) - s.append( - Text.literal(url).setStyle( - Style.EMPTY.withUnderline(true).withColor( - Formatting.AQUA - ).withHoverEvent(HoverEvent(HoverEvent.Action.SHOW_TEXT, Text.literal(url))) - .withClickEvent(ClickEvent(ClickEvent.Action.OPEN_URL, url)) - ) - ) - if (isImageUrl(url)) - tryCacheUrl(url) - index = range.last + 1 - } - s - } - } -} diff --git a/src/main/kotlin/moe/nea/firmament/features/chat/QuickCommands.kt b/src/main/kotlin/moe/nea/firmament/features/chat/QuickCommands.kt deleted file mode 100644 index 5944b92..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/chat/QuickCommands.kt +++ /dev/null @@ -1,100 +0,0 @@ - - -package moe.nea.firmament.features.chat - -import com.mojang.brigadier.context.CommandContext -import net.minecraft.text.Text -import moe.nea.firmament.annotations.Subscribe -import moe.nea.firmament.commands.DefaultSource -import moe.nea.firmament.commands.RestArgumentType -import moe.nea.firmament.commands.get -import moe.nea.firmament.commands.thenArgument -import moe.nea.firmament.commands.thenExecute -import moe.nea.firmament.events.CommandEvent -import moe.nea.firmament.features.FirmamentFeature -import moe.nea.firmament.util.MC -import moe.nea.firmament.util.SBData - -object QuickCommands : FirmamentFeature { - override val identifier: String - get() = "quick-commands" - - fun removePartialPrefix(text: String, prefix: String): String? { - var lf: String? = null - for (i in 1..prefix.length) { - if (text.startsWith(prefix.substring(0, i))) { - lf = text.substring(i) - } - } - return lf - } - - val kuudraLevelNames = listOf("NORMAL", "HOT", "BURNING", "FIERY", "INFERNAL") - val dungeonLevelNames = listOf("ONE", "TWO", "THREE", "FOUR", "FIVE", "SIX", "SEVEN") - - @Subscribe - fun onCommands(it: CommandEvent) { - it.register("join") { - thenArgument("what", RestArgumentType) { what -> - thenExecute { - val what = this[what] - if (!SBData.isOnSkyblock) { - MC.sendCommand("join $what") - return@thenExecute - } - val joinName = getNameForFloor(what.replace(" ", "").lowercase()) - if (joinName == null) { - source.sendFeedback(Text.stringifiedTranslatable("firmament.quick-commands.join.unknown", what)) - } else { - source.sendFeedback(Text.stringifiedTranslatable("firmament.quick-commands.join.success", - joinName)) - MC.sendCommand("joininstance $joinName") - } - } - } - thenExecute { - source.sendFeedback(Text.translatable("firmament.quick-commands.join.explain")) - } - } - } - - fun CommandContext.getNameForFloor(w: String): String? { - val kuudraLevel = removePartialPrefix(w, "kuudratier") ?: removePartialPrefix(w, "tier") - if (kuudraLevel != null) { - val l = kuudraLevel.toIntOrNull()?.let { it - 1 } ?: kuudraLevelNames.indexOfFirst { - it.startsWith( - kuudraLevel, - true - ) - } - if (l !in kuudraLevelNames.indices) { - source.sendFeedback(Text.stringifiedTranslatable("firmament.quick-commands.join.unknown-kuudra", - kuudraLevel)) - return null - } - return "KUUDRA_${kuudraLevelNames[l]}" - } - val masterLevel = removePartialPrefix(w, "master") - val normalLevel = - removePartialPrefix(w, "floor") ?: removePartialPrefix(w, "catacombs") ?: removePartialPrefix(w, "dungeons") - val dungeonLevel = masterLevel ?: normalLevel - if (dungeonLevel != null) { - val l = dungeonLevel.toIntOrNull()?.let { it - 1 } ?: dungeonLevelNames.indexOfFirst { - it.startsWith( - dungeonLevel, - true - ) - } - if (masterLevel == null && (l == -1 || null != removePartialPrefix(w, "entrance"))) { - return "CATACOMBS_ENTRANCE" - } - if (l !in dungeonLevelNames.indices) { - source.sendFeedback(Text.stringifiedTranslatable("firmament.quick-commands.join.unknown-catacombs", - kuudraLevel)) - return null - } - return "${if (masterLevel != null) "MASTER_" else ""}CATACOMBS_FLOOR_${dungeonLevelNames[l]}" - } - return null - } -} diff --git a/src/main/kotlin/moe/nea/firmament/features/debug/DebugLogger.kt b/src/main/kotlin/moe/nea/firmament/features/debug/DebugLogger.kt deleted file mode 100644 index ab06030..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/debug/DebugLogger.kt +++ /dev/null @@ -1,13 +0,0 @@ - -package moe.nea.firmament.features.debug - -import net.minecraft.text.Text -import moe.nea.firmament.util.MC - -class DebugLogger(val tag: String) { - fun isEnabled() = DeveloperFeatures.isEnabled // TODO: allow filtering by tag - fun log(text: () -> String) { - if (!isEnabled()) return - MC.sendChat(Text.literal(text())) - } -} diff --git a/src/main/kotlin/moe/nea/firmament/features/debug/DebugView.kt b/src/main/kotlin/moe/nea/firmament/features/debug/DebugView.kt deleted file mode 100644 index 7e1b8ec..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/debug/DebugView.kt +++ /dev/null @@ -1,38 +0,0 @@ - - -package moe.nea.firmament.features.debug - -import moe.nea.firmament.Firmament -import moe.nea.firmament.events.TickEvent -import moe.nea.firmament.features.FirmamentFeature -import moe.nea.firmament.util.TimeMark - -object DebugView : FirmamentFeature { - private data class StoredVariable( - val obj: T, - val timer: TimeMark, - ) - - private val storedVariables: MutableMap> = sortedMapOf() - override val identifier: String - get() = "debug-view" - override val defaultEnabled: Boolean - get() = Firmament.DEBUG - - fun showVariable(label: String, obj: T) { - synchronized(this) { - storedVariables[label] = StoredVariable(obj, TimeMark.now()) - } - } - - fun recalculateDebugWidget() { - } - - override fun onLoad() { - TickEvent.subscribe { - synchronized(this) { - recalculateDebugWidget() - } - } - } -} diff --git a/src/main/kotlin/moe/nea/firmament/features/debug/DeveloperFeatures.kt b/src/main/kotlin/moe/nea/firmament/features/debug/DeveloperFeatures.kt deleted file mode 100644 index 20c0cfd..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/debug/DeveloperFeatures.kt +++ /dev/null @@ -1,55 +0,0 @@ - - -package moe.nea.firmament.features.debug - -import java.nio.file.Path -import java.util.concurrent.CompletableFuture -import kotlin.io.path.absolute -import kotlin.io.path.exists -import net.minecraft.client.MinecraftClient -import net.minecraft.text.Text -import moe.nea.firmament.Firmament -import moe.nea.firmament.features.FirmamentFeature -import moe.nea.firmament.gui.config.ManagedConfig -import moe.nea.firmament.util.MC -import moe.nea.firmament.util.TimeMark -import moe.nea.firmament.util.errorBoundary -import moe.nea.firmament.util.iterate - -object DeveloperFeatures : FirmamentFeature { - override val identifier: String - get() = "developer" - override val config: TConfig - get() = TConfig - override val defaultEnabled: Boolean - get() = Firmament.DEBUG - - val gradleDir = - Path.of(".").absolute() - .iterate { it.parent } - .find { it.resolve("settings.gradle.kts").exists() } - - object TConfig : ManagedConfig("developer") { - val autoRebuildResources by toggle("auto-rebuild") { false } - } - - @JvmStatic - fun hookOnBeforeResourceReload(client: MinecraftClient): CompletableFuture { - val reloadFuture = if (TConfig.autoRebuildResources && isEnabled && gradleDir != null) { - val builder = ProcessBuilder("./gradlew", ":processResources") - builder.directory(gradleDir.toFile()) - builder.inheritIO() - val process = builder.start() - MC.player?.sendMessage(Text.translatable("firmament.dev.resourcerebuild.start")) - val startTime = TimeMark.now() - process.toHandle().onExit().thenApply { - MC.player?.sendMessage(Text.stringifiedTranslatable("firmament.dev.resourcerebuild.done", startTime.passedTime())) - Unit - } - } else { - CompletableFuture.completedFuture(Unit) - } - return reloadFuture.thenCompose { client.reloadResources() } - } -} - diff --git a/src/main/kotlin/moe/nea/firmament/features/debug/MinorTrolling.kt b/src/main/kotlin/moe/nea/firmament/features/debug/MinorTrolling.kt deleted file mode 100644 index 32035a6..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/debug/MinorTrolling.kt +++ /dev/null @@ -1,27 +0,0 @@ - - -package moe.nea.firmament.features.debug - -import net.minecraft.text.Text -import moe.nea.firmament.annotations.Subscribe -import moe.nea.firmament.events.ModifyChatEvent -import moe.nea.firmament.features.FirmamentFeature - - -// In memorian Dulkir -object MinorTrolling : FirmamentFeature { - override val identifier: String - get() = "minor-trolling" - - val trollers = listOf("nea89o", "lrg89") - val t = "From(?: \\[[^\\]]+])? ([^:]+): (.*)".toRegex() - - @Subscribe - fun onTroll(it: ModifyChatEvent) { - val m = t.matchEntire(it.unformattedString) ?: return - val (_, name, text) = m.groupValues - if (name !in trollers) return - if (!text.startsWith("c:")) return - it.replaceWith = Text.literal(text.substring(2).replace("&", "§")) - } -} diff --git a/src/main/kotlin/moe/nea/firmament/features/debug/PowerUserTools.kt b/src/main/kotlin/moe/nea/firmament/features/debug/PowerUserTools.kt deleted file mode 100644 index 7893eff..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/debug/PowerUserTools.kt +++ /dev/null @@ -1,193 +0,0 @@ - - -package moe.nea.firmament.features.debug - -import net.minecraft.block.SkullBlock -import net.minecraft.block.entity.SkullBlockEntity -import net.minecraft.component.DataComponentTypes -import net.minecraft.entity.Entity -import net.minecraft.entity.LivingEntity -import net.minecraft.item.ItemStack -import net.minecraft.item.Items -import net.minecraft.text.Text -import net.minecraft.util.hit.BlockHitResult -import net.minecraft.util.hit.EntityHitResult -import net.minecraft.util.hit.HitResult -import moe.nea.firmament.annotations.Subscribe -import moe.nea.firmament.events.CommandEvent -import moe.nea.firmament.events.CustomItemModelEvent -import moe.nea.firmament.events.HandledScreenKeyPressedEvent -import moe.nea.firmament.events.ItemTooltipEvent -import moe.nea.firmament.events.ScreenChangeEvent -import moe.nea.firmament.events.TickEvent -import moe.nea.firmament.events.WorldKeyboardEvent -import moe.nea.firmament.features.FirmamentFeature -import moe.nea.firmament.features.texturepack.CustomSkyBlockTextures -import moe.nea.firmament.gui.config.ManagedConfig -import moe.nea.firmament.mixins.accessor.AccessorHandledScreen -import moe.nea.firmament.util.ClipboardUtils -import moe.nea.firmament.util.MC -import moe.nea.firmament.util.focusedItemStack -import moe.nea.firmament.util.skyBlockId - -object PowerUserTools : FirmamentFeature { - override val identifier: String - get() = "power-user" - - object TConfig : ManagedConfig(identifier) { - val showItemIds by toggle("show-item-id") { false } - val copyItemId by keyBindingWithDefaultUnbound("copy-item-id") - val copyTexturePackId by keyBindingWithDefaultUnbound("copy-texture-pack-id") - val copyNbtData by keyBindingWithDefaultUnbound("copy-nbt-data") - val copySkullTexture by keyBindingWithDefaultUnbound("copy-skull-texture") - val copyEntityData by keyBindingWithDefaultUnbound("entity-data") - } - - override val config - get() = TConfig - - var lastCopiedStack: Pair? = null - set(value) { - field = value - if (value != null) - lastCopiedStackViewTime = true - } - var lastCopiedStackViewTime = false - - override fun onLoad() { - TickEvent.subscribe { - if (!lastCopiedStackViewTime) - lastCopiedStack = null - lastCopiedStackViewTime = false - } - ScreenChangeEvent.subscribe { - lastCopiedStack = null - } - } - - fun debugFormat(itemStack: ItemStack): Text { - return Text.literal(itemStack.skyBlockId?.toString() ?: itemStack.toString()) - } - - @Subscribe - fun onEntityInfo(event: WorldKeyboardEvent) { - if (!event.matches(TConfig.copyEntityData)) return - val target = (MC.instance.crosshairTarget as? EntityHitResult)?.entity - if (target == null) { - MC.sendChat(Text.translatable("firmament.poweruser.entity.fail")) - return - } - showEntity(target) - } - - fun showEntity(target: Entity) { - MC.sendChat(Text.translatable("firmament.poweruser.entity.type", target.type)) - MC.sendChat(Text.translatable("firmament.poweruser.entity.name", target.name)) - MC.sendChat(Text.stringifiedTranslatable("firmament.poweruser.entity.position", target.pos)) - if (target is LivingEntity) { - MC.sendChat(Text.translatable("firmament.poweruser.entity.armor")) - for (armorItem in target.armorItems) { - MC.sendChat(Text.translatable("firmament.poweruser.entity.armor.item", debugFormat(armorItem))) - } - } - MC.sendChat(Text.stringifiedTranslatable("firmament.poweruser.entity.passengers", target.passengerList.size)) - target.passengerList.forEach { - showEntity(it) - } - } - - - @Subscribe - fun copyInventoryInfo(it: HandledScreenKeyPressedEvent) { - if (it.screen !is AccessorHandledScreen) return - val item = it.screen.focusedItemStack ?: return - if (it.matches(TConfig.copyItemId)) { - val sbId = item.skyBlockId - if (sbId == null) { - lastCopiedStack = Pair(item, Text.translatable("firmament.tooltip.copied.skyblockid.fail")) - return - } - ClipboardUtils.setTextContent(sbId.neuItem) - lastCopiedStack = - Pair(item, Text.stringifiedTranslatable("firmament.tooltip.copied.skyblockid", sbId.neuItem)) - } else if (it.matches(TConfig.copyTexturePackId)) { - val model = CustomItemModelEvent.getModelIdentifier(item) - if (model == null) { - lastCopiedStack = Pair(item, Text.translatable("firmament.tooltip.copied.modelid.fail")) - return - } - ClipboardUtils.setTextContent(model.toString()) - lastCopiedStack = - Pair(item, Text.stringifiedTranslatable("firmament.tooltip.copied.modelid", model.toString())) - } else if (it.matches(TConfig.copyNbtData)) { - // TODO: copy full nbt - val nbt = item.get(DataComponentTypes.CUSTOM_DATA)?.nbt?.toString() ?: "" - ClipboardUtils.setTextContent(nbt) - lastCopiedStack = Pair(item, Text.translatable("firmament.tooltip.copied.nbt")) - } else if (it.matches(TConfig.copySkullTexture)) { - if (item.item != Items.PLAYER_HEAD) { - lastCopiedStack = Pair(item, Text.translatable("firmament.tooltip.copied.skull-id.fail.no-skull")) - return - } - val profile = item.get(DataComponentTypes.PROFILE) - if (profile == null) { - lastCopiedStack = Pair(item, Text.translatable("firmament.tooltip.copied.skull-id.fail.no-profile")) - return - } - val skullTexture = CustomSkyBlockTextures.getSkullTexture(profile) - if (skullTexture == null) { - lastCopiedStack = Pair(item, Text.translatable("firmament.tooltip.copied.skull-id.fail.no-texture")) - return - } - ClipboardUtils.setTextContent(skullTexture.toString()) - lastCopiedStack = - Pair( - item, - Text.stringifiedTranslatable("firmament.tooltip.copied.skull-id", skullTexture.toString()) - ) - println("Copied skull id: $skullTexture") - } - } - - @Subscribe - fun onCopyWorldInfo(it: WorldKeyboardEvent) { - if (it.matches(TConfig.copySkullTexture)) { - val p = MC.camera ?: return - val blockHit = p.raycast(20.0, 0.0f, false) ?: return - if (blockHit.type != HitResult.Type.BLOCK || blockHit !is BlockHitResult) { - MC.sendChat(Text.translatable("firmament.tooltip.copied.skull.fail")) - return - } - val blockAt = p.world.getBlockState(blockHit.blockPos)?.block - val entity = p.world.getBlockEntity(blockHit.blockPos) - if (blockAt !is SkullBlock || entity !is SkullBlockEntity || entity.owner == null) { - MC.sendChat(Text.translatable("firmament.tooltip.copied.skull.fail")) - return - } - val id = CustomSkyBlockTextures.getSkullTexture(entity.owner!!) - if (id == null) { - MC.sendChat(Text.translatable("firmament.tooltip.copied.skull.fail")) - } else { - ClipboardUtils.setTextContent(id.toString()) - MC.sendChat(Text.stringifiedTranslatable("firmament.tooltip.copied.skull", id.toString())) - } - } - } - - @Subscribe - fun addItemId(it: ItemTooltipEvent) { - if (TConfig.showItemIds) { - val id = it.stack.skyBlockId ?: return - it.lines.add(Text.stringifiedTranslatable("firmament.tooltip.skyblockid", id.neuItem)) - } - val (item, text) = lastCopiedStack ?: return - if (!ItemStack.areEqual(item, it.stack)) { - lastCopiedStack = null - return - } - lastCopiedStackViewTime = true - it.lines.add(text) - } - - -} diff --git a/src/main/kotlin/moe/nea/firmament/features/diana/AncestralSpadeSolver.kt b/src/main/kotlin/moe/nea/firmament/features/diana/AncestralSpadeSolver.kt deleted file mode 100644 index 39ca6d3..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/diana/AncestralSpadeSolver.kt +++ /dev/null @@ -1,131 +0,0 @@ - -package moe.nea.firmament.features.diana - -import kotlin.time.Duration.Companion.seconds -import net.minecraft.particle.ParticleTypes -import net.minecraft.sound.SoundEvents -import net.minecraft.util.math.Vec3d -import moe.nea.firmament.annotations.Subscribe -import moe.nea.firmament.events.ParticleSpawnEvent -import moe.nea.firmament.events.SoundReceiveEvent -import moe.nea.firmament.events.WorldKeyboardEvent -import moe.nea.firmament.events.WorldReadyEvent -import moe.nea.firmament.events.WorldRenderLastEvent -import moe.nea.firmament.events.subscription.SubscriptionOwner -import moe.nea.firmament.features.FirmamentFeature -import moe.nea.firmament.util.MC -import moe.nea.firmament.util.SBData -import moe.nea.firmament.util.SkyBlockIsland -import moe.nea.firmament.util.SkyblockId -import moe.nea.firmament.util.TimeMark -import moe.nea.firmament.util.WarpUtil -import moe.nea.firmament.util.render.RenderInWorldContext -import moe.nea.firmament.util.skyBlockId - -object AncestralSpadeSolver : SubscriptionOwner { - var lastDing = TimeMark.farPast() - private set - private val pitches = mutableListOf() - val particlePositions = mutableListOf() - var nextGuess: Vec3d? = null - private set - - val ancestralSpadeId = SkyblockId("ANCESTRAL_SPADE") - private var lastTeleportAttempt = TimeMark.farPast() - - fun isEnabled() = - DianaWaypoints.TConfig.ancestralSpadeSolver - && SBData.skyblockLocation == SkyBlockIsland.HUB - && MC.player?.inventory?.containsAny { it.skyBlockId == ancestralSpadeId } == true // TODO: add a reactive property here - - @Subscribe - fun onKeyBind(event: WorldKeyboardEvent) { - if (!isEnabled()) return - if (!event.matches(DianaWaypoints.TConfig.ancestralSpadeTeleport)) return - - if (lastTeleportAttempt.passedTime() < 3.seconds) return - WarpUtil.teleportToNearestWarp(SkyBlockIsland.HUB, nextGuess ?: return) - lastTeleportAttempt = TimeMark.now() - } - - @Subscribe - fun onParticleSpawn(event: ParticleSpawnEvent) { - if (!isEnabled()) return - if (event.particleEffect != ParticleTypes.DRIPPING_LAVA) return - if (event.offset.x != 0.0F || event.offset.y != 0F || event.offset.z != 0F) - return - particlePositions.add(event.position) - if (particlePositions.size > 20) { - particlePositions.removeFirst() - } - } - - @Subscribe - fun onPlaySound(event: SoundReceiveEvent) { - if (!isEnabled()) return - if (!SoundEvents.BLOCK_NOTE_BLOCK_HARP.matchesId(event.sound.value().id)) return - - if (lastDing.passedTime() > 1.seconds) { - particlePositions.clear() - pitches.clear() - } - lastDing = TimeMark.now() - - pitches.add(event.pitch) - if (pitches.size > 20) { - pitches.removeFirst() - } - - if (particlePositions.size < 3) { - return - } - - val averagePitchDelta = - if (pitches.isEmpty()) return - else pitches - .zipWithNext { a, b -> b - a } - .average() - - val soundDistanceEstimate = (Math.E / averagePitchDelta) - particlePositions.first().distanceTo(event.position) - - if (soundDistanceEstimate > 1000) { - return - } - - val lastParticleDirection = particlePositions - .takeLast(3) - .let { (a, _, b) -> b.subtract(a) } - .normalize() - - nextGuess = event.position.add(lastParticleDirection.multiply(soundDistanceEstimate)) - } - - @Subscribe - fun onWorldRender(event: WorldRenderLastEvent) { - if (!isEnabled()) return - RenderInWorldContext.renderInWorld(event) { - nextGuess?.let { - color(1f, 1f, 0f, 0.5f) - tinyBlock(it, 1f) - color(1f, 1f, 0f, 1f) - tracer(it, lineWidth = 3f) - } - if (particlePositions.size > 2 && lastDing.passedTime() < 10.seconds && nextGuess != null) { - color(0f, 1f, 0f, 0.7f) - line(particlePositions) - } - } - } - - @Subscribe - fun onSwapWorld(event: WorldReadyEvent) { - nextGuess = null - particlePositions.clear() - pitches.clear() - lastDing = TimeMark.farPast() - } - - override val delegateFeature: FirmamentFeature - get() = DianaWaypoints - -} diff --git a/src/main/kotlin/moe/nea/firmament/features/diana/DianaWaypoints.kt b/src/main/kotlin/moe/nea/firmament/features/diana/DianaWaypoints.kt deleted file mode 100644 index 0a34eaa..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/diana/DianaWaypoints.kt +++ /dev/null @@ -1,35 +0,0 @@ - -package moe.nea.firmament.features.diana - -import moe.nea.firmament.events.AttackBlockEvent -import moe.nea.firmament.events.ParticleSpawnEvent -import moe.nea.firmament.events.ProcessChatEvent -import moe.nea.firmament.events.SoundReceiveEvent -import moe.nea.firmament.events.UseBlockEvent -import moe.nea.firmament.events.WorldKeyboardEvent -import moe.nea.firmament.events.WorldReadyEvent -import moe.nea.firmament.events.WorldRenderLastEvent -import moe.nea.firmament.features.FirmamentFeature -import moe.nea.firmament.gui.config.ManagedConfig - -object DianaWaypoints : FirmamentFeature { - override val identifier get() = "diana" - override val config get() = TConfig - - object TConfig : ManagedConfig(identifier) { - val ancestralSpadeSolver by toggle("ancestral-spade") { true } - val ancestralSpadeTeleport by keyBindingWithDefaultUnbound("ancestral-teleport") - val nearbyWaypoints by toggle("nearby-waypoints") { true } - } - - override fun onLoad() { - UseBlockEvent.subscribe { - NearbyBurrowsSolver.onBlockClick(it.hitResult.blockPos) - } - AttackBlockEvent.subscribe { - NearbyBurrowsSolver.onBlockClick(it.blockPos) - } - } -} - - diff --git a/src/main/kotlin/moe/nea/firmament/features/diana/NearbyBurrowsSolver.kt b/src/main/kotlin/moe/nea/firmament/features/diana/NearbyBurrowsSolver.kt deleted file mode 100644 index 7158bb9..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/diana/NearbyBurrowsSolver.kt +++ /dev/null @@ -1,144 +0,0 @@ - -package moe.nea.firmament.features.diana - -import kotlin.time.Duration.Companion.seconds -import net.minecraft.particle.ParticleTypes -import net.minecraft.util.math.BlockPos -import net.minecraft.util.math.MathHelper -import net.minecraft.util.math.Position -import moe.nea.firmament.annotations.Subscribe -import moe.nea.firmament.events.ParticleSpawnEvent -import moe.nea.firmament.events.ProcessChatEvent -import moe.nea.firmament.events.WorldReadyEvent -import moe.nea.firmament.events.WorldRenderLastEvent -import moe.nea.firmament.events.subscription.SubscriptionOwner -import moe.nea.firmament.features.FirmamentFeature -import moe.nea.firmament.util.TimeMark -import moe.nea.firmament.util.mutableMapWithMaxSize -import moe.nea.firmament.util.render.RenderInWorldContext.Companion.renderInWorld - -object NearbyBurrowsSolver : SubscriptionOwner { - - - private val recentlyDugBurrows: MutableMap = mutableMapWithMaxSize(20) - private val recentEnchantParticles: MutableMap = mutableMapWithMaxSize(500) - private var lastBlockClick: BlockPos? = null - - enum class BurrowType { - START, MOB, TREASURE - } - - val burrows = mutableMapOf() - - @Subscribe - fun onChatEvent(event: ProcessChatEvent) { - val lastClickedBurrow = lastBlockClick ?: return - if (event.unformattedString.startsWith("You dug out a Griffin Burrow!") || - event.unformattedString.startsWith(" ☠ You were killed by") || - event.unformattedString.startsWith("You finished the Griffin burrow chain!") - ) { - markAsDug(lastClickedBurrow) - burrows.remove(lastClickedBurrow) - } - } - - - fun wasRecentlyDug(blockPos: BlockPos): Boolean { - val lastDigTime = recentlyDugBurrows[blockPos] ?: TimeMark.farPast() - return lastDigTime.passedTime() < 10.seconds - } - - fun markAsDug(blockPos: BlockPos) { - recentlyDugBurrows[blockPos] = TimeMark.now() - } - - fun wasRecentlyEnchanted(blockPos: BlockPos): Boolean { - val lastEnchantTime = recentEnchantParticles[blockPos] ?: TimeMark.farPast() - return lastEnchantTime.passedTime() < 4.seconds - } - - fun markAsEnchanted(blockPos: BlockPos) { - recentEnchantParticles[blockPos] = TimeMark.now() - } - - @Subscribe - fun onParticles(event: ParticleSpawnEvent) { - if (!DianaWaypoints.TConfig.nearbyWaypoints) return - - val position: BlockPos = event.position.toBlockPos().down() - - if (wasRecentlyDug(position)) return - - val isEven50Spread = (event.offset.x == 0.5f && event.offset.z == 0.5f) - - if (event.particleEffect.type == ParticleTypes.ENCHANT) { - if (event.count == 5 && event.speed == 0.05F && event.offset.y == 0.4F && isEven50Spread) { - markAsEnchanted(position) - } - return - } - - if (!wasRecentlyEnchanted(position)) return - - if (event.particleEffect.type == ParticleTypes.ENCHANTED_HIT - && event.count == 4 - && event.speed == 0.01F - && event.offset.y == 0.1f - && isEven50Spread - ) { - burrows[position] = BurrowType.START - } - if (event.particleEffect.type == ParticleTypes.CRIT - && event.count == 3 - && event.speed == 0.01F - && event.offset.y == 0.1F - && isEven50Spread - ) { - burrows[position] = BurrowType.MOB - } - if (event.particleEffect.type == ParticleTypes.DRIPPING_LAVA - && event.count == 2 - && event.speed == 0.01F - && event.offset.y == 0.1F - && event.offset.x == 0.35F && event.offset.z == 0.35f - ) { - burrows[position] = BurrowType.TREASURE - } - } - - @Subscribe - fun onRender(event: WorldRenderLastEvent) { - if (!DianaWaypoints.TConfig.nearbyWaypoints) return - renderInWorld(event) { - for ((location, burrow) in burrows) { - when (burrow) { - BurrowType.START -> color(.2f, .8f, .2f, 0.4f) - BurrowType.MOB -> color(0.3f, 0.4f, 0.9f, 0.4f) - BurrowType.TREASURE -> color(1f, 0.7f, 0.2f, 0.4f) - } - block(location) - } - } - } - - @Subscribe - fun onSwapWorld(worldReadyEvent: WorldReadyEvent) { - burrows.clear() - recentEnchantParticles.clear() - recentlyDugBurrows.clear() - lastBlockClick = null - } - - fun onBlockClick(blockPos: BlockPos) { - if (!DianaWaypoints.TConfig.nearbyWaypoints) return - burrows.remove(blockPos) - lastBlockClick = blockPos - } - - override val delegateFeature: FirmamentFeature - get() = DianaWaypoints -} - -fun Position.toBlockPos(): BlockPos { - return BlockPos(MathHelper.floor(x), MathHelper.floor(y), MathHelper.floor(z)) -} diff --git a/src/main/kotlin/moe/nea/firmament/features/events/anniversity/AnniversaryFeatures.kt b/src/main/kotlin/moe/nea/firmament/features/events/anniversity/AnniversaryFeatures.kt deleted file mode 100644 index 8926a95..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/events/anniversity/AnniversaryFeatures.kt +++ /dev/null @@ -1,224 +0,0 @@ - -package moe.nea.firmament.features.events.anniversity - -import io.github.notenoughupdates.moulconfig.observer.ObservableList -import io.github.notenoughupdates.moulconfig.xml.Bind -import moe.nea.jarvis.api.Point -import kotlin.time.Duration.Companion.seconds -import net.minecraft.entity.passive.PigEntity -import net.minecraft.util.math.BlockPos -import moe.nea.firmament.annotations.Subscribe -import moe.nea.firmament.events.EntityInteractionEvent -import moe.nea.firmament.events.ProcessChatEvent -import moe.nea.firmament.events.TickEvent -import moe.nea.firmament.events.WorldReadyEvent -import moe.nea.firmament.features.FirmamentFeature -import moe.nea.firmament.gui.config.ManagedConfig -import moe.nea.firmament.gui.hud.MoulConfigHud -import moe.nea.firmament.rei.SBItemEntryDefinition -import moe.nea.firmament.repo.ItemNameLookup -import moe.nea.firmament.util.MC -import moe.nea.firmament.util.SHORT_NUMBER_FORMAT -import moe.nea.firmament.util.SkyblockId -import moe.nea.firmament.util.TimeMark -import moe.nea.firmament.util.parseShortNumber -import moe.nea.firmament.util.useMatch - -object AnniversaryFeatures : FirmamentFeature { - override val identifier: String - get() = "anniversary" - - object TConfig : ManagedConfig(identifier) { - val enableShinyPigTracker by toggle("shiny-pigs") {true} - val trackPigCooldown by position("pig-hud", 200, 300) { Point(0.1, 0.2) } - } - - override val config: ManagedConfig? - get() = TConfig - - data class ClickedPig( - val clickedAt: TimeMark, - val startLocation: BlockPos, - val pigEntity: PigEntity - ) { - @Bind("timeLeft") - fun getTimeLeft(): Double = 1 - clickedAt.passedTime() / pigDuration - } - - val clickedPigs = ObservableList(mutableListOf()) - var lastClickedPig: PigEntity? = null - - val pigDuration = 90.seconds - - @Subscribe - fun onTick(event: TickEvent) { - clickedPigs.removeIf { it.clickedAt.passedTime() > pigDuration } - } - - val pattern = "SHINY! You extracted (?.*) from the piglet's orb!".toPattern() - - @Subscribe - fun onChat(event: ProcessChatEvent) { - if(!TConfig.enableShinyPigTracker)return - if (event.unformattedString == "Oink! Bring the pig back to the Shiny Orb!") { - val pig = lastClickedPig ?: return - // TODO: store proper location based on the orb location, maybe - val startLocation = pig.blockPos ?: return - clickedPigs.add(ClickedPig(TimeMark.now(), startLocation, pig)) - lastClickedPig = null - } - if (event.unformattedString == "SHINY! The orb is charged! Click on it for loot!") { - val player = MC.player ?: return - val lowest = - clickedPigs.minByOrNull { it.startLocation.getSquaredDistance(player.pos) } ?: return - clickedPigs.remove(lowest) - } - pattern.useMatch(event.unformattedString) { - val reward = group("reward") - val parsedReward = parseReward(reward) - addReward(parsedReward) - PigCooldown.rewards.atOnce { - PigCooldown.rewards.clear() - rewards.mapTo(PigCooldown.rewards) { PigCooldown.DisplayReward(it) } - } - } - } - - fun addReward(reward: Reward) { - val it = rewards.listIterator() - while (it.hasNext()) { - val merged = reward.mergeWith(it.next()) ?: continue - it.set(merged) - return - } - rewards.add(reward) - } - - val rewards = mutableListOf() - - fun ObservableList.atOnce(block: () -> Unit) { - val oldObserver = observer - observer = null - block() - observer = oldObserver - update() - } - - sealed interface Reward { - fun mergeWith(other: Reward): Reward? - data class EXP(val amount: Double, val skill: String) : Reward { - override fun mergeWith(other: Reward): Reward? { - if (other is EXP && other.skill == skill) - return EXP(amount + other.amount, skill) - return null - } - } - - data class Coins(val amount: Double) : Reward { - override fun mergeWith(other: Reward): Reward? { - if (other is Coins) - return Coins(other.amount + amount) - return null - } - } - - data class Items(val amount: Int, val item: SkyblockId) : Reward { - override fun mergeWith(other: Reward): Reward? { - if (other is Items && other.item == item) - return Items(amount + other.amount, item) - return null - } - } - - data class Unknown(val text: String) : Reward { - override fun mergeWith(other: Reward): Reward? { - return null - } - } - } - - val expReward = "\\+(?$SHORT_NUMBER_FORMAT) (?[^ ]+) XP".toPattern() - val coinReward = "\\+(?$SHORT_NUMBER_FORMAT) coins".toPattern() - val itemReward = "(?:(?[0-9]+)x )?(?.*)".toPattern() - fun parseReward(string: String): Reward { - expReward.useMatch(string) { - val exp = parseShortNumber(group("exp")) - val kind = group("kind") - return Reward.EXP(exp, kind) - } - coinReward.useMatch(string) { - val coins = parseShortNumber(group("amount")) - return Reward.Coins(coins) - } - itemReward.useMatch(string) { - val amount = group("amount")?.toIntOrNull() ?: 1 - val name = group("name") - val item = ItemNameLookup.guessItemByName(name, false) ?: return@useMatch - return Reward.Items(amount, item) - } - return Reward.Unknown(string) - } - - @Subscribe - fun onWorldClear(event: WorldReadyEvent) { - lastClickedPig = null - clickedPigs.clear() - } - - @Subscribe - fun onEntityClick(event: EntityInteractionEvent) { - if (event.entity is PigEntity) { - lastClickedPig = event.entity - } - } - - @Subscribe - fun init(event: WorldReadyEvent) { - PigCooldown.forceInit() - } - - object PigCooldown : MoulConfigHud("anniversary_pig", TConfig.trackPigCooldown) { - override fun shouldRender(): Boolean { - return clickedPigs.isNotEmpty() && TConfig.enableShinyPigTracker - } - - @Bind("pigs") - fun getPigs() = clickedPigs - - class DisplayReward(val backedBy: Reward) { - @Bind - fun count(): String { - return when (backedBy) { - is Reward.Coins -> backedBy.amount - is Reward.EXP -> backedBy.amount - is Reward.Items -> backedBy.amount - is Reward.Unknown -> 0 - }.toString() - } - - val itemStack = if (backedBy is Reward.Items) { - SBItemEntryDefinition.getEntry(backedBy.item, backedBy.amount) - } else { - SBItemEntryDefinition.getEntry(SkyblockId.NULL) - } - - @Bind - fun name(): String { - return when (backedBy) { - is Reward.Coins -> "Coins" - is Reward.EXP -> backedBy.skill - is Reward.Items -> itemStack.value.asItemStack().name.string - is Reward.Unknown -> backedBy.text - } - } - - @Bind - fun isKnown() = backedBy !is Reward.Unknown - } - - @get:Bind("rewards") - val rewards = ObservableList(mutableListOf()) - - } - -} diff --git a/src/main/kotlin/moe/nea/firmament/features/events/carnival/CarnivalFeatures.kt b/src/main/kotlin/moe/nea/firmament/features/events/carnival/CarnivalFeatures.kt deleted file mode 100644 index 1e6d97a..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/events/carnival/CarnivalFeatures.kt +++ /dev/null @@ -1,17 +0,0 @@ - -package moe.nea.firmament.features.events.carnival - -import moe.nea.firmament.features.FirmamentFeature -import moe.nea.firmament.gui.config.ManagedConfig - -object CarnivalFeatures : FirmamentFeature { - object TConfig : ManagedConfig(identifier) { - val enableBombSolver by toggle("bombs-solver") { true } - val displayTutorials by toggle("tutorials") { true } - } - - override val config: ManagedConfig? - get() = TConfig - override val identifier: String - get() = "carnival" -} diff --git a/src/main/kotlin/moe/nea/firmament/features/events/carnival/MinesweeperHelper.kt b/src/main/kotlin/moe/nea/firmament/features/events/carnival/MinesweeperHelper.kt deleted file mode 100644 index 06caf86..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/events/carnival/MinesweeperHelper.kt +++ /dev/null @@ -1,276 +0,0 @@ - -package moe.nea.firmament.features.events.carnival - -import io.github.notenoughupdates.moulconfig.observer.ObservableList -import io.github.notenoughupdates.moulconfig.platform.ModernItemStack -import io.github.notenoughupdates.moulconfig.xml.Bind -import java.util.UUID -import net.minecraft.block.Blocks -import net.minecraft.item.Item -import net.minecraft.item.ItemStack -import net.minecraft.item.Items -import net.minecraft.text.ClickEvent -import net.minecraft.text.Text -import net.minecraft.util.math.BlockPos -import net.minecraft.world.WorldAccess -import moe.nea.firmament.annotations.Subscribe -import moe.nea.firmament.commands.thenExecute -import moe.nea.firmament.events.AttackBlockEvent -import moe.nea.firmament.events.CommandEvent -import moe.nea.firmament.events.EntityUpdateEvent -import moe.nea.firmament.events.ProcessChatEvent -import moe.nea.firmament.events.WorldReadyEvent -import moe.nea.firmament.events.WorldRenderLastEvent -import moe.nea.firmament.features.debug.DebugLogger -import moe.nea.firmament.util.LegacyFormattingCode -import moe.nea.firmament.util.MC -import moe.nea.firmament.util.MoulConfigUtils -import moe.nea.firmament.util.ScreenUtil -import moe.nea.firmament.util.SkyblockId -import moe.nea.firmament.util.item.createSkullItem -import moe.nea.firmament.util.render.RenderInWorldContext -import moe.nea.firmament.util.setSkyBlockFirmamentUiId -import moe.nea.firmament.util.skyBlockId -import moe.nea.firmament.util.useMatch - -object MinesweeperHelper { - val sandBoxLow = BlockPos(-112, 72, -11) - val sandBoxHigh = BlockPos(-106, 72, -5) - val boardSize = Pair(sandBoxHigh.x - sandBoxLow.x, sandBoxHigh.z - sandBoxLow.z) - - val gameStartMessage = "[NPC] Carnival Pirateman: Good luck, matey!" - val gameEndMessage = "Fruit Digging" - val bombPattern = "MINES! There (are|is) (?[0-8]) bombs? hidden nearby\\.".toPattern() - val startGameQuestion = "[NPC] Carnival Pirateman: Would ye like to do some Fruit Digging?" - - - enum class Piece( - @get:Bind("fruitName") - val fruitName: String, - val points: Int, - val specialAbility: String, - val totalPerBoard: Int, - val textureHash: String, - val fruitColor: LegacyFormattingCode, - ) { - COCONUT("Coconut", - 200, - "Prevents a bomb from exploding next turn", - 3, - "10ceb1455b471d016a9f06d25f6e468df9fcf223e2c1e4795b16e84fcca264ee", - LegacyFormattingCode.DARK_PURPLE), - APPLE("Apple", - 100, - "Gains 100 points for each apple dug up", - 8, - "17ea278d6225c447c5943d652798d0bbbd1418434ce8c54c54fdac79994ddd6c", - LegacyFormattingCode.GREEN), - WATERMELON("Watermelon", - 100, - "Blows up an adjacent fruit for half the points", - 4, - "efe4ef83baf105e8dee6cf03dfe7407f1911b3b9952c891ae34139560f2931d6", - LegacyFormattingCode.DARK_BLUE), - DURIAN("Durian", - 800, - "Halves the points earned in the next turn", - 2, - "ac268d36c2c6047ffeec00124096376b56dbb4d756a55329363a1b27fcd659cd", - LegacyFormattingCode.DARK_PURPLE), - MANGO("Mango", - 300, - "Just an ordinary fruit", - 10, - "f363a62126a35537f8189343a22660de75e810c6ac004a7d3da65f1c040a839", - LegacyFormattingCode.GREEN), - DRAGON_FRUIT("Dragonfruit", - 1200, - "Halves the points earned in the next turn", - 1, - "3cc761bcb0579763d9b8ab6b7b96fa77eb6d9605a804d838fec39e7b25f95591", - LegacyFormattingCode.LIGHT_PURPLE), - POMEGRANATE("Pomegranate", - 200, - "Grants an extra 50% more points in the next turn", - 4, - "40824d18079042d5769f264f44394b95b9b99ce689688cc10c9eec3f882ccc08", - LegacyFormattingCode.DARK_BLUE), - CHERRY("Cherry", - 200, - "The second cherry grants 300 bonus points", - 2, - "c92b099a62cd2fbf8ada09dec145c75d7fda4dc57b968bea3a8fa11e37aa48b2", - LegacyFormattingCode.DARK_PURPLE), - BOMB("Bomb", - -1, - "Destroys nearby fruit", - 15, - "a76a2811d1e176a07b6d0a657b910f134896ce30850f6e80c7c83732d85381ea", - LegacyFormattingCode.DARK_RED), - RUM("Rum", - -1, - "Stops your dowsing ability for one turn", - 5, - "407b275d28b927b1bf7f6dd9f45fbdad2af8571c54c8f027d1bff6956fbf3c16", - LegacyFormattingCode.YELLOW), - ; - - val textureUrl = "http://textures.minecraft.net/texture/$textureHash" - val itemStack = createSkullItem(UUID.randomUUID(), textureUrl) - .setSkyBlockFirmamentUiId("MINESWEEPER_$name") - - @Bind - fun getIcon() = ModernItemStack.of(itemStack) - - @Bind - fun pieceLabel() = fruitColor.formattingCode + fruitName - - @Bind - fun boardLabel() = "§a$totalPerBoard§7/§rboard" - - @Bind("description") - fun getDescription() = buildString { - append(specialAbility) - if (points >= 0) { - append(" Default points: $points.") - } - } - } - - object TutorialScreen { - @get:Bind("pieces") - val pieces = ObservableList(Piece.entries.toList().reversed()) - - @get:Bind("modes") - val modes = ObservableList(DowsingMode.entries.toList()) - } - - enum class DowsingMode( - val itemType: Item, - @get:Bind("feature") - val feature: String, - @get:Bind("description") - val description: String, - ) { - MINES(Items.IRON_SHOVEL, "Bomb detection", "Tells you how many bombs are near the block"), - ANCHOR(Items.DIAMOND_SHOVEL, "Lowest fruit", "Shows you which block nearby contains the lowest scoring fruit"), - TREASURE(Items.GOLDEN_SHOVEL, "Highest fruit", "Tells you which kind of fruit is the highest scoring nearby"), - ; - - @Bind("itemType") - fun getItemStack() = ModernItemStack.of(ItemStack(itemType)) - - companion object { - val id = SkyblockId("CARNIVAL_SHOVEL") - fun fromItem(itemStack: ItemStack): DowsingMode? { - if (itemStack.skyBlockId != id) return null - return DowsingMode.entries.find { it.itemType == itemStack.item } - } - } - } - - data class BoardPosition( - val x: Int, - val y: Int - ) { - fun toBlockPos() = BlockPos(sandBoxLow.x + x, sandBoxLow.y, sandBoxLow.z + y) - - fun getBlock(world: WorldAccess) = world.getBlockState(toBlockPos()).block - fun isUnopened(world: WorldAccess) = getBlock(world) == Blocks.SAND - fun isOpened(world: WorldAccess) = getBlock(world) == Blocks.SANDSTONE - fun isScorched(world: WorldAccess) = getBlock(world) == Blocks.SANDSTONE_STAIRS - - companion object { - fun fromBlockPos(blockPos: BlockPos): BoardPosition? { - if (blockPos.y != sandBoxLow.y) return null - val x = blockPos.x - sandBoxLow.x - val y = blockPos.z - sandBoxLow.z - if (x < 0 || x >= boardSize.first) return null - if (y < 0 || y >= boardSize.second) return null - return BoardPosition(x, y) - } - } - } - - data class GameState( - val nearbyBombs: MutableMap = mutableMapOf(), - val knownBombPositions: MutableSet = mutableSetOf(), - var lastClickedPosition: BoardPosition? = null, - var lastDowsingMode: DowsingMode? = null, - ) - - var gameState: GameState? = null - val log = DebugLogger("minesweeper") - - @Subscribe - fun onCommand(event: CommandEvent.SubCommand) { - event.subcommand("minesweepertutorial") { - thenExecute { - ScreenUtil.setScreenLater(MoulConfigUtils.loadScreen("carnival/minesweeper_tutorial", - TutorialScreen, - null)) - } - } - } - - @Subscribe - fun onWorldChange(event: WorldReadyEvent) { - gameState = null - } - - @Subscribe - fun onChat(event: ProcessChatEvent) { - if (CarnivalFeatures.TConfig.displayTutorials && event.unformattedString == startGameQuestion) { - MC.sendChat(Text.translatable("firmament.carnival.tutorial.minesweeper").styled { - it.withClickEvent(ClickEvent(ClickEvent.Action.RUN_COMMAND, "/firm minesweepertutorial")) - }) - } - if (!CarnivalFeatures.TConfig.enableBombSolver) { - gameState = null // TODO: replace this which a watchable property - return - } - if (event.unformattedString == gameStartMessage) { - gameState = GameState() - log.log { "Game started" } - } - if (event.unformattedString.trim() == gameEndMessage) { - gameState = null // TODO: add a loot tracker maybe? probably not, i dont think people care - log.log { "Finished game" } - } - val gs = gameState ?: return - bombPattern.useMatch(event.unformattedString) { - val bombCount = group("bombCount").toInt() - log.log { "Marking ${gs.lastClickedPosition} as having $bombCount nearby" } - val pos = gs.lastClickedPosition ?: return - gs.nearbyBombs[pos] = bombCount - } - } - - @Subscribe - fun onMobChange(event: EntityUpdateEvent) { - val gs = gameState ?: return - if (event !is EntityUpdateEvent.TrackedDataUpdate) return - // TODO: listen to state - } - - @Subscribe - fun onBlockClick(event: AttackBlockEvent) { - val gs = gameState ?: return - val boardPosition = BoardPosition.fromBlockPos(event.blockPos) - log.log { "Breaking block at ${event.blockPos} ($boardPosition)" } - gs.lastClickedPosition = boardPosition - gs.lastDowsingMode = DowsingMode.fromItem(event.player.inventory.mainHandStack) - } - - @Subscribe - fun onRender(event: WorldRenderLastEvent) { - val gs = gameState ?: return - RenderInWorldContext.renderInWorld(event) { - for ((pos, bombCount) in gs.nearbyBombs) { - this.text(pos.toBlockPos().up().toCenterPos(), Text.literal("§a$bombCount \uD83D\uDCA3")) - } - } - } - - -} diff --git a/src/main/kotlin/moe/nea/firmament/features/fixes/CompatibliltyFeatures.kt b/src/main/kotlin/moe/nea/firmament/features/fixes/CompatibliltyFeatures.kt deleted file mode 100644 index 7c43cf6..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/fixes/CompatibliltyFeatures.kt +++ /dev/null @@ -1,51 +0,0 @@ - - -package moe.nea.firmament.features.fixes - -import net.fabricmc.loader.api.FabricLoader -import net.superkat.explosiveenhancement.api.ExplosiveApi -import net.minecraft.particle.ParticleTypes -import net.minecraft.util.math.Vec3d -import moe.nea.firmament.annotations.Subscribe -import moe.nea.firmament.events.ParticleSpawnEvent -import moe.nea.firmament.features.FirmamentFeature -import moe.nea.firmament.gui.config.ManagedConfig -import moe.nea.firmament.util.MC - -object CompatibliltyFeatures : FirmamentFeature { - override val identifier: String - get() = "compatibility" - - object TConfig : ManagedConfig(identifier) { - val enhancedExplosions by toggle("explosion-enabled") { false } - val explosionSize by integer("explosion-power", 10, 50) { 1 } - } - - override val config: ManagedConfig? - get() = TConfig - - interface ExplosiveApiWrapper { - fun spawnParticle(vec3d: Vec3d, power: Float) - } - - class ExplosiveApiWrapperImpl : ExplosiveApiWrapper { - override fun spawnParticle(vec3d: Vec3d, power: Float) { - ExplosiveApi.spawnParticles(MC.world, vec3d.x, vec3d.y, vec3d.z, TConfig.explosionSize / 10F) - } - } - - val explosiveApiWrapper = if (FabricLoader.getInstance().isModLoaded("explosiveenhancement")) { - ExplosiveApiWrapperImpl() - } else null - - @Subscribe - fun onExplosion(it: ParticleSpawnEvent) { - if (TConfig.enhancedExplosions && - it.particleEffect.type == ParticleTypes.EXPLOSION_EMITTER && - explosiveApiWrapper != null - ) { - it.cancel() - explosiveApiWrapper.spawnParticle(it.position, 2F) - } - } -} diff --git a/src/main/kotlin/moe/nea/firmament/features/fixes/Fixes.kt b/src/main/kotlin/moe/nea/firmament/features/fixes/Fixes.kt deleted file mode 100644 index d7b7a1c..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/fixes/Fixes.kt +++ /dev/null @@ -1,71 +0,0 @@ - - -package moe.nea.firmament.features.fixes - -import moe.nea.jarvis.api.Point -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable -import net.minecraft.client.MinecraftClient -import net.minecraft.client.option.KeyBinding -import net.minecraft.entity.player.PlayerEntity -import net.minecraft.text.Text -import net.minecraft.util.Arm -import moe.nea.firmament.annotations.Subscribe -import moe.nea.firmament.events.HudRenderEvent -import moe.nea.firmament.events.WorldKeyboardEvent -import moe.nea.firmament.features.FirmamentFeature -import moe.nea.firmament.gui.config.ManagedConfig -import moe.nea.firmament.util.MC -import moe.nea.firmament.util.errorBoundary - -object Fixes : FirmamentFeature { - override val identifier: String - get() = "fixes" - - object TConfig : ManagedConfig(identifier) { - val fixUnsignedPlayerSkins by toggle("player-skins") { true } - var autoSprint by toggle("auto-sprint") { false } - val autoSprintKeyBinding by keyBindingWithDefaultUnbound("auto-sprint-keybinding") - val autoSprintHud by position("auto-sprint-hud", 80, 10) { Point(0.0, 1.0) } - val peekChat by keyBindingWithDefaultUnbound("peek-chat") - } - - override val config: ManagedConfig - get() = TConfig - - fun handleIsPressed( - keyBinding: KeyBinding, - cir: CallbackInfoReturnable - ) { - if (keyBinding === MinecraftClient.getInstance().options.sprintKey && TConfig.autoSprint && MC.player?.isSprinting != true) - cir.returnValue = true - } - - @Subscribe - fun onRenderHud(it: HudRenderEvent) { - if (!TConfig.autoSprintKeyBinding.isBound) return - it.context.matrices.push() - TConfig.autoSprintHud.applyTransformations(it.context.matrices) - it.context.drawText( - MC.font, Text.translatable( - if (TConfig.autoSprint) - "firmament.fixes.auto-sprint.on" - else if (MC.player?.isSprinting == true) - "firmament.fixes.auto-sprint.sprinting" - else - "firmament.fixes.auto-sprint.not-sprinting" - ), 0, 0, -1, false - ) - it.context.matrices.pop() - } - - @Subscribe - fun onWorldKeyboard(it: WorldKeyboardEvent) { - if (it.matches(TConfig.autoSprintKeyBinding)) { - TConfig.autoSprint = !TConfig.autoSprint - } - } - - fun shouldPeekChat(): Boolean { - return TConfig.peekChat.isPressed(atLeast = true) - } -} diff --git a/src/main/kotlin/moe/nea/firmament/features/inventory/CraftingOverlay.kt b/src/main/kotlin/moe/nea/firmament/features/inventory/CraftingOverlay.kt deleted file mode 100644 index 031ef78..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/inventory/CraftingOverlay.kt +++ /dev/null @@ -1,66 +0,0 @@ - - -package moe.nea.firmament.features.inventory - -import net.minecraft.client.gui.screen.ingame.GenericContainerScreen -import net.minecraft.item.ItemStack -import net.minecraft.util.Formatting -import moe.nea.firmament.annotations.Subscribe -import moe.nea.firmament.events.SlotRenderEvents -import moe.nea.firmament.features.FirmamentFeature -import moe.nea.firmament.rei.FirmamentReiPlugin.Companion.asItemEntry -import moe.nea.firmament.rei.SBItemEntryDefinition -import moe.nea.firmament.rei.recipes.SBCraftingRecipe -import moe.nea.firmament.util.MC - -object CraftingOverlay : FirmamentFeature { - - private var screen: GenericContainerScreen? = null - private var recipe: SBCraftingRecipe? = null - private val craftingOverlayIndices = listOf( - 10, 11, 12, - 19, 20, 21, - 28, 29, 30, - ) - - - fun setOverlay(screen: GenericContainerScreen, recipe: SBCraftingRecipe) { - this.screen = screen - this.recipe = recipe - } - - override val identifier: String - get() = "crafting-overlay" - - @Subscribe - fun onSlotRender(event: SlotRenderEvents.After) { - val slot = event.slot - val recipe = this.recipe ?: return - if (slot.inventory != screen?.screenHandler?.inventory) return - val recipeIndex = craftingOverlayIndices.indexOf(slot.index) - if (recipeIndex < 0) return - val expectedItem = recipe.neuRecipe.inputs[recipeIndex] - val actualStack = slot.stack ?: ItemStack.EMPTY!! - val actualEntry = SBItemEntryDefinition.getEntry(actualStack).value - if ((actualEntry.skyblockId.neuItem != expectedItem.itemId || actualEntry.getStackSize() < expectedItem.amount) && expectedItem.amount.toInt() != 0) { - event.context.fill( - event.slot.x, - event.slot.y, - event.slot.x + 16, - event.slot.y + 16, - 0x80FF0000.toInt() - ) - } - if (!slot.hasStack()) { - val itemStack = SBItemEntryDefinition.getEntry(expectedItem).asItemEntry().value - event.context.drawItem(itemStack, event.slot.x, event.slot.y) - event.context.drawItemInSlot( - MC.font, - itemStack, - event.slot.x, - event.slot.y, - "${Formatting.RED}${expectedItem.amount.toInt()}" - ) - } - } -} diff --git a/src/main/kotlin/moe/nea/firmament/features/inventory/ItemRarityCosmetics.kt b/src/main/kotlin/moe/nea/firmament/features/inventory/ItemRarityCosmetics.kt deleted file mode 100644 index 566a813..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/inventory/ItemRarityCosmetics.kt +++ /dev/null @@ -1,85 +0,0 @@ - - -package moe.nea.firmament.features.inventory - -import java.awt.Color -import net.minecraft.client.gui.DrawContext -import net.minecraft.item.ItemStack -import net.minecraft.util.Formatting -import net.minecraft.util.Identifier -import moe.nea.firmament.annotations.Subscribe -import moe.nea.firmament.events.HotbarItemRenderEvent -import moe.nea.firmament.events.SlotRenderEvents -import moe.nea.firmament.features.FirmamentFeature -import moe.nea.firmament.gui.config.ManagedConfig -import moe.nea.firmament.util.MC -import moe.nea.firmament.util.item.loreAccordingToNbt -import moe.nea.firmament.util.lastNotNullOfOrNull -import moe.nea.firmament.util.memoize -import moe.nea.firmament.util.memoizeIdentity -import moe.nea.firmament.util.unformattedString - -object ItemRarityCosmetics : FirmamentFeature { - override val identifier: String - get() = "item-rarity-cosmetics" - - object TConfig : ManagedConfig(identifier) { - val showItemRarityBackground by toggle("background") { false } - val showItemRarityInHotbar by toggle("background-hotbar") { false } - } - - override val config: ManagedConfig - get() = TConfig - - private val rarityToColor = mapOf( - "UNCOMMON" to Formatting.GREEN, - "COMMON" to Formatting.WHITE, - "RARE" to Formatting.DARK_BLUE, - "EPIC" to Formatting.DARK_PURPLE, - "LEGENDARY" to Formatting.GOLD, - "LEGENJERRY" to Formatting.GOLD, - "MYTHIC" to Formatting.LIGHT_PURPLE, - "DIVINE" to Formatting.BLUE, - "SPECIAL" to Formatting.DARK_RED, - "SUPREME" to Formatting.DARK_RED, - ).mapValues { - val c = Color(it.value.colorValue!!) - Triple(c.red / 255F, c.green / 255F, c.blue / 255F) - } - - private fun getSkyblockRarity0(itemStack: ItemStack): Triple? { - return itemStack.loreAccordingToNbt.lastNotNullOfOrNull { - val entry = it.unformattedString - rarityToColor.entries.find { (k, v) -> k in entry }?.value - } - } - - val getSkyblockRarity = ::getSkyblockRarity0.memoizeIdentity(100) - - - fun drawItemStackRarity(drawContext: DrawContext, x: Int, y: Int, item: ItemStack) { - val (r, g, b) = getSkyblockRarity(item) ?: return - drawContext.drawSprite( - x, y, - 0, - 16, 16, - MC.guiAtlasManager.getSprite(Identifier.of("firmament:item_rarity_background")), - r, g, b, 1F - ) - } - - - @Subscribe - fun onRenderSlot(it: SlotRenderEvents.Before) { - if (!TConfig.showItemRarityBackground) return - val stack = it.slot.stack ?: return - drawItemStackRarity(it.context, it.slot.x, it.slot.y, stack) - } - - @Subscribe - fun onRenderHotbarItem(it: HotbarItemRenderEvent) { - if (!TConfig.showItemRarityInHotbar) return - val stack = it.item - drawItemStackRarity(it.context, it.x, it.y, stack) - } -} diff --git a/src/main/kotlin/moe/nea/firmament/features/inventory/PriceData.kt b/src/main/kotlin/moe/nea/firmament/features/inventory/PriceData.kt deleted file mode 100644 index c61f8e8..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/inventory/PriceData.kt +++ /dev/null @@ -1,51 +0,0 @@ - - -package moe.nea.firmament.features.inventory - -import net.minecraft.text.Text -import moe.nea.firmament.annotations.Subscribe -import moe.nea.firmament.events.ItemTooltipEvent -import moe.nea.firmament.features.FirmamentFeature -import moe.nea.firmament.gui.config.ManagedConfig -import moe.nea.firmament.repo.HypixelStaticData -import moe.nea.firmament.util.FirmFormatters -import moe.nea.firmament.util.skyBlockId - -object PriceData : FirmamentFeature { - override val identifier: String - get() = "price-data" - - object TConfig : ManagedConfig(identifier) { - val tooltipEnabled by toggle("enable-always") { true } - val enableKeybinding by keyBindingWithDefaultUnbound("enable-keybind") - } - - override val config get() = TConfig - - @Subscribe - fun onItemTooltip(it: ItemTooltipEvent) { - if (!TConfig.tooltipEnabled && !TConfig.enableKeybinding.isPressed()) { - return - } - val sbId = it.stack.skyBlockId - val bazaarData = HypixelStaticData.bazaarData[sbId] - val lowestBin = HypixelStaticData.lowestBin[sbId] - if (bazaarData != null) { - it.lines.add(Text.literal("")) - it.lines.add( - Text.stringifiedTranslatable("firmament.tooltip.bazaar.sell-order", - FirmFormatters.formatCommas(bazaarData.quickStatus.sellPrice, 1)) - ) - it.lines.add( - Text.stringifiedTranslatable("firmament.tooltip.bazaar.buy-order", - FirmFormatters.formatCommas(bazaarData.quickStatus.buyPrice, 1)) - ) - } else if (lowestBin != null) { - it.lines.add(Text.literal("")) - it.lines.add( - Text.stringifiedTranslatable("firmament.tooltip.ah.lowestbin", - FirmFormatters.formatCommas(lowestBin, 1)) - ) - } - } -} diff --git a/src/main/kotlin/moe/nea/firmament/features/inventory/SaveCursorPosition.kt b/src/main/kotlin/moe/nea/firmament/features/inventory/SaveCursorPosition.kt deleted file mode 100644 index 1c55753..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/inventory/SaveCursorPosition.kt +++ /dev/null @@ -1,66 +0,0 @@ - - -package moe.nea.firmament.features.inventory - -import kotlin.math.absoluteValue -import kotlin.time.Duration.Companion.milliseconds -import net.minecraft.client.util.InputUtil -import moe.nea.firmament.features.FirmamentFeature -import moe.nea.firmament.gui.config.ManagedConfig -import moe.nea.firmament.util.MC -import moe.nea.firmament.util.TimeMark -import moe.nea.firmament.util.assertNotNullOr - -object SaveCursorPosition : FirmamentFeature { - override val identifier: String - get() = "save-cursor-position" - - object TConfig : ManagedConfig(identifier) { - val enable by toggle("enable") { true } - val tolerance by duration("tolerance", 10.milliseconds, 5000.milliseconds) { 500.milliseconds } - } - - override val config: TConfig - get() = TConfig - - var savedPositionedP1: Pair? = null - var savedPosition: SavedPosition? = null - - data class SavedPosition( - val middle: Pair, - val cursor: Pair, - val savedAt: TimeMark = TimeMark.now() - ) - - @JvmStatic - fun saveCursorOriginal(positionedX: Double, positionedY: Double) { - savedPositionedP1 = Pair(positionedX, positionedY) - } - - @JvmStatic - fun loadCursor(middleX: Double, middleY: Double): Pair? { - if (!TConfig.enable) return null - val lastPosition = savedPosition?.takeIf { it.savedAt.passedTime() < TConfig.tolerance } - savedPosition = null - if (lastPosition != null && - (lastPosition.middle.first - middleX).absoluteValue < 1 && - (lastPosition.middle.second - middleY).absoluteValue < 1 - ) { - InputUtil.setCursorParameters( - MC.window.handle, - InputUtil.GLFW_CURSOR_NORMAL, - lastPosition.cursor.first, - lastPosition.cursor.second - ) - return lastPosition.cursor - } - return null - } - - @JvmStatic - fun saveCursorMiddle(middleX: Double, middleY: Double) { - if (!TConfig.enable) return - val cursorPos = assertNotNullOr(savedPositionedP1) { return } - savedPosition = SavedPosition(Pair(middleX, middleY), cursorPos) - } -} diff --git a/src/main/kotlin/moe/nea/firmament/features/inventory/SlotLocking.kt b/src/main/kotlin/moe/nea/firmament/features/inventory/SlotLocking.kt deleted file mode 100644 index a50d8fb..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/inventory/SlotLocking.kt +++ /dev/null @@ -1,203 +0,0 @@ - - -@file:UseSerializers(DashlessUUIDSerializer::class) - -package moe.nea.firmament.features.inventory - -import com.mojang.blaze3d.systems.RenderSystem -import java.util.UUID -import org.lwjgl.glfw.GLFW -import kotlinx.serialization.Serializable -import kotlinx.serialization.UseSerializers -import kotlinx.serialization.serializer -import net.minecraft.client.gui.screen.ingame.HandledScreen -import net.minecraft.entity.player.PlayerInventory -import net.minecraft.screen.GenericContainerScreenHandler -import net.minecraft.screen.slot.SlotActionType -import net.minecraft.util.Identifier -import moe.nea.firmament.annotations.Subscribe -import moe.nea.firmament.events.HandledScreenKeyPressedEvent -import moe.nea.firmament.events.IsSlotProtectedEvent -import moe.nea.firmament.events.SlotRenderEvents -import moe.nea.firmament.features.FirmamentFeature -import moe.nea.firmament.gui.config.ManagedConfig -import moe.nea.firmament.keybindings.SavedKeyBinding -import moe.nea.firmament.mixins.accessor.AccessorHandledScreen -import moe.nea.firmament.util.CommonSoundEffects -import moe.nea.firmament.util.MC -import moe.nea.firmament.util.SBData -import moe.nea.firmament.util.SkyBlockIsland -import moe.nea.firmament.util.data.ProfileSpecificDataHolder -import moe.nea.firmament.util.item.displayNameAccordingToNbt -import moe.nea.firmament.util.item.loreAccordingToNbt -import moe.nea.firmament.util.json.DashlessUUIDSerializer -import moe.nea.firmament.util.skyblockUUID -import moe.nea.firmament.util.unformattedString - -object SlotLocking : FirmamentFeature { - override val identifier: String - get() = "slot-locking" - - @Serializable - data class Data( - val lockedSlots: MutableSet = mutableSetOf(), - val lockedSlotsRift: MutableSet = mutableSetOf(), - - val lockedUUIDs: MutableSet = mutableSetOf(), - ) - - object TConfig : ManagedConfig(identifier) { - val lockSlot by keyBinding("lock") { GLFW.GLFW_KEY_L } - val lockUUID by keyBindingWithOutDefaultModifiers("lock-uuid") { - SavedKeyBinding(GLFW.GLFW_KEY_L, shift = true) - } - } - - override val config: TConfig - get() = TConfig - - object DConfig : ProfileSpecificDataHolder(serializer(), "locked-slots", ::Data) - - val lockedUUIDs get() = DConfig.data?.lockedUUIDs - - val lockedSlots - get() = when (SBData.skyblockLocation) { - SkyBlockIsland.RIFT -> DConfig.data?.lockedSlotsRift - null -> null - else -> DConfig.data?.lockedSlots - } - - fun isSalvageScreen(screen: HandledScreen<*>?): Boolean { - if (screen == null) return false - return screen.title.unformattedString.contains("Salvage Item") - } - - fun isTradeScreen(screen: HandledScreen<*>?): Boolean { - if (screen == null) return false - val handler = screen.screenHandler as? GenericContainerScreenHandler ?: return false - if (handler.inventory.size() < 9) return false - val middlePane = handler.inventory.getStack(handler.inventory.size() - 5) - if (middlePane == null) return false - return middlePane.displayNameAccordingToNbt?.unformattedString == "⇦ Your stuff" - } - - fun isNpcShop(screen: HandledScreen<*>?): Boolean { - if (screen == null) return false - val handler = screen.screenHandler as? GenericContainerScreenHandler ?: return false - if (handler.inventory.size() < 9) return false - val sellItem = handler.inventory.getStack(handler.inventory.size() - 5) - if (sellItem == null) return false - if (sellItem.displayNameAccordingToNbt?.unformattedString == "Sell Item") return true - val lore = sellItem.loreAccordingToNbt - return (lore.lastOrNull() ?: return false).unformattedString == "Click to buyback!" - } - - @Subscribe - fun onSalvageProtect(event: IsSlotProtectedEvent) { - if (event.slot == null) return - if (!event.slot.hasStack()) return - if (event.slot.stack.displayNameAccordingToNbt?.unformattedString != "Salvage Items") return - val inv = event.slot.inventory - var anyBlocked = false - for (i in 0 until event.slot.index) { - val stack = inv.getStack(i) - if (IsSlotProtectedEvent.shouldBlockInteraction(null, SlotActionType.THROW, stack)) - anyBlocked = true - } - if (anyBlocked) { - event.protectSilent() - } - } - - @Subscribe - fun onProtectUuidItems(event: IsSlotProtectedEvent) { - val doesNotDeleteItem = event.actionType == SlotActionType.SWAP - || event.actionType == SlotActionType.PICKUP - || event.actionType == SlotActionType.QUICK_MOVE - || event.actionType == SlotActionType.QUICK_CRAFT - || event.actionType == SlotActionType.CLONE - || event.actionType == SlotActionType.PICKUP_ALL - val isSellOrTradeScreen = - isNpcShop(MC.handledScreen) || isTradeScreen(MC.handledScreen) || isSalvageScreen(MC.handledScreen) - if ((!isSellOrTradeScreen || event.slot?.inventory !is PlayerInventory) - && doesNotDeleteItem - ) return - val stack = event.itemStack ?: return - val uuid = stack.skyblockUUID ?: return - if (uuid in (lockedUUIDs ?: return)) { - event.protect() - } - } - - @Subscribe - fun onProtectSlot(it: IsSlotProtectedEvent) { - if (it.slot != null && it.slot.inventory is PlayerInventory && it.slot.index in (lockedSlots ?: setOf())) { - it.protect() - } - } - - @Subscribe - fun onLockUUID(it: HandledScreenKeyPressedEvent) { - if (!it.matches(TConfig.lockUUID)) return - val inventory = MC.handledScreen ?: return - inventory as AccessorHandledScreen - - val slot = inventory.focusedSlot_Firmament ?: return - val stack = slot.stack ?: return - val uuid = stack.skyblockUUID ?: return - val lockedUUIDs = lockedUUIDs ?: return - if (uuid in lockedUUIDs) { - lockedUUIDs.remove(uuid) - } else { - lockedUUIDs.add(uuid) - } - DConfig.markDirty() - CommonSoundEffects.playSuccess() - it.cancel() - } - - @Subscribe - fun onLockSlot(it: HandledScreenKeyPressedEvent) { - if (!it.matches(TConfig.lockSlot)) return - val inventory = MC.handledScreen ?: return - inventory as AccessorHandledScreen - - val slot = inventory.focusedSlot_Firmament ?: return - val lockedSlots = lockedSlots ?: return - if (slot.inventory is PlayerInventory) { - if (slot.index in lockedSlots) { - lockedSlots.remove(slot.index) - } else { - lockedSlots.add(slot.index) - } - DConfig.markDirty() - CommonSoundEffects.playSuccess() - } - } - - @Subscribe - fun onRenderSlotOverlay(it: SlotRenderEvents.After) { - val isSlotLocked = it.slot.inventory is PlayerInventory && it.slot.index in (lockedSlots ?: setOf()) - val isUUIDLocked = (it.slot.stack?.skyblockUUID) in (lockedUUIDs ?: setOf()) - if (isSlotLocked || isUUIDLocked) { - RenderSystem.disableDepthTest() - it.context.drawSprite( - it.slot.x, it.slot.y, 0, - 16, 16, - MC.guiAtlasManager.getSprite( - when { - isSlotLocked -> - (Identifier.of("firmament:slot_locked")) - - isUUIDLocked -> - (Identifier.of("firmament:uuid_locked")) - - else -> - error("unreachable") - } - ) - ) - RenderSystem.enableDepthTest() - } - } -} diff --git a/src/main/kotlin/moe/nea/firmament/features/inventory/buttons/InventoryButton.kt b/src/main/kotlin/moe/nea/firmament/features/inventory/buttons/InventoryButton.kt deleted file mode 100644 index 539edf2..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/inventory/buttons/InventoryButton.kt +++ /dev/null @@ -1,85 +0,0 @@ - - -package moe.nea.firmament.features.inventory.buttons - -import com.mojang.brigadier.StringReader -import me.shedaniel.math.Dimension -import me.shedaniel.math.Point -import me.shedaniel.math.Rectangle -import kotlinx.serialization.Serializable -import net.minecraft.client.gui.DrawContext -import net.minecraft.command.CommandRegistryAccess -import net.minecraft.command.argument.ItemStackArgumentType -import net.minecraft.item.ItemStack -import net.minecraft.resource.featuretoggle.FeatureFlags -import net.minecraft.util.Identifier -import moe.nea.firmament.repo.ItemCache.asItemStack -import moe.nea.firmament.repo.RepoManager -import moe.nea.firmament.util.MC -import moe.nea.firmament.util.SkyblockId -import moe.nea.firmament.util.memoize - -@Serializable -data class InventoryButton( - var x: Int, - var y: Int, - var anchorRight: Boolean, - var anchorBottom: Boolean, - var icon: String? = "", - var command: String? = "", -) { - companion object { - val itemStackParser by lazy { - ItemStackArgumentType.itemStack(CommandRegistryAccess.of(MC.defaultRegistries, - FeatureFlags.VANILLA_FEATURES)) - } - val dimensions = Dimension(18, 18) - val getItemForName = ::getItemForName0.memoize(1024) - fun getItemForName0(icon: String): ItemStack { - val repoItem = RepoManager.getNEUItem(SkyblockId(icon)) - var itemStack = repoItem.asItemStack(idHint = SkyblockId(icon)) - if (repoItem == null) { - val giveSyntaxItem = if (icon.startsWith("/give") || icon.startsWith("give")) - icon.split(" ", limit = 3).getOrNull(2) ?: icon - else icon - val componentItem = - runCatching { - itemStackParser.parse(StringReader(giveSyntaxItem)).createStack(1, false) - }.getOrNull() - if (componentItem != null) - itemStack = componentItem - } - return itemStack - } - } - - fun render(context: DrawContext) { - context.drawSprite( - 0, - 0, - 0, - dimensions.width, - dimensions.height, - MC.guiAtlasManager.getSprite(Identifier.of("firmament:inventory_button_background")) - ) - context.drawItem(getItem(), 1, 1) - } - - fun isValid() = !icon.isNullOrBlank() && !command.isNullOrBlank() - - fun getPosition(guiRect: Rectangle): Point { - return Point( - (if (anchorRight) guiRect.maxX else guiRect.minX) + x, - (if (anchorBottom) guiRect.maxY else guiRect.minY) + y, - ) - } - - fun getBounds(guiRect: Rectangle): Rectangle { - return Rectangle(getPosition(guiRect), dimensions) - } - - fun getItem(): ItemStack { - return getItemForName(icon ?: "") - } - -} diff --git a/src/main/kotlin/moe/nea/firmament/features/inventory/buttons/InventoryButtonEditor.kt b/src/main/kotlin/moe/nea/firmament/features/inventory/buttons/InventoryButtonEditor.kt deleted file mode 100644 index c57563e..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/inventory/buttons/InventoryButtonEditor.kt +++ /dev/null @@ -1,184 +0,0 @@ - - -package moe.nea.firmament.features.inventory.buttons - -import io.github.notenoughupdates.moulconfig.common.IItemStack -import io.github.notenoughupdates.moulconfig.platform.ModernItemStack -import io.github.notenoughupdates.moulconfig.xml.Bind -import me.shedaniel.math.Point -import me.shedaniel.math.Rectangle -import org.lwjgl.glfw.GLFW -import net.minecraft.client.gui.DrawContext -import net.minecraft.client.gui.widget.ButtonWidget -import net.minecraft.client.util.InputUtil -import net.minecraft.text.Text -import net.minecraft.util.math.MathHelper -import net.minecraft.util.math.Vec2f -import moe.nea.firmament.util.ClipboardUtils -import moe.nea.firmament.util.FragmentGuiScreen -import moe.nea.firmament.util.MC -import moe.nea.firmament.util.MoulConfigUtils - -class InventoryButtonEditor( - val lastGuiRect: Rectangle, -) : FragmentGuiScreen() { - inner class Editor(val originalButton: InventoryButton) { - @field:Bind - var command: String = originalButton.command ?: "" - - @field:Bind - var icon: String = originalButton.icon ?: "" - - @Bind - fun getItemIcon(): IItemStack { - save() - return ModernItemStack.of(InventoryButton.getItemForName(icon)) - } - - @Bind - fun delete() { - buttons.removeIf { it === originalButton } - popup = null - } - - fun save() { - originalButton.icon = icon - originalButton.command = command - } - } - - var buttons: MutableList = - InventoryButtons.DConfig.data.buttons.map { it.copy() }.toMutableList() - - override fun close() { - InventoryButtons.DConfig.data.buttons = buttons - InventoryButtons.DConfig.markDirty() - super.close() - } - - override fun init() { - super.init() - addDrawableChild( - ButtonWidget.builder(Text.translatable("firmament.inventory-buttons.load-preset")) { - val t = ClipboardUtils.getTextContents() - val newButtons = InventoryButtonTemplates.loadTemplate(t) - if (newButtons != null) - buttons = newButtons.toMutableList() - } - .position(lastGuiRect.minX + 10, lastGuiRect.minY + 35) - .width(lastGuiRect.width - 20) - .build() - ) - addDrawableChild( - ButtonWidget.builder(Text.translatable("firmament.inventory-buttons.save-preset")) { - ClipboardUtils.setTextContent(InventoryButtonTemplates.saveTemplate(buttons)) - } - .position(lastGuiRect.minX + 10, lastGuiRect.minY + 60) - .width(lastGuiRect.width - 20) - .build() - ) - } - - override fun render(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) { - super.render(context, mouseX, mouseY, delta) - context.matrices.push() - context.matrices.translate(0f, 0f, -10f) - context.fill(lastGuiRect.minX, lastGuiRect.minY, lastGuiRect.maxX, lastGuiRect.maxY, -1) - context.setShaderColor(1f, 1f, 1f, 1f) - context.matrices.pop() - for (button in buttons) { - val buttonPosition = button.getBounds(lastGuiRect) - context.matrices.push() - context.matrices.translate(buttonPosition.minX.toFloat(), buttonPosition.minY.toFloat(), 0F) - button.render(context) - context.matrices.pop() - } - } - - override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean { - if (super.keyPressed(keyCode, scanCode, modifiers)) return true - if (keyCode == GLFW.GLFW_KEY_ESCAPE) { - close() - return true - } - return false - } - - override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean { - if (super.mouseReleased(mouseX, mouseY, button)) return true - val clickedButton = buttons.firstOrNull { it.getBounds(lastGuiRect).contains(Point(mouseX, mouseY)) } - if (clickedButton != null && !justPerformedAClickAction) { - createPopup(MoulConfigUtils.loadGui("button_editor_fragment", Editor(clickedButton)), Point(mouseX, mouseY)) - return true - } - justPerformedAClickAction = false - lastDraggedButton = null - return false - } - - override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean { - if (super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)) return true - - if (initialDragMousePosition.distanceSquared(Vec2f(mouseX.toFloat(), mouseY.toFloat())) >= 4 * 4) { - initialDragMousePosition = Vec2f(-10F, -10F) - lastDraggedButton?.let { dragging -> - justPerformedAClickAction = true - val (anchorRight, anchorBottom, offsetX, offsetY) = getCoordsForMouse(mouseX.toInt(), mouseY.toInt()) - ?: return true - dragging.x = offsetX - dragging.y = offsetY - dragging.anchorRight = anchorRight - dragging.anchorBottom = anchorBottom - } - } - return false - } - - var lastDraggedButton: InventoryButton? = null - var justPerformedAClickAction = false - var initialDragMousePosition = Vec2f(-10F, -10F) - - data class AnchoredCoords( - val anchorRight: Boolean, - val anchorBottom: Boolean, - val offsetX: Int, - val offsetY: Int, - ) - - fun getCoordsForMouse(mx: Int, my: Int): AnchoredCoords? { - if (lastGuiRect.contains(mx, my) || lastGuiRect.contains( - Point( - mx + InventoryButton.dimensions.width, - my + InventoryButton.dimensions.height, - ) - ) - ) return null - - val anchorRight = mx > lastGuiRect.maxX - val anchorBottom = my > lastGuiRect.maxY - var offsetX = mx - if (anchorRight) lastGuiRect.maxX else lastGuiRect.minX - var offsetY = my - if (anchorBottom) lastGuiRect.maxY else lastGuiRect.minY - if (InputUtil.isKeyPressed(MC.window.handle, InputUtil.GLFW_KEY_LEFT_SHIFT)) { - offsetX = MathHelper.floor(offsetX / 20F) * 20 - offsetY = MathHelper.floor(offsetY / 20F) * 20 - } - return AnchoredCoords(anchorRight, anchorBottom, offsetX, offsetY) - } - - override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean { - if (super.mouseClicked(mouseX, mouseY, button)) return true - val clickedButton = buttons.firstOrNull { it.getBounds(lastGuiRect).contains(Point(mouseX, mouseY)) } - if (clickedButton != null) { - lastDraggedButton = clickedButton - initialDragMousePosition = Vec2f(mouseX.toFloat(), mouseY.toFloat()) - return true - } - val mx = mouseX.toInt() - val my = mouseY.toInt() - val (anchorRight, anchorBottom, offsetX, offsetY) = getCoordsForMouse(mx, my) ?: return true - buttons.add(InventoryButton(offsetX, offsetY, anchorRight, anchorBottom, null, null)) - justPerformedAClickAction = true - return true - } - -} diff --git a/src/main/kotlin/moe/nea/firmament/features/inventory/buttons/InventoryButtonTemplates.kt b/src/main/kotlin/moe/nea/firmament/features/inventory/buttons/InventoryButtonTemplates.kt deleted file mode 100644 index 99b544b..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/inventory/buttons/InventoryButtonTemplates.kt +++ /dev/null @@ -1,35 +0,0 @@ - - -package moe.nea.firmament.features.inventory.buttons - -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.encodeToString -import net.minecraft.text.Text -import moe.nea.firmament.Firmament -import moe.nea.firmament.util.MC -import moe.nea.firmament.util.TemplateUtil - -object InventoryButtonTemplates { - - val legacyPrefix = "NEUBUTTONS/" - val modernPrefix = "MAYBEONEDAYIWILLHAVEMYOWNFORMAT" - - fun loadTemplate(t: String): List? { - val buttons = TemplateUtil.maybeDecodeTemplate>(legacyPrefix, t) ?: return null - return buttons.mapNotNull { - try { - Firmament.json.decodeFromString(it).also { - if (it.icon?.startsWith("extra:") == true || it.command?.any { it.isLowerCase() } == true) { - MC.sendChat(Text.translatable("firmament.inventory-buttons.import-failed")) - } - } - } catch (e: Exception) { - null - } - } - } - - fun saveTemplate(buttons: List): String { - return TemplateUtil.encodeTemplate(legacyPrefix, buttons.map { Firmament.json.encodeToString(it) }) - } -} diff --git a/src/main/kotlin/moe/nea/firmament/features/inventory/buttons/InventoryButtons.kt b/src/main/kotlin/moe/nea/firmament/features/inventory/buttons/InventoryButtons.kt deleted file mode 100644 index fa90d21..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/inventory/buttons/InventoryButtons.kt +++ /dev/null @@ -1,88 +0,0 @@ - - -package moe.nea.firmament.features.inventory.buttons - -import me.shedaniel.math.Rectangle -import kotlinx.serialization.Serializable -import kotlinx.serialization.serializer -import moe.nea.firmament.annotations.Subscribe -import moe.nea.firmament.events.HandledScreenClickEvent -import moe.nea.firmament.events.HandledScreenForegroundEvent -import moe.nea.firmament.events.HandledScreenPushREIEvent -import moe.nea.firmament.features.FirmamentFeature -import moe.nea.firmament.gui.config.ManagedConfig -import moe.nea.firmament.util.MC -import moe.nea.firmament.util.ScreenUtil -import moe.nea.firmament.util.data.DataHolder -import moe.nea.firmament.util.getRectangle - -object InventoryButtons : FirmamentFeature { - override val identifier: String - get() = "inventory-buttons" - - object TConfig : ManagedConfig(identifier) { - val _openEditor by button("open-editor") { - openEditor() - } - } - - object DConfig : DataHolder(serializer(), identifier, ::Data) - - @Serializable - data class Data( - var buttons: MutableList = mutableListOf() - ) - - - override val config: ManagedConfig - get() = TConfig - - fun getValidButtons() = DConfig.data.buttons.asSequence().filter { it.isValid() } - - @Subscribe - fun onRectangles(it: HandledScreenPushREIEvent) { - val bounds = it.screen.getRectangle() - for (button in getValidButtons()) { - val buttonBounds = button.getBounds(bounds) - it.block(buttonBounds) - } - } - - @Subscribe - fun onClickScreen(it: HandledScreenClickEvent) { - val bounds = it.screen.getRectangle() - for (button in getValidButtons()) { - val buttonBounds = button.getBounds(bounds) - if (buttonBounds.contains(it.mouseX, it.mouseY)) { - MC.sendCommand(button.command!! /* non null invariant covered by getValidButtons */) - break - } - } - } - - @Subscribe - fun onRenderForeground(it: HandledScreenForegroundEvent) { - val bounds = it.screen.getRectangle() - for (button in getValidButtons()) { - val buttonBounds = button.getBounds(bounds) - it.context.matrices.push() - it.context.matrices.translate(buttonBounds.minX.toFloat(), buttonBounds.minY.toFloat(), 0F) - button.render(it.context) - it.context.matrices.pop() - } - lastRectangle = bounds - } - - var lastRectangle: Rectangle? = null - fun openEditor() { - ScreenUtil.setScreenLater( - InventoryButtonEditor( - lastRectangle ?: Rectangle( - MC.window.scaledWidth / 2 - 100, - MC.window.scaledHeight / 2 - 100, - 200, 200, - ) - ) - ) - } -} diff --git a/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageBackingHandle.kt b/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageBackingHandle.kt deleted file mode 100644 index 1015578..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageBackingHandle.kt +++ /dev/null @@ -1,53 +0,0 @@ - - -package moe.nea.firmament.features.inventory.storageoverlay - -import net.minecraft.client.gui.screen.Screen -import net.minecraft.client.gui.screen.ingame.GenericContainerScreen -import net.minecraft.screen.GenericContainerScreenHandler -import moe.nea.firmament.util.ifMatches -import moe.nea.firmament.util.unformattedString - -/** - * A handle representing the state of the "server side" screens. - */ -sealed interface StorageBackingHandle { - - sealed interface HasBackingScreen { - val handler: GenericContainerScreenHandler - } - - /** - * The main storage overview is open. Clicking on a slot will open that page. This page is accessible via `/storage` - */ - data class Overview(override val handler: GenericContainerScreenHandler) : StorageBackingHandle, HasBackingScreen - - /** - * An individual storage page is open. This may be a backpack or an enderchest page. This page is accessible via - * the [Overview] or via `/ec ` for enderchest pages. - */ - data class Page(override val handler: GenericContainerScreenHandler, val storagePageSlot: StoragePageSlot) : - StorageBackingHandle, HasBackingScreen - - companion object { - private val enderChestName = "^Ender Chest \\(([1-9])/[1-9]\\)$".toRegex() - private val backPackName = "^.+Backpack \\(Slot #([0-9]+)\\)$".toRegex() - - /** - * Parse a screen into a [StorageBackingHandle]. If this returns null it means that the screen is not - * representable as a [StorageBackingHandle], meaning another screen is open, for example the enderchest icon - * selection screen. - */ - fun fromScreen(screen: Screen?): StorageBackingHandle? { - if (screen == null) return null - if (screen !is GenericContainerScreen) return null - val title = screen.title.unformattedString - if (title == "Storage") return Overview(screen.screenHandler) - return title.ifMatches(enderChestName) { - Page(screen.screenHandler, StoragePageSlot.ofEnderChestPage(it.groupValues[1].toInt())) - } ?: title.ifMatches(backPackName) { - Page(screen.screenHandler, StoragePageSlot.ofBackPackPage(it.groupValues[1].toInt())) - } - } - } -} diff --git a/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageData.kt b/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageData.kt deleted file mode 100644 index 7555c56..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageData.kt +++ /dev/null @@ -1,21 +0,0 @@ - - -@file:UseSerializers(SortedMapSerializer::class) -package moe.nea.firmament.features.inventory.storageoverlay - -import java.util.SortedMap -import kotlinx.serialization.Serializable -import kotlinx.serialization.UseSerializers -import moe.nea.firmament.util.SortedMapSerializer - -@Serializable -data class StorageData( - val storageInventories: SortedMap = sortedMapOf() -) { - @Serializable - data class StorageInventory( - var title: String, - val slot: StoragePageSlot, - var inventory: VirtualInventory?, - ) -} diff --git a/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverlay.kt b/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverlay.kt deleted file mode 100644 index b615c73..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverlay.kt +++ /dev/null @@ -1,154 +0,0 @@ - - -package moe.nea.firmament.features.inventory.storageoverlay - -import java.util.SortedMap -import kotlinx.serialization.serializer -import net.minecraft.client.gui.screen.ingame.GenericContainerScreen -import net.minecraft.client.gui.screen.ingame.HandledScreen -import net.minecraft.entity.player.PlayerInventory -import net.minecraft.item.Items -import net.minecraft.network.packet.c2s.play.CloseHandledScreenC2SPacket -import moe.nea.firmament.annotations.Subscribe -import moe.nea.firmament.events.ScreenChangeEvent -import moe.nea.firmament.events.SlotClickEvent -import moe.nea.firmament.events.TickEvent -import moe.nea.firmament.features.FirmamentFeature -import moe.nea.firmament.gui.config.ManagedConfig -import moe.nea.firmament.util.MC -import moe.nea.firmament.util.customgui.customGui -import moe.nea.firmament.util.data.ProfileSpecificDataHolder - -object StorageOverlay : FirmamentFeature { - - - object Data : ProfileSpecificDataHolder(serializer(), "storage-data", ::StorageData) - - override val identifier: String - get() = "storage-overlay" - - object TConfig : ManagedConfig(identifier) { - val alwaysReplace by toggle("always-replace") { true } - val columns by integer("rows", 1, 10) { 3 } - val scrollSpeed by integer("scroll-speed", 1, 50) { 10 } - val inverseScroll by toggle("inverse-scroll") { false } - val padding by integer("padding", 1, 20) { 5 } - val margin by integer("margin", 1, 60) { 20 } - } - - fun adjustScrollSpeed(amount: Double): Double { - return amount * TConfig.scrollSpeed * (if (TConfig.inverseScroll) 1 else -1) - } - - override val config: TConfig - get() = TConfig - - var lastStorageOverlay: StorageOverviewScreen? = null - var skipNextStorageOverlayBackflip = false - var currentHandler: StorageBackingHandle? = null - - @Subscribe - fun onTick(event: TickEvent) { - rememberContent(currentHandler ?: return) - } - - @Subscribe - fun onClick(event: SlotClickEvent) { - if (lastStorageOverlay != null && event.slot.inventory !is PlayerInventory && event.slot.index < 9 - && event.stack.item != Items.BLACK_STAINED_GLASS_PANE - ) { - skipNextStorageOverlayBackflip = true - } - } - - @Subscribe - fun onScreenChange(it: ScreenChangeEvent) { - if (it.old == null && it.new == null) return - val storageOverlayScreen = it.old as? StorageOverlayScreen - ?: ((it.old as? HandledScreen<*>)?.customGui as? StorageOverlayCustom)?.overview - var storageOverviewScreen = it.old as? StorageOverviewScreen - val screen = it.new as? GenericContainerScreen - val oldHandler = currentHandler - currentHandler = StorageBackingHandle.fromScreen(screen) - rememberContent(currentHandler) - if (storageOverviewScreen != null && oldHandler is StorageBackingHandle.HasBackingScreen) { - val player = MC.player - assert(player != null) - player?.networkHandler?.sendPacket(CloseHandledScreenC2SPacket(oldHandler.handler.syncId)) - if (player?.currentScreenHandler === oldHandler.handler) { - player.currentScreenHandler = player.playerScreenHandler - } - } - storageOverviewScreen = storageOverviewScreen ?: lastStorageOverlay - if (it.new == null && storageOverlayScreen != null && !storageOverlayScreen.isExiting) { - it.overrideScreen = storageOverlayScreen - return - } - if (storageOverviewScreen != null - && !storageOverviewScreen.isClosing - && (currentHandler is StorageBackingHandle.Overview || currentHandler == null) - ) { - if (skipNextStorageOverlayBackflip) { - skipNextStorageOverlayBackflip = false - } else { - it.overrideScreen = storageOverviewScreen - lastStorageOverlay = null - } - return - } - screen ?: return - screen.customGui = StorageOverlayCustom( - currentHandler ?: return, - screen, - storageOverlayScreen ?: (if (TConfig.alwaysReplace) StorageOverlayScreen() else return)) - } - - fun rememberContent(handler: StorageBackingHandle?) { - handler ?: return - // TODO: Make all of these functions work on deltas / updates instead of the entire contents - val data = Data.data?.storageInventories ?: return - when (handler) { - is StorageBackingHandle.Overview -> rememberStorageOverview(handler, data) - is StorageBackingHandle.Page -> rememberPage(handler, data) - } - Data.markDirty() - } - - private fun rememberStorageOverview( - handler: StorageBackingHandle.Overview, - data: SortedMap - ) { - for ((index, stack) in handler.handler.stacks.withIndex()) { - // Ignore unloaded item stacks - if (stack.isEmpty) continue - val slot = StoragePageSlot.fromOverviewSlotIndex(index) ?: continue - val isEmpty = stack.item in StorageOverviewScreen.emptyStorageSlotItems - if (slot in data) { - if (isEmpty) - data.remove(slot) - continue - } - if (!isEmpty) { - data[slot] = StorageData.StorageInventory(slot.defaultName(), slot, null) - } - } - } - - private fun rememberPage( - handler: StorageBackingHandle.Page, - data: SortedMap - ) { - // TODO: FIXME: FIXME NOW: Definitely don't copy all of this every tick into persistence - val newStacks = - VirtualInventory(handler.handler.stacks.take(handler.handler.rows * 9).drop(9).map { it.copy() }) - data.compute(handler.storagePageSlot) { slot, existingInventory -> - (existingInventory ?: StorageData.StorageInventory( - slot.defaultName(), - slot, - null - )).also { - it.inventory = newStacks - } - } - } -} diff --git a/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverlayCustom.kt b/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverlayCustom.kt deleted file mode 100644 index d0d9114..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverlayCustom.kt +++ /dev/null @@ -1,98 +0,0 @@ - -package moe.nea.firmament.features.inventory.storageoverlay - -import me.shedaniel.math.Point -import me.shedaniel.math.Rectangle -import net.minecraft.client.MinecraftClient -import net.minecraft.client.gui.DrawContext -import net.minecraft.client.gui.screen.ingame.GenericContainerScreen -import net.minecraft.entity.player.PlayerInventory -import net.minecraft.screen.slot.Slot -import moe.nea.firmament.mixins.accessor.AccessorHandledScreen -import moe.nea.firmament.util.customgui.CustomGui - -class StorageOverlayCustom( - val handler: StorageBackingHandle, - val screen: GenericContainerScreen, - val overview: StorageOverlayScreen, -) : CustomGui() { - override fun onVoluntaryExit(): Boolean { - overview.isExiting = true - return super.onVoluntaryExit() - } - - override fun getBounds(): List { - return overview.getBounds() - } - - override fun afterSlotRender(context: DrawContext, slot: Slot) { - if (slot.inventory !is PlayerInventory) - context.disableScissor() - } - - override fun beforeSlotRender(context: DrawContext, slot: Slot) { - if (slot.inventory !is PlayerInventory) - overview.createScissors(context) - } - - override fun onInit() { - overview.init(MinecraftClient.getInstance(), screen.width, screen.height) - overview.init() - screen as AccessorHandledScreen - screen.x_Firmament = overview.measurements.x - screen.y_Firmament = overview.measurements.y - screen.backgroundWidth_Firmament = overview.measurements.totalWidth - screen.backgroundHeight_Firmament = overview.measurements.totalHeight - } - - override fun isPointOverSlot(slot: Slot, xOffset: Int, yOffset: Int, pointX: Double, pointY: Double): Boolean { - if (!super.isPointOverSlot(slot, xOffset, yOffset, pointX, pointY)) - return false - if (slot.inventory !is PlayerInventory) { - if (!overview.getScrollPanelInner().contains(pointX, pointY)) - return false - } - return true - } - - override fun shouldDrawForeground(): Boolean { - return false - } - - override fun mouseClick(mouseX: Double, mouseY: Double, button: Int): Boolean { - return overview.mouseClicked(mouseX, mouseY, button, (handler as? StorageBackingHandle.Page)?.storagePageSlot) - } - - override fun render(drawContext: DrawContext, delta: Float, mouseX: Int, mouseY: Int) { - overview.drawBackgrounds(drawContext) - overview.drawPages(drawContext, - mouseX, - mouseY, - delta, - (handler as? StorageBackingHandle.Page)?.storagePageSlot, - screen.screenHandler.slots.take(screen.screenHandler.rows * 9).drop(9), - Point((screen as AccessorHandledScreen).x_Firmament, screen.y_Firmament)) - overview.drawScrollBar(drawContext) - } - - override fun moveSlot(slot: Slot) { - val index = slot.index - if (index in 0..<36) { - val (x, y) = overview.getPlayerInventorySlotPosition(index) - slot.x = x - (screen as AccessorHandledScreen).x_Firmament - slot.y = y - screen.y_Firmament - } else { - slot.x = -100000 - slot.y = -100000 - } - } - - override fun mouseScrolled( - mouseX: Double, - mouseY: Double, - horizontalAmount: Double, - verticalAmount: Double - ): Boolean { - return overview.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount) - } -} diff --git a/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverlayScreen.kt b/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverlayScreen.kt deleted file mode 100644 index 13c6974..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverlayScreen.kt +++ /dev/null @@ -1,296 +0,0 @@ - -package moe.nea.firmament.features.inventory.storageoverlay - -import me.shedaniel.math.Point -import me.shedaniel.math.Rectangle -import net.minecraft.client.gui.DrawContext -import net.minecraft.client.gui.screen.Screen -import net.minecraft.screen.slot.Slot -import net.minecraft.text.Text -import net.minecraft.util.Identifier -import moe.nea.firmament.annotations.Subscribe -import moe.nea.firmament.events.CommandEvent -import moe.nea.firmament.util.MC -import moe.nea.firmament.util.ScreenUtil -import moe.nea.firmament.util.assertTrueOr - -class StorageOverlayScreen : Screen(Text.literal("")) { - - companion object { - val PLAYER_WIDTH = 184 - val PLAYER_HEIGHT = 91 - val PLAYER_Y_INSET = 3 - val SLOT_SIZE = 18 - val PADDING = 10 - val PAGE_WIDTH = SLOT_SIZE * 9 - val HOTBAR_X = 12 - val HOTBAR_Y = 67 - val MAIN_INVENTORY_Y = 9 - val SCROLL_BAR_WIDTH = 8 - val SCROLL_BAR_HEIGHT = 16 - } - - var isExiting: Boolean = false - var scroll: Float = 0F - var pageWidthCount = StorageOverlay.TConfig.columns - - inner class Measurements { - val innerScrollPanelWidth = PAGE_WIDTH * pageWidthCount + (pageWidthCount - 1) * PADDING - val overviewWidth = innerScrollPanelWidth + 3 * PADDING + SCROLL_BAR_WIDTH - val x = width / 2 - overviewWidth / 2 - val overviewHeight = minOf(3 * 18 * 6, height - PLAYER_HEIGHT - minOf(80, height / 10)) - val innerScrollPanelHeight = overviewHeight - PADDING * 2 - val y = height / 2 - (overviewHeight + PLAYER_HEIGHT) / 2 - val playerX = width / 2 - PLAYER_WIDTH / 2 - val playerY = y + overviewHeight - PLAYER_Y_INSET - val totalWidth = overviewWidth - val totalHeight = overviewHeight - PLAYER_Y_INSET + PLAYER_HEIGHT - } - - var measurements = Measurements() - - var lastRenderedInnerHeight = 0 - public override fun init() { - super.init() - pageWidthCount = StorageOverlay.TConfig.columns - .coerceAtMost((width - PADDING) / (PAGE_WIDTH + PADDING)) - .coerceAtLeast(1) - measurements = Measurements() - } - - override fun mouseScrolled( - mouseX: Double, - mouseY: Double, - horizontalAmount: Double, - verticalAmount: Double - ): Boolean { - scroll = (scroll + StorageOverlay.adjustScrollSpeed(verticalAmount)).toFloat() - .coerceAtMost(getMaxScroll()) - .coerceAtLeast(0F) - return true - } - - fun getMaxScroll() = lastRenderedInnerHeight.toFloat() - getScrollPanelInner().height - - val playerInventorySprite = Identifier.of("firmament:storageoverlay/player_inventory") - val upperBackgroundSprite = Identifier.of("firmament:storageoverlay/upper_background") - val slotRowSprite = Identifier.of("firmament:storageoverlay/storage_row") - val scrollbarBackground = Identifier.of("firmament:storageoverlay/scroll_bar_background") - val scrollbarKnob = Identifier.of("firmament:storageoverlay/scroll_bar_knob") - - override fun close() { - isExiting = true - super.close() - } - - override fun render(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) { - super.render(context, mouseX, mouseY, delta) - drawBackgrounds(context) - drawPages(context, mouseX, mouseY, delta, null, null, Point()) - drawScrollBar(context) - drawPlayerInventory(context, mouseX, mouseY, delta) - } - - fun getScrollbarPercentage(): Float { - return scroll / getMaxScroll() - } - - fun drawScrollBar(context: DrawContext) { - val sbRect = getScrollBarRect() - context.drawGuiTexture( - scrollbarBackground, - sbRect.minX, sbRect.minY, - sbRect.width, sbRect.height, - ) - context.drawGuiTexture( - scrollbarKnob, - sbRect.minX, sbRect.minY + (getScrollbarPercentage() * (sbRect.height - SCROLL_BAR_HEIGHT)).toInt(), - SCROLL_BAR_WIDTH, SCROLL_BAR_HEIGHT - ) - } - - fun drawBackgrounds(context: DrawContext) { - context.drawGuiTexture(upperBackgroundSprite, - measurements.x, - measurements.y, - 0, - measurements.overviewWidth, - measurements.overviewHeight) - context.drawGuiTexture(playerInventorySprite, - measurements.playerX, - measurements.playerY, - 0, - PLAYER_WIDTH, - PLAYER_HEIGHT) - } - - fun getPlayerInventorySlotPosition(int: Int): Pair { - if (int < 9) { - return Pair(measurements.playerX + int * SLOT_SIZE + HOTBAR_X, HOTBAR_Y + measurements.playerY) - } - return Pair( - measurements.playerX + (int % 9) * SLOT_SIZE + HOTBAR_X, - measurements.playerY + (int / 9 - 1) * SLOT_SIZE + MAIN_INVENTORY_Y - ) - } - - fun drawPlayerInventory(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) { - val items = MC.player?.inventory?.main ?: return - items.withIndex().forEach { (index, item) -> - val (x, y) = getPlayerInventorySlotPosition(index) - context.drawItem(item, x, y, 0) - context.drawItemInSlot(textRenderer, item, x, y) - } - } - - fun getScrollBarRect(): Rectangle { - return Rectangle(measurements.x + PADDING + measurements.innerScrollPanelWidth + PADDING, - measurements.y + PADDING, - SCROLL_BAR_WIDTH, - measurements.innerScrollPanelHeight) - } - - fun getScrollPanelInner(): Rectangle { - return Rectangle(measurements.x + PADDING, - measurements.y + PADDING, - measurements.innerScrollPanelWidth, - measurements.innerScrollPanelHeight) - } - - fun createScissors(context: DrawContext) { - val rect = getScrollPanelInner() - context.enableScissor( - rect.minX, rect.minY, - rect.maxX, rect.maxY - ) - } - - fun drawPages( - context: DrawContext, mouseX: Int, mouseY: Int, delta: Float, - excluding: StoragePageSlot?, - slots: List?, - slotOffset: Point - ) { - createScissors(context) - val data = StorageOverlay.Data.data ?: StorageData() - layoutedForEach(data) { rect, page, inventory -> - drawPage(context, - rect.x, - rect.y, - page, inventory, - if (excluding == page) slots else null, - slotOffset - ) - } - context.disableScissor() - } - - override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean { - return mouseClicked(mouseX, mouseY, button, null) - } - - fun mouseClicked(mouseX: Double, mouseY: Double, button: Int, activePage: StoragePageSlot?): Boolean { - if (getScrollPanelInner().contains(mouseX, mouseY)) { - val data = StorageOverlay.Data.data ?: StorageData() - layoutedForEach(data) { rect, page, _ -> - if (rect.contains(mouseX, mouseY) && activePage != page && button == 0) { - page.navigateTo() - return true - } - } - return false - } - val sbRect = getScrollBarRect() - if (sbRect.contains(mouseX, mouseY)) { - // TODO: support dragging of the mouse and such - val percentage = (mouseY - sbRect.getY()) / sbRect.getHeight() - scroll = (getMaxScroll() * percentage).toFloat() - mouseScrolled(0.0, 0.0, 0.0, 0.0) - return true - } - return false - } - - private inline fun layoutedForEach( - data: StorageData, - func: ( - rectangle: Rectangle, - page: StoragePageSlot, inventory: StorageData.StorageInventory, - ) -> Unit - ) { - var yOffset = -scroll.toInt() - var xOffset = 0 - var maxHeight = 0 - for ((page, inventory) in data.storageInventories.entries) { - val currentHeight = inventory.inventory?.let { it.rows * SLOT_SIZE + 4 + textRenderer.fontHeight } - ?: 18 - maxHeight = maxOf(maxHeight, currentHeight) - val rect = Rectangle( - measurements.x + PADDING + (PAGE_WIDTH + PADDING) * xOffset, - yOffset + measurements.y + PADDING, - PAGE_WIDTH, - currentHeight - ) - func(rect, page, inventory) - xOffset++ - if (xOffset >= pageWidthCount) { - yOffset += maxHeight - xOffset = 0 - maxHeight = 0 - } - } - lastRenderedInnerHeight = maxHeight + yOffset + scroll.toInt() - } - - fun drawPage( - context: DrawContext, - x: Int, - y: Int, - page: StoragePageSlot, - inventory: StorageData.StorageInventory, - slots: List?, - slotOffset: Point, - ): Int { - val inv = inventory.inventory - if (inv == null) { - context.drawGuiTexture(upperBackgroundSprite, x, y, PAGE_WIDTH, 18) - context.drawText(textRenderer, - Text.literal("TODO: open this page"), - x + 4, - y + 4, - -1, - true) - return 18 - } - assertTrueOr(slots == null || slots.size == inv.stacks.size) { return 0 } - val name = page.defaultName() - context.drawText(textRenderer, Text.literal(name), x + 4, y + 2, - if (slots == null) 0xFFFFFFFF.toInt() else 0xFFFFFF00.toInt(), true) - context.drawGuiTexture(slotRowSprite, x, y + 4 + textRenderer.fontHeight, PAGE_WIDTH, inv.rows * SLOT_SIZE) - inv.stacks.forEachIndexed { index, stack -> - val slotX = (index % 9) * SLOT_SIZE + x + 1 - val slotY = (index / 9) * SLOT_SIZE + y + 4 + textRenderer.fontHeight + 1 - if (slots == null) { - context.drawItem(stack, slotX, slotY) - context.drawItemInSlot(textRenderer, stack, slotX, slotY) - } else { - val slot = slots[index] - slot.x = slotX - slotOffset.x - slot.y = slotY - slotOffset.y - } - } - return inv.rows * SLOT_SIZE + 4 + textRenderer.fontHeight - } - - fun getBounds(): List { - return listOf( - Rectangle(measurements.x, - measurements.y, - measurements.overviewWidth, - measurements.overviewHeight), - Rectangle(measurements.playerX, - measurements.playerY, - PLAYER_WIDTH, - PLAYER_HEIGHT)) - } -} diff --git a/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverviewScreen.kt b/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverviewScreen.kt deleted file mode 100644 index 2cbd54e..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverviewScreen.kt +++ /dev/null @@ -1,123 +0,0 @@ - - -package moe.nea.firmament.features.inventory.storageoverlay - -import org.lwjgl.glfw.GLFW -import kotlin.math.max -import net.minecraft.block.Blocks -import net.minecraft.client.gui.DrawContext -import net.minecraft.client.gui.screen.Screen -import net.minecraft.item.Item -import net.minecraft.item.Items -import net.minecraft.text.Text -import net.minecraft.util.DyeColor -import moe.nea.firmament.util.MC -import moe.nea.firmament.util.toShedaniel - -class StorageOverviewScreen() : Screen(Text.empty()) { - companion object { - val emptyStorageSlotItems = listOf( - Blocks.RED_STAINED_GLASS_PANE.asItem(), - Blocks.BROWN_STAINED_GLASS_PANE.asItem(), - Items.GRAY_DYE - ) - val pageWidth get() = 19 * 9 - } - - val content = StorageOverlay.Data.data ?: StorageData() - var isClosing = false - - var scroll = 0 - var lastRenderedHeight = 0 - - override fun render(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) { - super.render(context, mouseX, mouseY, delta) - context.fill(0, 0, width, height, 0x90000000.toInt()) - layoutedForEach { (key, value), offsetX, offsetY -> - context.matrices.push() - context.matrices.translate(offsetX.toFloat(), offsetY.toFloat(), 0F) - renderStoragePage(context, value, mouseX - offsetX, mouseY - offsetY) - context.matrices.pop() - } - } - - inline fun layoutedForEach(onEach: (data: Pair, offsetX: Int, offsetY: Int) -> Unit) { - var offsetY = 0 - var currentMaxHeight = StorageOverlay.config.margin - StorageOverlay.config.padding - scroll - var totalHeight = -currentMaxHeight - content.storageInventories.onEachIndexed { index, (key, value) -> - val pageX = (index % StorageOverlay.config.columns) - if (pageX == 0) { - currentMaxHeight += StorageOverlay.config.padding - offsetY += currentMaxHeight - totalHeight += currentMaxHeight - currentMaxHeight = 0 - } - val xPosition = - width / 2 - (StorageOverlay.config.columns * (pageWidth + StorageOverlay.config.padding) - StorageOverlay.config.padding) / 2 + pageX * (pageWidth + StorageOverlay.config.padding) - onEach(Pair(key, value), xPosition, offsetY) - val height = getStorePageHeight(value) - currentMaxHeight = max(currentMaxHeight, height) - } - lastRenderedHeight = totalHeight + currentMaxHeight - } - - override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean { - layoutedForEach { (k, p), x, y -> - val rx = mouseX - x - val ry = mouseY - y - if (rx in (0.0..pageWidth.toDouble()) && ry in (0.0..getStorePageHeight(p).toDouble())) { - close() - StorageOverlay.lastStorageOverlay = this - k.navigateTo() - return true - } - } - return super.mouseClicked(mouseX, mouseY, button) - } - - fun getStorePageHeight(page: StorageData.StorageInventory): Int { - return page.inventory?.rows?.let { it * 19 + MC.font.fontHeight + 2 } ?: 60 - } - - override fun mouseScrolled( - mouseX: Double, - mouseY: Double, - horizontalAmount: Double, - verticalAmount: Double - ): Boolean { - scroll = - (scroll + StorageOverlay.adjustScrollSpeed(verticalAmount)).toInt() - .coerceAtMost(lastRenderedHeight - height + 2 * StorageOverlay.config.margin).coerceAtLeast(0) - return true - } - - private fun renderStoragePage(context: DrawContext, page: StorageData.StorageInventory, mouseX: Int, mouseY: Int) { - context.drawText(MC.font, page.title, 2, 2, -1, true) - val inventory = page.inventory - if (inventory == null) { - // TODO: Missing texture - context.fill(0, 0, pageWidth, 60, DyeColor.RED.toShedaniel().darker(4.0).color) - context.drawCenteredTextWithShadow(MC.font, Text.literal("Not loaded yet"), pageWidth / 2, 30, -1) - return - } - - for ((index, stack) in inventory.stacks.withIndex()) { - val x = (index % 9) * 19 - val y = (index / 9) * 19 + MC.font.fontHeight + 2 - if (((mouseX - x) in 0 until 18) && ((mouseY - y) in 0 until 18)) { - context.fill(x, y, x + 18, y + 18, 0x80808080.toInt()) - } else { - context.fill(x, y, x + 18, y + 18, 0x40808080.toInt()) - } - context.drawItem(stack, x + 1, y + 1) - context.drawItemInSlot(MC.font, stack, x + 1, y + 1) - } - } - - override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean { - if (keyCode == GLFW.GLFW_KEY_ESCAPE) - isClosing = true - return super.keyPressed(keyCode, scanCode, modifiers) - } -} diff --git a/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StoragePageSlot.kt b/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StoragePageSlot.kt deleted file mode 100644 index 9259415..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StoragePageSlot.kt +++ /dev/null @@ -1,66 +0,0 @@ - - -package moe.nea.firmament.features.inventory.storageoverlay - -import kotlinx.serialization.KSerializer -import kotlinx.serialization.Serializable -import kotlinx.serialization.descriptors.PrimitiveKind -import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder -import moe.nea.firmament.util.MC - -@Serializable(with = StoragePageSlot.Serializer::class) -data class StoragePageSlot(val index: Int) : Comparable { - object Serializer : KSerializer { - override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("StoragePageSlot", PrimitiveKind.INT) - - override fun deserialize(decoder: Decoder): StoragePageSlot { - return StoragePageSlot(decoder.decodeInt()) - } - - override fun serialize(encoder: Encoder, value: StoragePageSlot) { - encoder.encodeInt(value.index) - } - } - - init { - assert(index in 0 until (3 * 9)) - } - - val isEnderChest get() = index < 9 - val isBackPack get() = !isEnderChest - val slotIndexInOverviewPage get() = if (isEnderChest) index + 9 else index + 18 - fun defaultName(): String = if (isEnderChest) "Ender Chest #${index + 1}" else "Backpack #${index - 9 + 1}" - - fun navigateTo() { - if (isBackPack) { - MC.sendCommand("backpack ${index - 9 + 1}") - } else { - MC.sendCommand("enderchest ${index + 1}") - } - } - - companion object { - fun fromOverviewSlotIndex(slot: Int): StoragePageSlot? { - if (slot in 9 until 18) return StoragePageSlot(slot - 9) - if (slot in 27 until 45) return StoragePageSlot(slot - 27 + 9) - return null - } - - fun ofEnderChestPage(slot: Int): StoragePageSlot { - assert(slot in 1..9) - return StoragePageSlot(slot - 1) - } - - fun ofBackPackPage(slot: Int): StoragePageSlot { - assert(slot in 1..18) - return StoragePageSlot(slot - 1 + 9) - } - } - - override fun compareTo(other: StoragePageSlot): Int { - return this.index - other.index - } -} diff --git a/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/VirtualInventory.kt b/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/VirtualInventory.kt deleted file mode 100644 index e07df8a..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/VirtualInventory.kt +++ /dev/null @@ -1,65 +0,0 @@ - - -package moe.nea.firmament.features.inventory.storageoverlay - -import io.ktor.util.decodeBase64Bytes -import io.ktor.util.encodeBase64 -import java.io.ByteArrayInputStream -import java.io.ByteArrayOutputStream -import kotlinx.serialization.KSerializer -import kotlinx.serialization.Serializable -import kotlinx.serialization.descriptors.PrimitiveKind -import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder -import net.minecraft.item.ItemStack -import net.minecraft.nbt.NbtCompound -import net.minecraft.nbt.NbtIo -import net.minecraft.nbt.NbtList -import net.minecraft.nbt.NbtOps -import net.minecraft.nbt.NbtSizeTracker - -@Serializable(with = VirtualInventory.Serializer::class) -data class VirtualInventory( - val stacks: List -) { - val rows = stacks.size / 9 - - init { - assert(stacks.size % 9 == 0) - assert(stacks.size / 9 in 1..5) - } - - - object Serializer : KSerializer { - const val INVENTORY = "INVENTORY" - override val descriptor: SerialDescriptor - get() = PrimitiveSerialDescriptor("VirtualInventory", PrimitiveKind.STRING) - - override fun deserialize(decoder: Decoder): VirtualInventory { - val s = decoder.decodeString() - val n = NbtIo.readCompressed(ByteArrayInputStream(s.decodeBase64Bytes()), NbtSizeTracker.of(100_000_000)) - val items = n.getList(INVENTORY, NbtCompound.COMPOUND_TYPE.toInt()) - return VirtualInventory(items.map { - it as NbtCompound - if (it.isEmpty) ItemStack.EMPTY - else runCatching { - ItemStack.CODEC.parse(NbtOps.INSTANCE, it).orThrow - }.getOrElse { ItemStack.EMPTY } - }) - } - - override fun serialize(encoder: Encoder, value: VirtualInventory) { - val list = NbtList() - value.stacks.forEach { - if (it.isEmpty) list.add(NbtCompound()) - else list.add(runCatching { ItemStack.CODEC.encode(it, NbtOps.INSTANCE, NbtCompound()).orThrow } - .getOrElse { NbtCompound() }) - } - val baos = ByteArrayOutputStream() - NbtIo.writeCompressed(NbtCompound().also { it.put(INVENTORY, list) }, baos) - encoder.encodeString(baos.toByteArray().encodeBase64()) - } - } -} diff --git a/src/main/kotlin/moe/nea/firmament/features/mining/Histogram.kt b/src/main/kotlin/moe/nea/firmament/features/mining/Histogram.kt deleted file mode 100644 index ed48437..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/mining/Histogram.kt +++ /dev/null @@ -1,81 +0,0 @@ - -package moe.nea.firmament.features.mining - -import java.util.* -import kotlin.time.Duration -import moe.nea.firmament.util.TimeMark - -class Histogram( - val maxSize: Int, - val maxDuration: Duration, -) { - - data class OrderedTimestamp(val timestamp: TimeMark, val order: Int) : Comparable { - override fun compareTo(other: OrderedTimestamp): Int { - val o = timestamp.compareTo(other.timestamp) - if (o != 0) return o - return order.compareTo(other.order) - } - } - - val size: Int get() = dataPoints.size - private val dataPoints: NavigableMap = TreeMap() - - private var order = Int.MIN_VALUE - - fun record(entry: T, timestamp: TimeMark = TimeMark.now()) { - dataPoints[OrderedTimestamp(timestamp, order++)] = entry - trim() - } - - fun oldestUpdate(): TimeMark { - trim() - return if (dataPoints.isEmpty()) TimeMark.now() else dataPoints.firstKey().timestamp - } - - fun latestUpdate(): TimeMark { - trim() - return if (dataPoints.isEmpty()) TimeMark.farPast() else dataPoints.lastKey().timestamp - } - - fun averagePer(valueExtractor: (T) -> Double, perDuration: Duration): Double? { - return aggregate( - seed = 0.0, - operator = { accumulator, entry, _ -> accumulator + valueExtractor(entry) }, - finish = { sum, beginning, end -> - val timespan = end - beginning - if (timespan > perDuration) - sum / (timespan / perDuration) - else null - }) - } - - fun aggregate( - seed: V, - operator: (V, T, TimeMark) -> V, - finish: (V, TimeMark, TimeMark) -> R - ): R? { - trim() - var accumulator = seed - var min: TimeMark? = null - var max: TimeMark? = null - dataPoints.forEach { (key, value) -> - max = key.timestamp - if (min == null) - min = key.timestamp - accumulator = operator(accumulator, value, key.timestamp) - } - if (min == null) - return null - return finish(accumulator, min!!, max!!) - } - - private fun trim() { - while (maxSize < dataPoints.size) { - dataPoints.pollFirstEntry() - } - dataPoints.headMap(OrderedTimestamp(TimeMark.ago(maxDuration), Int.MAX_VALUE)).clear() - } - - -} diff --git a/src/main/kotlin/moe/nea/firmament/features/mining/PickaxeAbility.kt b/src/main/kotlin/moe/nea/firmament/features/mining/PickaxeAbility.kt deleted file mode 100644 index 7879f2d..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/mining/PickaxeAbility.kt +++ /dev/null @@ -1,176 +0,0 @@ - -package moe.nea.firmament.features.mining - -import java.util.regex.Pattern -import kotlin.time.Duration -import kotlin.time.Duration.Companion.seconds -import net.minecraft.item.ItemStack -import net.minecraft.util.DyeColor -import net.minecraft.util.Hand -import net.minecraft.util.Identifier -import moe.nea.firmament.annotations.Subscribe -import moe.nea.firmament.events.HudRenderEvent -import moe.nea.firmament.events.ProcessChatEvent -import moe.nea.firmament.events.SlotClickEvent -import moe.nea.firmament.events.WorldReadyEvent -import moe.nea.firmament.features.FirmamentFeature -import moe.nea.firmament.gui.config.ManagedConfig -import moe.nea.firmament.util.DurabilityBarEvent -import moe.nea.firmament.util.MC -import moe.nea.firmament.util.SHORT_NUMBER_FORMAT -import moe.nea.firmament.util.TIME_PATTERN -import moe.nea.firmament.util.TimeMark -import moe.nea.firmament.util.extraAttributes -import moe.nea.firmament.util.item.displayNameAccordingToNbt -import moe.nea.firmament.util.item.loreAccordingToNbt -import moe.nea.firmament.util.parseShortNumber -import moe.nea.firmament.util.parseTimePattern -import moe.nea.firmament.util.render.RenderCircleProgress -import moe.nea.firmament.util.render.lerp -import moe.nea.firmament.util.toShedaniel -import moe.nea.firmament.util.unformattedString -import moe.nea.firmament.util.useMatch - -object PickaxeAbility : FirmamentFeature { - override val identifier: String - get() = "pickaxe-info" - - - object TConfig : ManagedConfig(identifier) { - val cooldownEnabled by toggle("ability-cooldown") { true } - val cooldownScale by integer("ability-scale", 16, 64) { 16 } - val drillFuelBar by toggle("fuel-bar") { true } - } - - var lobbyJoinTime = TimeMark.farPast() - var lastUsage = mutableMapOf() - var abilityOverride: String? = null - var defaultAbilityDurations = mutableMapOf( - "Mining Speed Boost" to 120.seconds, - "Pickobulus" to 110.seconds, - "Gemstone Infusion" to 140.seconds, - "Hazardous Miner" to 140.seconds, - "Maniac Miner" to 59.seconds, - "Vein Seeker" to 60.seconds - ) - - override val config: ManagedConfig - get() = TConfig - - fun getCooldownPercentage(name: String, cooldown: Duration): Double { - val sinceLastUsage = lastUsage[name]?.passedTime() ?: Duration.INFINITE - if (sinceLastUsage < cooldown) - return sinceLastUsage / cooldown - val sinceLobbyJoin = lobbyJoinTime.passedTime() - val halfCooldown = cooldown / 2 - if (sinceLobbyJoin < halfCooldown) { - return (sinceLobbyJoin / halfCooldown) - } - return 1.0 - } - - @Subscribe - fun onSlotClick(it: SlotClickEvent) { - if (MC.screen?.title?.unformattedString == "Heart of the Mountain") { - val name = it.stack.displayNameAccordingToNbt?.unformattedString ?: return - val cooldown = it.stack.loreAccordingToNbt.firstNotNullOfOrNull { - cooldownPattern.useMatch(it.unformattedString) { - parseTimePattern(group("cooldown")) - } - } ?: return - defaultAbilityDurations[name] = cooldown - } - } - - @Subscribe - fun onDurabilityBar(it: DurabilityBarEvent) { - if (!TConfig.drillFuelBar) return - val lore = it.item.loreAccordingToNbt - if (lore.lastOrNull()?.unformattedString?.contains("DRILL") != true) return - val maxFuel = lore.firstNotNullOfOrNull { - fuelPattern.useMatch(it.unformattedString) { - parseShortNumber(group("maxFuel")) - } - } ?: return - val extra = it.item.extraAttributes - if (!extra.contains("drill_fuel")) return - val fuel = extra.getInt("drill_fuel") - val percentage = fuel / maxFuel.toFloat() - it.barOverride = DurabilityBarEvent.DurabilityBar( - lerp( - DyeColor.RED.toShedaniel(), - DyeColor.GREEN.toShedaniel(), - percentage - ), percentage - ) - } - - @Subscribe - fun onChatMessage(it: ProcessChatEvent) { - abilityUsePattern.useMatch(it.unformattedString) { - lastUsage[group("name")] = TimeMark.now() - } - abilitySwitchPattern.useMatch(it.unformattedString) { - abilityOverride = group("ability") - } - } - - @Subscribe - fun onWorldReady(event: WorldReadyEvent) { - lastUsage.clear() - lobbyJoinTime = TimeMark.now() - abilityOverride = null - } - - val abilityUsePattern = Pattern.compile("You used your (?.*) Pickaxe Ability!") - val fuelPattern = Pattern.compile("Fuel: .*/(?$SHORT_NUMBER_FORMAT)") - - data class PickaxeAbilityData( - val name: String, - val cooldown: Duration, - ) - - fun getCooldownFromLore(itemStack: ItemStack): PickaxeAbilityData? { - val lore = itemStack.loreAccordingToNbt - if (!lore.any { it.unformattedString.contains("Breaking Power") == true }) - return null - val cooldown = lore.firstNotNullOfOrNull { - cooldownPattern.useMatch(it.unformattedString) { - parseTimePattern(group("cooldown")) - } - } ?: return null - val name = lore.firstNotNullOfOrNull { - abilityPattern.useMatch(it.unformattedString) { - group("name") - } - } ?: return null - return PickaxeAbilityData(name, cooldown) - } - - - val cooldownPattern = Pattern.compile("Cooldown: (?$TIME_PATTERN)") - val abilityPattern = Pattern.compile("Ability: (?.*) {2}RIGHT CLICK") - val abilitySwitchPattern = - Pattern.compile("You selected (?.*) as your Pickaxe Ability\\. This ability will apply to all of your pickaxes!") - - - @Subscribe - fun renderHud(event: HudRenderEvent) { - if (!TConfig.cooldownEnabled) return - var ability = getCooldownFromLore(MC.player?.getStackInHand(Hand.MAIN_HAND) ?: return) ?: return - defaultAbilityDurations[ability.name] = ability.cooldown - val ao = abilityOverride - if (ao != ability.name && ao != null) { - ability = PickaxeAbilityData(ao, defaultAbilityDurations[ao] ?: 120.seconds) - } - event.context.matrices.push() - event.context.matrices.translate(MC.window.scaledWidth / 2F, MC.window.scaledHeight / 2F, 0F) - event.context.matrices.scale(TConfig.cooldownScale.toFloat(), TConfig.cooldownScale.toFloat(), 1F) - RenderCircleProgress.renderCircle( - event.context, Identifier.of("firmament", "textures/gui/circle.png"), - getCooldownPercentage(ability.name, ability.cooldown).toFloat(), - 0f, 1f, 0f, 1f - ) - event.context.matrices.pop() - } -} diff --git a/src/main/kotlin/moe/nea/firmament/features/mining/PristineProfitTracker.kt b/src/main/kotlin/moe/nea/firmament/features/mining/PristineProfitTracker.kt deleted file mode 100644 index f1bc7e5..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/mining/PristineProfitTracker.kt +++ /dev/null @@ -1,133 +0,0 @@ - -package moe.nea.firmament.features.mining - -import io.github.notenoughupdates.moulconfig.xml.Bind -import moe.nea.jarvis.api.Point -import kotlinx.serialization.Serializable -import kotlinx.serialization.serializer -import kotlin.time.Duration.Companion.seconds -import net.minecraft.text.Text -import moe.nea.firmament.annotations.Subscribe -import moe.nea.firmament.events.ProcessChatEvent -import moe.nea.firmament.features.FirmamentFeature -import moe.nea.firmament.gui.config.ManagedConfig -import moe.nea.firmament.gui.hud.MoulConfigHud -import moe.nea.firmament.util.BazaarPriceStrategy -import moe.nea.firmament.util.FirmFormatters.formatCommas -import moe.nea.firmament.util.SkyblockId -import moe.nea.firmament.util.data.ProfileSpecificDataHolder -import moe.nea.firmament.util.formattedString -import moe.nea.firmament.util.parseIntWithComma -import moe.nea.firmament.util.useMatch - -object PristineProfitTracker : FirmamentFeature { - override val identifier: String - get() = "pristine-profit" - - enum class GemstoneKind( - val label: String, - val flawedId: SkyblockId, - ) { - SAPPHIRE("Sapphire", SkyblockId("FLAWED_SAPPHIRE_GEM")), - RUBY("Ruby", SkyblockId("FLAWED_RUBY_GEM")), - AMETHYST("Amethyst", SkyblockId("FLAWED_AMETHYST_GEM")), - AMBER("Amber", SkyblockId("FLAWED_AMBER_GEM")), - TOPAZ("Topaz", SkyblockId("FLAWED_TOPAZ_GEM")), - JADE("Jade", SkyblockId("FLAWED_JADE_GEM")), - JASPER("Jasper", SkyblockId("FLAWED_JASPER_GEM")), - OPAL("Opal", SkyblockId("FLAWED_OPAL_GEM")), - } - - @Serializable - data class Data( - var maxMoneyPerSecond: Double = 1.0, - var maxCollectionPerSecond: Double = 1.0, - ) - - object DConfig : ProfileSpecificDataHolder(serializer(), identifier, ::Data) - - override val config: ManagedConfig? - get() = TConfig - - object TConfig : ManagedConfig(identifier) { - val timeout by duration("timeout", 0.seconds, 120.seconds) { 30.seconds } - val gui by position("position", 80, 30) { Point(0.05, 0.2) } - } - - val sellingStrategy = BazaarPriceStrategy.SELL_ORDER - - val pristineRegex = - "PRISTINE! You found . Flawed (?${ - GemstoneKind.entries.joinToString("|") { it.label } - }) Gemstone x(?[0-9,]+)!".toPattern() - - val collectionHistogram = Histogram(10000, 180.seconds) - val moneyHistogram = Histogram(10000, 180.seconds) - - object ProfitHud : MoulConfigHud("pristine_profit", TConfig.gui) { - @field:Bind - var moneyCurrent: Double = 0.0 - - @field:Bind - var moneyMax: Double = 1.0 - - @field:Bind - var moneyText = "" - - @field:Bind - var collectionCurrent = 0.0 - - @field:Bind - var collectionMax = 1.0 - - @field:Bind - var collectionText = "" - override fun shouldRender(): Boolean = collectionHistogram.latestUpdate().passedTime() < TConfig.timeout - } - - val SECONDS_PER_HOUR = 3600 - val ROUGHS_PER_FLAWED = 80 - - fun updateUi() { - val collectionPerSecond = collectionHistogram.averagePer({ it }, 1.seconds) - val moneyPerSecond = moneyHistogram.averagePer({ it }, 1.seconds) - if (collectionPerSecond == null || moneyPerSecond == null) return - ProfitHud.collectionCurrent = collectionPerSecond - ProfitHud.collectionText = Text.stringifiedTranslatable("firmament.pristine-profit.collection", - formatCommas(collectionPerSecond * SECONDS_PER_HOUR, - 1)).formattedString() - ProfitHud.moneyCurrent = moneyPerSecond - ProfitHud.moneyText = Text.stringifiedTranslatable("firmament.pristine-profit.money", - formatCommas(moneyPerSecond * SECONDS_PER_HOUR, 1)) - .formattedString() - val data = DConfig.data - if (data != null) { - if (data.maxCollectionPerSecond < collectionPerSecond && collectionHistogram.oldestUpdate() - .passedTime() > 30.seconds - ) { - data.maxCollectionPerSecond = collectionPerSecond - DConfig.markDirty() - } - if (data.maxMoneyPerSecond < moneyPerSecond && moneyHistogram.oldestUpdate().passedTime() > 30.seconds) { - data.maxMoneyPerSecond = moneyPerSecond - DConfig.markDirty() - } - ProfitHud.collectionMax = maxOf(data.maxCollectionPerSecond, collectionPerSecond) - ProfitHud.moneyMax = maxOf(data.maxMoneyPerSecond, moneyPerSecond) - } - } - - - @Subscribe - fun onMessage(it: ProcessChatEvent) { - pristineRegex.useMatch(it.unformattedString) { - val gemstoneKind = GemstoneKind.valueOf(group("kind").uppercase()) - val flawedCount = parseIntWithComma(group("count")) - val moneyAmount = sellingStrategy.getSellPrice(gemstoneKind.flawedId) * flawedCount - moneyHistogram.record(moneyAmount) - val collectionAmount = flawedCount * ROUGHS_PER_FLAWED - collectionHistogram.record(collectionAmount.toDouble()) - updateUi() - } - } -} diff --git a/src/main/kotlin/moe/nea/firmament/features/notifications/Notifications.kt b/src/main/kotlin/moe/nea/firmament/features/notifications/Notifications.kt deleted file mode 100644 index 8d912d1..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/notifications/Notifications.kt +++ /dev/null @@ -1,7 +0,0 @@ - -package moe.nea.firmament.features.notifications - -import moe.nea.firmament.features.FirmamentFeature - -object Notifications { -} diff --git a/src/main/kotlin/moe/nea/firmament/features/texturepack/AlwaysPredicate.kt b/src/main/kotlin/moe/nea/firmament/features/texturepack/AlwaysPredicate.kt deleted file mode 100644 index 4dd28df..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/texturepack/AlwaysPredicate.kt +++ /dev/null @@ -1,17 +0,0 @@ - -package moe.nea.firmament.features.texturepack - -import com.google.gson.JsonElement -import net.minecraft.item.ItemStack - -object AlwaysPredicate : FirmamentModelPredicate { - override fun test(stack: ItemStack): Boolean { - return true - } - - object Parser : FirmamentModelPredicateParser { - override fun parse(jsonElement: JsonElement): FirmamentModelPredicate { - return AlwaysPredicate - } - } -} diff --git a/src/main/kotlin/moe/nea/firmament/features/texturepack/AndPredicate.kt b/src/main/kotlin/moe/nea/firmament/features/texturepack/AndPredicate.kt deleted file mode 100644 index 55a4f32..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/texturepack/AndPredicate.kt +++ /dev/null @@ -1,26 +0,0 @@ - -package moe.nea.firmament.features.texturepack - -import com.google.gson.JsonArray -import com.google.gson.JsonElement -import com.google.gson.JsonObject -import net.minecraft.item.ItemStack - -class AndPredicate(val children: Array) : FirmamentModelPredicate { - override fun test(stack: ItemStack): Boolean { - return children.all { it.test(stack) } - } - - object Parser : FirmamentModelPredicateParser { - override fun parse(jsonElement: JsonElement): FirmamentModelPredicate { - val children = - (jsonElement as JsonArray) - .flatMap { - CustomModelOverrideParser.parsePredicates(it as JsonObject) - } - .toTypedArray() - return AndPredicate(children) - } - - } -} diff --git a/src/main/kotlin/moe/nea/firmament/features/texturepack/BakedModelExtra.kt b/src/main/kotlin/moe/nea/firmament/features/texturepack/BakedModelExtra.kt deleted file mode 100644 index ae1f6d5..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/texturepack/BakedModelExtra.kt +++ /dev/null @@ -1,9 +0,0 @@ - -package moe.nea.firmament.features.texturepack - -import net.minecraft.client.render.model.BakedModel - -interface BakedModelExtra { - fun getHeadModel_firmament(): BakedModel? - fun setHeadModel_firmament(headModel: BakedModel?) -} diff --git a/src/main/kotlin/moe/nea/firmament/features/texturepack/BakedOverrideData.kt b/src/main/kotlin/moe/nea/firmament/features/texturepack/BakedOverrideData.kt deleted file mode 100644 index c012883..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/texturepack/BakedOverrideData.kt +++ /dev/null @@ -1,8 +0,0 @@ - -package moe.nea.firmament.features.texturepack - -interface BakedOverrideData { - fun getFirmamentOverrides(): Array? - fun setFirmamentOverrides(overrides: Array?) - -} diff --git a/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomBlockTextures.kt b/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomBlockTextures.kt deleted file mode 100644 index 18da54c..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomBlockTextures.kt +++ /dev/null @@ -1,296 +0,0 @@ -@file:UseSerializers(BlockPosSerializer::class, IdentifierSerializer::class) - -package moe.nea.firmament.features.texturepack - -import java.util.concurrent.CompletableFuture -import net.fabricmc.loader.api.FabricLoader -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.KSerializer -import kotlinx.serialization.Serializable -import kotlinx.serialization.Transient -import kotlinx.serialization.UseSerializers -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder -import kotlinx.serialization.json.JsonDecoder -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.JsonPrimitive -import kotlinx.serialization.serializer -import kotlin.jvm.optionals.getOrNull -import net.minecraft.block.Block -import net.minecraft.block.BlockState -import net.minecraft.client.render.model.BakedModel -import net.minecraft.client.util.ModelIdentifier -import net.minecraft.registry.RegistryKey -import net.minecraft.registry.RegistryKeys -import net.minecraft.resource.ResourceManager -import net.minecraft.resource.SinglePreparationResourceReloader -import net.minecraft.util.Identifier -import net.minecraft.util.math.BlockPos -import net.minecraft.util.profiler.Profiler -import moe.nea.firmament.Firmament -import moe.nea.firmament.annotations.Subscribe -import moe.nea.firmament.compat.SodiumChunkReloader -import moe.nea.firmament.events.BakeExtraModelsEvent -import moe.nea.firmament.events.EarlyResourceReloadEvent -import moe.nea.firmament.events.FinalizeResourceManagerEvent -import moe.nea.firmament.events.SkyblockServerUpdateEvent -import moe.nea.firmament.features.texturepack.CustomGlobalTextures.logger -import moe.nea.firmament.util.IdentifierSerializer -import moe.nea.firmament.util.MC -import moe.nea.firmament.util.SBData -import moe.nea.firmament.util.SkyBlockIsland -import moe.nea.firmament.util.json.BlockPosSerializer -import moe.nea.firmament.util.json.SingletonSerializableList - - -object CustomBlockTextures { - @Serializable - data class CustomBlockOverride( - val modes: @Serializable(SingletonSerializableList::class) List, - val area: List? = null, - val replacements: Map, - ) - - @Serializable(with = Replacement.Serializer::class) - data class Replacement( - val block: Identifier, - val sound: Identifier?, - ) { - - @Transient - val blockModelIdentifier get() = ModelIdentifier(block.withPrefixedPath("block/"), "firmament") - - @Transient - val bakedModel: BakedModel by lazy(LazyThreadSafetyMode.NONE) { - MC.instance.bakedModelManager.getModel(blockModelIdentifier) - } - - @OptIn(ExperimentalSerializationApi::class) - @kotlinx.serialization.Serializer(Replacement::class) - object DefaultSerializer : KSerializer - - object Serializer : KSerializer { - val delegate = serializer() - override val descriptor: SerialDescriptor - get() = delegate.descriptor - - override fun deserialize(decoder: Decoder): Replacement { - val jsonElement = decoder.decodeSerializableValue(delegate) - if (jsonElement is JsonPrimitive) { - require(jsonElement.isString) - return Replacement(Identifier.tryParse(jsonElement.content)!!, null) - } - return (decoder as JsonDecoder).json.decodeFromJsonElement(DefaultSerializer, jsonElement) - } - - override fun serialize(encoder: Encoder, value: Replacement) { - encoder.encodeSerializableValue(DefaultSerializer, value) - } - } - } - - @Serializable - data class Area( - val min: BlockPos, - val max: BlockPos, - ) { - @Transient - val realMin = BlockPos( - minOf(min.x, max.x), - minOf(min.y, max.y), - minOf(min.z, max.z), - ) - - @Transient - val realMax = BlockPos( - maxOf(min.x, max.x), - maxOf(min.y, max.y), - maxOf(min.z, max.z), - ) - - fun roughJoin(other: Area): Area { - return Area( - BlockPos( - minOf(realMin.x, other.realMin.x), - minOf(realMin.y, other.realMin.y), - minOf(realMin.z, other.realMin.z), - ), - BlockPos( - maxOf(realMax.x, other.realMax.x), - maxOf(realMax.y, other.realMax.y), - maxOf(realMax.z, other.realMax.z), - ) - ) - } - - fun contains(blockPos: BlockPos): Boolean { - return (blockPos.x in realMin.x..realMax.x) && - (blockPos.y in realMin.y..realMax.y) && - (blockPos.z in realMin.z..realMax.z) - } - } - - data class LocationReplacements( - val lookup: Map> - ) - - data class BlockReplacement( - val checks: List?, - val replacement: Replacement, - ) { - val roughCheck by lazy(LazyThreadSafetyMode.NONE) { - if (checks == null || checks.size < 3) return@lazy null - checks.reduce { acc, next -> acc.roughJoin(next) } - } - } - - data class BakedReplacements(val data: Map) - - var allLocationReplacements: BakedReplacements = BakedReplacements(mapOf()) - var currentIslandReplacements: LocationReplacements? = null - - fun refreshReplacements() { - val location = SBData.skyblockLocation - val replacements = - if (CustomSkyBlockTextures.TConfig.enableBlockOverrides) location?.let(allLocationReplacements.data::get) - else null - val lastReplacements = currentIslandReplacements - currentIslandReplacements = replacements - if (lastReplacements != replacements) { - MC.nextTick { - MC.worldRenderer.chunks?.chunks?.forEach { - // false schedules rebuilds outside a 27 block radius to happen async - it.scheduleRebuild(false) - } - sodiumReloadTask?.run() - } - } - } - - private val sodiumReloadTask = runCatching { - SodiumChunkReloader() - }.getOrElse { - if (FabricLoader.getInstance().isModLoaded("sodium")) - logger.error("Could not create sodium chunk reloader") - null - } - - - fun matchesPosition(replacement: BlockReplacement, blockPos: BlockPos?): Boolean { - if (blockPos == null) return true - val rc = replacement.roughCheck - if (rc != null && !rc.contains(blockPos)) return false - val areas = replacement.checks - if (areas != null && !areas.any { it.contains(blockPos) }) return false - return true - } - - @JvmStatic - fun getReplacementModel(block: BlockState, blockPos: BlockPos?): BakedModel? { - return getReplacement(block, blockPos)?.bakedModel - } - - @JvmStatic - fun getReplacement(block: BlockState, blockPos: BlockPos?): Replacement? { - if (isInFallback() && blockPos == null) return null - val replacements = currentIslandReplacements?.lookup?.get(block.block) ?: return null - for (replacement in replacements) { - if (replacement.checks == null || matchesPosition(replacement, blockPos)) - return replacement.replacement - } - return null - } - - - @Subscribe - fun onLocation(event: SkyblockServerUpdateEvent) { - refreshReplacements() - } - - @Volatile - var preparationFuture: CompletableFuture = CompletableFuture.completedFuture(BakedReplacements( - mapOf())) - - val insideFallbackCall = ThreadLocal.withInitial { 0 } - - @JvmStatic - fun enterFallbackCall() { - insideFallbackCall.set(insideFallbackCall.get() + 1) - } - - fun isInFallback() = insideFallbackCall.get() > 0 - - @JvmStatic - fun exitFallbackCall() { - insideFallbackCall.set(insideFallbackCall.get() - 1) - } - - @Subscribe - fun onEarlyReload(event: EarlyResourceReloadEvent) { - preparationFuture = CompletableFuture - .supplyAsync( - { prepare(event.resourceManager) }, event.preparationExecutor) - } - - @Subscribe - fun bakeExtraModels(event: BakeExtraModelsEvent) { - preparationFuture.join().data.values - .flatMap { it.lookup.values } - .flatten() - .mapTo(mutableSetOf()) { it.replacement.blockModelIdentifier } - .forEach { event.addNonItemModel(it) } - } - - private fun prepare(manager: ResourceManager): BakedReplacements { - val resources = manager.findResources("overrides/blocks") { - it.namespace == "firmskyblock" && it.path.endsWith(".json") - } - val map = mutableMapOf>>() - for ((file, resource) in resources) { - val json = - Firmament.tryDecodeJsonFromStream(resource.inputStream) - .getOrElse { ex -> - logger.error("Failed to load block texture override at $file", ex) - continue - } - for (mode in json.modes) { - val island = SkyBlockIsland.forMode(mode) - val islandMpa = map.getOrPut(island, ::mutableMapOf) - for ((blockId, replacement) in json.replacements) { - val block = MC.defaultRegistries.getWrapperOrThrow(RegistryKeys.BLOCK) - .getOptional(RegistryKey.of(RegistryKeys.BLOCK, blockId)) - .getOrNull() - if (block == null) { - logger.error("Failed to load block texture override at ${file}: unknown block '$blockId'") - continue - } - val replacements = islandMpa.getOrPut(block.value(), ::mutableListOf) - replacements.add(BlockReplacement(json.area, replacement)) - } - } - } - - return BakedReplacements(map.mapValues { LocationReplacements(it.value) }) - } - - @JvmStatic - fun patchIndigo(orig: BakedModel, pos: BlockPos, state: BlockState): BakedModel { - return getReplacementModel(state, pos) ?: orig - } - - @Subscribe - fun onStart(event: FinalizeResourceManagerEvent) { - event.resourceManager.registerReloader(object : - SinglePreparationResourceReloader() { - override fun prepare(manager: ResourceManager, profiler: Profiler): BakedReplacements { - return preparationFuture.join() - } - - override fun apply(prepared: BakedReplacements, manager: ResourceManager, profiler: Profiler?) { - allLocationReplacements = prepared - refreshReplacements() - } - }) - } -} diff --git a/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomGlobalArmorOverrides.kt b/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomGlobalArmorOverrides.kt deleted file mode 100644 index 23577ee..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomGlobalArmorOverrides.kt +++ /dev/null @@ -1,106 +0,0 @@ - -@file:UseSerializers(IdentifierSerializer::class) - -package moe.nea.firmament.features.texturepack - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.Transient -import kotlinx.serialization.UseSerializers -import net.minecraft.item.ArmorMaterial -import net.minecraft.item.ItemStack -import net.minecraft.resource.ResourceManager -import net.minecraft.resource.SinglePreparationResourceReloader -import net.minecraft.util.Identifier -import net.minecraft.util.profiler.Profiler -import moe.nea.firmament.Firmament -import moe.nea.firmament.annotations.Subscribe -import moe.nea.firmament.events.FinalizeResourceManagerEvent -import moe.nea.firmament.events.subscription.SubscriptionOwner -import moe.nea.firmament.features.FirmamentFeature -import moe.nea.firmament.features.texturepack.CustomGlobalTextures.logger -import moe.nea.firmament.util.IdentifierSerializer -import moe.nea.firmament.util.IdentityCharacteristics -import moe.nea.firmament.util.computeNullableFunction -import moe.nea.firmament.util.skyBlockId - -object CustomGlobalArmorOverrides : SubscriptionOwner { - @Serializable - data class ArmorOverride( - @SerialName("item_ids") - val itemIds: List, - val layers: List, - val overrides: List = listOf(), - ) { - @Transient - val bakedLayers = bakeLayers(layers) - } - - fun bakeLayers(layers: List): List { - return layers.map { ArmorMaterial.Layer(it.identifier, it.suffix, it.tint) } - } - - @Serializable - data class ArmorOverrideLayer( - val tint: Boolean = false, - val identifier: Identifier, - val suffix: String = "", - ) - - @Serializable - data class ArmorOverrideOverride( - val predicate: FirmamentModelPredicate, - val layers: List, - ) { - @Transient - val bakedLayers = bakeLayers(layers) - } - - override val delegateFeature: FirmamentFeature - get() = CustomSkyBlockTextures - - val overrideCache = mutableMapOf, Any>() - - @JvmStatic - fun overrideArmor(stack: ItemStack): List? { - if (!CustomSkyBlockTextures.TConfig.enableArmorOverrides) return null - return overrideCache.computeNullableFunction(IdentityCharacteristics(stack)) { - val id = stack.skyBlockId ?: return@computeNullableFunction null - val override = overrides[id.neuItem] ?: return@computeNullableFunction null - for (suboverride in override.overrides) { - if (suboverride.predicate.test(stack)) { - return@computeNullableFunction suboverride.bakedLayers - } - } - return@computeNullableFunction override.bakedLayers - } - } - - var overrides: Map = mapOf() - - @Subscribe - fun onStart(event: FinalizeResourceManagerEvent) { - event.resourceManager.registerReloader(object : - SinglePreparationResourceReloader>() { - override fun prepare(manager: ResourceManager, profiler: Profiler): Map { - val overrideFiles = manager.findResources("overrides/armor_models") { - it.namespace == "firmskyblock" && it.path.endsWith(".json") - } - val overrides = overrideFiles.mapNotNull { - Firmament.tryDecodeJsonFromStream(it.value.inputStream).getOrElse { ex -> - logger.error("Failed to load armor texture override at ${it.key}", ex) - null - } - } - val associatedMap = overrides.flatMap { obj -> obj.itemIds.map { it to obj } } - .toMap() - return associatedMap - } - - override fun apply(prepared: Map, manager: ResourceManager, profiler: Profiler) { - overrides = prepared - } - }) - } - -} diff --git a/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomGlobalTextures.kt b/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomGlobalTextures.kt deleted file mode 100644 index d64c844..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomGlobalTextures.kt +++ /dev/null @@ -1,167 +0,0 @@ - -@file:UseSerializers(IdentifierSerializer::class, CustomModelOverrideParser.FirmamentRootPredicateSerializer::class) - -package moe.nea.firmament.features.texturepack - - -import java.util.concurrent.CompletableFuture -import org.slf4j.LoggerFactory -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable -import kotlinx.serialization.Serializable -import kotlinx.serialization.UseSerializers -import kotlin.jvm.optionals.getOrNull -import net.minecraft.client.render.item.ItemModels -import net.minecraft.client.render.model.BakedModel -import net.minecraft.client.util.ModelIdentifier -import net.minecraft.item.ItemStack -import net.minecraft.resource.ResourceManager -import net.minecraft.resource.SinglePreparationResourceReloader -import net.minecraft.text.Text -import net.minecraft.util.Identifier -import net.minecraft.util.profiler.Profiler -import moe.nea.firmament.Firmament -import moe.nea.firmament.annotations.Subscribe -import moe.nea.firmament.events.BakeExtraModelsEvent -import moe.nea.firmament.events.EarlyResourceReloadEvent -import moe.nea.firmament.events.FinalizeResourceManagerEvent -import moe.nea.firmament.events.ScreenChangeEvent -import moe.nea.firmament.events.subscription.SubscriptionOwner -import moe.nea.firmament.features.FirmamentFeature -import moe.nea.firmament.util.IdentifierSerializer -import moe.nea.firmament.util.IdentityCharacteristics -import moe.nea.firmament.util.MC -import moe.nea.firmament.util.computeNullableFunction -import moe.nea.firmament.util.json.SingletonSerializableList -import moe.nea.firmament.util.runNull - -object CustomGlobalTextures : SinglePreparationResourceReloader(), - SubscriptionOwner { - override val delegateFeature: FirmamentFeature - get() = CustomSkyBlockTextures - - class CustomGuiTextureOverride( - val classes: List - ) - - @Serializable - data class GlobalItemOverride( - val screen: @Serializable(SingletonSerializableList::class) List, - val model: Identifier, - val predicate: FirmamentModelPredicate, - ) - - @Serializable - data class ScreenFilter( - val title: StringMatcher, - ) - - data class ItemOverrideCollection( - val screenFilter: ScreenFilter, - val overrides: List, - ) - - @Subscribe - fun onStart(event: FinalizeResourceManagerEvent) { - MC.resourceManager.registerReloader(this) - } - - @Subscribe - fun onEarlyReload(event: EarlyResourceReloadEvent) { - preparationFuture = CompletableFuture - .supplyAsync( - { - prepare(event.resourceManager) - }, event.preparationExecutor) - } - - @Subscribe - fun onBakeModels(event: BakeExtraModelsEvent) { - for (guiClassOverride in preparationFuture.join().classes) { - for (override in guiClassOverride.overrides) { - event.addItemModel(ModelIdentifier(override.model, "inventory")) - } - } - } - - @Volatile - var preparationFuture: CompletableFuture = CompletableFuture.completedFuture( - CustomGuiTextureOverride(listOf())) - - override fun prepare(manager: ResourceManager?, profiler: Profiler?): CustomGuiTextureOverride { - return preparationFuture.join() - } - - override fun apply(prepared: CustomGuiTextureOverride, manager: ResourceManager?, profiler: Profiler?) { - this.guiClassOverrides = prepared - } - - val logger = LoggerFactory.getLogger(CustomGlobalTextures::class.java) - fun prepare(manager: ResourceManager): CustomGuiTextureOverride { - val overrideResources = - manager.findResources("overrides/item") { it.namespace == "firmskyblock" && it.path.endsWith(".json") } - .mapNotNull { - Firmament.tryDecodeJsonFromStream(it.value.inputStream).getOrElse { ex -> - logger.error("Failed to load global item override at ${it.key}", ex) - null - } - } - - val byGuiClass = overrideResources.flatMap { override -> override.screen.toSet().map { it to override } } - .groupBy { it.first } - val guiClasses = byGuiClass.entries - .mapNotNull { - val key = it.key - val guiClassResource = - manager.getResource(Identifier.of(key.namespace, "filters/screen/${key.path}.json")) - .getOrNull() - ?: return@mapNotNull runNull { - logger.error("Failed to locate screen filter at $key") - } - val screenFilter = - Firmament.tryDecodeJsonFromStream(guiClassResource.inputStream) - .getOrElse { ex -> - logger.error("Failed to load screen filter at $key", ex) - return@mapNotNull null - } - ItemOverrideCollection(screenFilter, it.value.map { it.second }) - } - logger.info("Loaded ${overrideResources.size} global item overrides") - return CustomGuiTextureOverride(guiClasses) - } - - var guiClassOverrides = CustomGuiTextureOverride(listOf()) - - var matchingOverrides: Set = setOf() - - @Subscribe - fun onOpenGui(event: ScreenChangeEvent) { - val newTitle = event.new?.title ?: Text.empty() - matchingOverrides = guiClassOverrides.classes - .filterTo(mutableSetOf()) { it.screenFilter.title.matches(newTitle) } - } - - val overrideCache = mutableMapOf, Any>() - - @JvmStatic - fun replaceGlobalModel( - models: ItemModels, - stack: ItemStack, - cir: CallbackInfoReturnable - ) { - val value = overrideCache.computeNullableFunction(IdentityCharacteristics(stack)) { - for (guiClassOverride in matchingOverrides) { - for (override in guiClassOverride.overrides) { - if (override.predicate.test(stack)) { - return@computeNullableFunction models.modelManager.getModel( - ModelIdentifier(override.model, "inventory")) - } - } - } - null - } - if (value != null) - cir.returnValue = value - } - - -} diff --git a/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomModelOverrideParser.kt b/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomModelOverrideParser.kt deleted file mode 100644 index a4e7c02..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomModelOverrideParser.kt +++ /dev/null @@ -1,74 +0,0 @@ - -package moe.nea.firmament.features.texturepack - -import com.google.gson.JsonObject -import kotlinx.serialization.KSerializer -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder -import net.minecraft.item.ItemStack -import net.minecraft.util.Identifier - -object CustomModelOverrideParser { - object FirmamentRootPredicateSerializer : KSerializer { - val delegateSerializer = kotlinx.serialization.json.JsonObject.serializer() - override val descriptor: SerialDescriptor - get() = SerialDescriptor("FirmamentModelRootPredicate", delegateSerializer.descriptor) - - override fun deserialize(decoder: Decoder): FirmamentModelPredicate { - val json = decoder.decodeSerializableValue(delegateSerializer).intoGson() as JsonObject - return AndPredicate(parsePredicates(json).toTypedArray()) - } - - override fun serialize(encoder: Encoder, value: FirmamentModelPredicate) { - TODO("Cannot serialize firmament predicates") - } - } - - val predicateParsers = mutableMapOf() - - - fun registerPredicateParser(name: String, parser: FirmamentModelPredicateParser) { - predicateParsers[Identifier.of("firmament", name)] = parser - } - - init { - registerPredicateParser("display_name", DisplayNamePredicate.Parser) - registerPredicateParser("lore", LorePredicate.Parser) - registerPredicateParser("all", AndPredicate.Parser) - registerPredicateParser("any", OrPredicate.Parser) - registerPredicateParser("not", NotPredicate.Parser) - registerPredicateParser("item", ItemPredicate.Parser) - registerPredicateParser("extra_attributes", ExtraAttributesPredicate.Parser) - registerPredicateParser("pet", PetPredicate.Parser) - } - - private val neverPredicate = listOf( - object : FirmamentModelPredicate { - override fun test(stack: ItemStack): Boolean { - return false - } - } - ) - - fun parsePredicates(predicates: JsonObject): List { - val parsedPredicates = mutableListOf() - for (predicateName in predicates.keySet()) { - if (!predicateName.startsWith("firmament:")) continue - val identifier = Identifier.of(predicateName) - val parser = predicateParsers[identifier] ?: return neverPredicate - val parsedPredicate = parser.parse(predicates[predicateName]) ?: return neverPredicate - parsedPredicates.add(parsedPredicate) - } - return parsedPredicates - } - - @JvmStatic - fun parseCustomModelOverrides(jsonObject: JsonObject): Array? { - val predicates = (jsonObject["predicate"] as? JsonObject) ?: return null - val parsedPredicates = parsePredicates(predicates) - if (parsedPredicates.isEmpty()) - return null - return parsedPredicates.toTypedArray() - } -} diff --git a/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomSkyBlockTextures.kt b/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomSkyBlockTextures.kt deleted file mode 100644 index dec6046..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomSkyBlockTextures.kt +++ /dev/null @@ -1,114 +0,0 @@ -package moe.nea.firmament.features.texturepack - -import com.mojang.authlib.minecraft.MinecraftProfileTexture -import com.mojang.authlib.properties.Property -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable -import net.minecraft.block.SkullBlock -import net.minecraft.client.MinecraftClient -import net.minecraft.client.render.RenderLayer -import net.minecraft.client.util.ModelIdentifier -import net.minecraft.component.type.ProfileComponent -import net.minecraft.util.Identifier -import moe.nea.firmament.annotations.Subscribe -import moe.nea.firmament.events.BakeExtraModelsEvent -import moe.nea.firmament.events.CustomItemModelEvent -import moe.nea.firmament.events.TickEvent -import moe.nea.firmament.features.FirmamentFeature -import moe.nea.firmament.gui.config.ManagedConfig -import moe.nea.firmament.util.IdentityCharacteristics -import moe.nea.firmament.util.item.decodeProfileTextureProperty -import moe.nea.firmament.util.skyBlockId - -object CustomSkyBlockTextures : FirmamentFeature { - override val identifier: String - get() = "custom-skyblock-textures" - - object TConfig : ManagedConfig(identifier) { - val enabled by toggle("enabled") { true } - val skullsEnabled by toggle("skulls-enabled") { true } - val cacheDuration by integer("cache-duration", 0, 20) { 1 } - val enableModelOverrides by toggle("model-overrides") { true } - val enableArmorOverrides by toggle("armor-overrides") { true } - val enableBlockOverrides by toggle("block-overrides") { true } - } - - override val config: ManagedConfig - get() = TConfig - - @Subscribe - fun onTick(it: TickEvent) { - if (TConfig.cacheDuration < 1 || it.tickCount % TConfig.cacheDuration == 0) { - // TODO: unify all of those caches somehow - CustomItemModelEvent.clearCache() - skullTextureCache.clear() - CustomGlobalTextures.overrideCache.clear() - CustomGlobalArmorOverrides.overrideCache.clear() - } - } - - @Subscribe - fun bakeCustomFirmModels(event: BakeExtraModelsEvent) { - val resources = - MinecraftClient.getInstance().resourceManager.findResources("models/item" - ) { it: Identifier -> - "firmskyblock" == it.namespace && it.path - .endsWith(".json") - } - for (identifier in resources.keys) { - val modelId = ModelIdentifier.ofInventoryVariant( - Identifier.of( - "firmskyblock", - identifier.path.substring( - "models/item/".length, - identifier.path.length - ".json".length), - )) - event.addItemModel(modelId) - } - } - - @Subscribe - fun onCustomModelId(it: CustomItemModelEvent) { - if (!TConfig.enabled) return - val id = it.itemStack.skyBlockId ?: return - it.overrideModel = ModelIdentifier.ofInventoryVariant(Identifier.of("firmskyblock", id.identifier.path)) - } - - private val skullTextureCache = mutableMapOf, Any>() - private val sentinelPresentInvalid = Object() - - private val mcUrlRegex = "https?://textures.minecraft.net/texture/([a-fA-F0-9]+)".toRegex() - - fun getSkullId(textureProperty: Property): String? { - val texture = decodeProfileTextureProperty(textureProperty) ?: return null - val textureUrl = - texture.textures[MinecraftProfileTexture.Type.SKIN]?.url ?: return null - val mcUrlData = mcUrlRegex.matchEntire(textureUrl) ?: return null - return mcUrlData.groupValues[1] - } - - fun getSkullTexture(profile: ProfileComponent): Identifier? { - val id = getSkullId(profile.properties["textures"].firstOrNull() ?: return null) ?: return null - return Identifier.of("firmskyblock", "textures/placedskull/$id.png") - } - - fun modifySkullTexture( - type: SkullBlock.SkullType?, - component: ProfileComponent?, - cir: CallbackInfoReturnable - ) { - if (type != SkullBlock.Type.PLAYER) return - if (!TConfig.skullsEnabled) return - if (component == null) return - val ic = IdentityCharacteristics(component) - - val n = skullTextureCache.getOrPut(ic) { - val id = getSkullTexture(component) ?: return@getOrPut sentinelPresentInvalid - if (!MinecraftClient.getInstance().resourceManager.getResource(id).isPresent) { - return@getOrPut sentinelPresentInvalid - } - return@getOrPut id - } - if (n === sentinelPresentInvalid) return - cir.returnValue = RenderLayer.getEntityTranslucent(n as Identifier) - } -} diff --git a/src/main/kotlin/moe/nea/firmament/features/texturepack/DisplayNamePredicate.kt b/src/main/kotlin/moe/nea/firmament/features/texturepack/DisplayNamePredicate.kt deleted file mode 100644 index c89931e..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/texturepack/DisplayNamePredicate.kt +++ /dev/null @@ -1,22 +0,0 @@ - -package moe.nea.firmament.features.texturepack - -import com.google.gson.JsonElement -import net.minecraft.item.ItemStack -import net.minecraft.nbt.NbtElement -import net.minecraft.nbt.NbtString -import moe.nea.firmament.util.item.displayNameAccordingToNbt -import moe.nea.firmament.util.item.loreAccordingToNbt - -data class DisplayNamePredicate(val stringMatcher: StringMatcher) : FirmamentModelPredicate { - override fun test(stack: ItemStack): Boolean { - val display = stack.displayNameAccordingToNbt - return stringMatcher.matches(display) - } - - object Parser : FirmamentModelPredicateParser { - override fun parse(jsonElement: JsonElement): FirmamentModelPredicate { - return DisplayNamePredicate(StringMatcher.parse(jsonElement)) - } - } -} diff --git a/src/main/kotlin/moe/nea/firmament/features/texturepack/ExtraAttributesPredicate.kt b/src/main/kotlin/moe/nea/firmament/features/texturepack/ExtraAttributesPredicate.kt deleted file mode 100644 index 4114f45..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/texturepack/ExtraAttributesPredicate.kt +++ /dev/null @@ -1,268 +0,0 @@ - -package moe.nea.firmament.features.texturepack - -import com.google.gson.JsonArray -import com.google.gson.JsonElement -import com.google.gson.JsonObject -import com.google.gson.JsonPrimitive -import net.minecraft.item.ItemStack -import net.minecraft.nbt.NbtByte -import net.minecraft.nbt.NbtCompound -import net.minecraft.nbt.NbtDouble -import net.minecraft.nbt.NbtElement -import net.minecraft.nbt.NbtFloat -import net.minecraft.nbt.NbtInt -import net.minecraft.nbt.NbtList -import net.minecraft.nbt.NbtLong -import net.minecraft.nbt.NbtShort -import net.minecraft.nbt.NbtString -import moe.nea.firmament.util.extraAttributes - -fun interface NbtMatcher { - fun matches(nbt: NbtElement): Boolean - - object Parser { - fun parse(jsonElement: JsonElement): NbtMatcher? { - if (jsonElement is JsonPrimitive) { - if (jsonElement.isString) { - val string = jsonElement.asString - return MatchStringExact(string) - } - if (jsonElement.isNumber) { - return MatchNumberExact(jsonElement.asLong) //TODO: parse generic number - } - } - if (jsonElement is JsonObject) { - var encounteredParser: NbtMatcher? = null - for (entry in ExclusiveParserType.entries) { - val data = jsonElement[entry.key] ?: continue - if (encounteredParser != null) { - // TODO: warn - return null - } - encounteredParser = entry.parse(data) ?: return null - } - return encounteredParser - } - return null - } - - enum class ExclusiveParserType(val key: String) { - STRING("string") { - override fun parse(element: JsonElement): NbtMatcher? { - return MatchString(StringMatcher.parse(element)) - } - }, - INT("int") { - override fun parse(element: JsonElement): NbtMatcher? { - return parseGenericNumber(element, - { it.asInt }, - { (it as? NbtInt)?.intValue() }, - { a, b -> - if (a == b) Comparison.EQUAL - else if (a < b) Comparison.LESS_THAN - else Comparison.GREATER - }) - } - }, - FLOAT("float") { - override fun parse(element: JsonElement): NbtMatcher? { - return parseGenericNumber(element, - { it.asFloat }, - { (it as? NbtFloat)?.floatValue() }, - { a, b -> - if (a == b) Comparison.EQUAL - else if (a < b) Comparison.LESS_THAN - else Comparison.GREATER - }) - } - }, - DOUBLE("double") { - override fun parse(element: JsonElement): NbtMatcher? { - return parseGenericNumber(element, - { it.asDouble }, - { (it as? NbtDouble)?.doubleValue() }, - { a, b -> - if (a == b) Comparison.EQUAL - else if (a < b) Comparison.LESS_THAN - else Comparison.GREATER - }) - } - }, - LONG("long") { - override fun parse(element: JsonElement): NbtMatcher? { - return parseGenericNumber(element, - { it.asLong }, - { (it as? NbtLong)?.longValue() }, - { a, b -> - if (a == b) Comparison.EQUAL - else if (a < b) Comparison.LESS_THAN - else Comparison.GREATER - }) - } - }, - SHORT("short") { - override fun parse(element: JsonElement): NbtMatcher? { - return parseGenericNumber(element, - { it.asShort }, - { (it as? NbtShort)?.shortValue() }, - { a, b -> - if (a == b) Comparison.EQUAL - else if (a < b) Comparison.LESS_THAN - else Comparison.GREATER - }) - } - }, - BYTE("byte") { - override fun parse(element: JsonElement): NbtMatcher? { - return parseGenericNumber(element, - { it.asByte }, - { (it as? NbtByte)?.byteValue() }, - { a, b -> - if (a == b) Comparison.EQUAL - else if (a < b) Comparison.LESS_THAN - else Comparison.GREATER - }) - } - }, - ; - - abstract fun parse(element: JsonElement): NbtMatcher? - } - - enum class Comparison { - LESS_THAN, EQUAL, GREATER - } - - inline fun parseGenericNumber( - jsonElement: JsonElement, - primitiveExtractor: (JsonPrimitive) -> T?, - crossinline nbtExtractor: (NbtElement) -> T?, - crossinline compare: (T, T) -> Comparison - ): NbtMatcher? { - if (jsonElement is JsonPrimitive) { - val expected = primitiveExtractor(jsonElement) ?: return null - return NbtMatcher { - val actual = nbtExtractor(it) ?: return@NbtMatcher false - compare(actual, expected) == Comparison.EQUAL - } - } - if (jsonElement is JsonObject) { - val minElement = jsonElement.getAsJsonPrimitive("min") - val min = if (minElement != null) primitiveExtractor(minElement) ?: return null else null - val minExclusive = jsonElement.get("minExclusive")?.asBoolean ?: false - val maxElement = jsonElement.getAsJsonPrimitive("max") - val max = if (maxElement != null) primitiveExtractor(maxElement) ?: return null else null - val maxExclusive = jsonElement.get("maxExclusive")?.asBoolean ?: true - if (min == null && max == null) return null - return NbtMatcher { - val actual = nbtExtractor(it) ?: return@NbtMatcher false - if (max != null) { - val comp = compare(actual, max) - if (comp == Comparison.GREATER) return@NbtMatcher false - if (comp == Comparison.EQUAL && maxExclusive) return@NbtMatcher false - } - if (min != null) { - val comp = compare(actual, min) - if (comp == Comparison.LESS_THAN) return@NbtMatcher false - if (comp == Comparison.EQUAL && minExclusive) return@NbtMatcher false - } - return@NbtMatcher true - } - } - return null - - } - } - - class MatchNumberExact(val number: Long) : NbtMatcher { - override fun matches(nbt: NbtElement): Boolean { - return when (nbt) { - is NbtByte -> nbt.byteValue().toLong() == number - is NbtInt -> nbt.intValue().toLong() == number - is NbtShort -> nbt.shortValue().toLong() == number - is NbtLong -> nbt.longValue().toLong() == number - else -> false - } - } - - } - - class MatchStringExact(val string: String) : NbtMatcher { - override fun matches(nbt: NbtElement): Boolean { - return nbt is NbtString && nbt.asString() == string - } - - override fun toString(): String { - return "MatchNbtStringExactly($string)" - } - } - - class MatchString(val string: StringMatcher) : NbtMatcher { - override fun matches(nbt: NbtElement): Boolean { - return nbt is NbtString && string.matches(nbt.asString()) - } - - override fun toString(): String { - return "MatchNbtString($string)" - } - } -} - -data class ExtraAttributesPredicate( - val path: NbtPrism, - val matcher: NbtMatcher, -) : FirmamentModelPredicate { - - object Parser : FirmamentModelPredicateParser { - override fun parse(jsonElement: JsonElement): FirmamentModelPredicate? { - if (jsonElement !is JsonObject) return null - val path = jsonElement.get("path") ?: return null - val pathSegments = if (path is JsonArray) { - path.map { (it as JsonPrimitive).asString } - } else if (path is JsonPrimitive && path.isString) { - path.asString.split(".") - } else return null - val matcher = NbtMatcher.Parser.parse(jsonElement.get("match") ?: jsonElement) - ?: return null - return ExtraAttributesPredicate(NbtPrism(pathSegments), matcher) - } - } - - override fun test(stack: ItemStack): Boolean { - return path.access(stack.extraAttributes) - .any { matcher.matches(it) } - } -} - -class NbtPrism(val path: List) { - override fun toString(): String { - return "Prism($path)" - } - fun access(root: NbtElement): Collection { - var rootSet = mutableListOf(root) - var switch = mutableListOf() - for (pathSegment in path) { - if (pathSegment == ".") continue - for (element in rootSet) { - if (element is NbtList) { - if (pathSegment == "*") - switch.addAll(element) - val index = pathSegment.toIntOrNull() ?: continue - if (index !in element.indices) continue - switch.add(element[index]) - } - if (element is NbtCompound) { - if (pathSegment == "*") - element.keys.mapTo(switch) { element.get(it)!! } - switch.add(element.get(pathSegment) ?: continue) - } - } - val temp = switch - switch = rootSet - rootSet = temp - switch.clear() - } - return rootSet - } -} diff --git a/src/main/kotlin/moe/nea/firmament/features/texturepack/FirmamentModelPredicate.kt b/src/main/kotlin/moe/nea/firmament/features/texturepack/FirmamentModelPredicate.kt deleted file mode 100644 index d11fec0..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/texturepack/FirmamentModelPredicate.kt +++ /dev/null @@ -1,8 +0,0 @@ - -package moe.nea.firmament.features.texturepack - -import net.minecraft.item.ItemStack - -interface FirmamentModelPredicate { - fun test(stack: ItemStack): Boolean -} diff --git a/src/main/kotlin/moe/nea/firmament/features/texturepack/FirmamentModelPredicateParser.kt b/src/main/kotlin/moe/nea/firmament/features/texturepack/FirmamentModelPredicateParser.kt deleted file mode 100644 index 3ed0c67..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/texturepack/FirmamentModelPredicateParser.kt +++ /dev/null @@ -1,8 +0,0 @@ - -package moe.nea.firmament.features.texturepack - -import com.google.gson.JsonElement - -interface FirmamentModelPredicateParser { - fun parse(jsonElement: JsonElement): FirmamentModelPredicate? -} diff --git a/src/main/kotlin/moe/nea/firmament/features/texturepack/ItemPredicate.kt b/src/main/kotlin/moe/nea/firmament/features/texturepack/ItemPredicate.kt deleted file mode 100644 index 4302b53..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/texturepack/ItemPredicate.kt +++ /dev/null @@ -1,32 +0,0 @@ - -package moe.nea.firmament.features.texturepack - -import com.google.gson.JsonElement -import com.google.gson.JsonPrimitive -import kotlin.jvm.optionals.getOrNull -import net.minecraft.item.Item -import net.minecraft.item.ItemStack -import net.minecraft.registry.RegistryKey -import net.minecraft.registry.RegistryKeys -import net.minecraft.util.Identifier -import moe.nea.firmament.util.MC - -class ItemPredicate( - val item: Item -) : FirmamentModelPredicate { - override fun test(stack: ItemStack): Boolean { - return stack.item == item - } - - object Parser : FirmamentModelPredicateParser { - override fun parse(jsonElement: JsonElement): ItemPredicate? { - if (jsonElement is JsonPrimitive && jsonElement.isString) { - val itemKey = RegistryKey.of(RegistryKeys.ITEM, - Identifier.tryParse(jsonElement.asString) - ?: return null) - return ItemPredicate(MC.defaultItems.getOptional(itemKey).getOrNull()?.value() ?: return null) - } - return null - } - } -} diff --git a/src/main/kotlin/moe/nea/firmament/features/texturepack/JsonUnbakedModelFirmExtra.kt b/src/main/kotlin/moe/nea/firmament/features/texturepack/JsonUnbakedModelFirmExtra.kt deleted file mode 100644 index ab9e27d..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/texturepack/JsonUnbakedModelFirmExtra.kt +++ /dev/null @@ -1,10 +0,0 @@ - -package moe.nea.firmament.features.texturepack - -import net.minecraft.util.Identifier - -interface JsonUnbakedModelFirmExtra { - - fun setHeadModel_firmament(identifier: Identifier?) - fun getHeadModel_firmament(): Identifier? -} diff --git a/src/main/kotlin/moe/nea/firmament/features/texturepack/LorePredicate.kt b/src/main/kotlin/moe/nea/firmament/features/texturepack/LorePredicate.kt deleted file mode 100644 index 13e3974..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/texturepack/LorePredicate.kt +++ /dev/null @@ -1,19 +0,0 @@ - -package moe.nea.firmament.features.texturepack - -import com.google.gson.JsonElement -import net.minecraft.item.ItemStack -import moe.nea.firmament.util.item.loreAccordingToNbt - -class LorePredicate(val matcher: StringMatcher) : FirmamentModelPredicate { - object Parser : FirmamentModelPredicateParser { - override fun parse(jsonElement: JsonElement): FirmamentModelPredicate { - return LorePredicate(StringMatcher.parse(jsonElement)) - } - } - - override fun test(stack: ItemStack): Boolean { - val lore = stack.loreAccordingToNbt - return lore.any { matcher.matches(it) } - } -} diff --git a/src/main/kotlin/moe/nea/firmament/features/texturepack/ModelOverrideData.kt b/src/main/kotlin/moe/nea/firmament/features/texturepack/ModelOverrideData.kt deleted file mode 100644 index 1585bd7..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/texturepack/ModelOverrideData.kt +++ /dev/null @@ -1,7 +0,0 @@ - -package moe.nea.firmament.features.texturepack - -interface ModelOverrideData { - fun getFirmamentOverrides(): Array? - fun setFirmamentOverrides(overrides: Array?) -} diff --git a/src/main/kotlin/moe/nea/firmament/features/texturepack/ModelOverrideFilterSet.kt b/src/main/kotlin/moe/nea/firmament/features/texturepack/ModelOverrideFilterSet.kt deleted file mode 100644 index 4ef8d06..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/texturepack/ModelOverrideFilterSet.kt +++ /dev/null @@ -1,19 +0,0 @@ - -package moe.nea.firmament.features.texturepack - -import com.google.gson.JsonElement -import moe.nea.firmament.util.filter.IteratorFilterSet - -class ModelOverrideFilterSet(original: java.util.Set>) : - IteratorFilterSet>(original) { - companion object { - @JvmStatic - fun createFilterSet(set: java.util.Set<*>): java.util.Set<*> { - return ModelOverrideFilterSet(set as java.util.Set>) as java.util.Set<*> - } - } - - override fun shouldKeepElement(element: Map.Entry): Boolean { - return !element.key.startsWith("firmament:") - } -} diff --git a/src/main/kotlin/moe/nea/firmament/features/texturepack/NotPredicate.kt b/src/main/kotlin/moe/nea/firmament/features/texturepack/NotPredicate.kt deleted file mode 100644 index ecd67c3..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/texturepack/NotPredicate.kt +++ /dev/null @@ -1,18 +0,0 @@ - -package moe.nea.firmament.features.texturepack - -import com.google.gson.JsonElement -import com.google.gson.JsonObject -import net.minecraft.item.ItemStack - -class NotPredicate(val children: Array) : FirmamentModelPredicate { - override fun test(stack: ItemStack): Boolean { - return children.none { it.test(stack) } - } - - object Parser : FirmamentModelPredicateParser { - override fun parse(jsonElement: JsonElement): FirmamentModelPredicate { - return NotPredicate(CustomModelOverrideParser.parsePredicates(jsonElement as JsonObject).toTypedArray()) - } - } -} diff --git a/src/main/kotlin/moe/nea/firmament/features/texturepack/NumberMatcher.kt b/src/main/kotlin/moe/nea/firmament/features/texturepack/NumberMatcher.kt deleted file mode 100644 index 7e6665f..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/texturepack/NumberMatcher.kt +++ /dev/null @@ -1,125 +0,0 @@ - -package moe.nea.firmament.features.texturepack - -import com.google.gson.JsonElement -import com.google.gson.JsonPrimitive -import moe.nea.firmament.util.useMatch - -abstract class NumberMatcher { - abstract fun test(number: Number): Boolean - - - companion object { - fun parse(jsonElement: JsonElement): NumberMatcher? { - if (jsonElement is JsonPrimitive) { - if (jsonElement.isString) { - val string = jsonElement.asString - return parseRange(string) ?: parseOperator(string) - } - if (jsonElement.isNumber) { - val number = jsonElement.asNumber - val hasDecimals = (number.toString().contains(".")) - return MatchNumberExact(if (hasDecimals) number.toLong() else number.toDouble()) - } - } - return null - } - - private val intervalSpec = - "(?[\\[\\(])(?[0-9.]+)?,(?[0-9.]+)?(?[\\]\\)])" - .toPattern() - - fun parseRange(string: String): RangeMatcher? { - intervalSpec.useMatch(string) { - // Open in the set-theory sense, meaning does not include its end. - val beginningOpen = group("beginningOpen") == "(" - val endingOpen = group("endingOpen") == ")" - val beginning = group("beginning")?.toDouble() - val ending = group("ending")?.toDouble() - return RangeMatcher(beginning, !beginningOpen, ending, !endingOpen) - } - return null - } - - enum class Operator(val operator: String) { - LESS("<") { - override fun matches(comparisonResult: Int): Boolean { - return comparisonResult < 0 - } - }, - LESS_EQUALS("<=") { - override fun matches(comparisonResult: Int): Boolean { - return comparisonResult <= 0 - } - }, - GREATER(">") { - override fun matches(comparisonResult: Int): Boolean { - return comparisonResult > 0 - } - }, - GREATER_EQUALS(">=") { - override fun matches(comparisonResult: Int): Boolean { - return comparisonResult >= 0 - } - }, - ; - - abstract fun matches(comparisonResult: Int): Boolean - } - - private val operatorPattern = "(?${Operator.entries.joinToString("|") {it.operator}})(?[0-9.]+)".toPattern() - - fun parseOperator(string: String): OperatorMatcher? { - operatorPattern.useMatch(string) { - val operatorName = group("operator") - val operator = Operator.entries.find { it.operator == operatorName }!! - val value = group("value").toDouble() - return OperatorMatcher(operator, value) - } - return null - } - - data class OperatorMatcher(val operator: Operator, val value: Double) : NumberMatcher() { - override fun test(number: Number): Boolean { - return operator.matches(number.toDouble().compareTo(value)) - } - } - - - data class MatchNumberExact(val number: Number) : NumberMatcher() { - override fun test(number: Number): Boolean { - return when (this.number) { - is Double -> number.toDouble() == this.number.toDouble() - else -> number.toLong() == this.number.toLong() - } - } - } - - data class RangeMatcher( - val beginning: Double?, - val beginningInclusive: Boolean, - val ending: Double?, - val endingInclusive: Boolean, - ) : NumberMatcher() { - override fun test(number: Number): Boolean { - val value = number.toDouble() - if (beginning != null) { - if (beginningInclusive) { - if (value < beginning) return false - } else { - if (value <= beginning) return false - } - } - if (ending != null) { - if (endingInclusive) { - if (value > ending) return false - } else { - if (value >= ending) return false - } - } - return true - } - } - } - -} diff --git a/src/main/kotlin/moe/nea/firmament/features/texturepack/OrPredicate.kt b/src/main/kotlin/moe/nea/firmament/features/texturepack/OrPredicate.kt deleted file mode 100644 index 32f556b..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/texturepack/OrPredicate.kt +++ /dev/null @@ -1,26 +0,0 @@ - -package moe.nea.firmament.features.texturepack - -import com.google.gson.JsonArray -import com.google.gson.JsonElement -import com.google.gson.JsonObject -import net.minecraft.item.ItemStack - -class OrPredicate(val children: Array) : FirmamentModelPredicate { - override fun test(stack: ItemStack): Boolean { - return children.any { it.test(stack) } - } - - object Parser : FirmamentModelPredicateParser { - override fun parse(jsonElement: JsonElement): FirmamentModelPredicate { - val children = - (jsonElement as JsonArray) - .flatMap { - CustomModelOverrideParser.parsePredicates(it as JsonObject) - } - .toTypedArray() - return OrPredicate(children) - } - - } -} diff --git a/src/main/kotlin/moe/nea/firmament/features/texturepack/PetPredicate.kt b/src/main/kotlin/moe/nea/firmament/features/texturepack/PetPredicate.kt deleted file mode 100644 index 5e5d750..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/texturepack/PetPredicate.kt +++ /dev/null @@ -1,66 +0,0 @@ - -package moe.nea.firmament.features.texturepack - -import com.google.gson.JsonElement -import com.google.gson.JsonObject -import net.minecraft.item.ItemStack -import moe.nea.firmament.repo.ExpLadders -import moe.nea.firmament.util.petData - -class PetPredicate( - val petId: StringMatcher?, - val tier: RarityMatcher?, - val exp: NumberMatcher?, - val candyUsed: NumberMatcher?, - val level: NumberMatcher?, -) : FirmamentModelPredicate { - - override fun test(stack: ItemStack): Boolean { - val petData = stack.petData ?: return false - if (petId != null) { - if (!petId.matches(petData.type)) return false - } - if (exp != null) { - if (!exp.test(petData.exp)) return false - } - if (candyUsed != null) { - if (!candyUsed.test(petData.candyUsed)) return false - } - if (tier != null) { - if (!tier.match(petData.tier)) return false - } - val levelData by lazy(LazyThreadSafetyMode.NONE) { - ExpLadders.getExpLadder(petData.type, petData.tier) - .getPetLevel(petData.exp) - } - if (level != null) { - if (!level.test(levelData.currentLevel)) return false - } - return true - } - - object Parser : FirmamentModelPredicateParser { - override fun parse(jsonElement: JsonElement): FirmamentModelPredicate? { - if (jsonElement.isJsonPrimitive) { - return PetPredicate(StringMatcher.Equals(jsonElement.asString, false), null, null, null, null) - } - if (jsonElement !is JsonObject) return null - val idMatcher = jsonElement["id"]?.let(StringMatcher::parse) - val expMatcher = jsonElement["exp"]?.let(NumberMatcher::parse) - val levelMatcher = jsonElement["level"]?.let(NumberMatcher::parse) - val candyMatcher = jsonElement["candyUsed"]?.let(NumberMatcher::parse) - val tierMatcher = jsonElement["tier"]?.let(RarityMatcher::parse) - return PetPredicate( - idMatcher, - tierMatcher, - expMatcher, - candyMatcher, - levelMatcher, - ) - } - } - - override fun toString(): String { - return super.toString() - } -} diff --git a/src/main/kotlin/moe/nea/firmament/features/texturepack/RarityMatcher.kt b/src/main/kotlin/moe/nea/firmament/features/texturepack/RarityMatcher.kt deleted file mode 100644 index 634a171..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/texturepack/RarityMatcher.kt +++ /dev/null @@ -1,69 +0,0 @@ - -package moe.nea.firmament.features.texturepack - -import com.google.gson.JsonElement -import io.github.moulberry.repo.data.Rarity -import moe.nea.firmament.util.useMatch - -abstract class RarityMatcher { - abstract fun match(rarity: Rarity): Boolean - - companion object { - fun parse(jsonElement: JsonElement): RarityMatcher { - val string = jsonElement.asString - val range = parseRange(string) - if (range != null) return range - return Exact(Rarity.valueOf(string)) - } - - private val allRarities = Rarity.entries.joinToString("|", "(?:", ")") - private val intervalSpec = - "(?[\\[\\(])(?$allRarities)?,(?$allRarities)?(?[\\]\\)])" - .toPattern() - - fun parseRange(string: String): RangeMatcher? { - intervalSpec.useMatch(string) { - // Open in the set-theory sense, meaning does not include its end. - val beginningOpen = group("beginningOpen") == "(" - val endingOpen = group("endingOpen") == ")" - val beginning = group("beginning")?.let(Rarity::valueOf) - val ending = group("ending")?.let(Rarity::valueOf) - return RangeMatcher(beginning, !beginningOpen, ending, !endingOpen) - } - return null - } - - } - - data class Exact(val expected: Rarity) : RarityMatcher() { - override fun match(rarity: Rarity): Boolean { - return rarity == expected - } - } - - data class RangeMatcher( - val beginning: Rarity?, - val beginningInclusive: Boolean, - val ending: Rarity?, - val endingInclusive: Boolean, - ) : RarityMatcher() { - override fun match(rarity: Rarity): Boolean { - if (beginning != null) { - if (beginningInclusive) { - if (rarity < beginning) return false - } else { - if (rarity <= beginning) return false - } - } - if (ending != null) { - if (endingInclusive) { - if (rarity > ending) return false - } else { - if (rarity >= ending) return false - } - } - return true - } - } - -} diff --git a/src/main/kotlin/moe/nea/firmament/features/texturepack/StringMatcher.kt b/src/main/kotlin/moe/nea/firmament/features/texturepack/StringMatcher.kt deleted file mode 100644 index 5eb86ac..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/texturepack/StringMatcher.kt +++ /dev/null @@ -1,159 +0,0 @@ - -package moe.nea.firmament.features.texturepack - -import com.google.gson.JsonArray -import com.google.gson.JsonElement -import com.google.gson.JsonNull -import com.google.gson.JsonObject -import com.google.gson.JsonPrimitive -import com.google.gson.internal.LazilyParsedNumber -import java.util.function.Predicate -import kotlinx.serialization.KSerializer -import kotlinx.serialization.Serializable -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder -import net.minecraft.nbt.NbtString -import net.minecraft.text.Text -import moe.nea.firmament.util.MC -import moe.nea.firmament.util.removeColorCodes - -@Serializable(with = StringMatcher.Serializer::class) -interface StringMatcher { - fun matches(string: String): Boolean - fun matches(text: Text): Boolean { - return matches(text.string) - } - - fun matches(nbt: NbtString): Boolean { - val string = nbt.asString() - val jsonStart = string.indexOf('{') - val stringStart = string.indexOf('"') - val isString = stringStart >= 0 && string.subSequence(0, stringStart).isBlank() - val isJson = jsonStart >= 0 && string.subSequence(0, jsonStart).isBlank() - if (isString || isJson) - return matches(Text.Serialization.fromJson(string, MC.defaultRegistries) ?: return false) - return matches(string) - } - - class Equals(input: String, val stripColorCodes: Boolean) : StringMatcher { - private val expected = if (stripColorCodes) input.removeColorCodes() else input - override fun matches(string: String): Boolean { - return expected == (if (stripColorCodes) string.removeColorCodes() else string) - } - - override fun toString(): String { - return "Equals($expected, stripColorCodes = $stripColorCodes)" - } - } - - class Pattern(val patternWithColorCodes: String, val stripColorCodes: Boolean) : StringMatcher { - private val regex: Predicate = patternWithColorCodes.toPattern().asMatchPredicate() - override fun matches(string: String): Boolean { - return regex.test(if (stripColorCodes) string.removeColorCodes() else string) - } - - override fun toString(): String { - return "Pattern($patternWithColorCodes, stripColorCodes = $stripColorCodes)" - } - } - - object Serializer : KSerializer { - val delegateSerializer = kotlinx.serialization.json.JsonElement.serializer() - override val descriptor: SerialDescriptor - get() = SerialDescriptor("StringMatcher", delegateSerializer.descriptor) - - override fun deserialize(decoder: Decoder): StringMatcher { - val delegate = decoder.decodeSerializableValue(delegateSerializer) - val gsonDelegate = delegate.intoGson() - return parse(gsonDelegate) - } - - override fun serialize(encoder: Encoder, value: StringMatcher) { - encoder.encodeSerializableValue(delegateSerializer, Companion.serialize(value).intoKotlinJson()) - } - - } - - companion object { - fun serialize(stringMatcher: StringMatcher): JsonElement { - TODO("Cannot serialize string matchers rn") - } - - fun parse(jsonElement: JsonElement): StringMatcher { - if (jsonElement is JsonPrimitive) { - return Equals(jsonElement.asString, true) - } - if (jsonElement is JsonObject) { - val regex = jsonElement["regex"] as JsonPrimitive? - val text = jsonElement["equals"] as JsonPrimitive? - val shouldStripColor = when (val color = (jsonElement["color"] as JsonPrimitive?)?.asString) { - "preserve" -> false - "strip", null -> true - else -> error("Unknown color preservation mode: $color") - } - if ((regex == null) == (text == null)) error("Could not parse $jsonElement as string matcher") - if (regex != null) - return Pattern(regex.asString, shouldStripColor) - if (text != null) - return Equals(text.asString, shouldStripColor) - } - error("Could not parse $jsonElement as a string matcher") - } - } -} - -fun JsonElement.intoKotlinJson(): kotlinx.serialization.json.JsonElement { - when (this) { - is JsonNull -> return kotlinx.serialization.json.JsonNull - is JsonObject -> { - return kotlinx.serialization.json.JsonObject(this.entrySet() - .associate { it.key to it.value.intoKotlinJson() }) - } - - is JsonArray -> { - return kotlinx.serialization.json.JsonArray(this.map { it.intoKotlinJson() }) - } - - is JsonPrimitive -> { - if (this.isString) - return kotlinx.serialization.json.JsonPrimitive(this.asString) - if (this.isBoolean) - return kotlinx.serialization.json.JsonPrimitive(this.asBoolean) - return kotlinx.serialization.json.JsonPrimitive(this.asNumber) - } - - else -> error("Unknown json variant $this") - } -} - -fun kotlinx.serialization.json.JsonElement.intoGson(): JsonElement { - when (this) { - is kotlinx.serialization.json.JsonNull -> return JsonNull.INSTANCE - is kotlinx.serialization.json.JsonPrimitive -> { - if (this.isString) - return JsonPrimitive(this.content) - if (this.content == "true") - return JsonPrimitive(true) - if (this.content == "false") - return JsonPrimitive(false) - return JsonPrimitive(LazilyParsedNumber(this.content)) - } - - is kotlinx.serialization.json.JsonObject -> { - val obj = JsonObject() - for ((k, v) in this) { - obj.add(k, v.intoGson()) - } - return obj - } - - is kotlinx.serialization.json.JsonArray -> { - val arr = JsonArray() - for (v in this) { - arr.add(v.intoGson()) - } - return arr - } - } -} diff --git a/src/main/kotlin/moe/nea/firmament/features/world/FairySouls.kt b/src/main/kotlin/moe/nea/firmament/features/world/FairySouls.kt deleted file mode 100644 index 8a8291a..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/world/FairySouls.kt +++ /dev/null @@ -1,131 +0,0 @@ - - -package moe.nea.firmament.features.world - -import io.github.moulberry.repo.data.Coordinate -import kotlinx.serialization.Serializable -import kotlinx.serialization.serializer -import net.minecraft.text.Text -import net.minecraft.util.math.Vec3d -import moe.nea.firmament.annotations.Subscribe -import moe.nea.firmament.events.ProcessChatEvent -import moe.nea.firmament.events.SkyblockServerUpdateEvent -import moe.nea.firmament.events.WorldRenderLastEvent -import moe.nea.firmament.features.FirmamentFeature -import moe.nea.firmament.gui.config.ManagedConfig -import moe.nea.firmament.repo.RepoManager -import moe.nea.firmament.util.MC -import moe.nea.firmament.util.SBData -import moe.nea.firmament.util.SkyBlockIsland -import moe.nea.firmament.util.blockPos -import moe.nea.firmament.util.data.ProfileSpecificDataHolder -import moe.nea.firmament.util.render.RenderInWorldContext -import moe.nea.firmament.util.render.RenderInWorldContext.Companion.renderInWorld -import moe.nea.firmament.util.unformattedString - - -object FairySouls : FirmamentFeature { - - - @Serializable - data class Data( - val foundSouls: MutableMap> = mutableMapOf() - ) - - override val config: ManagedConfig - get() = TConfig - - object DConfig : ProfileSpecificDataHolder(serializer(), "found-fairysouls", ::Data) - - - object TConfig : ManagedConfig("fairy-souls") { - val displaySouls by toggle("show") { false } - val resetSouls by button("reset") { - DConfig.data?.foundSouls?.clear() != null - updateMissingSouls() - } - } - - - override val identifier: String get() = "fairy-souls" - - val playerReach = 5 - val playerReachSquared = playerReach * playerReach - - var currentLocationName: SkyBlockIsland? = null - var currentLocationSouls: List = emptyList() - var currentMissingSouls: List = emptyList() - - fun updateMissingSouls() { - currentMissingSouls = emptyList() - val c = DConfig.data ?: return - val fi = c.foundSouls[currentLocationName] ?: setOf() - val cms = currentLocationSouls.toMutableList() - fi.asSequence().sortedDescending().filter { it in cms.indices }.forEach { cms.removeAt(it) } - currentMissingSouls = cms - } - - fun updateWorldSouls() { - currentLocationSouls = emptyList() - val loc = currentLocationName ?: return - currentLocationSouls = RepoManager.neuRepo.constants.fairySouls.soulLocations[loc.locrawMode] ?: return - } - - fun findNearestClickableSoul(): Coordinate? { - val player = MC.player ?: return null - val pos = player.pos - val location = SBData.skyblockLocation ?: return null - val soulLocations: List = - RepoManager.neuRepo.constants.fairySouls.soulLocations[location.locrawMode] ?: return null - return soulLocations - .map { it to it.blockPos.getSquaredDistance(pos) } - .filter { it.second < playerReachSquared } - .minByOrNull { it.second } - ?.first - } - - private fun markNearestSoul() { - val nearestSoul = findNearestClickableSoul() ?: return - val c = DConfig.data ?: return - val loc = currentLocationName ?: return - val idx = currentLocationSouls.indexOf(nearestSoul) - c.foundSouls.computeIfAbsent(loc) { mutableSetOf() }.add(idx) - DConfig.markDirty() - updateMissingSouls() - } - - @Subscribe - fun onWorldRender(it: WorldRenderLastEvent) { - if (!TConfig.displaySouls) return - renderInWorld(it) { - color(1F, 1F, 0F, 0.8F) - currentMissingSouls.forEach { - block(it.blockPos) - } - color(1f, 0f, 1f, 1f) - currentLocationSouls.forEach { - wireframeCube(it.blockPos) - } - } - } - - @Subscribe - fun onProcessChat(it: ProcessChatEvent) { - when (it.text.unformattedString) { - "You have already found that Fairy Soul!" -> { - markNearestSoul() - } - - "SOUL! You found a Fairy Soul!" -> { - markNearestSoul() - } - } - } - - @Subscribe - fun onLocationChange(it: SkyblockServerUpdateEvent) { - currentLocationName = it.newLocraw?.skyblockLocation - updateWorldSouls() - updateMissingSouls() - } -} diff --git a/src/main/kotlin/moe/nea/firmament/features/world/NPCWaypoints.kt b/src/main/kotlin/moe/nea/firmament/features/world/NPCWaypoints.kt deleted file mode 100644 index 592b8fa..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/world/NPCWaypoints.kt +++ /dev/null @@ -1,40 +0,0 @@ -package moe.nea.firmament.features.world - -import moe.nea.firmament.annotations.Subscribe -import moe.nea.firmament.commands.thenExecute -import moe.nea.firmament.events.CommandEvent -import moe.nea.firmament.events.ReloadRegistrationEvent -import moe.nea.firmament.util.MoulConfigUtils -import moe.nea.firmament.util.ScreenUtil - -object NPCWaypoints { - - var allNpcWaypoints = listOf() - - @Subscribe - fun onRepoReloadRegistration(event: ReloadRegistrationEvent) { - event.repo.registerReloadListener { - allNpcWaypoints = it.items.items.values - .asSequence() - .filter { !it.island.isNullOrBlank() } - .map { - NavigableWaypoint.NPCWaypoint(it) - } - .toList() - } - } - - @Subscribe - fun onOpenGui(event: CommandEvent.SubCommand) { - event.subcommand("npcs") { - thenExecute { - ScreenUtil.setScreenLater(MoulConfigUtils.loadScreen( - "npc_waypoints", - NpcWaypointGui(allNpcWaypoints), - null)) - } - } - } - - -} diff --git a/src/main/kotlin/moe/nea/firmament/features/world/NavigableWaypoint.kt b/src/main/kotlin/moe/nea/firmament/features/world/NavigableWaypoint.kt deleted file mode 100644 index 28a517f..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/world/NavigableWaypoint.kt +++ /dev/null @@ -1,22 +0,0 @@ -package moe.nea.firmament.features.world - -import io.github.moulberry.repo.data.NEUItem -import net.minecraft.util.math.BlockPos -import moe.nea.firmament.util.SkyBlockIsland - -abstract class NavigableWaypoint { - abstract val name: String - abstract val position: BlockPos - abstract val island: SkyBlockIsland - - data class NPCWaypoint( - val item: NEUItem, - ) : NavigableWaypoint() { - override val name: String - get() = item.displayName - override val position: BlockPos - get() = BlockPos(item.x, item.y, item.z) - override val island: SkyBlockIsland - get() = SkyBlockIsland.forMode(item.island) - } -} diff --git a/src/main/kotlin/moe/nea/firmament/features/world/NavigationHelper.kt b/src/main/kotlin/moe/nea/firmament/features/world/NavigationHelper.kt deleted file mode 100644 index acdfb86..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/world/NavigationHelper.kt +++ /dev/null @@ -1,121 +0,0 @@ -package moe.nea.firmament.features.world - -import io.github.moulberry.repo.constants.Islands -import net.minecraft.text.Text -import net.minecraft.util.math.BlockPos -import net.minecraft.util.math.Position -import net.minecraft.util.math.Vec3i -import moe.nea.firmament.annotations.Subscribe -import moe.nea.firmament.events.SkyblockServerUpdateEvent -import moe.nea.firmament.events.TickEvent -import moe.nea.firmament.events.WorldRenderLastEvent -import moe.nea.firmament.repo.RepoManager -import moe.nea.firmament.util.MC -import moe.nea.firmament.util.SBData -import moe.nea.firmament.util.SkyBlockIsland -import moe.nea.firmament.util.WarpUtil -import moe.nea.firmament.util.render.RenderInWorldContext - -object NavigationHelper { - var targetWaypoint: NavigableWaypoint? = null - set(value) { - field = value - recalculateRoute() - } - - var nextTeleporter: Islands.Teleporter? = null - private set - - val Islands.Teleporter.toIsland get() = SkyBlockIsland.forMode(this.getTo()) - val Islands.Teleporter.fromIsland get() = SkyBlockIsland.forMode(this.getFrom()) - val Islands.Teleporter.blockPos get() = BlockPos(x.toInt(), y.toInt(), z.toInt()) - - @Subscribe - fun onWorldSwitch(event: SkyblockServerUpdateEvent) { - recalculateRoute() - } - - fun recalculateRoute() { - val tp = targetWaypoint - val currentIsland = SBData.skyblockLocation - if (tp == null || currentIsland == null) { - nextTeleporter = null - return - } - val route = findRoute(currentIsland, tp.island, mutableSetOf()) - nextTeleporter = route?.get(0) - } - - private fun findRoute( - fromIsland: SkyBlockIsland, - targetIsland: SkyBlockIsland, - visitedIslands: MutableSet - ): MutableList? { - var shortestChain: MutableList? = null - for (it in RepoManager.neuRepo.constants.islands.teleporters) { - if (it.toIsland in visitedIslands) continue - if (it.fromIsland != fromIsland) continue - if (it.toIsland == targetIsland) return mutableListOf(it) - visitedIslands.add(fromIsland) - val nextRoute = findRoute(it.toIsland, targetIsland, visitedIslands) ?: continue - nextRoute.add(0, it) - if (shortestChain == null || shortestChain.size > nextRoute.size) { - shortestChain = nextRoute - } - visitedIslands.remove(fromIsland) - } - return shortestChain - } - - - @Subscribe - fun onMovement(event: TickEvent) { // TODO: add a movement tick event maybe? - val tp = targetWaypoint ?: return - val p = MC.player ?: return - if (p.squaredDistanceTo(tp.position.toCenterPos()) < 5 * 5) { - targetWaypoint = null - } - } - - @Subscribe - fun drawWaypoint(event: WorldRenderLastEvent) { - val tp = targetWaypoint ?: return - val nt = nextTeleporter - RenderInWorldContext.renderInWorld(event) { - if (nt != null) { - waypoint(nt.blockPos, - Text.literal("Teleporter to " + nt.toIsland.userFriendlyName), - Text.literal("(towards " + tp.name + "§f)")) - } else if (tp.island == SBData.skyblockLocation) { - waypoint(tp.position, - Text.literal(tp.name)) - } - } - } - - fun tryWarpNear() { - val tp = targetWaypoint - if (tp == null) { - MC.sendChat(Text.literal("Could not find a waypoint to warp you to. Select one first.")) - return - } - WarpUtil.teleportToNearestWarp(tp.island, tp.position.asPositionView()) - } - -} - -fun Vec3i.asPositionView(): Position { - return object : Position { - override fun getX(): Double { - return this@asPositionView.x.toDouble() - } - - override fun getY(): Double { - return this@asPositionView.y.toDouble() - } - - override fun getZ(): Double { - return this@asPositionView.z.toDouble() - } - } -} diff --git a/src/main/kotlin/moe/nea/firmament/features/world/NpcWaypointGui.kt b/src/main/kotlin/moe/nea/firmament/features/world/NpcWaypointGui.kt deleted file mode 100644 index 6146e50..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/world/NpcWaypointGui.kt +++ /dev/null @@ -1,68 +0,0 @@ -package moe.nea.firmament.features.world - -import io.github.notenoughupdates.moulconfig.observer.ObservableList -import io.github.notenoughupdates.moulconfig.xml.Bind -import moe.nea.firmament.features.events.anniversity.AnniversaryFeatures.atOnce -import moe.nea.firmament.keybindings.SavedKeyBinding - -class NpcWaypointGui( - val allWaypoints: List, -) { - - data class NavigableWaypointW(val waypoint: NavigableWaypoint) { - @Bind - fun name() = waypoint.name - - @Bind - fun isSelected() = NavigationHelper.targetWaypoint == waypoint - - @Bind - fun click() { - if (SavedKeyBinding.isShiftDown()) { - NavigationHelper.targetWaypoint = waypoint - NavigationHelper.tryWarpNear() - } else if (isSelected()) { - NavigationHelper.targetWaypoint = null - } else { - NavigationHelper.targetWaypoint = waypoint - } - } - } - - @JvmField - @field:Bind - var search: String = "" - var lastSearch: String? = null - - @Bind("results") - fun results(): ObservableList { - return results - } - - @Bind - fun tick() { - if (search != lastSearch) { - updateSearch() - lastSearch = search - } - } - - val results: ObservableList = ObservableList(mutableListOf()) - - fun updateSearch() { - val split = search.split(" +".toRegex()) - results.atOnce { - results.clear() - allWaypoints.filter { waypoint -> - if (search.isBlank()) { - true - } else { - split.all { waypoint.name.contains(it, ignoreCase = true) } - } - }.mapTo(results) { - NavigableWaypointW(it) - } - } - } - -} diff --git a/src/main/kotlin/moe/nea/firmament/features/world/Waypoints.kt b/src/main/kotlin/moe/nea/firmament/features/world/Waypoints.kt deleted file mode 100644 index 91a06da..0000000 --- a/src/main/kotlin/moe/nea/firmament/features/world/Waypoints.kt +++ /dev/null @@ -1,297 +0,0 @@ - - -package moe.nea.firmament.features.world - -import com.mojang.brigadier.arguments.IntegerArgumentType -import me.shedaniel.math.Color -import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource -import kotlinx.serialization.Serializable -import kotlin.collections.component1 -import kotlin.collections.component2 -import kotlin.collections.set -import kotlin.time.Duration.Companion.hours -import kotlin.time.Duration.Companion.seconds -import net.minecraft.command.argument.BlockPosArgumentType -import net.minecraft.server.command.ServerCommandSource -import net.minecraft.text.Text -import net.minecraft.util.math.BlockPos -import net.minecraft.util.math.Vec3d -import moe.nea.firmament.Firmament -import moe.nea.firmament.annotations.Subscribe -import moe.nea.firmament.commands.get -import moe.nea.firmament.commands.thenArgument -import moe.nea.firmament.commands.thenExecute -import moe.nea.firmament.commands.thenLiteral -import moe.nea.firmament.events.CommandEvent -import moe.nea.firmament.events.ProcessChatEvent -import moe.nea.firmament.events.TickEvent -import moe.nea.firmament.events.WorldReadyEvent -import moe.nea.firmament.events.WorldRenderLastEvent -import moe.nea.firmament.features.FirmamentFeature -import moe.nea.firmament.gui.config.ManagedConfig -import moe.nea.firmament.util.ClipboardUtils -import moe.nea.firmament.util.MC -import moe.nea.firmament.util.TimeMark -import moe.nea.firmament.util.render.RenderInWorldContext - -object Waypoints : FirmamentFeature { - override val identifier: String - get() = "waypoints" - - object TConfig : ManagedConfig(identifier) { - val tempWaypointDuration by duration("temp-waypoint-duration", 0.seconds, 1.hours) { 30.seconds } - val showIndex by toggle("show-index") { true } - val skipToNearest by toggle("skip-to-nearest") { false } - // TODO: look ahead size - } - - data class TemporaryWaypoint( - val pos: BlockPos, - val postedAt: TimeMark, - ) - - override val config get() = TConfig - - val temporaryPlayerWaypointList = mutableMapOf() - val temporaryPlayerWaypointMatcher = "(?i)x: (-?[0-9]+),? y: (-?[0-9]+),? z: (-?[0-9]+)".toPattern() - - val waypoints = mutableListOf() - var ordered = false - var orderedIndex = 0 - - @Serializable - data class ColeWeightWaypoint( - val x: Int, - val y: Int, - val z: Int, - val r: Int = 0, - val g: Int = 0, - val b: Int = 0, - ) - - @Subscribe - fun onRenderOrderedWaypoints(event: WorldRenderLastEvent) { - if (waypoints.isEmpty()) return - RenderInWorldContext.renderInWorld(event) { - if (!ordered) { - waypoints.withIndex().forEach { - color(0f, 0.3f, 0.7f, 0.5f) - block(it.value) - color(1f, 1f, 1f, 1f) - if (TConfig.showIndex) - withFacingThePlayer(it.value.toCenterPos()) { - text(Text.literal(it.index.toString())) - } - } - } else { - orderedIndex %= waypoints.size - val firstColor = Color.ofRGBA(0, 200, 40, 180) - color(firstColor) - tracer(waypoints[orderedIndex].toCenterPos(), lineWidth = 3f) - waypoints.withIndex().toList() - .wrappingWindow(orderedIndex, 3) - .zip( - listOf( - firstColor, - Color.ofRGBA(180, 200, 40, 150), - Color.ofRGBA(180, 80, 20, 140), - ) - ) - .reversed() - .forEach { (waypoint, col) -> - val (index, pos) = waypoint - color(col) - block(pos) - color(1f, 1f, 1f, 1f) - if (TConfig.showIndex) - withFacingThePlayer(pos.toCenterPos()) { - text(Text.literal(index.toString())) - } - } - } - } - } - - @Subscribe - fun onTick(event: TickEvent) { - if (waypoints.isEmpty() || !ordered) return - orderedIndex %= waypoints.size - val p = MC.player?.pos ?: return - if (TConfig.skipToNearest) { - orderedIndex = - (waypoints.withIndex().minBy { it.value.getSquaredDistance(p) }.index + 1) % waypoints.size - } else { - if (waypoints[orderedIndex].isWithinDistance(p, 3.0)) { - orderedIndex = (orderedIndex + 1) % waypoints.size - } - } - } - - @Subscribe - fun onProcessChat(it: ProcessChatEvent) { - val matcher = temporaryPlayerWaypointMatcher.matcher(it.unformattedString) - if (it.nameHeuristic != null && TConfig.tempWaypointDuration > 0.seconds && matcher.find()) { - temporaryPlayerWaypointList[it.nameHeuristic] = TemporaryWaypoint( - BlockPos( - matcher.group(1).toInt(), - matcher.group(2).toInt(), - matcher.group(3).toInt(), - ), - TimeMark.now() - ) - } - } - - @Subscribe - fun onCommand(event: CommandEvent.SubCommand) { - event.subcommand("waypoint") { - thenArgument("pos", BlockPosArgumentType.blockPos()) { pos -> - thenExecute { - val position = pos.get(this).toAbsoluteBlockPos(source.asFakeServer()) - waypoints.add(position) - source.sendFeedback( - Text.stringifiedTranslatable( - "firmament.command.waypoint.added", - position.x, - position.y, - position.z - ) - ) - } - } - } - event.subcommand("waypoints") { - thenLiteral("clear") { - thenExecute { - waypoints.clear() - source.sendFeedback(Text.translatable("firmament.command.waypoint.clear")) - } - } - thenLiteral("toggleordered") { - thenExecute { - ordered = !ordered - if (ordered) { - val p = MC.player?.pos ?: Vec3d.ZERO - orderedIndex = - waypoints.withIndex().minByOrNull { it.value.getSquaredDistance(p) }?.index ?: 0 - } - source.sendFeedback(Text.translatable("firmament.command.waypoint.ordered.toggle.$ordered")) - } - } - thenLiteral("skip") { - thenExecute { - if (ordered && waypoints.isNotEmpty()) { - orderedIndex = (orderedIndex + 1) % waypoints.size - source.sendFeedback(Text.translatable("firmament.command.waypoint.skip")) - } else { - source.sendError(Text.translatable("firmament.command.waypoint.skip.error")) - } - } - } - thenLiteral("remove") { - thenArgument("index", IntegerArgumentType.integer(0)) { indexArg -> - thenExecute { - val index = get(indexArg) - if (index in waypoints.indices) { - waypoints.removeAt(index) - source.sendFeedback(Text.stringifiedTranslatable( - "firmament.command.waypoint.remove", - index)) - } else { - source.sendError(Text.stringifiedTranslatable("firmament.command.waypoint.remove.error")) - } - } - } - } - thenLiteral("import") { - thenExecute { - val contents = ClipboardUtils.getTextContents() - val data = try { - Firmament.json.decodeFromString>(contents) - } catch (ex: Exception) { - Firmament.logger.error("Could not load waypoints from clipboard", ex) - source.sendError(Text.translatable("firmament.command.waypoint.import.error")) - return@thenExecute - } - waypoints.clear() - data.mapTo(waypoints) { BlockPos(it.x, it.y, it.z) } - source.sendFeedback( - Text.stringifiedTranslatable( - "firmament.command.waypoint.import", - data.size - ) - ) - } - } - } - } - - @Subscribe - fun onRenderTemporaryWaypoints(event: WorldRenderLastEvent) { - temporaryPlayerWaypointList.entries.removeIf { it.value.postedAt.passedTime() > TConfig.tempWaypointDuration } - if (temporaryPlayerWaypointList.isEmpty()) return - RenderInWorldContext.renderInWorld(event) { - color(1f, 1f, 0f, 1f) - temporaryPlayerWaypointList.forEach { (player, waypoint) -> - block(waypoint.pos) - } - color(1f, 1f, 1f, 1f) - temporaryPlayerWaypointList.forEach { (player, waypoint) -> - val skin = - MC.networkHandler?.listedPlayerListEntries?.find { it.profile.name == player } - ?.skinTextures - ?.texture - withFacingThePlayer(waypoint.pos.toCenterPos()) { - waypoint(waypoint.pos, Text.stringifiedTranslatable("firmament.waypoint.temporary", player)) - if (skin != null) { - matrixStack.translate(0F, -20F, 0F) - // Head front - texture( - skin, 16, 16, - 1 / 8f, 1 / 8f, - 2 / 8f, 2 / 8f, - ) - // Head overlay - texture( - skin, 16, 16, - 5 / 8f, 1 / 8f, - 6 / 8f, 2 / 8f, - ) - } - } - } - } - } - - @Subscribe - fun onWorldReady(event: WorldReadyEvent) { - temporaryPlayerWaypointList.clear() - } -} - -fun List.wrappingWindow(startIndex: Int, windowSize: Int): List { - val result = ArrayList(windowSize) - if (startIndex + windowSize < size) { - result.addAll(subList(startIndex, startIndex + windowSize)) - } else { - result.addAll(subList(startIndex, size)) - result.addAll(subList(0, minOf(windowSize - (size - startIndex), startIndex))) - } - return result -} - - -fun FabricClientCommandSource.asFakeServer(): ServerCommandSource { - val source = this - return ServerCommandSource( - source.player, - source.position, - source.rotation, - null, - 0, - "FakeServerCommandSource", - Text.literal("FakeServerCommandSource"), - null, - source.player - ) -} diff --git a/src/main/kotlin/moe/nea/firmament/gui/BarComponent.kt b/src/main/kotlin/moe/nea/firmament/gui/BarComponent.kt deleted file mode 100644 index 8ef0753..0000000 --- a/src/main/kotlin/moe/nea/firmament/gui/BarComponent.kt +++ /dev/null @@ -1,125 +0,0 @@ - -package moe.nea.firmament.gui - -import com.mojang.blaze3d.systems.RenderSystem -import io.github.notenoughupdates.moulconfig.common.MyResourceLocation -import io.github.notenoughupdates.moulconfig.common.RenderContext -import io.github.notenoughupdates.moulconfig.gui.GuiComponent -import io.github.notenoughupdates.moulconfig.gui.GuiImmediateContext -import io.github.notenoughupdates.moulconfig.observer.GetSetter -import io.github.notenoughupdates.moulconfig.platform.ModernRenderContext -import me.shedaniel.math.Color -import net.minecraft.client.gui.DrawContext -import net.minecraft.util.Identifier -import moe.nea.firmament.Firmament - -class BarComponent( - val progress: GetSetter, val total: GetSetter, - val fillColor: Color, - val emptyColor: Color, -) : GuiComponent() { - override fun getWidth(): Int { - return 80 - } - - override fun getHeight(): Int { - return 8 - } - - data class Texture( - val identifier: Identifier, - val u1: Float, val v1: Float, - val u2: Float, val v2: Float, - ) { - fun draw(context: DrawContext, x: Int, y: Int, width: Int, height: Int, color: Color) { - context.drawTexturedQuad( - identifier, - x, y, x + width, x + height, 0, - u1, u2, v1, v2, - color.red / 255F, - color.green / 255F, - color.blue / 255F, - color.alpha / 255F, - ) - } - } - - companion object { - val resource = Firmament.identifier("textures/gui/bar.png") - val left = Texture(resource, 0 / 64F, 0 / 64F, 4 / 64F, 8 / 64F) - val middle = Texture(resource, 4 / 64F, 0 / 64F, 8 / 64F, 8 / 64F) - val right = Texture(resource, 8 / 64F, 0 / 64F, 12 / 64F, 8 / 64F) - val segmentOverlay = Texture(resource, 12 / 64F, 0 / 64F, 15 / 64F, 8 / 64F) - } - - private fun drawSection( - context: DrawContext, - texture: Texture, - x: Int, - y: Int, - width: Int, - sectionStart: Double, - sectionEnd: Double - ) { - if (sectionEnd < progress.get() && width == 4) { - texture.draw(context, x, y, 4, 8, fillColor) - return - } - if (sectionStart > progress.get() && width == 4) { - texture.draw(context, x, y, 4, 8, emptyColor) - return - } - val increasePerPixel = (sectionEnd - sectionStart) / width - var valueAtPixel = sectionStart - for (i in (0 until width)) { - val newTex = - Texture(texture.identifier, texture.u1 + i / 64F, texture.v1, texture.u1 + (i + 1) / 64F, texture.v2) - newTex.draw( - context, x + i, y, 1, 8, - if (valueAtPixel < progress.get()) fillColor else emptyColor - ) - valueAtPixel += increasePerPixel - } - } - - override fun render(context: GuiImmediateContext) { - val renderContext = (context.renderContext as ModernRenderContext).drawContext - var i = 0 - val x = 0 - val y = 0 - while (i < context.width - 4) { - drawSection( - renderContext, - if (i == 0) left else middle, - x + i, y, - (context.width - (i + 4)).coerceAtMost(4), - i * total.get() / context.width, (i + 4) * total.get() / context.width - ) - i += 4 - } - drawSection( - renderContext, - right, - x + context.width - 4, - y, - 4, - (context.width - 4) * total.get() / context.width, - total.get() - ) - RenderSystem.setShaderColor(1F, 1F, 1F, 1F) - - } - -} - -fun Identifier.toMoulConfig(): MyResourceLocation { - return MyResourceLocation(this.namespace, this.path) -} - -fun RenderContext.color(color: Color) { - color(color.red, color.green, color.blue, color.alpha) -} - -fun RenderContext.color(red: Int, green: Int, blue: Int, alpha: Int) { - color(red / 255f, green / 255f, blue / 255f, alpha / 255f) -} diff --git a/src/main/kotlin/moe/nea/firmament/gui/FirmButtonComponent.kt b/src/main/kotlin/moe/nea/firmament/gui/FirmButtonComponent.kt deleted file mode 100644 index 82e5b05..0000000 --- a/src/main/kotlin/moe/nea/firmament/gui/FirmButtonComponent.kt +++ /dev/null @@ -1,81 +0,0 @@ - -package moe.nea.firmament.gui - -import io.github.notenoughupdates.moulconfig.common.MyResourceLocation -import io.github.notenoughupdates.moulconfig.deps.libninepatch.NinePatch -import io.github.notenoughupdates.moulconfig.gui.GuiComponent -import io.github.notenoughupdates.moulconfig.gui.GuiImmediateContext -import io.github.notenoughupdates.moulconfig.gui.MouseEvent -import io.github.notenoughupdates.moulconfig.gui.component.PanelComponent -import io.github.notenoughupdates.moulconfig.observer.GetSetter - - -open class FirmButtonComponent( - child: GuiComponent, - val isEnabled: GetSetter = GetSetter.constant(true), - val noBackground: Boolean = false, - val action: Runnable, -) : PanelComponent(child, if (noBackground) 0 else 2, DefaultBackgroundRenderer.TRANSPARENT) { - - /* TODO: make use of vanillas built in nine slicer */ - val hoveredBg = - NinePatch.builder(MyResourceLocation("minecraft", "textures/gui/sprites/widget/button_highlighted.png")) - .cornerSize(5) - .cornerUv(5 / 200F, 5 / 20F) - .mode(NinePatch.Mode.STRETCHING) - .build() - val unhoveredBg = NinePatch.builder(MyResourceLocation("minecraft", "textures/gui/sprites/widget/button.png")) - .cornerSize(5) - .cornerUv(5 / 200F, 5 / 20F) - .mode(NinePatch.Mode.STRETCHING) - .build() - val disabledBg = - NinePatch.builder(MyResourceLocation("minecraft", "textures/gui/sprites/widget/button_disabled.png")) - .cornerSize(5) - .cornerUv(5 / 200F, 5 / 20F) - .mode(NinePatch.Mode.STRETCHING) - .build() - val activeBg = NinePatch.builder(MyResourceLocation("firmament", "textures/gui/sprites/widget/button_active.png")) - .cornerSize(5) - .cornerUv(5 / 200F, 5 / 20F) - .mode(NinePatch.Mode.STRETCHING) - .build() - var isClicking = false - override fun mouseEvent(mouseEvent: MouseEvent, context: GuiImmediateContext): Boolean { - if (!isEnabled.get()) return false - if (isClicking) { - if (mouseEvent is MouseEvent.Click && !mouseEvent.mouseState && mouseEvent.mouseButton == 0) { - isClicking = false - if (context.isHovered) { - action.run() - } - return true - } - } - if (!context.isHovered) return false - if (mouseEvent !is MouseEvent.Click) return false - if (mouseEvent.mouseState && mouseEvent.mouseButton == 0) { - requestFocus() - isClicking = true - return true - } - return false - } - - open fun getBackground(context: GuiImmediateContext): NinePatch = - if (!isEnabled.get()) disabledBg - else if (context.isHovered || isClicking) hoveredBg - else unhoveredBg - - override fun render(context: GuiImmediateContext) { - context.renderContext.pushMatrix() - if (!noBackground) - context.renderContext.drawNinePatch( - getBackground(context), - 0f, 0f, context.width, context.height - ) - context.renderContext.translate(insets.toFloat(), insets.toFloat(), 0f) - element.render(getChildContext(context)) - context.renderContext.popMatrix() - } -} diff --git a/src/main/kotlin/moe/nea/firmament/gui/FirmHoverComponent.kt b/src/main/kotlin/moe/nea/firmament/gui/FirmHoverComponent.kt deleted file mode 100644 index b1792ce..0000000 --- a/src/main/kotlin/moe/nea/firmament/gui/FirmHoverComponent.kt +++ /dev/null @@ -1,59 +0,0 @@ -package moe.nea.firmament.gui - -import io.github.notenoughupdates.moulconfig.gui.GuiComponent -import io.github.notenoughupdates.moulconfig.gui.GuiImmediateContext -import io.github.notenoughupdates.moulconfig.gui.KeyboardEvent -import io.github.notenoughupdates.moulconfig.gui.MouseEvent -import java.util.function.BiFunction -import java.util.function.Supplier -import kotlin.time.Duration -import moe.nea.firmament.util.TimeMark - -class FirmHoverComponent( - val child: GuiComponent, - val hoverLines: Supplier>, - val hoverDelay: Duration, -) : GuiComponent() { - override fun getWidth(): Int { - return child.width - } - - override fun getHeight(): Int { - return child.height - } - - override fun foldChildren( - initial: T, - visitor: BiFunction - ): T { - return visitor.apply(child, initial) - } - - override fun render(context: GuiImmediateContext) { - if (context.isHovered && (permaHover || lastMouseMove.passedTime() > hoverDelay)) { - context.renderContext.scheduleDrawTooltip(hoverLines.get()) - permaHover = true - } else { - permaHover = false - } - if (!context.isHovered) { - lastMouseMove = TimeMark.now() - } - child.render(context) - - } - - var permaHover = false - var lastMouseMove = TimeMark.farPast() - - override fun mouseEvent(mouseEvent: MouseEvent, context: GuiImmediateContext): Boolean { - if (mouseEvent is MouseEvent.Move) { - lastMouseMove = TimeMark.now() - } - return child.mouseEvent(mouseEvent, context) - } - - override fun keyboardEvent(event: KeyboardEvent, context: GuiImmediateContext): Boolean { - return child.keyboardEvent(event, context) - } -} diff --git a/src/main/kotlin/moe/nea/firmament/gui/FixedComponent.kt b/src/main/kotlin/moe/nea/firmament/gui/FixedComponent.kt deleted file mode 100644 index ae1da2d..0000000 --- a/src/main/kotlin/moe/nea/firmament/gui/FixedComponent.kt +++ /dev/null @@ -1,38 +0,0 @@ - -package moe.nea.firmament.gui - -import io.github.notenoughupdates.moulconfig.gui.GuiComponent -import io.github.notenoughupdates.moulconfig.gui.GuiImmediateContext -import io.github.notenoughupdates.moulconfig.gui.KeyboardEvent -import io.github.notenoughupdates.moulconfig.gui.MouseEvent -import io.github.notenoughupdates.moulconfig.observer.GetSetter -import java.util.function.BiFunction - -class FixedComponent( - val fixedWidth: GetSetter?, - val fixedHeight: GetSetter?, - val component: GuiComponent, -) : GuiComponent() { - override fun getWidth(): Int = fixedWidth?.get() ?: component.width - - override fun getHeight(): Int = fixedHeight?.get() ?: component.height - - override fun foldChildren(initial: T, visitor: BiFunction): T { - return visitor.apply(component, initial) - } - - fun fixContext(context: GuiImmediateContext): GuiImmediateContext = - context.translated(0, 0, width, height) - - override fun render(context: GuiImmediateContext) { - component.render(fixContext(context)) - } - - override fun mouseEvent(mouseEvent: MouseEvent, context: GuiImmediateContext): Boolean { - return component.mouseEvent(mouseEvent, fixContext(context)) - } - - override fun keyboardEvent(event: KeyboardEvent, context: GuiImmediateContext): Boolean { - return component.keyboardEvent(event, fixContext(context)) - } -} diff --git a/src/main/kotlin/moe/nea/firmament/gui/ImageComponent.kt b/src/main/kotlin/moe/nea/firmament/gui/ImageComponent.kt deleted file mode 100644 index bba7dee..0000000 --- a/src/main/kotlin/moe/nea/firmament/gui/ImageComponent.kt +++ /dev/null @@ -1,33 +0,0 @@ -package moe.nea.firmament.gui - -import io.github.notenoughupdates.moulconfig.common.MyResourceLocation -import io.github.notenoughupdates.moulconfig.gui.GuiComponent -import io.github.notenoughupdates.moulconfig.gui.GuiImmediateContext -import java.util.function.Supplier - -class ImageComponent( - private val width: Int, - private val height: Int, - val resourceLocation: Supplier, - val u1: Float, - val u2: Float, - val v1: Float, - val v2: Float, -) : GuiComponent() { - override fun getWidth(): Int { - return width - } - - override fun getHeight(): Int { - return height - } - - override fun render(context: GuiImmediateContext) { - context.renderContext.bindTexture(resourceLocation.get()) - context.renderContext.drawTexturedRect( - 0f, 0f, - context.width.toFloat(), context.height.toFloat(), - u1, v1, u2, v2 - ) - } -} diff --git a/src/main/kotlin/moe/nea/firmament/gui/TickComponent.kt b/src/main/kotlin/moe/nea/firmament/gui/TickComponent.kt deleted file mode 100644 index d1879b1..0000000 --- a/src/main/kotlin/moe/nea/firmament/gui/TickComponent.kt +++ /dev/null @@ -1,18 +0,0 @@ -package moe.nea.firmament.gui - -import io.github.notenoughupdates.moulconfig.gui.GuiComponent -import io.github.notenoughupdates.moulconfig.gui.GuiImmediateContext - -class TickComponent(val onTick: Runnable) : GuiComponent() { - override fun getWidth(): Int { - return 0 - } - - override fun getHeight(): Int { - return 0 - } - - override fun render(context: GuiImmediateContext) { - onTick.run() - } -} diff --git a/src/main/kotlin/moe/nea/firmament/gui/config/AllConfigsGui.kt b/src/main/kotlin/moe/nea/firmament/gui/config/AllConfigsGui.kt deleted file mode 100644 index 4f7731c..0000000 --- a/src/main/kotlin/moe/nea/firmament/gui/config/AllConfigsGui.kt +++ /dev/null @@ -1,46 +0,0 @@ - - -package moe.nea.firmament.gui.config - -import io.github.notenoughupdates.moulconfig.observer.ObservableList -import io.github.notenoughupdates.moulconfig.xml.Bind -import net.minecraft.client.gui.screen.Screen -import net.minecraft.text.Text -import moe.nea.firmament.features.FeatureManager -import moe.nea.firmament.repo.RepoManager -import moe.nea.firmament.util.MC -import moe.nea.firmament.util.MoulConfigUtils -import moe.nea.firmament.util.ScreenUtil.setScreenLater - -object AllConfigsGui { - - val allConfigs - get() = listOf( - RepoManager.Config - ) + FeatureManager.allFeatures.mapNotNull { it.config } - - fun List.toObservableList(): ObservableList = ObservableList(this) - - class MainMapping(val allConfigs: List) { - @get:Bind("configs") - val configs = allConfigs.map { EntryMapping(it) }.toObservableList() - - class EntryMapping(val config: ManagedConfig) { - @Bind - fun name() = Text.translatable("firmament.config.${config.name}").string - - @Bind - fun openEditor() { - config.showConfigEditor(MC.screen) - } - } - } - - fun makeScreen(parent: Screen? = null): Screen { - return MoulConfigUtils.loadScreen("config/main", MainMapping(allConfigs), parent) - } - - fun showAllGuis() { - setScreenLater(makeScreen()) - } -} diff --git a/src/main/kotlin/moe/nea/firmament/gui/config/BooleanHandler.kt b/src/main/kotlin/moe/nea/firmament/gui/config/BooleanHandler.kt deleted file mode 100644 index 8592777..0000000 --- a/src/main/kotlin/moe/nea/firmament/gui/config/BooleanHandler.kt +++ /dev/null @@ -1,37 +0,0 @@ - - -package moe.nea.firmament.gui.config - -import io.github.notenoughupdates.moulconfig.gui.component.CenterComponent -import io.github.notenoughupdates.moulconfig.gui.component.SwitchComponent -import io.github.notenoughupdates.moulconfig.observer.GetSetter -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.JsonPrimitive -import kotlinx.serialization.json.boolean -import kotlinx.serialization.json.jsonPrimitive - -class BooleanHandler(val config: ManagedConfig) : ManagedConfig.OptionHandler { - override fun toJson(element: Boolean): JsonElement? { - return JsonPrimitive(element) - } - - override fun fromJson(element: JsonElement): Boolean { - return element.jsonPrimitive.boolean - } - - override fun emitGuiElements(opt: ManagedOption, guiAppender: GuiAppender) { - guiAppender.appendLabeledRow( - opt.labelText, - CenterComponent(SwitchComponent(object : GetSetter { - override fun get(): Boolean { - return opt.get() - } - - override fun set(newValue: Boolean) { - opt.set(newValue) - config.save() - } - }, 200) - )) - } -} diff --git a/src/main/kotlin/moe/nea/firmament/gui/config/ClickHandler.kt b/src/main/kotlin/moe/nea/firmament/gui/config/ClickHandler.kt deleted file mode 100644 index fa1c621..0000000 --- a/src/main/kotlin/moe/nea/firmament/gui/config/ClickHandler.kt +++ /dev/null @@ -1,24 +0,0 @@ - - -package moe.nea.firmament.gui.config - -import io.github.notenoughupdates.moulconfig.gui.component.TextComponent -import kotlinx.serialization.json.JsonElement -import moe.nea.firmament.gui.FirmButtonComponent - -class ClickHandler(val config: ManagedConfig, val runnable: () -> Unit) : ManagedConfig.OptionHandler { - override fun toJson(element: Unit): JsonElement? { - return null - } - - override fun fromJson(element: JsonElement) {} - - override fun emitGuiElements(opt: ManagedOption, guiAppender: GuiAppender) { - guiAppender.appendLabeledRow( - opt.labelText, - FirmButtonComponent( - TextComponent(opt.labelText.string), - action = runnable), - ) - } -} diff --git a/src/main/kotlin/moe/nea/firmament/gui/config/DurationHandler.kt b/src/main/kotlin/moe/nea/firmament/gui/config/DurationHandler.kt deleted file mode 100644 index 8d485b1..0000000 --- a/src/main/kotlin/moe/nea/firmament/gui/config/DurationHandler.kt +++ /dev/null @@ -1,58 +0,0 @@ - - -package moe.nea.firmament.gui.config - -import io.github.notenoughupdates.moulconfig.common.IMinecraft -import io.github.notenoughupdates.moulconfig.gui.component.RowComponent -import io.github.notenoughupdates.moulconfig.gui.component.SliderComponent -import io.github.notenoughupdates.moulconfig.gui.component.TextComponent -import io.github.notenoughupdates.moulconfig.observer.GetSetter -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.JsonPrimitive -import kotlinx.serialization.json.jsonPrimitive -import kotlinx.serialization.json.long -import kotlin.time.Duration -import kotlin.time.DurationUnit -import kotlin.time.toDuration -import net.minecraft.text.Text -import moe.nea.firmament.util.FirmFormatters - -class DurationHandler(val config: ManagedConfig, val min: Duration, val max: Duration) : - ManagedConfig.OptionHandler { - override fun toJson(element: Duration): JsonElement? { - return JsonPrimitive(element.inWholeMilliseconds) - } - - override fun fromJson(element: JsonElement): Duration { - return element.jsonPrimitive.long.toDuration(DurationUnit.MILLISECONDS) - } - - override fun emitGuiElements(opt: ManagedOption, guiAppender: GuiAppender) { - guiAppender.appendLabeledRow( - opt.labelText, - RowComponent( - TextComponent(IMinecraft.instance.defaultFontRenderer, - { FirmFormatters.formatTimespan(opt.value) }, - 40, - TextComponent.TextAlignment.CENTER, - true, - false), - SliderComponent( - object : GetSetter { - override fun get(): Float { - return opt.value.toDouble(DurationUnit.SECONDS).toFloat() - } - - override fun set(newValue: Float) { - opt.value = newValue.toDouble().toDuration(DurationUnit.SECONDS) - } - }, - min.toDouble(DurationUnit.SECONDS).toFloat(), - max.toDouble(DurationUnit.SECONDS).toFloat(), - 0.1F, - 130 - ) - )) - } - -} diff --git a/src/main/kotlin/moe/nea/firmament/gui/config/GuiAppender.kt b/src/main/kotlin/moe/nea/firmament/gui/config/GuiAppender.kt deleted file mode 100644 index 329319d..0000000 --- a/src/main/kotlin/moe/nea/firmament/gui/config/GuiAppender.kt +++ /dev/null @@ -1,40 +0,0 @@ - - -package moe.nea.firmament.gui.config - -import io.github.notenoughupdates.moulconfig.gui.GuiComponent -import io.github.notenoughupdates.moulconfig.gui.component.RowComponent -import io.github.notenoughupdates.moulconfig.gui.component.TextComponent -import io.github.notenoughupdates.moulconfig.observer.GetSetter -import net.minecraft.client.gui.screen.Screen -import net.minecraft.text.Text -import moe.nea.firmament.gui.FixedComponent - -class GuiAppender(val width: Int, val screenAccessor: () -> Screen) { - val panel = mutableListOf() - internal val reloadables = mutableListOf<(() -> Unit)>() - - fun onReload(reloadable: () -> Unit) { - reloadables.add(reloadable) - } - - fun appendLabeledRow(label: Text, right: GuiComponent) { - appendSplitRow( - TextComponent(label.string), - right - ) - } - - fun appendSplitRow(left: GuiComponent, right: GuiComponent) { - // TODO: make this more dynamic - // i could just make a component that allows for using half the available size - appendFullRow(RowComponent( - FixedComponent(GetSetter.constant(width / 2), null, left), - FixedComponent(GetSetter.constant(width / 2), null, right), - )) - } - - fun appendFullRow(widget: GuiComponent) { - panel.add(widget) - } -} diff --git a/src/main/kotlin/moe/nea/firmament/gui/config/HudMetaHandler.kt b/src/main/kotlin/moe/nea/firmament/gui/config/HudMetaHandler.kt deleted file mode 100644 index 35c9d51..0000000 --- a/src/main/kotlin/moe/nea/firmament/gui/config/HudMetaHandler.kt +++ /dev/null @@ -1,39 +0,0 @@ - - -package moe.nea.firmament.gui.config - -import io.github.notenoughupdates.moulconfig.gui.component.TextComponent -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.decodeFromJsonElement -import kotlinx.serialization.json.encodeToJsonElement -import net.minecraft.text.MutableText -import net.minecraft.text.Text -import moe.nea.firmament.gui.FirmButtonComponent -import moe.nea.firmament.jarvis.JarvisIntegration -import moe.nea.firmament.util.MC - -class HudMetaHandler(val config: ManagedConfig, val label: MutableText, val width: Int, val height: Int) : - ManagedConfig.OptionHandler { - override fun toJson(element: HudMeta): JsonElement? { - return Json.encodeToJsonElement(element.position) - } - - override fun fromJson(element: JsonElement): HudMeta { - return HudMeta(Json.decodeFromJsonElement(element), label, width, height) - } - - override fun emitGuiElements(opt: ManagedOption, guiAppender: GuiAppender) { - guiAppender.appendLabeledRow( - opt.labelText, - FirmButtonComponent( - TextComponent( - Text.stringifiedTranslatable("firmament.hud.edit", label).string), - ) { - MC.screen = JarvisIntegration.jarvis.getHudEditor( - guiAppender.screenAccessor.invoke(), - listOf(opt.value) - ) - }) - } -} diff --git a/src/main/kotlin/moe/nea/firmament/gui/config/IntegerHandler.kt b/src/main/kotlin/moe/nea/firmament/gui/config/IntegerHandler.kt deleted file mode 100644 index 31ce90f..0000000 --- a/src/main/kotlin/moe/nea/firmament/gui/config/IntegerHandler.kt +++ /dev/null @@ -1,54 +0,0 @@ - - -package moe.nea.firmament.gui.config - -import io.github.notenoughupdates.moulconfig.common.IMinecraft -import io.github.notenoughupdates.moulconfig.gui.component.RowComponent -import io.github.notenoughupdates.moulconfig.gui.component.SliderComponent -import io.github.notenoughupdates.moulconfig.gui.component.TextComponent -import io.github.notenoughupdates.moulconfig.observer.GetSetter -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.JsonPrimitive -import kotlinx.serialization.json.int -import kotlinx.serialization.json.jsonPrimitive -import moe.nea.firmament.util.FirmFormatters - -class IntegerHandler(val config: ManagedConfig, val min: Int, val max: Int) : ManagedConfig.OptionHandler { - override fun toJson(element: Int): JsonElement? { - return JsonPrimitive(element) - } - - override fun fromJson(element: JsonElement): Int { - return element.jsonPrimitive.int - } - - override fun emitGuiElements(opt: ManagedOption, guiAppender: GuiAppender) { - guiAppender.appendLabeledRow( - opt.labelText, - RowComponent( - TextComponent(IMinecraft.instance.defaultFontRenderer, - { FirmFormatters.formatCommas(opt.value, 0) }, - 40, - TextComponent.TextAlignment.CENTER, - true, - false), - SliderComponent( - object : GetSetter { - override fun get(): Float { - return opt.value.toFloat() - } - - override fun set(newValue: Float) { - opt.value = newValue.toInt() - } - }, - min.toFloat(), - max.toFloat(), - 0.1F, - 130 - ) - )) - - } - -} diff --git a/src/main/kotlin/moe/nea/firmament/gui/config/JAnyHud.kt b/src/main/kotlin/moe/nea/firmament/gui/config/JAnyHud.kt deleted file mode 100644 index 35c4eb2..0000000 --- a/src/main/kotlin/moe/nea/firmament/gui/config/JAnyHud.kt +++ /dev/null @@ -1,48 +0,0 @@ - - -package moe.nea.firmament.gui.config - -import moe.nea.jarvis.api.JarvisHud -import moe.nea.jarvis.api.JarvisScalable -import kotlinx.serialization.Serializable -import net.minecraft.text.Text - -@Serializable -data class HudPosition( - var x: Double, - var y: Double, - var scale: Float, -) - - -data class HudMeta( - val position: HudPosition, - private val label: Text, - private val width: Int, - private val height: Int, -) : JarvisScalable, JarvisHud { - override fun getX(): Double = position.x - - override fun setX(newX: Double) { - position.x = newX - } - - override fun getY(): Double = position.y - - override fun setY(newY: Double) { - position.y = newY - } - - override fun getLabel(): Text = label - - override fun getWidth(): Int = width - - override fun getHeight(): Int = height - - override fun getScale(): Float = position.scale - - override fun setScale(newScale: Float) { - position.scale = newScale - } - -} diff --git a/src/main/kotlin/moe/nea/firmament/gui/config/KeyBindingHandler.kt b/src/main/kotlin/moe/nea/firmament/gui/config/KeyBindingHandler.kt deleted file mode 100644 index c389cc9..0000000 --- a/src/main/kotlin/moe/nea/firmament/gui/config/KeyBindingHandler.kt +++ /dev/null @@ -1,149 +0,0 @@ - - -package moe.nea.firmament.gui.config - -import io.github.notenoughupdates.moulconfig.common.IMinecraft -import io.github.notenoughupdates.moulconfig.common.MyResourceLocation -import io.github.notenoughupdates.moulconfig.deps.libninepatch.NinePatch -import io.github.notenoughupdates.moulconfig.gui.GuiImmediateContext -import io.github.notenoughupdates.moulconfig.gui.KeyboardEvent -import io.github.notenoughupdates.moulconfig.gui.component.TextComponent -import org.lwjgl.glfw.GLFW -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.decodeFromJsonElement -import kotlinx.serialization.json.encodeToJsonElement -import net.minecraft.text.Text -import net.minecraft.util.Formatting -import moe.nea.firmament.gui.FirmButtonComponent -import moe.nea.firmament.keybindings.FirmamentKeyBindings -import moe.nea.firmament.keybindings.SavedKeyBinding - -class KeyBindingHandler(val name: String, val managedConfig: ManagedConfig) : - ManagedConfig.OptionHandler { - - override fun initOption(opt: ManagedOption) { - FirmamentKeyBindings.registerKeyBinding(name, opt) - } - - override fun toJson(element: SavedKeyBinding): JsonElement? { - return Json.encodeToJsonElement(element) - } - - override fun fromJson(element: JsonElement): SavedKeyBinding { - return Json.decodeFromJsonElement(element) - } - - override fun emitGuiElements(opt: ManagedOption, guiAppender: GuiAppender) { - var editing = false - var lastPressed = 0 - var lastPressedNonModifier = 0 - var label: String = "" - var button: FirmButtonComponent? = null - fun updateLabel() { - var stroke = opt.value.format() - if (editing) { - stroke = Text.literal("") - val (shift, alt, ctrl) = SavedKeyBinding.getMods(SavedKeyBinding.getModInt()) - if (shift) { - stroke.append("SHIFT + ") - } - if (alt) { - stroke.append("ALT + ") - } - if (ctrl) { - stroke.append("CTRL + ") - } - stroke.append("???") - stroke.styled { it.withColor(Formatting.YELLOW) } - } - label = (stroke).string - managedConfig.save() - } - button = object : FirmButtonComponent( - TextComponent( - IMinecraft.instance.defaultFontRenderer, - { label }, - 130, - TextComponent.TextAlignment.LEFT, - false, - false - ), action = { - if (editing) { - button!!.blur() - } else { - editing = true - button!!.requestFocus() - updateLabel() - } - }) { - override fun keyboardEvent(event: KeyboardEvent, context: GuiImmediateContext): Boolean { - if (event is KeyboardEvent.KeyPressed) { - return if (event.pressed) onKeyPressed(event.keycode, SavedKeyBinding.getModInt()) - else onKeyReleased(event.keycode, SavedKeyBinding.getModInt()) - } - return super.keyboardEvent(event, context) - } - - override fun getBackground(context: GuiImmediateContext): NinePatch { - if (editing) return activeBg - return super.getBackground(context) - } - - fun onKeyPressed(ch: Int, modifiers: Int): Boolean { - if (!editing) { - return false - } - if (ch == GLFW.GLFW_KEY_ESCAPE) { - lastPressedNonModifier = 0 - editing = false - lastPressed = 0 - opt.value = SavedKeyBinding(GLFW.GLFW_KEY_UNKNOWN) - updateLabel() - blur() - return true - } - if (ch == GLFW.GLFW_KEY_LEFT_SHIFT || ch == GLFW.GLFW_KEY_RIGHT_SHIFT - || ch == GLFW.GLFW_KEY_LEFT_ALT || ch == GLFW.GLFW_KEY_RIGHT_ALT - || ch == GLFW.GLFW_KEY_LEFT_CONTROL || ch == GLFW.GLFW_KEY_RIGHT_CONTROL - ) { - lastPressed = ch - } else { - opt.value = SavedKeyBinding( - ch, modifiers - ) - editing = false - blur() - lastPressed = 0 - lastPressedNonModifier = 0 - } - updateLabel() - return true - } - - override fun onLostFocus() { - lastPressedNonModifier = 0 - editing = false - lastPressed = 0 - updateLabel() - } - - fun onKeyReleased(ch: Int, modifiers: Int): Boolean { - if (!editing) - return false - if (lastPressedNonModifier == ch || (lastPressedNonModifier == 0 && ch == lastPressed)) { - opt.value = SavedKeyBinding(ch, modifiers) - editing = false - blur() - lastPressed = 0 - lastPressedNonModifier = 0 - } - updateLabel() - return true - } - } - updateLabel() - guiAppender.appendLabeledRow(opt.labelText, button) - } - -} diff --git a/src/main/kotlin/moe/nea/firmament/gui/config/ManagedConfig.kt b/src/main/kotlin/moe/nea/firmament/gui/config/ManagedConfig.kt deleted file mode 100644 index aa6e3c8..0000000 --- a/src/main/kotlin/moe/nea/firmament/gui/config/ManagedConfig.kt +++ /dev/null @@ -1,181 +0,0 @@ - - -package moe.nea.firmament.gui.config - -import io.github.notenoughupdates.moulconfig.gui.CloseEventListener -import io.github.notenoughupdates.moulconfig.gui.GuiComponentWrapper -import io.github.notenoughupdates.moulconfig.gui.GuiContext -import io.github.notenoughupdates.moulconfig.gui.component.CenterComponent -import io.github.notenoughupdates.moulconfig.gui.component.ColumnComponent -import io.github.notenoughupdates.moulconfig.gui.component.PanelComponent -import io.github.notenoughupdates.moulconfig.gui.component.RowComponent -import io.github.notenoughupdates.moulconfig.gui.component.ScrollPanelComponent -import io.github.notenoughupdates.moulconfig.gui.component.TextComponent -import moe.nea.jarvis.api.Point -import org.lwjgl.glfw.GLFW -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.JsonObject -import kotlin.io.path.createDirectories -import kotlin.io.path.readText -import kotlin.io.path.writeText -import kotlin.time.Duration -import net.minecraft.client.gui.screen.Screen -import net.minecraft.text.Text -import moe.nea.firmament.Firmament -import moe.nea.firmament.gui.FirmButtonComponent -import moe.nea.firmament.keybindings.SavedKeyBinding -import moe.nea.firmament.util.ScreenUtil.setScreenLater - -abstract class ManagedConfig(override val name: String) : ManagedConfigElement() { - - interface OptionHandler { - fun initOption(opt: ManagedOption) {} - fun toJson(element: T): JsonElement? - fun fromJson(element: JsonElement): T - fun emitGuiElements(opt: ManagedOption, guiAppender: GuiAppender) - } - - val file = Firmament.CONFIG_DIR.resolve("$name.json") - val data: JsonObject by lazy { - try { - Firmament.json.decodeFromString( - file.readText() - ) - } catch (e: Exception) { - Firmament.logger.info("Could not read config $name. Loading empty config.") - JsonObject(mutableMapOf()) - } - } - - fun save() { - val data = JsonObject(allOptions.mapNotNull { (key, value) -> - value.toJson()?.let { - key to it - } - }.toMap()) - file.parent.createDirectories() - file.writeText(Firmament.json.encodeToString(data)) - } - - - val allOptions = mutableMapOf>() - val sortedOptions = mutableListOf>() - - private var latestGuiAppender: GuiAppender? = null - - protected fun option( - propertyName: String, - default: () -> T, - handler: OptionHandler - ): ManagedOption { - if (propertyName in allOptions) error("Cannot register the same name twice") - return ManagedOption(this, propertyName, default, handler).also { - it.handler.initOption(it) - it.load(data) - allOptions[propertyName] = it - sortedOptions.add(it) - } - } - - protected fun toggle(propertyName: String, default: () -> Boolean): ManagedOption { - return option(propertyName, default, BooleanHandler(this)) - } - - protected fun duration( - propertyName: String, - min: Duration, - max: Duration, - default: () -> Duration, - ): ManagedOption { - return option(propertyName, default, DurationHandler(this, min, max)) - } - - - protected fun position( - propertyName: String, - width: Int, - height: Int, - default: () -> Point, - ): ManagedOption { - val label = Text.translatable("firmament.config.${name}.${propertyName}") - return option(propertyName, { - val p = default() - HudMeta(HudPosition(p.x, p.y, 1F), label, width, height) - }, HudMetaHandler(this, label, width, height)) - } - - protected fun keyBinding( - propertyName: String, - default: () -> Int, - ): ManagedOption = keyBindingWithOutDefaultModifiers(propertyName) { SavedKeyBinding(default()) } - - protected fun keyBindingWithOutDefaultModifiers( - propertyName: String, - default: () -> SavedKeyBinding, - ): ManagedOption { - return option(propertyName, default, KeyBindingHandler("firmament.config.${name}.${propertyName}", this)) - } - - protected fun keyBindingWithDefaultUnbound( - propertyName: String, - ): ManagedOption { - return keyBindingWithOutDefaultModifiers(propertyName) { SavedKeyBinding(GLFW.GLFW_KEY_UNKNOWN) } - } - - protected fun integer( - propertyName: String, - min: Int, - max: Int, - default: () -> Int, - ): ManagedOption { - return option(propertyName, default, IntegerHandler(this, min, max)) - } - - protected fun button(propertyName: String, runnable: () -> Unit): ManagedOption { - return option(propertyName, { }, ClickHandler(this, runnable)) - } - - protected fun string(propertyName: String, default: () -> String): ManagedOption { - return option(propertyName, default, StringHandler(this)) - } - - - fun reloadGui() { - latestGuiAppender?.reloadables?.forEach { it() } - } - - val labelText = Text.translatable("firmament.config.${name}") - - fun getConfigEditor(parent: Screen? = null): Screen { - var screen: Screen? = null - val guiapp = GuiAppender(400) { requireNotNull(screen) { "Screen Accessor called too early" } } - latestGuiAppender = guiapp - guiapp.appendFullRow(RowComponent( - FirmButtonComponent(TextComponent("←")) { - if (parent != null) { - save() - setScreenLater(parent) - } else { - AllConfigsGui.showAllGuis() - } - } - )) - sortedOptions.forEach { it.appendToGui(guiapp) } - guiapp.reloadables.forEach { it() } - val component = CenterComponent(PanelComponent(ScrollPanelComponent(400, 300, ColumnComponent(guiapp.panel)), 10, PanelComponent.DefaultBackgroundRenderer.VANILLA)) - screen = object : GuiComponentWrapper(GuiContext(component)) { - override fun close() { - if (context.onBeforeClose() == CloseEventListener.CloseAction.NO_OBJECTIONS_TO_CLOSE) { - client!!.setScreen(parent) - } - } - } - return screen - } - - fun showConfigEditor(parent: Screen? = null) { - setScreenLater(getConfigEditor(parent)) - } - -} diff --git a/src/main/kotlin/moe/nea/firmament/gui/config/ManagedConfigElement.kt b/src/main/kotlin/moe/nea/firmament/gui/config/ManagedConfigElement.kt deleted file mode 100644 index 28cd6b8..0000000 --- a/src/main/kotlin/moe/nea/firmament/gui/config/ManagedConfigElement.kt +++ /dev/null @@ -1,8 +0,0 @@ - - -package moe.nea.firmament.gui.config - -abstract class ManagedConfigElement { - abstract val name: String - -} diff --git a/src/main/kotlin/moe/nea/firmament/gui/config/ManagedOption.kt b/src/main/kotlin/moe/nea/firmament/gui/config/ManagedOption.kt deleted file mode 100644 index b7264e8..0000000 --- a/src/main/kotlin/moe/nea/firmament/gui/config/ManagedOption.kt +++ /dev/null @@ -1,62 +0,0 @@ - - -package moe.nea.firmament.gui.config - -import io.github.notenoughupdates.moulconfig.observer.GetSetter -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.JsonObject -import kotlin.properties.ReadWriteProperty -import kotlin.reflect.KProperty -import net.minecraft.text.Text -import moe.nea.firmament.Firmament - -class ManagedOption( - val element: ManagedConfigElement, - val propertyName: String, - val default: () -> T, - val handler: ManagedConfig.OptionHandler -) : ReadWriteProperty, GetSetter { - override fun set(newValue: T) { - this.value = newValue - } - - override fun get(): T { - return this.value - } - - val rawLabelText = "firmament.config.${element.name}.${propertyName}" - val labelText = Text.translatable(rawLabelText) - - lateinit var value: T - - override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { - this.value = value - } - - override fun getValue(thisRef: Any?, property: KProperty<*>): T { - return value - } - - fun load(root: JsonElement) { - if (root is JsonObject && root.containsKey(propertyName)) { - try { - value = handler.fromJson(root[propertyName]!!) - return - } catch (e: Exception) { - Firmament.logger.error( - "Exception during loading of config file ${element.name}. This will reset this config.", - e - ) - } - } - value = default() - } - - fun toJson(): JsonElement? { - return handler.toJson(value) - } - - fun appendToGui(guiapp: GuiAppender) { - handler.emitGuiElements(this, guiapp) - } -} diff --git a/src/main/kotlin/moe/nea/firmament/gui/config/StringHandler.kt b/src/main/kotlin/moe/nea/firmament/gui/config/StringHandler.kt deleted file mode 100644 index a326abb..0000000 --- a/src/main/kotlin/moe/nea/firmament/gui/config/StringHandler.kt +++ /dev/null @@ -1,36 +0,0 @@ - - -package moe.nea.firmament.gui.config - -import io.github.notenoughupdates.moulconfig.gui.component.TextFieldComponent -import io.github.notenoughupdates.moulconfig.observer.GetSetter -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.JsonPrimitive -import kotlinx.serialization.json.jsonPrimitive -import net.minecraft.text.Text - -class StringHandler(val config: ManagedConfig) : ManagedConfig.OptionHandler { - override fun toJson(element: String): JsonElement? { - return JsonPrimitive(element) - } - - override fun fromJson(element: JsonElement): String { - return element.jsonPrimitive.content - } - - override fun emitGuiElements(opt: ManagedOption, guiAppender: GuiAppender) { - guiAppender.appendLabeledRow( - opt.labelText, - TextFieldComponent( - object : GetSetter by opt { - override fun set(newValue: String) { - opt.set(newValue) - config.save() - } - }, - 130, - suggestion = Text.translatableWithFallback(opt.rawLabelText + ".hint", "").string - ), - ) - } -} diff --git a/src/main/kotlin/moe/nea/firmament/gui/entity/EntityModifier.kt b/src/main/kotlin/moe/nea/firmament/gui/entity/EntityModifier.kt deleted file mode 100644 index 9623070..0000000 --- a/src/main/kotlin/moe/nea/firmament/gui/entity/EntityModifier.kt +++ /dev/null @@ -1,9 +0,0 @@ - -package moe.nea.firmament.gui.entity - -import com.google.gson.JsonObject -import net.minecraft.entity.LivingEntity - -fun interface EntityModifier { - fun apply(entity: LivingEntity, info: JsonObject): LivingEntity -} diff --git a/src/main/kotlin/moe/nea/firmament/gui/entity/EntityRenderer.kt b/src/main/kotlin/moe/nea/firmament/gui/entity/EntityRenderer.kt deleted file mode 100644 index 8c7428d..0000000 --- a/src/main/kotlin/moe/nea/firmament/gui/entity/EntityRenderer.kt +++ /dev/null @@ -1,197 +0,0 @@ - -package moe.nea.firmament.gui.entity - -import com.google.gson.Gson -import com.google.gson.JsonArray -import com.google.gson.JsonObject -import org.apache.logging.log4j.LogManager -import org.joml.Quaternionf -import org.joml.Vector3f -import kotlin.math.atan -import net.minecraft.client.gui.DrawContext -import net.minecraft.client.gui.screen.ingame.InventoryScreen -import net.minecraft.entity.Entity -import net.minecraft.entity.EntityType -import net.minecraft.entity.LivingEntity -import net.minecraft.util.Identifier -import moe.nea.firmament.util.MC -import moe.nea.firmament.util.assertNotNullOr -import moe.nea.firmament.util.iterate -import moe.nea.firmament.util.openFirmamentResource -import moe.nea.firmament.util.render.enableScissorWithTranslation - -object EntityRenderer { - val fakeWorld = FakeWorld() - private fun t(entityType: EntityType): () -> T { - return { entityType.create(fakeWorld)!! } - } - - val entityIds: Map LivingEntity> = mapOf( - "Zombie" to t(EntityType.ZOMBIE), - "Chicken" to t(EntityType.CHICKEN), - "Slime" to t(EntityType.SLIME), - "Wolf" to t(EntityType.WOLF), - "Skeleton" to t(EntityType.SKELETON), - "Creeper" to t(EntityType.CREEPER), - "Ocelot" to t(EntityType.OCELOT), - "Blaze" to t(EntityType.BLAZE), - "Rabbit" to t(EntityType.RABBIT), - "Sheep" to t(EntityType.SHEEP), - "Horse" to t(EntityType.HORSE), - "Eisengolem" to t(EntityType.IRON_GOLEM), - "Silverfish" to t(EntityType.SILVERFISH), - "Witch" to t(EntityType.WITCH), - "Endermite" to t(EntityType.ENDERMITE), - "Snowman" to t(EntityType.SNOW_GOLEM), - "Villager" to t(EntityType.VILLAGER), - "Guardian" to t(EntityType.GUARDIAN), - "ArmorStand" to t(EntityType.ARMOR_STAND), - "Squid" to t(EntityType.SQUID), - "Bat" to t(EntityType.BAT), - "Spider" to t(EntityType.SPIDER), - "CaveSpider" to t(EntityType.CAVE_SPIDER), - "Pigman" to t(EntityType.ZOMBIFIED_PIGLIN), - "Ghast" to t(EntityType.GHAST), - "MagmaCube" to t(EntityType.MAGMA_CUBE), - "Wither" to t(EntityType.WITHER), - "Enderman" to t(EntityType.ENDERMAN), - "Mooshroom" to t(EntityType.MOOSHROOM), - "WitherSkeleton" to t(EntityType.WITHER_SKELETON), - "Cow" to t(EntityType.COW), - "Dragon" to t(EntityType.ENDER_DRAGON), - "Player" to { makeGuiPlayer(fakeWorld) }, - "Pig" to t(EntityType.PIG), - "Giant" to t(EntityType.GIANT), - ) - val entityModifiers: Map = mapOf( - "playerdata" to ModifyPlayerSkin, - "equipment" to ModifyEquipment, - "riding" to ModifyRiding, - "charged" to ModifyCharged, - "witherdata" to ModifyWither, - "invisible" to ModifyInvisible, - "age" to ModifyAge, - "horse" to ModifyHorse, - "name" to ModifyName, - ) - - val logger = LogManager.getLogger("Firmament.Entity") - fun applyModifiers(entityId: String, modifiers: List): LivingEntity? { - val entityType = assertNotNullOr(entityIds[entityId]) { - logger.error("Could not create entity with id $entityId") - return null - } - var entity = entityType() - for (modifierJson in modifiers) { - val modifier = assertNotNullOr(modifierJson["type"]?.asString?.let(entityModifiers::get)) { - logger.error("Unknown modifier $modifierJson") - return null - } - entity = modifier.apply(entity, modifierJson) - } - return entity - } - - fun constructEntity(info: JsonObject): LivingEntity? { - val modifiers = (info["modifiers"] as JsonArray?)?.map { it.asJsonObject } ?: emptyList() - val entityType = assertNotNullOr(info["entity"]?.asString) { - logger.error("Missing entity type on entity object") - return null - } - return applyModifiers(entityType, modifiers) - } - - private val gson = Gson() - fun constructEntity(location: Identifier): LivingEntity? { - return constructEntity( - gson.fromJson( - location.openFirmamentResource().bufferedReader(), JsonObject::class.java - ) - ) - } - - fun renderEntity( - entity: LivingEntity, - renderContext: DrawContext, - posX: Int, - posY: Int, - mouseX: Float, - mouseY: Float - ) { - var bottomOffset = 0.0F - var currentEntity = entity - val maxSize = entity.iterate { it.firstPassenger as? LivingEntity } - .map { it.height } - .sum() - while (true) { - currentEntity.age = MC.player?.age ?: 0 - drawEntity( - renderContext, - posX, - posY, - posX + 50, - posY + 80, - minOf(2F / maxSize, 1F) * 30, - -bottomOffset, - mouseX, - mouseY, - currentEntity - ) - val next = currentEntity.firstPassenger as? LivingEntity ?: break - bottomOffset += currentEntity.getPassengerRidingPos(next).y.toFloat() * 0.75F - currentEntity = next - } - } - - - fun drawEntity( - context: DrawContext, - x1: Int, - y1: Int, - x2: Int, - y2: Int, - size: Float, - bottomOffset: Float, - mouseX: Float, - mouseY: Float, - entity: LivingEntity - ) { - context.enableScissorWithTranslation(x1.toFloat(), y1.toFloat(), x2.toFloat(), y2.toFloat()) - val centerX = (x1 + x2) / 2f - val centerY = (y1 + y2) / 2f - val targetYaw = atan(((centerX - mouseX) / 40.0f).toDouble()).toFloat() - val targetPitch = atan(((centerY - mouseY) / 40.0f).toDouble()).toFloat() - val rotateToFaceTheFront = Quaternionf().rotateZ(Math.PI.toFloat()) - val rotateToFaceTheCamera = Quaternionf().rotateX(targetPitch * 20.0f * (Math.PI.toFloat() / 180)) - rotateToFaceTheFront.mul(rotateToFaceTheCamera) - val oldBodyYaw = entity.bodyYaw - val oldYaw = entity.yaw - val oldPitch = entity.pitch - val oldPrevHeadYaw = entity.prevHeadYaw - val oldHeadYaw = entity.headYaw - entity.bodyYaw = 180.0f + targetYaw * 20.0f - entity.yaw = 180.0f + targetYaw * 40.0f - entity.pitch = -targetPitch * 20.0f - entity.headYaw = entity.yaw - entity.prevHeadYaw = entity.yaw - val vector3f = Vector3f(0.0f, entity.height / 2.0f + bottomOffset, 0.0f) - InventoryScreen.drawEntity( - context, - centerX, - centerY, - size, - vector3f, - rotateToFaceTheFront, - rotateToFaceTheCamera, - entity - ) - entity.bodyYaw = oldBodyYaw - entity.yaw = oldYaw - entity.pitch = oldPitch - entity.prevHeadYaw = oldPrevHeadYaw - entity.headYaw = oldHeadYaw - context.disableScissor() - } - - -} diff --git a/src/main/kotlin/moe/nea/firmament/gui/entity/EntityWidget.kt b/src/main/kotlin/moe/nea/firmament/gui/entity/EntityWidget.kt deleted file mode 100644 index 2e49072..0000000 --- a/src/main/kotlin/moe/nea/firmament/gui/entity/EntityWidget.kt +++ /dev/null @@ -1,35 +0,0 @@ - -package moe.nea.firmament.gui.entity - -import me.shedaniel.math.Dimension -import me.shedaniel.math.Point -import me.shedaniel.math.Rectangle -import me.shedaniel.rei.api.client.gui.widgets.WidgetWithBounds -import net.minecraft.client.gui.DrawContext -import net.minecraft.client.gui.Element -import net.minecraft.entity.LivingEntity - -class EntityWidget(val entity: LivingEntity, val point: Point) : WidgetWithBounds() { - override fun children(): List { - return emptyList() - } - - var hasErrored = false - - override fun render(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) { - try { - if (!hasErrored) - EntityRenderer.renderEntity(entity, context, point.x, point.y, mouseX.toFloat(), mouseY.toFloat()) - } catch (ex: Exception) { - EntityRenderer.logger.error("Failed to render constructed entity: $entity", ex) - hasErrored = true - } - if (hasErrored) { - context.fill(point.x, point.y, point.x + 50, point.y + 80, 0xFFAA2222.toInt()) - } - } - - override fun getBounds(): Rectangle { - return Rectangle(point, Dimension(50, 80)) - } -} diff --git a/src/main/kotlin/moe/nea/firmament/gui/entity/FakeWorld.kt b/src/main/kotlin/moe/nea/firmament/gui/entity/FakeWorld.kt deleted file mode 100644 index f354d5a..0000000 --- a/src/main/kotlin/moe/nea/firmament/gui/entity/FakeWorld.kt +++ /dev/null @@ -1,488 +0,0 @@ - -package moe.nea.firmament.gui.entity - -import com.mojang.datafixers.util.Pair -import com.mojang.serialization.Lifecycle -import java.util.* -import java.util.function.BooleanSupplier -import java.util.function.Consumer -import java.util.stream.Stream -import kotlin.jvm.optionals.getOrNull -import kotlin.streams.asSequence -import net.minecraft.block.Block -import net.minecraft.block.BlockState -import net.minecraft.component.type.MapIdComponent -import net.minecraft.entity.Entity -import net.minecraft.entity.player.PlayerEntity -import net.minecraft.fluid.Fluid -import net.minecraft.item.map.MapState -import net.minecraft.recipe.BrewingRecipeRegistry -import net.minecraft.recipe.Ingredient -import net.minecraft.recipe.RecipeManager -import net.minecraft.registry.BuiltinRegistries -import net.minecraft.registry.DynamicRegistryManager -import net.minecraft.registry.Registry -import net.minecraft.registry.RegistryKey -import net.minecraft.registry.RegistryKeys -import net.minecraft.registry.RegistryWrapper -import net.minecraft.registry.entry.RegistryEntry -import net.minecraft.registry.entry.RegistryEntryInfo -import net.minecraft.registry.entry.RegistryEntryList -import net.minecraft.registry.entry.RegistryEntryOwner -import net.minecraft.registry.tag.TagKey -import net.minecraft.resource.featuretoggle.FeatureFlags -import net.minecraft.resource.featuretoggle.FeatureSet -import net.minecraft.scoreboard.Scoreboard -import net.minecraft.sound.SoundCategory -import net.minecraft.sound.SoundEvent -import net.minecraft.util.Identifier -import net.minecraft.util.TypeFilter -import net.minecraft.util.function.LazyIterationConsumer -import net.minecraft.util.math.BlockPos -import net.minecraft.util.math.Box -import net.minecraft.util.math.ChunkPos -import net.minecraft.util.math.Direction -import net.minecraft.util.math.Vec3d -import net.minecraft.util.math.random.Random -import net.minecraft.util.profiler.DummyProfiler -import net.minecraft.world.BlockView -import net.minecraft.world.Difficulty -import net.minecraft.world.GameRules -import net.minecraft.world.MutableWorldProperties -import net.minecraft.world.World -import net.minecraft.world.biome.Biome -import net.minecraft.world.biome.BiomeKeys -import net.minecraft.world.chunk.Chunk -import net.minecraft.world.chunk.ChunkManager -import net.minecraft.world.chunk.ChunkStatus -import net.minecraft.world.chunk.EmptyChunk -import net.minecraft.world.chunk.light.LightingProvider -import net.minecraft.world.entity.EntityLookup -import net.minecraft.world.event.GameEvent -import net.minecraft.world.tick.OrderedTick -import net.minecraft.world.tick.QueryableTickScheduler -import net.minecraft.world.tick.TickManager - -fun makeRegistry(registryWrapper: RegistryWrapper.Impl, key: RegistryKey>): Registry { - val inverseLookup = registryWrapper.streamEntries() - .asSequence().map { it.value() to it.registryKey() } - .toMap() - val idLookup = registryWrapper.streamEntries() - .asSequence() - .map { it.registryKey() } - .withIndex() - .associate { it.value to it.index } - val map = registryWrapper.streamEntries().asSequence().map { it.registryKey() to it.value() }.toMap(mutableMapOf()) - val inverseIdLookup = idLookup.asIterable().associate { (k, v) -> v to k } - return object : Registry { - override fun get(key: RegistryKey?): T? { - return registryWrapper.getOptional(key).getOrNull()?.value() - } - - override fun iterator(): MutableIterator { - return object : MutableIterator { - val iterator = registryWrapper.streamEntries().iterator() - override fun hasNext(): Boolean { - return iterator.hasNext() - } - - override fun next(): T { - return iterator.next().value() - } - - override fun remove() { - TODO("Not yet implemented") - } - } - } - - override fun getRawId(value: T?): Int { - return idLookup[inverseLookup[value ?: return -1] ?: return -1] ?: return -1 - } - - override fun get(id: Identifier?): T? { - return get(RegistryKey.of(key, id)) - } - - override fun get(index: Int): T? { - return get(inverseIdLookup[index] ?: return null) - } - - override fun size(): Int { - return idLookup.size - } - - override fun getKey(): RegistryKey> { - return key - } - - override fun getEntryInfo(key: RegistryKey?): Optional { - TODO("Not yet implemented") - } - - override fun getLifecycle(): Lifecycle { - return Lifecycle.stable() - } - - override fun getDefaultEntry(): Optional> { - return Optional.empty() - } - - override fun getIds(): MutableSet { - return idLookup.keys.mapTo(mutableSetOf()) { it.value } - } - - override fun getEntrySet(): MutableSet, T>> { - return map.entries - } - - override fun getKeys(): MutableSet> { - return map.keys - } - - override fun getRandom(random: Random?): Optional> { - return registryWrapper.streamEntries().findFirst() - } - - override fun containsId(id: Identifier?): Boolean { - return idLookup.containsKey(RegistryKey.of(key, id ?: return false)) - } - - override fun freeze(): Registry { - return this - } - - override fun getEntry(rawId: Int): Optional> { - val x = inverseIdLookup[rawId] ?: return Optional.empty() - return Optional.of(RegistryEntry.Reference.standAlone(registryWrapper, x)) - } - - override fun streamEntries(): Stream> { - return registryWrapper.streamEntries() - } - - override fun streamTagsAndEntries(): Stream, RegistryEntryList.Named>> { - return streamTags().map { Pair(it, getOrCreateEntryList(it)) } - } - - override fun streamTags(): Stream> { - return registryWrapper.streamTagKeys() - } - - override fun clearTags() { - } - - override fun getEntryOwner(): RegistryEntryOwner { - return registryWrapper - } - - override fun getReadOnlyWrapper(): RegistryWrapper.Impl { - return registryWrapper - } - - override fun populateTags(tagEntries: MutableMap, MutableList>>?) { - } - - override fun getOrCreateEntryList(tag: TagKey?): RegistryEntryList.Named { - return getEntryList(tag).orElseGet { RegistryEntryList.of(registryWrapper, tag) } - } - - override fun getEntryList(tag: TagKey?): Optional> { - return registryWrapper.getOptional(tag ?: return Optional.empty()) - } - - override fun getEntry(value: T): RegistryEntry { - return registryWrapper.getOptional(inverseLookup[value]!!).get() - } - - override fun getEntry(key: RegistryKey?): Optional> { - return registryWrapper.getOptional(key ?: return Optional.empty()) - } - - override fun getEntry(id: Identifier?): Optional> { - TODO("Not yet implemented") - } - - override fun createEntry(value: T): RegistryEntry.Reference { - TODO("Not yet implemented") - } - - override fun contains(key: RegistryKey?): Boolean { - return getEntry(key).isPresent - } - - override fun getId(value: T): Identifier? { - return (inverseLookup[value] ?: return null).value - } - - override fun getKey(entry: T): Optional> { - return Optional.ofNullable(inverseLookup[entry ?: return Optional.empty()]) - } - } -} - -fun createDynamicRegistry(): DynamicRegistryManager.Immutable { - val wrapperLookup = BuiltinRegistries.createWrapperLookup() - return object : DynamicRegistryManager.Immutable { - override fun getOptional(key: RegistryKey>): Optional> { - val lookup = wrapperLookup.getOptionalWrapper(key).getOrNull() ?: return Optional.empty() - val registry = makeRegistry(lookup, key as RegistryKey>) - return Optional.of(registry) - } - - fun entry(reg: RegistryKey>): DynamicRegistryManager.Entry { - return DynamicRegistryManager.Entry(reg, getOptional(reg).get()) - } - - override fun streamAllRegistries(): Stream> { - return wrapperLookup.streamAllRegistryKeys() - .map { entry(it as RegistryKey>) } - } - } -} - -class FakeWorld( - registries: DynamicRegistryManager.Immutable = createDynamicRegistry(), -) : World( - Properties, - RegistryKey.of(RegistryKeys.WORLD, Identifier.of("firmament", "fakeworld")), - registries, - registries[RegistryKeys.DIMENSION_TYPE].entryOf( - RegistryKey.of( - RegistryKeys.DIMENSION_TYPE, - Identifier.of("minecraft", "overworld") - ) - ), - { DummyProfiler.INSTANCE }, - true, - false, - 0, 0 -) { - object Properties : MutableWorldProperties { - override fun getSpawnPos(): BlockPos { - return BlockPos.ORIGIN - } - - override fun getSpawnAngle(): Float { - return 0F - } - - override fun getTime(): Long { - return 0 - } - - override fun getTimeOfDay(): Long { - return 0 - } - - override fun isThundering(): Boolean { - return false - } - - override fun isRaining(): Boolean { - return false - } - - override fun setRaining(raining: Boolean) { - } - - override fun isHardcore(): Boolean { - return false - } - - override fun getGameRules(): GameRules { - return GameRules() - } - - override fun getDifficulty(): Difficulty { - return Difficulty.HARD - } - - override fun isDifficultyLocked(): Boolean { - return false - } - - override fun setSpawnPos(pos: BlockPos?, angle: Float) {} - } - - override fun getPlayers(): List { - return emptyList() - } - - override fun getBrightness(direction: Direction?, shaded: Boolean): Float { - return 1f - } - - override fun getGeneratorStoredBiome(biomeX: Int, biomeY: Int, biomeZ: Int): RegistryEntry { - return registryManager.get(RegistryKeys.BIOME).entryOf(BiomeKeys.PLAINS) - } - - override fun getEnabledFeatures(): FeatureSet { - return FeatureFlags.VANILLA_FEATURES - } - - class FakeTickScheduler : QueryableTickScheduler { - override fun scheduleTick(orderedTick: OrderedTick?) { - } - - override fun isQueued(pos: BlockPos?, type: T): Boolean { - return true - } - - override fun getTickCount(): Int { - return 0 - } - - override fun isTicking(pos: BlockPos?, type: T): Boolean { - return true - } - - } - - override fun getBlockTickScheduler(): QueryableTickScheduler { - return FakeTickScheduler() - } - - override fun getFluidTickScheduler(): QueryableTickScheduler { - return FakeTickScheduler() - } - - - class FakeChunkManager(val world: FakeWorld) : ChunkManager() { - override fun getChunk(x: Int, z: Int, leastStatus: ChunkStatus?, create: Boolean): Chunk { - return EmptyChunk( - world, - ChunkPos(x, z), - world.registryManager.get(RegistryKeys.BIOME).entryOf(BiomeKeys.PLAINS) - ) - } - - override fun getWorld(): BlockView { - return world - } - - override fun tick(shouldKeepTicking: BooleanSupplier?, tickChunks: Boolean) { - } - - override fun getDebugString(): String { - return "FakeChunkManager" - } - - override fun getLoadedChunkCount(): Int { - return 0 - } - - override fun getLightingProvider(): LightingProvider { - return FakeLightingProvider(this) - } - } - - class FakeLightingProvider(chunkManager: FakeChunkManager) : LightingProvider(chunkManager, false, false) - - override fun getChunkManager(): ChunkManager { - return FakeChunkManager(this) - } - - override fun playSound( - source: PlayerEntity?, - x: Double, - y: Double, - z: Double, - sound: RegistryEntry?, - category: SoundCategory?, - volume: Float, - pitch: Float, - seed: Long - ) { - } - - override fun syncWorldEvent(player: PlayerEntity?, eventId: Int, pos: BlockPos?, data: Int) { - } - - override fun emitGameEvent(event: RegistryEntry?, emitterPos: Vec3d?, emitter: GameEvent.Emitter?) { - } - - override fun updateListeners(pos: BlockPos?, oldState: BlockState?, newState: BlockState?, flags: Int) { - } - - override fun playSoundFromEntity( - source: PlayerEntity?, - entity: Entity?, - sound: RegistryEntry?, - category: SoundCategory?, - volume: Float, - pitch: Float, - seed: Long - ) { - } - - override fun asString(): String { - return "FakeWorld" - } - - override fun getEntityById(id: Int): Entity? { - return null - } - - override fun getTickManager(): TickManager { - return TickManager() - } - - override fun getMapState(id: MapIdComponent?): MapState? { - return null - } - - override fun putMapState(id: MapIdComponent?, state: MapState?) { - } - - override fun increaseAndGetMapId(): MapIdComponent { - return MapIdComponent(0) - } - - override fun setBlockBreakingInfo(entityId: Int, pos: BlockPos?, progress: Int) { - } - - override fun getScoreboard(): Scoreboard { - return Scoreboard() - } - - override fun getRecipeManager(): RecipeManager { - return RecipeManager(registryManager) - } - - object FakeEntityLookup : EntityLookup { - override fun get(id: Int): Entity? { - return null - } - - override fun get(uuid: UUID?): Entity? { - return null - } - - override fun iterate(): MutableIterable { - return mutableListOf() - } - - override fun forEachIntersects( - filter: TypeFilter?, - box: Box?, - consumer: LazyIterationConsumer? - ) { - } - - override fun forEachIntersects(box: Box?, action: Consumer?) { - } - - override fun forEach(filter: TypeFilter?, consumer: LazyIterationConsumer?) { - } - - } - - override fun getEntityLookup(): EntityLookup { - return FakeEntityLookup - } - - override fun getBrewingRecipeRegistry(): BrewingRecipeRegistry { - return BrewingRecipeRegistry.EMPTY - } -} diff --git a/src/main/kotlin/moe/nea/firmament/gui/entity/GuiPlayer.kt b/src/main/kotlin/moe/nea/firmament/gui/entity/GuiPlayer.kt deleted file mode 100644 index d00b44d..0000000 --- a/src/main/kotlin/moe/nea/firmament/gui/entity/GuiPlayer.kt +++ /dev/null @@ -1,54 +0,0 @@ - -package moe.nea.firmament.gui.entity - -import com.mojang.authlib.GameProfile -import java.util.* -import net.minecraft.client.network.AbstractClientPlayerEntity -import net.minecraft.client.util.DefaultSkinHelper -import net.minecraft.client.util.SkinTextures -import net.minecraft.client.util.SkinTextures.Model -import net.minecraft.client.world.ClientWorld -import net.minecraft.util.Identifier -import net.minecraft.util.math.BlockPos -import net.minecraft.world.World - -/** - * @see moe.nea.firmament.init.EarlyRiser - */ -fun makeGuiPlayer(world: FakeWorld): GuiPlayer { - val constructor = GuiPlayer::class.java.getDeclaredConstructor( - World::class.java, - BlockPos::class.java, - Float::class.javaPrimitiveType, - GameProfile::class.java - ) - return constructor.newInstance(world, BlockPos.ORIGIN, 0F, GameProfile(UUID.randomUUID(), "Linnea")) -} - -class GuiPlayer(world: ClientWorld?, profile: GameProfile?) : AbstractClientPlayerEntity(world, profile) { - override fun isSpectator(): Boolean { - return false - } - - override fun isCreative(): Boolean { - return false - } - - override fun shouldRenderName(): Boolean { - return false - } - - var skinTexture: Identifier = DefaultSkinHelper.getSkinTextures(this.getUuid()).texture - var capeTexture: Identifier? = null - var model: Model = Model.WIDE - override fun getSkinTextures(): SkinTextures { - return SkinTextures( - skinTexture, - null, - capeTexture, - null, - model, - true - ) - } -} diff --git a/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyAge.kt b/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyAge.kt deleted file mode 100644 index a65c368..0000000 --- a/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyAge.kt +++ /dev/null @@ -1,25 +0,0 @@ - -package moe.nea.firmament.gui.entity - -import com.google.gson.JsonObject -import net.minecraft.entity.LivingEntity -import net.minecraft.entity.decoration.ArmorStandEntity -import net.minecraft.entity.mob.ZombieEntity -import net.minecraft.entity.passive.PassiveEntity - -object ModifyAge : EntityModifier { - override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity { - val isBaby = info["baby"]?.asBoolean ?: false - if (entity is PassiveEntity) { - entity.breedingAge = if (isBaby) -1 else 1 - } else if (entity is ZombieEntity) { - entity.isBaby = isBaby - } else if (entity is ArmorStandEntity) { - entity.isSmall = isBaby - } else { - error("Cannot set age for $entity") - } - return entity - } - -} diff --git a/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyCharged.kt b/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyCharged.kt deleted file mode 100644 index d22f6e3..0000000 --- a/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyCharged.kt +++ /dev/null @@ -1,14 +0,0 @@ - -package moe.nea.firmament.gui.entity - -import com.google.gson.JsonObject -import net.minecraft.entity.LivingEntity -import net.minecraft.entity.mob.CreeperEntity - -object ModifyCharged : EntityModifier { - override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity { - require(entity is CreeperEntity) - entity.dataTracker.set(CreeperEntity.CHARGED, true) - return entity - } -} diff --git a/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyEquipment.kt b/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyEquipment.kt deleted file mode 100644 index 73e450e..0000000 --- a/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyEquipment.kt +++ /dev/null @@ -1,55 +0,0 @@ - -package moe.nea.firmament.gui.entity - -import com.google.gson.JsonObject -import net.minecraft.component.DataComponentTypes -import net.minecraft.component.type.DyedColorComponent -import net.minecraft.entity.EquipmentSlot -import net.minecraft.entity.LivingEntity -import net.minecraft.item.ArmorItem -import net.minecraft.item.Item -import net.minecraft.item.ItemStack -import net.minecraft.item.Items -import moe.nea.firmament.rei.SBItemStack -import moe.nea.firmament.util.SkyblockId -import moe.nea.firmament.util.item.setEncodedSkullOwner -import moe.nea.firmament.util.item.zeroUUID - -object ModifyEquipment : EntityModifier { - val names = mapOf( - "hand" to EquipmentSlot.MAINHAND, - "helmet" to EquipmentSlot.HEAD, - "chestplate" to EquipmentSlot.CHEST, - "leggings" to EquipmentSlot.LEGS, - "feet" to EquipmentSlot.FEET, - ) - - override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity { - names.forEach { (key, slot) -> - info[key]?.let { - entity.equipStack(slot, createItem(it.asString)) - } - } - return entity - } - - private fun createItem(item: String): ItemStack { - val split = item.split("#") - if (split.size != 2) return SBItemStack(SkyblockId(item)).asImmutableItemStack() - val (type, data) = split - return when (type) { - "SKULL" -> ItemStack(Items.PLAYER_HEAD).also { it.setEncodedSkullOwner(zeroUUID, data) } - "LEATHER_LEGGINGS" -> coloredLeatherArmor(Items.LEATHER_LEGGINGS, data) - "LEATHER_BOOTS" -> coloredLeatherArmor(Items.LEATHER_BOOTS, data) - "LEATHER_HELMET" -> coloredLeatherArmor(Items.LEATHER_HELMET, data) - "LEATHER_CHESTPLATE" -> coloredLeatherArmor(Items.LEATHER_CHESTPLATE, data) - else -> error("Unknown leather piece: $type") - } - } - - private fun coloredLeatherArmor(leatherArmor: Item, data: String): ItemStack { - val stack = ItemStack(leatherArmor) - stack.set(DataComponentTypes.DYED_COLOR, DyedColorComponent(data.toInt(16), false)) - return stack - } -} diff --git a/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyHorse.kt b/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyHorse.kt deleted file mode 100644 index 8ac011b..0000000 --- a/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyHorse.kt +++ /dev/null @@ -1,61 +0,0 @@ - -package moe.nea.firmament.gui.entity - -import com.google.gson.JsonNull -import com.google.gson.JsonObject -import kotlin.experimental.and -import kotlin.experimental.inv -import kotlin.experimental.or -import net.minecraft.entity.EntityType -import net.minecraft.entity.LivingEntity -import net.minecraft.entity.passive.AbstractHorseEntity -import net.minecraft.item.ItemStack -import net.minecraft.item.Items -import moe.nea.firmament.gui.entity.EntityRenderer.fakeWorld - -object ModifyHorse : EntityModifier { - override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity { - require(entity is AbstractHorseEntity) - var entity: AbstractHorseEntity = entity - info["kind"]?.let { - entity = when (it.asString) { - "skeleton" -> EntityType.SKELETON_HORSE.create(fakeWorld)!! - "zombie" -> EntityType.ZOMBIE_HORSE.create(fakeWorld)!! - "mule" -> EntityType.MULE.create(fakeWorld)!! - "donkey" -> EntityType.DONKEY.create(fakeWorld)!! - "horse" -> EntityType.HORSE.create(fakeWorld)!! - else -> error("Unknown horse kind $it") - } - } - info["armor"]?.let { - if (it is JsonNull) { - entity.setHorseArmor(ItemStack.EMPTY) - } else { - when (it.asString) { - "iron" -> entity.setHorseArmor(ItemStack(Items.IRON_HORSE_ARMOR)) - "golden" -> entity.setHorseArmor(ItemStack(Items.GOLDEN_HORSE_ARMOR)) - "diamond" -> entity.setHorseArmor(ItemStack(Items.DIAMOND_HORSE_ARMOR)) - else -> error("Unknown horse armor $it") - } - } - } - info["saddled"]?.let { - entity.setIsSaddled(it.asBoolean) - } - return entity - } - -} - -fun AbstractHorseEntity.setIsSaddled(shouldBeSaddled: Boolean) { - val oldFlag = dataTracker.get(AbstractHorseEntity.HORSE_FLAGS) - dataTracker.set( - AbstractHorseEntity.HORSE_FLAGS, - if (shouldBeSaddled) oldFlag or AbstractHorseEntity.SADDLED_FLAG.toByte() - else oldFlag and AbstractHorseEntity.SADDLED_FLAG.toByte().inv() - ) -} - -fun AbstractHorseEntity.setHorseArmor(itemStack: ItemStack) { - items.setStack(1, itemStack) -} diff --git a/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyInvisible.kt b/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyInvisible.kt deleted file mode 100644 index 8d36991..0000000 --- a/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyInvisible.kt +++ /dev/null @@ -1,13 +0,0 @@ - -package moe.nea.firmament.gui.entity - -import com.google.gson.JsonObject -import net.minecraft.entity.LivingEntity - -object ModifyInvisible : EntityModifier { - override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity { - entity.isInvisible = info.get("invisible")?.asBoolean ?: true - return entity - } - -} diff --git a/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyName.kt b/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyName.kt deleted file mode 100644 index a03da96..0000000 --- a/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyName.kt +++ /dev/null @@ -1,14 +0,0 @@ - -package moe.nea.firmament.gui.entity - -import com.google.gson.JsonObject -import net.minecraft.entity.LivingEntity -import net.minecraft.text.Text - -object ModifyName : EntityModifier { - override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity { - entity.customName = Text.literal(info.get("name").asString) - return entity - } - -} diff --git a/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyPlayerSkin.kt b/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyPlayerSkin.kt deleted file mode 100644 index 28f0070..0000000 --- a/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyPlayerSkin.kt +++ /dev/null @@ -1,47 +0,0 @@ - -package moe.nea.firmament.gui.entity - -import com.google.gson.JsonObject -import com.google.gson.JsonPrimitive -import kotlin.experimental.and -import kotlin.experimental.or -import net.minecraft.client.util.SkinTextures -import net.minecraft.entity.LivingEntity -import net.minecraft.entity.player.PlayerEntity -import net.minecraft.entity.player.PlayerModelPart -import net.minecraft.util.Identifier - -object ModifyPlayerSkin : EntityModifier { - val playerModelPartIndex = PlayerModelPart.entries.associateBy { it.getName() } - override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity { - require(entity is GuiPlayer) - info["cape"]?.let { - entity.capeTexture = Identifier.of(it.asString) - } - info["skin"]?.let { - entity.skinTexture = Identifier.of(it.asString) - } - info["slim"]?.let { - entity.model = if (it.asBoolean) SkinTextures.Model.SLIM else SkinTextures.Model.WIDE - } - info["parts"]?.let { - var trackedData = entity.dataTracker.get(PlayerEntity.PLAYER_MODEL_PARTS) - if (it is JsonPrimitive && it.isBoolean) { - trackedData = (if (it.asBoolean) -1 else 0).toByte() - } else { - val obj = it.asJsonObject - for ((k, v) in obj.entrySet()) { - val part = playerModelPartIndex[k]!! - trackedData = if (v.asBoolean) { - trackedData and (part.bitFlag.inv().toByte()) - } else { - trackedData or (part.bitFlag.toByte()) - } - } - } - entity.dataTracker.set(PlayerEntity.PLAYER_MODEL_PARTS, trackedData) - } - return entity - } - -} diff --git a/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyRiding.kt b/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyRiding.kt deleted file mode 100644 index 5c4c78d..0000000 --- a/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyRiding.kt +++ /dev/null @@ -1,15 +0,0 @@ - -package moe.nea.firmament.gui.entity - -import com.google.gson.JsonObject -import net.minecraft.entity.LivingEntity - -object ModifyRiding : EntityModifier { - override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity { - val newEntity = EntityRenderer.constructEntity(info) - require(newEntity != null) - newEntity.startRiding(entity, true) - return entity - } - -} diff --git a/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyWither.kt b/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyWither.kt deleted file mode 100644 index 6083d88..0000000 --- a/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyWither.kt +++ /dev/null @@ -1,20 +0,0 @@ - -package moe.nea.firmament.gui.entity - -import com.google.gson.JsonObject -import net.minecraft.entity.LivingEntity -import net.minecraft.entity.boss.WitherEntity - -object ModifyWither : EntityModifier { - override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity { - require(entity is WitherEntity) - info["tiny"]?.let { - entity.setInvulTimer(if (it.asBoolean) 800 else 0) - } - info["armored"]?.let { - entity.health = if (it.asBoolean) 1F else entity.maxHealth - } - return entity - } - -} diff --git a/src/main/kotlin/moe/nea/firmament/gui/hud/MoulConfigHud.kt b/src/main/kotlin/moe/nea/firmament/gui/hud/MoulConfigHud.kt deleted file mode 100644 index e77d9af..0000000 --- a/src/main/kotlin/moe/nea/firmament/gui/hud/MoulConfigHud.kt +++ /dev/null @@ -1,66 +0,0 @@ - -package moe.nea.firmament.gui.hud - -import io.github.notenoughupdates.moulconfig.gui.GuiComponentWrapper -import io.github.notenoughupdates.moulconfig.gui.GuiContext -import io.github.notenoughupdates.moulconfig.gui.component.TextComponent -import net.minecraft.resource.ResourceManager -import net.minecraft.resource.SynchronousResourceReloader -import moe.nea.firmament.events.FinalizeResourceManagerEvent -import moe.nea.firmament.events.HudRenderEvent -import moe.nea.firmament.gui.config.HudMeta -import moe.nea.firmament.util.MC -import moe.nea.firmament.util.MoulConfigUtils - -abstract class MoulConfigHud( - val name: String, - val hudMeta: HudMeta, -) { - companion object { - private val componentWrapper by lazy { - object : GuiComponentWrapper(GuiContext(TextComponent("§cERROR"))) { - init { - this.client = MC.instance - } - } - } - } - - private var fragment: GuiContext? = null - - fun forceInit() { - } - - open fun shouldRender(): Boolean { - return true - } - - init { - require(name.matches("^[a-z_/]+$".toRegex())) - HudRenderEvent.subscribe { - if (!shouldRender()) return@subscribe - val renderContext = componentWrapper.createContext(it.context) - if (fragment == null) - loadFragment() - it.context.matrices.push() - hudMeta.applyTransformations(it.context.matrices) - val renderContextTranslated = - renderContext.translated(hudMeta.absoluteX, hudMeta.absoluteY, hudMeta.width, hudMeta.height) - .scaled(hudMeta.scale) - fragment!!.root.render(renderContextTranslated) - it.context.matrices.pop() - } - FinalizeResourceManagerEvent.subscribe { - MC.resourceManager.registerReloader(object : SynchronousResourceReloader { - override fun reload(manager: ResourceManager?) { - fragment = null - } - }) - } - } - - fun loadFragment() { - fragment = MoulConfigUtils.loadGui(name, this) - } - -} diff --git a/src/main/kotlin/moe/nea/firmament/jarvis/JarvisIntegration.kt b/src/main/kotlin/moe/nea/firmament/jarvis/JarvisIntegration.kt deleted file mode 100644 index 96f47f7..0000000 --- a/src/main/kotlin/moe/nea/firmament/jarvis/JarvisIntegration.kt +++ /dev/null @@ -1,64 +0,0 @@ - - -package moe.nea.firmament.jarvis - -import moe.nea.jarvis.api.Jarvis -import moe.nea.jarvis.api.JarvisConfigOption -import moe.nea.jarvis.api.JarvisHud -import moe.nea.jarvis.api.JarvisPlugin -import net.minecraft.client.gui.screen.Screen -import net.minecraft.text.Text -import moe.nea.firmament.Firmament -import moe.nea.firmament.features.FeatureManager -import moe.nea.firmament.gui.config.HudMeta -import moe.nea.firmament.gui.config.HudMetaHandler -import moe.nea.firmament.repo.RepoManager - -class JarvisIntegration : JarvisPlugin { - override fun getModId(): String = - Firmament.MOD_ID - - companion object { - lateinit var jarvis: Jarvis - } - - override fun onInitialize(jarvis: Jarvis) { - Companion.jarvis = jarvis - } - - val configs - get() = listOf( - RepoManager.Config - ) + FeatureManager.allFeatures.mapNotNull { it.config } - - - override fun getAllHuds(): List { - return configs.flatMap { config -> - config.sortedOptions.mapNotNull { if (it.handler is HudMetaHandler) it.value as HudMeta else null } - } - } - - override fun onHudEditorClosed() { - configs.forEach { it.save() } - } - - override fun getAllConfigOptions(): List { - return configs.flatMap { config -> - config.sortedOptions.map { - object : JarvisConfigOption { - override fun title(): Text { - return it.labelText - } - - override fun description(): List { - return emptyList() - } - - override fun jumpTo(parentScreen: Screen?): Screen { - return config.getConfigEditor(parentScreen) - } - } - } - } - } -} diff --git a/src/main/kotlin/moe/nea/firmament/keybindings/FirmamentKeyBindings.kt b/src/main/kotlin/moe/nea/firmament/keybindings/FirmamentKeyBindings.kt deleted file mode 100644 index e2bed8d..0000000 --- a/src/main/kotlin/moe/nea/firmament/keybindings/FirmamentKeyBindings.kt +++ /dev/null @@ -1,26 +0,0 @@ - - -package moe.nea.firmament.keybindings - -import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper -import net.minecraft.client.option.KeyBinding -import net.minecraft.client.util.InputUtil -import moe.nea.firmament.gui.config.KeyBindingHandler -import moe.nea.firmament.gui.config.ManagedOption - -object FirmamentKeyBindings { - fun registerKeyBinding(name: String, config: ManagedOption) { - val vanillaKeyBinding = KeyBindingHelper.registerKeyBinding( - KeyBinding( - name, - InputUtil.Type.KEYSYM, - -1, - "firmament.key.category" - ) - ) - keyBindings[vanillaKeyBinding] = config - } - - val keyBindings = mutableMapOf>() - -} diff --git a/src/main/kotlin/moe/nea/firmament/keybindings/IKeyBinding.kt b/src/main/kotlin/moe/nea/firmament/keybindings/IKeyBinding.kt deleted file mode 100644 index 1975361..0000000 --- a/src/main/kotlin/moe/nea/firmament/keybindings/IKeyBinding.kt +++ /dev/null @@ -1,29 +0,0 @@ - - -package moe.nea.firmament.keybindings - -import net.minecraft.client.option.KeyBinding - -interface IKeyBinding { - fun matches(keyCode: Int, scanCode: Int, modifiers: Int): Boolean - - fun withModifiers(wantedModifiers: Int): IKeyBinding { - val old = this - return object : IKeyBinding { - override fun matches(keyCode: Int, scanCode: Int, modifiers: Int): Boolean { - return old.matches(keyCode, scanCode, modifiers) && (modifiers and wantedModifiers) == wantedModifiers - } - } - } - - companion object { - fun minecraft(keyBinding: KeyBinding) = object : IKeyBinding { - override fun matches(keyCode: Int, scanCode: Int, modifiers: Int) = - keyBinding.matchesKey(keyCode, scanCode) - } - - fun ofKeyCode(wantedKeyCode: Int) = object : IKeyBinding { - override fun matches(keyCode: Int, scanCode: Int, modifiers: Int): Boolean = keyCode == wantedKeyCode - } - } -} diff --git a/src/main/kotlin/moe/nea/firmament/keybindings/SavedKeyBinding.kt b/src/main/kotlin/moe/nea/firmament/keybindings/SavedKeyBinding.kt deleted file mode 100644 index 8607fd0..0000000 --- a/src/main/kotlin/moe/nea/firmament/keybindings/SavedKeyBinding.kt +++ /dev/null @@ -1,106 +0,0 @@ - - -package moe.nea.firmament.keybindings - -import org.lwjgl.glfw.GLFW -import kotlinx.serialization.Serializable -import net.minecraft.client.MinecraftClient -import net.minecraft.client.util.InputUtil -import net.minecraft.text.Text -import moe.nea.firmament.util.MC - -@Serializable -data class SavedKeyBinding( - val keyCode: Int, - val shift: Boolean = false, - val ctrl: Boolean = false, - val alt: Boolean = false, -) : IKeyBinding { - val isBound: Boolean get() = keyCode != GLFW.GLFW_KEY_UNKNOWN - - constructor(keyCode: Int, mods: Triple) : this( - keyCode, - mods.first && keyCode != GLFW.GLFW_KEY_LEFT_SHIFT && keyCode != GLFW.GLFW_KEY_RIGHT_SHIFT, - mods.second && keyCode != GLFW.GLFW_KEY_LEFT_CONTROL && keyCode != GLFW.GLFW_KEY_RIGHT_CONTROL, - mods.third && keyCode != GLFW.GLFW_KEY_LEFT_ALT && keyCode != GLFW.GLFW_KEY_RIGHT_ALT, - ) - - constructor(keyCode: Int, mods: Int) : this(keyCode, getMods(mods)) - - companion object { - fun getMods(modifiers: Int): Triple { - return Triple( - modifiers and GLFW.GLFW_MOD_SHIFT != 0, - modifiers and GLFW.GLFW_MOD_CONTROL != 0, - modifiers and GLFW.GLFW_MOD_ALT != 0 - ) - } - - fun getModInt(): Int { - val h = MC.window.handle - val ctrl = if (MinecraftClient.IS_SYSTEM_MAC) { - InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_LEFT_SUPER) - || InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_RIGHT_SUPER) - } else InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_LEFT_CONTROL) - || InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_RIGHT_CONTROL) - val shift = isShiftDown() - val alt = InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_LEFT_ALT) - || InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_RIGHT_ALT) - var mods = 0 - if (ctrl) mods = mods or GLFW.GLFW_MOD_CONTROL - if (shift) mods = mods or GLFW.GLFW_MOD_SHIFT - if (alt) mods = mods or GLFW.GLFW_MOD_ALT - return mods - } - - private val h get() = MC.window.handle - fun isShiftDown() = InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_LEFT_SHIFT) - || InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_RIGHT_SHIFT) - - } - - fun isPressed(atLeast: Boolean = false): Boolean { - if (!isBound) return false - val h = MC.window.handle - if (!InputUtil.isKeyPressed(h, keyCode)) return false - - val ctrl = if (MinecraftClient.IS_SYSTEM_MAC) { - InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_LEFT_SUPER) - || InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_RIGHT_SUPER) - } else InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_LEFT_CONTROL) - || InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_RIGHT_CONTROL) - val shift = isShiftDown() - val alt = InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_LEFT_ALT) - || InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_RIGHT_ALT) - if (atLeast) - return (ctrl >= this.ctrl) && - (alt >= this.alt) && - (shift >= this.shift) - - return (ctrl == this.ctrl) && - (alt == this.alt) && - (shift == this.shift) - } - - override fun matches(keyCode: Int, scanCode: Int, modifiers: Int): Boolean { - if (this.keyCode == GLFW.GLFW_KEY_UNKNOWN) return false - return keyCode == this.keyCode && getMods(modifiers) == Triple(shift, ctrl, alt) - } - - fun format(): Text { - val stroke = Text.literal("") - if (ctrl) { - stroke.append("CTRL + ") - } - if (alt) { - stroke.append("ALT + ") - } - if (shift) { - stroke.append("SHIFT + ") // TODO: translations? - } - - stroke.append(InputUtil.Type.KEYSYM.createFromCode(keyCode).localizedText) - return stroke - } - -} diff --git a/src/main/kotlin/moe/nea/firmament/modmenu/FirmamentModMenuPlugin.kt b/src/main/kotlin/moe/nea/firmament/modmenu/FirmamentModMenuPlugin.kt deleted file mode 100644 index f889bf3..0000000 --- a/src/main/kotlin/moe/nea/firmament/modmenu/FirmamentModMenuPlugin.kt +++ /dev/null @@ -1,14 +0,0 @@ - - -package moe.nea.firmament.modmenu - -import com.terraformersmc.modmenu.api.ConfigScreenFactory -import com.terraformersmc.modmenu.api.ModMenuApi -import moe.nea.firmament.gui.config.AllConfigsGui - -class FirmamentModMenuPlugin : ModMenuApi { - override fun getModConfigScreenFactory(): ConfigScreenFactory<*> { - return ConfigScreenFactory { AllConfigsGui.makeScreen(it) } - } -} - diff --git a/src/main/kotlin/moe/nea/firmament/rei/FirmamentReiPlugin.kt b/src/main/kotlin/moe/nea/firmament/rei/FirmamentReiPlugin.kt deleted file mode 100644 index b585336..0000000 --- a/src/main/kotlin/moe/nea/firmament/rei/FirmamentReiPlugin.kt +++ /dev/null @@ -1,128 +0,0 @@ - - -package moe.nea.firmament.rei - -import me.shedaniel.rei.api.client.plugins.REIClientPlugin -import me.shedaniel.rei.api.client.registry.category.CategoryRegistry -import me.shedaniel.rei.api.client.registry.display.DisplayRegistry -import me.shedaniel.rei.api.client.registry.entry.CollapsibleEntryRegistry -import me.shedaniel.rei.api.client.registry.entry.EntryRegistry -import me.shedaniel.rei.api.client.registry.screen.ExclusionZones -import me.shedaniel.rei.api.client.registry.screen.OverlayDecider -import me.shedaniel.rei.api.client.registry.screen.ScreenRegistry -import me.shedaniel.rei.api.client.registry.transfer.TransferHandler -import me.shedaniel.rei.api.client.registry.transfer.TransferHandlerRegistry -import me.shedaniel.rei.api.common.entry.EntryStack -import me.shedaniel.rei.api.common.entry.type.EntryTypeRegistry -import me.shedaniel.rei.api.common.entry.type.VanillaEntryTypes -import net.minecraft.client.gui.screen.Screen -import net.minecraft.client.gui.screen.ingame.GenericContainerScreen -import net.minecraft.client.gui.screen.ingame.HandledScreen -import net.minecraft.item.ItemStack -import net.minecraft.text.Text -import net.minecraft.util.ActionResult -import net.minecraft.util.Identifier -import moe.nea.firmament.events.HandledScreenPushREIEvent -import moe.nea.firmament.features.inventory.CraftingOverlay -import moe.nea.firmament.features.inventory.storageoverlay.StorageOverlayScreen -import moe.nea.firmament.rei.recipes.SBCraftingRecipe -import moe.nea.firmament.rei.recipes.SBEssenceUpgradeRecipe -import moe.nea.firmament.rei.recipes.SBForgeRecipe -import moe.nea.firmament.rei.recipes.SBKatRecipe -import moe.nea.firmament.rei.recipes.SBMobDropRecipe -import moe.nea.firmament.repo.RepoManager -import moe.nea.firmament.util.SkyblockId -import moe.nea.firmament.util.skyblockId -import moe.nea.firmament.util.unformattedString - - -class FirmamentReiPlugin : REIClientPlugin { - - companion object { - fun EntryStack.asItemEntry(): EntryStack { - return EntryStack.of(VanillaEntryTypes.ITEM, value.asImmutableItemStack()) - } - - val SKYBLOCK_ITEM_TYPE_ID = Identifier.of("firmament", "skyblockitems") - } - - override fun registerTransferHandlers(registry: TransferHandlerRegistry) { - registry.register(TransferHandler { context -> - val screen = context.containerScreen - val display = context.display - if (display !is SBCraftingRecipe || screen !is GenericContainerScreen || screen.title?.unformattedString != "Craft Item") { - return@TransferHandler TransferHandler.Result.createNotApplicable() - } - if (context.isActuallyCrafting) - CraftingOverlay.setOverlay(screen, display) - return@TransferHandler TransferHandler.Result.createSuccessful().blocksFurtherHandling(true) - }) - } - - override fun registerEntryTypes(registry: EntryTypeRegistry) { - registry.register(SKYBLOCK_ITEM_TYPE_ID, SBItemEntryDefinition) - } - - override fun registerCategories(registry: CategoryRegistry) { - registry.add(SBCraftingRecipe.Category) - registry.add(SBForgeRecipe.Category) - registry.add(SBMobDropRecipe.Category) - registry.add(SBKatRecipe.Category) - registry.add(SBEssenceUpgradeRecipe.Category) - } - - override fun registerExclusionZones(zones: ExclusionZones) { - zones.register(HandledScreen::class.java) { HandledScreenPushREIEvent.publish(HandledScreenPushREIEvent(it)).rectangles } - zones.register(StorageOverlayScreen::class.java) { it.getBounds() } - } - - override fun registerDisplays(registry: DisplayRegistry) { - registry.registerDisplayGenerator( - SBCraftingRecipe.Category.catIdentifier, - SkyblockCraftingRecipeDynamicGenerator) - registry.registerDisplayGenerator( - SBForgeRecipe.Category.categoryIdentifier, - SkyblockForgeRecipeDynamicGenerator) - registry.registerDisplayGenerator( - SBMobDropRecipe.Category.categoryIdentifier, - SkyblockMobDropRecipeDynamicGenerator) - registry.registerDisplayGenerator( - SBKatRecipe.Category.categoryIdentifier, - SkyblockKatRecipeDynamicGenerator) - registry.registerDisplayGenerator( - SBEssenceUpgradeRecipe.Category.categoryIdentifier, - SkyblockEssenceRecipeDynamicGenerator - ) - } - - override fun registerCollapsibleEntries(registry: CollapsibleEntryRegistry) { - if (!RepoManager.Config.disableItemGroups) - RepoManager.neuRepo.constants.parents.parents - .forEach { (parent, children) -> - registry.group( - SkyblockId(parent).identifier, - Text.literal(RepoManager.getNEUItem(SkyblockId(parent))?.displayName ?: parent), - (children + parent).map { SBItemEntryDefinition.getEntry(SkyblockId(it)) }) - } - } - - override fun registerScreens(registry: ScreenRegistry) { - registry.registerDecider(object : OverlayDecider { - override fun isHandingScreen(screen: Class?): Boolean { - return screen == StorageOverlayScreen::class.java - } - - override fun shouldScreenBeOverlaid(screen: R): ActionResult { - return ActionResult.SUCCESS - } - }) - registry.registerFocusedStack(SkyblockItemIdFocusedStackProvider) - } - - override fun registerEntries(registry: EntryRegistry) { - registry.removeEntryIf { true } - RepoManager.neuRepo.items?.items?.values?.forEach { neuItem -> - registry.addEntry(SBItemEntryDefinition.getEntry(neuItem.skyblockId)) - } - } -} diff --git a/src/main/kotlin/moe/nea/firmament/rei/NEUItemEntryRenderer.kt b/src/main/kotlin/moe/nea/firmament/rei/NEUItemEntryRenderer.kt deleted file mode 100644 index ba99b30..0000000 --- a/src/main/kotlin/moe/nea/firmament/rei/NEUItemEntryRenderer.kt +++ /dev/null @@ -1,186 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2018-2023 shedaniel - * SPDX-FileCopyrightText: 2023 Linnea Gräf - * SPDX-FileCopyrightText: 2024 Linnea Gräf - * - * SPDX-License-Identifier: GPL-3.0-or-later - * SPDX-License-Identifier: MIT - */ - -package moe.nea.firmament.rei - -import com.mojang.blaze3d.platform.GlStateManager.DstFactor -import com.mojang.blaze3d.platform.GlStateManager.SrcFactor -import com.mojang.blaze3d.systems.RenderSystem -import me.shedaniel.math.Rectangle -import me.shedaniel.rei.api.client.entry.renderer.BatchedEntryRenderer -import me.shedaniel.rei.api.client.entry.renderer.EntryRenderer -import me.shedaniel.rei.api.client.gui.widgets.Tooltip -import me.shedaniel.rei.api.client.gui.widgets.TooltipContext -import me.shedaniel.rei.api.common.entry.EntryStack -import net.minecraft.client.MinecraftClient -import net.minecraft.client.gui.DrawContext -import net.minecraft.client.render.DiffuseLighting -import net.minecraft.client.render.LightmapTextureManager -import net.minecraft.client.render.OverlayTexture -import net.minecraft.client.render.VertexConsumerProvider -import net.minecraft.client.render.model.BakedModel -import net.minecraft.client.render.model.json.ModelTransformationMode -import net.minecraft.client.texture.SpriteAtlasTexture -import net.minecraft.item.Item -import net.minecraft.item.ItemStack -import net.minecraft.item.tooltip.TooltipType -import moe.nea.firmament.rei.FirmamentReiPlugin.Companion.asItemEntry - -object NEUItemEntryRenderer : EntryRenderer, BatchedEntryRenderer { - override fun render( - entry: EntryStack, - context: DrawContext, - bounds: Rectangle, - mouseX: Int, - mouseY: Int, - delta: Float - ) { - entry.asItemEntry().render(context, bounds, mouseX, mouseY, delta) - } - - val minecraft = MinecraftClient.getInstance() - - override fun getTooltip(entry: EntryStack, tooltipContext: TooltipContext): Tooltip? { - val stack = entry.value.asImmutableItemStack() - val lore = stack.getTooltip( - Item.TooltipContext.DEFAULT, - null, - TooltipType.BASIC - ) - return Tooltip.create(lore) - } - - override fun getExtraData(entry: EntryStack): BakedModel { - return minecraft.itemRenderer.getModel(entry.asItemEntry().value, minecraft.world, minecraft.player, 0) - } - - override fun getBatchIdentifier(entry: EntryStack?, bounds: Rectangle?, extraData: BakedModel): Int { - return 1738923 + if (extraData.isSideLit) 1 else 0 - } - - override fun startBatch( - entry: EntryStack, - model: BakedModel, - graphics: DrawContext, - delta: Float - ) { - val modelViewStack = RenderSystem.getModelViewStack() - modelViewStack.pushMatrix() - modelViewStack.scale(20.0f, 20.0f, 1.0f) - RenderSystem.applyModelViewMatrix() - setupGL(model) - } - - fun setupGL(model: BakedModel) { - minecraft.textureManager.getTexture(SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE) - .setFilter(false, false) - RenderSystem.setShaderTexture(0, SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE) - RenderSystem.enableBlend() - RenderSystem.blendFunc(SrcFactor.SRC_ALPHA, DstFactor.ONE_MINUS_SRC_ALPHA) - RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f) - val sideLit = model.isSideLit - if (!sideLit) { - DiffuseLighting.disableGuiDepthLighting() - } - } - - override fun renderBase( - entry: EntryStack, - model: BakedModel, - graphics: DrawContext, - immediate: VertexConsumerProvider.Immediate, - bounds: Rectangle, - mouseX: Int, - mouseY: Int, - delta: Float - ) { - if (entry.isEmpty) return - val value = entry.asItemEntry().value - graphics.matrices.push() - graphics.matrices.translate(bounds.centerX.toFloat() / 20.0f, bounds.centerY.toFloat() / 20.0f, 0.0f) - graphics.matrices.scale( - bounds.getWidth().toFloat() / 20.0f, - -(bounds.getWidth() + bounds.getHeight()).toFloat() / 2.0f / 20.0f, - 1.0f - ) - minecraft - .itemRenderer - .renderItem( - value, - ModelTransformationMode.GUI, - false, - graphics.matrices, - immediate, - LightmapTextureManager.MAX_LIGHT_COORDINATE, - OverlayTexture.DEFAULT_UV, - model - ) - graphics.matrices.pop() - - } - - override fun afterBase( - entry: EntryStack, - model: BakedModel, - graphics: DrawContext, - delta: Float - ) { - RenderSystem.getModelViewStack().popMatrix() - RenderSystem.applyModelViewMatrix() - this.endGL(model) - } - - fun endGL(model: BakedModel) { - RenderSystem.enableDepthTest() - val sideLit = model.isSideLit - if (!sideLit) { - DiffuseLighting.enableGuiDepthLighting() - } - } - - override fun renderOverlay( - entry: EntryStack, - extraData: BakedModel, - graphics: DrawContext, - immediate: VertexConsumerProvider.Immediate, - bounds: Rectangle, - mouseX: Int, - mouseY: Int, - delta: Float - ) { - val modelViewStack = RenderSystem.getModelViewStack() - modelViewStack.pushMatrix() - modelViewStack.mul(graphics.matrices.peek().positionMatrix) - modelViewStack.translate(bounds.x.toFloat(), bounds.y.toFloat(), 0.0f) - modelViewStack.scale( - bounds.width.toFloat() / 16.0f, - -(bounds.getWidth() + bounds.getHeight()).toFloat() / 2.0f / 16.0f, - 1.0f - ) - RenderSystem.applyModelViewMatrix() - renderOverlay(DrawContext(minecraft, graphics.vertexConsumers), entry.asItemEntry()) - modelViewStack.popMatrix() - RenderSystem.applyModelViewMatrix() - } - - fun renderOverlay(graphics: DrawContext, entry: EntryStack) { - if (!entry.isEmpty) { - graphics.drawItemInSlot(MinecraftClient.getInstance().textRenderer, entry.value, 0, 0, null) - } - } - - override fun endBatch( - entry: EntryStack?, - extraData: BakedModel?, - graphics: DrawContext?, - delta: Float - ) { - } - -} diff --git a/src/main/kotlin/moe/nea/firmament/rei/NEUItemEntrySerializer.kt b/src/main/kotlin/moe/nea/firmament/rei/NEUItemEntrySerializer.kt deleted file mode 100644 index a35d75f..0000000 --- a/src/main/kotlin/moe/nea/firmament/rei/NEUItemEntrySerializer.kt +++ /dev/null @@ -1,29 +0,0 @@ - - -package moe.nea.firmament.rei - -import me.shedaniel.rei.api.common.entry.EntrySerializer -import me.shedaniel.rei.api.common.entry.EntryStack -import net.minecraft.nbt.NbtCompound -import moe.nea.firmament.util.SkyblockId - -object NEUItemEntrySerializer : EntrySerializer { - const val SKYBLOCK_ID_ENTRY = "SKYBLOCK_ID" - const val SKYBLOCK_ITEM_COUNT = "SKYBLOCK_ITEM_COUNT" - - override fun supportSaving(): Boolean = true - override fun supportReading(): Boolean = true - - override fun read(tag: NbtCompound): SBItemStack { - val id = SkyblockId(tag.getString(SKYBLOCK_ID_ENTRY)) - val count = if (tag.contains(SKYBLOCK_ITEM_COUNT)) tag.getInt(SKYBLOCK_ITEM_COUNT) else 1 - return SBItemStack(id, count) - } - - override fun save(entry: EntryStack, value: SBItemStack): NbtCompound { - return NbtCompound().apply { - putString(SKYBLOCK_ID_ENTRY, value.skyblockId.neuItem) - putInt(SKYBLOCK_ITEM_COUNT, value.getStackSize()) - } - } -} diff --git a/src/main/kotlin/moe/nea/firmament/rei/SBItemEntryDefinition.kt b/src/main/kotlin/moe/nea/firmament/rei/SBItemEntryDefinition.kt deleted file mode 100644 index 5c6740e..0000000 --- a/src/main/kotlin/moe/nea/firmament/rei/SBItemEntryDefinition.kt +++ /dev/null @@ -1,254 +0,0 @@ - - -package moe.nea.firmament.rei - -import io.github.moulberry.repo.constants.PetNumbers -import io.github.moulberry.repo.data.NEUIngredient -import io.github.moulberry.repo.data.NEUItem -import io.github.moulberry.repo.data.Rarity -import java.util.stream.Stream -import me.shedaniel.rei.api.client.entry.renderer.EntryRenderer -import me.shedaniel.rei.api.common.entry.EntrySerializer -import me.shedaniel.rei.api.common.entry.EntryStack -import me.shedaniel.rei.api.common.entry.comparison.ComparisonContext -import me.shedaniel.rei.api.common.entry.type.EntryDefinition -import me.shedaniel.rei.api.common.entry.type.EntryType -import me.shedaniel.rei.api.common.entry.type.VanillaEntryTypes -import net.minecraft.item.ItemStack -import net.minecraft.registry.tag.TagKey -import net.minecraft.text.Text -import net.minecraft.util.Formatting -import net.minecraft.util.Identifier -import moe.nea.firmament.rei.FirmamentReiPlugin.Companion.asItemEntry -import moe.nea.firmament.repo.ExpLadders -import moe.nea.firmament.repo.ItemCache -import moe.nea.firmament.repo.ItemCache.asItemStack -import moe.nea.firmament.repo.RepoManager -import moe.nea.firmament.util.FirmFormatters -import moe.nea.firmament.util.HypixelPetInfo -import moe.nea.firmament.util.LegacyFormattingCode -import moe.nea.firmament.util.SkyblockId -import moe.nea.firmament.util.appendLore -import moe.nea.firmament.util.item.displayNameAccordingToNbt -import moe.nea.firmament.util.petData -import moe.nea.firmament.util.skyBlockId -import moe.nea.firmament.util.withColor - -// TODO: add in extra data like pet info, into this structure -data class PetData( - val rarity: Rarity, - val petId: String, - val exp: Double, - val isStub: Boolean = false, -) { - companion object { - fun fromHypixel(petInfo: HypixelPetInfo) = PetData( - petInfo.tier, petInfo.type, petInfo.exp, - ) - - fun forLevel(petId: String, rarity: Rarity, level: Int) = PetData( - rarity, petId, ExpLadders.getExpLadder(petId, rarity).getPetExpForLevel(level).toDouble() - ) - } - - val levelData by lazy { ExpLadders.getExpLadder(petId, rarity).getPetLevel(exp) } -} - -data class SBItemStack constructor( - val skyblockId: SkyblockId, - val neuItem: NEUItem?, - private var stackSize: Int, - private var petData: PetData?, - val extraLore: List = emptyList(), - // TODO: grab this star data from nbt if possible - val stars: Int = 0, -) { - - fun getStackSize() = stackSize - fun setStackSize(newSize: Int) { - this.stackSize = newSize - this.itemStack_ = null - } - - fun getPetData() = petData - fun setPetData(petData: PetData?) { - this.petData = petData - this.itemStack_ = null - } - - constructor(skyblockId: SkyblockId, petData: PetData) : this( - skyblockId, - RepoManager.getNEUItem(skyblockId), - 1, - petData - ) - - constructor(skyblockId: SkyblockId, stackSize: Int = 1) : this( - skyblockId, - RepoManager.getNEUItem(skyblockId), - stackSize, - RepoManager.getPotentialStubPetData(skyblockId) - ) - - private fun injectReplacementDataForPetLevel( - petInfo: PetNumbers, - level: Int, - replacementData: MutableMap - ) { - val stats = petInfo.interpolatedStatsAtLevel(level) ?: return - stats.otherNumbers.forEachIndexed { index, it -> - replacementData[index.toString()] = FirmFormatters.formatCommas(it, 1) - } - stats.statNumbers.forEach { (t, u) -> - replacementData[t] = FirmFormatters.formatCommas(u, 1) - } - } - - private fun injectReplacementDataForPets(replacementData: MutableMap) { - val petData = this.petData ?: return - val petInfo = RepoManager.neuRepo.constants.petNumbers[petData.petId]?.get(petData.rarity) ?: return - if (petData.isStub) { - val mapLow = mutableMapOf() - injectReplacementDataForPetLevel(petInfo, petInfo.lowLevel, mapLow) - val mapHigh = mutableMapOf() - injectReplacementDataForPetLevel(petInfo, petInfo.highLevel, mapHigh) - mapHigh.forEach { (key, highValue) -> - mapLow.merge(key, highValue) { a, b -> "$a → $b" } - } - replacementData.putAll(mapLow) - replacementData["LVL"] = "${petInfo.lowLevel} → ${petInfo.highLevel}" - } else { - injectReplacementDataForPetLevel(petInfo, petData.levelData.currentLevel, replacementData) - replacementData["LVL"] = petData.levelData.currentLevel.toString() - } - } - - - private var itemStack_: ItemStack? = null - - private val itemStack: ItemStack - get() { - val itemStack = itemStack_ ?: run { - if (skyblockId == SkyblockId.COINS) - return@run ItemCache.coinItem(stackSize).also { it.appendLore(extraLore) } - val replacementData = mutableMapOf() - injectReplacementDataForPets(replacementData) - return@run neuItem.asItemStack(idHint = skyblockId, replacementData) - .copyWithCount(stackSize) - .also { it.appendLore(extraLore) } - .also { enhanceStatsByStars(it, stars) } - } - if (itemStack_ == null) - itemStack_ = itemStack - return itemStack - } - - - private fun starString(stars: Int): Text { - if (stars <= 0) return Text.empty() - val tiers = listOf( - LegacyFormattingCode.GOLD, - LegacyFormattingCode.LIGHT_PURPLE, - LegacyFormattingCode.AQUA, - ) - val maxStars = 5 - if (stars > tiers.size * maxStars) return Text.literal(" ${stars}✪").withColor(Formatting.RED) - val starBaseTier = (stars - 1) / maxStars - val starBaseColor = tiers[starBaseTier] - val starsInCurrentTier = stars - starBaseTier * maxStars - val starString = Text.literal(" " + "✪".repeat(starsInCurrentTier)).withColor(starBaseColor.modern) - if (starBaseTier > 0) { - val starLastTier = tiers[starBaseTier - 1] - val starsInLastTier = 5 - starsInCurrentTier - starString.append(Text.literal("✪".repeat(starsInLastTier)).withColor(starLastTier.modern)) - } - return starString - } - - private fun enhanceStatsByStars(itemStack: ItemStack, stars: Int) { - if (stars == 0) return - // TODO: increase stats and add the star level into the nbt data so star displays work - itemStack.displayNameAccordingToNbt = itemStack.displayNameAccordingToNbt.copy() - .append(starString(stars)) - } - - fun asImmutableItemStack(): ItemStack { - return itemStack - } - - fun asItemStack(): ItemStack { - return itemStack.copy() - } -} - -object SBItemEntryDefinition : EntryDefinition { - override fun equals(o1: SBItemStack, o2: SBItemStack, context: ComparisonContext): Boolean { - return o1.skyblockId == o2.skyblockId && o1.getStackSize() == o2.getStackSize() - } - - override fun cheatsAs(entry: EntryStack?, value: SBItemStack): ItemStack { - return value.asItemStack() - } - - override fun getValueType(): Class = SBItemStack::class.java - override fun getType(): EntryType = EntryType.deferred(FirmamentReiPlugin.SKYBLOCK_ITEM_TYPE_ID) - - override fun getRenderer(): EntryRenderer = NEUItemEntryRenderer - - override fun getSerializer(): EntrySerializer { - return NEUItemEntrySerializer - } - - override fun getTagsFor(entry: EntryStack?, value: SBItemStack?): Stream>? { - return Stream.empty() - } - - override fun asFormattedText(entry: EntryStack, value: SBItemStack): Text { - return VanillaEntryTypes.ITEM.definition.asFormattedText(entry.asItemEntry(), value.asItemStack()) - } - - override fun hash(entry: EntryStack, value: SBItemStack, context: ComparisonContext): Long { - // Repo items are immutable, and get replaced entirely when loaded from disk - return value.skyblockId.hashCode() * 31L - } - - override fun wildcard(entry: EntryStack?, value: SBItemStack): SBItemStack { - return value.copy(stackSize = 1, petData = RepoManager.getPotentialStubPetData(value.skyblockId), - stars = 0, extraLore = listOf()) - } - - override fun normalize(entry: EntryStack?, value: SBItemStack): SBItemStack { - return wildcard(entry, value) - } - - override fun copy(entry: EntryStack?, value: SBItemStack): SBItemStack { - return value - } - - override fun isEmpty(entry: EntryStack?, value: SBItemStack): Boolean { - return value.getStackSize() == 0 - } - - override fun getIdentifier(entry: EntryStack?, value: SBItemStack): Identifier { - return value.skyblockId.identifier - } - - fun getEntry(sbItemStack: SBItemStack): EntryStack = - EntryStack.of(this, sbItemStack) - - fun getEntry(skyblockId: SkyblockId, count: Int = 1): EntryStack = - getEntry(SBItemStack(skyblockId, count)) - - fun getEntry(ingredient: NEUIngredient): EntryStack = - getEntry(SkyblockId(ingredient.itemId), count = ingredient.amount.toInt()) - - fun getEntry(stack: ItemStack): EntryStack = - getEntry( - SBItemStack( - stack.skyBlockId ?: SkyblockId.NULL, - RepoManager.getNEUItem(stack.skyBlockId ?: SkyblockId.NULL), - stack.count, - petData = stack.petData?.let { PetData.fromHypixel(it) } - ) - ) -} diff --git a/src/main/kotlin/moe/nea/firmament/rei/SkyblockCraftingRecipeDynamicGenerator.kt b/src/main/kotlin/moe/nea/firmament/rei/SkyblockCraftingRecipeDynamicGenerator.kt deleted file mode 100644 index 5136902..0000000 --- a/src/main/kotlin/moe/nea/firmament/rei/SkyblockCraftingRecipeDynamicGenerator.kt +++ /dev/null @@ -1,64 +0,0 @@ - - -package moe.nea.firmament.rei - -import io.github.moulberry.repo.data.NEUCraftingRecipe -import io.github.moulberry.repo.data.NEUForgeRecipe -import io.github.moulberry.repo.data.NEUKatUpgradeRecipe -import io.github.moulberry.repo.data.NEUMobDropRecipe -import io.github.moulberry.repo.data.NEURecipe -import java.util.Optional -import me.shedaniel.rei.api.client.registry.display.DynamicDisplayGenerator -import me.shedaniel.rei.api.client.view.ViewSearchBuilder -import me.shedaniel.rei.api.common.display.Display -import me.shedaniel.rei.api.common.entry.EntryStack -import moe.nea.firmament.rei.recipes.SBCraftingRecipe -import moe.nea.firmament.rei.recipes.SBEssenceUpgradeRecipe -import moe.nea.firmament.rei.recipes.SBForgeRecipe -import moe.nea.firmament.rei.recipes.SBKatRecipe -import moe.nea.firmament.rei.recipes.SBMobDropRecipe -import moe.nea.firmament.repo.EssenceRecipeProvider -import moe.nea.firmament.repo.RepoManager - - -val SkyblockCraftingRecipeDynamicGenerator = - neuDisplayGenerator { SBCraftingRecipe(it) } - -val SkyblockForgeRecipeDynamicGenerator = - neuDisplayGenerator { SBForgeRecipe(it) } - -val SkyblockMobDropRecipeDynamicGenerator = - neuDisplayGenerator { SBMobDropRecipe(it) } - -val SkyblockKatRecipeDynamicGenerator = - neuDisplayGenerator { SBKatRecipe(it) } -val SkyblockEssenceRecipeDynamicGenerator = - neuDisplayGenerator { SBEssenceUpgradeRecipe(it) } - -inline fun neuDisplayGenerator(crossinline mapper: (T) -> D) = - object : DynamicDisplayGenerator { - override fun getRecipeFor(entry: EntryStack<*>): Optional> { - if (entry.type != SBItemEntryDefinition.type) return Optional.empty() - val item = entry.castValue() - val recipes = RepoManager.getRecipesFor(item.skyblockId) - val craftingRecipes = recipes.filterIsInstance() - return Optional.of(craftingRecipes.map(mapper)) - } - - override fun generate(builder: ViewSearchBuilder): Optional> { - if (SBCraftingRecipe.Category.catIdentifier !in builder.categories) return Optional.empty() - return Optional.of( - RepoManager.getAllRecipes().filterIsInstance().map { mapper(it) } - .toList() - ) - } - - override fun getUsageFor(entry: EntryStack<*>): Optional> { - if (entry.type != SBItemEntryDefinition.type) return Optional.empty() - val item = entry.castValue() - val recipes = RepoManager.getUsagesFor(item.skyblockId) - val craftingRecipes = recipes.filterIsInstance() - return Optional.of(craftingRecipes.map(mapper)) - - } - } diff --git a/src/main/kotlin/moe/nea/firmament/rei/SkyblockItemIdFocusedStackProvider.kt b/src/main/kotlin/moe/nea/firmament/rei/SkyblockItemIdFocusedStackProvider.kt deleted file mode 100644 index bb0a5a5..0000000 --- a/src/main/kotlin/moe/nea/firmament/rei/SkyblockItemIdFocusedStackProvider.kt +++ /dev/null @@ -1,25 +0,0 @@ - - -package moe.nea.firmament.rei - -import dev.architectury.event.CompoundEventResult -import me.shedaniel.math.Point -import me.shedaniel.rei.api.client.registry.screen.FocusedStackProvider -import me.shedaniel.rei.api.common.entry.EntryStack -import net.minecraft.client.gui.screen.Screen -import net.minecraft.client.gui.screen.ingame.HandledScreen -import moe.nea.firmament.mixins.accessor.AccessorHandledScreen -import moe.nea.firmament.util.skyBlockId - -object SkyblockItemIdFocusedStackProvider : FocusedStackProvider { - override fun provide(screen: Screen?, mouse: Point?): CompoundEventResult> { - if (screen !is HandledScreen<*>) return CompoundEventResult.pass() - screen as AccessorHandledScreen - val focusedSlot = screen.focusedSlot_Firmament ?: return CompoundEventResult.pass() - val item = focusedSlot.stack ?: return CompoundEventResult.pass() - val skyblockId = item.skyBlockId ?: return CompoundEventResult.pass() - return CompoundEventResult.interruptTrue(SBItemEntryDefinition.getEntry(skyblockId)) - } - - override fun getPriority(): Double = 1_000_000.0 -} diff --git a/src/main/kotlin/moe/nea/firmament/rei/math.kt b/src/main/kotlin/moe/nea/firmament/rei/math.kt deleted file mode 100644 index 1318beb..0000000 --- a/src/main/kotlin/moe/nea/firmament/rei/math.kt +++ /dev/null @@ -1,10 +0,0 @@ - - -package moe.nea.firmament.rei - -import me.shedaniel.math.Point - -operator fun Point.plus(other: Point): Point = Point( - this.x + other.x, - this.y + other.y, -) diff --git a/src/main/kotlin/moe/nea/firmament/rei/recipes/SBCraftingRecipe.kt b/src/main/kotlin/moe/nea/firmament/rei/recipes/SBCraftingRecipe.kt deleted file mode 100644 index d6bbf0c..0000000 --- a/src/main/kotlin/moe/nea/firmament/rei/recipes/SBCraftingRecipe.kt +++ /dev/null @@ -1,55 +0,0 @@ - - -package moe.nea.firmament.rei.recipes - -import io.github.moulberry.repo.data.NEUCraftingRecipe -import io.github.moulberry.repo.data.NEUIngredient -import me.shedaniel.math.Point -import me.shedaniel.math.Rectangle -import me.shedaniel.rei.api.client.gui.Renderer -import me.shedaniel.rei.api.client.gui.widgets.Widget -import me.shedaniel.rei.api.client.gui.widgets.Widgets -import me.shedaniel.rei.api.client.registry.display.DisplayCategory -import me.shedaniel.rei.api.common.category.CategoryIdentifier -import me.shedaniel.rei.api.common.util.EntryStacks -import net.minecraft.block.Blocks -import net.minecraft.text.Text -import moe.nea.firmament.Firmament -import moe.nea.firmament.rei.SBItemEntryDefinition - -class SBCraftingRecipe(override val neuRecipe: NEUCraftingRecipe) : SBRecipe() { - override fun getCategoryIdentifier(): CategoryIdentifier<*> = Category.catIdentifier - - object Category : DisplayCategory { - val catIdentifier = CategoryIdentifier.of(Firmament.MOD_ID, "crafing_recipe") - override fun getCategoryIdentifier(): CategoryIdentifier = catIdentifier - - override fun getTitle(): Text = Text.literal("SkyBlock Crafting") - - override fun getIcon(): Renderer = EntryStacks.of(Blocks.CRAFTING_TABLE) - override fun setupDisplay(display: SBCraftingRecipe, bounds: Rectangle): List { - val point = Point(bounds.centerX - 58, bounds.centerY - 27) - return buildList { - add(Widgets.createRecipeBase(bounds)) - add(Widgets.createArrow(Point(point.x + 60, point.y + 18))) - add(Widgets.createResultSlotBackground(Point(point.x + 95, point.y + 19))) - for (i in 0 until 3) { - for (j in 0 until 3) { - val slot = Widgets.createSlot(Point(point.x + 1 + i * 18, point.y + 1 + j * 18)).markInput() - add(slot) - val item = display.neuRecipe.inputs[i + j * 3] - if (item == NEUIngredient.SENTINEL_EMPTY) continue - slot.entry(SBItemEntryDefinition.getEntry(item)) // TODO: make use of stackable item entries - } - } - add( - Widgets.createSlot(Point(point.x + 95, point.y + 19)) - .entry(SBItemEntryDefinition.getEntry(display.neuRecipe.output)) - .disableBackground().markOutput() - ) - } - } - - } - -} diff --git a/src/main/kotlin/moe/nea/firmament/rei/recipes/SBEssenceUpgradeRecipe.kt b/src/main/kotlin/moe/nea/firmament/rei/recipes/SBEssenceUpgradeRecipe.kt deleted file mode 100644 index 80bc2b7..0000000 --- a/src/main/kotlin/moe/nea/firmament/rei/recipes/SBEssenceUpgradeRecipe.kt +++ /dev/null @@ -1,62 +0,0 @@ - -package moe.nea.firmament.rei.recipes - -import me.shedaniel.math.Point -import me.shedaniel.math.Rectangle -import me.shedaniel.rei.api.client.gui.Renderer -import me.shedaniel.rei.api.client.gui.widgets.Widget -import me.shedaniel.rei.api.client.gui.widgets.Widgets -import me.shedaniel.rei.api.client.registry.display.DisplayCategory -import me.shedaniel.rei.api.common.category.CategoryIdentifier -import net.minecraft.text.Text -import moe.nea.firmament.Firmament -import moe.nea.firmament.rei.SBItemEntryDefinition -import moe.nea.firmament.rei.SBItemStack -import moe.nea.firmament.repo.EssenceRecipeProvider -import moe.nea.firmament.util.SkyblockId - -class SBEssenceUpgradeRecipe(override val neuRecipe: EssenceRecipeProvider.EssenceUpgradeRecipe) : SBRecipe() { - object Category : DisplayCategory { - override fun getCategoryIdentifier(): CategoryIdentifier = - CategoryIdentifier.of(Firmament.MOD_ID, "essence_upgrade") - - override fun getTitle(): Text { - return Text.literal("Essence Upgrades") - } - - override fun getIcon(): Renderer { - return SBItemEntryDefinition.getEntry(SkyblockId("ESSENCE_WITHER")) - } - - override fun setupDisplay(display: SBEssenceUpgradeRecipe, bounds: Rectangle): List { - val recipe = display.neuRecipe - val list = mutableListOf() - list.add(Widgets.createRecipeBase(bounds)) - list.add(Widgets.createSlot(Point(bounds.minX + 12, bounds.centerY - 8 - 18 / 2)) - .markInput() - .entry(SBItemEntryDefinition.getEntry(SBItemStack(recipe.itemId).copy(stars = recipe.starCountAfter - 1)))) - list.add(Widgets.createSlot(Point(bounds.minX + 12, bounds.centerY - 8 + 18 / 2)) - .markInput() - .entry(SBItemEntryDefinition.getEntry(recipe.essenceIngredient))) - list.add(Widgets.createSlot(Point(bounds.maxX - 12 - 16, bounds.centerY - 8)) - .markOutput() - .entry(SBItemEntryDefinition.getEntry(SBItemStack(recipe.itemId).copy(stars = recipe.starCountAfter)))) - val extraItems = recipe.extraItems - list.add(Widgets.createArrow(Point(bounds.centerX - 24 / 2, - if (extraItems.isEmpty()) bounds.centerY - 17 / 2 - else bounds.centerY + 18 / 2))) - for ((index, item) in extraItems.withIndex()) { - list.add(Widgets.createSlot( - Point(bounds.centerX - extraItems.size * 16 / 2 - 2 / 2 + index * 18, - bounds.centerY - 18 / 2)) - .markInput() - .entry(SBItemEntryDefinition.getEntry(item))) - } - return list - } - } - - override fun getCategoryIdentifier(): CategoryIdentifier<*> { - return Category.categoryIdentifier - } -} diff --git a/src/main/kotlin/moe/nea/firmament/rei/recipes/SBForgeRecipe.kt b/src/main/kotlin/moe/nea/firmament/rei/recipes/SBForgeRecipe.kt deleted file mode 100644 index 569f4a0..0000000 --- a/src/main/kotlin/moe/nea/firmament/rei/recipes/SBForgeRecipe.kt +++ /dev/null @@ -1,71 +0,0 @@ - - -package moe.nea.firmament.rei.recipes - -import io.github.moulberry.repo.data.NEUForgeRecipe -import me.shedaniel.math.Point -import me.shedaniel.math.Rectangle -import me.shedaniel.rei.api.client.gui.Renderer -import me.shedaniel.rei.api.client.gui.widgets.Widget -import me.shedaniel.rei.api.client.gui.widgets.Widgets -import me.shedaniel.rei.api.client.registry.display.DisplayCategory -import me.shedaniel.rei.api.common.category.CategoryIdentifier -import me.shedaniel.rei.api.common.util.EntryStacks -import kotlin.math.cos -import kotlin.math.sin -import kotlin.time.Duration.Companion.seconds -import net.minecraft.block.Blocks -import net.minecraft.text.Text -import moe.nea.firmament.Firmament -import moe.nea.firmament.rei.SBItemEntryDefinition -import moe.nea.firmament.rei.plus - -class SBForgeRecipe(override val neuRecipe: NEUForgeRecipe) : SBRecipe() { - override fun getCategoryIdentifier(): CategoryIdentifier<*> = Category.categoryIdentifier - - object Category : DisplayCategory { - override fun getCategoryIdentifier(): CategoryIdentifier = - CategoryIdentifier.of(Firmament.MOD_ID, "forge_recipe") - - override fun getTitle(): Text = Text.literal("Forge Recipes") - override fun getDisplayHeight(): Int { - return 104 - } - - override fun getIcon(): Renderer = EntryStacks.of(Blocks.ANVIL) - override fun setupDisplay(display: SBForgeRecipe, bounds: Rectangle): List { - return buildList { - add(Widgets.createRecipeBase(bounds)) - add(Widgets.createResultSlotBackground(Point(bounds.minX + 124, bounds.minY + 46))) - val arrow = Widgets.createArrow(Point(bounds.minX + 90, bounds.minY + 54 - 18 / 2)) - add(arrow) - add(Widgets.createTooltip(arrow.bounds, Text.stringifiedTranslatable("firmament.recipe.forge.time", display.neuRecipe.duration.seconds))) - val ingredientsCenter = Point(bounds.minX + 49 - 8, bounds.minY + 54 - 8) - val count = display.neuRecipe.inputs.size - if (count == 1) { - add( - Widgets.createSlot(Point(ingredientsCenter.x, ingredientsCenter.y)).markInput() - .entry(SBItemEntryDefinition.getEntry(display.neuRecipe.inputs.single())) - ) - } else { - display.neuRecipe.inputs.forEachIndexed { idx, ingredient -> - val rad = Math.PI * 2 * idx / count - add( - Widgets.createSlot( - Point( - cos(rad) * 30, - sin(rad) * 30, - ) + ingredientsCenter - ).markInput().entry(SBItemEntryDefinition.getEntry(ingredient)) - ) - } - } - add( - Widgets.createSlot(Point(bounds.minX + 124, bounds.minY + 46)).markOutput().disableBackground() - .entry(SBItemEntryDefinition.getEntry(display.neuRecipe.outputStack)) - ) - } - } - } - -} diff --git a/src/main/kotlin/moe/nea/firmament/rei/recipes/SBKatRecipe.kt b/src/main/kotlin/moe/nea/firmament/rei/recipes/SBKatRecipe.kt deleted file mode 100644 index f906a43..0000000 --- a/src/main/kotlin/moe/nea/firmament/rei/recipes/SBKatRecipe.kt +++ /dev/null @@ -1,224 +0,0 @@ - -package moe.nea.firmament.rei.recipes - -import io.github.moulberry.repo.data.NEUKatUpgradeRecipe -import io.github.notenoughupdates.moulconfig.common.IMinecraft -import io.github.notenoughupdates.moulconfig.gui.GuiComponent -import io.github.notenoughupdates.moulconfig.gui.GuiImmediateContext -import io.github.notenoughupdates.moulconfig.gui.MouseEvent -import io.github.notenoughupdates.moulconfig.gui.component.SliderComponent -import io.github.notenoughupdates.moulconfig.observer.GetSetter -import io.github.notenoughupdates.moulconfig.observer.Property -import io.github.notenoughupdates.moulconfig.platform.ModernRenderContext -import me.shedaniel.math.Point -import me.shedaniel.math.Rectangle -import me.shedaniel.rei.api.client.gui.Renderer -import me.shedaniel.rei.api.client.gui.widgets.Widget -import me.shedaniel.rei.api.client.gui.widgets.WidgetWithBounds -import me.shedaniel.rei.api.client.gui.widgets.Widgets -import me.shedaniel.rei.api.client.registry.display.DisplayCategory -import me.shedaniel.rei.api.common.category.CategoryIdentifier -import me.shedaniel.rei.api.common.util.EntryStacks -import kotlin.time.Duration.Companion.seconds -import net.minecraft.block.Blocks -import net.minecraft.client.gui.DrawContext -import net.minecraft.client.gui.Element -import net.minecraft.item.Items -import net.minecraft.text.Text -import moe.nea.firmament.Firmament -import moe.nea.firmament.rei.PetData -import moe.nea.firmament.rei.SBItemEntryDefinition -import moe.nea.firmament.rei.SBItemStack -import moe.nea.firmament.util.FirmFormatters -import moe.nea.firmament.util.MC -import moe.nea.firmament.util.SkyblockId - -class SBKatRecipe(override val neuRecipe: NEUKatUpgradeRecipe) : SBRecipe() { - override fun getCategoryIdentifier(): CategoryIdentifier<*> = Category.categoryIdentifier - - object Category : DisplayCategory { - override fun getCategoryIdentifier(): CategoryIdentifier = - CategoryIdentifier.of(Firmament.MOD_ID, "kat_recipe") - - override fun getTitle(): Text = Text.literal("Kat Pet Upgrade") - override fun getDisplayHeight(): Int { - return 100 - } - - override fun getIcon(): Renderer = EntryStacks.of(Items.BONE) - override fun setupDisplay(display: SBKatRecipe, bounds: Rectangle): List { - return buildList { - val arrowWidth = 24 - val recipe = display.neuRecipe - val levelValue = Property.upgrade(GetSetter.floating(0F)) - val slider = SliderComponent(levelValue, 1F, 100F, 1f, 100) - val outputStack = SBItemStack(SkyblockId(recipe.output.itemId)) - val inputStack = SBItemStack(SkyblockId(recipe.input.itemId)) - val inputLevelLabelCenter = Point(bounds.minX + 30 - 18 + 5 + 8, bounds.minY + 25) - val inputLevelLabel = Widgets.createLabel( - inputLevelLabelCenter, - Text.literal("")).centered() - val outputLevelLabelCenter = Point(bounds.maxX - 30 + 8, bounds.minY + 25) - val outputLevelLabel = Widgets.createLabel( - outputLevelLabelCenter, - Text.literal("")).centered() - val coinStack = SBItemStack(SkyblockId.COINS, recipe.coins.toInt()) - levelValue.whenChanged { oldValue, newValue -> - if (oldValue.toInt() == newValue.toInt()) return@whenChanged - val oldInput = inputStack.getPetData() ?: return@whenChanged - val newInput = PetData.forLevel(oldInput.petId, oldInput.rarity, newValue.toInt()) - inputStack.setPetData(newInput) - val oldOutput = outputStack.getPetData() ?: return@whenChanged - val newOutput = PetData(oldOutput.rarity, oldOutput.petId, newInput.exp) - outputStack.setPetData(newOutput) - inputLevelLabel.message = Text.literal(newInput.levelData.currentLevel.toString()) - inputLevelLabel.bounds.location = Point( - inputLevelLabelCenter.x - MC.font.getWidth(inputLevelLabel.message) / 2, - inputLevelLabelCenter.y) - outputLevelLabel.message = Text.literal(newOutput.levelData.currentLevel.toString()) - outputLevelLabel.bounds.location = Point( - outputLevelLabelCenter.x - MC.font.getWidth(outputLevelLabel.message) / 2, - outputLevelLabelCenter.y) - coinStack.setStackSize((recipe.coins * (1 - 0.3 * newValue / 100)).toInt()) - } - levelValue.set(1F) - add(Widgets.createRecipeBase(bounds)) - add(wrapWidget(Rectangle(bounds.centerX - slider.width / 2, - bounds.maxY - 30, - slider.width, - slider.height), - slider)) - add(Widgets.withTooltip( - Widgets.createArrow(Point(bounds.centerX - arrowWidth / 2, bounds.minY + 40)), - Text.literal("Upgrade time: " + FirmFormatters.formatTimespan(recipe.seconds.seconds)))) - - add(Widgets.createResultSlotBackground(Point(bounds.maxX - 30, bounds.minY + 40))) - add(inputLevelLabel) - add(outputLevelLabel) - add(Widgets.createSlot(Point(bounds.maxX - 30, bounds.minY + 40)).markOutput().disableBackground() - .entry(SBItemEntryDefinition.getEntry(outputStack))) - add(Widgets.createSlot(Point(bounds.minX + 30 - 18 + 5, bounds.minY + 40)).markInput() - .entry(SBItemEntryDefinition.getEntry(inputStack))) - - val allInputs = recipe.items.map { SBItemEntryDefinition.getEntry(it) } + - listOf(SBItemEntryDefinition.getEntry(coinStack)) - for ((index, item) in allInputs.withIndex()) { - add(Widgets.createSlot( - Point(bounds.centerX + index * 20 - allInputs.size * 18 / 2 - (allInputs.size - 1) * 2 / 2, - bounds.minY + 20)) - .markInput() - .entry(item)) - } - } - } - } -} - -fun wrapWidget(bounds: Rectangle, component: GuiComponent): Widget { - return object : WidgetWithBounds() { - override fun getBounds(): Rectangle { - return bounds - } - - override fun children(): List { - return listOf() - } - - override fun render(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) { - context.matrices.push() - context.matrices.translate(bounds.minX.toFloat(), bounds.minY.toFloat(), 0F) - component.render( - GuiImmediateContext( - ModernRenderContext(context), - bounds.minX, bounds.minY, - bounds.width, bounds.height, - mouseX - bounds.minX, mouseY - bounds.minY, - mouseX, mouseY, - mouseX.toFloat(), mouseY.toFloat() - )) - context.matrices.pop() - } - - override fun mouseMoved(mouseX: Double, mouseY: Double) { - val mouseXInt = mouseX.toInt() - val mouseYInt = mouseY.toInt() - component.mouseEvent(MouseEvent.Move(0F, 0F), - GuiImmediateContext( - IMinecraft.instance.provideTopLevelRenderContext(), - bounds.minX, bounds.minY, - bounds.width, bounds.height, - mouseXInt - bounds.minX, mouseYInt - bounds.minY, - mouseXInt, mouseYInt, - mouseX.toFloat(), mouseY.toFloat() - )) - } - - override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean { - val mouseXInt = mouseX.toInt() - val mouseYInt = mouseY.toInt() - return component.mouseEvent(MouseEvent.Click(button, true), - GuiImmediateContext( - IMinecraft.instance.provideTopLevelRenderContext(), - bounds.minX, bounds.minY, - bounds.width, bounds.height, - mouseXInt - bounds.minX, mouseYInt - bounds.minY, - mouseXInt, mouseYInt, - mouseX.toFloat(), mouseY.toFloat() - )) - } - - override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean { - val mouseXInt = mouseX.toInt() - val mouseYInt = mouseY.toInt() - return component.mouseEvent(MouseEvent.Click(button, false), - GuiImmediateContext( - IMinecraft.instance.provideTopLevelRenderContext(), - bounds.minX, bounds.minY, - bounds.width, bounds.height, - mouseXInt - bounds.minX, mouseYInt - bounds.minY, - mouseXInt, mouseYInt, - mouseX.toFloat(), mouseY.toFloat() - )) - } - - override fun mouseDragged( - mouseX: Double, - mouseY: Double, - button: Int, - deltaX: Double, - deltaY: Double - ): Boolean { - val mouseXInt = mouseX.toInt() - val mouseYInt = mouseY.toInt() - return component.mouseEvent(MouseEvent.Move(deltaX.toFloat(), deltaY.toFloat()), - GuiImmediateContext( - IMinecraft.instance.provideTopLevelRenderContext(), - bounds.minX, bounds.minY, - bounds.width, bounds.height, - mouseXInt - bounds.minX, mouseYInt - bounds.minY, - mouseXInt, mouseYInt, - mouseX.toFloat(), mouseY.toFloat() - )) - - } - - override fun mouseScrolled( - mouseX: Double, - mouseY: Double, - horizontalAmount: Double, - verticalAmount: Double - ): Boolean { - val mouseXInt = mouseX.toInt() - val mouseYInt = mouseY.toInt() - return component.mouseEvent(MouseEvent.Scroll(verticalAmount.toFloat()), - GuiImmediateContext( - IMinecraft.instance.provideTopLevelRenderContext(), - bounds.minX, bounds.minY, - bounds.width, bounds.height, - mouseXInt - bounds.minX, mouseYInt - bounds.minY, - mouseXInt, mouseYInt, - mouseX.toFloat(), mouseY.toFloat() - )) - } - } -} diff --git a/src/main/kotlin/moe/nea/firmament/rei/recipes/SBMobDropRecipe.kt b/src/main/kotlin/moe/nea/firmament/rei/recipes/SBMobDropRecipe.kt deleted file mode 100644 index a02220f..0000000 --- a/src/main/kotlin/moe/nea/firmament/rei/recipes/SBMobDropRecipe.kt +++ /dev/null @@ -1,108 +0,0 @@ - -package moe.nea.firmament.rei.recipes - -import io.github.moulberry.repo.data.NEUMobDropRecipe -import me.shedaniel.math.Point -import me.shedaniel.math.Rectangle -import me.shedaniel.rei.api.client.gui.Renderer -import me.shedaniel.rei.api.client.gui.widgets.Widget -import me.shedaniel.rei.api.client.gui.widgets.Widgets -import me.shedaniel.rei.api.client.registry.display.DisplayCategory -import me.shedaniel.rei.api.common.category.CategoryIdentifier -import me.shedaniel.rei.api.common.util.EntryStacks -import net.minecraft.item.Items -import net.minecraft.text.Text -import net.minecraft.util.Identifier -import moe.nea.firmament.Firmament -import moe.nea.firmament.gui.entity.EntityRenderer -import moe.nea.firmament.gui.entity.EntityWidget -import moe.nea.firmament.rei.SBItemEntryDefinition - -class SBMobDropRecipe(override val neuRecipe: NEUMobDropRecipe) : SBRecipe() { - override fun getCategoryIdentifier(): CategoryIdentifier<*> = Category.categoryIdentifier - - object Category : DisplayCategory { - override fun getCategoryIdentifier(): CategoryIdentifier = - CategoryIdentifier.of(Firmament.MOD_ID, "mob_drop_recipe") - - override fun getTitle(): Text = Text.literal("Mob Drops") - override fun getDisplayHeight(): Int { - return 100 - } - - override fun getIcon(): Renderer = EntryStacks.of(Items.DIAMOND_SWORD) - override fun setupDisplay(display: SBMobDropRecipe, bounds: Rectangle): List { - return buildList { - add(Widgets.createRecipeBase(bounds)) - val source = display.neuRecipe.render - val entity = if (source.startsWith("@")) { - EntityRenderer.constructEntity(Identifier.of(source.substring(1))) - } else { - EntityRenderer.applyModifiers(source, listOf()) - } - if (entity != null) { - val level = display.neuRecipe.level - val fullMobName = - if (level > 0) Text.translatable("firmament.recipe.mobs.name", level, display.neuRecipe.name) - else Text.translatable("firmament.recipe.mobs.name.nolevel", display.neuRecipe.name) - val tt = mutableListOf() - tt.add((fullMobName)) - tt.add(Text.literal("")) - if (display.neuRecipe.coins > 0) { - tt.add(Text.stringifiedTranslatable("firmament.recipe.mobs.coins", display.neuRecipe.coins)) - } - if (display.neuRecipe.combatExperience > 0) { - tt.add( - Text.stringifiedTranslatable( - "firmament.recipe.mobs.combat", - display.neuRecipe.combatExperience - ) - ) - } - if (display.neuRecipe.enchantingExperience > 0) { - tt.add( - Text.stringifiedTranslatable( - "firmament.recipe.mobs.exp", - display.neuRecipe.enchantingExperience - ) - ) - } - if (display.neuRecipe.extra != null) - display.neuRecipe.extra.mapTo(tt) { Text.literal(it) } - if (tt.size == 2) - tt.removeAt(1) - add( - Widgets.withTooltip( - EntityWidget(entity, Point(bounds.minX + 5, bounds.minY + 15)), - tt - ) - ) - } - add( - Widgets.createLabel(Point(bounds.minX + 15, bounds.minY + 5), Text.literal(display.neuRecipe.name)) - .leftAligned() - ) - var x = bounds.minX + 60 - var y = bounds.minY + 20 - for (drop in display.neuRecipe.drops) { - val lore = drop.extra.mapTo(mutableListOf()) { Text.literal(it) } - if (drop.chance != null) { - lore += listOf(Text.translatable("firmament.recipe.mobs.drops", drop.chance)) - } - val item = SBItemEntryDefinition.getEntry(drop.dropItem) - .value.copy(extraLore = lore) - add( - Widgets.createSlot(Point(x, y)).markOutput() - .entries(listOf(SBItemEntryDefinition.getEntry(item))) - ) - x += 18 - if (x > bounds.maxX - 30) { - x = bounds.minX + 60 - y += 18 - } - } - } - } - } - -} diff --git a/src/main/kotlin/moe/nea/firmament/rei/recipes/SBRecipe.kt b/src/main/kotlin/moe/nea/firmament/rei/recipes/SBRecipe.kt deleted file mode 100644 index 7872d83..0000000 --- a/src/main/kotlin/moe/nea/firmament/rei/recipes/SBRecipe.kt +++ /dev/null @@ -1,31 +0,0 @@ - - -package moe.nea.firmament.rei.recipes - -import io.github.moulberry.repo.data.NEUIngredient -import io.github.moulberry.repo.data.NEURecipe -import me.shedaniel.rei.api.common.display.Display -import me.shedaniel.rei.api.common.entry.EntryIngredient -import moe.nea.firmament.rei.SBItemEntryDefinition -import moe.nea.firmament.util.SkyblockId - -abstract class SBRecipe : Display { - abstract val neuRecipe: NEURecipe - override fun getInputEntries(): List { - return neuRecipe.allInputs - .filter { it.itemId != NEUIngredient.NEU_SENTINEL_EMPTY } - .map { - val entryStack = SBItemEntryDefinition.getEntry(SkyblockId(it.itemId)) - EntryIngredient.of(entryStack) - } - } - - override fun getOutputEntries(): List { - return neuRecipe.allOutputs - .filter { it.itemId != NEUIngredient.NEU_SENTINEL_EMPTY } - .map { - val entryStack = SBItemEntryDefinition.getEntry(SkyblockId(it.itemId)) - EntryIngredient.of(entryStack) - } - } -} diff --git a/src/main/kotlin/moe/nea/firmament/repo/BetterRepoRecipeCache.kt b/src/main/kotlin/moe/nea/firmament/repo/BetterRepoRecipeCache.kt deleted file mode 100644 index 91a6b50..0000000 --- a/src/main/kotlin/moe/nea/firmament/repo/BetterRepoRecipeCache.kt +++ /dev/null @@ -1,28 +0,0 @@ - -package moe.nea.firmament.repo - -import io.github.moulberry.repo.IReloadable -import io.github.moulberry.repo.NEURepository -import io.github.moulberry.repo.data.NEURecipe -import moe.nea.firmament.util.SkyblockId - -class BetterRepoRecipeCache(val essenceRecipeProvider: EssenceRecipeProvider) : IReloadable { - var usages: Map> = mapOf() - var recipes: Map> = mapOf() - - override fun reload(repository: NEURepository) { - val usages = mutableMapOf>() - val recipes = mutableMapOf>() - val baseRecipes = repository.items.items.values - .asSequence() - .flatMap { it.recipes } - val extraRecipes = essenceRecipeProvider.recipes - (baseRecipes + extraRecipes) - .forEach { recipe -> - recipe.allInputs.forEach { usages.getOrPut(SkyblockId(it.itemId), ::mutableSetOf).add(recipe) } - recipe.allOutputs.forEach { recipes.getOrPut(SkyblockId(it.itemId), ::mutableSetOf).add(recipe) } - } - this.usages = usages - this.recipes = recipes - } -} diff --git a/src/main/kotlin/moe/nea/firmament/repo/EssenceRecipeProvider.kt b/src/main/kotlin/moe/nea/firmament/repo/EssenceRecipeProvider.kt deleted file mode 100644 index 1833258..0000000 --- a/src/main/kotlin/moe/nea/firmament/repo/EssenceRecipeProvider.kt +++ /dev/null @@ -1,50 +0,0 @@ - -package moe.nea.firmament.repo - -import io.github.moulberry.repo.IReloadable -import io.github.moulberry.repo.NEURepository -import io.github.moulberry.repo.data.NEUIngredient -import io.github.moulberry.repo.data.NEURecipe -import moe.nea.firmament.util.SkyblockId - -class EssenceRecipeProvider : IReloadable { - data class EssenceUpgradeRecipe( - val itemId: SkyblockId, - val starCountAfter: Int, - val essenceCost: Int, - val essenceType: String, // TODO: replace with proper type - val extraItems: List, - ) : NEURecipe { - val essenceIngredient= NEUIngredient.fromString("${essenceType}:$essenceCost") - val allUpgradeComponents = listOf(essenceIngredient) + extraItems - - override fun getAllInputs(): Collection { - return listOf(NEUIngredient.fromString(itemId.neuItem + ":1")) + allUpgradeComponents - } - - override fun getAllOutputs(): Collection { - return listOf(NEUIngredient.fromString(itemId.neuItem + ":1")) - } - } - - var recipes = listOf() - private set - - override fun reload(repository: NEURepository) { - val recipes = mutableListOf() - for ((neuId, costs) in repository.constants.essenceCost.costs) { - // TODO: add dungeonization costs. this is in repo, but not in the repo parser. - for ((starCountAfter, essenceCost) in costs.essenceCosts.entries) { - val items = costs.itemCosts[starCountAfter] ?: emptyList() - recipes.add( - EssenceUpgradeRecipe( - SkyblockId(neuId), - starCountAfter, - essenceCost, - "ESSENCE_" + costs.type.uppercase(), // how flimsy - items.map { NEUIngredient.fromString(it) })) - } - } - this.recipes = recipes - } -} diff --git a/src/main/kotlin/moe/nea/firmament/repo/ExpLadder.kt b/src/main/kotlin/moe/nea/firmament/repo/ExpLadder.kt deleted file mode 100644 index fbc9eb8..0000000 --- a/src/main/kotlin/moe/nea/firmament/repo/ExpLadder.kt +++ /dev/null @@ -1,94 +0,0 @@ - - -package moe.nea.firmament.repo - -import com.google.common.cache.CacheBuilder -import com.google.common.cache.CacheLoader -import io.github.moulberry.repo.IReloadable -import io.github.moulberry.repo.NEURepository -import io.github.moulberry.repo.constants.PetLevelingBehaviourOverride -import io.github.moulberry.repo.data.Rarity - -object ExpLadders : IReloadable { - - data class PetLevel( - val currentLevel: Int, - val maxLevel: Int, - val expRequiredForNextLevel: Long, - val expRequiredForMaxLevel: Long, - val expInCurrentLevel: Float, - var expTotal: Float, - ) { - val percentageToNextLevel: Float = expInCurrentLevel / expRequiredForNextLevel - } - - data class ExpLadder( - val individualLevelCost: List, - ) { - val cumulativeLevelCost = individualLevelCost.runningFold(0F) { a, b -> a + b }.map { it.toLong() } - fun getPetLevel(currentExp: Double): PetLevel { - val currentOneIndexedLevel = cumulativeLevelCost.indexOfLast { it <= currentExp } + 1 - val expForNextLevel = if (currentOneIndexedLevel > individualLevelCost.size) { // Max leveled pet - individualLevelCost.last() - } else { - individualLevelCost[currentOneIndexedLevel - 1] - } - val expInCurrentLevel = - if (currentOneIndexedLevel >= cumulativeLevelCost.size) - currentExp.toFloat() - cumulativeLevelCost.last() - else - (expForNextLevel - (cumulativeLevelCost[currentOneIndexedLevel] - currentExp.toFloat())).coerceAtLeast( - 0F - ) - return PetLevel( - currentLevel = currentOneIndexedLevel, - maxLevel = cumulativeLevelCost.size, - expRequiredForNextLevel = expForNextLevel, - expRequiredForMaxLevel = cumulativeLevelCost.last(), - expInCurrentLevel = expInCurrentLevel, - expTotal = currentExp.toFloat() - ) - } - - fun getPetExpForLevel(level: Int): Long { - if (level < 2) return 0L - if (level >= cumulativeLevelCost.size) return cumulativeLevelCost.last() - return cumulativeLevelCost[level - 1] - } - } - - private data class Key(val petIdWithoutRarity: String, val rarity: Rarity) - - private val expLadders = CacheBuilder.newBuilder() - .build(object : CacheLoader() { - override fun load(key: Key): ExpLadder { - val pld = RepoManager.neuRepo.constants.petLevelingData - var exp = pld.petExpCostForLevel - var offset = pld.petLevelStartOffset[key.rarity]!! - var maxLevel = 100 - val override = pld.petLevelingBehaviourOverrides[key.petIdWithoutRarity] - if (override != null) { - maxLevel = override.maxLevel ?: maxLevel - offset = override.petLevelStartOffset?.get(key.rarity) ?: offset - when (override.petExpCostModifierType) { - PetLevelingBehaviourOverride.PetExpModifierType.APPEND -> - exp = exp + override.petExpCostModifier - - PetLevelingBehaviourOverride.PetExpModifierType.REPLACE -> - exp = override.petExpCostModifier - - null -> {} - } - } - return ExpLadder(exp.drop(offset).take(maxLevel - 1).map { it.toLong() }) - } - }) - - override fun reload(repository: NEURepository?) { - expLadders.invalidateAll() - } - - fun getExpLadder(petId: String, rarity: Rarity): ExpLadder { - return expLadders.get(Key(petId, rarity)) - } -} diff --git a/src/main/kotlin/moe/nea/firmament/repo/HypixelStaticData.kt b/src/main/kotlin/moe/nea/firmament/repo/HypixelStaticData.kt deleted file mode 100644 index 5c2a2fc..0000000 --- a/src/main/kotlin/moe/nea/firmament/repo/HypixelStaticData.kt +++ /dev/null @@ -1,107 +0,0 @@ - - -package moe.nea.firmament.repo - -import io.ktor.client.call.body -import io.ktor.client.request.get -import org.apache.logging.log4j.LogManager -import org.lwjgl.glfw.GLFW -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.launch -import kotlinx.coroutines.withTimeoutOrNull -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlin.time.Duration.Companion.minutes -import moe.nea.firmament.Firmament -import moe.nea.firmament.apis.CollectionResponse -import moe.nea.firmament.apis.CollectionSkillData -import moe.nea.firmament.keybindings.IKeyBinding -import moe.nea.firmament.util.SkyblockId -import moe.nea.firmament.util.async.waitForInput - -object HypixelStaticData { - private val logger = LogManager.getLogger("Firmament.HypixelStaticData") - private val moulberryBaseUrl = "https://moulberry.codes" - private val hypixelApiBaseUrl = "https://api.hypixel.net" - var lowestBin: Map = mapOf() - private set - var bazaarData: Map = mapOf() - private set - var collectionData: Map = mapOf() - private set - - @Serializable - data class BazaarData( - @SerialName("product_id") - val productId: SkyblockId.BazaarStock, - @SerialName("quick_status") - val quickStatus: BazaarStatus, - ) - - @Serializable - data class BazaarStatus( - val sellPrice: Double, - val sellVolume: Long, - val sellMovingWeek: Long, - val sellOrders: Long, - val buyPrice: Double, - val buyVolume: Long, - val buyMovingWeek: Long, - val buyOrders: Long - ) - - @Serializable - private data class BazaarResponse( - val success: Boolean, - val products: Map = mapOf(), - ) - - fun getPriceOfItem(item: SkyblockId): Double? = bazaarData[item]?.quickStatus?.buyPrice ?: lowestBin[item] - - - fun spawnDataCollectionLoop() { - Firmament.coroutineScope.launch { - logger.info("Updating collection data") - updateCollectionData() - } - Firmament.coroutineScope.launch { - while (true) { - logger.info("Updating NEU prices") - updatePrices() - withTimeoutOrNull(10.minutes) { waitForInput(IKeyBinding.ofKeyCode(GLFW.GLFW_KEY_U)) } - } - } - } - - private suspend fun updatePrices() { - awaitAll( - Firmament.coroutineScope.async { fetchBazaarPrices() }, - Firmament.coroutineScope.async { fetchPricesFromMoulberry() }, - ) - } - - private suspend fun fetchPricesFromMoulberry() { - lowestBin = Firmament.httpClient.get("$moulberryBaseUrl/lowestbin.json") - .body>() - } - - private suspend fun fetchBazaarPrices() { - val response = Firmament.httpClient.get("$hypixelApiBaseUrl/skyblock/bazaar").body() - if (!response.success) { - logger.warn("Retrieved unsuccessful bazaar data") - } - bazaarData = response.products.mapKeys { it.key.toRepoId() } - } - - private suspend fun updateCollectionData() { - val response = - Firmament.httpClient.get("$hypixelApiBaseUrl/resources/skyblock/collections").body() - if (!response.success) { - logger.warn("Retrieved unsuccessful collection data") - } - collectionData = response.collections - logger.info("Downloaded ${collectionData.values.sumOf { it.items.values.size }} collections") - } - -} diff --git a/src/main/kotlin/moe/nea/firmament/repo/ItemCache.kt b/src/main/kotlin/moe/nea/firmament/repo/ItemCache.kt deleted file mode 100644 index 08143be..0000000 --- a/src/main/kotlin/moe/nea/firmament/repo/ItemCache.kt +++ /dev/null @@ -1,215 +0,0 @@ - - -package moe.nea.firmament.repo - -import com.mojang.serialization.Dynamic -import io.github.moulberry.repo.IReloadable -import io.github.moulberry.repo.NEURepository -import io.github.moulberry.repo.data.NEUItem -import io.github.notenoughupdates.moulconfig.xml.Bind -import java.text.NumberFormat -import java.util.UUID -import java.util.concurrent.ConcurrentHashMap -import org.apache.logging.log4j.LogManager -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch -import kotlin.jvm.optionals.getOrNull -import net.minecraft.SharedConstants -import net.minecraft.client.resource.language.I18n -import net.minecraft.component.DataComponentTypes -import net.minecraft.component.type.NbtComponent -import net.minecraft.datafixer.Schemas -import net.minecraft.datafixer.TypeReferences -import net.minecraft.item.ItemStack -import net.minecraft.item.Items -import net.minecraft.nbt.NbtCompound -import net.minecraft.nbt.NbtElement -import net.minecraft.nbt.NbtOps -import net.minecraft.text.Text -import moe.nea.firmament.Firmament -import moe.nea.firmament.gui.config.HudMeta -import moe.nea.firmament.gui.config.HudPosition -import moe.nea.firmament.gui.hud.MoulConfigHud -import moe.nea.firmament.util.LegacyTagParser -import moe.nea.firmament.util.MC -import moe.nea.firmament.util.SkyblockId -import moe.nea.firmament.util.appendLore -import moe.nea.firmament.util.item.setCustomName -import moe.nea.firmament.util.item.setSkullOwner -import moe.nea.firmament.util.modifyLore -import moe.nea.firmament.util.skyblockId - -object ItemCache : IReloadable { - private val cache: MutableMap = ConcurrentHashMap() - private val df = Schemas.getFixer() - val logger = LogManager.getLogger("${Firmament.logger.name}.ItemCache") - var isFlawless = true - private set - - private fun NEUItem.get10809CompoundTag(): NbtCompound = NbtCompound().apply { - put("tag", LegacyTagParser.parse(nbttag)) - putString("id", minecraftItemId) - putByte("Count", 1) - putShort("Damage", damage.toShort()) - } - - private fun NbtCompound.transformFrom10809ToModern(): NbtCompound? = - try { - df.update( - TypeReferences.ITEM_STACK, - Dynamic(NbtOps.INSTANCE, this), - -1, - SharedConstants.getGameVersion().saveVersion.id - ).value as NbtCompound - } catch (e: Exception) { - isFlawless = false - logger.error("Could not data fix up $this", e) - null - } - - fun brokenItemStack(neuItem: NEUItem?, idHint: SkyblockId? = null): ItemStack { - return ItemStack(Items.PAINTING).apply { - setCustomName(Text.literal(neuItem?.displayName ?: idHint?.neuItem ?: "null")) - appendLore( - listOf( - Text.stringifiedTranslatable( - "firmament.repo.brokenitem", - (neuItem?.skyblockItemId ?: idHint) - ) - ) - ) - } - } - - private fun NEUItem.asItemStackNow(): ItemStack { - try { - val oldItemTag = get10809CompoundTag() - val modernItemTag = oldItemTag.transformFrom10809ToModern() - ?: return brokenItemStack(this) - val itemInstance = - ItemStack.fromNbt(MC.defaultRegistries, modernItemTag).getOrNull() ?: return brokenItemStack(this) - val extraAttributes = oldItemTag.getCompound("tag").getCompound("ExtraAttributes") - if (extraAttributes != null) - itemInstance.set(DataComponentTypes.CUSTOM_DATA, NbtComponent.of(extraAttributes)) - return itemInstance - } catch (e: Exception) { - e.printStackTrace() - return brokenItemStack(this) - } - } - - fun NEUItem?.asItemStack(idHint: SkyblockId? = null, loreReplacements: Map? = null): ItemStack { - if (this == null) return brokenItemStack(null, idHint) - var s = cache[this.skyblockItemId] - if (s == null) { - s = asItemStackNow() - cache[this.skyblockItemId] = s - } - if (!loreReplacements.isNullOrEmpty()) { - s = s.copy()!! - s.applyLoreReplacements(loreReplacements) - s.setCustomName(s.name.applyLoreReplacements(loreReplacements)) - } - return s - } - - fun ItemStack.applyLoreReplacements(loreReplacements: Map) { - modifyLore { lore -> - lore.map { - it.applyLoreReplacements(loreReplacements) - } - } - } - - fun Text.applyLoreReplacements(loreReplacements: Map): Text { - assert(this.siblings.isEmpty()) - var string = this.string - loreReplacements.forEach { (find, replace) -> - string = string.replace("{$find}", replace) - } - return Text.literal(string).styled { this.style } - } - - fun NEUItem.getIdentifier() = skyblockId.identifier - - var job: Job? = null - object ReloadProgressHud : MoulConfigHud( - "repo_reload", HudMeta(HudPosition(0.0, 0.0, 1F), Text.literal("Repo Reload"), 180, 18)) { - - - var isEnabled = false - override fun shouldRender(): Boolean { - return isEnabled - } - - @get:Bind("current") - var current: Double = 0.0 - - @get:Bind("label") - var label: String = "" - - @get:Bind("max") - var max: Double = 0.0 - - fun reportProgress(label: String, current: Int, max: Int) { - this.label = label - this.current = current.toDouble() - this.max = max.toDouble() - } - } - - override fun reload(repository: NEURepository) { - val j = job - if (j != null && j.isActive) { - j.cancel() - } - cache.clear() - isFlawless = true - - job = Firmament.coroutineScope.launch { - val items = repository.items?.items - if (items == null) { - ReloadProgressHud.isEnabled = false - return@launch - } - val recacheItems = I18n.translate("firmament.repo.cache") - ReloadProgressHud.reportProgress(recacheItems, 0, items.size) - ReloadProgressHud.isEnabled = true - var i = 0 - items.values.forEach { - it.asItemStack() // Rebuild cache - ReloadProgressHud.reportProgress(recacheItems, i++, items.size) - } - ReloadProgressHud.isEnabled = false - } - } - - fun coinItem(coinAmount: Int): ItemStack { - var uuid = UUID.fromString("2070f6cb-f5db-367a-acd0-64d39a7e5d1b") - var texture = - "http://textures.minecraft.net/texture/538071721cc5b4cd406ce431a13f86083a8973e1064d2f8897869930ee6e5237" - if (coinAmount >= 100000) { - uuid = UUID.fromString("94fa2455-2881-31fe-bb4e-e3e24d58dbe3") - texture = - "http://textures.minecraft.net/texture/c9b77999fed3a2758bfeaf0793e52283817bea64044bf43ef29433f954bb52f6" - } - if (coinAmount >= 10000000) { - uuid = UUID.fromString("0af8df1f-098c-3b72-ac6b-65d65fd0b668") - texture = - "http://textures.minecraft.net/texture/7b951fed6a7b2cbc2036916dec7a46c4a56481564d14f945b6ebc03382766d3b" - } - val itemStack = ItemStack(Items.PLAYER_HEAD) - itemStack.setCustomName(Text.literal("§r§6" + NumberFormat.getInstance().format(coinAmount) + " Coins")) - itemStack.setSkullOwner(uuid, texture) - return itemStack - } -} - - -operator fun NbtCompound.set(key: String, value: String) { - putString(key, value) -} - -operator fun NbtCompound.set(key: String, value: NbtElement) { - put(key, value) -} diff --git a/src/main/kotlin/moe/nea/firmament/repo/ItemNameLookup.kt b/src/main/kotlin/moe/nea/firmament/repo/ItemNameLookup.kt deleted file mode 100644 index 770de85..0000000 --- a/src/main/kotlin/moe/nea/firmament/repo/ItemNameLookup.kt +++ /dev/null @@ -1,98 +0,0 @@ - -package moe.nea.firmament.repo - -import io.github.moulberry.repo.IReloadable -import io.github.moulberry.repo.NEURepository -import io.github.moulberry.repo.data.NEUItem -import java.util.NavigableMap -import java.util.TreeMap -import moe.nea.firmament.util.SkyblockId -import moe.nea.firmament.util.removeColorCodes -import moe.nea.firmament.util.skyblockId - -object ItemNameLookup : IReloadable { - - fun getItemNameChunks(name: String): Set { - return name.removeColorCodes().split(" ").filterTo(mutableSetOf()) { it.isNotBlank() } - } - - var nameMap: NavigableMap> = TreeMap() - - override fun reload(repository: NEURepository) { - val nameMap = TreeMap>() - repository.items.items.values.forEach { item -> - getAllNamesForItem(item).forEach { name -> - val chunks = getItemNameChunks(name) - chunks.forEach { chunk -> - val set = nameMap.getOrPut(chunk, ::mutableSetOf) - set.add(item.skyblockId) - } - } - } - this.nameMap = nameMap - } - - fun getAllNamesForItem(item: NEUItem): Set { - val names = mutableSetOf() - names.add(item.displayName) - if (item.displayName.contains("Enchanted Book")) { - val enchantName = item.lore.firstOrNull() - if (enchantName != null) { - names.add(enchantName) - } - } - return names - } - - fun findItemCandidatesByName(name: String): MutableSet { - val candidates = mutableSetOf() - for (chunk in getItemNameChunks(name)) { - val set = nameMap[chunk] ?: emptySet() - candidates.addAll(set) - } - return candidates - } - - - fun guessItemByName( - /** - * The display name of the item. Color codes will be ignored. - */ - name: String, - /** - * Whether the [name] may contain other text, such as reforges, master stars and such. - */ - mayBeMangled: Boolean - ): SkyblockId? { - val cleanName = name.removeColorCodes() - return findBestItemFromCandidates( - findItemCandidatesByName(cleanName), - cleanName, - true - ) - } - - fun findBestItemFromCandidates( - candidates: Iterable, - name: String, mayBeMangled: Boolean - ): SkyblockId? { - val expectedClean = name.removeColorCodes() - var bestMatch: SkyblockId? = null - var bestMatchLength = -1 - for (candidate in candidates) { - val item = RepoManager.getNEUItem(candidate) ?: continue - for (name in getAllNamesForItem(item)) { - val actualClean = name.removeColorCodes() - val matches = if (mayBeMangled) expectedClean == actualClean - else expectedClean.contains(actualClean) - if (!matches) continue - if (actualClean.length > bestMatchLength) { - bestMatch = candidate - bestMatchLength = actualClean.length - } - } - } - return bestMatch - } - -} diff --git a/src/main/kotlin/moe/nea/firmament/repo/RepoDownloadManager.kt b/src/main/kotlin/moe/nea/firmament/repo/RepoDownloadManager.kt deleted file mode 100644 index d674f23..0000000 --- a/src/main/kotlin/moe/nea/firmament/repo/RepoDownloadManager.kt +++ /dev/null @@ -1,128 +0,0 @@ - - -package moe.nea.firmament.repo - -import io.ktor.client.call.body -import io.ktor.client.request.get -import io.ktor.client.statement.bodyAsChannel -import io.ktor.utils.io.jvm.nio.copyTo -import java.io.IOException -import java.nio.file.Files -import java.nio.file.Path -import java.nio.file.StandardOpenOption -import java.util.zip.ZipInputStream -import kotlinx.coroutines.CoroutineName -import kotlinx.coroutines.Dispatchers.IO -import kotlinx.coroutines.withContext -import kotlinx.serialization.Serializable -import kotlin.io.path.createDirectories -import kotlin.io.path.exists -import kotlin.io.path.inputStream -import kotlin.io.path.outputStream -import kotlin.io.path.readText -import kotlin.io.path.writeText -import moe.nea.firmament.Firmament -import moe.nea.firmament.Firmament.logger -import moe.nea.firmament.util.iterate - - -object RepoDownloadManager { - - val repoSavedLocation = Firmament.DATA_DIR.resolve("repo-extracted") - val repoMetadataLocation = Firmament.DATA_DIR.resolve("loaded-repo-sha.txt") - - private fun loadSavedVersionHash(): String? = - if (repoSavedLocation.exists()) { - if (repoMetadataLocation.exists()) { - try { - repoMetadataLocation.readText().trim() - } catch (e: IOException) { - null - } - } else { - null - } - } else null - - private fun saveVersionHash(versionHash: String) { - latestSavedVersionHash = versionHash - repoMetadataLocation.writeText(versionHash) - } - - var latestSavedVersionHash: String? = loadSavedVersionHash() - private set - - @Serializable - private class GithubCommitsResponse(val sha: String) - - private suspend fun requestLatestGithubSha(): String? { - if (RepoManager.Config.branch == "prerelease") { - RepoManager.Config.branch = "master" - } - val response = - Firmament.httpClient.get("https://api.github.com/repos/${RepoManager.Config.username}/${RepoManager.Config.reponame}/commits/${RepoManager.Config.branch}") - if (response.status.value != 200) { - return null - } - return response.body().sha - } - - private suspend fun downloadGithubArchive(url: String): Path = withContext(IO) { - val response = Firmament.httpClient.get(url) - val targetFile = Files.createTempFile("firmament-repo", ".zip") - val outputChannel = Files.newByteChannel(targetFile, StandardOpenOption.CREATE, StandardOpenOption.WRITE) - response.bodyAsChannel().copyTo(outputChannel) - targetFile - } - - /** - * Downloads the latest repository from github, setting [latestSavedVersionHash]. - * @return true, if an update was performed, false, otherwise (no update needed, or wasn't able to complete update) - */ - suspend fun downloadUpdate(force: Boolean): Boolean = withContext(CoroutineName("Repo Update Check")) { - val latestSha = requestLatestGithubSha() - if (latestSha == null) { - logger.warn("Could not request github API to retrieve latest REPO sha.") - return@withContext false - } - val currentSha = loadSavedVersionHash() - if (latestSha != currentSha || force) { - val requestUrl = - "https://github.com/${RepoManager.Config.username}/${RepoManager.Config.reponame}/archive/$latestSha.zip" - logger.info("Planning to upgrade repository from $currentSha to $latestSha from $requestUrl") - val zipFile = downloadGithubArchive(requestUrl) - logger.info("Download repository zip file to $zipFile. Deleting old repository") - withContext(IO) { repoSavedLocation.toFile().deleteRecursively() } - logger.info("Extracting new repository") - withContext(IO) { extractNewRepository(zipFile) } - logger.info("Repository loaded on disk.") - saveVersionHash(latestSha) - return@withContext true - } else { - logger.debug("Repository on latest sha $currentSha. Not performing update") - return@withContext false - } - } - - private fun extractNewRepository(zipFile: Path) { - repoSavedLocation.createDirectories() - ZipInputStream(zipFile.inputStream()).use { cis -> - while (true) { - val entry = cis.nextEntry ?: break - if (entry.isDirectory) continue - val extractedLocation = - repoSavedLocation.resolve( - entry.name.substringAfter('/', missingDelimiterValue = "") - ) - if (repoSavedLocation !in extractedLocation.iterate { it.parent }) { - logger.error("Firmament detected an invalid zip file. This is a potential security risk, please report this in the Firmament discord.") - throw RuntimeException("Firmament detected an invalid zip file. This is a potential security risk, please report this in the Firmament discord.") - } - extractedLocation.parent.createDirectories() - extractedLocation.outputStream().use { cis.copyTo(it) } - } - } - } - - -} diff --git a/src/main/kotlin/moe/nea/firmament/repo/RepoManager.kt b/src/main/kotlin/moe/nea/firmament/repo/RepoManager.kt deleted file mode 100644 index f0da397..0000000 --- a/src/main/kotlin/moe/nea/firmament/repo/RepoManager.kt +++ /dev/null @@ -1,145 +0,0 @@ -package moe.nea.firmament.repo - -import io.github.moulberry.repo.NEURepository -import io.github.moulberry.repo.NEURepositoryException -import io.github.moulberry.repo.data.NEUItem -import io.github.moulberry.repo.data.NEURecipe -import io.github.moulberry.repo.data.Rarity -import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents -import kotlinx.coroutines.launch -import net.minecraft.client.MinecraftClient -import net.minecraft.network.packet.s2c.play.SynchronizeRecipesS2CPacket -import net.minecraft.text.Text -import moe.nea.firmament.Firmament -import moe.nea.firmament.Firmament.logger -import moe.nea.firmament.events.ReloadRegistrationEvent -import moe.nea.firmament.gui.config.ManagedConfig -import moe.nea.firmament.rei.PetData -import moe.nea.firmament.util.MinecraftDispatcher -import moe.nea.firmament.util.SkyblockId - -object RepoManager { - object Config : ManagedConfig("repo") { - var username by string("username") { "NotEnoughUpdates" } - var reponame by string("reponame") { "NotEnoughUpdates-REPO" } - var branch by string("branch") { "master" } - val autoUpdate by toggle("autoUpdate") { true } - val reset by button("reset") { - username = "NotEnoughUpdates" - reponame = "NotEnoughUpdates-REPO" - branch = "master" - save() - } - - val disableItemGroups by toggle("disable-item-groups") { true } - val reload by button("reload") { - save() - RepoManager.reload() - } - val redownload by button("redownload") { - save() - RepoManager.launchAsyncUpdate(true) - } - } - - val currentDownloadedSha by RepoDownloadManager::latestSavedVersionHash - - var recentlyFailedToUpdateItemList = false - - val neuRepo: NEURepository = NEURepository.of(RepoDownloadManager.repoSavedLocation).apply { - registerReloadListener(ItemCache) - registerReloadListener(ExpLadders) - registerReloadListener(ItemNameLookup) - ReloadRegistrationEvent.publish(ReloadRegistrationEvent(this)) - registerReloadListener { - Firmament.coroutineScope.launch(MinecraftDispatcher) { - if (!trySendClientboundUpdateRecipesPacket()) { - logger.warn("Failed to issue a ClientboundUpdateRecipesPacket (to reload REI). This may lead to an outdated item list.") - recentlyFailedToUpdateItemList = true - } - } - } - } - - val essenceRecipeProvider = EssenceRecipeProvider() - val recipeCache = BetterRepoRecipeCache(essenceRecipeProvider) - - init { - neuRepo.registerReloadListener(essenceRecipeProvider) - neuRepo.registerReloadListener(recipeCache) - } - - fun getAllRecipes() = neuRepo.items.items.values.asSequence().flatMap { it.recipes } - - fun getRecipesFor(skyblockId: SkyblockId): Set = recipeCache.recipes[skyblockId] ?: setOf() - fun getUsagesFor(skyblockId: SkyblockId): Set = recipeCache.usages[skyblockId] ?: setOf() - - private fun trySendClientboundUpdateRecipesPacket(): Boolean { - return MinecraftClient.getInstance().world != null && MinecraftClient.getInstance().networkHandler?.onSynchronizeRecipes( - SynchronizeRecipesS2CPacket(mutableListOf()) - ) != null - } - - init { - ClientTickEvents.START_WORLD_TICK.register(ClientTickEvents.StartWorldTick { - if (recentlyFailedToUpdateItemList && trySendClientboundUpdateRecipesPacket()) - recentlyFailedToUpdateItemList = false - }) - } - - fun getNEUItem(skyblockId: SkyblockId): NEUItem? = neuRepo.items.getItemBySkyblockId(skyblockId.neuItem) - - fun launchAsyncUpdate(force: Boolean = false) { - Firmament.coroutineScope.launch { - ItemCache.ReloadProgressHud.reportProgress("Downloading", 0, -1) // TODO: replace with a proper boundy bar - ItemCache.ReloadProgressHud.isEnabled = true - try { - RepoDownloadManager.downloadUpdate(force) - ItemCache.ReloadProgressHud.reportProgress("Download complete", 1, 1) - } finally { - ItemCache.ReloadProgressHud.isEnabled = false - } - reload() - } - } - - fun reload() { - try { - ItemCache.ReloadProgressHud.reportProgress("Reloading from Disk", - 0, - -1) // TODO: replace with a proper boundy bar - ItemCache.ReloadProgressHud.isEnabled = true - neuRepo.reload() - } catch (exc: NEURepositoryException) { - MinecraftClient.getInstance().player?.sendMessage( - Text.literal("Failed to reload repository. This will result in some mod features not working.") - ) - ItemCache.ReloadProgressHud.isEnabled = false - exc.printStackTrace() - } - } - - fun initialize() { - if (Config.autoUpdate) { - launchAsyncUpdate() - } else { - reload() - } - } - - fun getPotentialStubPetData(skyblockId: SkyblockId): PetData? { - val parts = skyblockId.neuItem.split(";") - if (parts.size != 2) { - return null - } - val (petId, rarityIndex) = parts - if (!rarityIndex.all { it.isDigit() }) { - return null - } - val intIndex = rarityIndex.toInt() - if (intIndex !in Rarity.values().indices) return null - if (petId !in neuRepo.constants.petNumbers) return null - return PetData(Rarity.values()[intIndex], petId, 0.0, true) - } - -} diff --git a/src/main/kotlin/moe/nea/firmament/repo/RepoModResourcePack.kt b/src/main/kotlin/moe/nea/firmament/repo/RepoModResourcePack.kt deleted file mode 100644 index f92fe4f..0000000 --- a/src/main/kotlin/moe/nea/firmament/repo/RepoModResourcePack.kt +++ /dev/null @@ -1,126 +0,0 @@ - -package moe.nea.firmament.repo - -import java.io.InputStream -import java.nio.file.Files -import java.nio.file.Path -import java.util.* -import net.fabricmc.fabric.api.resource.ModResourcePack -import net.fabricmc.loader.api.FabricLoader -import net.fabricmc.loader.api.metadata.ModMetadata -import kotlin.io.path.exists -import kotlin.io.path.isRegularFile -import kotlin.io.path.relativeTo -import kotlin.streams.asSequence -import net.minecraft.resource.AbstractFileResourcePack -import net.minecraft.resource.InputSupplier -import net.minecraft.resource.NamespaceResourceManager -import net.minecraft.resource.Resource -import net.minecraft.resource.ResourcePack -import net.minecraft.resource.ResourcePackInfo -import net.minecraft.resource.ResourcePackSource -import net.minecraft.resource.ResourceType -import net.minecraft.resource.metadata.ResourceMetadata -import net.minecraft.resource.metadata.ResourceMetadataReader -import net.minecraft.text.Text -import net.minecraft.util.Identifier -import net.minecraft.util.PathUtil -import moe.nea.firmament.Firmament - -class RepoModResourcePack(val basePath: Path) : ModResourcePack { - companion object { - fun append(packs: MutableList) { - Firmament.logger.info("Registering mod resource pack") - packs.add(RepoModResourcePack(RepoDownloadManager.repoSavedLocation)) - } - - fun createResourceDirectly(identifier: Identifier): Optional { - val pack = RepoModResourcePack(RepoDownloadManager.repoSavedLocation) - return Optional.of( - Resource( - pack, - pack.open(ResourceType.CLIENT_RESOURCES, identifier) ?: return Optional.empty() - ) { - val base = - pack.open(ResourceType.CLIENT_RESOURCES, identifier.withPath(identifier.path + ".mcmeta")) - if (base == null) - ResourceMetadata.NONE - else - NamespaceResourceManager.loadMetadata(base) - } - ) - } - } - - override fun close() { - } - - override fun openRoot(vararg segments: String): InputSupplier? { - return getFile(segments)?.let { InputSupplier.create(it) } - } - - fun getFile(segments: Array): Path? { - PathUtil.validatePath(*segments) - val path = segments.fold(basePath, Path::resolve) - if (!path.isRegularFile()) return null - return path - } - - override fun open(type: ResourceType?, id: Identifier): InputSupplier? { - if (type != ResourceType.CLIENT_RESOURCES) return null - if (id.namespace != "neurepo") return null - val file = getFile(id.path.split("/").toTypedArray()) - return file?.let { InputSupplier.create(it) } - } - - override fun findResources( - type: ResourceType?, - namespace: String, - prefix: String, - consumer: ResourcePack.ResultConsumer - ) { - if (namespace != "neurepo") return - if (type != ResourceType.CLIENT_RESOURCES) return - - val prefixPath = basePath.resolve(prefix) - if (!prefixPath.exists()) - return - Files.walk(prefixPath) - .asSequence() - .map { it.relativeTo(basePath) } - .forEach { - consumer.accept(Identifier.of("neurepo", it.toString()), InputSupplier.create(it)) - } - } - - override fun getNamespaces(type: ResourceType?): Set { - if (type != ResourceType.CLIENT_RESOURCES) return emptySet() - return setOf("neurepo") - } - - override fun parseMetadata(metaReader: ResourceMetadataReader): T? { - return AbstractFileResourcePack.parseMetadata( - metaReader, """ -{ - "pack": { - "pack_format": 12, - "description": "NEU Repo Resources" - } -} -""".trimIndent().byteInputStream() - ) - } - - override fun getInfo(): ResourcePackInfo { - return ResourcePackInfo("neurepo", Text.literal("NEU Repo"), ResourcePackSource.BUILTIN, Optional.empty()) - } - - override fun getFabricModMetadata(): ModMetadata { - return FabricLoader.getInstance().getModContainer("firmament") - .get().metadata - } - - override fun createOverlay(overlay: String): ModResourcePack { - return RepoModResourcePack(basePath.resolve(overlay)) - } -} diff --git a/src/main/kotlin/moe/nea/firmament/util/Base64Util.kt b/src/main/kotlin/moe/nea/firmament/util/Base64Util.kt deleted file mode 100644 index 44bcdfd..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/Base64Util.kt +++ /dev/null @@ -1,10 +0,0 @@ - -package moe.nea.firmament.util - -object Base64Util { - fun String.padToValidBase64(): String { - val align = this.length % 4 - if (align == 0) return this - return this + "=".repeat(4 - align) - } -} diff --git a/src/main/kotlin/moe/nea/firmament/util/BazaarPriceStrategy.kt b/src/main/kotlin/moe/nea/firmament/util/BazaarPriceStrategy.kt deleted file mode 100644 index 002eedb..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/BazaarPriceStrategy.kt +++ /dev/null @@ -1,19 +0,0 @@ - -package moe.nea.firmament.util - -import moe.nea.firmament.repo.HypixelStaticData - -enum class BazaarPriceStrategy { - BUY_ORDER, - SELL_ORDER, - NPC_SELL; - - fun getSellPrice(skyblockId: SkyblockId): Double { - val bazaarEntry = HypixelStaticData.bazaarData[skyblockId] ?: return 0.0 - return when (this) { - BUY_ORDER -> bazaarEntry.quickStatus.sellPrice - SELL_ORDER -> bazaarEntry.quickStatus.buyPrice - NPC_SELL -> TODO() - } - } -} diff --git a/src/main/kotlin/moe/nea/firmament/util/ClipboardUtils.kt b/src/main/kotlin/moe/nea/firmament/util/ClipboardUtils.kt deleted file mode 100644 index 7b9b836..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/ClipboardUtils.kt +++ /dev/null @@ -1,24 +0,0 @@ - - -package moe.nea.firmament.util - -import moe.nea.firmament.Firmament - -object ClipboardUtils { - fun setTextContent(string: String) { - try { - MC.keyboard.clipboard = string.ifEmpty { " " } - } catch (e: Exception) { - Firmament.logger.error("Could not write clipboard", e) - } - } - - fun getTextContents(): String { - try { - return MC.keyboard.clipboard ?: "" - } catch (e: Exception) { - Firmament.logger.error("Could not read clipboard", e) - return "" - } - } -} diff --git a/src/main/kotlin/moe/nea/firmament/util/CommonSoundEffects.kt b/src/main/kotlin/moe/nea/firmament/util/CommonSoundEffects.kt deleted file mode 100644 index a97a2cb..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/CommonSoundEffects.kt +++ /dev/null @@ -1,26 +0,0 @@ - - -package moe.nea.firmament.util - -import net.minecraft.client.sound.PositionedSoundInstance -import net.minecraft.sound.SoundEvent -import net.minecraft.util.Identifier - -// TODO: Replace these with custom sound events that just re use the vanilla ogg s -object CommonSoundEffects { - fun playSound(identifier: Identifier) { - MC.soundManager.play(PositionedSoundInstance.master(SoundEvent.of(identifier), 1F)) - } - - fun playFailure() { - playSound(Identifier.of("minecraft", "block.anvil.place")) - } - - fun playSuccess() { - playDing() - } - - fun playDing() { - playSound(Identifier.of("minecraft", "entity.arrow.hit_player")) - } -} diff --git a/src/main/kotlin/moe/nea/firmament/util/DurabilityBarEvent.kt b/src/main/kotlin/moe/nea/firmament/util/DurabilityBarEvent.kt deleted file mode 100644 index 993462c..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/DurabilityBarEvent.kt +++ /dev/null @@ -1,20 +0,0 @@ - -package moe.nea.firmament.util - -import me.shedaniel.math.Color -import net.minecraft.item.ItemStack -import moe.nea.firmament.events.FirmamentEvent -import moe.nea.firmament.events.FirmamentEventBus - -data class DurabilityBarEvent( - val item: ItemStack, -) : FirmamentEvent() { - data class DurabilityBar( - val color: Color, - val percentage: Float, - ) - - var barOverride: DurabilityBar? = null - - companion object : FirmamentEventBus() -} diff --git a/src/main/kotlin/moe/nea/firmament/util/ErrorBoundary.kt b/src/main/kotlin/moe/nea/firmament/util/ErrorBoundary.kt deleted file mode 100644 index fbc5b37..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/ErrorBoundary.kt +++ /dev/null @@ -1,10 +0,0 @@ - - -package moe.nea.firmament.util - - -fun errorBoundary(block: () -> T): T? { - // TODO: implement a proper error boundary here to avoid crashing minecraft code - return block() -} - diff --git a/src/main/kotlin/moe/nea/firmament/util/FirmFormatters.kt b/src/main/kotlin/moe/nea/firmament/util/FirmFormatters.kt deleted file mode 100644 index c3bdd16..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/FirmFormatters.kt +++ /dev/null @@ -1,59 +0,0 @@ - - -package moe.nea.firmament.util - -import com.google.common.math.IntMath.pow -import kotlin.math.absoluteValue -import kotlin.time.Duration - -object FirmFormatters { - fun formatCommas(int: Int, segments: Int = 3): String = formatCommas(int.toLong(), segments) - fun formatCommas(long: Long, segments: Int = 3): String { - val α = long / 1000 - if (α != 0L) { - return formatCommas(α, segments) + "," + (long - α * 1000).toString().padStart(3, '0') - } - return long.toString() - } - - fun formatCommas(float: Float, fractionalDigits: Int): String = formatCommas(float.toDouble(), fractionalDigits) - fun formatCommas(double: Double, fractionalDigits: Int): String { - val long = double.toLong() - val δ = (double - long).absoluteValue - val μ = pow(10, fractionalDigits) - val digits = (μ * δ).toInt().toString().padStart(fractionalDigits, '0').trimEnd('0') - return formatCommas(long) + (if (digits.isEmpty()) "" else ".$digits") - } - - fun formatDistance(distance: Double): String { - if (distance < 10) - return "%.1fm".format(distance) - return "%dm".format(distance.toInt()) - } - - fun formatTimespan(duration: Duration, millis: Boolean = false): String { - if (duration.isInfinite()) { - return if (duration.isPositive()) "∞" - else "-∞" - } - val sb = StringBuilder() - if (duration.isNegative()) sb.append("-") - duration.toComponents { days, hours, minutes, seconds, nanoseconds -> - if (days > 0) { - sb.append(days).append("d") - } - if (hours > 0) { - sb.append(hours).append("h") - } - if (minutes > 0) { - sb.append(minutes).append("m") - } - sb.append(seconds).append("s") - if (millis) { - sb.append(nanoseconds / 1_000_000).append("ms") - } - } - return sb.toString() - } - -} diff --git a/src/main/kotlin/moe/nea/firmament/util/FragmentGuiScreen.kt b/src/main/kotlin/moe/nea/firmament/util/FragmentGuiScreen.kt deleted file mode 100644 index 5e13d51..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/FragmentGuiScreen.kt +++ /dev/null @@ -1,93 +0,0 @@ - - -package moe.nea.firmament.util - -import io.github.notenoughupdates.moulconfig.gui.GuiContext -import me.shedaniel.math.Dimension -import me.shedaniel.math.Point -import me.shedaniel.math.Rectangle -import net.minecraft.client.gui.DrawContext -import net.minecraft.client.gui.screen.Screen -import net.minecraft.text.Text - -abstract class FragmentGuiScreen( - val dismissOnOutOfBounds: Boolean = true -) : Screen(Text.literal("")) { - var popup: MoulConfigFragment? = null - - fun createPopup(context: GuiContext, position: Point) { - popup = MoulConfigFragment(context, position) { popup = null } - } - - override fun render(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) { - super.render(context, mouseX, mouseY, delta) - context.matrices.push() - context.matrices.translate(0f, 0f, 1000f) - popup?.render(context, mouseX, mouseY, delta) - context.matrices.pop() - } - - private inline fun ifPopup(ifYes: (MoulConfigFragment) -> Unit): Boolean { - val p = popup ?: return false - ifYes(p) - return true - } - - override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean { - return ifPopup { - it.keyPressed(keyCode, scanCode, modifiers) - } - } - - override fun keyReleased(keyCode: Int, scanCode: Int, modifiers: Int): Boolean { - return ifPopup { - it.keyReleased(keyCode, scanCode, modifiers) - } - } - - override fun mouseMoved(mouseX: Double, mouseY: Double) { - ifPopup { it.mouseMoved(mouseX, mouseY) } - } - - override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean { - return ifPopup { - it.mouseReleased(mouseX, mouseY, button) - } - } - - override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean { - return ifPopup { - it.mouseDragged(mouseX, mouseY, button, deltaX, deltaY) - } - } - - override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean { - return ifPopup { - if (!Rectangle( - it.position, - Dimension(it.context.root.width, it.context.root.height) - ).contains(Point(mouseX, mouseY)) - && dismissOnOutOfBounds - ) { - popup = null - } else { - it.mouseClicked(mouseX, mouseY, button) - } - }|| super.mouseClicked(mouseX, mouseY, button) - } - - override fun charTyped(chr: Char, modifiers: Int): Boolean { - return ifPopup { it.charTyped(chr, modifiers) } - } - - override fun mouseScrolled( - mouseX: Double, - mouseY: Double, - horizontalAmount: Double, - verticalAmount: Double - ): Boolean { - return ifPopup { - it.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount) - } - } -} diff --git a/src/main/kotlin/moe/nea/firmament/util/GetRectangle.kt b/src/main/kotlin/moe/nea/firmament/util/GetRectangle.kt deleted file mode 100644 index ec64f31..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/GetRectangle.kt +++ /dev/null @@ -1,17 +0,0 @@ - - -package moe.nea.firmament.util - -import me.shedaniel.math.Rectangle -import moe.nea.firmament.mixins.accessor.AccessorHandledScreen -import net.minecraft.client.gui.screen.ingame.HandledScreen - -fun HandledScreen<*>.getRectangle(): Rectangle { - this as AccessorHandledScreen - return Rectangle( - getX_Firmament(), - getY_Firmament(), - getBackgroundWidth_Firmament(), - getBackgroundHeight_Firmament() - ) -} diff --git a/src/main/kotlin/moe/nea/firmament/util/HoveredItemStack.kt b/src/main/kotlin/moe/nea/firmament/util/HoveredItemStack.kt deleted file mode 100644 index 47a59d0..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/HoveredItemStack.kt +++ /dev/null @@ -1,31 +0,0 @@ - - -package moe.nea.firmament.util - -import me.shedaniel.math.impl.PointHelper -import me.shedaniel.rei.api.client.REIRuntime -import me.shedaniel.rei.api.client.gui.widgets.Slot -import me.shedaniel.rei.api.client.registry.screen.ScreenRegistry -import net.minecraft.client.gui.Element -import net.minecraft.client.gui.ParentElement -import net.minecraft.client.gui.screen.ingame.HandledScreen -import net.minecraft.item.ItemStack -import moe.nea.firmament.mixins.accessor.AccessorHandledScreen - - -val HandledScreen<*>.focusedItemStack: ItemStack? - get() { - this as AccessorHandledScreen - val vanillaSlot = this.focusedSlot_Firmament?.stack - if (vanillaSlot != null) return vanillaSlot - val focusedSlot = ScreenRegistry.getInstance().getFocusedStack(this, PointHelper.ofMouse()) - if (focusedSlot != null) return focusedSlot.cheatsAs().value - var baseElement: Element? = REIRuntime.getInstance().overlay.orElse(null) - val mx = PointHelper.getMouseFloatingX() - val my = PointHelper.getMouseFloatingY() - while (true) { - if (baseElement is Slot) return baseElement.currentEntry.cheatsAs().value - if (baseElement !is ParentElement) return null - baseElement = baseElement.hoveredElement(mx, my).orElse(null) - } - } diff --git a/src/main/kotlin/moe/nea/firmament/util/IdentifierSerializer.kt b/src/main/kotlin/moe/nea/firmament/util/IdentifierSerializer.kt deleted file mode 100644 index 65c5b1c..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/IdentifierSerializer.kt +++ /dev/null @@ -1,25 +0,0 @@ - -package moe.nea.firmament.util - -import kotlinx.serialization.KSerializer -import kotlinx.serialization.builtins.serializer -import kotlinx.serialization.descriptors.PrimitiveKind -import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder -import net.minecraft.util.Identifier - -object IdentifierSerializer : KSerializer { - val delegateSerializer = String.serializer() - override val descriptor: SerialDescriptor - get() = PrimitiveSerialDescriptor("Identifier", PrimitiveKind.STRING) - - override fun deserialize(decoder: Decoder): Identifier { - return Identifier.of(decoder.decodeSerializableValue(delegateSerializer)) - } - - override fun serialize(encoder: Encoder, value: Identifier) { - encoder.encodeSerializableValue(delegateSerializer, value.toString()) - } -} diff --git a/src/main/kotlin/moe/nea/firmament/util/IdentityCharacteristics.kt b/src/main/kotlin/moe/nea/firmament/util/IdentityCharacteristics.kt deleted file mode 100644 index f6054c4..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/IdentityCharacteristics.kt +++ /dev/null @@ -1,15 +0,0 @@ - - -package moe.nea.firmament.util - -class IdentityCharacteristics(val value: T) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is IdentityCharacteristics<*>) return false - return value === other.value - } - - override fun hashCode(): Int { - return System.identityHashCode(value) - } -} diff --git a/src/main/kotlin/moe/nea/firmament/util/ItemUtil.kt b/src/main/kotlin/moe/nea/firmament/util/ItemUtil.kt deleted file mode 100644 index 40d6198..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/ItemUtil.kt +++ /dev/null @@ -1,26 +0,0 @@ - - -package moe.nea.firmament.util - -import net.minecraft.item.ItemStack -import net.minecraft.nbt.NbtCompound -import net.minecraft.nbt.NbtList -import net.minecraft.text.Text -import moe.nea.firmament.util.item.loreAccordingToNbt - - -fun ItemStack.appendLore(args: List) { - if (args.isEmpty()) return - modifyLore { - val loreList = loreAccordingToNbt.toMutableList() - for (arg in args) { - loreList.add(arg) - } - loreList - } -} - -fun ItemStack.modifyLore(update: (List) -> List) { - val loreList = loreAccordingToNbt - loreAccordingToNbt = update(loreList) -} diff --git a/src/main/kotlin/moe/nea/firmament/util/LegacyFormattingCode.kt b/src/main/kotlin/moe/nea/firmament/util/LegacyFormattingCode.kt deleted file mode 100644 index 44bacfc..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/LegacyFormattingCode.kt +++ /dev/null @@ -1,35 +0,0 @@ - - -package moe.nea.firmament.util - -import net.minecraft.util.Formatting - -enum class LegacyFormattingCode(val label: String, val char: Char, val index: Int) { - BLACK("BLACK", '0', 0), - DARK_BLUE("DARK_BLUE", '1', 1), - DARK_GREEN("DARK_GREEN", '2', 2), - DARK_AQUA("DARK_AQUA", '3', 3), - DARK_RED("DARK_RED", '4', 4), - DARK_PURPLE("DARK_PURPLE", '5', 5), - GOLD("GOLD", '6', 6), - GRAY("GRAY", '7', 7), - DARK_GRAY("DARK_GRAY", '8', 8), - BLUE("BLUE", '9', 9), - GREEN("GREEN", 'a', 10), - AQUA("AQUA", 'b', 11), - RED("RED", 'c', 12), - LIGHT_PURPLE("LIGHT_PURPLE", 'd', 13), - YELLOW("YELLOW", 'e', 14), - WHITE("WHITE", 'f', 15), - OBFUSCATED("OBFUSCATED", 'k', -1), - BOLD("BOLD", 'l', -1), - STRIKETHROUGH("STRIKETHROUGH", 'm', -1), - UNDERLINE("UNDERLINE", 'n', -1), - ITALIC("ITALIC", 'o', -1), - RESET("RESET", 'r', -1); - - val modern = Formatting.byCode(char)!! - - val formattingCode = "§$char" - -} diff --git a/src/main/kotlin/moe/nea/firmament/util/LegacyTagParser.kt b/src/main/kotlin/moe/nea/firmament/util/LegacyTagParser.kt deleted file mode 100644 index 4e08da1..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/LegacyTagParser.kt +++ /dev/null @@ -1,245 +0,0 @@ - - -package moe.nea.firmament.util - -import java.util.* -import net.minecraft.nbt.AbstractNbtNumber -import net.minecraft.nbt.NbtByte -import net.minecraft.nbt.NbtCompound -import net.minecraft.nbt.NbtDouble -import net.minecraft.nbt.NbtElement -import net.minecraft.nbt.NbtFloat -import net.minecraft.nbt.NbtInt -import net.minecraft.nbt.NbtList -import net.minecraft.nbt.NbtLong -import net.minecraft.nbt.NbtShort -import net.minecraft.nbt.NbtString - -class LegacyTagParser private constructor(string: String) { - data class TagParsingException(val baseString: String, val offset: Int, val mes0: String) : - Exception("$mes0 at $offset in `$baseString`.") - - class StringRacer(val backing: String) { - var idx = 0 - val stack = Stack() - - fun pushState() { - stack.push(idx) - } - - fun popState() { - idx = stack.pop() - } - - fun discardState() { - stack.pop() - } - - fun peek(count: Int): String { - return backing.substring(minOf(idx, backing.length), minOf(idx + count, backing.length)) - } - - fun finished(): Boolean { - return peek(1).isEmpty() - } - - fun peekReq(count: Int): String? { - val p = peek(count) - if (p.length != count) - return null - return p - } - - fun consumeCountReq(count: Int): String? { - val p = peekReq(count) - if (p != null) - idx += count - return p - } - - fun tryConsume(string: String): Boolean { - val p = peek(string.length) - if (p != string) - return false - idx += p.length - return true - } - - fun consumeWhile(shouldConsumeThisString: (String) -> Boolean): String { - var lastString: String = "" - while (true) { - val nextString = lastString + peek(1) - if (!shouldConsumeThisString(nextString)) { - return lastString - } - idx++ - lastString = nextString - } - } - - fun expect(search: String, errorMessage: String) { - if (!tryConsume(search)) - error(errorMessage) - } - - fun error(errorMessage: String): Nothing { - throw TagParsingException(backing, idx, errorMessage) - } - - } - - val racer = StringRacer(string) - val baseTag = parseTag() - - companion object { - val digitRange = "0123456789-" - fun parse(string: String): NbtCompound { - return LegacyTagParser(string).baseTag - } - } - - fun skipWhitespace() { - racer.consumeWhile { Character.isWhitespace(it.last()) } // Only check last since other chars are always checked before. - } - - fun parseTag(): NbtCompound { - skipWhitespace() - racer.expect("{", "Expected '{’ at start of tag") - skipWhitespace() - val tag = NbtCompound() - while (!racer.tryConsume("}")) { - skipWhitespace() - val lhs = parseIdentifier() - skipWhitespace() - racer.expect(":", "Expected ':' after identifier in tag") - skipWhitespace() - val rhs = parseAny() - tag.put(lhs, rhs) - racer.tryConsume(",") - skipWhitespace() - } - return tag - } - - private fun parseAny(): NbtElement { - skipWhitespace() - val nextChar = racer.peekReq(1) ?: racer.error("Expected new object, found EOF") - return when { - nextChar == "{" -> parseTag() - nextChar == "[" -> parseList() - nextChar == "\"" -> parseStringTag() - nextChar.first() in (digitRange) -> parseNumericTag() - else -> racer.error("Unexpected token found. Expected start of new element") - } - } - - fun parseList(): NbtList { - skipWhitespace() - racer.expect("[", "Expected '[' at start of tag") - skipWhitespace() - val list = NbtList() - while (!racer.tryConsume("]")) { - skipWhitespace() - racer.pushState() - val lhs = racer.consumeWhile { it.all { it in digitRange } } - skipWhitespace() - if (!racer.tryConsume(":") || lhs.isEmpty()) { // No prefixed 0: - racer.popState() - list.add(parseAny()) // Reparse our number (or not a number) as actual tag - } else { - racer.discardState() - skipWhitespace() - list.add(parseAny()) // Ignore prefix indexes. They should not be generated out of order by any vanilla implementation (which is what NEU should export). Instead append where it appears in order. - } - skipWhitespace() - racer.tryConsume(",") - } - return list - } - - fun parseQuotedString(): String { - skipWhitespace() - racer.expect("\"", "Expected '\"' at string start") - val sb = StringBuilder() - while (true) { - when (val peek = racer.consumeCountReq(1)) { - "\"" -> break - "\\" -> { - val escaped = racer.consumeCountReq(1) ?: racer.error("Unfinished backslash escape") - if (escaped != "\"" && escaped != "\\") { - // Surprisingly i couldn't find unicode escapes to be generated by the original minecraft 1.8.9 implementation - racer.idx-- - racer.error("Invalid backslash escape '$escaped'") - } - sb.append(escaped) - } - - null -> racer.error("Unfinished string") - else -> { - sb.append(peek) - } - } - } - return sb.toString() - } - - fun parseStringTag(): NbtString { - return NbtString.of(parseQuotedString()) - } - - object Patterns { - val DOUBLE = "([-+]?[0-9]*\\.?[0-9]+)[d|D]".toRegex() - val FLOAT = "([-+]?[0-9]*\\.?[0-9]+)[f|F]".toRegex() - val BYTE = "([-+]?[0-9]+)[b|B]".toRegex() - val LONG = "([-+]?[0-9]+)[l|L]".toRegex() - val SHORT = "([-+]?[0-9]+)[s|S]".toRegex() - val INTEGER = "([-+]?[0-9]+)".toRegex() - val DOUBLE_UNTYPED = "([-+]?[0-9]*\\.?[0-9]+)".toRegex() - val ROUGH_PATTERN = "[-+]?[0-9]*\\.?[0-9]*[dDbBfFlLsS]?".toRegex() - } - - fun parseNumericTag(): AbstractNbtNumber { - skipWhitespace() - val textForm = racer.consumeWhile { Patterns.ROUGH_PATTERN.matchEntire(it) != null } - if (textForm.isEmpty()) { - racer.error("Expected numeric tag (starting with either -, +, . or a digit") - } - val floatMatch = Patterns.FLOAT.matchEntire(textForm) - if (floatMatch != null) { - return NbtFloat.of(floatMatch.groups[1]!!.value.toFloat()) - } - val byteMatch = Patterns.BYTE.matchEntire(textForm) - if (byteMatch != null) { - return NbtByte.of(byteMatch.groups[1]!!.value.toByte()) - } - val longMatch = Patterns.LONG.matchEntire(textForm) - if (longMatch != null) { - return NbtLong.of(longMatch.groups[1]!!.value.toLong()) - } - val shortMatch = Patterns.SHORT.matchEntire(textForm) - if (shortMatch != null) { - return NbtShort.of(shortMatch.groups[1]!!.value.toShort()) - } - val integerMatch = Patterns.INTEGER.matchEntire(textForm) - if (integerMatch != null) { - return NbtInt.of(integerMatch.groups[1]!!.value.toInt()) - } - val doubleMatch = Patterns.DOUBLE.matchEntire(textForm) ?: Patterns.DOUBLE_UNTYPED.matchEntire(textForm) - if (doubleMatch != null) { - return NbtDouble.of(doubleMatch.groups[1]!!.value.toDouble()) - } - throw IllegalStateException("Could not properly parse numeric tag '$textForm', despite passing rough verification. This is a bug in the LegacyTagParser") - } - - private fun parseIdentifier(): String { - skipWhitespace() - if (racer.peek(1) == "\"") { - return parseQuotedString() - } - return racer.consumeWhile { - val x = it.last() - x != ':' && !Character.isWhitespace(x) - } - } - -} diff --git a/src/main/kotlin/moe/nea/firmament/util/LoadResource.kt b/src/main/kotlin/moe/nea/firmament/util/LoadResource.kt deleted file mode 100644 index 4bc8704..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/LoadResource.kt +++ /dev/null @@ -1,20 +0,0 @@ - -package moe.nea.firmament.util - -import java.io.InputStream -import kotlin.io.path.inputStream -import kotlin.jvm.optionals.getOrNull -import net.minecraft.util.Identifier -import moe.nea.firmament.repo.RepoDownloadManager - - -fun Identifier.openFirmamentResource(): InputStream { - val resource = MC.resourceManager.getResource(this).getOrNull() - if (resource == null) { - if (namespace == "neurepo") - return RepoDownloadManager.repoSavedLocation.resolve(path).inputStream() - error("Could not read resource $this") - } - return resource.inputStream -} - diff --git a/src/main/kotlin/moe/nea/firmament/util/Locraw.kt b/src/main/kotlin/moe/nea/firmament/util/Locraw.kt deleted file mode 100644 index 9778bc7..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/Locraw.kt +++ /dev/null @@ -1,12 +0,0 @@ - - -package moe.nea.firmament.util - -import kotlinx.serialization.Serializable -import kotlinx.serialization.Transient - -@Serializable -data class Locraw(val server: String, val gametype: String? = null, val mode: String? = null, val map: String? = null) { - @Transient - val skyblockLocation = if (gametype == "SKYBLOCK") mode?.let(SkyBlockIsland::forMode) else null -} diff --git a/src/main/kotlin/moe/nea/firmament/util/LogIfNull.kt b/src/main/kotlin/moe/nea/firmament/util/LogIfNull.kt deleted file mode 100644 index 600c5e6..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/LogIfNull.kt +++ /dev/null @@ -1,8 +0,0 @@ - -package moe.nea.firmament.util - - -fun runNull(block: () -> Unit): Nothing? { - block() - return null -} diff --git a/src/main/kotlin/moe/nea/firmament/util/MC.kt b/src/main/kotlin/moe/nea/firmament/util/MC.kt deleted file mode 100644 index b0d3056..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/MC.kt +++ /dev/null @@ -1,94 +0,0 @@ -package moe.nea.firmament.util - -import io.github.moulberry.repo.data.Coordinate -import java.util.concurrent.ConcurrentLinkedQueue -import net.minecraft.client.MinecraftClient -import net.minecraft.client.gui.screen.ingame.HandledScreen -import net.minecraft.client.render.WorldRenderer -import net.minecraft.network.packet.c2s.play.CommandExecutionC2SPacket -import net.minecraft.registry.BuiltinRegistries -import net.minecraft.registry.RegistryKeys -import net.minecraft.registry.RegistryWrapper -import net.minecraft.resource.ReloadableResourceManagerImpl -import net.minecraft.text.Text -import net.minecraft.util.math.BlockPos -import moe.nea.firmament.events.TickEvent - -object MC { - - private val messageQueue = ConcurrentLinkedQueue() - - init { - TickEvent.subscribe { - while (true) { - inGameHud.chatHud.addMessage(messageQueue.poll() ?: break) - } - while (true) { - (nextTickTodos.poll() ?: break).invoke() - } - } - } - - fun sendChat(text: Text) { - if (instance.isOnThread) - inGameHud.chatHud.addMessage(text) - else - messageQueue.add(text) - } - - fun sendServerCommand(command: String) { - val nh = player?.networkHandler ?: return - nh.sendPacket( - CommandExecutionC2SPacket( - command, - ) - ) - } - - fun sendServerChat(text: String) { - player?.networkHandler?.sendChatMessage(text) - } - - fun sendCommand(command: String) { - player?.networkHandler?.sendCommand(command) - } - - fun onMainThread(block: () -> Unit) { - if (instance.isOnThread) - block() - else - instance.send(block) - } - - private val nextTickTodos = ConcurrentLinkedQueue<() -> Unit>() - fun nextTick(function: () -> Unit) { - nextTickTodos.add(function) - } - - - inline val resourceManager get() = (instance.resourceManager as ReloadableResourceManagerImpl) - inline val worldRenderer: WorldRenderer get() = instance.worldRenderer - inline val networkHandler get() = player?.networkHandler - inline val instance get() = MinecraftClient.getInstance() - inline val keyboard get() = instance.keyboard - inline val textureManager get() = instance.textureManager - inline val inGameHud get() = instance.inGameHud - inline val font get() = instance.textRenderer - inline val soundManager get() = instance.soundManager - inline val player get() = instance.player - inline val camera get() = instance.cameraEntity - inline val guiAtlasManager get() = instance.guiAtlasManager - inline val world get() = instance.world - inline var screen - get() = instance.currentScreen - set(value) = instance.setScreen(value) - inline val handledScreen: HandledScreen<*>? get() = instance.currentScreen as? HandledScreen<*> - inline val window get() = instance.window - inline val currentRegistries: RegistryWrapper.WrapperLookup? get() = world?.registryManager - val defaultRegistries: RegistryWrapper.WrapperLookup = BuiltinRegistries.createWrapperLookup() - val defaultItems = defaultRegistries.getWrapperOrThrow(RegistryKeys.ITEM) -} - - -val Coordinate.blockPos: BlockPos - get() = BlockPos(x, y, z) diff --git a/src/main/kotlin/moe/nea/firmament/util/MinecraftDispatcher.kt b/src/main/kotlin/moe/nea/firmament/util/MinecraftDispatcher.kt deleted file mode 100644 index d1f22a9..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/MinecraftDispatcher.kt +++ /dev/null @@ -1,8 +0,0 @@ - - -package moe.nea.firmament.util - -import kotlinx.coroutines.asCoroutineDispatcher -import net.minecraft.client.MinecraftClient - -val MinecraftDispatcher by lazy { MinecraftClient.getInstance().asCoroutineDispatcher() } diff --git a/src/main/kotlin/moe/nea/firmament/util/MoulConfigFragment.kt b/src/main/kotlin/moe/nea/firmament/util/MoulConfigFragment.kt deleted file mode 100644 index 36132cd..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/MoulConfigFragment.kt +++ /dev/null @@ -1,44 +0,0 @@ - - -package moe.nea.firmament.util - -import io.github.notenoughupdates.moulconfig.gui.GuiComponentWrapper -import io.github.notenoughupdates.moulconfig.gui.GuiContext -import io.github.notenoughupdates.moulconfig.gui.GuiImmediateContext -import me.shedaniel.math.Point -import net.minecraft.client.gui.DrawContext - -class MoulConfigFragment( - context: GuiContext, - val position: Point, - val dismiss: () -> Unit -) : GuiComponentWrapper(context) { - init { - this.init(MC.instance, MC.screen!!.width, MC.screen!!.height) - } - - override fun createContext(drawContext: DrawContext?): GuiImmediateContext { - val oldContext = super.createContext(drawContext) - return oldContext.translated( - position.x, - position.y, - context.root.width, - context.root.height, - ) - } - - - override fun render(drawContext: DrawContext?, i: Int, j: Int, f: Float) { - val ctx = createContext(drawContext) - val m = drawContext!!.matrices - m.push() - m.translate(position.x.toFloat(), position.y.toFloat(), 0F) - context.root.render(ctx) - m.pop() - ctx.renderContext.doDrawTooltip() - } - - override fun close() { - dismiss() - } -} diff --git a/src/main/kotlin/moe/nea/firmament/util/MoulConfigUtils.kt b/src/main/kotlin/moe/nea/firmament/util/MoulConfigUtils.kt deleted file mode 100644 index 00561d1..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/MoulConfigUtils.kt +++ /dev/null @@ -1,230 +0,0 @@ - - -package moe.nea.firmament.util - -import io.github.notenoughupdates.moulconfig.common.MyResourceLocation -import io.github.notenoughupdates.moulconfig.gui.CloseEventListener -import io.github.notenoughupdates.moulconfig.gui.GuiComponentWrapper -import io.github.notenoughupdates.moulconfig.gui.GuiContext -import io.github.notenoughupdates.moulconfig.observer.GetSetter -import io.github.notenoughupdates.moulconfig.xml.ChildCount -import io.github.notenoughupdates.moulconfig.xml.XMLContext -import io.github.notenoughupdates.moulconfig.xml.XMLGuiLoader -import io.github.notenoughupdates.moulconfig.xml.XMLUniverse -import io.github.notenoughupdates.moulconfig.xml.XSDGenerator -import java.io.File -import java.util.function.Supplier -import javax.xml.namespace.QName -import me.shedaniel.math.Color -import org.w3c.dom.Element -import kotlin.time.Duration -import kotlin.time.Duration.Companion.seconds -import net.minecraft.client.gui.screen.Screen -import moe.nea.firmament.gui.BarComponent -import moe.nea.firmament.gui.FirmButtonComponent -import moe.nea.firmament.gui.FirmHoverComponent -import moe.nea.firmament.gui.FixedComponent -import moe.nea.firmament.gui.ImageComponent -import moe.nea.firmament.gui.TickComponent - -object MoulConfigUtils { - val firmUrl = "http://firmament.nea.moe/moulconfig" - val universe = XMLUniverse.getDefaultUniverse().also { uni -> - uni.registerMapper(java.awt.Color::class.java) { - if (it.startsWith("#")) { - val hexString = it.substring(1) - val hex = hexString.toInt(16) - if (hexString.length == 6) { - return@registerMapper java.awt.Color(hex) - } - if (hexString.length == 8) { - return@registerMapper java.awt.Color(hex, true) - } - error("Hexcolor $it needs to be exactly 6 or 8 hex digits long") - } - return@registerMapper java.awt.Color(it.toInt(), true) - } - uni.registerMapper(Color::class.java) { - val color = uni.mapXMLObject(it, java.awt.Color::class.java) - Color.ofRGBA(color.red, color.green, color.blue, color.alpha) - } - uni.registerLoader(object : XMLGuiLoader.Basic { - override fun getName(): QName { - return QName(firmUrl, "Bar") - } - - override fun createInstance(context: XMLContext<*>, element: Element): BarComponent { - return BarComponent( - context.getPropertyFromAttribute(element, QName("progress"), Double::class.java)!!, - context.getPropertyFromAttribute(element, QName("total"), Double::class.java)!!, - context.getPropertyFromAttribute(element, QName("fillColor"), Color::class.java)!!.get(), - context.getPropertyFromAttribute(element, QName("emptyColor"), Color::class.java)!!.get(), - ) - } - - override fun getChildCount(): ChildCount { - return ChildCount.NONE - } - - override fun getAttributeNames(): Map { - return mapOf("progress" to true, "total" to true, "emptyColor" to true, "fillColor" to true) - } - }) - uni.registerLoader(object : XMLGuiLoader.Basic { - override fun createInstance(context: XMLContext<*>, element: Element): FirmHoverComponent { - return FirmHoverComponent( - context.getChildFragment(element), - context.getPropertyFromAttribute(element, QName("lines"), List::class.java) as Supplier>, - context.getPropertyFromAttribute(element, QName("delay"), Duration::class.java, 0.6.seconds), - ) - } - - override fun getName(): QName { - return QName(firmUrl, "Hover") - } - - override fun getChildCount(): ChildCount { - return ChildCount.ONE - } - - override fun getAttributeNames(): Map { - return mapOf( - "lines" to true, - "delay" to false, - ) - } - - }) - uni.registerLoader(object : XMLGuiLoader.Basic { - override fun getName(): QName { - return QName(firmUrl, "Button") - } - - override fun createInstance(context: XMLContext<*>, element: Element): FirmButtonComponent { - return FirmButtonComponent( - context.getChildFragment(element), - context.getPropertyFromAttribute(element, QName("enabled"), Boolean::class.java) - ?: GetSetter.constant(true), - context.getPropertyFromAttribute(element, QName("noBackground"), Boolean::class.java, false), - context.getMethodFromAttribute(element, QName("onClick")), - ) - } - - override fun getChildCount(): ChildCount { - return ChildCount.ONE - } - - override fun getAttributeNames(): Map { - return mapOf("onClick" to true, "enabled" to false, "noBackground" to false) - } - }) - uni.registerLoader(object : XMLGuiLoader.Basic { - override fun createInstance(context: XMLContext<*>, element: Element): ImageComponent { - return ImageComponent( - context.getPropertyFromAttribute(element, QName("width"), Int::class.java)!!.get(), - context.getPropertyFromAttribute(element, QName("height"), Int::class.java)!!.get(), - context.getPropertyFromAttribute(element, QName("resource"), MyResourceLocation::class.java)!!, - context.getPropertyFromAttribute(element, QName("u1"), Float::class.java, 0f), - context.getPropertyFromAttribute(element, QName("u2"), Float::class.java, 1f), - context.getPropertyFromAttribute(element, QName("v1"), Float::class.java, 0f), - context.getPropertyFromAttribute(element, QName("v2"), Float::class.java, 1f), - ) - } - - override fun getName(): QName { - return QName(firmUrl, "Image") - } - - override fun getChildCount(): ChildCount { - return ChildCount.NONE - } - - override fun getAttributeNames(): Map { - return mapOf( - "width" to true, "height" to true, - "resource" to true, - "u1" to false, - "u2" to false, - "v1" to false, - "v2" to false, - ) - } - }) - uni.registerLoader(object : XMLGuiLoader.Basic { - override fun createInstance(context: XMLContext<*>, element: Element): TickComponent { - return TickComponent(context.getMethodFromAttribute(element, QName("tick"))) - } - - override fun getName(): QName { - return QName(firmUrl, "Tick") - } - - override fun getChildCount(): ChildCount { - return ChildCount.NONE - } - - override fun getAttributeNames(): Map { - return mapOf("tick" to true) - } - }) - uni.registerLoader(object : XMLGuiLoader.Basic { - override fun createInstance(context: XMLContext<*>, element: Element): FixedComponent { - return FixedComponent( - context.getPropertyFromAttribute(element, QName("width"), Int::class.java) - ?: error("Requires width specified"), - context.getPropertyFromAttribute(element, QName("height"), Int::class.java) - ?: error("Requires height specified"), - context.getChildFragment(element) - ) - } - - override fun getName(): QName { - return QName(firmUrl, "Fixed") - } - - override fun getChildCount(): ChildCount { - return ChildCount.ONE - } - - override fun getAttributeNames(): Map { - return mapOf("width" to true, "height" to true) - } - }) - } - - fun generateXSD( - file: File, - namespace: String - ) { - val generator = XSDGenerator(universe, namespace) - generator.writeAll() - generator.dumpToFile(file) - } - - @JvmStatic - fun main(args: Array) { - generateXSD(File("MoulConfig.xsd"), XMLUniverse.MOULCONFIG_XML_NS) - generateXSD(File("MoulConfig.Firmament.xsd"), firmUrl) - File("wrapper.xsd").writeText(""" - - - - - - """.trimIndent()) - } - - fun loadScreen(name: String, bindTo: Any, parent: Screen?): Screen { - return object : GuiComponentWrapper(loadGui(name, bindTo)) { - override fun close() { - if (context.onBeforeClose() == CloseEventListener.CloseAction.NO_OBJECTIONS_TO_CLOSE) { - client!!.setScreen(parent) - } - } - } - } - - fun loadGui(name: String, bindTo: Any): GuiContext { - return GuiContext(universe.load(bindTo, MyResourceLocation("firmament", "gui/$name.xml"))) - } -} diff --git a/src/main/kotlin/moe/nea/firmament/util/MutableMapWithMaxSize.kt b/src/main/kotlin/moe/nea/firmament/util/MutableMapWithMaxSize.kt deleted file mode 100644 index 067e652..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/MutableMapWithMaxSize.kt +++ /dev/null @@ -1,38 +0,0 @@ - -package moe.nea.firmament.util - -fun mutableMapWithMaxSize(maxSize: Int): MutableMap = object : LinkedHashMap() { - override fun removeEldestEntry(eldest: MutableMap.MutableEntry): Boolean { - return size > maxSize - } -} - -fun ((T) -> R).memoizeIdentity(maxCacheSize: Int): (T) -> R { - val memoized = { it: IdentityCharacteristics -> - this(it.value) - }.memoize(maxCacheSize) - return { memoized(IdentityCharacteristics(it)) } -} - -@PublishedApi -internal val SENTINEL_NULL = java.lang.Object() - -/** - * Requires the map to only contain values of type [R] or [SENTINEL_NULL]. This is ensured if the map is only ever - * accessed via this function. - */ -inline fun MutableMap.computeNullableFunction(key: T, crossinline func: () -> R): R { - val value = this.getOrPut(key) { - func() ?: SENTINEL_NULL - } - @Suppress("UNCHECKED_CAST") - return if (value === SENTINEL_NULL) null as R - else value as R -} - -fun ((T) -> R).memoize(maxCacheSize: Int): (T) -> R { - val map = mutableMapWithMaxSize(maxCacheSize) - return { - map.computeNullableFunction(it) { this@memoize(it) } - } -} diff --git a/src/main/kotlin/moe/nea/firmament/util/SBData.kt b/src/main/kotlin/moe/nea/firmament/util/SBData.kt deleted file mode 100644 index b30c6fb..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/SBData.kt +++ /dev/null @@ -1,66 +0,0 @@ -package moe.nea.firmament.util - -import java.util.UUID -import net.hypixel.modapi.HypixelModAPI -import net.hypixel.modapi.packet.impl.clientbound.event.ClientboundLocationPacket -import kotlin.jvm.optionals.getOrNull -import kotlin.time.Duration.Companion.seconds -import moe.nea.firmament.events.AllowChatEvent -import moe.nea.firmament.events.ProcessChatEvent -import moe.nea.firmament.events.ServerConnectedEvent -import moe.nea.firmament.events.SkyblockServerUpdateEvent -import moe.nea.firmament.events.WorldReadyEvent - -object SBData { - private val profileRegex = "Profile ID: ([a-z0-9\\-]+)".toRegex() - val profileSuggestTexts = listOf( - "CLICK THIS TO SUGGEST IT IN CHAT [DASHES]", - "CLICK THIS TO SUGGEST IT IN CHAT [NO DASHES]", - ) - var profileId: UUID? = null - - private var hasReceivedProfile = false - var locraw: Locraw? = null - val skyblockLocation: SkyBlockIsland? get() = locraw?.skyblockLocation - val hasValidLocraw get() = locraw?.server !in listOf("limbo", null) - val isOnSkyblock get() = locraw?.gametype == "SKYBLOCK" - var lastProfileIdRequest = TimeMark.farPast() - fun init() { - ServerConnectedEvent.subscribe { - HypixelModAPI.getInstance().subscribeToEventPacket(ClientboundLocationPacket::class.java) - } - HypixelModAPI.getInstance().createHandler(ClientboundLocationPacket::class.java) { - MC.onMainThread { - val lastLocraw = locraw - locraw = Locraw(it.serverName, - it.serverType.getOrNull()?.name?.uppercase(), - it.mode.getOrNull(), - it.map.getOrNull()) - SkyblockServerUpdateEvent.publish(SkyblockServerUpdateEvent(lastLocraw, null)) - } - } - SkyblockServerUpdateEvent.subscribe { - if (!hasReceivedProfile && isOnSkyblock && lastProfileIdRequest.passedTime() > 30.seconds) { - lastProfileIdRequest = TimeMark.now() - MC.sendServerCommand("profileid") - } - } - AllowChatEvent.subscribe { event -> - if (event.unformattedString in profileSuggestTexts && lastProfileIdRequest.passedTime() < 5.seconds) { - event.cancel() - } - } - ProcessChatEvent.subscribe(receivesCancelled = true) { event -> - val profileMatch = profileRegex.matchEntire(event.unformattedString) - if (profileMatch != null) { - try { - profileId = UUID.fromString(profileMatch.groupValues[1]) - hasReceivedProfile = true - } catch (e: IllegalArgumentException) { - profileId = null - e.printStackTrace() - } - } - } - } -} diff --git a/src/main/kotlin/moe/nea/firmament/util/ScoreboardUtil.kt b/src/main/kotlin/moe/nea/firmament/util/ScoreboardUtil.kt deleted file mode 100644 index 4311971..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/ScoreboardUtil.kt +++ /dev/null @@ -1,45 +0,0 @@ - - -package moe.nea.firmament.util - -import java.util.* -import net.minecraft.client.gui.hud.InGameHud -import net.minecraft.scoreboard.ScoreboardDisplaySlot -import net.minecraft.scoreboard.Team -import net.minecraft.text.StringVisitable -import net.minecraft.text.Style -import net.minecraft.text.Text -import net.minecraft.util.Formatting - -fun getScoreboardLines(): List { - val scoreboard = MC.player?.scoreboard ?: return listOf() - val activeObjective = scoreboard.getObjectiveForSlot(ScoreboardDisplaySlot.SIDEBAR) ?: return listOf() - return scoreboard.getScoreboardEntries(activeObjective) - .filter { !it.hidden() } - .sortedWith(InGameHud.SCOREBOARD_ENTRY_COMPARATOR) - .take(15).map { - val team = scoreboard.getScoreHolderTeam(it.owner) - val text = it.name() - Team.decorateName(team, text) - } -} - - -fun Text.formattedString(): String { - val sb = StringBuilder() - visit(StringVisitable.StyledVisitor { style, string -> - val c = Formatting.byName(style.color?.name) - if (c != null) { - sb.append("§${c.code}") - } - if (style.isUnderlined) { - sb.append("§n") - } - if (style.isBold) { - sb.append("§l") - } - sb.append(string) - Optional.empty() - }, Style.EMPTY) - return sb.toString().replace("§[^a-f0-9]".toRegex(), "") -} diff --git a/src/main/kotlin/moe/nea/firmament/util/ScreenUtil.kt b/src/main/kotlin/moe/nea/firmament/util/ScreenUtil.kt deleted file mode 100644 index 99d77fb..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/ScreenUtil.kt +++ /dev/null @@ -1,38 +0,0 @@ - - -package moe.nea.firmament.util - -import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents -import net.minecraft.client.MinecraftClient -import net.minecraft.client.gui.screen.Screen -import moe.nea.firmament.Firmament - -object ScreenUtil { - init { - ClientTickEvents.START_CLIENT_TICK.register(::onTick) - } - - private fun onTick(minecraft: MinecraftClient) { - if (nextOpenedGui != null) { - val p = minecraft.player - if (p?.currentScreenHandler != null) { - p.closeHandledScreen() - } - minecraft.setScreen(nextOpenedGui) - nextOpenedGui = null - } - } - - private var nextOpenedGui: Screen? = null - - fun setScreenLater(nextScreen: Screen?) { - val nog = nextOpenedGui - if (nog != null) { - Firmament.logger.warn("Setting screen ${if (nextScreen == null) "null" else nextScreen::class.qualifiedName} to be opened later, but ${nog::class.qualifiedName} is already queued.") - return - } - nextOpenedGui = nextScreen - } - - -} diff --git a/src/main/kotlin/moe/nea/firmament/util/SequenceUtil.kt b/src/main/kotlin/moe/nea/firmament/util/SequenceUtil.kt deleted file mode 100644 index 7b5bad0..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/SequenceUtil.kt +++ /dev/null @@ -1,11 +0,0 @@ - - -package moe.nea.firmament.util - -fun T.iterate(iterator: (T) -> T?): Sequence = sequence { - var x: T? = this@iterate - while (x != null) { - yield(x) - x = iterator(x) - } -} diff --git a/src/main/kotlin/moe/nea/firmament/util/SkyBlockIsland.kt b/src/main/kotlin/moe/nea/firmament/util/SkyBlockIsland.kt deleted file mode 100644 index bd0567d..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/SkyBlockIsland.kt +++ /dev/null @@ -1,42 +0,0 @@ - -package moe.nea.firmament.util - -import kotlinx.serialization.KSerializer -import kotlinx.serialization.Serializable -import kotlinx.serialization.descriptors.PrimitiveKind -import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder -import moe.nea.firmament.repo.RepoManager - -@Serializable(with = SkyBlockIsland.Serializer::class) -class SkyBlockIsland -private constructor( - val locrawMode: String, -) { - - object Serializer : KSerializer { - override val descriptor: SerialDescriptor - get() = PrimitiveSerialDescriptor("SkyBlockIsland", PrimitiveKind.STRING) - - override fun deserialize(decoder: Decoder): SkyBlockIsland { - return forMode(decoder.decodeString()) - } - - override fun serialize(encoder: Encoder, value: SkyBlockIsland) { - encoder.encodeString(value.locrawMode) - } - } - companion object { - private val allIslands = mutableMapOf() - fun forMode(mode: String): SkyBlockIsland = allIslands.computeIfAbsent(mode, ::SkyBlockIsland) - val HUB = forMode("hub") - val PRIVATE_ISLAND = forMode("dynamic") - val RIFT = forMode("rift") - } - - val userFriendlyName - get() = RepoManager.neuRepo.constants.islands.areaNames - .getOrDefault(locrawMode, locrawMode) -} diff --git a/src/main/kotlin/moe/nea/firmament/util/SkyblockId.kt b/src/main/kotlin/moe/nea/firmament/util/SkyblockId.kt deleted file mode 100644 index 59b1d2c..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/SkyblockId.kt +++ /dev/null @@ -1,149 +0,0 @@ - - -@file:UseSerializers(DashlessUUIDSerializer::class) - -package moe.nea.firmament.util - -import io.github.moulberry.repo.data.NEUItem -import io.github.moulberry.repo.data.Rarity -import java.util.UUID -import kotlinx.serialization.Serializable -import kotlinx.serialization.UseSerializers -import kotlinx.serialization.json.Json -import net.minecraft.component.DataComponentTypes -import net.minecraft.component.type.NbtComponent -import net.minecraft.item.ItemStack -import net.minecraft.nbt.NbtCompound -import net.minecraft.util.Identifier -import moe.nea.firmament.repo.set -import moe.nea.firmament.util.json.DashlessUUIDSerializer - -/** - * A skyblock item id, as used by the NEU repo. - * This is not exactly the format used by HyPixel, but is mostly the same. - * Usually this id splits an id used by HyPixel into more sub items. For example `PET` becomes `$PET_ID;$PET_RARITY`, - * with those values extracted from other metadata. - */ -@JvmInline -@Serializable -value class SkyblockId(val neuItem: String) { - val identifier - get() = Identifier.of("skyblockitem", - neuItem.lowercase().replace(";", "__") - .replace(":", "___") - .replace(illlegalPathRegex) { - it.value.toCharArray() - .joinToString("") { "__" + it.code.toString(16).padStart(4, '0') } - }) - - override fun toString(): String { - return neuItem - } - - /** - * A bazaar stock item id, as returned by the HyPixel bazaar api endpoint. - * These are not equivalent to the in-game ids, or the NEU repo ids, and in fact, do not refer to items, but instead - * to bazaar stocks. The main difference from [SkyblockId]s is concerning enchanted books. There are probably more, - * but for now this holds. - */ - @JvmInline - @Serializable - value class BazaarStock(val bazaarId: String) { - fun toRepoId(): SkyblockId { - bazaarEnchantmentRegex.matchEntire(bazaarId)?.let { - return SkyblockId("${it.groupValues[1]};${it.groupValues[2]}") - } - return SkyblockId(bazaarId.replace(":", "-")) - } - } - - companion object { - val COINS: SkyblockId = SkyblockId("SKYBLOCK_COIN") - private val bazaarEnchantmentRegex = "ENCHANTMENT_(\\D*)_(\\d+)".toRegex() - val NULL: SkyblockId = SkyblockId("null") - val PET_NULL: SkyblockId = SkyblockId("null_pet") - private val illlegalPathRegex = "[^a-z0-9_.-/]".toRegex() - } -} - -val NEUItem.skyblockId get() = SkyblockId(skyblockItemId) - -@Serializable -data class HypixelPetInfo( - val type: String, - val tier: Rarity, - val exp: Double = 0.0, - val candyUsed: Int = 0, - val uuid: UUID? = null, -) { - val skyblockId get() = SkyblockId("${type.uppercase()};${tier.ordinal}") -} - -private val jsonparser = Json { ignoreUnknownKeys = true } - -val ItemStack.extraAttributes: NbtCompound - get() { - val customData = get(DataComponentTypes.CUSTOM_DATA) ?: run { - val component = NbtComponent.of(NbtCompound()) - set(DataComponentTypes.CUSTOM_DATA, component) - component - } - return customData.nbt - } - -val ItemStack.skyblockUUIDString: String? - get() = extraAttributes.getString("uuid")?.takeIf { it.isNotBlank() } - -val ItemStack.skyblockUUID: UUID? - get() = skyblockUUIDString?.let { UUID.fromString(it) } - -val ItemStack.petData: HypixelPetInfo? - get() { - val jsonString = extraAttributes.getString("petInfo") - if (jsonString.isNullOrBlank()) return null - return runCatching { jsonparser.decodeFromString(jsonString) } - .getOrElse { return null } - } - -fun ItemStack.setSkyBlockFirmamentUiId(uiId: String) = setSkyBlockId(SkyblockId("FIRMAMENT_UI_$uiId")) -fun ItemStack.setSkyBlockId(skyblockId: SkyblockId): ItemStack { - this.extraAttributes["id"] = skyblockId.neuItem - return this -} - -val ItemStack.skyBlockId: SkyblockId? - get() { - return when (val id = extraAttributes.getString("id")) { - "" -> { - null - } - - "PET" -> { - petData?.skyblockId ?: SkyblockId.PET_NULL - } - - "RUNE", "UNIQUE_RUNE" -> { - val runeData = extraAttributes.getCompound("runes") - val runeKind = runeData.keys.singleOrNull() - if (runeKind == null) SkyblockId("RUNE") - else SkyblockId("${runeKind.uppercase()}_RUNE;${runeData.getInt(runeKind)}") - } - - "ABICASE" -> { - SkyblockId("ABICASE_${extraAttributes.getString("model").uppercase()}") - } - - "ENCHANTED_BOOK" -> { - val enchantmentData = extraAttributes.getCompound("enchantments") - val enchantName = enchantmentData.keys.singleOrNull() - if (enchantName == null) SkyblockId("ENCHANTED_BOOK") - else SkyblockId("${enchantName.uppercase()};${enchantmentData.getInt(enchantName)}") - } - - // TODO: PARTY_HAT_CRAB{,_ANIMATED,_SLOTH},POTION - else -> { - SkyblockId(id) - } - } - } - diff --git a/src/main/kotlin/moe/nea/firmament/util/SortedMapSerializer.kt b/src/main/kotlin/moe/nea/firmament/util/SortedMapSerializer.kt deleted file mode 100644 index baa10ad..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/SortedMapSerializer.kt +++ /dev/null @@ -1,25 +0,0 @@ - - -package moe.nea.firmament.util - -import java.util.SortedMap -import kotlinx.serialization.KSerializer -import kotlinx.serialization.builtins.MapSerializer -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder - -class SortedMapSerializer, V>(val keyDelegate: KSerializer, val valueDelegate: KSerializer) : - KSerializer> { - val mapSerializer = MapSerializer(keyDelegate, valueDelegate) - override val descriptor: SerialDescriptor - get() = mapSerializer.descriptor - - override fun deserialize(decoder: Decoder): SortedMap { - return (mapSerializer.deserialize(decoder).toSortedMap(Comparator.naturalOrder())) - } - - override fun serialize(encoder: Encoder, value: SortedMap) { - mapSerializer.serialize(encoder, value) - } -} diff --git a/src/main/kotlin/moe/nea/firmament/util/TemplateUtil.kt b/src/main/kotlin/moe/nea/firmament/util/TemplateUtil.kt deleted file mode 100644 index 11100e9..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/TemplateUtil.kt +++ /dev/null @@ -1,85 +0,0 @@ - - -package moe.nea.firmament.util - -import java.util.* -import kotlinx.serialization.DeserializationStrategy -import kotlinx.serialization.SerializationStrategy -import kotlinx.serialization.serializer -import moe.nea.firmament.Firmament - -object TemplateUtil { - - @JvmStatic - fun getTemplatePrefix(data: String): String? { - val decoded = maybeFromBase64Encoded(data) ?: return null - return decoded.replaceAfter("/", "", "").ifBlank { null } - } - - @JvmStatic - fun intoBase64Encoded(raw: String): String { - return Base64.getEncoder().encodeToString(raw.encodeToByteArray()) - } - - private val base64Alphabet = charArrayOf( - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', - 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', - 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', '=' - ) - - @JvmStatic - fun maybeFromBase64Encoded(raw: String): String? { - val raw = raw.trim() - if (raw.any { it !in base64Alphabet }) { - return null - } - return try { - Base64.getDecoder().decode(raw).decodeToString() - } catch (ex: Exception) { - null - } - } - - - /** - * Returns a base64 encoded string, truncated such that for all `x`, `x.startsWith(prefix)` implies - * `base64Encoded(x).startsWith(getPrefixComparisonSafeBase64Encoding(prefix))` - * (however, the inverse may not always be true). - */ - @JvmStatic - fun getPrefixComparisonSafeBase64Encoding(prefix: String): String { - val rawEncoded = - Base64.getEncoder().encodeToString(prefix.encodeToByteArray()) - .replace("=", "") - return rawEncoded.substring(0, rawEncoded.length - rawEncoded.length % 4) - } - - inline fun encodeTemplate(sharePrefix: String, data: T): String = - encodeTemplate(sharePrefix, data, serializer()) - - fun encodeTemplate(sharePrefix: String, data: T, serializer: SerializationStrategy): String { - require(sharePrefix.endsWith("/")) - return intoBase64Encoded(sharePrefix + Firmament.json.encodeToString(serializer, data)) - } - - inline fun maybeDecodeTemplate(sharePrefix: String, data: String): T? = - maybeDecodeTemplate(sharePrefix, data, serializer()) - - fun maybeDecodeTemplate(sharePrefix: String, data: String, serializer: DeserializationStrategy): T? { - require(sharePrefix.endsWith("/")) - val data = data.trim() - if (!data.startsWith(getPrefixComparisonSafeBase64Encoding(sharePrefix))) - return null - val decoded = maybeFromBase64Encoded(data) ?: return null - if (!decoded.startsWith(sharePrefix)) - return null - return try { - Firmament.json.decodeFromString(serializer, decoded.substring(sharePrefix.length)) - } catch (e: Exception) { - null - } - } - -} diff --git a/src/main/kotlin/moe/nea/firmament/util/TimeMark.kt b/src/main/kotlin/moe/nea/firmament/util/TimeMark.kt deleted file mode 100644 index 1264212..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/TimeMark.kt +++ /dev/null @@ -1,44 +0,0 @@ - - -package moe.nea.firmament.util - -import kotlin.time.Duration -import kotlin.time.Duration.Companion.milliseconds - -class TimeMark private constructor(private val timeMark: Long) : Comparable { - fun passedTime() = if (timeMark == 0L) Duration.INFINITE else (System.currentTimeMillis() - timeMark).milliseconds - - operator fun minus(other: TimeMark): Duration { - if (other.timeMark == timeMark) - return 0.milliseconds - if (other.timeMark == 0L) - return Duration.INFINITE - if (timeMark == 0L) - return -Duration.INFINITE - return (timeMark - other.timeMark).milliseconds - } - - companion object { - fun now() = TimeMark(System.currentTimeMillis()) - fun farPast() = TimeMark(0L) - fun ago(timeDelta: Duration): TimeMark { - if (timeDelta.isFinite()) { - return TimeMark(System.currentTimeMillis() - timeDelta.inWholeMilliseconds) - } - require(timeDelta.isPositive()) - return farPast() - } - } - - override fun hashCode(): Int { - return timeMark.hashCode() - } - - override fun equals(other: Any?): Boolean { - return other is TimeMark && other.timeMark == timeMark - } - - override fun compareTo(other: TimeMark): Int { - return this.timeMark.compareTo(other.timeMark) - } -} diff --git a/src/main/kotlin/moe/nea/firmament/util/Timer.kt b/src/main/kotlin/moe/nea/firmament/util/Timer.kt deleted file mode 100644 index 6e9b467..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/Timer.kt +++ /dev/null @@ -1,25 +0,0 @@ - - -package moe.nea.firmament.util - -import kotlin.time.Duration -import kotlin.time.ExperimentalTime -import kotlin.time.TimeSource - -@OptIn(ExperimentalTime::class) -class Timer { - private var mark: TimeSource.Monotonic.ValueTimeMark? = null - - fun timePassed(): Duration { - return mark?.elapsedNow() ?: Duration.INFINITE - } - - fun markNow() { - mark = TimeSource.Monotonic.markNow() - } - - fun markFarPast() { - mark = null - } - -} diff --git a/src/main/kotlin/moe/nea/firmament/util/WarpUtil.kt b/src/main/kotlin/moe/nea/firmament/util/WarpUtil.kt deleted file mode 100644 index 8fca6f3..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/WarpUtil.kt +++ /dev/null @@ -1,75 +0,0 @@ - -package moe.nea.firmament.util - -import io.github.moulberry.repo.constants.Islands -import io.github.moulberry.repo.constants.Islands.Warp -import kotlinx.serialization.Serializable -import kotlinx.serialization.serializer -import kotlin.math.sqrt -import kotlin.time.Duration.Companion.seconds -import net.minecraft.text.Text -import net.minecraft.util.math.Position -import moe.nea.firmament.events.ProcessChatEvent -import moe.nea.firmament.repo.RepoManager -import moe.nea.firmament.util.data.ProfileSpecificDataHolder - -object WarpUtil { - val warps: List get() = RepoManager.neuRepo.constants.islands.warps - - @Serializable - data class Data( - val excludedWarps: MutableSet = mutableSetOf(), - ) - - object DConfig : ProfileSpecificDataHolder(serializer(), "warp-util", ::Data) - - private var lastAttemptedWarp = "" - private var lastWarpAttempt = TimeMark.farPast() - fun findNearestWarp(island: SkyBlockIsland, pos: Position): Islands.Warp? { - return warps.asSequence().filter { it.mode == island.locrawMode }.minByOrNull { - if (DConfig.data?.excludedWarps?.contains(it.warp) == true) { - return@minByOrNull Double.MAX_VALUE - } else { - return@minByOrNull squaredDist(pos, it) - } - } - } - - private fun squaredDist(pos: Position, warp: Warp): Double { - val dx = pos.x - warp.x - val dy = pos.y - warp.y - val dz = pos.z - warp.z - return dx * dx + dy * dy + dz * dz - } - - fun teleportToNearestWarp(island: SkyBlockIsland, pos: Position) { - val nearestWarp = findNearestWarp(island, pos) - if (nearestWarp == null) { - MC.sendChat(Text.literal("Could not find an unlocked warp in ${island.userFriendlyName}")) - return - } - if (island == SBData.skyblockLocation - && sqrt(squaredDist(pos, nearestWarp)) > 1.1 * sqrt(squaredDist((MC.player ?: return).pos, nearestWarp)) - ) { - return - } - MC.sendServerCommand("warp ${nearestWarp.warp}") - } - - init { - ProcessChatEvent.subscribe { - if (it.unformattedString == "You haven't unlocked this fast travel destination!" - && lastWarpAttempt.passedTime() < 2.seconds - ) { - DConfig.data?.excludedWarps?.add(lastAttemptedWarp) - DConfig.markDirty() - MC.sendChat(Text.stringifiedTranslatable("firmament.warp-util.mark-excluded", lastAttemptedWarp)) - lastWarpAttempt = TimeMark.farPast() - } - if (it.unformattedString == "You may now fast travel to") { - DConfig.data?.excludedWarps?.clear() - DConfig.markDirty() - } - } - } -} diff --git a/src/main/kotlin/moe/nea/firmament/util/assertions.kt b/src/main/kotlin/moe/nea/firmament/util/assertions.kt deleted file mode 100644 index 6f2ed19..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/assertions.kt +++ /dev/null @@ -1,25 +0,0 @@ - - -package moe.nea.firmament.util - -/** - * Less aggressive version of `require(obj != null)`, which fails in devenv but continues at runtime. - */ -inline fun assertNotNullOr(obj: T?, message: String? = null, block: () -> T): T { - if (message == null) - assert(obj != null) - else - assert(obj != null) { message } - return obj ?: block() -} - - -/** - * Less aggressive version of `require(condition)`, which fails in devenv but continues at runtime. - */ -inline fun assertTrueOr(condition: Boolean, block: () -> Unit) { - assert(condition) - if (!condition) block() -} - - diff --git a/src/main/kotlin/moe/nea/firmament/util/async/input.kt b/src/main/kotlin/moe/nea/firmament/util/async/input.kt deleted file mode 100644 index 9aab5cf..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/async/input.kt +++ /dev/null @@ -1,47 +0,0 @@ - - -package moe.nea.firmament.util.async - -import kotlinx.coroutines.suspendCancellableCoroutine -import kotlin.coroutines.resume -import moe.nea.firmament.events.HandledScreenKeyPressedEvent -import moe.nea.firmament.keybindings.IKeyBinding - -private object InputHandler { - data class KeyInputContinuation(val keybind: IKeyBinding, val onContinue: () -> Unit) - - private val activeContinuations = mutableListOf() - - fun registerContinuation(keyInputContinuation: KeyInputContinuation): () -> Unit { - synchronized(InputHandler) { - activeContinuations.add(keyInputContinuation) - } - return { - synchronized(this) { - activeContinuations.remove(keyInputContinuation) - } - } - } - - init { - HandledScreenKeyPressedEvent.subscribe { event -> - synchronized(InputHandler) { - val toRemove = activeContinuations.filter { - event.matches(it.keybind) - } - toRemove.forEach { it.onContinue() } - activeContinuations.removeAll(toRemove) - } - } - } -} - -suspend fun waitForInput(keybind: IKeyBinding): Unit = suspendCancellableCoroutine { cont -> - val unregister = - InputHandler.registerContinuation(InputHandler.KeyInputContinuation(keybind) { cont.resume(Unit) }) - cont.invokeOnCancellation { - unregister() - } -} - - diff --git a/src/main/kotlin/moe/nea/firmament/util/colorconversion.kt b/src/main/kotlin/moe/nea/firmament/util/colorconversion.kt deleted file mode 100644 index d7a5dad..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/colorconversion.kt +++ /dev/null @@ -1,13 +0,0 @@ - - -package moe.nea.firmament.util - -import net.minecraft.text.TextColor -import net.minecraft.util.DyeColor - -fun DyeColor.toShedaniel(): me.shedaniel.math.Color = - me.shedaniel.math.Color.ofOpaque(this.signColor) - -fun DyeColor.toTextColor(): TextColor = - TextColor.fromRgb(this.signColor) - diff --git a/src/main/kotlin/moe/nea/firmament/util/customgui/CoordRememberingSlot.kt b/src/main/kotlin/moe/nea/firmament/util/customgui/CoordRememberingSlot.kt deleted file mode 100644 index c61c711..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/customgui/CoordRememberingSlot.kt +++ /dev/null @@ -1,14 +0,0 @@ - -package moe.nea.firmament.util.customgui - -import net.minecraft.screen.slot.Slot - -interface CoordRememberingSlot { - fun rememberCoords_firmament() - fun restoreCoords_firmament() - fun getOriginalX_firmament(): Int - fun getOriginalY_firmament(): Int -} - -val Slot.originalX get() = (this as CoordRememberingSlot).getOriginalX_firmament() -val Slot.originalY get() = (this as CoordRememberingSlot).getOriginalY_firmament() diff --git a/src/main/kotlin/moe/nea/firmament/util/customgui/CustomGui.kt b/src/main/kotlin/moe/nea/firmament/util/customgui/CustomGui.kt deleted file mode 100644 index f9094b2..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/customgui/CustomGui.kt +++ /dev/null @@ -1,72 +0,0 @@ - -package moe.nea.firmament.util.customgui - -import me.shedaniel.math.Rectangle -import net.minecraft.client.gui.DrawContext -import net.minecraft.screen.slot.Slot -import moe.nea.firmament.annotations.Subscribe -import moe.nea.firmament.events.HandledScreenPushREIEvent - -abstract class CustomGui { - - abstract fun getBounds(): List - - open fun moveSlot(slot: Slot) { - // TODO: return a Pair maybe? worth an investigation - } - - companion object { - @Subscribe - fun onExclusionZone(event: HandledScreenPushREIEvent) { - val customGui = event.screen.customGui ?: return - event.rectangles.addAll(customGui.getBounds()) - } - } - - open fun render( - drawContext: DrawContext, - delta: Float, - mouseX: Int, - mouseY: Int - ) { - } - - open fun mouseClick(mouseX: Double, mouseY: Double, button: Int): Boolean { - return false - } - - open fun afterSlotRender(context: DrawContext, slot: Slot) {} - open fun beforeSlotRender(context: DrawContext, slot: Slot) {} - open fun mouseScrolled(mouseX: Double, mouseY: Double, horizontalAmount: Double, verticalAmount: Double): Boolean { - return false - } - - open fun isClickOutsideBounds(mouseX: Double, mouseY: Double): Boolean { - return getBounds().none { it.contains(mouseX, mouseY) } - } - - open fun isPointWithinBounds( - x: Int, - y: Int, - width: Int, - height: Int, - pointX: Double, - pointY: Double, - ): Boolean { - return getBounds().any { it.contains(pointX, pointY) } && - Rectangle(x, y, width, height).contains(pointX, pointY) - } - - open fun isPointOverSlot(slot: Slot, xOffset: Int, yOffset: Int, pointX: Double, pointY: Double): Boolean { - return isPointWithinBounds(slot.x + xOffset, slot.y + yOffset, 16, 16, pointX, pointY) - } - - open fun onInit() {} - open fun shouldDrawForeground(): Boolean { - return true - } - - open fun onVoluntaryExit(): Boolean { - return true - } -} diff --git a/src/main/kotlin/moe/nea/firmament/util/customgui/HasCustomGui.kt b/src/main/kotlin/moe/nea/firmament/util/customgui/HasCustomGui.kt deleted file mode 100644 index edead2e..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/customgui/HasCustomGui.kt +++ /dev/null @@ -1,17 +0,0 @@ - -package moe.nea.firmament.util.customgui - -import net.minecraft.client.gui.screen.ingame.HandledScreen - -@Suppress("FunctionName") -interface HasCustomGui { - fun getCustomGui_Firmament(): CustomGui? - fun setCustomGui_Firmament(gui: CustomGui?) -} - -var > T.customGui: CustomGui? - get() = (this as HasCustomGui).getCustomGui_Firmament() - set(value) { - (this as HasCustomGui).setCustomGui_Firmament(value) - } - diff --git a/src/main/kotlin/moe/nea/firmament/util/data/DataHolder.kt b/src/main/kotlin/moe/nea/firmament/util/data/DataHolder.kt deleted file mode 100644 index 21a6014..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/data/DataHolder.kt +++ /dev/null @@ -1,62 +0,0 @@ - - -package moe.nea.firmament.util.data - -import java.nio.file.Path -import kotlinx.serialization.KSerializer -import kotlin.io.path.exists -import kotlin.io.path.readText -import kotlin.io.path.writeText -import moe.nea.firmament.Firmament - -abstract class DataHolder( - val serializer: KSerializer, - val name: String, - val default: () -> T -) : IDataHolder { - - - final override var data: T - private set - - init { - data = readValueOrDefault() - IDataHolder.putDataHolder(this::class, this) - } - - private val file: Path get() = Firmament.CONFIG_DIR.resolve("$name.json") - - protected fun readValueOrDefault(): T { - if (file.exists()) - try { - return Firmament.json.decodeFromString( - serializer, - file.readText() - ) - } catch (e: Exception) {/* Expecting IOException and SerializationException, but Kotlin doesn't allow multi catches*/ - IDataHolder.badLoads.add(name) - Firmament.logger.error( - "Exception during loading of config file $name. This will reset this config.", - e - ) - } - return default() - } - - private fun writeValue(t: T) { - file.writeText(Firmament.json.encodeToString(serializer, t)) - } - - override fun save() { - writeValue(data) - } - - override fun load() { - data = readValueOrDefault() - } - - override fun markDirty() { - IDataHolder.markDirty(this::class) - } - -} diff --git a/src/main/kotlin/moe/nea/firmament/util/data/IDataHolder.kt b/src/main/kotlin/moe/nea/firmament/util/data/IDataHolder.kt deleted file mode 100644 index 5d09bcd..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/data/IDataHolder.kt +++ /dev/null @@ -1,77 +0,0 @@ - - -package moe.nea.firmament.util.data - -import java.util.concurrent.CopyOnWriteArrayList -import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents -import kotlin.reflect.KClass -import net.minecraft.client.MinecraftClient -import net.minecraft.server.command.CommandOutput -import net.minecraft.text.Text -import moe.nea.firmament.Firmament -import moe.nea.firmament.events.ScreenChangeEvent - -interface IDataHolder { - companion object { - internal var badLoads: MutableList = CopyOnWriteArrayList() - private val allConfigs: MutableMap>, IDataHolder<*>> = mutableMapOf() - private val dirty: MutableSet>> = mutableSetOf() - - internal fun , K> putDataHolder(kClass: KClass, inst: IDataHolder) { - allConfigs[kClass] = inst - } - - fun , K> markDirty(kClass: KClass) { - if (kClass !in allConfigs) { - Firmament.logger.error("Tried to markDirty '${kClass.qualifiedName}', which isn't registered as 'IConfigHolder'") - return - } - dirty.add(kClass) - } - - private fun performSaves() { - val toSave = dirty.toList().also { - dirty.clear() - } - for (it in toSave) { - val obj = allConfigs[it] - if (obj == null) { - Firmament.logger.error("Tried to save '${it}', which isn't registered as 'ConfigHolder'") - continue - } - obj.save() - } - } - - private fun warnForResetConfigs(player: CommandOutput) { - if (badLoads.isNotEmpty()) { - player.sendMessage( - Text.literal( - "The following configs have been reset: ${badLoads.joinToString(", ")}. " + - "This can be intentional, but probably isn't." - ) - ) - badLoads.clear() - } - } - - fun registerEvents() { - ScreenChangeEvent.subscribe { event -> - performSaves() - val p = MinecraftClient.getInstance().player - if (p != null) { - warnForResetConfigs(p) - } - } - ClientLifecycleEvents.CLIENT_STOPPING.register(ClientLifecycleEvents.ClientStopping { - performSaves() - }) - } - - } - - val data: T - fun save() - fun markDirty() - fun load() -} diff --git a/src/main/kotlin/moe/nea/firmament/util/data/ProfileSpecificDataHolder.kt b/src/main/kotlin/moe/nea/firmament/util/data/ProfileSpecificDataHolder.kt deleted file mode 100644 index 1cd4f22..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/data/ProfileSpecificDataHolder.kt +++ /dev/null @@ -1,84 +0,0 @@ - - -package moe.nea.firmament.util.data - -import java.nio.file.Path -import java.util.UUID -import kotlinx.serialization.KSerializer -import kotlin.io.path.createDirectories -import kotlin.io.path.deleteExisting -import kotlin.io.path.exists -import kotlin.io.path.extension -import kotlin.io.path.listDirectoryEntries -import kotlin.io.path.nameWithoutExtension -import kotlin.io.path.readText -import kotlin.io.path.writeText -import moe.nea.firmament.Firmament -import moe.nea.firmament.util.SBData - -abstract class ProfileSpecificDataHolder( - private val dataSerializer: KSerializer, - val configName: String, - private val configDefault: () -> S -) : IDataHolder { - - var allConfigs: MutableMap - - override val data: S? - get() = SBData.profileId?.let { - allConfigs.computeIfAbsent(it) { configDefault() } - } - - init { - allConfigs = readValues() - IDataHolder.putDataHolder(this::class, this) - } - - private val configDirectory: Path get() = Firmament.CONFIG_DIR.resolve("profiles").resolve(configName) - - private fun readValues(): MutableMap { - if (!configDirectory.exists()) { - configDirectory.createDirectories() - } - val profileFiles = configDirectory.listDirectoryEntries() - return profileFiles - .filter { it.extension == "json" } - .mapNotNull { - try { - UUID.fromString(it.nameWithoutExtension) to Firmament.json.decodeFromString(dataSerializer, it.readText()) - } catch (e: Exception) { /* Expecting IOException and SerializationException, but Kotlin doesn't allow multi catches*/ - IDataHolder.badLoads.add(configName) - Firmament.logger.error( - "Exception during loading of profile specific config file $it ($configName). This will reset that profiles config.", - e - ) - null - } - }.toMap().toMutableMap() - } - - override fun save() { - if (!configDirectory.exists()) { - configDirectory.createDirectories() - } - val c = allConfigs - configDirectory.listDirectoryEntries().forEach { - if (it.nameWithoutExtension !in c.mapKeys { it.toString() }) { - it.deleteExisting() - } - } - c.forEach { (name, value) -> - val f = configDirectory.resolve("$name.json") - f.writeText(Firmament.json.encodeToString(dataSerializer, value)) - } - } - - override fun markDirty() { - IDataHolder.markDirty(this::class) - } - - override fun load() { - allConfigs = readValues() - } - -} diff --git a/src/main/kotlin/moe/nea/firmament/util/filter/IteratorFilterSet.kt b/src/main/kotlin/moe/nea/firmament/util/filter/IteratorFilterSet.kt deleted file mode 100644 index 483b8d9..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/filter/IteratorFilterSet.kt +++ /dev/null @@ -1,33 +0,0 @@ - -package moe.nea.firmament.util.filter - -abstract class IteratorFilterSet(val original: java.util.Set) : java.util.Set by original { - abstract fun shouldKeepElement(element: K): Boolean - - override fun iterator(): MutableIterator { - val parentIterator = original.iterator() - return object : MutableIterator { - var lastEntry: K? = null - override fun hasNext(): Boolean { - while (lastEntry == null) { - if (!parentIterator.hasNext()) - break - val element = parentIterator.next() - if (!shouldKeepElement(element)) continue - lastEntry = element - } - return lastEntry != null - } - - override fun next(): K { - if (!hasNext()) throw NoSuchElementException() - return lastEntry ?: throw NoSuchElementException() - } - - override fun remove() { - TODO("Not yet implemented") - } - } - } -} - diff --git a/src/main/kotlin/moe/nea/firmament/util/item/NbtItemData.kt b/src/main/kotlin/moe/nea/firmament/util/item/NbtItemData.kt deleted file mode 100644 index f7f259d..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/item/NbtItemData.kt +++ /dev/null @@ -1,24 +0,0 @@ - - -package moe.nea.firmament.util.item - -import net.minecraft.component.DataComponentTypes -import net.minecraft.component.type.LoreComponent -import net.minecraft.item.ItemStack -import net.minecraft.text.Text - -var ItemStack.loreAccordingToNbt - get() = get(DataComponentTypes.LORE)?.lines ?: listOf() - set(value) { - set(DataComponentTypes.LORE, LoreComponent(value)) - } - -var ItemStack.displayNameAccordingToNbt: Text - get() = get(DataComponentTypes.CUSTOM_NAME) ?: get(DataComponentTypes.ITEM_NAME) ?: item.name - set(value) { - set(DataComponentTypes.CUSTOM_NAME, value) - } - -fun ItemStack.setCustomName(text: Text) { - set(DataComponentTypes.CUSTOM_NAME, text) -} diff --git a/src/main/kotlin/moe/nea/firmament/util/item/SkullItemData.kt b/src/main/kotlin/moe/nea/firmament/util/item/SkullItemData.kt deleted file mode 100644 index ddab88e..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/item/SkullItemData.kt +++ /dev/null @@ -1,90 +0,0 @@ - - -@file:UseSerializers(DashlessUUIDSerializer::class, InstantAsLongSerializer::class) - -package moe.nea.firmament.util.item - -import com.mojang.authlib.GameProfile -import com.mojang.authlib.minecraft.MinecraftProfileTexture -import com.mojang.authlib.properties.Property -import java.util.UUID -import kotlinx.datetime.Clock -import kotlinx.datetime.Instant -import kotlinx.serialization.Serializable -import kotlinx.serialization.UseSerializers -import kotlinx.serialization.encodeToString -import net.minecraft.component.DataComponentTypes -import net.minecraft.component.type.ProfileComponent -import net.minecraft.item.ItemStack -import net.minecraft.item.Items -import moe.nea.firmament.Firmament -import moe.nea.firmament.util.Base64Util.padToValidBase64 -import moe.nea.firmament.util.assertTrueOr -import moe.nea.firmament.util.json.DashlessUUIDSerializer -import moe.nea.firmament.util.json.InstantAsLongSerializer - -@Serializable -data class MinecraftProfileTextureKt( - val url: String, - val metadata: Map = mapOf(), -) - -@Serializable -data class MinecraftTexturesPayloadKt( - val textures: Map = mapOf(), - val profileId: UUID? = null, - val profileName: String? = null, - val isPublic: Boolean = true, - val timestamp: Instant = Clock.System.now(), -) - -fun GameProfile.setTextures(textures: MinecraftTexturesPayloadKt) { - val json = Firmament.json.encodeToString(textures) - val encoded = java.util.Base64.getEncoder().encodeToString(json.encodeToByteArray()) - properties.put(propertyTextures, Property(propertyTextures, encoded)) -} - -private val propertyTextures = "textures" - -fun ItemStack.setEncodedSkullOwner(uuid: UUID, encodedData: String) { - assert(this.item == Items.PLAYER_HEAD) - val gameProfile = GameProfile(uuid, "LameGuy123") - gameProfile.properties.put(propertyTextures, Property(propertyTextures, encodedData.padToValidBase64())) - this.set(DataComponentTypes.PROFILE, ProfileComponent(gameProfile)) -} - -val zeroUUID = UUID.fromString("d3cb85e2-3075-48a1-b213-a9bfb62360c1") -fun createSkullItem(uuid: UUID, url: String) = ItemStack(Items.PLAYER_HEAD) - .also { it.setSkullOwner(uuid, url) } - -fun ItemStack.setSkullOwner(uuid: UUID, url: String) { - assert(this.item == Items.PLAYER_HEAD) - val gameProfile = GameProfile(uuid, "nea89") - gameProfile.setTextures( - MinecraftTexturesPayloadKt( - textures = mapOf(MinecraftProfileTexture.Type.SKIN to MinecraftProfileTextureKt(url)), - profileId = uuid, - profileName = "nea89", - ) - ) - this.set(DataComponentTypes.PROFILE, ProfileComponent(gameProfile)) -} - - -fun decodeProfileTextureProperty(property: Property): MinecraftTexturesPayloadKt? { - assertTrueOr(property.name == propertyTextures) { return null } - return try { - var encodedF: String = property.value - while (encodedF.length % 4 != 0 && encodedF.last() == '=') { - encodedF = encodedF.substring(0, encodedF.length - 1) - } - val json = java.util.Base64.getDecoder().decode(encodedF).decodeToString() - Firmament.json.decodeFromString(json) - } catch (e: Exception) { - // Malformed profile data - if (Firmament.DEBUG) - e.printStackTrace() - null - } -} - diff --git a/src/main/kotlin/moe/nea/firmament/util/json/BlockPosSerializer.kt b/src/main/kotlin/moe/nea/firmament/util/json/BlockPosSerializer.kt deleted file mode 100644 index 144b0a0..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/json/BlockPosSerializer.kt +++ /dev/null @@ -1,25 +0,0 @@ -package moe.nea.firmament.util.json - -import kotlinx.serialization.KSerializer -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder -import kotlinx.serialization.serializer -import net.minecraft.util.math.BlockPos - -object BlockPosSerializer : KSerializer { - val delegate = serializer>() - - override val descriptor: SerialDescriptor - get() = SerialDescriptor("BlockPos", delegate.descriptor) - - override fun deserialize(decoder: Decoder): BlockPos { - val list = decoder.decodeSerializableValue(delegate) - require(list.size == 3) - return BlockPos(list[0], list[1], list[2]) - } - - override fun serialize(encoder: Encoder, value: BlockPos) { - encoder.encodeSerializableValue(delegate, listOf(value.x, value.y, value.z)) - } -} diff --git a/src/main/kotlin/moe/nea/firmament/util/json/DashlessUUIDSerializer.kt b/src/main/kotlin/moe/nea/firmament/util/json/DashlessUUIDSerializer.kt deleted file mode 100644 index acb1dc8..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/json/DashlessUUIDSerializer.kt +++ /dev/null @@ -1,29 +0,0 @@ - - -package moe.nea.firmament.util.json - -import java.util.UUID -import kotlinx.serialization.KSerializer -import kotlinx.serialization.descriptors.PrimitiveKind -import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder -import moe.nea.firmament.util.parseDashlessUUID - -object DashlessUUIDSerializer : KSerializer { - override val descriptor: SerialDescriptor = - PrimitiveSerialDescriptor("DashlessUUIDSerializer", PrimitiveKind.STRING) - - override fun deserialize(decoder: Decoder): UUID { - val str = decoder.decodeString() - if ("-" in str) { - return UUID.fromString(str) - } - return parseDashlessUUID(str) - } - - override fun serialize(encoder: Encoder, value: UUID) { - encoder.encodeString(value.toString().replace("-", "")) - } -} diff --git a/src/main/kotlin/moe/nea/firmament/util/json/InstantAsLongSerializer.kt b/src/main/kotlin/moe/nea/firmament/util/json/InstantAsLongSerializer.kt deleted file mode 100644 index ad738dc..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/json/InstantAsLongSerializer.kt +++ /dev/null @@ -1,22 +0,0 @@ - - -package moe.nea.firmament.util.json - -import kotlinx.datetime.Instant -import kotlinx.serialization.KSerializer -import kotlinx.serialization.descriptors.PrimitiveKind -import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder - -object InstantAsLongSerializer : KSerializer { - override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("InstantAsLongSerializer", PrimitiveKind.LONG) - override fun deserialize(decoder: Decoder): Instant { - return Instant.fromEpochMilliseconds(decoder.decodeLong()) - } - - override fun serialize(encoder: Encoder, value: Instant) { - encoder.encodeLong(value.toEpochMilliseconds()) - } -} diff --git a/src/main/kotlin/moe/nea/firmament/util/json/SingletonSerializableList.kt b/src/main/kotlin/moe/nea/firmament/util/json/SingletonSerializableList.kt deleted file mode 100644 index aa543d6..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/json/SingletonSerializableList.kt +++ /dev/null @@ -1,31 +0,0 @@ - -package moe.nea.firmament.util.json - -import kotlinx.serialization.KSerializer -import kotlinx.serialization.builtins.ListSerializer -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder -import kotlinx.serialization.json.JsonArray -import kotlinx.serialization.json.JsonDecoder -import kotlinx.serialization.json.JsonElement - -class SingletonSerializableList(val child: KSerializer) : KSerializer> { - override val descriptor: SerialDescriptor - get() = JsonElement.serializer().descriptor - - override fun deserialize(decoder: Decoder): List { - decoder as JsonDecoder - val list = JsonElement.serializer().deserialize(decoder) - if (list is JsonArray) { - return list.map { - decoder.json.decodeFromJsonElement(child, it) - } - } - return listOf(decoder.json.decodeFromJsonElement(child, list)) - } - - override fun serialize(encoder: Encoder, value: List) { - ListSerializer(child).serialize(encoder, value) - } -} diff --git a/src/main/kotlin/moe/nea/firmament/util/listutil.kt b/src/main/kotlin/moe/nea/firmament/util/listutil.kt deleted file mode 100644 index 73cb23e..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/listutil.kt +++ /dev/null @@ -1,9 +0,0 @@ - -package moe.nea.firmament.util - -fun List.lastNotNullOfOrNull(func: (T) -> R?): R? { - for (i in indices.reversed()) { - return func(this[i]) ?: continue - } - return null -} diff --git a/src/main/kotlin/moe/nea/firmament/util/propertyutil.kt b/src/main/kotlin/moe/nea/firmament/util/propertyutil.kt deleted file mode 100644 index 795a0d2..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/propertyutil.kt +++ /dev/null @@ -1,9 +0,0 @@ - - -package moe.nea.firmament.util - -import kotlin.properties.ReadOnlyProperty - -fun ReadOnlyProperty.map(mapper: (V) -> M): ReadOnlyProperty { - return ReadOnlyProperty { thisRef, property -> mapper(this@map.getValue(thisRef, property)) } -} diff --git a/src/main/kotlin/moe/nea/firmament/util/regex.kt b/src/main/kotlin/moe/nea/firmament/util/regex.kt deleted file mode 100644 index 3ce5bd8..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/regex.kt +++ /dev/null @@ -1,55 +0,0 @@ - - -package moe.nea.firmament.util - -import java.util.regex.Matcher -import java.util.regex.Pattern -import org.intellij.lang.annotations.Language -import kotlin.time.Duration -import kotlin.time.Duration.Companion.minutes -import kotlin.time.Duration.Companion.seconds - -inline fun String.ifMatches(regex: Regex, block: (MatchResult) -> T): T? = - regex.matchEntire(this)?.let(block) - -inline fun Pattern.useMatch(string: String, block: Matcher.() -> T): T? = - matcher(string) - .takeIf(Matcher::matches) - ?.let(block) - -@Language("RegExp") -val TIME_PATTERN = "[0-9]+[ms]" - -@Language("RegExp") -val SHORT_NUMBER_FORMAT = "[0-9]+(?:,[0-9]+)*(?:\\.[0-9]+)?[kKmMbB]?" - - -val siScalars = mapOf( - 'k' to 1_000.0, - 'K' to 1_000.0, - 'm' to 1_000_000.0, - 'M' to 1_000_000.0, - 'b' to 1_000_000_000.0, - 'B' to 1_000_000_000.0, -) - -fun parseTimePattern(text: String): Duration { - val length = text.dropLast(1).toInt() - return when (text.last()) { - 'm' -> length.minutes - 's' -> length.seconds - else -> error("Invalid pattern for time $text") - } -} - -fun parseShortNumber(string: String): Double { - var k = string.replace(",", "") - val scalar = k.last() - var scalarMultiplier = siScalars[scalar] - if (scalarMultiplier == null) { - scalarMultiplier = 1.0 - } else { - k = k.dropLast(1) - } - return k.toDouble() * scalarMultiplier -} diff --git a/src/main/kotlin/moe/nea/firmament/util/render/FacingThePlayerContext.kt b/src/main/kotlin/moe/nea/firmament/util/render/FacingThePlayerContext.kt deleted file mode 100644 index eb37e35..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/render/FacingThePlayerContext.kt +++ /dev/null @@ -1,101 +0,0 @@ - -package moe.nea.firmament.util.render - -import com.mojang.blaze3d.systems.RenderSystem -import io.github.notenoughupdates.moulconfig.platform.next -import org.joml.Matrix4f -import net.minecraft.client.font.TextRenderer -import net.minecraft.client.render.BufferRenderer -import net.minecraft.client.render.GameRenderer -import net.minecraft.client.render.LightmapTextureManager -import net.minecraft.client.render.RenderLayer -import net.minecraft.client.render.Tessellator -import net.minecraft.client.render.VertexConsumer -import net.minecraft.client.render.VertexFormat -import net.minecraft.client.render.VertexFormats -import net.minecraft.text.Text -import net.minecraft.util.Identifier -import net.minecraft.util.math.BlockPos -import moe.nea.firmament.util.FirmFormatters -import moe.nea.firmament.util.MC -import moe.nea.firmament.util.assertTrueOr - -@RenderContextDSL -class FacingThePlayerContext(val worldContext: RenderInWorldContext) { - val matrixStack by worldContext::matrixStack - fun waypoint(position: BlockPos, label: Text) { - text( - label, - Text.literal("§e${FirmFormatters.formatDistance(MC.player?.pos?.distanceTo(position.toCenterPos()) ?: 42069.0)}") - ) - } - - fun text( - vararg texts: Text, - verticalAlign: RenderInWorldContext.VerticalAlign = RenderInWorldContext.VerticalAlign.CENTER, - background: Int = 0x70808080, - ) { - assertTrueOr(texts.isNotEmpty()) { return@text } - for ((index, text) in texts.withIndex()) { - worldContext.matrixStack.push() - val width = MC.font.getWidth(text) - worldContext.matrixStack.translate(-width / 2F, verticalAlign.align(index, texts.size), 0F) - val vertexConsumer: VertexConsumer = - worldContext.vertexConsumers.getBuffer(RenderLayer.getTextBackgroundSeeThrough()) - val matrix4f = worldContext.matrixStack.peek().positionMatrix - vertexConsumer.vertex(matrix4f, -1.0f, -1.0f, 0.0f).color(background) - .light(LightmapTextureManager.MAX_BLOCK_LIGHT_COORDINATE).next() - vertexConsumer.vertex(matrix4f, -1.0f, MC.font.fontHeight.toFloat(), 0.0f).color(background) - .light(LightmapTextureManager.MAX_BLOCK_LIGHT_COORDINATE).next() - vertexConsumer.vertex(matrix4f, width.toFloat(), MC.font.fontHeight.toFloat(), 0.0f) - .color(background) - .light(LightmapTextureManager.MAX_BLOCK_LIGHT_COORDINATE).next() - vertexConsumer.vertex(matrix4f, width.toFloat(), -1.0f, 0.0f).color(background) - .light(LightmapTextureManager.MAX_BLOCK_LIGHT_COORDINATE).next() - worldContext.matrixStack.translate(0F, 0F, 0.01F) - - MC.font.draw( - text, - 0F, - 0F, - -1, - false, - worldContext.matrixStack.peek().positionMatrix, - worldContext.vertexConsumers, - TextRenderer.TextLayerType.SEE_THROUGH, - 0, - LightmapTextureManager.MAX_LIGHT_COORDINATE - ) - worldContext.matrixStack.pop() - } - } - - - fun texture( - texture: Identifier, width: Int, height: Int, - u1: Float, v1: Float, - u2: Float, v2: Float, - ) { - RenderSystem.setShaderTexture(0, texture) - RenderSystem.setShader(GameRenderer::getPositionTexColorProgram) - val hw = width / 2F - val hh = height / 2F - val matrix4f: Matrix4f = worldContext.matrixStack.peek().positionMatrix - val buf = Tessellator.getInstance() - .begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_TEXTURE_COLOR) - buf.vertex(matrix4f, -hw, -hh, 0F) - .color(-1) - .texture(u1, v1).next() - buf.vertex(matrix4f, -hw, +hh, 0F) - .color(-1) - .texture(u1, v2).next() - buf.vertex(matrix4f, +hw, +hh, 0F) - .color(-1) - .texture(u2, v2).next() - buf.vertex(matrix4f, +hw, -hh, 0F) - .color(-1) - .texture(u2, v1).next() - BufferRenderer.drawWithGlobalProgram(buf.end()) - } - -} diff --git a/src/main/kotlin/moe/nea/firmament/util/render/LerpUtils.kt b/src/main/kotlin/moe/nea/firmament/util/render/LerpUtils.kt deleted file mode 100644 index f2c2f25..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/render/LerpUtils.kt +++ /dev/null @@ -1,33 +0,0 @@ - -package moe.nea.firmament.util.render - -import me.shedaniel.math.Color - -val pi = Math.PI -val tau = Math.PI * 2 -fun lerpAngle(a: Float, b: Float, progress: Float): Float { - // TODO: there is at least 10 mods to many in here lol - val shortestAngle = ((((b.mod(tau) - a.mod(tau)).mod(tau)) + tau + pi).mod(tau)) - pi - return ((a + (shortestAngle) * progress).mod(tau)).toFloat() -} - -fun lerp(a: Float, b: Float, progress: Float): Float { - return a + (b - a) * progress -} -fun lerp(a: Int, b: Int, progress: Float): Int { - return (a + (b - a) * progress).toInt() -} - -fun ilerp(a: Float, b: Float, value: Float): Float { - return (value - a) / (b - a) -} - -fun lerp(a: Color, b: Color, progress: Float): Color { - return Color.ofRGBA( - lerp(a.red, b.red, progress), - lerp(a.green, b.green, progress), - lerp(a.blue, b.blue, progress), - lerp(a.alpha, b.alpha, progress), - ) -} - diff --git a/src/main/kotlin/moe/nea/firmament/util/render/RenderCircleProgress.kt b/src/main/kotlin/moe/nea/firmament/util/render/RenderCircleProgress.kt deleted file mode 100644 index a2f42b5..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/render/RenderCircleProgress.kt +++ /dev/null @@ -1,95 +0,0 @@ - -package moe.nea.firmament.util.render - -import com.mojang.blaze3d.systems.RenderSystem -import io.github.notenoughupdates.moulconfig.platform.next -import org.joml.Matrix4f -import org.joml.Vector2f -import kotlin.math.atan2 -import kotlin.math.tan -import net.minecraft.client.gui.DrawContext -import net.minecraft.client.render.BufferRenderer -import net.minecraft.client.render.GameRenderer -import net.minecraft.client.render.Tessellator -import net.minecraft.client.render.VertexFormat.DrawMode -import net.minecraft.client.render.VertexFormats -import net.minecraft.util.Identifier - -object RenderCircleProgress { - - fun renderCircle( - drawContext: DrawContext, - texture: Identifier, - progress: Float, - u1: Float, - u2: Float, - v1: Float, - v2: Float, - ) { - RenderSystem.setShaderTexture(0, texture) - RenderSystem.setShader(GameRenderer::getPositionTexColorProgram) - RenderSystem.enableBlend() - val matrix: Matrix4f = drawContext.matrices.peek().positionMatrix - val bufferBuilder = Tessellator.getInstance().begin(DrawMode.TRIANGLES, VertexFormats.POSITION_TEXTURE_COLOR) - - val corners = listOf( - Vector2f(0F, -1F), - Vector2f(1F, -1F), - Vector2f(1F, 0F), - Vector2f(1F, 1F), - Vector2f(0F, 1F), - Vector2f(-1F, 1F), - Vector2f(-1F, 0F), - Vector2f(-1F, -1F), - ) - - for (i in (0 until 8)) { - if (progress < i / 8F) { - break - } - val second = corners[(i + 1) % 8] - val first = corners[i] - if (progress <= (i + 1) / 8F) { - val internalProgress = 1 - (progress - i / 8F) * 8F - val angle = lerpAngle( - atan2(second.y, second.x), - atan2(first.y, first.x), - internalProgress - ) - if (angle < tau / 8 || angle >= tau * 7 / 8) { - second.set(1F, tan(angle)) - } else if (angle < tau * 3 / 8) { - second.set(1 / tan(angle), 1F) - } else if (angle < tau * 5 / 8) { - second.set(-1F, -tan(angle)) - } else { - second.set(-1 / tan(angle), -1F) - } - } - - fun ilerp(f: Float): Float = - ilerp(-1f, 1f, f) - - bufferBuilder - .vertex(matrix, second.x, second.y, 0F) - .texture(lerp(u1, u2, ilerp(second.x)), lerp(v1, v2, ilerp(second.y))) - .color(-1) - .next() - bufferBuilder - .vertex(matrix, first.x, first.y, 0F) - .texture(lerp(u1, u2, ilerp(first.x)), lerp(v1, v2, ilerp(first.y))) - .color(-1) - .next() - bufferBuilder - .vertex(matrix, 0F, 0F, 0F) - .texture(lerp(u1, u2, ilerp(0F)), lerp(v1, v2, ilerp(0F))) - .color(-1) - .next() - } - BufferRenderer.drawWithGlobalProgram(bufferBuilder.end()) - RenderSystem.disableBlend() - } - - - -} diff --git a/src/main/kotlin/moe/nea/firmament/util/render/RenderContextDSL.kt b/src/main/kotlin/moe/nea/firmament/util/render/RenderContextDSL.kt deleted file mode 100644 index 9bb4431..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/render/RenderContextDSL.kt +++ /dev/null @@ -1,6 +0,0 @@ - -package moe.nea.firmament.util.render - -@DslMarker -annotation class RenderContextDSL { -} diff --git a/src/main/kotlin/moe/nea/firmament/util/render/RenderInWorldContext.kt b/src/main/kotlin/moe/nea/firmament/util/render/RenderInWorldContext.kt deleted file mode 100644 index 7faa499..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/render/RenderInWorldContext.kt +++ /dev/null @@ -1,294 +0,0 @@ - - -package moe.nea.firmament.util.render - -import com.mojang.blaze3d.systems.RenderSystem -import io.github.notenoughupdates.moulconfig.platform.next -import java.lang.Math.pow -import org.joml.Matrix4f -import org.joml.Vector3f -import net.minecraft.client.gl.VertexBuffer -import net.minecraft.client.render.BufferBuilder -import net.minecraft.client.render.BufferRenderer -import net.minecraft.client.render.Camera -import net.minecraft.client.render.GameRenderer -import net.minecraft.client.render.RenderLayer -import net.minecraft.client.render.RenderPhase -import net.minecraft.client.render.RenderTickCounter -import net.minecraft.client.render.Tessellator -import net.minecraft.client.render.VertexConsumerProvider -import net.minecraft.client.render.VertexFormat -import net.minecraft.client.render.VertexFormats -import net.minecraft.client.texture.Sprite -import net.minecraft.client.util.math.MatrixStack -import net.minecraft.text.Text -import net.minecraft.util.Identifier -import net.minecraft.util.math.BlockPos -import net.minecraft.util.math.Vec3d -import moe.nea.firmament.events.WorldRenderLastEvent -import moe.nea.firmament.util.FirmFormatters -import moe.nea.firmament.util.MC - -@RenderContextDSL -class RenderInWorldContext private constructor( - private val tesselator: Tessellator, - val matrixStack: MatrixStack, - private val camera: Camera, - private val tickCounter: RenderTickCounter, - val vertexConsumers: VertexConsumerProvider.Immediate, -) { - - object RenderLayers { - val TRANSLUCENT_TRIS = RenderLayer.of("firmament_translucent_tris", - VertexFormats.POSITION_COLOR, - VertexFormat.DrawMode.TRIANGLES, - RenderLayer.DEFAULT_BUFFER_SIZE, - false, true, - RenderLayer.MultiPhaseParameters.builder() - .depthTest(RenderPhase.ALWAYS_DEPTH_TEST) - .transparency(RenderPhase.TRANSLUCENT_TRANSPARENCY) - .program(RenderPhase.COLOR_PROGRAM) - .build(false)) - } - - fun color(color: me.shedaniel.math.Color) { - color(color.red / 255F, color.green / 255f, color.blue / 255f, color.alpha / 255f) - } - - fun color(red: Float, green: Float, blue: Float, alpha: Float) { - RenderSystem.setShaderColor(red, green, blue, alpha) - } - - fun block(blockPos: BlockPos) { - matrixStack.push() - matrixStack.translate(blockPos.x.toFloat(), blockPos.y.toFloat(), blockPos.z.toFloat()) - buildCube(matrixStack.peek().positionMatrix, tesselator) - matrixStack.pop() - } - - enum class VerticalAlign { - TOP, BOTTOM, CENTER; - - fun align(index: Int, count: Int): Float { - return when (this) { - CENTER -> (index - count / 2F) * (1 + MC.font.fontHeight.toFloat()) - BOTTOM -> (index - count) * (1 + MC.font.fontHeight.toFloat()) - TOP -> (index) * (1 + MC.font.fontHeight.toFloat()) - } - } - } - - fun waypoint(position: BlockPos, vararg label: Text) { - text( - position.toCenterPos(), - *label, - Text.literal("§e${FirmFormatters.formatDistance(MC.player?.pos?.distanceTo(position.toCenterPos()) ?: 42069.0)}"), - background = 0xAA202020.toInt() - ) - } - - fun withFacingThePlayer(position: Vec3d, block: FacingThePlayerContext.() -> Unit) { - matrixStack.push() - matrixStack.translate(position.x, position.y, position.z) - val actualCameraDistance = position.distanceTo(camera.pos) - val distanceToMoveTowardsCamera = if (actualCameraDistance < 10) 0.0 else -(actualCameraDistance - 10.0) - val vec = position.subtract(camera.pos).multiply(distanceToMoveTowardsCamera / actualCameraDistance) - matrixStack.translate(vec.x, vec.y, vec.z) - matrixStack.multiply(camera.rotation) - matrixStack.scale(0.025F, -0.025F, 1F) - - FacingThePlayerContext(this).run(block) - - matrixStack.pop() - vertexConsumers.drawCurrentLayer() - } - - fun sprite(position: Vec3d, sprite: Sprite, width: Int, height: Int) { - texture( - position, sprite.atlasId, width, height, sprite.minU, sprite.minV, sprite.maxU, sprite.maxV - ) - } - - fun texture( - position: Vec3d, texture: Identifier, width: Int, height: Int, - u1: Float, v1: Float, - u2: Float, v2: Float, - ) { - withFacingThePlayer(position) { - texture(texture, width, height, u1, v1, u2, v2) - } - } - - fun text(position: Vec3d, vararg texts: Text, verticalAlign: VerticalAlign = VerticalAlign.CENTER, background: Int = 0x70808080) { - withFacingThePlayer(position) { - text(*texts, verticalAlign = verticalAlign, background = background) - } - } - - fun tinyBlock(vec3d: Vec3d, size: Float) { - RenderSystem.setShader(GameRenderer::getPositionColorProgram) - matrixStack.push() - matrixStack.translate(vec3d.x, vec3d.y, vec3d.z) - matrixStack.scale(size, size, size) - matrixStack.translate(-.5, -.5, -.5) - buildCube(matrixStack.peek().positionMatrix, tesselator) - matrixStack.pop() - } - - fun wireframeCube(blockPos: BlockPos, lineWidth: Float = 10F) { - RenderSystem.setShader(GameRenderer::getRenderTypeLinesProgram) - matrixStack.push() - RenderSystem.lineWidth(lineWidth / pow(camera.pos.squaredDistanceTo(blockPos.toCenterPos()), 0.25).toFloat()) - matrixStack.translate(blockPos.x.toFloat(), blockPos.y.toFloat(), blockPos.z.toFloat()) - buildWireFrameCube(matrixStack.peek(), tesselator) - matrixStack.pop() - } - - fun line(vararg points: Vec3d, lineWidth: Float = 10F) { - line(points.toList(), lineWidth) - } - - fun tracer(toWhere: Vec3d, lineWidth: Float = 3f) { - val cameraForward = Vector3f(0f, 0f, 1f).rotate(camera.rotation) - line(camera.pos.add(Vec3d(cameraForward)), toWhere, lineWidth = lineWidth) - } - - fun line(points: List, lineWidth: Float = 10F) { - RenderSystem.setShader(GameRenderer::getRenderTypeLinesProgram) - RenderSystem.lineWidth(lineWidth) - val buffer = tesselator.begin(VertexFormat.DrawMode.LINES, VertexFormats.LINES) - - val matrix = matrixStack.peek() - var lastNormal: Vector3f? = null - points.zipWithNext().forEach { (a, b) -> - val normal = Vector3f(b.x.toFloat(), b.y.toFloat(), b.z.toFloat()) - .sub(a.x.toFloat(), a.y.toFloat(), a.z.toFloat()) - .normalize() - val lastNormal0 = lastNormal ?: normal - lastNormal = normal - buffer.vertex(matrix.positionMatrix, a.x.toFloat(), a.y.toFloat(), a.z.toFloat()) - .color(-1) - .normal(matrix, lastNormal0.x, lastNormal0.y, lastNormal0.z) - .next() - buffer.vertex(matrix.positionMatrix, b.x.toFloat(), b.y.toFloat(), b.z.toFloat()) - .color(-1) - .normal(matrix, normal.x, normal.y, normal.z) - .next() - } - - BufferRenderer.drawWithGlobalProgram(buffer.end()) - } - - companion object { - private fun doLine( - matrix: MatrixStack.Entry, - buf: BufferBuilder, - i: Float, - j: Float, - k: Float, - x: Float, - y: Float, - z: Float - ) { - val normal = Vector3f(x, y, z) - .sub(i, j, k) - .normalize() - buf.vertex(matrix.positionMatrix, i, j, k) - .normal(matrix, normal.x, normal.y, normal.z) - .color(-1) - .next() - buf.vertex(matrix.positionMatrix, x, y, z) - .normal(matrix, normal.x, normal.y, normal.z) - .color(-1) - .next() - } - - - private fun buildWireFrameCube(matrix: MatrixStack.Entry, tessellator: Tessellator) { - val buf = tessellator.begin(VertexFormat.DrawMode.LINES, VertexFormats.LINES) - - for (i in 0..1) { - for (j in 0..1) { - val i = i.toFloat() - val j = j.toFloat() - doLine(matrix, buf, 0F, i, j, 1F, i, j) - doLine(matrix, buf, i, 0F, j, i, 1F, j) - doLine(matrix, buf, i, j, 0F, i, j, 1F) - } - } - BufferRenderer.drawWithGlobalProgram(buf.end()) - } - - private fun buildCube(matrix: Matrix4f, tessellator: Tessellator) { - val buf = tessellator.begin(VertexFormat.DrawMode.TRIANGLES, VertexFormats.POSITION_COLOR) - buf.vertex(matrix, 0.0F, 0.0F, 0.0F).color(-1).next() - buf.vertex(matrix, 0.0F, 0.0F, 1.0F).color(-1).next() - buf.vertex(matrix, 0.0F, 1.0F, 1.0F).color(-1).next() - buf.vertex(matrix, 1.0F, 1.0F, 0.0F).color(-1).next() - buf.vertex(matrix, 0.0F, 0.0F, 0.0F).color(-1).next() - buf.vertex(matrix, 0.0F, 1.0F, 0.0F).color(-1).next() - buf.vertex(matrix, 1.0F, 0.0F, 1.0F).color(-1).next() - buf.vertex(matrix, 0.0F, 0.0F, 0.0F).color(-1).next() - buf.vertex(matrix, 1.0F, 0.0F, 0.0F).color(-1).next() - buf.vertex(matrix, 1.0F, 1.0F, 0.0F).color(-1).next() - buf.vertex(matrix, 1.0F, 0.0F, 0.0F).color(-1).next() - buf.vertex(matrix, 0.0F, 0.0F, 0.0F).color(-1).next() - buf.vertex(matrix, 0.0F, 0.0F, 0.0F).color(-1).next() - buf.vertex(matrix, 0.0F, 1.0F, 1.0F).color(-1).next() - buf.vertex(matrix, 0.0F, 1.0F, 0.0F).color(-1).next() - buf.vertex(matrix, 1.0F, 0.0F, 1.0F).color(-1).next() - buf.vertex(matrix, 0.0F, 0.0F, 1.0F).color(-1).next() - buf.vertex(matrix, 0.0F, 0.0F, 0.0F).color(-1).next() - buf.vertex(matrix, 0.0F, 1.0F, 1.0F).color(-1).next() - buf.vertex(matrix, 0.0F, 0.0F, 1.0F).color(-1).next() - buf.vertex(matrix, 1.0F, 0.0F, 1.0F).color(-1).next() - buf.vertex(matrix, 1.0F, 1.0F, 1.0F).color(-1).next() - buf.vertex(matrix, 1.0F, 0.0F, 0.0F).color(-1).next() - buf.vertex(matrix, 1.0F, 1.0F, 0.0F).color(-1).next() - buf.vertex(matrix, 1.0F, 0.0F, 0.0F).color(-1).next() - buf.vertex(matrix, 1.0F, 1.0F, 1.0F).color(-1).next() - buf.vertex(matrix, 1.0F, 0.0F, 1.0F).color(-1).next() - buf.vertex(matrix, 1.0F, 1.0F, 1.0F).color(-1).next() - buf.vertex(matrix, 1.0F, 1.0F, 0.0F).color(-1).next() - buf.vertex(matrix, 0.0F, 1.0F, 0.0F).color(-1).next() - buf.vertex(matrix, 1.0F, 1.0F, 1.0F).color(-1).next() - buf.vertex(matrix, 0.0F, 1.0F, 0.0F).color(-1).next() - buf.vertex(matrix, 0.0F, 1.0F, 1.0F).color(-1).next() - buf.vertex(matrix, 1.0F, 1.0F, 1.0F).color(-1).next() - buf.vertex(matrix, 0.0F, 1.0F, 1.0F).color(-1).next() - buf.vertex(matrix, 1.0F, 0.0F, 1.0F).color(-1).next() - RenderLayers.TRANSLUCENT_TRIS.draw(buf.end()) - } - - - fun renderInWorld(event: WorldRenderLastEvent, block: RenderInWorldContext. () -> Unit) { - RenderSystem.disableDepthTest() - RenderSystem.enableBlend() - RenderSystem.defaultBlendFunc() - RenderSystem.disableCull() - - event.matrices.push() - event.matrices.translate(-event.camera.pos.x, -event.camera.pos.y, -event.camera.pos.z) - - val ctx = RenderInWorldContext( - RenderSystem.renderThreadTesselator(), - event.matrices, - event.camera, - event.tickCounter, - event.vertexConsumers - ) - - block(ctx) - - event.matrices.pop() - - RenderSystem.setShaderColor(1F, 1F, 1F, 1F) - VertexBuffer.unbind() - RenderSystem.enableDepthTest() - RenderSystem.enableCull() - RenderSystem.disableBlend() - } - } -} - - diff --git a/src/main/kotlin/moe/nea/firmament/util/render/TranslatedScissors.kt b/src/main/kotlin/moe/nea/firmament/util/render/TranslatedScissors.kt deleted file mode 100644 index c1e6544..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/render/TranslatedScissors.kt +++ /dev/null @@ -1,22 +0,0 @@ - -package moe.nea.firmament.util.render - -import org.joml.Vector4f -import net.minecraft.client.gui.DrawContext - -fun DrawContext.enableScissorWithTranslation(x1: Float, y1: Float, x2: Float, y2: Float) { - val pMat = matrices.peek().positionMatrix - val target = Vector4f() - - target.set(x1, y1, 0f, 1f) - target.mul(pMat) - val scissorX1 = target.x - val scissorY1 = target.y - - target.set(x2, y2, 0f, 1f) - target.mul(pMat) - val scissorX2 = target.x - val scissorY2 = target.y - - enableScissor(scissorX1.toInt(), scissorY1.toInt(), scissorX2.toInt(), scissorY2.toInt()) -} diff --git a/src/main/kotlin/moe/nea/firmament/util/stringutil.kt b/src/main/kotlin/moe/nea/firmament/util/stringutil.kt deleted file mode 100644 index 56f8dbe..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/stringutil.kt +++ /dev/null @@ -1,6 +0,0 @@ - -package moe.nea.firmament.util - -fun parseIntWithComma(string: String): Int { - return string.replace(",", "").toInt() -} diff --git a/src/main/kotlin/moe/nea/firmament/util/textutil.kt b/src/main/kotlin/moe/nea/firmament/util/textutil.kt deleted file mode 100644 index a05733c..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/textutil.kt +++ /dev/null @@ -1,117 +0,0 @@ - - -package moe.nea.firmament.util - -import net.minecraft.text.MutableText -import net.minecraft.text.PlainTextContent -import net.minecraft.text.Style -import net.minecraft.text.Text -import net.minecraft.text.TranslatableTextContent -import net.minecraft.util.Formatting -import moe.nea.firmament.Firmament - - -class TextMatcher(text: Text) { - data class State( - var iterator: MutableList, - var currentText: Text?, - var offset: Int, - var textContent: String, - ) - - var state = State( - mutableListOf(text), - null, - 0, - "" - ) - - fun pollChunk(): Boolean { - val firstOrNull = state.iterator.removeFirstOrNull() ?: return false - state.offset = 0 - state.currentText = firstOrNull - state.textContent = when (val content = firstOrNull.content) { - is PlainTextContent.Literal -> content.string - else -> { - Firmament.logger.warn("TextContent of type ${content.javaClass} not understood.") - return false - } - } - state.iterator.addAll(0, firstOrNull.siblings) - return true - } - - fun pollChunks(): Boolean { - while (state.offset !in state.textContent.indices) { - if (!pollChunk()) { - return false - } - } - return true - } - - fun pollChar(): Char? { - if (!pollChunks()) return null - return state.textContent[state.offset++] - } - - - fun expectString(string: String): Boolean { - var found = "" - while (found.length < string.length) { - if (!pollChunks()) return false - val takeable = state.textContent.drop(state.offset).take(string.length - found.length) - state.offset += takeable.length - found += takeable - } - return found == string - } -} - -val formattingChars = "kmolnrKMOLNR".toSet() -fun CharSequence.removeColorCodes(keepNonColorCodes: Boolean = false): String { - var nextParagraph = indexOf('§') - if (nextParagraph < 0) return this.toString() - val stringBuffer = StringBuilder(this.length) - var readIndex = 0 - while (nextParagraph >= 0) { - stringBuffer.append(this, readIndex, nextParagraph) - if (keepNonColorCodes && nextParagraph + 1 < length && this[nextParagraph + 1] in formattingChars) { - readIndex = nextParagraph - nextParagraph = indexOf('§', startIndex = readIndex + 1) - } else { - readIndex = nextParagraph + 2 - nextParagraph = indexOf('§', startIndex = readIndex) - } - if (readIndex > this.length) - readIndex = this.length - } - stringBuffer.append(this, readIndex, this.length) - return stringBuffer.toString() -} - -val Text.unformattedString: String - get() = string.removeColorCodes() - - -fun MutableText.withColor(formatting: Formatting) = this.styled { it.withColor(formatting).withItalic(false) } - -fun Text.transformEachRecursively(function: (Text) -> Text): Text { - val c = this.content - if (c is TranslatableTextContent) { - return Text.translatableWithFallback(c.key, c.fallback, *c.args.map { - (if (it is Text) it else Text.literal(it.toString())).transformEachRecursively(function) - }.toTypedArray()).also { new -> - new.style = this.style - new.siblings.clear() - this.siblings.forEach { child -> - new.siblings.add(child.transformEachRecursively(function)) - } - } - } - return function(this.copy().also { it.siblings.clear() }).also { tt -> - this.siblings.forEach { - tt.siblings.add(it.transformEachRecursively(function)) - } - } -} diff --git a/src/main/kotlin/moe/nea/firmament/util/uuid.kt b/src/main/kotlin/moe/nea/firmament/util/uuid.kt deleted file mode 100644 index 4aa0749..0000000 --- a/src/main/kotlin/moe/nea/firmament/util/uuid.kt +++ /dev/null @@ -1,12 +0,0 @@ - - -package moe.nea.firmament.util - -import java.math.BigInteger -import java.util.UUID - -fun parseDashlessUUID(dashlessUuid: String): UUID { - val most = BigInteger(dashlessUuid.substring(0, 16), 16) - val least = BigInteger(dashlessUuid.substring(16, 32), 16) - return UUID(most.toLong(), least.toLong()) -} diff --git a/src/main/kotlin/rei/FirmamentReiPlugin.kt b/src/main/kotlin/rei/FirmamentReiPlugin.kt new file mode 100644 index 0000000..b585336 --- /dev/null +++ b/src/main/kotlin/rei/FirmamentReiPlugin.kt @@ -0,0 +1,128 @@ + + +package moe.nea.firmament.rei + +import me.shedaniel.rei.api.client.plugins.REIClientPlugin +import me.shedaniel.rei.api.client.registry.category.CategoryRegistry +import me.shedaniel.rei.api.client.registry.display.DisplayRegistry +import me.shedaniel.rei.api.client.registry.entry.CollapsibleEntryRegistry +import me.shedaniel.rei.api.client.registry.entry.EntryRegistry +import me.shedaniel.rei.api.client.registry.screen.ExclusionZones +import me.shedaniel.rei.api.client.registry.screen.OverlayDecider +import me.shedaniel.rei.api.client.registry.screen.ScreenRegistry +import me.shedaniel.rei.api.client.registry.transfer.TransferHandler +import me.shedaniel.rei.api.client.registry.transfer.TransferHandlerRegistry +import me.shedaniel.rei.api.common.entry.EntryStack +import me.shedaniel.rei.api.common.entry.type.EntryTypeRegistry +import me.shedaniel.rei.api.common.entry.type.VanillaEntryTypes +import net.minecraft.client.gui.screen.Screen +import net.minecraft.client.gui.screen.ingame.GenericContainerScreen +import net.minecraft.client.gui.screen.ingame.HandledScreen +import net.minecraft.item.ItemStack +import net.minecraft.text.Text +import net.minecraft.util.ActionResult +import net.minecraft.util.Identifier +import moe.nea.firmament.events.HandledScreenPushREIEvent +import moe.nea.firmament.features.inventory.CraftingOverlay +import moe.nea.firmament.features.inventory.storageoverlay.StorageOverlayScreen +import moe.nea.firmament.rei.recipes.SBCraftingRecipe +import moe.nea.firmament.rei.recipes.SBEssenceUpgradeRecipe +import moe.nea.firmament.rei.recipes.SBForgeRecipe +import moe.nea.firmament.rei.recipes.SBKatRecipe +import moe.nea.firmament.rei.recipes.SBMobDropRecipe +import moe.nea.firmament.repo.RepoManager +import moe.nea.firmament.util.SkyblockId +import moe.nea.firmament.util.skyblockId +import moe.nea.firmament.util.unformattedString + + +class FirmamentReiPlugin : REIClientPlugin { + + companion object { + fun EntryStack.asItemEntry(): EntryStack { + return EntryStack.of(VanillaEntryTypes.ITEM, value.asImmutableItemStack()) + } + + val SKYBLOCK_ITEM_TYPE_ID = Identifier.of("firmament", "skyblockitems") + } + + override fun registerTransferHandlers(registry: TransferHandlerRegistry) { + registry.register(TransferHandler { context -> + val screen = context.containerScreen + val display = context.display + if (display !is SBCraftingRecipe || screen !is GenericContainerScreen || screen.title?.unformattedString != "Craft Item") { + return@TransferHandler TransferHandler.Result.createNotApplicable() + } + if (context.isActuallyCrafting) + CraftingOverlay.setOverlay(screen, display) + return@TransferHandler TransferHandler.Result.createSuccessful().blocksFurtherHandling(true) + }) + } + + override fun registerEntryTypes(registry: EntryTypeRegistry) { + registry.register(SKYBLOCK_ITEM_TYPE_ID, SBItemEntryDefinition) + } + + override fun registerCategories(registry: CategoryRegistry) { + registry.add(SBCraftingRecipe.Category) + registry.add(SBForgeRecipe.Category) + registry.add(SBMobDropRecipe.Category) + registry.add(SBKatRecipe.Category) + registry.add(SBEssenceUpgradeRecipe.Category) + } + + override fun registerExclusionZones(zones: ExclusionZones) { + zones.register(HandledScreen::class.java) { HandledScreenPushREIEvent.publish(HandledScreenPushREIEvent(it)).rectangles } + zones.register(StorageOverlayScreen::class.java) { it.getBounds() } + } + + override fun registerDisplays(registry: DisplayRegistry) { + registry.registerDisplayGenerator( + SBCraftingRecipe.Category.catIdentifier, + SkyblockCraftingRecipeDynamicGenerator) + registry.registerDisplayGenerator( + SBForgeRecipe.Category.categoryIdentifier, + SkyblockForgeRecipeDynamicGenerator) + registry.registerDisplayGenerator( + SBMobDropRecipe.Category.categoryIdentifier, + SkyblockMobDropRecipeDynamicGenerator) + registry.registerDisplayGenerator( + SBKatRecipe.Category.categoryIdentifier, + SkyblockKatRecipeDynamicGenerator) + registry.registerDisplayGenerator( + SBEssenceUpgradeRecipe.Category.categoryIdentifier, + SkyblockEssenceRecipeDynamicGenerator + ) + } + + override fun registerCollapsibleEntries(registry: CollapsibleEntryRegistry) { + if (!RepoManager.Config.disableItemGroups) + RepoManager.neuRepo.constants.parents.parents + .forEach { (parent, children) -> + registry.group( + SkyblockId(parent).identifier, + Text.literal(RepoManager.getNEUItem(SkyblockId(parent))?.displayName ?: parent), + (children + parent).map { SBItemEntryDefinition.getEntry(SkyblockId(it)) }) + } + } + + override fun registerScreens(registry: ScreenRegistry) { + registry.registerDecider(object : OverlayDecider { + override fun isHandingScreen(screen: Class?): Boolean { + return screen == StorageOverlayScreen::class.java + } + + override fun shouldScreenBeOverlaid(screen: R): ActionResult { + return ActionResult.SUCCESS + } + }) + registry.registerFocusedStack(SkyblockItemIdFocusedStackProvider) + } + + override fun registerEntries(registry: EntryRegistry) { + registry.removeEntryIf { true } + RepoManager.neuRepo.items?.items?.values?.forEach { neuItem -> + registry.addEntry(SBItemEntryDefinition.getEntry(neuItem.skyblockId)) + } + } +} diff --git a/src/main/kotlin/rei/NEUItemEntryRenderer.kt b/src/main/kotlin/rei/NEUItemEntryRenderer.kt new file mode 100644 index 0000000..ba99b30 --- /dev/null +++ b/src/main/kotlin/rei/NEUItemEntryRenderer.kt @@ -0,0 +1,186 @@ +/* + * SPDX-FileCopyrightText: 2018-2023 shedaniel + * SPDX-FileCopyrightText: 2023 Linnea Gräf + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + * SPDX-License-Identifier: MIT + */ + +package moe.nea.firmament.rei + +import com.mojang.blaze3d.platform.GlStateManager.DstFactor +import com.mojang.blaze3d.platform.GlStateManager.SrcFactor +import com.mojang.blaze3d.systems.RenderSystem +import me.shedaniel.math.Rectangle +import me.shedaniel.rei.api.client.entry.renderer.BatchedEntryRenderer +import me.shedaniel.rei.api.client.entry.renderer.EntryRenderer +import me.shedaniel.rei.api.client.gui.widgets.Tooltip +import me.shedaniel.rei.api.client.gui.widgets.TooltipContext +import me.shedaniel.rei.api.common.entry.EntryStack +import net.minecraft.client.MinecraftClient +import net.minecraft.client.gui.DrawContext +import net.minecraft.client.render.DiffuseLighting +import net.minecraft.client.render.LightmapTextureManager +import net.minecraft.client.render.OverlayTexture +import net.minecraft.client.render.VertexConsumerProvider +import net.minecraft.client.render.model.BakedModel +import net.minecraft.client.render.model.json.ModelTransformationMode +import net.minecraft.client.texture.SpriteAtlasTexture +import net.minecraft.item.Item +import net.minecraft.item.ItemStack +import net.minecraft.item.tooltip.TooltipType +import moe.nea.firmament.rei.FirmamentReiPlugin.Companion.asItemEntry + +object NEUItemEntryRenderer : EntryRenderer, BatchedEntryRenderer { + override fun render( + entry: EntryStack, + context: DrawContext, + bounds: Rectangle, + mouseX: Int, + mouseY: Int, + delta: Float + ) { + entry.asItemEntry().render(context, bounds, mouseX, mouseY, delta) + } + + val minecraft = MinecraftClient.getInstance() + + override fun getTooltip(entry: EntryStack, tooltipContext: TooltipContext): Tooltip? { + val stack = entry.value.asImmutableItemStack() + val lore = stack.getTooltip( + Item.TooltipContext.DEFAULT, + null, + TooltipType.BASIC + ) + return Tooltip.create(lore) + } + + override fun getExtraData(entry: EntryStack): BakedModel { + return minecraft.itemRenderer.getModel(entry.asItemEntry().value, minecraft.world, minecraft.player, 0) + } + + override fun getBatchIdentifier(entry: EntryStack?, bounds: Rectangle?, extraData: BakedModel): Int { + return 1738923 + if (extraData.isSideLit) 1 else 0 + } + + override fun startBatch( + entry: EntryStack, + model: BakedModel, + graphics: DrawContext, + delta: Float + ) { + val modelViewStack = RenderSystem.getModelViewStack() + modelViewStack.pushMatrix() + modelViewStack.scale(20.0f, 20.0f, 1.0f) + RenderSystem.applyModelViewMatrix() + setupGL(model) + } + + fun setupGL(model: BakedModel) { + minecraft.textureManager.getTexture(SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE) + .setFilter(false, false) + RenderSystem.setShaderTexture(0, SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE) + RenderSystem.enableBlend() + RenderSystem.blendFunc(SrcFactor.SRC_ALPHA, DstFactor.ONE_MINUS_SRC_ALPHA) + RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f) + val sideLit = model.isSideLit + if (!sideLit) { + DiffuseLighting.disableGuiDepthLighting() + } + } + + override fun renderBase( + entry: EntryStack, + model: BakedModel, + graphics: DrawContext, + immediate: VertexConsumerProvider.Immediate, + bounds: Rectangle, + mouseX: Int, + mouseY: Int, + delta: Float + ) { + if (entry.isEmpty) return + val value = entry.asItemEntry().value + graphics.matrices.push() + graphics.matrices.translate(bounds.centerX.toFloat() / 20.0f, bounds.centerY.toFloat() / 20.0f, 0.0f) + graphics.matrices.scale( + bounds.getWidth().toFloat() / 20.0f, + -(bounds.getWidth() + bounds.getHeight()).toFloat() / 2.0f / 20.0f, + 1.0f + ) + minecraft + .itemRenderer + .renderItem( + value, + ModelTransformationMode.GUI, + false, + graphics.matrices, + immediate, + LightmapTextureManager.MAX_LIGHT_COORDINATE, + OverlayTexture.DEFAULT_UV, + model + ) + graphics.matrices.pop() + + } + + override fun afterBase( + entry: EntryStack, + model: BakedModel, + graphics: DrawContext, + delta: Float + ) { + RenderSystem.getModelViewStack().popMatrix() + RenderSystem.applyModelViewMatrix() + this.endGL(model) + } + + fun endGL(model: BakedModel) { + RenderSystem.enableDepthTest() + val sideLit = model.isSideLit + if (!sideLit) { + DiffuseLighting.enableGuiDepthLighting() + } + } + + override fun renderOverlay( + entry: EntryStack, + extraData: BakedModel, + graphics: DrawContext, + immediate: VertexConsumerProvider.Immediate, + bounds: Rectangle, + mouseX: Int, + mouseY: Int, + delta: Float + ) { + val modelViewStack = RenderSystem.getModelViewStack() + modelViewStack.pushMatrix() + modelViewStack.mul(graphics.matrices.peek().positionMatrix) + modelViewStack.translate(bounds.x.toFloat(), bounds.y.toFloat(), 0.0f) + modelViewStack.scale( + bounds.width.toFloat() / 16.0f, + -(bounds.getWidth() + bounds.getHeight()).toFloat() / 2.0f / 16.0f, + 1.0f + ) + RenderSystem.applyModelViewMatrix() + renderOverlay(DrawContext(minecraft, graphics.vertexConsumers), entry.asItemEntry()) + modelViewStack.popMatrix() + RenderSystem.applyModelViewMatrix() + } + + fun renderOverlay(graphics: DrawContext, entry: EntryStack) { + if (!entry.isEmpty) { + graphics.drawItemInSlot(MinecraftClient.getInstance().textRenderer, entry.value, 0, 0, null) + } + } + + override fun endBatch( + entry: EntryStack?, + extraData: BakedModel?, + graphics: DrawContext?, + delta: Float + ) { + } + +} diff --git a/src/main/kotlin/rei/NEUItemEntrySerializer.kt b/src/main/kotlin/rei/NEUItemEntrySerializer.kt new file mode 100644 index 0000000..a35d75f --- /dev/null +++ b/src/main/kotlin/rei/NEUItemEntrySerializer.kt @@ -0,0 +1,29 @@ + + +package moe.nea.firmament.rei + +import me.shedaniel.rei.api.common.entry.EntrySerializer +import me.shedaniel.rei.api.common.entry.EntryStack +import net.minecraft.nbt.NbtCompound +import moe.nea.firmament.util.SkyblockId + +object NEUItemEntrySerializer : EntrySerializer { + const val SKYBLOCK_ID_ENTRY = "SKYBLOCK_ID" + const val SKYBLOCK_ITEM_COUNT = "SKYBLOCK_ITEM_COUNT" + + override fun supportSaving(): Boolean = true + override fun supportReading(): Boolean = true + + override fun read(tag: NbtCompound): SBItemStack { + val id = SkyblockId(tag.getString(SKYBLOCK_ID_ENTRY)) + val count = if (tag.contains(SKYBLOCK_ITEM_COUNT)) tag.getInt(SKYBLOCK_ITEM_COUNT) else 1 + return SBItemStack(id, count) + } + + override fun save(entry: EntryStack, value: SBItemStack): NbtCompound { + return NbtCompound().apply { + putString(SKYBLOCK_ID_ENTRY, value.skyblockId.neuItem) + putInt(SKYBLOCK_ITEM_COUNT, value.getStackSize()) + } + } +} diff --git a/src/main/kotlin/rei/SBItemEntryDefinition.kt b/src/main/kotlin/rei/SBItemEntryDefinition.kt new file mode 100644 index 0000000..5c6740e --- /dev/null +++ b/src/main/kotlin/rei/SBItemEntryDefinition.kt @@ -0,0 +1,254 @@ + + +package moe.nea.firmament.rei + +import io.github.moulberry.repo.constants.PetNumbers +import io.github.moulberry.repo.data.NEUIngredient +import io.github.moulberry.repo.data.NEUItem +import io.github.moulberry.repo.data.Rarity +import java.util.stream.Stream +import me.shedaniel.rei.api.client.entry.renderer.EntryRenderer +import me.shedaniel.rei.api.common.entry.EntrySerializer +import me.shedaniel.rei.api.common.entry.EntryStack +import me.shedaniel.rei.api.common.entry.comparison.ComparisonContext +import me.shedaniel.rei.api.common.entry.type.EntryDefinition +import me.shedaniel.rei.api.common.entry.type.EntryType +import me.shedaniel.rei.api.common.entry.type.VanillaEntryTypes +import net.minecraft.item.ItemStack +import net.minecraft.registry.tag.TagKey +import net.minecraft.text.Text +import net.minecraft.util.Formatting +import net.minecraft.util.Identifier +import moe.nea.firmament.rei.FirmamentReiPlugin.Companion.asItemEntry +import moe.nea.firmament.repo.ExpLadders +import moe.nea.firmament.repo.ItemCache +import moe.nea.firmament.repo.ItemCache.asItemStack +import moe.nea.firmament.repo.RepoManager +import moe.nea.firmament.util.FirmFormatters +import moe.nea.firmament.util.HypixelPetInfo +import moe.nea.firmament.util.LegacyFormattingCode +import moe.nea.firmament.util.SkyblockId +import moe.nea.firmament.util.appendLore +import moe.nea.firmament.util.item.displayNameAccordingToNbt +import moe.nea.firmament.util.petData +import moe.nea.firmament.util.skyBlockId +import moe.nea.firmament.util.withColor + +// TODO: add in extra data like pet info, into this structure +data class PetData( + val rarity: Rarity, + val petId: String, + val exp: Double, + val isStub: Boolean = false, +) { + companion object { + fun fromHypixel(petInfo: HypixelPetInfo) = PetData( + petInfo.tier, petInfo.type, petInfo.exp, + ) + + fun forLevel(petId: String, rarity: Rarity, level: Int) = PetData( + rarity, petId, ExpLadders.getExpLadder(petId, rarity).getPetExpForLevel(level).toDouble() + ) + } + + val levelData by lazy { ExpLadders.getExpLadder(petId, rarity).getPetLevel(exp) } +} + +data class SBItemStack constructor( + val skyblockId: SkyblockId, + val neuItem: NEUItem?, + private var stackSize: Int, + private var petData: PetData?, + val extraLore: List = emptyList(), + // TODO: grab this star data from nbt if possible + val stars: Int = 0, +) { + + fun getStackSize() = stackSize + fun setStackSize(newSize: Int) { + this.stackSize = newSize + this.itemStack_ = null + } + + fun getPetData() = petData + fun setPetData(petData: PetData?) { + this.petData = petData + this.itemStack_ = null + } + + constructor(skyblockId: SkyblockId, petData: PetData) : this( + skyblockId, + RepoManager.getNEUItem(skyblockId), + 1, + petData + ) + + constructor(skyblockId: SkyblockId, stackSize: Int = 1) : this( + skyblockId, + RepoManager.getNEUItem(skyblockId), + stackSize, + RepoManager.getPotentialStubPetData(skyblockId) + ) + + private fun injectReplacementDataForPetLevel( + petInfo: PetNumbers, + level: Int, + replacementData: MutableMap + ) { + val stats = petInfo.interpolatedStatsAtLevel(level) ?: return + stats.otherNumbers.forEachIndexed { index, it -> + replacementData[index.toString()] = FirmFormatters.formatCommas(it, 1) + } + stats.statNumbers.forEach { (t, u) -> + replacementData[t] = FirmFormatters.formatCommas(u, 1) + } + } + + private fun injectReplacementDataForPets(replacementData: MutableMap) { + val petData = this.petData ?: return + val petInfo = RepoManager.neuRepo.constants.petNumbers[petData.petId]?.get(petData.rarity) ?: return + if (petData.isStub) { + val mapLow = mutableMapOf() + injectReplacementDataForPetLevel(petInfo, petInfo.lowLevel, mapLow) + val mapHigh = mutableMapOf() + injectReplacementDataForPetLevel(petInfo, petInfo.highLevel, mapHigh) + mapHigh.forEach { (key, highValue) -> + mapLow.merge(key, highValue) { a, b -> "$a → $b" } + } + replacementData.putAll(mapLow) + replacementData["LVL"] = "${petInfo.lowLevel} → ${petInfo.highLevel}" + } else { + injectReplacementDataForPetLevel(petInfo, petData.levelData.currentLevel, replacementData) + replacementData["LVL"] = petData.levelData.currentLevel.toString() + } + } + + + private var itemStack_: ItemStack? = null + + private val itemStack: ItemStack + get() { + val itemStack = itemStack_ ?: run { + if (skyblockId == SkyblockId.COINS) + return@run ItemCache.coinItem(stackSize).also { it.appendLore(extraLore) } + val replacementData = mutableMapOf() + injectReplacementDataForPets(replacementData) + return@run neuItem.asItemStack(idHint = skyblockId, replacementData) + .copyWithCount(stackSize) + .also { it.appendLore(extraLore) } + .also { enhanceStatsByStars(it, stars) } + } + if (itemStack_ == null) + itemStack_ = itemStack + return itemStack + } + + + private fun starString(stars: Int): Text { + if (stars <= 0) return Text.empty() + val tiers = listOf( + LegacyFormattingCode.GOLD, + LegacyFormattingCode.LIGHT_PURPLE, + LegacyFormattingCode.AQUA, + ) + val maxStars = 5 + if (stars > tiers.size * maxStars) return Text.literal(" ${stars}✪").withColor(Formatting.RED) + val starBaseTier = (stars - 1) / maxStars + val starBaseColor = tiers[starBaseTier] + val starsInCurrentTier = stars - starBaseTier * maxStars + val starString = Text.literal(" " + "✪".repeat(starsInCurrentTier)).withColor(starBaseColor.modern) + if (starBaseTier > 0) { + val starLastTier = tiers[starBaseTier - 1] + val starsInLastTier = 5 - starsInCurrentTier + starString.append(Text.literal("✪".repeat(starsInLastTier)).withColor(starLastTier.modern)) + } + return starString + } + + private fun enhanceStatsByStars(itemStack: ItemStack, stars: Int) { + if (stars == 0) return + // TODO: increase stats and add the star level into the nbt data so star displays work + itemStack.displayNameAccordingToNbt = itemStack.displayNameAccordingToNbt.copy() + .append(starString(stars)) + } + + fun asImmutableItemStack(): ItemStack { + return itemStack + } + + fun asItemStack(): ItemStack { + return itemStack.copy() + } +} + +object SBItemEntryDefinition : EntryDefinition { + override fun equals(o1: SBItemStack, o2: SBItemStack, context: ComparisonContext): Boolean { + return o1.skyblockId == o2.skyblockId && o1.getStackSize() == o2.getStackSize() + } + + override fun cheatsAs(entry: EntryStack?, value: SBItemStack): ItemStack { + return value.asItemStack() + } + + override fun getValueType(): Class = SBItemStack::class.java + override fun getType(): EntryType = EntryType.deferred(FirmamentReiPlugin.SKYBLOCK_ITEM_TYPE_ID) + + override fun getRenderer(): EntryRenderer = NEUItemEntryRenderer + + override fun getSerializer(): EntrySerializer { + return NEUItemEntrySerializer + } + + override fun getTagsFor(entry: EntryStack?, value: SBItemStack?): Stream>? { + return Stream.empty() + } + + override fun asFormattedText(entry: EntryStack, value: SBItemStack): Text { + return VanillaEntryTypes.ITEM.definition.asFormattedText(entry.asItemEntry(), value.asItemStack()) + } + + override fun hash(entry: EntryStack, value: SBItemStack, context: ComparisonContext): Long { + // Repo items are immutable, and get replaced entirely when loaded from disk + return value.skyblockId.hashCode() * 31L + } + + override fun wildcard(entry: EntryStack?, value: SBItemStack): SBItemStack { + return value.copy(stackSize = 1, petData = RepoManager.getPotentialStubPetData(value.skyblockId), + stars = 0, extraLore = listOf()) + } + + override fun normalize(entry: EntryStack?, value: SBItemStack): SBItemStack { + return wildcard(entry, value) + } + + override fun copy(entry: EntryStack?, value: SBItemStack): SBItemStack { + return value + } + + override fun isEmpty(entry: EntryStack?, value: SBItemStack): Boolean { + return value.getStackSize() == 0 + } + + override fun getIdentifier(entry: EntryStack?, value: SBItemStack): Identifier { + return value.skyblockId.identifier + } + + fun getEntry(sbItemStack: SBItemStack): EntryStack = + EntryStack.of(this, sbItemStack) + + fun getEntry(skyblockId: SkyblockId, count: Int = 1): EntryStack = + getEntry(SBItemStack(skyblockId, count)) + + fun getEntry(ingredient: NEUIngredient): EntryStack = + getEntry(SkyblockId(ingredient.itemId), count = ingredient.amount.toInt()) + + fun getEntry(stack: ItemStack): EntryStack = + getEntry( + SBItemStack( + stack.skyBlockId ?: SkyblockId.NULL, + RepoManager.getNEUItem(stack.skyBlockId ?: SkyblockId.NULL), + stack.count, + petData = stack.petData?.let { PetData.fromHypixel(it) } + ) + ) +} diff --git a/src/main/kotlin/rei/SkyblockCraftingRecipeDynamicGenerator.kt b/src/main/kotlin/rei/SkyblockCraftingRecipeDynamicGenerator.kt new file mode 100644 index 0000000..5136902 --- /dev/null +++ b/src/main/kotlin/rei/SkyblockCraftingRecipeDynamicGenerator.kt @@ -0,0 +1,64 @@ + + +package moe.nea.firmament.rei + +import io.github.moulberry.repo.data.NEUCraftingRecipe +import io.github.moulberry.repo.data.NEUForgeRecipe +import io.github.moulberry.repo.data.NEUKatUpgradeRecipe +import io.github.moulberry.repo.data.NEUMobDropRecipe +import io.github.moulberry.repo.data.NEURecipe +import java.util.Optional +import me.shedaniel.rei.api.client.registry.display.DynamicDisplayGenerator +import me.shedaniel.rei.api.client.view.ViewSearchBuilder +import me.shedaniel.rei.api.common.display.Display +import me.shedaniel.rei.api.common.entry.EntryStack +import moe.nea.firmament.rei.recipes.SBCraftingRecipe +import moe.nea.firmament.rei.recipes.SBEssenceUpgradeRecipe +import moe.nea.firmament.rei.recipes.SBForgeRecipe +import moe.nea.firmament.rei.recipes.SBKatRecipe +import moe.nea.firmament.rei.recipes.SBMobDropRecipe +import moe.nea.firmament.repo.EssenceRecipeProvider +import moe.nea.firmament.repo.RepoManager + + +val SkyblockCraftingRecipeDynamicGenerator = + neuDisplayGenerator { SBCraftingRecipe(it) } + +val SkyblockForgeRecipeDynamicGenerator = + neuDisplayGenerator { SBForgeRecipe(it) } + +val SkyblockMobDropRecipeDynamicGenerator = + neuDisplayGenerator { SBMobDropRecipe(it) } + +val SkyblockKatRecipeDynamicGenerator = + neuDisplayGenerator { SBKatRecipe(it) } +val SkyblockEssenceRecipeDynamicGenerator = + neuDisplayGenerator { SBEssenceUpgradeRecipe(it) } + +inline fun neuDisplayGenerator(crossinline mapper: (T) -> D) = + object : DynamicDisplayGenerator { + override fun getRecipeFor(entry: EntryStack<*>): Optional> { + if (entry.type != SBItemEntryDefinition.type) return Optional.empty() + val item = entry.castValue() + val recipes = RepoManager.getRecipesFor(item.skyblockId) + val craftingRecipes = recipes.filterIsInstance() + return Optional.of(craftingRecipes.map(mapper)) + } + + override fun generate(builder: ViewSearchBuilder): Optional> { + if (SBCraftingRecipe.Category.catIdentifier !in builder.categories) return Optional.empty() + return Optional.of( + RepoManager.getAllRecipes().filterIsInstance().map { mapper(it) } + .toList() + ) + } + + override fun getUsageFor(entry: EntryStack<*>): Optional> { + if (entry.type != SBItemEntryDefinition.type) return Optional.empty() + val item = entry.castValue() + val recipes = RepoManager.getUsagesFor(item.skyblockId) + val craftingRecipes = recipes.filterIsInstance() + return Optional.of(craftingRecipes.map(mapper)) + + } + } diff --git a/src/main/kotlin/rei/SkyblockItemIdFocusedStackProvider.kt b/src/main/kotlin/rei/SkyblockItemIdFocusedStackProvider.kt new file mode 100644 index 0000000..bb0a5a5 --- /dev/null +++ b/src/main/kotlin/rei/SkyblockItemIdFocusedStackProvider.kt @@ -0,0 +1,25 @@ + + +package moe.nea.firmament.rei + +import dev.architectury.event.CompoundEventResult +import me.shedaniel.math.Point +import me.shedaniel.rei.api.client.registry.screen.FocusedStackProvider +import me.shedaniel.rei.api.common.entry.EntryStack +import net.minecraft.client.gui.screen.Screen +import net.minecraft.client.gui.screen.ingame.HandledScreen +import moe.nea.firmament.mixins.accessor.AccessorHandledScreen +import moe.nea.firmament.util.skyBlockId + +object SkyblockItemIdFocusedStackProvider : FocusedStackProvider { + override fun provide(screen: Screen?, mouse: Point?): CompoundEventResult> { + if (screen !is HandledScreen<*>) return CompoundEventResult.pass() + screen as AccessorHandledScreen + val focusedSlot = screen.focusedSlot_Firmament ?: return CompoundEventResult.pass() + val item = focusedSlot.stack ?: return CompoundEventResult.pass() + val skyblockId = item.skyBlockId ?: return CompoundEventResult.pass() + return CompoundEventResult.interruptTrue(SBItemEntryDefinition.getEntry(skyblockId)) + } + + override fun getPriority(): Double = 1_000_000.0 +} diff --git a/src/main/kotlin/rei/math.kt b/src/main/kotlin/rei/math.kt new file mode 100644 index 0000000..1318beb --- /dev/null +++ b/src/main/kotlin/rei/math.kt @@ -0,0 +1,10 @@ + + +package moe.nea.firmament.rei + +import me.shedaniel.math.Point + +operator fun Point.plus(other: Point): Point = Point( + this.x + other.x, + this.y + other.y, +) diff --git a/src/main/kotlin/rei/recipes/SBCraftingRecipe.kt b/src/main/kotlin/rei/recipes/SBCraftingRecipe.kt new file mode 100644 index 0000000..d6bbf0c --- /dev/null +++ b/src/main/kotlin/rei/recipes/SBCraftingRecipe.kt @@ -0,0 +1,55 @@ + + +package moe.nea.firmament.rei.recipes + +import io.github.moulberry.repo.data.NEUCraftingRecipe +import io.github.moulberry.repo.data.NEUIngredient +import me.shedaniel.math.Point +import me.shedaniel.math.Rectangle +import me.shedaniel.rei.api.client.gui.Renderer +import me.shedaniel.rei.api.client.gui.widgets.Widget +import me.shedaniel.rei.api.client.gui.widgets.Widgets +import me.shedaniel.rei.api.client.registry.display.DisplayCategory +import me.shedaniel.rei.api.common.category.CategoryIdentifier +import me.shedaniel.rei.api.common.util.EntryStacks +import net.minecraft.block.Blocks +import net.minecraft.text.Text +import moe.nea.firmament.Firmament +import moe.nea.firmament.rei.SBItemEntryDefinition + +class SBCraftingRecipe(override val neuRecipe: NEUCraftingRecipe) : SBRecipe() { + override fun getCategoryIdentifier(): CategoryIdentifier<*> = Category.catIdentifier + + object Category : DisplayCategory { + val catIdentifier = CategoryIdentifier.of(Firmament.MOD_ID, "crafing_recipe") + override fun getCategoryIdentifier(): CategoryIdentifier = catIdentifier + + override fun getTitle(): Text = Text.literal("SkyBlock Crafting") + + override fun getIcon(): Renderer = EntryStacks.of(Blocks.CRAFTING_TABLE) + override fun setupDisplay(display: SBCraftingRecipe, bounds: Rectangle): List { + val point = Point(bounds.centerX - 58, bounds.centerY - 27) + return buildList { + add(Widgets.createRecipeBase(bounds)) + add(Widgets.createArrow(Point(point.x + 60, point.y + 18))) + add(Widgets.createResultSlotBackground(Point(point.x + 95, point.y + 19))) + for (i in 0 until 3) { + for (j in 0 until 3) { + val slot = Widgets.createSlot(Point(point.x + 1 + i * 18, point.y + 1 + j * 18)).markInput() + add(slot) + val item = display.neuRecipe.inputs[i + j * 3] + if (item == NEUIngredient.SENTINEL_EMPTY) continue + slot.entry(SBItemEntryDefinition.getEntry(item)) // TODO: make use of stackable item entries + } + } + add( + Widgets.createSlot(Point(point.x + 95, point.y + 19)) + .entry(SBItemEntryDefinition.getEntry(display.neuRecipe.output)) + .disableBackground().markOutput() + ) + } + } + + } + +} diff --git a/src/main/kotlin/rei/recipes/SBEssenceUpgradeRecipe.kt b/src/main/kotlin/rei/recipes/SBEssenceUpgradeRecipe.kt new file mode 100644 index 0000000..80bc2b7 --- /dev/null +++ b/src/main/kotlin/rei/recipes/SBEssenceUpgradeRecipe.kt @@ -0,0 +1,62 @@ + +package moe.nea.firmament.rei.recipes + +import me.shedaniel.math.Point +import me.shedaniel.math.Rectangle +import me.shedaniel.rei.api.client.gui.Renderer +import me.shedaniel.rei.api.client.gui.widgets.Widget +import me.shedaniel.rei.api.client.gui.widgets.Widgets +import me.shedaniel.rei.api.client.registry.display.DisplayCategory +import me.shedaniel.rei.api.common.category.CategoryIdentifier +import net.minecraft.text.Text +import moe.nea.firmament.Firmament +import moe.nea.firmament.rei.SBItemEntryDefinition +import moe.nea.firmament.rei.SBItemStack +import moe.nea.firmament.repo.EssenceRecipeProvider +import moe.nea.firmament.util.SkyblockId + +class SBEssenceUpgradeRecipe(override val neuRecipe: EssenceRecipeProvider.EssenceUpgradeRecipe) : SBRecipe() { + object Category : DisplayCategory { + override fun getCategoryIdentifier(): CategoryIdentifier = + CategoryIdentifier.of(Firmament.MOD_ID, "essence_upgrade") + + override fun getTitle(): Text { + return Text.literal("Essence Upgrades") + } + + override fun getIcon(): Renderer { + return SBItemEntryDefinition.getEntry(SkyblockId("ESSENCE_WITHER")) + } + + override fun setupDisplay(display: SBEssenceUpgradeRecipe, bounds: Rectangle): List { + val recipe = display.neuRecipe + val list = mutableListOf() + list.add(Widgets.createRecipeBase(bounds)) + list.add(Widgets.createSlot(Point(bounds.minX + 12, bounds.centerY - 8 - 18 / 2)) + .markInput() + .entry(SBItemEntryDefinition.getEntry(SBItemStack(recipe.itemId).copy(stars = recipe.starCountAfter - 1)))) + list.add(Widgets.createSlot(Point(bounds.minX + 12, bounds.centerY - 8 + 18 / 2)) + .markInput() + .entry(SBItemEntryDefinition.getEntry(recipe.essenceIngredient))) + list.add(Widgets.createSlot(Point(bounds.maxX - 12 - 16, bounds.centerY - 8)) + .markOutput() + .entry(SBItemEntryDefinition.getEntry(SBItemStack(recipe.itemId).copy(stars = recipe.starCountAfter)))) + val extraItems = recipe.extraItems + list.add(Widgets.createArrow(Point(bounds.centerX - 24 / 2, + if (extraItems.isEmpty()) bounds.centerY - 17 / 2 + else bounds.centerY + 18 / 2))) + for ((index, item) in extraItems.withIndex()) { + list.add(Widgets.createSlot( + Point(bounds.centerX - extraItems.size * 16 / 2 - 2 / 2 + index * 18, + bounds.centerY - 18 / 2)) + .markInput() + .entry(SBItemEntryDefinition.getEntry(item))) + } + return list + } + } + + override fun getCategoryIdentifier(): CategoryIdentifier<*> { + return Category.categoryIdentifier + } +} diff --git a/src/main/kotlin/rei/recipes/SBForgeRecipe.kt b/src/main/kotlin/rei/recipes/SBForgeRecipe.kt new file mode 100644 index 0000000..569f4a0 --- /dev/null +++ b/src/main/kotlin/rei/recipes/SBForgeRecipe.kt @@ -0,0 +1,71 @@ + + +package moe.nea.firmament.rei.recipes + +import io.github.moulberry.repo.data.NEUForgeRecipe +import me.shedaniel.math.Point +import me.shedaniel.math.Rectangle +import me.shedaniel.rei.api.client.gui.Renderer +import me.shedaniel.rei.api.client.gui.widgets.Widget +import me.shedaniel.rei.api.client.gui.widgets.Widgets +import me.shedaniel.rei.api.client.registry.display.DisplayCategory +import me.shedaniel.rei.api.common.category.CategoryIdentifier +import me.shedaniel.rei.api.common.util.EntryStacks +import kotlin.math.cos +import kotlin.math.sin +import kotlin.time.Duration.Companion.seconds +import net.minecraft.block.Blocks +import net.minecraft.text.Text +import moe.nea.firmament.Firmament +import moe.nea.firmament.rei.SBItemEntryDefinition +import moe.nea.firmament.rei.plus + +class SBForgeRecipe(override val neuRecipe: NEUForgeRecipe) : SBRecipe() { + override fun getCategoryIdentifier(): CategoryIdentifier<*> = Category.categoryIdentifier + + object Category : DisplayCategory { + override fun getCategoryIdentifier(): CategoryIdentifier = + CategoryIdentifier.of(Firmament.MOD_ID, "forge_recipe") + + override fun getTitle(): Text = Text.literal("Forge Recipes") + override fun getDisplayHeight(): Int { + return 104 + } + + override fun getIcon(): Renderer = EntryStacks.of(Blocks.ANVIL) + override fun setupDisplay(display: SBForgeRecipe, bounds: Rectangle): List { + return buildList { + add(Widgets.createRecipeBase(bounds)) + add(Widgets.createResultSlotBackground(Point(bounds.minX + 124, bounds.minY + 46))) + val arrow = Widgets.createArrow(Point(bounds.minX + 90, bounds.minY + 54 - 18 / 2)) + add(arrow) + add(Widgets.createTooltip(arrow.bounds, Text.stringifiedTranslatable("firmament.recipe.forge.time", display.neuRecipe.duration.seconds))) + val ingredientsCenter = Point(bounds.minX + 49 - 8, bounds.minY + 54 - 8) + val count = display.neuRecipe.inputs.size + if (count == 1) { + add( + Widgets.createSlot(Point(ingredientsCenter.x, ingredientsCenter.y)).markInput() + .entry(SBItemEntryDefinition.getEntry(display.neuRecipe.inputs.single())) + ) + } else { + display.neuRecipe.inputs.forEachIndexed { idx, ingredient -> + val rad = Math.PI * 2 * idx / count + add( + Widgets.createSlot( + Point( + cos(rad) * 30, + sin(rad) * 30, + ) + ingredientsCenter + ).markInput().entry(SBItemEntryDefinition.getEntry(ingredient)) + ) + } + } + add( + Widgets.createSlot(Point(bounds.minX + 124, bounds.minY + 46)).markOutput().disableBackground() + .entry(SBItemEntryDefinition.getEntry(display.neuRecipe.outputStack)) + ) + } + } + } + +} diff --git a/src/main/kotlin/rei/recipes/SBKatRecipe.kt b/src/main/kotlin/rei/recipes/SBKatRecipe.kt new file mode 100644 index 0000000..f906a43 --- /dev/null +++ b/src/main/kotlin/rei/recipes/SBKatRecipe.kt @@ -0,0 +1,224 @@ + +package moe.nea.firmament.rei.recipes + +import io.github.moulberry.repo.data.NEUKatUpgradeRecipe +import io.github.notenoughupdates.moulconfig.common.IMinecraft +import io.github.notenoughupdates.moulconfig.gui.GuiComponent +import io.github.notenoughupdates.moulconfig.gui.GuiImmediateContext +import io.github.notenoughupdates.moulconfig.gui.MouseEvent +import io.github.notenoughupdates.moulconfig.gui.component.SliderComponent +import io.github.notenoughupdates.moulconfig.observer.GetSetter +import io.github.notenoughupdates.moulconfig.observer.Property +import io.github.notenoughupdates.moulconfig.platform.ModernRenderContext +import me.shedaniel.math.Point +import me.shedaniel.math.Rectangle +import me.shedaniel.rei.api.client.gui.Renderer +import me.shedaniel.rei.api.client.gui.widgets.Widget +import me.shedaniel.rei.api.client.gui.widgets.WidgetWithBounds +import me.shedaniel.rei.api.client.gui.widgets.Widgets +import me.shedaniel.rei.api.client.registry.display.DisplayCategory +import me.shedaniel.rei.api.common.category.CategoryIdentifier +import me.shedaniel.rei.api.common.util.EntryStacks +import kotlin.time.Duration.Companion.seconds +import net.minecraft.block.Blocks +import net.minecraft.client.gui.DrawContext +import net.minecraft.client.gui.Element +import net.minecraft.item.Items +import net.minecraft.text.Text +import moe.nea.firmament.Firmament +import moe.nea.firmament.rei.PetData +import moe.nea.firmament.rei.SBItemEntryDefinition +import moe.nea.firmament.rei.SBItemStack +import moe.nea.firmament.util.FirmFormatters +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.SkyblockId + +class SBKatRecipe(override val neuRecipe: NEUKatUpgradeRecipe) : SBRecipe() { + override fun getCategoryIdentifier(): CategoryIdentifier<*> = Category.categoryIdentifier + + object Category : DisplayCategory { + override fun getCategoryIdentifier(): CategoryIdentifier = + CategoryIdentifier.of(Firmament.MOD_ID, "kat_recipe") + + override fun getTitle(): Text = Text.literal("Kat Pet Upgrade") + override fun getDisplayHeight(): Int { + return 100 + } + + override fun getIcon(): Renderer = EntryStacks.of(Items.BONE) + override fun setupDisplay(display: SBKatRecipe, bounds: Rectangle): List { + return buildList { + val arrowWidth = 24 + val recipe = display.neuRecipe + val levelValue = Property.upgrade(GetSetter.floating(0F)) + val slider = SliderComponent(levelValue, 1F, 100F, 1f, 100) + val outputStack = SBItemStack(SkyblockId(recipe.output.itemId)) + val inputStack = SBItemStack(SkyblockId(recipe.input.itemId)) + val inputLevelLabelCenter = Point(bounds.minX + 30 - 18 + 5 + 8, bounds.minY + 25) + val inputLevelLabel = Widgets.createLabel( + inputLevelLabelCenter, + Text.literal("")).centered() + val outputLevelLabelCenter = Point(bounds.maxX - 30 + 8, bounds.minY + 25) + val outputLevelLabel = Widgets.createLabel( + outputLevelLabelCenter, + Text.literal("")).centered() + val coinStack = SBItemStack(SkyblockId.COINS, recipe.coins.toInt()) + levelValue.whenChanged { oldValue, newValue -> + if (oldValue.toInt() == newValue.toInt()) return@whenChanged + val oldInput = inputStack.getPetData() ?: return@whenChanged + val newInput = PetData.forLevel(oldInput.petId, oldInput.rarity, newValue.toInt()) + inputStack.setPetData(newInput) + val oldOutput = outputStack.getPetData() ?: return@whenChanged + val newOutput = PetData(oldOutput.rarity, oldOutput.petId, newInput.exp) + outputStack.setPetData(newOutput) + inputLevelLabel.message = Text.literal(newInput.levelData.currentLevel.toString()) + inputLevelLabel.bounds.location = Point( + inputLevelLabelCenter.x - MC.font.getWidth(inputLevelLabel.message) / 2, + inputLevelLabelCenter.y) + outputLevelLabel.message = Text.literal(newOutput.levelData.currentLevel.toString()) + outputLevelLabel.bounds.location = Point( + outputLevelLabelCenter.x - MC.font.getWidth(outputLevelLabel.message) / 2, + outputLevelLabelCenter.y) + coinStack.setStackSize((recipe.coins * (1 - 0.3 * newValue / 100)).toInt()) + } + levelValue.set(1F) + add(Widgets.createRecipeBase(bounds)) + add(wrapWidget(Rectangle(bounds.centerX - slider.width / 2, + bounds.maxY - 30, + slider.width, + slider.height), + slider)) + add(Widgets.withTooltip( + Widgets.createArrow(Point(bounds.centerX - arrowWidth / 2, bounds.minY + 40)), + Text.literal("Upgrade time: " + FirmFormatters.formatTimespan(recipe.seconds.seconds)))) + + add(Widgets.createResultSlotBackground(Point(bounds.maxX - 30, bounds.minY + 40))) + add(inputLevelLabel) + add(outputLevelLabel) + add(Widgets.createSlot(Point(bounds.maxX - 30, bounds.minY + 40)).markOutput().disableBackground() + .entry(SBItemEntryDefinition.getEntry(outputStack))) + add(Widgets.createSlot(Point(bounds.minX + 30 - 18 + 5, bounds.minY + 40)).markInput() + .entry(SBItemEntryDefinition.getEntry(inputStack))) + + val allInputs = recipe.items.map { SBItemEntryDefinition.getEntry(it) } + + listOf(SBItemEntryDefinition.getEntry(coinStack)) + for ((index, item) in allInputs.withIndex()) { + add(Widgets.createSlot( + Point(bounds.centerX + index * 20 - allInputs.size * 18 / 2 - (allInputs.size - 1) * 2 / 2, + bounds.minY + 20)) + .markInput() + .entry(item)) + } + } + } + } +} + +fun wrapWidget(bounds: Rectangle, component: GuiComponent): Widget { + return object : WidgetWithBounds() { + override fun getBounds(): Rectangle { + return bounds + } + + override fun children(): List { + return listOf() + } + + override fun render(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) { + context.matrices.push() + context.matrices.translate(bounds.minX.toFloat(), bounds.minY.toFloat(), 0F) + component.render( + GuiImmediateContext( + ModernRenderContext(context), + bounds.minX, bounds.minY, + bounds.width, bounds.height, + mouseX - bounds.minX, mouseY - bounds.minY, + mouseX, mouseY, + mouseX.toFloat(), mouseY.toFloat() + )) + context.matrices.pop() + } + + override fun mouseMoved(mouseX: Double, mouseY: Double) { + val mouseXInt = mouseX.toInt() + val mouseYInt = mouseY.toInt() + component.mouseEvent(MouseEvent.Move(0F, 0F), + GuiImmediateContext( + IMinecraft.instance.provideTopLevelRenderContext(), + bounds.minX, bounds.minY, + bounds.width, bounds.height, + mouseXInt - bounds.minX, mouseYInt - bounds.minY, + mouseXInt, mouseYInt, + mouseX.toFloat(), mouseY.toFloat() + )) + } + + override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean { + val mouseXInt = mouseX.toInt() + val mouseYInt = mouseY.toInt() + return component.mouseEvent(MouseEvent.Click(button, true), + GuiImmediateContext( + IMinecraft.instance.provideTopLevelRenderContext(), + bounds.minX, bounds.minY, + bounds.width, bounds.height, + mouseXInt - bounds.minX, mouseYInt - bounds.minY, + mouseXInt, mouseYInt, + mouseX.toFloat(), mouseY.toFloat() + )) + } + + override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean { + val mouseXInt = mouseX.toInt() + val mouseYInt = mouseY.toInt() + return component.mouseEvent(MouseEvent.Click(button, false), + GuiImmediateContext( + IMinecraft.instance.provideTopLevelRenderContext(), + bounds.minX, bounds.minY, + bounds.width, bounds.height, + mouseXInt - bounds.minX, mouseYInt - bounds.minY, + mouseXInt, mouseYInt, + mouseX.toFloat(), mouseY.toFloat() + )) + } + + override fun mouseDragged( + mouseX: Double, + mouseY: Double, + button: Int, + deltaX: Double, + deltaY: Double + ): Boolean { + val mouseXInt = mouseX.toInt() + val mouseYInt = mouseY.toInt() + return component.mouseEvent(MouseEvent.Move(deltaX.toFloat(), deltaY.toFloat()), + GuiImmediateContext( + IMinecraft.instance.provideTopLevelRenderContext(), + bounds.minX, bounds.minY, + bounds.width, bounds.height, + mouseXInt - bounds.minX, mouseYInt - bounds.minY, + mouseXInt, mouseYInt, + mouseX.toFloat(), mouseY.toFloat() + )) + + } + + override fun mouseScrolled( + mouseX: Double, + mouseY: Double, + horizontalAmount: Double, + verticalAmount: Double + ): Boolean { + val mouseXInt = mouseX.toInt() + val mouseYInt = mouseY.toInt() + return component.mouseEvent(MouseEvent.Scroll(verticalAmount.toFloat()), + GuiImmediateContext( + IMinecraft.instance.provideTopLevelRenderContext(), + bounds.minX, bounds.minY, + bounds.width, bounds.height, + mouseXInt - bounds.minX, mouseYInt - bounds.minY, + mouseXInt, mouseYInt, + mouseX.toFloat(), mouseY.toFloat() + )) + } + } +} diff --git a/src/main/kotlin/rei/recipes/SBMobDropRecipe.kt b/src/main/kotlin/rei/recipes/SBMobDropRecipe.kt new file mode 100644 index 0000000..a02220f --- /dev/null +++ b/src/main/kotlin/rei/recipes/SBMobDropRecipe.kt @@ -0,0 +1,108 @@ + +package moe.nea.firmament.rei.recipes + +import io.github.moulberry.repo.data.NEUMobDropRecipe +import me.shedaniel.math.Point +import me.shedaniel.math.Rectangle +import me.shedaniel.rei.api.client.gui.Renderer +import me.shedaniel.rei.api.client.gui.widgets.Widget +import me.shedaniel.rei.api.client.gui.widgets.Widgets +import me.shedaniel.rei.api.client.registry.display.DisplayCategory +import me.shedaniel.rei.api.common.category.CategoryIdentifier +import me.shedaniel.rei.api.common.util.EntryStacks +import net.minecraft.item.Items +import net.minecraft.text.Text +import net.minecraft.util.Identifier +import moe.nea.firmament.Firmament +import moe.nea.firmament.gui.entity.EntityRenderer +import moe.nea.firmament.gui.entity.EntityWidget +import moe.nea.firmament.rei.SBItemEntryDefinition + +class SBMobDropRecipe(override val neuRecipe: NEUMobDropRecipe) : SBRecipe() { + override fun getCategoryIdentifier(): CategoryIdentifier<*> = Category.categoryIdentifier + + object Category : DisplayCategory { + override fun getCategoryIdentifier(): CategoryIdentifier = + CategoryIdentifier.of(Firmament.MOD_ID, "mob_drop_recipe") + + override fun getTitle(): Text = Text.literal("Mob Drops") + override fun getDisplayHeight(): Int { + return 100 + } + + override fun getIcon(): Renderer = EntryStacks.of(Items.DIAMOND_SWORD) + override fun setupDisplay(display: SBMobDropRecipe, bounds: Rectangle): List { + return buildList { + add(Widgets.createRecipeBase(bounds)) + val source = display.neuRecipe.render + val entity = if (source.startsWith("@")) { + EntityRenderer.constructEntity(Identifier.of(source.substring(1))) + } else { + EntityRenderer.applyModifiers(source, listOf()) + } + if (entity != null) { + val level = display.neuRecipe.level + val fullMobName = + if (level > 0) Text.translatable("firmament.recipe.mobs.name", level, display.neuRecipe.name) + else Text.translatable("firmament.recipe.mobs.name.nolevel", display.neuRecipe.name) + val tt = mutableListOf() + tt.add((fullMobName)) + tt.add(Text.literal("")) + if (display.neuRecipe.coins > 0) { + tt.add(Text.stringifiedTranslatable("firmament.recipe.mobs.coins", display.neuRecipe.coins)) + } + if (display.neuRecipe.combatExperience > 0) { + tt.add( + Text.stringifiedTranslatable( + "firmament.recipe.mobs.combat", + display.neuRecipe.combatExperience + ) + ) + } + if (display.neuRecipe.enchantingExperience > 0) { + tt.add( + Text.stringifiedTranslatable( + "firmament.recipe.mobs.exp", + display.neuRecipe.enchantingExperience + ) + ) + } + if (display.neuRecipe.extra != null) + display.neuRecipe.extra.mapTo(tt) { Text.literal(it) } + if (tt.size == 2) + tt.removeAt(1) + add( + Widgets.withTooltip( + EntityWidget(entity, Point(bounds.minX + 5, bounds.minY + 15)), + tt + ) + ) + } + add( + Widgets.createLabel(Point(bounds.minX + 15, bounds.minY + 5), Text.literal(display.neuRecipe.name)) + .leftAligned() + ) + var x = bounds.minX + 60 + var y = bounds.minY + 20 + for (drop in display.neuRecipe.drops) { + val lore = drop.extra.mapTo(mutableListOf()) { Text.literal(it) } + if (drop.chance != null) { + lore += listOf(Text.translatable("firmament.recipe.mobs.drops", drop.chance)) + } + val item = SBItemEntryDefinition.getEntry(drop.dropItem) + .value.copy(extraLore = lore) + add( + Widgets.createSlot(Point(x, y)).markOutput() + .entries(listOf(SBItemEntryDefinition.getEntry(item))) + ) + x += 18 + if (x > bounds.maxX - 30) { + x = bounds.minX + 60 + y += 18 + } + } + } + } + } + +} diff --git a/src/main/kotlin/rei/recipes/SBRecipe.kt b/src/main/kotlin/rei/recipes/SBRecipe.kt new file mode 100644 index 0000000..7872d83 --- /dev/null +++ b/src/main/kotlin/rei/recipes/SBRecipe.kt @@ -0,0 +1,31 @@ + + +package moe.nea.firmament.rei.recipes + +import io.github.moulberry.repo.data.NEUIngredient +import io.github.moulberry.repo.data.NEURecipe +import me.shedaniel.rei.api.common.display.Display +import me.shedaniel.rei.api.common.entry.EntryIngredient +import moe.nea.firmament.rei.SBItemEntryDefinition +import moe.nea.firmament.util.SkyblockId + +abstract class SBRecipe : Display { + abstract val neuRecipe: NEURecipe + override fun getInputEntries(): List { + return neuRecipe.allInputs + .filter { it.itemId != NEUIngredient.NEU_SENTINEL_EMPTY } + .map { + val entryStack = SBItemEntryDefinition.getEntry(SkyblockId(it.itemId)) + EntryIngredient.of(entryStack) + } + } + + override fun getOutputEntries(): List { + return neuRecipe.allOutputs + .filter { it.itemId != NEUIngredient.NEU_SENTINEL_EMPTY } + .map { + val entryStack = SBItemEntryDefinition.getEntry(SkyblockId(it.itemId)) + EntryIngredient.of(entryStack) + } + } +} diff --git a/src/main/kotlin/repo/BetterRepoRecipeCache.kt b/src/main/kotlin/repo/BetterRepoRecipeCache.kt new file mode 100644 index 0000000..91a6b50 --- /dev/null +++ b/src/main/kotlin/repo/BetterRepoRecipeCache.kt @@ -0,0 +1,28 @@ + +package moe.nea.firmament.repo + +import io.github.moulberry.repo.IReloadable +import io.github.moulberry.repo.NEURepository +import io.github.moulberry.repo.data.NEURecipe +import moe.nea.firmament.util.SkyblockId + +class BetterRepoRecipeCache(val essenceRecipeProvider: EssenceRecipeProvider) : IReloadable { + var usages: Map> = mapOf() + var recipes: Map> = mapOf() + + override fun reload(repository: NEURepository) { + val usages = mutableMapOf>() + val recipes = mutableMapOf>() + val baseRecipes = repository.items.items.values + .asSequence() + .flatMap { it.recipes } + val extraRecipes = essenceRecipeProvider.recipes + (baseRecipes + extraRecipes) + .forEach { recipe -> + recipe.allInputs.forEach { usages.getOrPut(SkyblockId(it.itemId), ::mutableSetOf).add(recipe) } + recipe.allOutputs.forEach { recipes.getOrPut(SkyblockId(it.itemId), ::mutableSetOf).add(recipe) } + } + this.usages = usages + this.recipes = recipes + } +} diff --git a/src/main/kotlin/repo/EssenceRecipeProvider.kt b/src/main/kotlin/repo/EssenceRecipeProvider.kt new file mode 100644 index 0000000..1833258 --- /dev/null +++ b/src/main/kotlin/repo/EssenceRecipeProvider.kt @@ -0,0 +1,50 @@ + +package moe.nea.firmament.repo + +import io.github.moulberry.repo.IReloadable +import io.github.moulberry.repo.NEURepository +import io.github.moulberry.repo.data.NEUIngredient +import io.github.moulberry.repo.data.NEURecipe +import moe.nea.firmament.util.SkyblockId + +class EssenceRecipeProvider : IReloadable { + data class EssenceUpgradeRecipe( + val itemId: SkyblockId, + val starCountAfter: Int, + val essenceCost: Int, + val essenceType: String, // TODO: replace with proper type + val extraItems: List, + ) : NEURecipe { + val essenceIngredient= NEUIngredient.fromString("${essenceType}:$essenceCost") + val allUpgradeComponents = listOf(essenceIngredient) + extraItems + + override fun getAllInputs(): Collection { + return listOf(NEUIngredient.fromString(itemId.neuItem + ":1")) + allUpgradeComponents + } + + override fun getAllOutputs(): Collection { + return listOf(NEUIngredient.fromString(itemId.neuItem + ":1")) + } + } + + var recipes = listOf() + private set + + override fun reload(repository: NEURepository) { + val recipes = mutableListOf() + for ((neuId, costs) in repository.constants.essenceCost.costs) { + // TODO: add dungeonization costs. this is in repo, but not in the repo parser. + for ((starCountAfter, essenceCost) in costs.essenceCosts.entries) { + val items = costs.itemCosts[starCountAfter] ?: emptyList() + recipes.add( + EssenceUpgradeRecipe( + SkyblockId(neuId), + starCountAfter, + essenceCost, + "ESSENCE_" + costs.type.uppercase(), // how flimsy + items.map { NEUIngredient.fromString(it) })) + } + } + this.recipes = recipes + } +} diff --git a/src/main/kotlin/repo/ExpLadder.kt b/src/main/kotlin/repo/ExpLadder.kt new file mode 100644 index 0000000..fbc9eb8 --- /dev/null +++ b/src/main/kotlin/repo/ExpLadder.kt @@ -0,0 +1,94 @@ + + +package moe.nea.firmament.repo + +import com.google.common.cache.CacheBuilder +import com.google.common.cache.CacheLoader +import io.github.moulberry.repo.IReloadable +import io.github.moulberry.repo.NEURepository +import io.github.moulberry.repo.constants.PetLevelingBehaviourOverride +import io.github.moulberry.repo.data.Rarity + +object ExpLadders : IReloadable { + + data class PetLevel( + val currentLevel: Int, + val maxLevel: Int, + val expRequiredForNextLevel: Long, + val expRequiredForMaxLevel: Long, + val expInCurrentLevel: Float, + var expTotal: Float, + ) { + val percentageToNextLevel: Float = expInCurrentLevel / expRequiredForNextLevel + } + + data class ExpLadder( + val individualLevelCost: List, + ) { + val cumulativeLevelCost = individualLevelCost.runningFold(0F) { a, b -> a + b }.map { it.toLong() } + fun getPetLevel(currentExp: Double): PetLevel { + val currentOneIndexedLevel = cumulativeLevelCost.indexOfLast { it <= currentExp } + 1 + val expForNextLevel = if (currentOneIndexedLevel > individualLevelCost.size) { // Max leveled pet + individualLevelCost.last() + } else { + individualLevelCost[currentOneIndexedLevel - 1] + } + val expInCurrentLevel = + if (currentOneIndexedLevel >= cumulativeLevelCost.size) + currentExp.toFloat() - cumulativeLevelCost.last() + else + (expForNextLevel - (cumulativeLevelCost[currentOneIndexedLevel] - currentExp.toFloat())).coerceAtLeast( + 0F + ) + return PetLevel( + currentLevel = currentOneIndexedLevel, + maxLevel = cumulativeLevelCost.size, + expRequiredForNextLevel = expForNextLevel, + expRequiredForMaxLevel = cumulativeLevelCost.last(), + expInCurrentLevel = expInCurrentLevel, + expTotal = currentExp.toFloat() + ) + } + + fun getPetExpForLevel(level: Int): Long { + if (level < 2) return 0L + if (level >= cumulativeLevelCost.size) return cumulativeLevelCost.last() + return cumulativeLevelCost[level - 1] + } + } + + private data class Key(val petIdWithoutRarity: String, val rarity: Rarity) + + private val expLadders = CacheBuilder.newBuilder() + .build(object : CacheLoader() { + override fun load(key: Key): ExpLadder { + val pld = RepoManager.neuRepo.constants.petLevelingData + var exp = pld.petExpCostForLevel + var offset = pld.petLevelStartOffset[key.rarity]!! + var maxLevel = 100 + val override = pld.petLevelingBehaviourOverrides[key.petIdWithoutRarity] + if (override != null) { + maxLevel = override.maxLevel ?: maxLevel + offset = override.petLevelStartOffset?.get(key.rarity) ?: offset + when (override.petExpCostModifierType) { + PetLevelingBehaviourOverride.PetExpModifierType.APPEND -> + exp = exp + override.petExpCostModifier + + PetLevelingBehaviourOverride.PetExpModifierType.REPLACE -> + exp = override.petExpCostModifier + + null -> {} + } + } + return ExpLadder(exp.drop(offset).take(maxLevel - 1).map { it.toLong() }) + } + }) + + override fun reload(repository: NEURepository?) { + expLadders.invalidateAll() + } + + fun getExpLadder(petId: String, rarity: Rarity): ExpLadder { + return expLadders.get(Key(petId, rarity)) + } +} diff --git a/src/main/kotlin/repo/HypixelStaticData.kt b/src/main/kotlin/repo/HypixelStaticData.kt new file mode 100644 index 0000000..5c2a2fc --- /dev/null +++ b/src/main/kotlin/repo/HypixelStaticData.kt @@ -0,0 +1,107 @@ + + +package moe.nea.firmament.repo + +import io.ktor.client.call.body +import io.ktor.client.request.get +import org.apache.logging.log4j.LogManager +import org.lwjgl.glfw.GLFW +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.launch +import kotlinx.coroutines.withTimeoutOrNull +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlin.time.Duration.Companion.minutes +import moe.nea.firmament.Firmament +import moe.nea.firmament.apis.CollectionResponse +import moe.nea.firmament.apis.CollectionSkillData +import moe.nea.firmament.keybindings.IKeyBinding +import moe.nea.firmament.util.SkyblockId +import moe.nea.firmament.util.async.waitForInput + +object HypixelStaticData { + private val logger = LogManager.getLogger("Firmament.HypixelStaticData") + private val moulberryBaseUrl = "https://moulberry.codes" + private val hypixelApiBaseUrl = "https://api.hypixel.net" + var lowestBin: Map = mapOf() + private set + var bazaarData: Map = mapOf() + private set + var collectionData: Map = mapOf() + private set + + @Serializable + data class BazaarData( + @SerialName("product_id") + val productId: SkyblockId.BazaarStock, + @SerialName("quick_status") + val quickStatus: BazaarStatus, + ) + + @Serializable + data class BazaarStatus( + val sellPrice: Double, + val sellVolume: Long, + val sellMovingWeek: Long, + val sellOrders: Long, + val buyPrice: Double, + val buyVolume: Long, + val buyMovingWeek: Long, + val buyOrders: Long + ) + + @Serializable + private data class BazaarResponse( + val success: Boolean, + val products: Map = mapOf(), + ) + + fun getPriceOfItem(item: SkyblockId): Double? = bazaarData[item]?.quickStatus?.buyPrice ?: lowestBin[item] + + + fun spawnDataCollectionLoop() { + Firmament.coroutineScope.launch { + logger.info("Updating collection data") + updateCollectionData() + } + Firmament.coroutineScope.launch { + while (true) { + logger.info("Updating NEU prices") + updatePrices() + withTimeoutOrNull(10.minutes) { waitForInput(IKeyBinding.ofKeyCode(GLFW.GLFW_KEY_U)) } + } + } + } + + private suspend fun updatePrices() { + awaitAll( + Firmament.coroutineScope.async { fetchBazaarPrices() }, + Firmament.coroutineScope.async { fetchPricesFromMoulberry() }, + ) + } + + private suspend fun fetchPricesFromMoulberry() { + lowestBin = Firmament.httpClient.get("$moulberryBaseUrl/lowestbin.json") + .body>() + } + + private suspend fun fetchBazaarPrices() { + val response = Firmament.httpClient.get("$hypixelApiBaseUrl/skyblock/bazaar").body() + if (!response.success) { + logger.warn("Retrieved unsuccessful bazaar data") + } + bazaarData = response.products.mapKeys { it.key.toRepoId() } + } + + private suspend fun updateCollectionData() { + val response = + Firmament.httpClient.get("$hypixelApiBaseUrl/resources/skyblock/collections").body() + if (!response.success) { + logger.warn("Retrieved unsuccessful collection data") + } + collectionData = response.collections + logger.info("Downloaded ${collectionData.values.sumOf { it.items.values.size }} collections") + } + +} diff --git a/src/main/kotlin/repo/ItemCache.kt b/src/main/kotlin/repo/ItemCache.kt new file mode 100644 index 0000000..08143be --- /dev/null +++ b/src/main/kotlin/repo/ItemCache.kt @@ -0,0 +1,215 @@ + + +package moe.nea.firmament.repo + +import com.mojang.serialization.Dynamic +import io.github.moulberry.repo.IReloadable +import io.github.moulberry.repo.NEURepository +import io.github.moulberry.repo.data.NEUItem +import io.github.notenoughupdates.moulconfig.xml.Bind +import java.text.NumberFormat +import java.util.UUID +import java.util.concurrent.ConcurrentHashMap +import org.apache.logging.log4j.LogManager +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import kotlin.jvm.optionals.getOrNull +import net.minecraft.SharedConstants +import net.minecraft.client.resource.language.I18n +import net.minecraft.component.DataComponentTypes +import net.minecraft.component.type.NbtComponent +import net.minecraft.datafixer.Schemas +import net.minecraft.datafixer.TypeReferences +import net.minecraft.item.ItemStack +import net.minecraft.item.Items +import net.minecraft.nbt.NbtCompound +import net.minecraft.nbt.NbtElement +import net.minecraft.nbt.NbtOps +import net.minecraft.text.Text +import moe.nea.firmament.Firmament +import moe.nea.firmament.gui.config.HudMeta +import moe.nea.firmament.gui.config.HudPosition +import moe.nea.firmament.gui.hud.MoulConfigHud +import moe.nea.firmament.util.LegacyTagParser +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.SkyblockId +import moe.nea.firmament.util.appendLore +import moe.nea.firmament.util.item.setCustomName +import moe.nea.firmament.util.item.setSkullOwner +import moe.nea.firmament.util.modifyLore +import moe.nea.firmament.util.skyblockId + +object ItemCache : IReloadable { + private val cache: MutableMap = ConcurrentHashMap() + private val df = Schemas.getFixer() + val logger = LogManager.getLogger("${Firmament.logger.name}.ItemCache") + var isFlawless = true + private set + + private fun NEUItem.get10809CompoundTag(): NbtCompound = NbtCompound().apply { + put("tag", LegacyTagParser.parse(nbttag)) + putString("id", minecraftItemId) + putByte("Count", 1) + putShort("Damage", damage.toShort()) + } + + private fun NbtCompound.transformFrom10809ToModern(): NbtCompound? = + try { + df.update( + TypeReferences.ITEM_STACK, + Dynamic(NbtOps.INSTANCE, this), + -1, + SharedConstants.getGameVersion().saveVersion.id + ).value as NbtCompound + } catch (e: Exception) { + isFlawless = false + logger.error("Could not data fix up $this", e) + null + } + + fun brokenItemStack(neuItem: NEUItem?, idHint: SkyblockId? = null): ItemStack { + return ItemStack(Items.PAINTING).apply { + setCustomName(Text.literal(neuItem?.displayName ?: idHint?.neuItem ?: "null")) + appendLore( + listOf( + Text.stringifiedTranslatable( + "firmament.repo.brokenitem", + (neuItem?.skyblockItemId ?: idHint) + ) + ) + ) + } + } + + private fun NEUItem.asItemStackNow(): ItemStack { + try { + val oldItemTag = get10809CompoundTag() + val modernItemTag = oldItemTag.transformFrom10809ToModern() + ?: return brokenItemStack(this) + val itemInstance = + ItemStack.fromNbt(MC.defaultRegistries, modernItemTag).getOrNull() ?: return brokenItemStack(this) + val extraAttributes = oldItemTag.getCompound("tag").getCompound("ExtraAttributes") + if (extraAttributes != null) + itemInstance.set(DataComponentTypes.CUSTOM_DATA, NbtComponent.of(extraAttributes)) + return itemInstance + } catch (e: Exception) { + e.printStackTrace() + return brokenItemStack(this) + } + } + + fun NEUItem?.asItemStack(idHint: SkyblockId? = null, loreReplacements: Map? = null): ItemStack { + if (this == null) return brokenItemStack(null, idHint) + var s = cache[this.skyblockItemId] + if (s == null) { + s = asItemStackNow() + cache[this.skyblockItemId] = s + } + if (!loreReplacements.isNullOrEmpty()) { + s = s.copy()!! + s.applyLoreReplacements(loreReplacements) + s.setCustomName(s.name.applyLoreReplacements(loreReplacements)) + } + return s + } + + fun ItemStack.applyLoreReplacements(loreReplacements: Map) { + modifyLore { lore -> + lore.map { + it.applyLoreReplacements(loreReplacements) + } + } + } + + fun Text.applyLoreReplacements(loreReplacements: Map): Text { + assert(this.siblings.isEmpty()) + var string = this.string + loreReplacements.forEach { (find, replace) -> + string = string.replace("{$find}", replace) + } + return Text.literal(string).styled { this.style } + } + + fun NEUItem.getIdentifier() = skyblockId.identifier + + var job: Job? = null + object ReloadProgressHud : MoulConfigHud( + "repo_reload", HudMeta(HudPosition(0.0, 0.0, 1F), Text.literal("Repo Reload"), 180, 18)) { + + + var isEnabled = false + override fun shouldRender(): Boolean { + return isEnabled + } + + @get:Bind("current") + var current: Double = 0.0 + + @get:Bind("label") + var label: String = "" + + @get:Bind("max") + var max: Double = 0.0 + + fun reportProgress(label: String, current: Int, max: Int) { + this.label = label + this.current = current.toDouble() + this.max = max.toDouble() + } + } + + override fun reload(repository: NEURepository) { + val j = job + if (j != null && j.isActive) { + j.cancel() + } + cache.clear() + isFlawless = true + + job = Firmament.coroutineScope.launch { + val items = repository.items?.items + if (items == null) { + ReloadProgressHud.isEnabled = false + return@launch + } + val recacheItems = I18n.translate("firmament.repo.cache") + ReloadProgressHud.reportProgress(recacheItems, 0, items.size) + ReloadProgressHud.isEnabled = true + var i = 0 + items.values.forEach { + it.asItemStack() // Rebuild cache + ReloadProgressHud.reportProgress(recacheItems, i++, items.size) + } + ReloadProgressHud.isEnabled = false + } + } + + fun coinItem(coinAmount: Int): ItemStack { + var uuid = UUID.fromString("2070f6cb-f5db-367a-acd0-64d39a7e5d1b") + var texture = + "http://textures.minecraft.net/texture/538071721cc5b4cd406ce431a13f86083a8973e1064d2f8897869930ee6e5237" + if (coinAmount >= 100000) { + uuid = UUID.fromString("94fa2455-2881-31fe-bb4e-e3e24d58dbe3") + texture = + "http://textures.minecraft.net/texture/c9b77999fed3a2758bfeaf0793e52283817bea64044bf43ef29433f954bb52f6" + } + if (coinAmount >= 10000000) { + uuid = UUID.fromString("0af8df1f-098c-3b72-ac6b-65d65fd0b668") + texture = + "http://textures.minecraft.net/texture/7b951fed6a7b2cbc2036916dec7a46c4a56481564d14f945b6ebc03382766d3b" + } + val itemStack = ItemStack(Items.PLAYER_HEAD) + itemStack.setCustomName(Text.literal("§r§6" + NumberFormat.getInstance().format(coinAmount) + " Coins")) + itemStack.setSkullOwner(uuid, texture) + return itemStack + } +} + + +operator fun NbtCompound.set(key: String, value: String) { + putString(key, value) +} + +operator fun NbtCompound.set(key: String, value: NbtElement) { + put(key, value) +} diff --git a/src/main/kotlin/repo/ItemNameLookup.kt b/src/main/kotlin/repo/ItemNameLookup.kt new file mode 100644 index 0000000..770de85 --- /dev/null +++ b/src/main/kotlin/repo/ItemNameLookup.kt @@ -0,0 +1,98 @@ + +package moe.nea.firmament.repo + +import io.github.moulberry.repo.IReloadable +import io.github.moulberry.repo.NEURepository +import io.github.moulberry.repo.data.NEUItem +import java.util.NavigableMap +import java.util.TreeMap +import moe.nea.firmament.util.SkyblockId +import moe.nea.firmament.util.removeColorCodes +import moe.nea.firmament.util.skyblockId + +object ItemNameLookup : IReloadable { + + fun getItemNameChunks(name: String): Set { + return name.removeColorCodes().split(" ").filterTo(mutableSetOf()) { it.isNotBlank() } + } + + var nameMap: NavigableMap> = TreeMap() + + override fun reload(repository: NEURepository) { + val nameMap = TreeMap>() + repository.items.items.values.forEach { item -> + getAllNamesForItem(item).forEach { name -> + val chunks = getItemNameChunks(name) + chunks.forEach { chunk -> + val set = nameMap.getOrPut(chunk, ::mutableSetOf) + set.add(item.skyblockId) + } + } + } + this.nameMap = nameMap + } + + fun getAllNamesForItem(item: NEUItem): Set { + val names = mutableSetOf() + names.add(item.displayName) + if (item.displayName.contains("Enchanted Book")) { + val enchantName = item.lore.firstOrNull() + if (enchantName != null) { + names.add(enchantName) + } + } + return names + } + + fun findItemCandidatesByName(name: String): MutableSet { + val candidates = mutableSetOf() + for (chunk in getItemNameChunks(name)) { + val set = nameMap[chunk] ?: emptySet() + candidates.addAll(set) + } + return candidates + } + + + fun guessItemByName( + /** + * The display name of the item. Color codes will be ignored. + */ + name: String, + /** + * Whether the [name] may contain other text, such as reforges, master stars and such. + */ + mayBeMangled: Boolean + ): SkyblockId? { + val cleanName = name.removeColorCodes() + return findBestItemFromCandidates( + findItemCandidatesByName(cleanName), + cleanName, + true + ) + } + + fun findBestItemFromCandidates( + candidates: Iterable, + name: String, mayBeMangled: Boolean + ): SkyblockId? { + val expectedClean = name.removeColorCodes() + var bestMatch: SkyblockId? = null + var bestMatchLength = -1 + for (candidate in candidates) { + val item = RepoManager.getNEUItem(candidate) ?: continue + for (name in getAllNamesForItem(item)) { + val actualClean = name.removeColorCodes() + val matches = if (mayBeMangled) expectedClean == actualClean + else expectedClean.contains(actualClean) + if (!matches) continue + if (actualClean.length > bestMatchLength) { + bestMatch = candidate + bestMatchLength = actualClean.length + } + } + } + return bestMatch + } + +} diff --git a/src/main/kotlin/repo/RepoDownloadManager.kt b/src/main/kotlin/repo/RepoDownloadManager.kt new file mode 100644 index 0000000..d674f23 --- /dev/null +++ b/src/main/kotlin/repo/RepoDownloadManager.kt @@ -0,0 +1,128 @@ + + +package moe.nea.firmament.repo + +import io.ktor.client.call.body +import io.ktor.client.request.get +import io.ktor.client.statement.bodyAsChannel +import io.ktor.utils.io.jvm.nio.copyTo +import java.io.IOException +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.StandardOpenOption +import java.util.zip.ZipInputStream +import kotlinx.coroutines.CoroutineName +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.withContext +import kotlinx.serialization.Serializable +import kotlin.io.path.createDirectories +import kotlin.io.path.exists +import kotlin.io.path.inputStream +import kotlin.io.path.outputStream +import kotlin.io.path.readText +import kotlin.io.path.writeText +import moe.nea.firmament.Firmament +import moe.nea.firmament.Firmament.logger +import moe.nea.firmament.util.iterate + + +object RepoDownloadManager { + + val repoSavedLocation = Firmament.DATA_DIR.resolve("repo-extracted") + val repoMetadataLocation = Firmament.DATA_DIR.resolve("loaded-repo-sha.txt") + + private fun loadSavedVersionHash(): String? = + if (repoSavedLocation.exists()) { + if (repoMetadataLocation.exists()) { + try { + repoMetadataLocation.readText().trim() + } catch (e: IOException) { + null + } + } else { + null + } + } else null + + private fun saveVersionHash(versionHash: String) { + latestSavedVersionHash = versionHash + repoMetadataLocation.writeText(versionHash) + } + + var latestSavedVersionHash: String? = loadSavedVersionHash() + private set + + @Serializable + private class GithubCommitsResponse(val sha: String) + + private suspend fun requestLatestGithubSha(): String? { + if (RepoManager.Config.branch == "prerelease") { + RepoManager.Config.branch = "master" + } + val response = + Firmament.httpClient.get("https://api.github.com/repos/${RepoManager.Config.username}/${RepoManager.Config.reponame}/commits/${RepoManager.Config.branch}") + if (response.status.value != 200) { + return null + } + return response.body().sha + } + + private suspend fun downloadGithubArchive(url: String): Path = withContext(IO) { + val response = Firmament.httpClient.get(url) + val targetFile = Files.createTempFile("firmament-repo", ".zip") + val outputChannel = Files.newByteChannel(targetFile, StandardOpenOption.CREATE, StandardOpenOption.WRITE) + response.bodyAsChannel().copyTo(outputChannel) + targetFile + } + + /** + * Downloads the latest repository from github, setting [latestSavedVersionHash]. + * @return true, if an update was performed, false, otherwise (no update needed, or wasn't able to complete update) + */ + suspend fun downloadUpdate(force: Boolean): Boolean = withContext(CoroutineName("Repo Update Check")) { + val latestSha = requestLatestGithubSha() + if (latestSha == null) { + logger.warn("Could not request github API to retrieve latest REPO sha.") + return@withContext false + } + val currentSha = loadSavedVersionHash() + if (latestSha != currentSha || force) { + val requestUrl = + "https://github.com/${RepoManager.Config.username}/${RepoManager.Config.reponame}/archive/$latestSha.zip" + logger.info("Planning to upgrade repository from $currentSha to $latestSha from $requestUrl") + val zipFile = downloadGithubArchive(requestUrl) + logger.info("Download repository zip file to $zipFile. Deleting old repository") + withContext(IO) { repoSavedLocation.toFile().deleteRecursively() } + logger.info("Extracting new repository") + withContext(IO) { extractNewRepository(zipFile) } + logger.info("Repository loaded on disk.") + saveVersionHash(latestSha) + return@withContext true + } else { + logger.debug("Repository on latest sha $currentSha. Not performing update") + return@withContext false + } + } + + private fun extractNewRepository(zipFile: Path) { + repoSavedLocation.createDirectories() + ZipInputStream(zipFile.inputStream()).use { cis -> + while (true) { + val entry = cis.nextEntry ?: break + if (entry.isDirectory) continue + val extractedLocation = + repoSavedLocation.resolve( + entry.name.substringAfter('/', missingDelimiterValue = "") + ) + if (repoSavedLocation !in extractedLocation.iterate { it.parent }) { + logger.error("Firmament detected an invalid zip file. This is a potential security risk, please report this in the Firmament discord.") + throw RuntimeException("Firmament detected an invalid zip file. This is a potential security risk, please report this in the Firmament discord.") + } + extractedLocation.parent.createDirectories() + extractedLocation.outputStream().use { cis.copyTo(it) } + } + } + } + + +} diff --git a/src/main/kotlin/repo/RepoManager.kt b/src/main/kotlin/repo/RepoManager.kt new file mode 100644 index 0000000..f0da397 --- /dev/null +++ b/src/main/kotlin/repo/RepoManager.kt @@ -0,0 +1,145 @@ +package moe.nea.firmament.repo + +import io.github.moulberry.repo.NEURepository +import io.github.moulberry.repo.NEURepositoryException +import io.github.moulberry.repo.data.NEUItem +import io.github.moulberry.repo.data.NEURecipe +import io.github.moulberry.repo.data.Rarity +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents +import kotlinx.coroutines.launch +import net.minecraft.client.MinecraftClient +import net.minecraft.network.packet.s2c.play.SynchronizeRecipesS2CPacket +import net.minecraft.text.Text +import moe.nea.firmament.Firmament +import moe.nea.firmament.Firmament.logger +import moe.nea.firmament.events.ReloadRegistrationEvent +import moe.nea.firmament.gui.config.ManagedConfig +import moe.nea.firmament.rei.PetData +import moe.nea.firmament.util.MinecraftDispatcher +import moe.nea.firmament.util.SkyblockId + +object RepoManager { + object Config : ManagedConfig("repo") { + var username by string("username") { "NotEnoughUpdates" } + var reponame by string("reponame") { "NotEnoughUpdates-REPO" } + var branch by string("branch") { "master" } + val autoUpdate by toggle("autoUpdate") { true } + val reset by button("reset") { + username = "NotEnoughUpdates" + reponame = "NotEnoughUpdates-REPO" + branch = "master" + save() + } + + val disableItemGroups by toggle("disable-item-groups") { true } + val reload by button("reload") { + save() + RepoManager.reload() + } + val redownload by button("redownload") { + save() + RepoManager.launchAsyncUpdate(true) + } + } + + val currentDownloadedSha by RepoDownloadManager::latestSavedVersionHash + + var recentlyFailedToUpdateItemList = false + + val neuRepo: NEURepository = NEURepository.of(RepoDownloadManager.repoSavedLocation).apply { + registerReloadListener(ItemCache) + registerReloadListener(ExpLadders) + registerReloadListener(ItemNameLookup) + ReloadRegistrationEvent.publish(ReloadRegistrationEvent(this)) + registerReloadListener { + Firmament.coroutineScope.launch(MinecraftDispatcher) { + if (!trySendClientboundUpdateRecipesPacket()) { + logger.warn("Failed to issue a ClientboundUpdateRecipesPacket (to reload REI). This may lead to an outdated item list.") + recentlyFailedToUpdateItemList = true + } + } + } + } + + val essenceRecipeProvider = EssenceRecipeProvider() + val recipeCache = BetterRepoRecipeCache(essenceRecipeProvider) + + init { + neuRepo.registerReloadListener(essenceRecipeProvider) + neuRepo.registerReloadListener(recipeCache) + } + + fun getAllRecipes() = neuRepo.items.items.values.asSequence().flatMap { it.recipes } + + fun getRecipesFor(skyblockId: SkyblockId): Set = recipeCache.recipes[skyblockId] ?: setOf() + fun getUsagesFor(skyblockId: SkyblockId): Set = recipeCache.usages[skyblockId] ?: setOf() + + private fun trySendClientboundUpdateRecipesPacket(): Boolean { + return MinecraftClient.getInstance().world != null && MinecraftClient.getInstance().networkHandler?.onSynchronizeRecipes( + SynchronizeRecipesS2CPacket(mutableListOf()) + ) != null + } + + init { + ClientTickEvents.START_WORLD_TICK.register(ClientTickEvents.StartWorldTick { + if (recentlyFailedToUpdateItemList && trySendClientboundUpdateRecipesPacket()) + recentlyFailedToUpdateItemList = false + }) + } + + fun getNEUItem(skyblockId: SkyblockId): NEUItem? = neuRepo.items.getItemBySkyblockId(skyblockId.neuItem) + + fun launchAsyncUpdate(force: Boolean = false) { + Firmament.coroutineScope.launch { + ItemCache.ReloadProgressHud.reportProgress("Downloading", 0, -1) // TODO: replace with a proper boundy bar + ItemCache.ReloadProgressHud.isEnabled = true + try { + RepoDownloadManager.downloadUpdate(force) + ItemCache.ReloadProgressHud.reportProgress("Download complete", 1, 1) + } finally { + ItemCache.ReloadProgressHud.isEnabled = false + } + reload() + } + } + + fun reload() { + try { + ItemCache.ReloadProgressHud.reportProgress("Reloading from Disk", + 0, + -1) // TODO: replace with a proper boundy bar + ItemCache.ReloadProgressHud.isEnabled = true + neuRepo.reload() + } catch (exc: NEURepositoryException) { + MinecraftClient.getInstance().player?.sendMessage( + Text.literal("Failed to reload repository. This will result in some mod features not working.") + ) + ItemCache.ReloadProgressHud.isEnabled = false + exc.printStackTrace() + } + } + + fun initialize() { + if (Config.autoUpdate) { + launchAsyncUpdate() + } else { + reload() + } + } + + fun getPotentialStubPetData(skyblockId: SkyblockId): PetData? { + val parts = skyblockId.neuItem.split(";") + if (parts.size != 2) { + return null + } + val (petId, rarityIndex) = parts + if (!rarityIndex.all { it.isDigit() }) { + return null + } + val intIndex = rarityIndex.toInt() + if (intIndex !in Rarity.values().indices) return null + if (petId !in neuRepo.constants.petNumbers) return null + return PetData(Rarity.values()[intIndex], petId, 0.0, true) + } + +} diff --git a/src/main/kotlin/repo/RepoModResourcePack.kt b/src/main/kotlin/repo/RepoModResourcePack.kt new file mode 100644 index 0000000..f92fe4f --- /dev/null +++ b/src/main/kotlin/repo/RepoModResourcePack.kt @@ -0,0 +1,126 @@ + +package moe.nea.firmament.repo + +import java.io.InputStream +import java.nio.file.Files +import java.nio.file.Path +import java.util.* +import net.fabricmc.fabric.api.resource.ModResourcePack +import net.fabricmc.loader.api.FabricLoader +import net.fabricmc.loader.api.metadata.ModMetadata +import kotlin.io.path.exists +import kotlin.io.path.isRegularFile +import kotlin.io.path.relativeTo +import kotlin.streams.asSequence +import net.minecraft.resource.AbstractFileResourcePack +import net.minecraft.resource.InputSupplier +import net.minecraft.resource.NamespaceResourceManager +import net.minecraft.resource.Resource +import net.minecraft.resource.ResourcePack +import net.minecraft.resource.ResourcePackInfo +import net.minecraft.resource.ResourcePackSource +import net.minecraft.resource.ResourceType +import net.minecraft.resource.metadata.ResourceMetadata +import net.minecraft.resource.metadata.ResourceMetadataReader +import net.minecraft.text.Text +import net.minecraft.util.Identifier +import net.minecraft.util.PathUtil +import moe.nea.firmament.Firmament + +class RepoModResourcePack(val basePath: Path) : ModResourcePack { + companion object { + fun append(packs: MutableList) { + Firmament.logger.info("Registering mod resource pack") + packs.add(RepoModResourcePack(RepoDownloadManager.repoSavedLocation)) + } + + fun createResourceDirectly(identifier: Identifier): Optional { + val pack = RepoModResourcePack(RepoDownloadManager.repoSavedLocation) + return Optional.of( + Resource( + pack, + pack.open(ResourceType.CLIENT_RESOURCES, identifier) ?: return Optional.empty() + ) { + val base = + pack.open(ResourceType.CLIENT_RESOURCES, identifier.withPath(identifier.path + ".mcmeta")) + if (base == null) + ResourceMetadata.NONE + else + NamespaceResourceManager.loadMetadata(base) + } + ) + } + } + + override fun close() { + } + + override fun openRoot(vararg segments: String): InputSupplier? { + return getFile(segments)?.let { InputSupplier.create(it) } + } + + fun getFile(segments: Array): Path? { + PathUtil.validatePath(*segments) + val path = segments.fold(basePath, Path::resolve) + if (!path.isRegularFile()) return null + return path + } + + override fun open(type: ResourceType?, id: Identifier): InputSupplier? { + if (type != ResourceType.CLIENT_RESOURCES) return null + if (id.namespace != "neurepo") return null + val file = getFile(id.path.split("/").toTypedArray()) + return file?.let { InputSupplier.create(it) } + } + + override fun findResources( + type: ResourceType?, + namespace: String, + prefix: String, + consumer: ResourcePack.ResultConsumer + ) { + if (namespace != "neurepo") return + if (type != ResourceType.CLIENT_RESOURCES) return + + val prefixPath = basePath.resolve(prefix) + if (!prefixPath.exists()) + return + Files.walk(prefixPath) + .asSequence() + .map { it.relativeTo(basePath) } + .forEach { + consumer.accept(Identifier.of("neurepo", it.toString()), InputSupplier.create(it)) + } + } + + override fun getNamespaces(type: ResourceType?): Set { + if (type != ResourceType.CLIENT_RESOURCES) return emptySet() + return setOf("neurepo") + } + + override fun parseMetadata(metaReader: ResourceMetadataReader): T? { + return AbstractFileResourcePack.parseMetadata( + metaReader, """ +{ + "pack": { + "pack_format": 12, + "description": "NEU Repo Resources" + } +} +""".trimIndent().byteInputStream() + ) + } + + override fun getInfo(): ResourcePackInfo { + return ResourcePackInfo("neurepo", Text.literal("NEU Repo"), ResourcePackSource.BUILTIN, Optional.empty()) + } + + override fun getFabricModMetadata(): ModMetadata { + return FabricLoader.getInstance().getModContainer("firmament") + .get().metadata + } + + override fun createOverlay(overlay: String): ModResourcePack { + return RepoModResourcePack(basePath.resolve(overlay)) + } +} diff --git a/src/main/kotlin/util/Base64Util.kt b/src/main/kotlin/util/Base64Util.kt new file mode 100644 index 0000000..44bcdfd --- /dev/null +++ b/src/main/kotlin/util/Base64Util.kt @@ -0,0 +1,10 @@ + +package moe.nea.firmament.util + +object Base64Util { + fun String.padToValidBase64(): String { + val align = this.length % 4 + if (align == 0) return this + return this + "=".repeat(4 - align) + } +} diff --git a/src/main/kotlin/util/BazaarPriceStrategy.kt b/src/main/kotlin/util/BazaarPriceStrategy.kt new file mode 100644 index 0000000..002eedb --- /dev/null +++ b/src/main/kotlin/util/BazaarPriceStrategy.kt @@ -0,0 +1,19 @@ + +package moe.nea.firmament.util + +import moe.nea.firmament.repo.HypixelStaticData + +enum class BazaarPriceStrategy { + BUY_ORDER, + SELL_ORDER, + NPC_SELL; + + fun getSellPrice(skyblockId: SkyblockId): Double { + val bazaarEntry = HypixelStaticData.bazaarData[skyblockId] ?: return 0.0 + return when (this) { + BUY_ORDER -> bazaarEntry.quickStatus.sellPrice + SELL_ORDER -> bazaarEntry.quickStatus.buyPrice + NPC_SELL -> TODO() + } + } +} diff --git a/src/main/kotlin/util/ClipboardUtils.kt b/src/main/kotlin/util/ClipboardUtils.kt new file mode 100644 index 0000000..7b9b836 --- /dev/null +++ b/src/main/kotlin/util/ClipboardUtils.kt @@ -0,0 +1,24 @@ + + +package moe.nea.firmament.util + +import moe.nea.firmament.Firmament + +object ClipboardUtils { + fun setTextContent(string: String) { + try { + MC.keyboard.clipboard = string.ifEmpty { " " } + } catch (e: Exception) { + Firmament.logger.error("Could not write clipboard", e) + } + } + + fun getTextContents(): String { + try { + return MC.keyboard.clipboard ?: "" + } catch (e: Exception) { + Firmament.logger.error("Could not read clipboard", e) + return "" + } + } +} diff --git a/src/main/kotlin/util/CommonSoundEffects.kt b/src/main/kotlin/util/CommonSoundEffects.kt new file mode 100644 index 0000000..a97a2cb --- /dev/null +++ b/src/main/kotlin/util/CommonSoundEffects.kt @@ -0,0 +1,26 @@ + + +package moe.nea.firmament.util + +import net.minecraft.client.sound.PositionedSoundInstance +import net.minecraft.sound.SoundEvent +import net.minecraft.util.Identifier + +// TODO: Replace these with custom sound events that just re use the vanilla ogg s +object CommonSoundEffects { + fun playSound(identifier: Identifier) { + MC.soundManager.play(PositionedSoundInstance.master(SoundEvent.of(identifier), 1F)) + } + + fun playFailure() { + playSound(Identifier.of("minecraft", "block.anvil.place")) + } + + fun playSuccess() { + playDing() + } + + fun playDing() { + playSound(Identifier.of("minecraft", "entity.arrow.hit_player")) + } +} diff --git a/src/main/kotlin/util/DurabilityBarEvent.kt b/src/main/kotlin/util/DurabilityBarEvent.kt new file mode 100644 index 0000000..993462c --- /dev/null +++ b/src/main/kotlin/util/DurabilityBarEvent.kt @@ -0,0 +1,20 @@ + +package moe.nea.firmament.util + +import me.shedaniel.math.Color +import net.minecraft.item.ItemStack +import moe.nea.firmament.events.FirmamentEvent +import moe.nea.firmament.events.FirmamentEventBus + +data class DurabilityBarEvent( + val item: ItemStack, +) : FirmamentEvent() { + data class DurabilityBar( + val color: Color, + val percentage: Float, + ) + + var barOverride: DurabilityBar? = null + + companion object : FirmamentEventBus() +} diff --git a/src/main/kotlin/util/ErrorBoundary.kt b/src/main/kotlin/util/ErrorBoundary.kt new file mode 100644 index 0000000..fbc5b37 --- /dev/null +++ b/src/main/kotlin/util/ErrorBoundary.kt @@ -0,0 +1,10 @@ + + +package moe.nea.firmament.util + + +fun errorBoundary(block: () -> T): T? { + // TODO: implement a proper error boundary here to avoid crashing minecraft code + return block() +} + diff --git a/src/main/kotlin/util/FirmFormatters.kt b/src/main/kotlin/util/FirmFormatters.kt new file mode 100644 index 0000000..c3bdd16 --- /dev/null +++ b/src/main/kotlin/util/FirmFormatters.kt @@ -0,0 +1,59 @@ + + +package moe.nea.firmament.util + +import com.google.common.math.IntMath.pow +import kotlin.math.absoluteValue +import kotlin.time.Duration + +object FirmFormatters { + fun formatCommas(int: Int, segments: Int = 3): String = formatCommas(int.toLong(), segments) + fun formatCommas(long: Long, segments: Int = 3): String { + val α = long / 1000 + if (α != 0L) { + return formatCommas(α, segments) + "," + (long - α * 1000).toString().padStart(3, '0') + } + return long.toString() + } + + fun formatCommas(float: Float, fractionalDigits: Int): String = formatCommas(float.toDouble(), fractionalDigits) + fun formatCommas(double: Double, fractionalDigits: Int): String { + val long = double.toLong() + val δ = (double - long).absoluteValue + val μ = pow(10, fractionalDigits) + val digits = (μ * δ).toInt().toString().padStart(fractionalDigits, '0').trimEnd('0') + return formatCommas(long) + (if (digits.isEmpty()) "" else ".$digits") + } + + fun formatDistance(distance: Double): String { + if (distance < 10) + return "%.1fm".format(distance) + return "%dm".format(distance.toInt()) + } + + fun formatTimespan(duration: Duration, millis: Boolean = false): String { + if (duration.isInfinite()) { + return if (duration.isPositive()) "∞" + else "-∞" + } + val sb = StringBuilder() + if (duration.isNegative()) sb.append("-") + duration.toComponents { days, hours, minutes, seconds, nanoseconds -> + if (days > 0) { + sb.append(days).append("d") + } + if (hours > 0) { + sb.append(hours).append("h") + } + if (minutes > 0) { + sb.append(minutes).append("m") + } + sb.append(seconds).append("s") + if (millis) { + sb.append(nanoseconds / 1_000_000).append("ms") + } + } + return sb.toString() + } + +} diff --git a/src/main/kotlin/util/FragmentGuiScreen.kt b/src/main/kotlin/util/FragmentGuiScreen.kt new file mode 100644 index 0000000..5e13d51 --- /dev/null +++ b/src/main/kotlin/util/FragmentGuiScreen.kt @@ -0,0 +1,93 @@ + + +package moe.nea.firmament.util + +import io.github.notenoughupdates.moulconfig.gui.GuiContext +import me.shedaniel.math.Dimension +import me.shedaniel.math.Point +import me.shedaniel.math.Rectangle +import net.minecraft.client.gui.DrawContext +import net.minecraft.client.gui.screen.Screen +import net.minecraft.text.Text + +abstract class FragmentGuiScreen( + val dismissOnOutOfBounds: Boolean = true +) : Screen(Text.literal("")) { + var popup: MoulConfigFragment? = null + + fun createPopup(context: GuiContext, position: Point) { + popup = MoulConfigFragment(context, position) { popup = null } + } + + override fun render(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) { + super.render(context, mouseX, mouseY, delta) + context.matrices.push() + context.matrices.translate(0f, 0f, 1000f) + popup?.render(context, mouseX, mouseY, delta) + context.matrices.pop() + } + + private inline fun ifPopup(ifYes: (MoulConfigFragment) -> Unit): Boolean { + val p = popup ?: return false + ifYes(p) + return true + } + + override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean { + return ifPopup { + it.keyPressed(keyCode, scanCode, modifiers) + } + } + + override fun keyReleased(keyCode: Int, scanCode: Int, modifiers: Int): Boolean { + return ifPopup { + it.keyReleased(keyCode, scanCode, modifiers) + } + } + + override fun mouseMoved(mouseX: Double, mouseY: Double) { + ifPopup { it.mouseMoved(mouseX, mouseY) } + } + + override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean { + return ifPopup { + it.mouseReleased(mouseX, mouseY, button) + } + } + + override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean { + return ifPopup { + it.mouseDragged(mouseX, mouseY, button, deltaX, deltaY) + } + } + + override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean { + return ifPopup { + if (!Rectangle( + it.position, + Dimension(it.context.root.width, it.context.root.height) + ).contains(Point(mouseX, mouseY)) + && dismissOnOutOfBounds + ) { + popup = null + } else { + it.mouseClicked(mouseX, mouseY, button) + } + }|| super.mouseClicked(mouseX, mouseY, button) + } + + override fun charTyped(chr: Char, modifiers: Int): Boolean { + return ifPopup { it.charTyped(chr, modifiers) } + } + + override fun mouseScrolled( + mouseX: Double, + mouseY: Double, + horizontalAmount: Double, + verticalAmount: Double + ): Boolean { + return ifPopup { + it.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount) + } + } +} diff --git a/src/main/kotlin/util/GetRectangle.kt b/src/main/kotlin/util/GetRectangle.kt new file mode 100644 index 0000000..ec64f31 --- /dev/null +++ b/src/main/kotlin/util/GetRectangle.kt @@ -0,0 +1,17 @@ + + +package moe.nea.firmament.util + +import me.shedaniel.math.Rectangle +import moe.nea.firmament.mixins.accessor.AccessorHandledScreen +import net.minecraft.client.gui.screen.ingame.HandledScreen + +fun HandledScreen<*>.getRectangle(): Rectangle { + this as AccessorHandledScreen + return Rectangle( + getX_Firmament(), + getY_Firmament(), + getBackgroundWidth_Firmament(), + getBackgroundHeight_Firmament() + ) +} diff --git a/src/main/kotlin/util/HoveredItemStack.kt b/src/main/kotlin/util/HoveredItemStack.kt new file mode 100644 index 0000000..47a59d0 --- /dev/null +++ b/src/main/kotlin/util/HoveredItemStack.kt @@ -0,0 +1,31 @@ + + +package moe.nea.firmament.util + +import me.shedaniel.math.impl.PointHelper +import me.shedaniel.rei.api.client.REIRuntime +import me.shedaniel.rei.api.client.gui.widgets.Slot +import me.shedaniel.rei.api.client.registry.screen.ScreenRegistry +import net.minecraft.client.gui.Element +import net.minecraft.client.gui.ParentElement +import net.minecraft.client.gui.screen.ingame.HandledScreen +import net.minecraft.item.ItemStack +import moe.nea.firmament.mixins.accessor.AccessorHandledScreen + + +val HandledScreen<*>.focusedItemStack: ItemStack? + get() { + this as AccessorHandledScreen + val vanillaSlot = this.focusedSlot_Firmament?.stack + if (vanillaSlot != null) return vanillaSlot + val focusedSlot = ScreenRegistry.getInstance().getFocusedStack(this, PointHelper.ofMouse()) + if (focusedSlot != null) return focusedSlot.cheatsAs().value + var baseElement: Element? = REIRuntime.getInstance().overlay.orElse(null) + val mx = PointHelper.getMouseFloatingX() + val my = PointHelper.getMouseFloatingY() + while (true) { + if (baseElement is Slot) return baseElement.currentEntry.cheatsAs().value + if (baseElement !is ParentElement) return null + baseElement = baseElement.hoveredElement(mx, my).orElse(null) + } + } diff --git a/src/main/kotlin/util/IdentifierSerializer.kt b/src/main/kotlin/util/IdentifierSerializer.kt new file mode 100644 index 0000000..65c5b1c --- /dev/null +++ b/src/main/kotlin/util/IdentifierSerializer.kt @@ -0,0 +1,25 @@ + +package moe.nea.firmament.util + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import net.minecraft.util.Identifier + +object IdentifierSerializer : KSerializer { + val delegateSerializer = String.serializer() + override val descriptor: SerialDescriptor + get() = PrimitiveSerialDescriptor("Identifier", PrimitiveKind.STRING) + + override fun deserialize(decoder: Decoder): Identifier { + return Identifier.of(decoder.decodeSerializableValue(delegateSerializer)) + } + + override fun serialize(encoder: Encoder, value: Identifier) { + encoder.encodeSerializableValue(delegateSerializer, value.toString()) + } +} diff --git a/src/main/kotlin/util/IdentityCharacteristics.kt b/src/main/kotlin/util/IdentityCharacteristics.kt new file mode 100644 index 0000000..f6054c4 --- /dev/null +++ b/src/main/kotlin/util/IdentityCharacteristics.kt @@ -0,0 +1,15 @@ + + +package moe.nea.firmament.util + +class IdentityCharacteristics(val value: T) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is IdentityCharacteristics<*>) return false + return value === other.value + } + + override fun hashCode(): Int { + return System.identityHashCode(value) + } +} diff --git a/src/main/kotlin/util/ItemUtil.kt b/src/main/kotlin/util/ItemUtil.kt new file mode 100644 index 0000000..40d6198 --- /dev/null +++ b/src/main/kotlin/util/ItemUtil.kt @@ -0,0 +1,26 @@ + + +package moe.nea.firmament.util + +import net.minecraft.item.ItemStack +import net.minecraft.nbt.NbtCompound +import net.minecraft.nbt.NbtList +import net.minecraft.text.Text +import moe.nea.firmament.util.item.loreAccordingToNbt + + +fun ItemStack.appendLore(args: List) { + if (args.isEmpty()) return + modifyLore { + val loreList = loreAccordingToNbt.toMutableList() + for (arg in args) { + loreList.add(arg) + } + loreList + } +} + +fun ItemStack.modifyLore(update: (List) -> List) { + val loreList = loreAccordingToNbt + loreAccordingToNbt = update(loreList) +} diff --git a/src/main/kotlin/util/LegacyFormattingCode.kt b/src/main/kotlin/util/LegacyFormattingCode.kt new file mode 100644 index 0000000..44bacfc --- /dev/null +++ b/src/main/kotlin/util/LegacyFormattingCode.kt @@ -0,0 +1,35 @@ + + +package moe.nea.firmament.util + +import net.minecraft.util.Formatting + +enum class LegacyFormattingCode(val label: String, val char: Char, val index: Int) { + BLACK("BLACK", '0', 0), + DARK_BLUE("DARK_BLUE", '1', 1), + DARK_GREEN("DARK_GREEN", '2', 2), + DARK_AQUA("DARK_AQUA", '3', 3), + DARK_RED("DARK_RED", '4', 4), + DARK_PURPLE("DARK_PURPLE", '5', 5), + GOLD("GOLD", '6', 6), + GRAY("GRAY", '7', 7), + DARK_GRAY("DARK_GRAY", '8', 8), + BLUE("BLUE", '9', 9), + GREEN("GREEN", 'a', 10), + AQUA("AQUA", 'b', 11), + RED("RED", 'c', 12), + LIGHT_PURPLE("LIGHT_PURPLE", 'd', 13), + YELLOW("YELLOW", 'e', 14), + WHITE("WHITE", 'f', 15), + OBFUSCATED("OBFUSCATED", 'k', -1), + BOLD("BOLD", 'l', -1), + STRIKETHROUGH("STRIKETHROUGH", 'm', -1), + UNDERLINE("UNDERLINE", 'n', -1), + ITALIC("ITALIC", 'o', -1), + RESET("RESET", 'r', -1); + + val modern = Formatting.byCode(char)!! + + val formattingCode = "§$char" + +} diff --git a/src/main/kotlin/util/LegacyTagParser.kt b/src/main/kotlin/util/LegacyTagParser.kt new file mode 100644 index 0000000..4e08da1 --- /dev/null +++ b/src/main/kotlin/util/LegacyTagParser.kt @@ -0,0 +1,245 @@ + + +package moe.nea.firmament.util + +import java.util.* +import net.minecraft.nbt.AbstractNbtNumber +import net.minecraft.nbt.NbtByte +import net.minecraft.nbt.NbtCompound +import net.minecraft.nbt.NbtDouble +import net.minecraft.nbt.NbtElement +import net.minecraft.nbt.NbtFloat +import net.minecraft.nbt.NbtInt +import net.minecraft.nbt.NbtList +import net.minecraft.nbt.NbtLong +import net.minecraft.nbt.NbtShort +import net.minecraft.nbt.NbtString + +class LegacyTagParser private constructor(string: String) { + data class TagParsingException(val baseString: String, val offset: Int, val mes0: String) : + Exception("$mes0 at $offset in `$baseString`.") + + class StringRacer(val backing: String) { + var idx = 0 + val stack = Stack() + + fun pushState() { + stack.push(idx) + } + + fun popState() { + idx = stack.pop() + } + + fun discardState() { + stack.pop() + } + + fun peek(count: Int): String { + return backing.substring(minOf(idx, backing.length), minOf(idx + count, backing.length)) + } + + fun finished(): Boolean { + return peek(1).isEmpty() + } + + fun peekReq(count: Int): String? { + val p = peek(count) + if (p.length != count) + return null + return p + } + + fun consumeCountReq(count: Int): String? { + val p = peekReq(count) + if (p != null) + idx += count + return p + } + + fun tryConsume(string: String): Boolean { + val p = peek(string.length) + if (p != string) + return false + idx += p.length + return true + } + + fun consumeWhile(shouldConsumeThisString: (String) -> Boolean): String { + var lastString: String = "" + while (true) { + val nextString = lastString + peek(1) + if (!shouldConsumeThisString(nextString)) { + return lastString + } + idx++ + lastString = nextString + } + } + + fun expect(search: String, errorMessage: String) { + if (!tryConsume(search)) + error(errorMessage) + } + + fun error(errorMessage: String): Nothing { + throw TagParsingException(backing, idx, errorMessage) + } + + } + + val racer = StringRacer(string) + val baseTag = parseTag() + + companion object { + val digitRange = "0123456789-" + fun parse(string: String): NbtCompound { + return LegacyTagParser(string).baseTag + } + } + + fun skipWhitespace() { + racer.consumeWhile { Character.isWhitespace(it.last()) } // Only check last since other chars are always checked before. + } + + fun parseTag(): NbtCompound { + skipWhitespace() + racer.expect("{", "Expected '{’ at start of tag") + skipWhitespace() + val tag = NbtCompound() + while (!racer.tryConsume("}")) { + skipWhitespace() + val lhs = parseIdentifier() + skipWhitespace() + racer.expect(":", "Expected ':' after identifier in tag") + skipWhitespace() + val rhs = parseAny() + tag.put(lhs, rhs) + racer.tryConsume(",") + skipWhitespace() + } + return tag + } + + private fun parseAny(): NbtElement { + skipWhitespace() + val nextChar = racer.peekReq(1) ?: racer.error("Expected new object, found EOF") + return when { + nextChar == "{" -> parseTag() + nextChar == "[" -> parseList() + nextChar == "\"" -> parseStringTag() + nextChar.first() in (digitRange) -> parseNumericTag() + else -> racer.error("Unexpected token found. Expected start of new element") + } + } + + fun parseList(): NbtList { + skipWhitespace() + racer.expect("[", "Expected '[' at start of tag") + skipWhitespace() + val list = NbtList() + while (!racer.tryConsume("]")) { + skipWhitespace() + racer.pushState() + val lhs = racer.consumeWhile { it.all { it in digitRange } } + skipWhitespace() + if (!racer.tryConsume(":") || lhs.isEmpty()) { // No prefixed 0: + racer.popState() + list.add(parseAny()) // Reparse our number (or not a number) as actual tag + } else { + racer.discardState() + skipWhitespace() + list.add(parseAny()) // Ignore prefix indexes. They should not be generated out of order by any vanilla implementation (which is what NEU should export). Instead append where it appears in order. + } + skipWhitespace() + racer.tryConsume(",") + } + return list + } + + fun parseQuotedString(): String { + skipWhitespace() + racer.expect("\"", "Expected '\"' at string start") + val sb = StringBuilder() + while (true) { + when (val peek = racer.consumeCountReq(1)) { + "\"" -> break + "\\" -> { + val escaped = racer.consumeCountReq(1) ?: racer.error("Unfinished backslash escape") + if (escaped != "\"" && escaped != "\\") { + // Surprisingly i couldn't find unicode escapes to be generated by the original minecraft 1.8.9 implementation + racer.idx-- + racer.error("Invalid backslash escape '$escaped'") + } + sb.append(escaped) + } + + null -> racer.error("Unfinished string") + else -> { + sb.append(peek) + } + } + } + return sb.toString() + } + + fun parseStringTag(): NbtString { + return NbtString.of(parseQuotedString()) + } + + object Patterns { + val DOUBLE = "([-+]?[0-9]*\\.?[0-9]+)[d|D]".toRegex() + val FLOAT = "([-+]?[0-9]*\\.?[0-9]+)[f|F]".toRegex() + val BYTE = "([-+]?[0-9]+)[b|B]".toRegex() + val LONG = "([-+]?[0-9]+)[l|L]".toRegex() + val SHORT = "([-+]?[0-9]+)[s|S]".toRegex() + val INTEGER = "([-+]?[0-9]+)".toRegex() + val DOUBLE_UNTYPED = "([-+]?[0-9]*\\.?[0-9]+)".toRegex() + val ROUGH_PATTERN = "[-+]?[0-9]*\\.?[0-9]*[dDbBfFlLsS]?".toRegex() + } + + fun parseNumericTag(): AbstractNbtNumber { + skipWhitespace() + val textForm = racer.consumeWhile { Patterns.ROUGH_PATTERN.matchEntire(it) != null } + if (textForm.isEmpty()) { + racer.error("Expected numeric tag (starting with either -, +, . or a digit") + } + val floatMatch = Patterns.FLOAT.matchEntire(textForm) + if (floatMatch != null) { + return NbtFloat.of(floatMatch.groups[1]!!.value.toFloat()) + } + val byteMatch = Patterns.BYTE.matchEntire(textForm) + if (byteMatch != null) { + return NbtByte.of(byteMatch.groups[1]!!.value.toByte()) + } + val longMatch = Patterns.LONG.matchEntire(textForm) + if (longMatch != null) { + return NbtLong.of(longMatch.groups[1]!!.value.toLong()) + } + val shortMatch = Patterns.SHORT.matchEntire(textForm) + if (shortMatch != null) { + return NbtShort.of(shortMatch.groups[1]!!.value.toShort()) + } + val integerMatch = Patterns.INTEGER.matchEntire(textForm) + if (integerMatch != null) { + return NbtInt.of(integerMatch.groups[1]!!.value.toInt()) + } + val doubleMatch = Patterns.DOUBLE.matchEntire(textForm) ?: Patterns.DOUBLE_UNTYPED.matchEntire(textForm) + if (doubleMatch != null) { + return NbtDouble.of(doubleMatch.groups[1]!!.value.toDouble()) + } + throw IllegalStateException("Could not properly parse numeric tag '$textForm', despite passing rough verification. This is a bug in the LegacyTagParser") + } + + private fun parseIdentifier(): String { + skipWhitespace() + if (racer.peek(1) == "\"") { + return parseQuotedString() + } + return racer.consumeWhile { + val x = it.last() + x != ':' && !Character.isWhitespace(x) + } + } + +} diff --git a/src/main/kotlin/util/LoadResource.kt b/src/main/kotlin/util/LoadResource.kt new file mode 100644 index 0000000..4bc8704 --- /dev/null +++ b/src/main/kotlin/util/LoadResource.kt @@ -0,0 +1,20 @@ + +package moe.nea.firmament.util + +import java.io.InputStream +import kotlin.io.path.inputStream +import kotlin.jvm.optionals.getOrNull +import net.minecraft.util.Identifier +import moe.nea.firmament.repo.RepoDownloadManager + + +fun Identifier.openFirmamentResource(): InputStream { + val resource = MC.resourceManager.getResource(this).getOrNull() + if (resource == null) { + if (namespace == "neurepo") + return RepoDownloadManager.repoSavedLocation.resolve(path).inputStream() + error("Could not read resource $this") + } + return resource.inputStream +} + diff --git a/src/main/kotlin/util/Locraw.kt b/src/main/kotlin/util/Locraw.kt new file mode 100644 index 0000000..9778bc7 --- /dev/null +++ b/src/main/kotlin/util/Locraw.kt @@ -0,0 +1,12 @@ + + +package moe.nea.firmament.util + +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient + +@Serializable +data class Locraw(val server: String, val gametype: String? = null, val mode: String? = null, val map: String? = null) { + @Transient + val skyblockLocation = if (gametype == "SKYBLOCK") mode?.let(SkyBlockIsland::forMode) else null +} diff --git a/src/main/kotlin/util/LogIfNull.kt b/src/main/kotlin/util/LogIfNull.kt new file mode 100644 index 0000000..600c5e6 --- /dev/null +++ b/src/main/kotlin/util/LogIfNull.kt @@ -0,0 +1,8 @@ + +package moe.nea.firmament.util + + +fun runNull(block: () -> Unit): Nothing? { + block() + return null +} diff --git a/src/main/kotlin/util/MC.kt b/src/main/kotlin/util/MC.kt new file mode 100644 index 0000000..b0d3056 --- /dev/null +++ b/src/main/kotlin/util/MC.kt @@ -0,0 +1,94 @@ +package moe.nea.firmament.util + +import io.github.moulberry.repo.data.Coordinate +import java.util.concurrent.ConcurrentLinkedQueue +import net.minecraft.client.MinecraftClient +import net.minecraft.client.gui.screen.ingame.HandledScreen +import net.minecraft.client.render.WorldRenderer +import net.minecraft.network.packet.c2s.play.CommandExecutionC2SPacket +import net.minecraft.registry.BuiltinRegistries +import net.minecraft.registry.RegistryKeys +import net.minecraft.registry.RegistryWrapper +import net.minecraft.resource.ReloadableResourceManagerImpl +import net.minecraft.text.Text +import net.minecraft.util.math.BlockPos +import moe.nea.firmament.events.TickEvent + +object MC { + + private val messageQueue = ConcurrentLinkedQueue() + + init { + TickEvent.subscribe { + while (true) { + inGameHud.chatHud.addMessage(messageQueue.poll() ?: break) + } + while (true) { + (nextTickTodos.poll() ?: break).invoke() + } + } + } + + fun sendChat(text: Text) { + if (instance.isOnThread) + inGameHud.chatHud.addMessage(text) + else + messageQueue.add(text) + } + + fun sendServerCommand(command: String) { + val nh = player?.networkHandler ?: return + nh.sendPacket( + CommandExecutionC2SPacket( + command, + ) + ) + } + + fun sendServerChat(text: String) { + player?.networkHandler?.sendChatMessage(text) + } + + fun sendCommand(command: String) { + player?.networkHandler?.sendCommand(command) + } + + fun onMainThread(block: () -> Unit) { + if (instance.isOnThread) + block() + else + instance.send(block) + } + + private val nextTickTodos = ConcurrentLinkedQueue<() -> Unit>() + fun nextTick(function: () -> Unit) { + nextTickTodos.add(function) + } + + + inline val resourceManager get() = (instance.resourceManager as ReloadableResourceManagerImpl) + inline val worldRenderer: WorldRenderer get() = instance.worldRenderer + inline val networkHandler get() = player?.networkHandler + inline val instance get() = MinecraftClient.getInstance() + inline val keyboard get() = instance.keyboard + inline val textureManager get() = instance.textureManager + inline val inGameHud get() = instance.inGameHud + inline val font get() = instance.textRenderer + inline val soundManager get() = instance.soundManager + inline val player get() = instance.player + inline val camera get() = instance.cameraEntity + inline val guiAtlasManager get() = instance.guiAtlasManager + inline val world get() = instance.world + inline var screen + get() = instance.currentScreen + set(value) = instance.setScreen(value) + inline val handledScreen: HandledScreen<*>? get() = instance.currentScreen as? HandledScreen<*> + inline val window get() = instance.window + inline val currentRegistries: RegistryWrapper.WrapperLookup? get() = world?.registryManager + val defaultRegistries: RegistryWrapper.WrapperLookup = BuiltinRegistries.createWrapperLookup() + val defaultItems = defaultRegistries.getWrapperOrThrow(RegistryKeys.ITEM) +} + + +val Coordinate.blockPos: BlockPos + get() = BlockPos(x, y, z) diff --git a/src/main/kotlin/util/MinecraftDispatcher.kt b/src/main/kotlin/util/MinecraftDispatcher.kt new file mode 100644 index 0000000..d1f22a9 --- /dev/null +++ b/src/main/kotlin/util/MinecraftDispatcher.kt @@ -0,0 +1,8 @@ + + +package moe.nea.firmament.util + +import kotlinx.coroutines.asCoroutineDispatcher +import net.minecraft.client.MinecraftClient + +val MinecraftDispatcher by lazy { MinecraftClient.getInstance().asCoroutineDispatcher() } diff --git a/src/main/kotlin/util/MoulConfigFragment.kt b/src/main/kotlin/util/MoulConfigFragment.kt new file mode 100644 index 0000000..36132cd --- /dev/null +++ b/src/main/kotlin/util/MoulConfigFragment.kt @@ -0,0 +1,44 @@ + + +package moe.nea.firmament.util + +import io.github.notenoughupdates.moulconfig.gui.GuiComponentWrapper +import io.github.notenoughupdates.moulconfig.gui.GuiContext +import io.github.notenoughupdates.moulconfig.gui.GuiImmediateContext +import me.shedaniel.math.Point +import net.minecraft.client.gui.DrawContext + +class MoulConfigFragment( + context: GuiContext, + val position: Point, + val dismiss: () -> Unit +) : GuiComponentWrapper(context) { + init { + this.init(MC.instance, MC.screen!!.width, MC.screen!!.height) + } + + override fun createContext(drawContext: DrawContext?): GuiImmediateContext { + val oldContext = super.createContext(drawContext) + return oldContext.translated( + position.x, + position.y, + context.root.width, + context.root.height, + ) + } + + + override fun render(drawContext: DrawContext?, i: Int, j: Int, f: Float) { + val ctx = createContext(drawContext) + val m = drawContext!!.matrices + m.push() + m.translate(position.x.toFloat(), position.y.toFloat(), 0F) + context.root.render(ctx) + m.pop() + ctx.renderContext.doDrawTooltip() + } + + override fun close() { + dismiss() + } +} diff --git a/src/main/kotlin/util/MoulConfigUtils.kt b/src/main/kotlin/util/MoulConfigUtils.kt new file mode 100644 index 0000000..00561d1 --- /dev/null +++ b/src/main/kotlin/util/MoulConfigUtils.kt @@ -0,0 +1,230 @@ + + +package moe.nea.firmament.util + +import io.github.notenoughupdates.moulconfig.common.MyResourceLocation +import io.github.notenoughupdates.moulconfig.gui.CloseEventListener +import io.github.notenoughupdates.moulconfig.gui.GuiComponentWrapper +import io.github.notenoughupdates.moulconfig.gui.GuiContext +import io.github.notenoughupdates.moulconfig.observer.GetSetter +import io.github.notenoughupdates.moulconfig.xml.ChildCount +import io.github.notenoughupdates.moulconfig.xml.XMLContext +import io.github.notenoughupdates.moulconfig.xml.XMLGuiLoader +import io.github.notenoughupdates.moulconfig.xml.XMLUniverse +import io.github.notenoughupdates.moulconfig.xml.XSDGenerator +import java.io.File +import java.util.function.Supplier +import javax.xml.namespace.QName +import me.shedaniel.math.Color +import org.w3c.dom.Element +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds +import net.minecraft.client.gui.screen.Screen +import moe.nea.firmament.gui.BarComponent +import moe.nea.firmament.gui.FirmButtonComponent +import moe.nea.firmament.gui.FirmHoverComponent +import moe.nea.firmament.gui.FixedComponent +import moe.nea.firmament.gui.ImageComponent +import moe.nea.firmament.gui.TickComponent + +object MoulConfigUtils { + val firmUrl = "http://firmament.nea.moe/moulconfig" + val universe = XMLUniverse.getDefaultUniverse().also { uni -> + uni.registerMapper(java.awt.Color::class.java) { + if (it.startsWith("#")) { + val hexString = it.substring(1) + val hex = hexString.toInt(16) + if (hexString.length == 6) { + return@registerMapper java.awt.Color(hex) + } + if (hexString.length == 8) { + return@registerMapper java.awt.Color(hex, true) + } + error("Hexcolor $it needs to be exactly 6 or 8 hex digits long") + } + return@registerMapper java.awt.Color(it.toInt(), true) + } + uni.registerMapper(Color::class.java) { + val color = uni.mapXMLObject(it, java.awt.Color::class.java) + Color.ofRGBA(color.red, color.green, color.blue, color.alpha) + } + uni.registerLoader(object : XMLGuiLoader.Basic { + override fun getName(): QName { + return QName(firmUrl, "Bar") + } + + override fun createInstance(context: XMLContext<*>, element: Element): BarComponent { + return BarComponent( + context.getPropertyFromAttribute(element, QName("progress"), Double::class.java)!!, + context.getPropertyFromAttribute(element, QName("total"), Double::class.java)!!, + context.getPropertyFromAttribute(element, QName("fillColor"), Color::class.java)!!.get(), + context.getPropertyFromAttribute(element, QName("emptyColor"), Color::class.java)!!.get(), + ) + } + + override fun getChildCount(): ChildCount { + return ChildCount.NONE + } + + override fun getAttributeNames(): Map { + return mapOf("progress" to true, "total" to true, "emptyColor" to true, "fillColor" to true) + } + }) + uni.registerLoader(object : XMLGuiLoader.Basic { + override fun createInstance(context: XMLContext<*>, element: Element): FirmHoverComponent { + return FirmHoverComponent( + context.getChildFragment(element), + context.getPropertyFromAttribute(element, QName("lines"), List::class.java) as Supplier>, + context.getPropertyFromAttribute(element, QName("delay"), Duration::class.java, 0.6.seconds), + ) + } + + override fun getName(): QName { + return QName(firmUrl, "Hover") + } + + override fun getChildCount(): ChildCount { + return ChildCount.ONE + } + + override fun getAttributeNames(): Map { + return mapOf( + "lines" to true, + "delay" to false, + ) + } + + }) + uni.registerLoader(object : XMLGuiLoader.Basic { + override fun getName(): QName { + return QName(firmUrl, "Button") + } + + override fun createInstance(context: XMLContext<*>, element: Element): FirmButtonComponent { + return FirmButtonComponent( + context.getChildFragment(element), + context.getPropertyFromAttribute(element, QName("enabled"), Boolean::class.java) + ?: GetSetter.constant(true), + context.getPropertyFromAttribute(element, QName("noBackground"), Boolean::class.java, false), + context.getMethodFromAttribute(element, QName("onClick")), + ) + } + + override fun getChildCount(): ChildCount { + return ChildCount.ONE + } + + override fun getAttributeNames(): Map { + return mapOf("onClick" to true, "enabled" to false, "noBackground" to false) + } + }) + uni.registerLoader(object : XMLGuiLoader.Basic { + override fun createInstance(context: XMLContext<*>, element: Element): ImageComponent { + return ImageComponent( + context.getPropertyFromAttribute(element, QName("width"), Int::class.java)!!.get(), + context.getPropertyFromAttribute(element, QName("height"), Int::class.java)!!.get(), + context.getPropertyFromAttribute(element, QName("resource"), MyResourceLocation::class.java)!!, + context.getPropertyFromAttribute(element, QName("u1"), Float::class.java, 0f), + context.getPropertyFromAttribute(element, QName("u2"), Float::class.java, 1f), + context.getPropertyFromAttribute(element, QName("v1"), Float::class.java, 0f), + context.getPropertyFromAttribute(element, QName("v2"), Float::class.java, 1f), + ) + } + + override fun getName(): QName { + return QName(firmUrl, "Image") + } + + override fun getChildCount(): ChildCount { + return ChildCount.NONE + } + + override fun getAttributeNames(): Map { + return mapOf( + "width" to true, "height" to true, + "resource" to true, + "u1" to false, + "u2" to false, + "v1" to false, + "v2" to false, + ) + } + }) + uni.registerLoader(object : XMLGuiLoader.Basic { + override fun createInstance(context: XMLContext<*>, element: Element): TickComponent { + return TickComponent(context.getMethodFromAttribute(element, QName("tick"))) + } + + override fun getName(): QName { + return QName(firmUrl, "Tick") + } + + override fun getChildCount(): ChildCount { + return ChildCount.NONE + } + + override fun getAttributeNames(): Map { + return mapOf("tick" to true) + } + }) + uni.registerLoader(object : XMLGuiLoader.Basic { + override fun createInstance(context: XMLContext<*>, element: Element): FixedComponent { + return FixedComponent( + context.getPropertyFromAttribute(element, QName("width"), Int::class.java) + ?: error("Requires width specified"), + context.getPropertyFromAttribute(element, QName("height"), Int::class.java) + ?: error("Requires height specified"), + context.getChildFragment(element) + ) + } + + override fun getName(): QName { + return QName(firmUrl, "Fixed") + } + + override fun getChildCount(): ChildCount { + return ChildCount.ONE + } + + override fun getAttributeNames(): Map { + return mapOf("width" to true, "height" to true) + } + }) + } + + fun generateXSD( + file: File, + namespace: String + ) { + val generator = XSDGenerator(universe, namespace) + generator.writeAll() + generator.dumpToFile(file) + } + + @JvmStatic + fun main(args: Array) { + generateXSD(File("MoulConfig.xsd"), XMLUniverse.MOULCONFIG_XML_NS) + generateXSD(File("MoulConfig.Firmament.xsd"), firmUrl) + File("wrapper.xsd").writeText(""" + + + + + + """.trimIndent()) + } + + fun loadScreen(name: String, bindTo: Any, parent: Screen?): Screen { + return object : GuiComponentWrapper(loadGui(name, bindTo)) { + override fun close() { + if (context.onBeforeClose() == CloseEventListener.CloseAction.NO_OBJECTIONS_TO_CLOSE) { + client!!.setScreen(parent) + } + } + } + } + + fun loadGui(name: String, bindTo: Any): GuiContext { + return GuiContext(universe.load(bindTo, MyResourceLocation("firmament", "gui/$name.xml"))) + } +} diff --git a/src/main/kotlin/util/MutableMapWithMaxSize.kt b/src/main/kotlin/util/MutableMapWithMaxSize.kt new file mode 100644 index 0000000..067e652 --- /dev/null +++ b/src/main/kotlin/util/MutableMapWithMaxSize.kt @@ -0,0 +1,38 @@ + +package moe.nea.firmament.util + +fun mutableMapWithMaxSize(maxSize: Int): MutableMap = object : LinkedHashMap() { + override fun removeEldestEntry(eldest: MutableMap.MutableEntry): Boolean { + return size > maxSize + } +} + +fun ((T) -> R).memoizeIdentity(maxCacheSize: Int): (T) -> R { + val memoized = { it: IdentityCharacteristics -> + this(it.value) + }.memoize(maxCacheSize) + return { memoized(IdentityCharacteristics(it)) } +} + +@PublishedApi +internal val SENTINEL_NULL = java.lang.Object() + +/** + * Requires the map to only contain values of type [R] or [SENTINEL_NULL]. This is ensured if the map is only ever + * accessed via this function. + */ +inline fun MutableMap.computeNullableFunction(key: T, crossinline func: () -> R): R { + val value = this.getOrPut(key) { + func() ?: SENTINEL_NULL + } + @Suppress("UNCHECKED_CAST") + return if (value === SENTINEL_NULL) null as R + else value as R +} + +fun ((T) -> R).memoize(maxCacheSize: Int): (T) -> R { + val map = mutableMapWithMaxSize(maxCacheSize) + return { + map.computeNullableFunction(it) { this@memoize(it) } + } +} diff --git a/src/main/kotlin/util/SBData.kt b/src/main/kotlin/util/SBData.kt new file mode 100644 index 0000000..b30c6fb --- /dev/null +++ b/src/main/kotlin/util/SBData.kt @@ -0,0 +1,66 @@ +package moe.nea.firmament.util + +import java.util.UUID +import net.hypixel.modapi.HypixelModAPI +import net.hypixel.modapi.packet.impl.clientbound.event.ClientboundLocationPacket +import kotlin.jvm.optionals.getOrNull +import kotlin.time.Duration.Companion.seconds +import moe.nea.firmament.events.AllowChatEvent +import moe.nea.firmament.events.ProcessChatEvent +import moe.nea.firmament.events.ServerConnectedEvent +import moe.nea.firmament.events.SkyblockServerUpdateEvent +import moe.nea.firmament.events.WorldReadyEvent + +object SBData { + private val profileRegex = "Profile ID: ([a-z0-9\\-]+)".toRegex() + val profileSuggestTexts = listOf( + "CLICK THIS TO SUGGEST IT IN CHAT [DASHES]", + "CLICK THIS TO SUGGEST IT IN CHAT [NO DASHES]", + ) + var profileId: UUID? = null + + private var hasReceivedProfile = false + var locraw: Locraw? = null + val skyblockLocation: SkyBlockIsland? get() = locraw?.skyblockLocation + val hasValidLocraw get() = locraw?.server !in listOf("limbo", null) + val isOnSkyblock get() = locraw?.gametype == "SKYBLOCK" + var lastProfileIdRequest = TimeMark.farPast() + fun init() { + ServerConnectedEvent.subscribe { + HypixelModAPI.getInstance().subscribeToEventPacket(ClientboundLocationPacket::class.java) + } + HypixelModAPI.getInstance().createHandler(ClientboundLocationPacket::class.java) { + MC.onMainThread { + val lastLocraw = locraw + locraw = Locraw(it.serverName, + it.serverType.getOrNull()?.name?.uppercase(), + it.mode.getOrNull(), + it.map.getOrNull()) + SkyblockServerUpdateEvent.publish(SkyblockServerUpdateEvent(lastLocraw, null)) + } + } + SkyblockServerUpdateEvent.subscribe { + if (!hasReceivedProfile && isOnSkyblock && lastProfileIdRequest.passedTime() > 30.seconds) { + lastProfileIdRequest = TimeMark.now() + MC.sendServerCommand("profileid") + } + } + AllowChatEvent.subscribe { event -> + if (event.unformattedString in profileSuggestTexts && lastProfileIdRequest.passedTime() < 5.seconds) { + event.cancel() + } + } + ProcessChatEvent.subscribe(receivesCancelled = true) { event -> + val profileMatch = profileRegex.matchEntire(event.unformattedString) + if (profileMatch != null) { + try { + profileId = UUID.fromString(profileMatch.groupValues[1]) + hasReceivedProfile = true + } catch (e: IllegalArgumentException) { + profileId = null + e.printStackTrace() + } + } + } + } +} diff --git a/src/main/kotlin/util/ScoreboardUtil.kt b/src/main/kotlin/util/ScoreboardUtil.kt new file mode 100644 index 0000000..4311971 --- /dev/null +++ b/src/main/kotlin/util/ScoreboardUtil.kt @@ -0,0 +1,45 @@ + + +package moe.nea.firmament.util + +import java.util.* +import net.minecraft.client.gui.hud.InGameHud +import net.minecraft.scoreboard.ScoreboardDisplaySlot +import net.minecraft.scoreboard.Team +import net.minecraft.text.StringVisitable +import net.minecraft.text.Style +import net.minecraft.text.Text +import net.minecraft.util.Formatting + +fun getScoreboardLines(): List { + val scoreboard = MC.player?.scoreboard ?: return listOf() + val activeObjective = scoreboard.getObjectiveForSlot(ScoreboardDisplaySlot.SIDEBAR) ?: return listOf() + return scoreboard.getScoreboardEntries(activeObjective) + .filter { !it.hidden() } + .sortedWith(InGameHud.SCOREBOARD_ENTRY_COMPARATOR) + .take(15).map { + val team = scoreboard.getScoreHolderTeam(it.owner) + val text = it.name() + Team.decorateName(team, text) + } +} + + +fun Text.formattedString(): String { + val sb = StringBuilder() + visit(StringVisitable.StyledVisitor { style, string -> + val c = Formatting.byName(style.color?.name) + if (c != null) { + sb.append("§${c.code}") + } + if (style.isUnderlined) { + sb.append("§n") + } + if (style.isBold) { + sb.append("§l") + } + sb.append(string) + Optional.empty() + }, Style.EMPTY) + return sb.toString().replace("§[^a-f0-9]".toRegex(), "") +} diff --git a/src/main/kotlin/util/ScreenUtil.kt b/src/main/kotlin/util/ScreenUtil.kt new file mode 100644 index 0000000..99d77fb --- /dev/null +++ b/src/main/kotlin/util/ScreenUtil.kt @@ -0,0 +1,38 @@ + + +package moe.nea.firmament.util + +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents +import net.minecraft.client.MinecraftClient +import net.minecraft.client.gui.screen.Screen +import moe.nea.firmament.Firmament + +object ScreenUtil { + init { + ClientTickEvents.START_CLIENT_TICK.register(::onTick) + } + + private fun onTick(minecraft: MinecraftClient) { + if (nextOpenedGui != null) { + val p = minecraft.player + if (p?.currentScreenHandler != null) { + p.closeHandledScreen() + } + minecraft.setScreen(nextOpenedGui) + nextOpenedGui = null + } + } + + private var nextOpenedGui: Screen? = null + + fun setScreenLater(nextScreen: Screen?) { + val nog = nextOpenedGui + if (nog != null) { + Firmament.logger.warn("Setting screen ${if (nextScreen == null) "null" else nextScreen::class.qualifiedName} to be opened later, but ${nog::class.qualifiedName} is already queued.") + return + } + nextOpenedGui = nextScreen + } + + +} diff --git a/src/main/kotlin/util/SequenceUtil.kt b/src/main/kotlin/util/SequenceUtil.kt new file mode 100644 index 0000000..7b5bad0 --- /dev/null +++ b/src/main/kotlin/util/SequenceUtil.kt @@ -0,0 +1,11 @@ + + +package moe.nea.firmament.util + +fun T.iterate(iterator: (T) -> T?): Sequence = sequence { + var x: T? = this@iterate + while (x != null) { + yield(x) + x = iterator(x) + } +} diff --git a/src/main/kotlin/util/SkyBlockIsland.kt b/src/main/kotlin/util/SkyBlockIsland.kt new file mode 100644 index 0000000..bd0567d --- /dev/null +++ b/src/main/kotlin/util/SkyBlockIsland.kt @@ -0,0 +1,42 @@ + +package moe.nea.firmament.util + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import moe.nea.firmament.repo.RepoManager + +@Serializable(with = SkyBlockIsland.Serializer::class) +class SkyBlockIsland +private constructor( + val locrawMode: String, +) { + + object Serializer : KSerializer { + override val descriptor: SerialDescriptor + get() = PrimitiveSerialDescriptor("SkyBlockIsland", PrimitiveKind.STRING) + + override fun deserialize(decoder: Decoder): SkyBlockIsland { + return forMode(decoder.decodeString()) + } + + override fun serialize(encoder: Encoder, value: SkyBlockIsland) { + encoder.encodeString(value.locrawMode) + } + } + companion object { + private val allIslands = mutableMapOf() + fun forMode(mode: String): SkyBlockIsland = allIslands.computeIfAbsent(mode, ::SkyBlockIsland) + val HUB = forMode("hub") + val PRIVATE_ISLAND = forMode("dynamic") + val RIFT = forMode("rift") + } + + val userFriendlyName + get() = RepoManager.neuRepo.constants.islands.areaNames + .getOrDefault(locrawMode, locrawMode) +} diff --git a/src/main/kotlin/util/SkyblockId.kt b/src/main/kotlin/util/SkyblockId.kt new file mode 100644 index 0000000..59b1d2c --- /dev/null +++ b/src/main/kotlin/util/SkyblockId.kt @@ -0,0 +1,149 @@ + + +@file:UseSerializers(DashlessUUIDSerializer::class) + +package moe.nea.firmament.util + +import io.github.moulberry.repo.data.NEUItem +import io.github.moulberry.repo.data.Rarity +import java.util.UUID +import kotlinx.serialization.Serializable +import kotlinx.serialization.UseSerializers +import kotlinx.serialization.json.Json +import net.minecraft.component.DataComponentTypes +import net.minecraft.component.type.NbtComponent +import net.minecraft.item.ItemStack +import net.minecraft.nbt.NbtCompound +import net.minecraft.util.Identifier +import moe.nea.firmament.repo.set +import moe.nea.firmament.util.json.DashlessUUIDSerializer + +/** + * A skyblock item id, as used by the NEU repo. + * This is not exactly the format used by HyPixel, but is mostly the same. + * Usually this id splits an id used by HyPixel into more sub items. For example `PET` becomes `$PET_ID;$PET_RARITY`, + * with those values extracted from other metadata. + */ +@JvmInline +@Serializable +value class SkyblockId(val neuItem: String) { + val identifier + get() = Identifier.of("skyblockitem", + neuItem.lowercase().replace(";", "__") + .replace(":", "___") + .replace(illlegalPathRegex) { + it.value.toCharArray() + .joinToString("") { "__" + it.code.toString(16).padStart(4, '0') } + }) + + override fun toString(): String { + return neuItem + } + + /** + * A bazaar stock item id, as returned by the HyPixel bazaar api endpoint. + * These are not equivalent to the in-game ids, or the NEU repo ids, and in fact, do not refer to items, but instead + * to bazaar stocks. The main difference from [SkyblockId]s is concerning enchanted books. There are probably more, + * but for now this holds. + */ + @JvmInline + @Serializable + value class BazaarStock(val bazaarId: String) { + fun toRepoId(): SkyblockId { + bazaarEnchantmentRegex.matchEntire(bazaarId)?.let { + return SkyblockId("${it.groupValues[1]};${it.groupValues[2]}") + } + return SkyblockId(bazaarId.replace(":", "-")) + } + } + + companion object { + val COINS: SkyblockId = SkyblockId("SKYBLOCK_COIN") + private val bazaarEnchantmentRegex = "ENCHANTMENT_(\\D*)_(\\d+)".toRegex() + val NULL: SkyblockId = SkyblockId("null") + val PET_NULL: SkyblockId = SkyblockId("null_pet") + private val illlegalPathRegex = "[^a-z0-9_.-/]".toRegex() + } +} + +val NEUItem.skyblockId get() = SkyblockId(skyblockItemId) + +@Serializable +data class HypixelPetInfo( + val type: String, + val tier: Rarity, + val exp: Double = 0.0, + val candyUsed: Int = 0, + val uuid: UUID? = null, +) { + val skyblockId get() = SkyblockId("${type.uppercase()};${tier.ordinal}") +} + +private val jsonparser = Json { ignoreUnknownKeys = true } + +val ItemStack.extraAttributes: NbtCompound + get() { + val customData = get(DataComponentTypes.CUSTOM_DATA) ?: run { + val component = NbtComponent.of(NbtCompound()) + set(DataComponentTypes.CUSTOM_DATA, component) + component + } + return customData.nbt + } + +val ItemStack.skyblockUUIDString: String? + get() = extraAttributes.getString("uuid")?.takeIf { it.isNotBlank() } + +val ItemStack.skyblockUUID: UUID? + get() = skyblockUUIDString?.let { UUID.fromString(it) } + +val ItemStack.petData: HypixelPetInfo? + get() { + val jsonString = extraAttributes.getString("petInfo") + if (jsonString.isNullOrBlank()) return null + return runCatching { jsonparser.decodeFromString(jsonString) } + .getOrElse { return null } + } + +fun ItemStack.setSkyBlockFirmamentUiId(uiId: String) = setSkyBlockId(SkyblockId("FIRMAMENT_UI_$uiId")) +fun ItemStack.setSkyBlockId(skyblockId: SkyblockId): ItemStack { + this.extraAttributes["id"] = skyblockId.neuItem + return this +} + +val ItemStack.skyBlockId: SkyblockId? + get() { + return when (val id = extraAttributes.getString("id")) { + "" -> { + null + } + + "PET" -> { + petData?.skyblockId ?: SkyblockId.PET_NULL + } + + "RUNE", "UNIQUE_RUNE" -> { + val runeData = extraAttributes.getCompound("runes") + val runeKind = runeData.keys.singleOrNull() + if (runeKind == null) SkyblockId("RUNE") + else SkyblockId("${runeKind.uppercase()}_RUNE;${runeData.getInt(runeKind)}") + } + + "ABICASE" -> { + SkyblockId("ABICASE_${extraAttributes.getString("model").uppercase()}") + } + + "ENCHANTED_BOOK" -> { + val enchantmentData = extraAttributes.getCompound("enchantments") + val enchantName = enchantmentData.keys.singleOrNull() + if (enchantName == null) SkyblockId("ENCHANTED_BOOK") + else SkyblockId("${enchantName.uppercase()};${enchantmentData.getInt(enchantName)}") + } + + // TODO: PARTY_HAT_CRAB{,_ANIMATED,_SLOTH},POTION + else -> { + SkyblockId(id) + } + } + } + diff --git a/src/main/kotlin/util/SortedMapSerializer.kt b/src/main/kotlin/util/SortedMapSerializer.kt new file mode 100644 index 0000000..baa10ad --- /dev/null +++ b/src/main/kotlin/util/SortedMapSerializer.kt @@ -0,0 +1,25 @@ + + +package moe.nea.firmament.util + +import java.util.SortedMap +import kotlinx.serialization.KSerializer +import kotlinx.serialization.builtins.MapSerializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder + +class SortedMapSerializer, V>(val keyDelegate: KSerializer, val valueDelegate: KSerializer) : + KSerializer> { + val mapSerializer = MapSerializer(keyDelegate, valueDelegate) + override val descriptor: SerialDescriptor + get() = mapSerializer.descriptor + + override fun deserialize(decoder: Decoder): SortedMap { + return (mapSerializer.deserialize(decoder).toSortedMap(Comparator.naturalOrder())) + } + + override fun serialize(encoder: Encoder, value: SortedMap) { + mapSerializer.serialize(encoder, value) + } +} diff --git a/src/main/kotlin/util/TemplateUtil.kt b/src/main/kotlin/util/TemplateUtil.kt new file mode 100644 index 0000000..11100e9 --- /dev/null +++ b/src/main/kotlin/util/TemplateUtil.kt @@ -0,0 +1,85 @@ + + +package moe.nea.firmament.util + +import java.util.* +import kotlinx.serialization.DeserializationStrategy +import kotlinx.serialization.SerializationStrategy +import kotlinx.serialization.serializer +import moe.nea.firmament.Firmament + +object TemplateUtil { + + @JvmStatic + fun getTemplatePrefix(data: String): String? { + val decoded = maybeFromBase64Encoded(data) ?: return null + return decoded.replaceAfter("/", "", "").ifBlank { null } + } + + @JvmStatic + fun intoBase64Encoded(raw: String): String { + return Base64.getEncoder().encodeToString(raw.encodeToByteArray()) + } + + private val base64Alphabet = charArrayOf( + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', '=' + ) + + @JvmStatic + fun maybeFromBase64Encoded(raw: String): String? { + val raw = raw.trim() + if (raw.any { it !in base64Alphabet }) { + return null + } + return try { + Base64.getDecoder().decode(raw).decodeToString() + } catch (ex: Exception) { + null + } + } + + + /** + * Returns a base64 encoded string, truncated such that for all `x`, `x.startsWith(prefix)` implies + * `base64Encoded(x).startsWith(getPrefixComparisonSafeBase64Encoding(prefix))` + * (however, the inverse may not always be true). + */ + @JvmStatic + fun getPrefixComparisonSafeBase64Encoding(prefix: String): String { + val rawEncoded = + Base64.getEncoder().encodeToString(prefix.encodeToByteArray()) + .replace("=", "") + return rawEncoded.substring(0, rawEncoded.length - rawEncoded.length % 4) + } + + inline fun encodeTemplate(sharePrefix: String, data: T): String = + encodeTemplate(sharePrefix, data, serializer()) + + fun encodeTemplate(sharePrefix: String, data: T, serializer: SerializationStrategy): String { + require(sharePrefix.endsWith("/")) + return intoBase64Encoded(sharePrefix + Firmament.json.encodeToString(serializer, data)) + } + + inline fun maybeDecodeTemplate(sharePrefix: String, data: String): T? = + maybeDecodeTemplate(sharePrefix, data, serializer()) + + fun maybeDecodeTemplate(sharePrefix: String, data: String, serializer: DeserializationStrategy): T? { + require(sharePrefix.endsWith("/")) + val data = data.trim() + if (!data.startsWith(getPrefixComparisonSafeBase64Encoding(sharePrefix))) + return null + val decoded = maybeFromBase64Encoded(data) ?: return null + if (!decoded.startsWith(sharePrefix)) + return null + return try { + Firmament.json.decodeFromString(serializer, decoded.substring(sharePrefix.length)) + } catch (e: Exception) { + null + } + } + +} diff --git a/src/main/kotlin/util/TimeMark.kt b/src/main/kotlin/util/TimeMark.kt new file mode 100644 index 0000000..1264212 --- /dev/null +++ b/src/main/kotlin/util/TimeMark.kt @@ -0,0 +1,44 @@ + + +package moe.nea.firmament.util + +import kotlin.time.Duration +import kotlin.time.Duration.Companion.milliseconds + +class TimeMark private constructor(private val timeMark: Long) : Comparable { + fun passedTime() = if (timeMark == 0L) Duration.INFINITE else (System.currentTimeMillis() - timeMark).milliseconds + + operator fun minus(other: TimeMark): Duration { + if (other.timeMark == timeMark) + return 0.milliseconds + if (other.timeMark == 0L) + return Duration.INFINITE + if (timeMark == 0L) + return -Duration.INFINITE + return (timeMark - other.timeMark).milliseconds + } + + companion object { + fun now() = TimeMark(System.currentTimeMillis()) + fun farPast() = TimeMark(0L) + fun ago(timeDelta: Duration): TimeMark { + if (timeDelta.isFinite()) { + return TimeMark(System.currentTimeMillis() - timeDelta.inWholeMilliseconds) + } + require(timeDelta.isPositive()) + return farPast() + } + } + + override fun hashCode(): Int { + return timeMark.hashCode() + } + + override fun equals(other: Any?): Boolean { + return other is TimeMark && other.timeMark == timeMark + } + + override fun compareTo(other: TimeMark): Int { + return this.timeMark.compareTo(other.timeMark) + } +} diff --git a/src/main/kotlin/util/Timer.kt b/src/main/kotlin/util/Timer.kt new file mode 100644 index 0000000..6e9b467 --- /dev/null +++ b/src/main/kotlin/util/Timer.kt @@ -0,0 +1,25 @@ + + +package moe.nea.firmament.util + +import kotlin.time.Duration +import kotlin.time.ExperimentalTime +import kotlin.time.TimeSource + +@OptIn(ExperimentalTime::class) +class Timer { + private var mark: TimeSource.Monotonic.ValueTimeMark? = null + + fun timePassed(): Duration { + return mark?.elapsedNow() ?: Duration.INFINITE + } + + fun markNow() { + mark = TimeSource.Monotonic.markNow() + } + + fun markFarPast() { + mark = null + } + +} diff --git a/src/main/kotlin/util/WarpUtil.kt b/src/main/kotlin/util/WarpUtil.kt new file mode 100644 index 0000000..8fca6f3 --- /dev/null +++ b/src/main/kotlin/util/WarpUtil.kt @@ -0,0 +1,75 @@ + +package moe.nea.firmament.util + +import io.github.moulberry.repo.constants.Islands +import io.github.moulberry.repo.constants.Islands.Warp +import kotlinx.serialization.Serializable +import kotlinx.serialization.serializer +import kotlin.math.sqrt +import kotlin.time.Duration.Companion.seconds +import net.minecraft.text.Text +import net.minecraft.util.math.Position +import moe.nea.firmament.events.ProcessChatEvent +import moe.nea.firmament.repo.RepoManager +import moe.nea.firmament.util.data.ProfileSpecificDataHolder + +object WarpUtil { + val warps: List get() = RepoManager.neuRepo.constants.islands.warps + + @Serializable + data class Data( + val excludedWarps: MutableSet = mutableSetOf(), + ) + + object DConfig : ProfileSpecificDataHolder(serializer(), "warp-util", ::Data) + + private var lastAttemptedWarp = "" + private var lastWarpAttempt = TimeMark.farPast() + fun findNearestWarp(island: SkyBlockIsland, pos: Position): Islands.Warp? { + return warps.asSequence().filter { it.mode == island.locrawMode }.minByOrNull { + if (DConfig.data?.excludedWarps?.contains(it.warp) == true) { + return@minByOrNull Double.MAX_VALUE + } else { + return@minByOrNull squaredDist(pos, it) + } + } + } + + private fun squaredDist(pos: Position, warp: Warp): Double { + val dx = pos.x - warp.x + val dy = pos.y - warp.y + val dz = pos.z - warp.z + return dx * dx + dy * dy + dz * dz + } + + fun teleportToNearestWarp(island: SkyBlockIsland, pos: Position) { + val nearestWarp = findNearestWarp(island, pos) + if (nearestWarp == null) { + MC.sendChat(Text.literal("Could not find an unlocked warp in ${island.userFriendlyName}")) + return + } + if (island == SBData.skyblockLocation + && sqrt(squaredDist(pos, nearestWarp)) > 1.1 * sqrt(squaredDist((MC.player ?: return).pos, nearestWarp)) + ) { + return + } + MC.sendServerCommand("warp ${nearestWarp.warp}") + } + + init { + ProcessChatEvent.subscribe { + if (it.unformattedString == "You haven't unlocked this fast travel destination!" + && lastWarpAttempt.passedTime() < 2.seconds + ) { + DConfig.data?.excludedWarps?.add(lastAttemptedWarp) + DConfig.markDirty() + MC.sendChat(Text.stringifiedTranslatable("firmament.warp-util.mark-excluded", lastAttemptedWarp)) + lastWarpAttempt = TimeMark.farPast() + } + if (it.unformattedString == "You may now fast travel to") { + DConfig.data?.excludedWarps?.clear() + DConfig.markDirty() + } + } + } +} diff --git a/src/main/kotlin/util/assertions.kt b/src/main/kotlin/util/assertions.kt new file mode 100644 index 0000000..6f2ed19 --- /dev/null +++ b/src/main/kotlin/util/assertions.kt @@ -0,0 +1,25 @@ + + +package moe.nea.firmament.util + +/** + * Less aggressive version of `require(obj != null)`, which fails in devenv but continues at runtime. + */ +inline fun assertNotNullOr(obj: T?, message: String? = null, block: () -> T): T { + if (message == null) + assert(obj != null) + else + assert(obj != null) { message } + return obj ?: block() +} + + +/** + * Less aggressive version of `require(condition)`, which fails in devenv but continues at runtime. + */ +inline fun assertTrueOr(condition: Boolean, block: () -> Unit) { + assert(condition) + if (!condition) block() +} + + diff --git a/src/main/kotlin/util/async/input.kt b/src/main/kotlin/util/async/input.kt new file mode 100644 index 0000000..9aab5cf --- /dev/null +++ b/src/main/kotlin/util/async/input.kt @@ -0,0 +1,47 @@ + + +package moe.nea.firmament.util.async + +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlin.coroutines.resume +import moe.nea.firmament.events.HandledScreenKeyPressedEvent +import moe.nea.firmament.keybindings.IKeyBinding + +private object InputHandler { + data class KeyInputContinuation(val keybind: IKeyBinding, val onContinue: () -> Unit) + + private val activeContinuations = mutableListOf() + + fun registerContinuation(keyInputContinuation: KeyInputContinuation): () -> Unit { + synchronized(InputHandler) { + activeContinuations.add(keyInputContinuation) + } + return { + synchronized(this) { + activeContinuations.remove(keyInputContinuation) + } + } + } + + init { + HandledScreenKeyPressedEvent.subscribe { event -> + synchronized(InputHandler) { + val toRemove = activeContinuations.filter { + event.matches(it.keybind) + } + toRemove.forEach { it.onContinue() } + activeContinuations.removeAll(toRemove) + } + } + } +} + +suspend fun waitForInput(keybind: IKeyBinding): Unit = suspendCancellableCoroutine { cont -> + val unregister = + InputHandler.registerContinuation(InputHandler.KeyInputContinuation(keybind) { cont.resume(Unit) }) + cont.invokeOnCancellation { + unregister() + } +} + + diff --git a/src/main/kotlin/util/colorconversion.kt b/src/main/kotlin/util/colorconversion.kt new file mode 100644 index 0000000..d7a5dad --- /dev/null +++ b/src/main/kotlin/util/colorconversion.kt @@ -0,0 +1,13 @@ + + +package moe.nea.firmament.util + +import net.minecraft.text.TextColor +import net.minecraft.util.DyeColor + +fun DyeColor.toShedaniel(): me.shedaniel.math.Color = + me.shedaniel.math.Color.ofOpaque(this.signColor) + +fun DyeColor.toTextColor(): TextColor = + TextColor.fromRgb(this.signColor) + diff --git a/src/main/kotlin/util/customgui/CoordRememberingSlot.kt b/src/main/kotlin/util/customgui/CoordRememberingSlot.kt new file mode 100644 index 0000000..c61c711 --- /dev/null +++ b/src/main/kotlin/util/customgui/CoordRememberingSlot.kt @@ -0,0 +1,14 @@ + +package moe.nea.firmament.util.customgui + +import net.minecraft.screen.slot.Slot + +interface CoordRememberingSlot { + fun rememberCoords_firmament() + fun restoreCoords_firmament() + fun getOriginalX_firmament(): Int + fun getOriginalY_firmament(): Int +} + +val Slot.originalX get() = (this as CoordRememberingSlot).getOriginalX_firmament() +val Slot.originalY get() = (this as CoordRememberingSlot).getOriginalY_firmament() diff --git a/src/main/kotlin/util/customgui/CustomGui.kt b/src/main/kotlin/util/customgui/CustomGui.kt new file mode 100644 index 0000000..f9094b2 --- /dev/null +++ b/src/main/kotlin/util/customgui/CustomGui.kt @@ -0,0 +1,72 @@ + +package moe.nea.firmament.util.customgui + +import me.shedaniel.math.Rectangle +import net.minecraft.client.gui.DrawContext +import net.minecraft.screen.slot.Slot +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.events.HandledScreenPushREIEvent + +abstract class CustomGui { + + abstract fun getBounds(): List + + open fun moveSlot(slot: Slot) { + // TODO: return a Pair maybe? worth an investigation + } + + companion object { + @Subscribe + fun onExclusionZone(event: HandledScreenPushREIEvent) { + val customGui = event.screen.customGui ?: return + event.rectangles.addAll(customGui.getBounds()) + } + } + + open fun render( + drawContext: DrawContext, + delta: Float, + mouseX: Int, + mouseY: Int + ) { + } + + open fun mouseClick(mouseX: Double, mouseY: Double, button: Int): Boolean { + return false + } + + open fun afterSlotRender(context: DrawContext, slot: Slot) {} + open fun beforeSlotRender(context: DrawContext, slot: Slot) {} + open fun mouseScrolled(mouseX: Double, mouseY: Double, horizontalAmount: Double, verticalAmount: Double): Boolean { + return false + } + + open fun isClickOutsideBounds(mouseX: Double, mouseY: Double): Boolean { + return getBounds().none { it.contains(mouseX, mouseY) } + } + + open fun isPointWithinBounds( + x: Int, + y: Int, + width: Int, + height: Int, + pointX: Double, + pointY: Double, + ): Boolean { + return getBounds().any { it.contains(pointX, pointY) } && + Rectangle(x, y, width, height).contains(pointX, pointY) + } + + open fun isPointOverSlot(slot: Slot, xOffset: Int, yOffset: Int, pointX: Double, pointY: Double): Boolean { + return isPointWithinBounds(slot.x + xOffset, slot.y + yOffset, 16, 16, pointX, pointY) + } + + open fun onInit() {} + open fun shouldDrawForeground(): Boolean { + return true + } + + open fun onVoluntaryExit(): Boolean { + return true + } +} diff --git a/src/main/kotlin/util/customgui/HasCustomGui.kt b/src/main/kotlin/util/customgui/HasCustomGui.kt new file mode 100644 index 0000000..edead2e --- /dev/null +++ b/src/main/kotlin/util/customgui/HasCustomGui.kt @@ -0,0 +1,17 @@ + +package moe.nea.firmament.util.customgui + +import net.minecraft.client.gui.screen.ingame.HandledScreen + +@Suppress("FunctionName") +interface HasCustomGui { + fun getCustomGui_Firmament(): CustomGui? + fun setCustomGui_Firmament(gui: CustomGui?) +} + +var > T.customGui: CustomGui? + get() = (this as HasCustomGui).getCustomGui_Firmament() + set(value) { + (this as HasCustomGui).setCustomGui_Firmament(value) + } + diff --git a/src/main/kotlin/util/data/DataHolder.kt b/src/main/kotlin/util/data/DataHolder.kt new file mode 100644 index 0000000..21a6014 --- /dev/null +++ b/src/main/kotlin/util/data/DataHolder.kt @@ -0,0 +1,62 @@ + + +package moe.nea.firmament.util.data + +import java.nio.file.Path +import kotlinx.serialization.KSerializer +import kotlin.io.path.exists +import kotlin.io.path.readText +import kotlin.io.path.writeText +import moe.nea.firmament.Firmament + +abstract class DataHolder( + val serializer: KSerializer, + val name: String, + val default: () -> T +) : IDataHolder { + + + final override var data: T + private set + + init { + data = readValueOrDefault() + IDataHolder.putDataHolder(this::class, this) + } + + private val file: Path get() = Firmament.CONFIG_DIR.resolve("$name.json") + + protected fun readValueOrDefault(): T { + if (file.exists()) + try { + return Firmament.json.decodeFromString( + serializer, + file.readText() + ) + } catch (e: Exception) {/* Expecting IOException and SerializationException, but Kotlin doesn't allow multi catches*/ + IDataHolder.badLoads.add(name) + Firmament.logger.error( + "Exception during loading of config file $name. This will reset this config.", + e + ) + } + return default() + } + + private fun writeValue(t: T) { + file.writeText(Firmament.json.encodeToString(serializer, t)) + } + + override fun save() { + writeValue(data) + } + + override fun load() { + data = readValueOrDefault() + } + + override fun markDirty() { + IDataHolder.markDirty(this::class) + } + +} diff --git a/src/main/kotlin/util/data/IDataHolder.kt b/src/main/kotlin/util/data/IDataHolder.kt new file mode 100644 index 0000000..5d09bcd --- /dev/null +++ b/src/main/kotlin/util/data/IDataHolder.kt @@ -0,0 +1,77 @@ + + +package moe.nea.firmament.util.data + +import java.util.concurrent.CopyOnWriteArrayList +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents +import kotlin.reflect.KClass +import net.minecraft.client.MinecraftClient +import net.minecraft.server.command.CommandOutput +import net.minecraft.text.Text +import moe.nea.firmament.Firmament +import moe.nea.firmament.events.ScreenChangeEvent + +interface IDataHolder { + companion object { + internal var badLoads: MutableList = CopyOnWriteArrayList() + private val allConfigs: MutableMap>, IDataHolder<*>> = mutableMapOf() + private val dirty: MutableSet>> = mutableSetOf() + + internal fun , K> putDataHolder(kClass: KClass, inst: IDataHolder) { + allConfigs[kClass] = inst + } + + fun , K> markDirty(kClass: KClass) { + if (kClass !in allConfigs) { + Firmament.logger.error("Tried to markDirty '${kClass.qualifiedName}', which isn't registered as 'IConfigHolder'") + return + } + dirty.add(kClass) + } + + private fun performSaves() { + val toSave = dirty.toList().also { + dirty.clear() + } + for (it in toSave) { + val obj = allConfigs[it] + if (obj == null) { + Firmament.logger.error("Tried to save '${it}', which isn't registered as 'ConfigHolder'") + continue + } + obj.save() + } + } + + private fun warnForResetConfigs(player: CommandOutput) { + if (badLoads.isNotEmpty()) { + player.sendMessage( + Text.literal( + "The following configs have been reset: ${badLoads.joinToString(", ")}. " + + "This can be intentional, but probably isn't." + ) + ) + badLoads.clear() + } + } + + fun registerEvents() { + ScreenChangeEvent.subscribe { event -> + performSaves() + val p = MinecraftClient.getInstance().player + if (p != null) { + warnForResetConfigs(p) + } + } + ClientLifecycleEvents.CLIENT_STOPPING.register(ClientLifecycleEvents.ClientStopping { + performSaves() + }) + } + + } + + val data: T + fun save() + fun markDirty() + fun load() +} diff --git a/src/main/kotlin/util/data/ProfileSpecificDataHolder.kt b/src/main/kotlin/util/data/ProfileSpecificDataHolder.kt new file mode 100644 index 0000000..1cd4f22 --- /dev/null +++ b/src/main/kotlin/util/data/ProfileSpecificDataHolder.kt @@ -0,0 +1,84 @@ + + +package moe.nea.firmament.util.data + +import java.nio.file.Path +import java.util.UUID +import kotlinx.serialization.KSerializer +import kotlin.io.path.createDirectories +import kotlin.io.path.deleteExisting +import kotlin.io.path.exists +import kotlin.io.path.extension +import kotlin.io.path.listDirectoryEntries +import kotlin.io.path.nameWithoutExtension +import kotlin.io.path.readText +import kotlin.io.path.writeText +import moe.nea.firmament.Firmament +import moe.nea.firmament.util.SBData + +abstract class ProfileSpecificDataHolder( + private val dataSerializer: KSerializer, + val configName: String, + private val configDefault: () -> S +) : IDataHolder { + + var allConfigs: MutableMap + + override val data: S? + get() = SBData.profileId?.let { + allConfigs.computeIfAbsent(it) { configDefault() } + } + + init { + allConfigs = readValues() + IDataHolder.putDataHolder(this::class, this) + } + + private val configDirectory: Path get() = Firmament.CONFIG_DIR.resolve("profiles").resolve(configName) + + private fun readValues(): MutableMap { + if (!configDirectory.exists()) { + configDirectory.createDirectories() + } + val profileFiles = configDirectory.listDirectoryEntries() + return profileFiles + .filter { it.extension == "json" } + .mapNotNull { + try { + UUID.fromString(it.nameWithoutExtension) to Firmament.json.decodeFromString(dataSerializer, it.readText()) + } catch (e: Exception) { /* Expecting IOException and SerializationException, but Kotlin doesn't allow multi catches*/ + IDataHolder.badLoads.add(configName) + Firmament.logger.error( + "Exception during loading of profile specific config file $it ($configName). This will reset that profiles config.", + e + ) + null + } + }.toMap().toMutableMap() + } + + override fun save() { + if (!configDirectory.exists()) { + configDirectory.createDirectories() + } + val c = allConfigs + configDirectory.listDirectoryEntries().forEach { + if (it.nameWithoutExtension !in c.mapKeys { it.toString() }) { + it.deleteExisting() + } + } + c.forEach { (name, value) -> + val f = configDirectory.resolve("$name.json") + f.writeText(Firmament.json.encodeToString(dataSerializer, value)) + } + } + + override fun markDirty() { + IDataHolder.markDirty(this::class) + } + + override fun load() { + allConfigs = readValues() + } + +} diff --git a/src/main/kotlin/util/filter/IteratorFilterSet.kt b/src/main/kotlin/util/filter/IteratorFilterSet.kt new file mode 100644 index 0000000..483b8d9 --- /dev/null +++ b/src/main/kotlin/util/filter/IteratorFilterSet.kt @@ -0,0 +1,33 @@ + +package moe.nea.firmament.util.filter + +abstract class IteratorFilterSet(val original: java.util.Set) : java.util.Set by original { + abstract fun shouldKeepElement(element: K): Boolean + + override fun iterator(): MutableIterator { + val parentIterator = original.iterator() + return object : MutableIterator { + var lastEntry: K? = null + override fun hasNext(): Boolean { + while (lastEntry == null) { + if (!parentIterator.hasNext()) + break + val element = parentIterator.next() + if (!shouldKeepElement(element)) continue + lastEntry = element + } + return lastEntry != null + } + + override fun next(): K { + if (!hasNext()) throw NoSuchElementException() + return lastEntry ?: throw NoSuchElementException() + } + + override fun remove() { + TODO("Not yet implemented") + } + } + } +} + diff --git a/src/main/kotlin/util/item/NbtItemData.kt b/src/main/kotlin/util/item/NbtItemData.kt new file mode 100644 index 0000000..f7f259d --- /dev/null +++ b/src/main/kotlin/util/item/NbtItemData.kt @@ -0,0 +1,24 @@ + + +package moe.nea.firmament.util.item + +import net.minecraft.component.DataComponentTypes +import net.minecraft.component.type.LoreComponent +import net.minecraft.item.ItemStack +import net.minecraft.text.Text + +var ItemStack.loreAccordingToNbt + get() = get(DataComponentTypes.LORE)?.lines ?: listOf() + set(value) { + set(DataComponentTypes.LORE, LoreComponent(value)) + } + +var ItemStack.displayNameAccordingToNbt: Text + get() = get(DataComponentTypes.CUSTOM_NAME) ?: get(DataComponentTypes.ITEM_NAME) ?: item.name + set(value) { + set(DataComponentTypes.CUSTOM_NAME, value) + } + +fun ItemStack.setCustomName(text: Text) { + set(DataComponentTypes.CUSTOM_NAME, text) +} diff --git a/src/main/kotlin/util/item/SkullItemData.kt b/src/main/kotlin/util/item/SkullItemData.kt new file mode 100644 index 0000000..ddab88e --- /dev/null +++ b/src/main/kotlin/util/item/SkullItemData.kt @@ -0,0 +1,90 @@ + + +@file:UseSerializers(DashlessUUIDSerializer::class, InstantAsLongSerializer::class) + +package moe.nea.firmament.util.item + +import com.mojang.authlib.GameProfile +import com.mojang.authlib.minecraft.MinecraftProfileTexture +import com.mojang.authlib.properties.Property +import java.util.UUID +import kotlinx.datetime.Clock +import kotlinx.datetime.Instant +import kotlinx.serialization.Serializable +import kotlinx.serialization.UseSerializers +import kotlinx.serialization.encodeToString +import net.minecraft.component.DataComponentTypes +import net.minecraft.component.type.ProfileComponent +import net.minecraft.item.ItemStack +import net.minecraft.item.Items +import moe.nea.firmament.Firmament +import moe.nea.firmament.util.Base64Util.padToValidBase64 +import moe.nea.firmament.util.assertTrueOr +import moe.nea.firmament.util.json.DashlessUUIDSerializer +import moe.nea.firmament.util.json.InstantAsLongSerializer + +@Serializable +data class MinecraftProfileTextureKt( + val url: String, + val metadata: Map = mapOf(), +) + +@Serializable +data class MinecraftTexturesPayloadKt( + val textures: Map = mapOf(), + val profileId: UUID? = null, + val profileName: String? = null, + val isPublic: Boolean = true, + val timestamp: Instant = Clock.System.now(), +) + +fun GameProfile.setTextures(textures: MinecraftTexturesPayloadKt) { + val json = Firmament.json.encodeToString(textures) + val encoded = java.util.Base64.getEncoder().encodeToString(json.encodeToByteArray()) + properties.put(propertyTextures, Property(propertyTextures, encoded)) +} + +private val propertyTextures = "textures" + +fun ItemStack.setEncodedSkullOwner(uuid: UUID, encodedData: String) { + assert(this.item == Items.PLAYER_HEAD) + val gameProfile = GameProfile(uuid, "LameGuy123") + gameProfile.properties.put(propertyTextures, Property(propertyTextures, encodedData.padToValidBase64())) + this.set(DataComponentTypes.PROFILE, ProfileComponent(gameProfile)) +} + +val zeroUUID = UUID.fromString("d3cb85e2-3075-48a1-b213-a9bfb62360c1") +fun createSkullItem(uuid: UUID, url: String) = ItemStack(Items.PLAYER_HEAD) + .also { it.setSkullOwner(uuid, url) } + +fun ItemStack.setSkullOwner(uuid: UUID, url: String) { + assert(this.item == Items.PLAYER_HEAD) + val gameProfile = GameProfile(uuid, "nea89") + gameProfile.setTextures( + MinecraftTexturesPayloadKt( + textures = mapOf(MinecraftProfileTexture.Type.SKIN to MinecraftProfileTextureKt(url)), + profileId = uuid, + profileName = "nea89", + ) + ) + this.set(DataComponentTypes.PROFILE, ProfileComponent(gameProfile)) +} + + +fun decodeProfileTextureProperty(property: Property): MinecraftTexturesPayloadKt? { + assertTrueOr(property.name == propertyTextures) { return null } + return try { + var encodedF: String = property.value + while (encodedF.length % 4 != 0 && encodedF.last() == '=') { + encodedF = encodedF.substring(0, encodedF.length - 1) + } + val json = java.util.Base64.getDecoder().decode(encodedF).decodeToString() + Firmament.json.decodeFromString(json) + } catch (e: Exception) { + // Malformed profile data + if (Firmament.DEBUG) + e.printStackTrace() + null + } +} + diff --git a/src/main/kotlin/util/json/BlockPosSerializer.kt b/src/main/kotlin/util/json/BlockPosSerializer.kt new file mode 100644 index 0000000..144b0a0 --- /dev/null +++ b/src/main/kotlin/util/json/BlockPosSerializer.kt @@ -0,0 +1,25 @@ +package moe.nea.firmament.util.json + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.serializer +import net.minecraft.util.math.BlockPos + +object BlockPosSerializer : KSerializer { + val delegate = serializer>() + + override val descriptor: SerialDescriptor + get() = SerialDescriptor("BlockPos", delegate.descriptor) + + override fun deserialize(decoder: Decoder): BlockPos { + val list = decoder.decodeSerializableValue(delegate) + require(list.size == 3) + return BlockPos(list[0], list[1], list[2]) + } + + override fun serialize(encoder: Encoder, value: BlockPos) { + encoder.encodeSerializableValue(delegate, listOf(value.x, value.y, value.z)) + } +} diff --git a/src/main/kotlin/util/json/DashlessUUIDSerializer.kt b/src/main/kotlin/util/json/DashlessUUIDSerializer.kt new file mode 100644 index 0000000..acb1dc8 --- /dev/null +++ b/src/main/kotlin/util/json/DashlessUUIDSerializer.kt @@ -0,0 +1,29 @@ + + +package moe.nea.firmament.util.json + +import java.util.UUID +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import moe.nea.firmament.util.parseDashlessUUID + +object DashlessUUIDSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("DashlessUUIDSerializer", PrimitiveKind.STRING) + + override fun deserialize(decoder: Decoder): UUID { + val str = decoder.decodeString() + if ("-" in str) { + return UUID.fromString(str) + } + return parseDashlessUUID(str) + } + + override fun serialize(encoder: Encoder, value: UUID) { + encoder.encodeString(value.toString().replace("-", "")) + } +} diff --git a/src/main/kotlin/util/json/InstantAsLongSerializer.kt b/src/main/kotlin/util/json/InstantAsLongSerializer.kt new file mode 100644 index 0000000..ad738dc --- /dev/null +++ b/src/main/kotlin/util/json/InstantAsLongSerializer.kt @@ -0,0 +1,22 @@ + + +package moe.nea.firmament.util.json + +import kotlinx.datetime.Instant +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder + +object InstantAsLongSerializer : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("InstantAsLongSerializer", PrimitiveKind.LONG) + override fun deserialize(decoder: Decoder): Instant { + return Instant.fromEpochMilliseconds(decoder.decodeLong()) + } + + override fun serialize(encoder: Encoder, value: Instant) { + encoder.encodeLong(value.toEpochMilliseconds()) + } +} diff --git a/src/main/kotlin/util/json/SingletonSerializableList.kt b/src/main/kotlin/util/json/SingletonSerializableList.kt new file mode 100644 index 0000000..aa543d6 --- /dev/null +++ b/src/main/kotlin/util/json/SingletonSerializableList.kt @@ -0,0 +1,31 @@ + +package moe.nea.firmament.util.json + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.builtins.ListSerializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonDecoder +import kotlinx.serialization.json.JsonElement + +class SingletonSerializableList(val child: KSerializer) : KSerializer> { + override val descriptor: SerialDescriptor + get() = JsonElement.serializer().descriptor + + override fun deserialize(decoder: Decoder): List { + decoder as JsonDecoder + val list = JsonElement.serializer().deserialize(decoder) + if (list is JsonArray) { + return list.map { + decoder.json.decodeFromJsonElement(child, it) + } + } + return listOf(decoder.json.decodeFromJsonElement(child, list)) + } + + override fun serialize(encoder: Encoder, value: List) { + ListSerializer(child).serialize(encoder, value) + } +} diff --git a/src/main/kotlin/util/listutil.kt b/src/main/kotlin/util/listutil.kt new file mode 100644 index 0000000..73cb23e --- /dev/null +++ b/src/main/kotlin/util/listutil.kt @@ -0,0 +1,9 @@ + +package moe.nea.firmament.util + +fun List.lastNotNullOfOrNull(func: (T) -> R?): R? { + for (i in indices.reversed()) { + return func(this[i]) ?: continue + } + return null +} diff --git a/src/main/kotlin/util/propertyutil.kt b/src/main/kotlin/util/propertyutil.kt new file mode 100644 index 0000000..795a0d2 --- /dev/null +++ b/src/main/kotlin/util/propertyutil.kt @@ -0,0 +1,9 @@ + + +package moe.nea.firmament.util + +import kotlin.properties.ReadOnlyProperty + +fun ReadOnlyProperty.map(mapper: (V) -> M): ReadOnlyProperty { + return ReadOnlyProperty { thisRef, property -> mapper(this@map.getValue(thisRef, property)) } +} diff --git a/src/main/kotlin/util/regex.kt b/src/main/kotlin/util/regex.kt new file mode 100644 index 0000000..3ce5bd8 --- /dev/null +++ b/src/main/kotlin/util/regex.kt @@ -0,0 +1,55 @@ + + +package moe.nea.firmament.util + +import java.util.regex.Matcher +import java.util.regex.Pattern +import org.intellij.lang.annotations.Language +import kotlin.time.Duration +import kotlin.time.Duration.Companion.minutes +import kotlin.time.Duration.Companion.seconds + +inline fun String.ifMatches(regex: Regex, block: (MatchResult) -> T): T? = + regex.matchEntire(this)?.let(block) + +inline fun Pattern.useMatch(string: String, block: Matcher.() -> T): T? = + matcher(string) + .takeIf(Matcher::matches) + ?.let(block) + +@Language("RegExp") +val TIME_PATTERN = "[0-9]+[ms]" + +@Language("RegExp") +val SHORT_NUMBER_FORMAT = "[0-9]+(?:,[0-9]+)*(?:\\.[0-9]+)?[kKmMbB]?" + + +val siScalars = mapOf( + 'k' to 1_000.0, + 'K' to 1_000.0, + 'm' to 1_000_000.0, + 'M' to 1_000_000.0, + 'b' to 1_000_000_000.0, + 'B' to 1_000_000_000.0, +) + +fun parseTimePattern(text: String): Duration { + val length = text.dropLast(1).toInt() + return when (text.last()) { + 'm' -> length.minutes + 's' -> length.seconds + else -> error("Invalid pattern for time $text") + } +} + +fun parseShortNumber(string: String): Double { + var k = string.replace(",", "") + val scalar = k.last() + var scalarMultiplier = siScalars[scalar] + if (scalarMultiplier == null) { + scalarMultiplier = 1.0 + } else { + k = k.dropLast(1) + } + return k.toDouble() * scalarMultiplier +} diff --git a/src/main/kotlin/util/render/FacingThePlayerContext.kt b/src/main/kotlin/util/render/FacingThePlayerContext.kt new file mode 100644 index 0000000..eb37e35 --- /dev/null +++ b/src/main/kotlin/util/render/FacingThePlayerContext.kt @@ -0,0 +1,101 @@ + +package moe.nea.firmament.util.render + +import com.mojang.blaze3d.systems.RenderSystem +import io.github.notenoughupdates.moulconfig.platform.next +import org.joml.Matrix4f +import net.minecraft.client.font.TextRenderer +import net.minecraft.client.render.BufferRenderer +import net.minecraft.client.render.GameRenderer +import net.minecraft.client.render.LightmapTextureManager +import net.minecraft.client.render.RenderLayer +import net.minecraft.client.render.Tessellator +import net.minecraft.client.render.VertexConsumer +import net.minecraft.client.render.VertexFormat +import net.minecraft.client.render.VertexFormats +import net.minecraft.text.Text +import net.minecraft.util.Identifier +import net.minecraft.util.math.BlockPos +import moe.nea.firmament.util.FirmFormatters +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.assertTrueOr + +@RenderContextDSL +class FacingThePlayerContext(val worldContext: RenderInWorldContext) { + val matrixStack by worldContext::matrixStack + fun waypoint(position: BlockPos, label: Text) { + text( + label, + Text.literal("§e${FirmFormatters.formatDistance(MC.player?.pos?.distanceTo(position.toCenterPos()) ?: 42069.0)}") + ) + } + + fun text( + vararg texts: Text, + verticalAlign: RenderInWorldContext.VerticalAlign = RenderInWorldContext.VerticalAlign.CENTER, + background: Int = 0x70808080, + ) { + assertTrueOr(texts.isNotEmpty()) { return@text } + for ((index, text) in texts.withIndex()) { + worldContext.matrixStack.push() + val width = MC.font.getWidth(text) + worldContext.matrixStack.translate(-width / 2F, verticalAlign.align(index, texts.size), 0F) + val vertexConsumer: VertexConsumer = + worldContext.vertexConsumers.getBuffer(RenderLayer.getTextBackgroundSeeThrough()) + val matrix4f = worldContext.matrixStack.peek().positionMatrix + vertexConsumer.vertex(matrix4f, -1.0f, -1.0f, 0.0f).color(background) + .light(LightmapTextureManager.MAX_BLOCK_LIGHT_COORDINATE).next() + vertexConsumer.vertex(matrix4f, -1.0f, MC.font.fontHeight.toFloat(), 0.0f).color(background) + .light(LightmapTextureManager.MAX_BLOCK_LIGHT_COORDINATE).next() + vertexConsumer.vertex(matrix4f, width.toFloat(), MC.font.fontHeight.toFloat(), 0.0f) + .color(background) + .light(LightmapTextureManager.MAX_BLOCK_LIGHT_COORDINATE).next() + vertexConsumer.vertex(matrix4f, width.toFloat(), -1.0f, 0.0f).color(background) + .light(LightmapTextureManager.MAX_BLOCK_LIGHT_COORDINATE).next() + worldContext.matrixStack.translate(0F, 0F, 0.01F) + + MC.font.draw( + text, + 0F, + 0F, + -1, + false, + worldContext.matrixStack.peek().positionMatrix, + worldContext.vertexConsumers, + TextRenderer.TextLayerType.SEE_THROUGH, + 0, + LightmapTextureManager.MAX_LIGHT_COORDINATE + ) + worldContext.matrixStack.pop() + } + } + + + fun texture( + texture: Identifier, width: Int, height: Int, + u1: Float, v1: Float, + u2: Float, v2: Float, + ) { + RenderSystem.setShaderTexture(0, texture) + RenderSystem.setShader(GameRenderer::getPositionTexColorProgram) + val hw = width / 2F + val hh = height / 2F + val matrix4f: Matrix4f = worldContext.matrixStack.peek().positionMatrix + val buf = Tessellator.getInstance() + .begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_TEXTURE_COLOR) + buf.vertex(matrix4f, -hw, -hh, 0F) + .color(-1) + .texture(u1, v1).next() + buf.vertex(matrix4f, -hw, +hh, 0F) + .color(-1) + .texture(u1, v2).next() + buf.vertex(matrix4f, +hw, +hh, 0F) + .color(-1) + .texture(u2, v2).next() + buf.vertex(matrix4f, +hw, -hh, 0F) + .color(-1) + .texture(u2, v1).next() + BufferRenderer.drawWithGlobalProgram(buf.end()) + } + +} diff --git a/src/main/kotlin/util/render/LerpUtils.kt b/src/main/kotlin/util/render/LerpUtils.kt new file mode 100644 index 0000000..f2c2f25 --- /dev/null +++ b/src/main/kotlin/util/render/LerpUtils.kt @@ -0,0 +1,33 @@ + +package moe.nea.firmament.util.render + +import me.shedaniel.math.Color + +val pi = Math.PI +val tau = Math.PI * 2 +fun lerpAngle(a: Float, b: Float, progress: Float): Float { + // TODO: there is at least 10 mods to many in here lol + val shortestAngle = ((((b.mod(tau) - a.mod(tau)).mod(tau)) + tau + pi).mod(tau)) - pi + return ((a + (shortestAngle) * progress).mod(tau)).toFloat() +} + +fun lerp(a: Float, b: Float, progress: Float): Float { + return a + (b - a) * progress +} +fun lerp(a: Int, b: Int, progress: Float): Int { + return (a + (b - a) * progress).toInt() +} + +fun ilerp(a: Float, b: Float, value: Float): Float { + return (value - a) / (b - a) +} + +fun lerp(a: Color, b: Color, progress: Float): Color { + return Color.ofRGBA( + lerp(a.red, b.red, progress), + lerp(a.green, b.green, progress), + lerp(a.blue, b.blue, progress), + lerp(a.alpha, b.alpha, progress), + ) +} + diff --git a/src/main/kotlin/util/render/RenderCircleProgress.kt b/src/main/kotlin/util/render/RenderCircleProgress.kt new file mode 100644 index 0000000..a2f42b5 --- /dev/null +++ b/src/main/kotlin/util/render/RenderCircleProgress.kt @@ -0,0 +1,95 @@ + +package moe.nea.firmament.util.render + +import com.mojang.blaze3d.systems.RenderSystem +import io.github.notenoughupdates.moulconfig.platform.next +import org.joml.Matrix4f +import org.joml.Vector2f +import kotlin.math.atan2 +import kotlin.math.tan +import net.minecraft.client.gui.DrawContext +import net.minecraft.client.render.BufferRenderer +import net.minecraft.client.render.GameRenderer +import net.minecraft.client.render.Tessellator +import net.minecraft.client.render.VertexFormat.DrawMode +import net.minecraft.client.render.VertexFormats +import net.minecraft.util.Identifier + +object RenderCircleProgress { + + fun renderCircle( + drawContext: DrawContext, + texture: Identifier, + progress: Float, + u1: Float, + u2: Float, + v1: Float, + v2: Float, + ) { + RenderSystem.setShaderTexture(0, texture) + RenderSystem.setShader(GameRenderer::getPositionTexColorProgram) + RenderSystem.enableBlend() + val matrix: Matrix4f = drawContext.matrices.peek().positionMatrix + val bufferBuilder = Tessellator.getInstance().begin(DrawMode.TRIANGLES, VertexFormats.POSITION_TEXTURE_COLOR) + + val corners = listOf( + Vector2f(0F, -1F), + Vector2f(1F, -1F), + Vector2f(1F, 0F), + Vector2f(1F, 1F), + Vector2f(0F, 1F), + Vector2f(-1F, 1F), + Vector2f(-1F, 0F), + Vector2f(-1F, -1F), + ) + + for (i in (0 until 8)) { + if (progress < i / 8F) { + break + } + val second = corners[(i + 1) % 8] + val first = corners[i] + if (progress <= (i + 1) / 8F) { + val internalProgress = 1 - (progress - i / 8F) * 8F + val angle = lerpAngle( + atan2(second.y, second.x), + atan2(first.y, first.x), + internalProgress + ) + if (angle < tau / 8 || angle >= tau * 7 / 8) { + second.set(1F, tan(angle)) + } else if (angle < tau * 3 / 8) { + second.set(1 / tan(angle), 1F) + } else if (angle < tau * 5 / 8) { + second.set(-1F, -tan(angle)) + } else { + second.set(-1 / tan(angle), -1F) + } + } + + fun ilerp(f: Float): Float = + ilerp(-1f, 1f, f) + + bufferBuilder + .vertex(matrix, second.x, second.y, 0F) + .texture(lerp(u1, u2, ilerp(second.x)), lerp(v1, v2, ilerp(second.y))) + .color(-1) + .next() + bufferBuilder + .vertex(matrix, first.x, first.y, 0F) + .texture(lerp(u1, u2, ilerp(first.x)), lerp(v1, v2, ilerp(first.y))) + .color(-1) + .next() + bufferBuilder + .vertex(matrix, 0F, 0F, 0F) + .texture(lerp(u1, u2, ilerp(0F)), lerp(v1, v2, ilerp(0F))) + .color(-1) + .next() + } + BufferRenderer.drawWithGlobalProgram(bufferBuilder.end()) + RenderSystem.disableBlend() + } + + + +} diff --git a/src/main/kotlin/util/render/RenderContextDSL.kt b/src/main/kotlin/util/render/RenderContextDSL.kt new file mode 100644 index 0000000..9bb4431 --- /dev/null +++ b/src/main/kotlin/util/render/RenderContextDSL.kt @@ -0,0 +1,6 @@ + +package moe.nea.firmament.util.render + +@DslMarker +annotation class RenderContextDSL { +} diff --git a/src/main/kotlin/util/render/RenderInWorldContext.kt b/src/main/kotlin/util/render/RenderInWorldContext.kt new file mode 100644 index 0000000..7faa499 --- /dev/null +++ b/src/main/kotlin/util/render/RenderInWorldContext.kt @@ -0,0 +1,294 @@ + + +package moe.nea.firmament.util.render + +import com.mojang.blaze3d.systems.RenderSystem +import io.github.notenoughupdates.moulconfig.platform.next +import java.lang.Math.pow +import org.joml.Matrix4f +import org.joml.Vector3f +import net.minecraft.client.gl.VertexBuffer +import net.minecraft.client.render.BufferBuilder +import net.minecraft.client.render.BufferRenderer +import net.minecraft.client.render.Camera +import net.minecraft.client.render.GameRenderer +import net.minecraft.client.render.RenderLayer +import net.minecraft.client.render.RenderPhase +import net.minecraft.client.render.RenderTickCounter +import net.minecraft.client.render.Tessellator +import net.minecraft.client.render.VertexConsumerProvider +import net.minecraft.client.render.VertexFormat +import net.minecraft.client.render.VertexFormats +import net.minecraft.client.texture.Sprite +import net.minecraft.client.util.math.MatrixStack +import net.minecraft.text.Text +import net.minecraft.util.Identifier +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Vec3d +import moe.nea.firmament.events.WorldRenderLastEvent +import moe.nea.firmament.util.FirmFormatters +import moe.nea.firmament.util.MC + +@RenderContextDSL +class RenderInWorldContext private constructor( + private val tesselator: Tessellator, + val matrixStack: MatrixStack, + private val camera: Camera, + private val tickCounter: RenderTickCounter, + val vertexConsumers: VertexConsumerProvider.Immediate, +) { + + object RenderLayers { + val TRANSLUCENT_TRIS = RenderLayer.of("firmament_translucent_tris", + VertexFormats.POSITION_COLOR, + VertexFormat.DrawMode.TRIANGLES, + RenderLayer.DEFAULT_BUFFER_SIZE, + false, true, + RenderLayer.MultiPhaseParameters.builder() + .depthTest(RenderPhase.ALWAYS_DEPTH_TEST) + .transparency(RenderPhase.TRANSLUCENT_TRANSPARENCY) + .program(RenderPhase.COLOR_PROGRAM) + .build(false)) + } + + fun color(color: me.shedaniel.math.Color) { + color(color.red / 255F, color.green / 255f, color.blue / 255f, color.alpha / 255f) + } + + fun color(red: Float, green: Float, blue: Float, alpha: Float) { + RenderSystem.setShaderColor(red, green, blue, alpha) + } + + fun block(blockPos: BlockPos) { + matrixStack.push() + matrixStack.translate(blockPos.x.toFloat(), blockPos.y.toFloat(), blockPos.z.toFloat()) + buildCube(matrixStack.peek().positionMatrix, tesselator) + matrixStack.pop() + } + + enum class VerticalAlign { + TOP, BOTTOM, CENTER; + + fun align(index: Int, count: Int): Float { + return when (this) { + CENTER -> (index - count / 2F) * (1 + MC.font.fontHeight.toFloat()) + BOTTOM -> (index - count) * (1 + MC.font.fontHeight.toFloat()) + TOP -> (index) * (1 + MC.font.fontHeight.toFloat()) + } + } + } + + fun waypoint(position: BlockPos, vararg label: Text) { + text( + position.toCenterPos(), + *label, + Text.literal("§e${FirmFormatters.formatDistance(MC.player?.pos?.distanceTo(position.toCenterPos()) ?: 42069.0)}"), + background = 0xAA202020.toInt() + ) + } + + fun withFacingThePlayer(position: Vec3d, block: FacingThePlayerContext.() -> Unit) { + matrixStack.push() + matrixStack.translate(position.x, position.y, position.z) + val actualCameraDistance = position.distanceTo(camera.pos) + val distanceToMoveTowardsCamera = if (actualCameraDistance < 10) 0.0 else -(actualCameraDistance - 10.0) + val vec = position.subtract(camera.pos).multiply(distanceToMoveTowardsCamera / actualCameraDistance) + matrixStack.translate(vec.x, vec.y, vec.z) + matrixStack.multiply(camera.rotation) + matrixStack.scale(0.025F, -0.025F, 1F) + + FacingThePlayerContext(this).run(block) + + matrixStack.pop() + vertexConsumers.drawCurrentLayer() + } + + fun sprite(position: Vec3d, sprite: Sprite, width: Int, height: Int) { + texture( + position, sprite.atlasId, width, height, sprite.minU, sprite.minV, sprite.maxU, sprite.maxV + ) + } + + fun texture( + position: Vec3d, texture: Identifier, width: Int, height: Int, + u1: Float, v1: Float, + u2: Float, v2: Float, + ) { + withFacingThePlayer(position) { + texture(texture, width, height, u1, v1, u2, v2) + } + } + + fun text(position: Vec3d, vararg texts: Text, verticalAlign: VerticalAlign = VerticalAlign.CENTER, background: Int = 0x70808080) { + withFacingThePlayer(position) { + text(*texts, verticalAlign = verticalAlign, background = background) + } + } + + fun tinyBlock(vec3d: Vec3d, size: Float) { + RenderSystem.setShader(GameRenderer::getPositionColorProgram) + matrixStack.push() + matrixStack.translate(vec3d.x, vec3d.y, vec3d.z) + matrixStack.scale(size, size, size) + matrixStack.translate(-.5, -.5, -.5) + buildCube(matrixStack.peek().positionMatrix, tesselator) + matrixStack.pop() + } + + fun wireframeCube(blockPos: BlockPos, lineWidth: Float = 10F) { + RenderSystem.setShader(GameRenderer::getRenderTypeLinesProgram) + matrixStack.push() + RenderSystem.lineWidth(lineWidth / pow(camera.pos.squaredDistanceTo(blockPos.toCenterPos()), 0.25).toFloat()) + matrixStack.translate(blockPos.x.toFloat(), blockPos.y.toFloat(), blockPos.z.toFloat()) + buildWireFrameCube(matrixStack.peek(), tesselator) + matrixStack.pop() + } + + fun line(vararg points: Vec3d, lineWidth: Float = 10F) { + line(points.toList(), lineWidth) + } + + fun tracer(toWhere: Vec3d, lineWidth: Float = 3f) { + val cameraForward = Vector3f(0f, 0f, 1f).rotate(camera.rotation) + line(camera.pos.add(Vec3d(cameraForward)), toWhere, lineWidth = lineWidth) + } + + fun line(points: List, lineWidth: Float = 10F) { + RenderSystem.setShader(GameRenderer::getRenderTypeLinesProgram) + RenderSystem.lineWidth(lineWidth) + val buffer = tesselator.begin(VertexFormat.DrawMode.LINES, VertexFormats.LINES) + + val matrix = matrixStack.peek() + var lastNormal: Vector3f? = null + points.zipWithNext().forEach { (a, b) -> + val normal = Vector3f(b.x.toFloat(), b.y.toFloat(), b.z.toFloat()) + .sub(a.x.toFloat(), a.y.toFloat(), a.z.toFloat()) + .normalize() + val lastNormal0 = lastNormal ?: normal + lastNormal = normal + buffer.vertex(matrix.positionMatrix, a.x.toFloat(), a.y.toFloat(), a.z.toFloat()) + .color(-1) + .normal(matrix, lastNormal0.x, lastNormal0.y, lastNormal0.z) + .next() + buffer.vertex(matrix.positionMatrix, b.x.toFloat(), b.y.toFloat(), b.z.toFloat()) + .color(-1) + .normal(matrix, normal.x, normal.y, normal.z) + .next() + } + + BufferRenderer.drawWithGlobalProgram(buffer.end()) + } + + companion object { + private fun doLine( + matrix: MatrixStack.Entry, + buf: BufferBuilder, + i: Float, + j: Float, + k: Float, + x: Float, + y: Float, + z: Float + ) { + val normal = Vector3f(x, y, z) + .sub(i, j, k) + .normalize() + buf.vertex(matrix.positionMatrix, i, j, k) + .normal(matrix, normal.x, normal.y, normal.z) + .color(-1) + .next() + buf.vertex(matrix.positionMatrix, x, y, z) + .normal(matrix, normal.x, normal.y, normal.z) + .color(-1) + .next() + } + + + private fun buildWireFrameCube(matrix: MatrixStack.Entry, tessellator: Tessellator) { + val buf = tessellator.begin(VertexFormat.DrawMode.LINES, VertexFormats.LINES) + + for (i in 0..1) { + for (j in 0..1) { + val i = i.toFloat() + val j = j.toFloat() + doLine(matrix, buf, 0F, i, j, 1F, i, j) + doLine(matrix, buf, i, 0F, j, i, 1F, j) + doLine(matrix, buf, i, j, 0F, i, j, 1F) + } + } + BufferRenderer.drawWithGlobalProgram(buf.end()) + } + + private fun buildCube(matrix: Matrix4f, tessellator: Tessellator) { + val buf = tessellator.begin(VertexFormat.DrawMode.TRIANGLES, VertexFormats.POSITION_COLOR) + buf.vertex(matrix, 0.0F, 0.0F, 0.0F).color(-1).next() + buf.vertex(matrix, 0.0F, 0.0F, 1.0F).color(-1).next() + buf.vertex(matrix, 0.0F, 1.0F, 1.0F).color(-1).next() + buf.vertex(matrix, 1.0F, 1.0F, 0.0F).color(-1).next() + buf.vertex(matrix, 0.0F, 0.0F, 0.0F).color(-1).next() + buf.vertex(matrix, 0.0F, 1.0F, 0.0F).color(-1).next() + buf.vertex(matrix, 1.0F, 0.0F, 1.0F).color(-1).next() + buf.vertex(matrix, 0.0F, 0.0F, 0.0F).color(-1).next() + buf.vertex(matrix, 1.0F, 0.0F, 0.0F).color(-1).next() + buf.vertex(matrix, 1.0F, 1.0F, 0.0F).color(-1).next() + buf.vertex(matrix, 1.0F, 0.0F, 0.0F).color(-1).next() + buf.vertex(matrix, 0.0F, 0.0F, 0.0F).color(-1).next() + buf.vertex(matrix, 0.0F, 0.0F, 0.0F).color(-1).next() + buf.vertex(matrix, 0.0F, 1.0F, 1.0F).color(-1).next() + buf.vertex(matrix, 0.0F, 1.0F, 0.0F).color(-1).next() + buf.vertex(matrix, 1.0F, 0.0F, 1.0F).color(-1).next() + buf.vertex(matrix, 0.0F, 0.0F, 1.0F).color(-1).next() + buf.vertex(matrix, 0.0F, 0.0F, 0.0F).color(-1).next() + buf.vertex(matrix, 0.0F, 1.0F, 1.0F).color(-1).next() + buf.vertex(matrix, 0.0F, 0.0F, 1.0F).color(-1).next() + buf.vertex(matrix, 1.0F, 0.0F, 1.0F).color(-1).next() + buf.vertex(matrix, 1.0F, 1.0F, 1.0F).color(-1).next() + buf.vertex(matrix, 1.0F, 0.0F, 0.0F).color(-1).next() + buf.vertex(matrix, 1.0F, 1.0F, 0.0F).color(-1).next() + buf.vertex(matrix, 1.0F, 0.0F, 0.0F).color(-1).next() + buf.vertex(matrix, 1.0F, 1.0F, 1.0F).color(-1).next() + buf.vertex(matrix, 1.0F, 0.0F, 1.0F).color(-1).next() + buf.vertex(matrix, 1.0F, 1.0F, 1.0F).color(-1).next() + buf.vertex(matrix, 1.0F, 1.0F, 0.0F).color(-1).next() + buf.vertex(matrix, 0.0F, 1.0F, 0.0F).color(-1).next() + buf.vertex(matrix, 1.0F, 1.0F, 1.0F).color(-1).next() + buf.vertex(matrix, 0.0F, 1.0F, 0.0F).color(-1).next() + buf.vertex(matrix, 0.0F, 1.0F, 1.0F).color(-1).next() + buf.vertex(matrix, 1.0F, 1.0F, 1.0F).color(-1).next() + buf.vertex(matrix, 0.0F, 1.0F, 1.0F).color(-1).next() + buf.vertex(matrix, 1.0F, 0.0F, 1.0F).color(-1).next() + RenderLayers.TRANSLUCENT_TRIS.draw(buf.end()) + } + + + fun renderInWorld(event: WorldRenderLastEvent, block: RenderInWorldContext. () -> Unit) { + RenderSystem.disableDepthTest() + RenderSystem.enableBlend() + RenderSystem.defaultBlendFunc() + RenderSystem.disableCull() + + event.matrices.push() + event.matrices.translate(-event.camera.pos.x, -event.camera.pos.y, -event.camera.pos.z) + + val ctx = RenderInWorldContext( + RenderSystem.renderThreadTesselator(), + event.matrices, + event.camera, + event.tickCounter, + event.vertexConsumers + ) + + block(ctx) + + event.matrices.pop() + + RenderSystem.setShaderColor(1F, 1F, 1F, 1F) + VertexBuffer.unbind() + RenderSystem.enableDepthTest() + RenderSystem.enableCull() + RenderSystem.disableBlend() + } + } +} + + diff --git a/src/main/kotlin/util/render/TranslatedScissors.kt b/src/main/kotlin/util/render/TranslatedScissors.kt new file mode 100644 index 0000000..c1e6544 --- /dev/null +++ b/src/main/kotlin/util/render/TranslatedScissors.kt @@ -0,0 +1,22 @@ + +package moe.nea.firmament.util.render + +import org.joml.Vector4f +import net.minecraft.client.gui.DrawContext + +fun DrawContext.enableScissorWithTranslation(x1: Float, y1: Float, x2: Float, y2: Float) { + val pMat = matrices.peek().positionMatrix + val target = Vector4f() + + target.set(x1, y1, 0f, 1f) + target.mul(pMat) + val scissorX1 = target.x + val scissorY1 = target.y + + target.set(x2, y2, 0f, 1f) + target.mul(pMat) + val scissorX2 = target.x + val scissorY2 = target.y + + enableScissor(scissorX1.toInt(), scissorY1.toInt(), scissorX2.toInt(), scissorY2.toInt()) +} diff --git a/src/main/kotlin/util/stringutil.kt b/src/main/kotlin/util/stringutil.kt new file mode 100644 index 0000000..56f8dbe --- /dev/null +++ b/src/main/kotlin/util/stringutil.kt @@ -0,0 +1,6 @@ + +package moe.nea.firmament.util + +fun parseIntWithComma(string: String): Int { + return string.replace(",", "").toInt() +} diff --git a/src/main/kotlin/util/textutil.kt b/src/main/kotlin/util/textutil.kt new file mode 100644 index 0000000..a05733c --- /dev/null +++ b/src/main/kotlin/util/textutil.kt @@ -0,0 +1,117 @@ + + +package moe.nea.firmament.util + +import net.minecraft.text.MutableText +import net.minecraft.text.PlainTextContent +import net.minecraft.text.Style +import net.minecraft.text.Text +import net.minecraft.text.TranslatableTextContent +import net.minecraft.util.Formatting +import moe.nea.firmament.Firmament + + +class TextMatcher(text: Text) { + data class State( + var iterator: MutableList, + var currentText: Text?, + var offset: Int, + var textContent: String, + ) + + var state = State( + mutableListOf(text), + null, + 0, + "" + ) + + fun pollChunk(): Boolean { + val firstOrNull = state.iterator.removeFirstOrNull() ?: return false + state.offset = 0 + state.currentText = firstOrNull + state.textContent = when (val content = firstOrNull.content) { + is PlainTextContent.Literal -> content.string + else -> { + Firmament.logger.warn("TextContent of type ${content.javaClass} not understood.") + return false + } + } + state.iterator.addAll(0, firstOrNull.siblings) + return true + } + + fun pollChunks(): Boolean { + while (state.offset !in state.textContent.indices) { + if (!pollChunk()) { + return false + } + } + return true + } + + fun pollChar(): Char? { + if (!pollChunks()) return null + return state.textContent[state.offset++] + } + + + fun expectString(string: String): Boolean { + var found = "" + while (found.length < string.length) { + if (!pollChunks()) return false + val takeable = state.textContent.drop(state.offset).take(string.length - found.length) + state.offset += takeable.length + found += takeable + } + return found == string + } +} + +val formattingChars = "kmolnrKMOLNR".toSet() +fun CharSequence.removeColorCodes(keepNonColorCodes: Boolean = false): String { + var nextParagraph = indexOf('§') + if (nextParagraph < 0) return this.toString() + val stringBuffer = StringBuilder(this.length) + var readIndex = 0 + while (nextParagraph >= 0) { + stringBuffer.append(this, readIndex, nextParagraph) + if (keepNonColorCodes && nextParagraph + 1 < length && this[nextParagraph + 1] in formattingChars) { + readIndex = nextParagraph + nextParagraph = indexOf('§', startIndex = readIndex + 1) + } else { + readIndex = nextParagraph + 2 + nextParagraph = indexOf('§', startIndex = readIndex) + } + if (readIndex > this.length) + readIndex = this.length + } + stringBuffer.append(this, readIndex, this.length) + return stringBuffer.toString() +} + +val Text.unformattedString: String + get() = string.removeColorCodes() + + +fun MutableText.withColor(formatting: Formatting) = this.styled { it.withColor(formatting).withItalic(false) } + +fun Text.transformEachRecursively(function: (Text) -> Text): Text { + val c = this.content + if (c is TranslatableTextContent) { + return Text.translatableWithFallback(c.key, c.fallback, *c.args.map { + (if (it is Text) it else Text.literal(it.toString())).transformEachRecursively(function) + }.toTypedArray()).also { new -> + new.style = this.style + new.siblings.clear() + this.siblings.forEach { child -> + new.siblings.add(child.transformEachRecursively(function)) + } + } + } + return function(this.copy().also { it.siblings.clear() }).also { tt -> + this.siblings.forEach { + tt.siblings.add(it.transformEachRecursively(function)) + } + } +} diff --git a/src/main/kotlin/util/uuid.kt b/src/main/kotlin/util/uuid.kt new file mode 100644 index 0000000..4aa0749 --- /dev/null +++ b/src/main/kotlin/util/uuid.kt @@ -0,0 +1,12 @@ + + +package moe.nea.firmament.util + +import java.math.BigInteger +import java.util.UUID + +fun parseDashlessUUID(dashlessUuid: String): UUID { + val most = BigInteger(dashlessUuid.substring(0, 16), 16) + val least = BigInteger(dashlessUuid.substring(16, 32), 16) + return UUID(most.toLong(), least.toLong()) +} -- cgit