aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/compat/moulconfig/java/MCConfigEditorIntegration.kt29
-rw-r--r--src/compat/rei/java/moe/nea/firmament/compat/rei/EntityWidget.kt29
-rw-r--r--src/compat/rei/java/moe/nea/firmament/compat/rei/FirmamentReiPlugin.kt11
-rw-r--r--src/compat/rei/java/moe/nea/firmament/compat/rei/NEUItemEntryRenderer.kt119
-rw-r--r--src/compat/rei/java/moe/nea/firmament/compat/rei/SBItemEntryDefinition.kt17
-rw-r--r--src/compat/rei/java/moe/nea/firmament/compat/rei/SkyblockCraftingRecipeDynamicGenerator.kt63
-rw-r--r--src/compat/rei/java/moe/nea/firmament/compat/rei/SkyblockItemIdFocusedStackProvider.kt3
-rw-r--r--src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBCraftingRecipe.kt7
-rw-r--r--src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBEssenceUpgradeRecipe.kt7
-rw-r--r--src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBForgeRecipe.kt4
-rw-r--r--src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBKatRecipe.kt2
-rw-r--r--src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBMobDropRecipe.kt2
-rw-r--r--src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBReforgeRecipe.kt212
-rw-r--r--src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBShopRecipe.kt61
-rw-r--r--src/compat/rei/java/moe/nea/firmament/mixins/compat/HideREIRecipeWarning.java20
-rw-r--r--src/compat/wildfireGender/java/moe/nea/firmament/mixins/compat/wildfiregender/PatchArmorTexturesInGenderMod.java32
-rw-r--r--src/compat/yacl/java/YaclIntegration.kt29
-rw-r--r--src/main/java/moe/nea/firmament/init/AutoDiscoveryPlugin.java318
-rw-r--r--src/main/java/moe/nea/firmament/init/EarlyRiser.java2
-rw-r--r--src/main/java/moe/nea/firmament/init/HandledScreenRiser.java118
-rw-r--r--src/main/java/moe/nea/firmament/init/ItemColorsSodiumRiser.java64
-rw-r--r--src/main/java/moe/nea/firmament/mixins/AlwaysDisplayFirmamentClientCommandErrors.java18
-rw-r--r--src/main/java/moe/nea/firmament/mixins/CustomModelEventPatch.java36
-rw-r--r--src/main/java/moe/nea/firmament/mixins/FirmKeybindsInVanillaControlsPatch.java2
-rw-r--r--src/main/java/moe/nea/firmament/mixins/HideStatusEffectsPatch.java29
-rw-r--r--src/main/java/moe/nea/firmament/mixins/IncomingPacketListenerPatches.java3
-rw-r--r--src/main/java/moe/nea/firmament/mixins/LenientProfileComponentPatch.java21
-rw-r--r--src/main/java/moe/nea/firmament/mixins/MixinHandledScreen.java20
-rw-r--r--src/main/java/moe/nea/firmament/mixins/PlayerDropEventPatch.java20
-rw-r--r--src/main/java/moe/nea/firmament/mixins/PropertySignatureIgnorePatch.java36
-rw-r--r--src/main/java/moe/nea/firmament/mixins/SaveOriginalCommandTreePacket.java17
-rw-r--r--src/main/java/moe/nea/firmament/mixins/TolerateFirmamentTolerateRegistryOwners.java18
-rw-r--r--src/main/java/moe/nea/firmament/mixins/customgui/PatchHandledScreen.java21
-rw-r--r--src/main/java/moe/nea/firmament/mixins/custommodels/ApplyHeadModelInItemRenderer.java35
-rw-r--r--src/main/java/moe/nea/firmament/mixins/custommodels/BakedModelDataHolderBasic.java42
-rw-r--r--src/main/java/moe/nea/firmament/mixins/custommodels/BakedModelDataHolderBuiltin.java43
-rw-r--r--src/main/java/moe/nea/firmament/mixins/custommodels/BakedOverrideDataHolder.java28
-rw-r--r--src/main/java/moe/nea/firmament/mixins/custommodels/HeadModelReplacerPatch.java57
-rw-r--r--src/main/java/moe/nea/firmament/mixins/custommodels/ItemColorRemovalPatch.java39
-rw-r--r--src/main/java/moe/nea/firmament/mixins/custommodels/ItemModelGeneratorJsonUnbakedModelCopy.java22
-rw-r--r--src/main/java/moe/nea/firmament/mixins/custommodels/ItemRendererTintContextPatch.java35
-rw-r--r--src/main/java/moe/nea/firmament/mixins/custommodels/JsonUnbakedModelDataHolder.java130
-rw-r--r--src/main/java/moe/nea/firmament/mixins/custommodels/ModelOverrideDataHolder.java28
-rw-r--r--src/main/java/moe/nea/firmament/mixins/custommodels/PatchJsonUnbakedModelDeserializer.java31
-rw-r--r--src/main/java/moe/nea/firmament/mixins/custommodels/PatchLegacyArmorLayerSupport.java22
-rw-r--r--src/main/java/moe/nea/firmament/mixins/custommodels/PatchOverrideDeserializer.java50
-rw-r--r--src/main/java/moe/nea/firmament/mixins/custommodels/ProvideBakerToJsonUnbakedModelPatch.java27
-rw-r--r--src/main/java/moe/nea/firmament/mixins/custommodels/ReferenceCustomModelsPatch.java36
-rw-r--r--src/main/java/moe/nea/firmament/mixins/custommodels/TestForFirmamentOverridePredicatesPatch.java68
-rw-r--r--src/main/java/moe/nea/firmament/mixins/render/entitytints/ChangeColorOfLivingEntities.java62
-rw-r--r--src/main/java/moe/nea/firmament/mixins/render/entitytints/EntityRenderStateTint.java55
-rw-r--r--src/main/java/moe/nea/firmament/mixins/render/entitytints/InjectIntoRenderState.java30
-rw-r--r--src/main/java/moe/nea/firmament/mixins/render/entitytints/ReplaceOverlayTexture.java24
-rw-r--r--src/main/java/moe/nea/firmament/mixins/render/entitytints/UseOverlayableEquipmentRenderer.java34
-rw-r--r--src/main/java/moe/nea/firmament/mixins/render/entitytints/UseOverlayableHeadFeatureRenderer.java25
-rw-r--r--src/main/java/moe/nea/firmament/mixins/render/entitytints/UseOverlayableItemRenderer.java25
-rw-r--r--src/main/java/moe/nea/firmament/mixins/render/entitytints/UseOverlayableSkullBlockEntityRenderer.java25
-rw-r--r--src/main/kotlin/Firmament.kt2
-rw-r--r--src/main/kotlin/commands/Duration.kt75
-rw-r--r--src/main/kotlin/commands/rome.kt1
-rw-r--r--src/main/kotlin/events/BakeExtraModelsEvent.kt24
-rw-r--r--src/main/kotlin/events/CustomItemModelEvent.kt43
-rw-r--r--src/main/kotlin/events/EntityRenderTintEvent.kt66
-rw-r--r--src/main/kotlin/events/FirmamentEventBus.kt3
-rw-r--r--src/main/kotlin/events/IsSlotProtectedEvent.kt72
-rw-r--r--src/main/kotlin/events/PartyMessageReceivedEvent.kt9
-rw-r--r--src/main/kotlin/events/UseItemEvent.kt11
-rw-r--r--src/main/kotlin/events/registration/ChatEvents.kt81
-rw-r--r--src/main/kotlin/features/FeatureManager.kt6
-rw-r--r--src/main/kotlin/features/chat/ChatLinks.kt239
-rw-r--r--src/main/kotlin/features/chat/PartyCommands.kt134
-rw-r--r--src/main/kotlin/features/chat/QuickCommands.kt212
-rw-r--r--src/main/kotlin/features/debug/PowerUserTools.kt31
-rw-r--r--src/main/kotlin/features/events/anniversity/CenturyRaffleFeatures.kt63
-rw-r--r--src/main/kotlin/features/fixes/Fixes.kt94
-rw-r--r--src/main/kotlin/features/inventory/ItemRarityCosmetics.kt13
-rw-r--r--src/main/kotlin/features/inventory/SlotLocking.kt66
-rw-r--r--src/main/kotlin/features/inventory/TimerInLore.kt130
-rw-r--r--src/main/kotlin/features/inventory/storageoverlay/StorageOverlayCustom.kt12
-rw-r--r--src/main/kotlin/features/inventory/storageoverlay/StorageOverlayScreen.kt128
-rw-r--r--src/main/kotlin/features/inventory/storageoverlay/VirtualInventory.kt82
-rw-r--r--src/main/kotlin/features/mining/PickaxeAbility.kt55
-rw-r--r--src/main/kotlin/features/misc/TimerFeature.kt124
-rw-r--r--src/main/kotlin/features/texturepack/BakedModelExtra.kt30
-rw-r--r--src/main/kotlin/features/texturepack/BakedOverrideData.kt14
-rw-r--r--src/main/kotlin/features/texturepack/CustomModelOverrideParser.kt82
-rw-r--r--src/main/kotlin/features/texturepack/CustomSkyBlockTextures.kt135
-rw-r--r--src/main/kotlin/features/texturepack/FirmamentModelPredicate.kt8
-rw-r--r--src/main/kotlin/features/texturepack/JsonUnbakedModelFirmExtra.kt16
-rw-r--r--src/main/kotlin/features/texturepack/ModelOverrideData.kt15
-rw-r--r--src/main/kotlin/features/world/Waypoints.kt15
-rw-r--r--src/main/kotlin/gui/CheckboxComponent.kt56
-rw-r--r--src/main/kotlin/gui/config/ChoiceHandler.kt48
-rw-r--r--src/main/kotlin/gui/config/EnumRenderer.kt15
-rw-r--r--src/main/kotlin/gui/config/ManagedConfig.kt37
-rw-r--r--src/main/kotlin/gui/config/ManagedOption.kt9
-rw-r--r--src/main/kotlin/gui/entity/EntityRenderer.kt34
-rw-r--r--src/main/kotlin/gui/entity/FakeWorld.kt5
-rw-r--r--src/main/kotlin/keybindings/FirmamentKeyBindings.kt29
-rw-r--r--src/main/kotlin/repo/BetterRepoRecipeCache.kt41
-rw-r--r--src/main/kotlin/repo/EssenceRecipeProvider.kt75
-rw-r--r--src/main/kotlin/repo/ExtraRecipeProvider.kt7
-rw-r--r--src/main/kotlin/repo/ItemCache.kt55
-rw-r--r--src/main/kotlin/repo/Reforge.kt160
-rw-r--r--src/main/kotlin/repo/ReforgeStore.kt125
-rw-r--r--src/main/kotlin/repo/RepoItemTypeCache.kt15
-rw-r--r--src/main/kotlin/repo/RepoManager.kt6
-rw-r--r--src/main/kotlin/repo/RepoModResourcePack.kt156
-rw-r--r--src/main/kotlin/repo/SBItemStack.kt249
-rw-r--r--src/main/kotlin/util/AprilFoolsUtil.kt10
-rw-r--r--src/main/kotlin/util/FirmFormatters.kt42
-rw-r--r--src/main/kotlin/util/JvmUtil.kt32
-rw-r--r--src/main/kotlin/util/LegacyFormattingCode.kt54
-rw-r--r--src/main/kotlin/util/MC.kt15
-rw-r--r--src/main/kotlin/util/MoulConfigUtils.kt24
-rw-r--r--src/main/kotlin/util/SBData.kt125
-rw-r--r--src/main/kotlin/util/ScoreboardUtil.kt72
-rw-r--r--src/main/kotlin/util/SkyBlockIsland.kt2
-rw-r--r--src/main/kotlin/util/SkyblockId.kt29
-rw-r--r--src/main/kotlin/util/StringUtil.kt9
-rw-r--r--src/main/kotlin/util/TestUtil.kt6
-rw-r--r--src/main/kotlin/util/customgui/CustomGui.kt12
-rw-r--r--src/main/kotlin/util/json/FirmCodecs.kt20
-rw-r--r--src/main/kotlin/util/json/KJsonOps.kt131
-rw-r--r--src/main/kotlin/util/mc/IntrospectableItemModelManager.kt7
-rw-r--r--src/main/kotlin/util/mc/NbtItemData.kt4
-rw-r--r--src/main/kotlin/util/mc/TolerantRegistriesOps.kt29
-rw-r--r--src/main/kotlin/util/render/TintedOverlayTexture.kt44
-rw-r--r--src/main/kotlin/util/render/TranslatedScissors.kt6
-rw-r--r--src/main/kotlin/util/skyblock/AbilityUtils.kt1
-rw-r--r--src/main/kotlin/util/skyblock/DungeonUtil.kt33
-rw-r--r--src/main/kotlin/util/skyblock/ItemType.kt74
-rw-r--r--src/main/kotlin/util/skyblock/Rarity.kt38
-rw-r--r--src/main/kotlin/util/skyblock/SkyBlockItems.kt6
-rw-r--r--src/main/kotlin/util/textutil.kt113
-rw-r--r--src/main/resources/assets/firmament/textures/gui/sprites/widget/checkbox_checked.pngbin0 -> 683 bytes
-rw-r--r--src/main/resources/assets/firmament/textures/gui/sprites/widget/checkbox_unchecked.pngbin0 -> 614 bytes
-rw-r--r--src/main/resources/firmament.accesswidener23
-rw-r--r--src/test/kotlin/testutil/ItemResources.kt8
-rw-r--r--src/test/kotlin/testutil/KotestPlugin.kt16
-rw-r--r--src/test/kotlin/util/ColorCodeTest.kt26
-rw-r--r--src/test/kotlin/util/TextUtilText.kt7
-rw-r--r--src/test/kotlin/util/skyblock/AbilityUtilsTest.kt4
-rw-r--r--src/test/kotlin/util/skyblock/ItemTypeTest.kt26
-rw-r--r--src/test/kotlin/util/skyblock/SackUtilTest.kt4
-rw-r--r--src/test/resources/testdata/items/books/feather_falling.snbt36
-rw-r--r--src/test/resources/testdata/items/gemstone-gauntlet.snbt103
-rw-r--r--src/test/resources/testdata/items/hyperion.snbt96
-rw-r--r--src/test/resources/testdata/items/implosion-belt.snbt105
-rw-r--r--src/test/resources/testdata/items/pets/lion-item.snbt62
-rw-r--r--src/test/resources/testdata/items/pets/mithril-golem-not-selected.snbt52
-rw-r--r--src/test/resources/testdata/items/pets/rabbit-selected.snbt60
-rw-r--r--src/texturePacks/README.md13
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomBlockTextures.kt (renamed from src/main/kotlin/features/texturepack/CustomBlockTextures.kt)20
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomGlobalArmorOverrides.kt (renamed from src/main/kotlin/features/texturepack/CustomGlobalArmorOverrides.kt)52
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomGlobalTextures.kt (renamed from src/main/kotlin/features/texturepack/CustomGlobalTextures.kt)60
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomModelOverrideParser.kt115
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomSkyBlockTextures.kt118
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomTextColors.kt (renamed from src/main/kotlin/features/texturepack/CustomTextColors.kt)0
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/FirmamentModelPredicate.kt9
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/FirmamentModelPredicateParser.kt (renamed from src/main/kotlin/features/texturepack/FirmamentModelPredicateParser.kt)0
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/FirmamentRootPredicateSerializer.kt23
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/PredicateModel.kt106
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/RarityMatcher.kt (renamed from src/main/kotlin/features/texturepack/RarityMatcher.kt)0
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/StringMatcher.kt (renamed from src/main/kotlin/features/texturepack/StringMatcher.kt)2
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/TintOverrides.kt (renamed from src/main/kotlin/features/texturepack/TintOverrides.kt)2
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/AlwaysPredicate.kt (renamed from src/main/kotlin/features/texturepack/predicates/AlwaysPredicate.kt)0
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/AndPredicate.kt (renamed from src/main/kotlin/features/texturepack/predicates/AndPredicate.kt)7
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/CastPredicate.kt25
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/DisplayNamePredicate.kt (renamed from src/main/kotlin/features/texturepack/predicates/DisplayNamePredicate.kt)0
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/ExtraAttributesPredicate.kt (renamed from src/main/kotlin/features/texturepack/predicates/ExtraAttributesPredicate.kt)0
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/ItemPredicate.kt (renamed from src/main/kotlin/features/texturepack/predicates/ItemPredicate.kt)2
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/LorePredicate.kt (renamed from src/main/kotlin/features/texturepack/predicates/LorePredicate.kt)0
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/NotPredicate.kt (renamed from src/main/kotlin/features/texturepack/predicates/NotPredicate.kt)0
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/NumberMatcher.kt (renamed from src/main/kotlin/features/texturepack/predicates/NumberMatcher.kt)0
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/OrPredicate.kt (renamed from src/main/kotlin/features/texturepack/predicates/OrPredicate.kt)0
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/PetPredicate.kt (renamed from src/main/kotlin/features/texturepack/predicates/PetPredicate.kt)0
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/PullingPredicate.kt26
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ApplyHeadModelInItemRenderer.java23
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/CustomSkullTexturePatch.java (renamed from src/main/java/moe/nea/firmament/mixins/CustomSkullTexturePatch.java)11
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/PatchArmorTexture.java (renamed from src/main/java/moe/nea/firmament/mixins/custommodels/PatchArmorTexture.java)21
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/PatchLegacyArmorLayerSupport.java23
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/PatchLegacyTexturePathsIntoArmorLayers.java38
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockBreakSoundPatch.java (renamed from src/main/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockBreakSoundPatch.java)0
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockHitSoundPatch.java (renamed from src/main/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockHitSoundPatch.java)0
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockRenderManagerBlockModel.java (renamed from src/main/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockRenderManagerBlockModel.java)0
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceFallbackBlockModel.java (renamed from src/main/java/moe/nea/firmament/mixins/custommodels/ReplaceFallbackBlockModel.java)0
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceItemModelPatch.java43
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceTextColorInHandledScreen.java (renamed from src/main/java/moe/nea/firmament/mixins/ReplaceTextColorInHandledScreen.java)2
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/SupplyFakeModelPatch.java95
190 files changed, 5453 insertions, 2525 deletions
diff --git a/src/compat/moulconfig/java/MCConfigEditorIntegration.kt b/src/compat/moulconfig/java/MCConfigEditorIntegration.kt
index 20a79a8..dec2559 100644
--- a/src/compat/moulconfig/java/MCConfigEditorIntegration.kt
+++ b/src/compat/moulconfig/java/MCConfigEditorIntegration.kt
@@ -20,6 +20,7 @@ import io.github.notenoughupdates.moulconfig.gui.editors.ComponentEditor
import io.github.notenoughupdates.moulconfig.gui.editors.GuiOptionEditorAccordion
import io.github.notenoughupdates.moulconfig.gui.editors.GuiOptionEditorBoolean
import io.github.notenoughupdates.moulconfig.gui.editors.GuiOptionEditorButton
+import io.github.notenoughupdates.moulconfig.gui.editors.GuiOptionEditorDropdown
import io.github.notenoughupdates.moulconfig.gui.editors.GuiOptionEditorText
import io.github.notenoughupdates.moulconfig.observer.GetSetter
import io.github.notenoughupdates.moulconfig.processor.ProcessedCategory
@@ -31,9 +32,11 @@ import kotlin.time.Duration.Companion.seconds
import kotlin.time.DurationUnit
import net.minecraft.client.gui.screen.Screen
import net.minecraft.util.Identifier
+import net.minecraft.util.StringIdentifiable
import net.minecraft.util.Util
import moe.nea.firmament.Firmament
import moe.nea.firmament.gui.config.BooleanHandler
+import moe.nea.firmament.gui.config.ChoiceHandler
import moe.nea.firmament.gui.config.ClickHandler
import moe.nea.firmament.gui.config.DurationHandler
import moe.nea.firmament.gui.config.FirmamentConfigScreenProvider
@@ -115,7 +118,33 @@ class MCConfigEditorIntegration : FirmamentConfigScreenProvider {
}
}
+ fun <T> helpRegisterChoice() where T : Enum<T>, T : StringIdentifiable {
+ register(ChoiceHandler::class.java as Class<ChoiceHandler<T>>) { handler, option, categoryAccordionId, configObject ->
+ object : ProcessedEditableOptionFirm<T>(option, categoryAccordionId, configObject) {
+ override fun createEditor(): GuiOptionEditor {
+ return GuiOptionEditorDropdown(
+ this,
+ handler.universe.map { handler.renderer.getName(option, it).string }.toTypedArray()
+ )
+ }
+
+ override fun toT(any: Any?): T? {
+ return handler.universe[any as Int]
+ }
+
+ override fun getType(): Type {
+ return Int::class.java
+ }
+
+ override fun fromT(t: T): Any {
+ return t.ordinal
+ }
+ }
+ }
+ }
+
init {
+ helpRegisterChoice<Nothing>()
register(BooleanHandler::class.java) { handler, option, categoryAccordionId, configObject ->
object : ProcessedEditableOptionFirm<Boolean>(option, categoryAccordionId, configObject) {
override fun createEditor(): GuiOptionEditor {
diff --git a/src/compat/rei/java/moe/nea/firmament/compat/rei/EntityWidget.kt b/src/compat/rei/java/moe/nea/firmament/compat/rei/EntityWidget.kt
index d8238be..1097654 100644
--- a/src/compat/rei/java/moe/nea/firmament/compat/rei/EntityWidget.kt
+++ b/src/compat/rei/java/moe/nea/firmament/compat/rei/EntityWidget.kt
@@ -1,19 +1,22 @@
package moe.nea.firmament.compat.rei
import me.shedaniel.math.Dimension
+import me.shedaniel.math.FloatingDimension
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.Drawable
import net.minecraft.client.gui.Element
-import net.minecraft.client.gui.ParentElement
import net.minecraft.entity.LivingEntity
import moe.nea.firmament.gui.entity.EntityRenderer
import moe.nea.firmament.util.ErrorUtil
-class EntityWidget(val entity: LivingEntity?, val point: Point) : WidgetWithBounds() {
+class EntityWidget(
+ val entity: LivingEntity?,
+ val point: Point,
+ val size: FloatingDimension = FloatingDimension(defaultSize)
+) : WidgetWithBounds() {
override fun children(): List<Element> {
return emptyList()
}
@@ -22,18 +25,30 @@ class EntityWidget(val entity: LivingEntity?, val point: Point) : WidgetWithBoun
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())
+ if (!hasErrored) {
+ EntityRenderer.renderEntity(
+ entity!!,
+ context,
+ point.x, point.y,
+ size.width, size.height,
+ mouseX.toDouble(),
+ mouseY.toDouble())
+ }
} catch (ex: Exception) {
ErrorUtil.softError("Failed to render constructed entity: $entity", ex)
hasErrored = true
+ } finally {
}
if (hasErrored) {
- context.fill(point.x, point.y, point.x + 50, point.y + 80, 0xFFAA2222.toInt())
+ context.fill(point.x, point.y, point.x + size.width.toInt(), point.y + size.height.toInt(), 0xFFAA2222.toInt())
}
}
+ companion object {
+ val defaultSize = Dimension(50, 80)
+ }
+
override fun getBounds(): Rectangle {
- return Rectangle(point, Dimension(50, 80))
+ return Rectangle(point, size)
}
}
diff --git a/src/compat/rei/java/moe/nea/firmament/compat/rei/FirmamentReiPlugin.kt b/src/compat/rei/java/moe/nea/firmament/compat/rei/FirmamentReiPlugin.kt
index f576eda..aade59e 100644
--- a/src/compat/rei/java/moe/nea/firmament/compat/rei/FirmamentReiPlugin.kt
+++ b/src/compat/rei/java/moe/nea/firmament/compat/rei/FirmamentReiPlugin.kt
@@ -24,6 +24,8 @@ import moe.nea.firmament.compat.rei.recipes.SBEssenceUpgradeRecipe
import moe.nea.firmament.compat.rei.recipes.SBForgeRecipe
import moe.nea.firmament.compat.rei.recipes.SBKatRecipe
import moe.nea.firmament.compat.rei.recipes.SBMobDropRecipe
+import moe.nea.firmament.compat.rei.recipes.SBReforgeRecipe
+import moe.nea.firmament.compat.rei.recipes.SBShopRecipe
import moe.nea.firmament.events.HandledScreenPushREIEvent
import moe.nea.firmament.features.inventory.CraftingOverlay
import moe.nea.firmament.features.inventory.storageoverlay.StorageOverlayScreen
@@ -78,7 +80,9 @@ class FirmamentReiPlugin : REIClientPlugin {
registry.add(SBForgeRecipe.Category)
registry.add(SBMobDropRecipe.Category)
registry.add(SBKatRecipe.Category)
+ registry.add(SBReforgeRecipe.Category)
registry.add(SBEssenceUpgradeRecipe.Category)
+ registry.add(SBShopRecipe.Category)
}
override fun registerExclusionZones(zones: ExclusionZones) {
@@ -91,12 +95,19 @@ class FirmamentReiPlugin : REIClientPlugin {
SBCraftingRecipe.Category.catIdentifier,
SkyblockCraftingRecipeDynamicGenerator)
registry.registerDisplayGenerator(
+ SBReforgeRecipe.catIdentifier,
+ SBReforgeRecipe.DynamicGenerator
+ )
+ registry.registerDisplayGenerator(
SBForgeRecipe.Category.categoryIdentifier,
SkyblockForgeRecipeDynamicGenerator)
registry.registerDisplayGenerator(
SBMobDropRecipe.Category.categoryIdentifier,
SkyblockMobDropRecipeDynamicGenerator)
registry.registerDisplayGenerator(
+ SBShopRecipe.Category.categoryIdentifier,
+ SkyblockShopRecipeDynamicGenerator)
+ registry.registerDisplayGenerator(
SBKatRecipe.Category.categoryIdentifier,
SkyblockKatRecipeDynamicGenerator)
registry.registerDisplayGenerator(
diff --git a/src/compat/rei/java/moe/nea/firmament/compat/rei/NEUItemEntryRenderer.kt b/src/compat/rei/java/moe/nea/firmament/compat/rei/NEUItemEntryRenderer.kt
index 336c103..35a1e1b 100644
--- a/src/compat/rei/java/moe/nea/firmament/compat/rei/NEUItemEntryRenderer.kt
+++ b/src/compat/rei/java/moe/nea/firmament/compat/rei/NEUItemEntryRenderer.kt
@@ -9,11 +9,7 @@
package moe.nea.firmament.compat.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
@@ -21,23 +17,20 @@ import me.shedaniel.rei.api.common.entry.EntryStack
import net.fabricmc.fabric.api.client.item.v1.ItemTooltipCallback
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.texture.SpriteAtlasTexture
-import net.minecraft.item.ModelTransformationMode
import net.minecraft.item.tooltip.TooltipType
+import net.minecraft.text.Text
import moe.nea.firmament.compat.rei.FirmamentReiPlugin.Companion.asItemEntry
import moe.nea.firmament.events.ItemTooltipEvent
import moe.nea.firmament.repo.SBItemStack
import moe.nea.firmament.util.ErrorUtil
-import moe.nea.firmament.util.MC
+import moe.nea.firmament.util.FirmFormatters
+import moe.nea.firmament.util.darkGrey
import moe.nea.firmament.util.mc.displayNameAccordingToNbt
import moe.nea.firmament.util.mc.loreAccordingToNbt
-object NEUItemEntryRenderer : EntryRenderer<SBItemStack>, BatchedEntryRenderer<SBItemStack, BakedModel> {
+// TODO: make this re implement BatchedEntryRenderer, if possible (likely not, due to no-alloc rendering)
+// Also it is probably not even that much faster now, with render layers.
+object NEUItemEntryRenderer : EntryRenderer<SBItemStack> {
override fun render(
entry: EntryStack<SBItemStack>,
context: DrawContext,
@@ -46,11 +39,21 @@ object NEUItemEntryRenderer : EntryRenderer<SBItemStack>, BatchedEntryRenderer<S
mouseY: Int,
delta: Float
) {
- entry.asItemEntry().render(context, bounds, mouseX, mouseY, delta)
+ context.matrices.push()
+ context.matrices.translate(bounds.centerX.toFloat(), bounds.centerY.toFloat(), 0F)
+ context.matrices.scale(bounds.width.toFloat() / 16F, bounds.height.toFloat() / 16F, 1f)
+ val item = entry.asItemEntry().value
+ context.drawItemWithoutEntity(item, -8, -8)
+ context.drawStackOverlay(minecraft.textRenderer, item, -8, -8,
+ if (entry.value.getStackSize() > 1000) FirmFormatters.shortFormat(entry.value.getStackSize()
+ .toDouble())
+ else null
+ )
+ context.matrices.pop()
}
val minecraft = MinecraftClient.getInstance()
- var canUseVanillaTooltipEvents = false
+ var canUseVanillaTooltipEvents = true
override fun getTooltip(entry: EntryStack<SBItemStack>, tooltipContext: TooltipContext): Tooltip? {
val stack = entry.value.asImmutableItemStack()
@@ -63,6 +66,7 @@ object NEUItemEntryRenderer : EntryRenderer<SBItemStack>, BatchedEntryRenderer<S
stack, tooltipContext.vanillaContext(), TooltipType.BASIC, lore
)
} catch (ex: Exception) {
+ canUseVanillaTooltipEvents = false
ErrorUtil.softError("Failed to use vanilla tooltips", ex)
}
} else {
@@ -73,6 +77,8 @@ object NEUItemEntryRenderer : EntryRenderer<SBItemStack>, BatchedEntryRenderer<S
lore
))
}
+ if (entry.value.getStackSize() > 1000 && lore.isNotEmpty())
+ lore.add(1, Text.literal("${entry.value.getStackSize()}x").darkGrey())
// TODO: tags aren't sent as early now so some tooltip components that use tags will crash the game
// stack.getTooltip(
// Item.TooltipContext.create(
@@ -85,88 +91,5 @@ object NEUItemEntryRenderer : EntryRenderer<SBItemStack>, BatchedEntryRenderer<S
return Tooltip.create(lore)
}
- override fun getExtraData(entry: EntryStack<SBItemStack>): BakedModel {
- return MC.itemRenderer.getModel(entry.asItemEntry().value,
- MC.world,
- MC.player, 0)
-
- }
-
- override fun getBatchIdentifier(entry: EntryStack<SBItemStack>, bounds: Rectangle?, extraData: BakedModel): Int {
- return 1738923 + if (extraData.isSideLit) 1 else 0
- }
-
-
- override fun startBatch(entryStack: EntryStack<SBItemStack>, e: BakedModel, drawContext: DrawContext, v: Float) {
- MC.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)
- if (!e.isSideLit) {
- DiffuseLighting.disableGuiDepthLighting()
- }
- }
-
- override fun renderBase(
- entryStack: EntryStack<SBItemStack>,
- model: BakedModel,
- drawContext: DrawContext,
- immediate: VertexConsumerProvider.Immediate,
- bounds: Rectangle,
- i: Int,
- i1: Int,
- v: Float
- ) {
- if (entryStack.isEmpty) return
- drawContext.matrices.push()
- drawContext.matrices.translate(bounds.centerX.toDouble(), bounds.centerY.toDouble(), 0.0)
- // TODO: check the scaling here again
- drawContext.matrices.scale(
- bounds.width.toFloat(),
- (bounds.height + bounds.height) / -2F,
- (bounds.width + bounds.height) / 2f)
- MC.itemRenderer.renderItem(
- entryStack.value.asImmutableItemStack(),
- ModelTransformationMode.GUI,
- false, drawContext.matrices,
- immediate, LightmapTextureManager.MAX_LIGHT_COORDINATE,
- OverlayTexture.DEFAULT_UV,
- model
- )
- drawContext.matrices.pop()
- }
-
- override fun afterBase(entryStack: EntryStack<SBItemStack>?, e: BakedModel, drawContext: DrawContext?, v: Float) {
- RenderSystem.enableDepthTest()
- if (!e.isSideLit)
- DiffuseLighting.enableGuiDepthLighting()
- }
-
- override fun renderOverlay(
- entryStack: EntryStack<SBItemStack>,
- e: BakedModel,
- drawContext: DrawContext,
- immediate: VertexConsumerProvider.Immediate,
- bounds: Rectangle,
- i: Int,
- i1: Int,
- v: Float
- ) {
- if (entryStack.isEmpty) return
- val modelViewStack = RenderSystem.getModelViewStack()
- modelViewStack.pushMatrix()
- modelViewStack.mul(drawContext.matrices.peek().positionMatrix)
- modelViewStack.translate(bounds.x.toFloat(), bounds.y.toFloat(), 0F)
- modelViewStack.scale(bounds.width / 16.0f,
- (bounds.width + bounds.height) / 2.0f / 16.0f,
- 1.0f) // TODO: weird scale again
- drawContext.drawStackOverlay(MC.font, entryStack.value.asImmutableItemStack(), 0, 0, null)
- modelViewStack.popMatrix()
- }
-
- override fun endBatch(entryStack: EntryStack<SBItemStack>?, e: BakedModel?, drawContext: DrawContext?, v: Float) {
- }
}
diff --git a/src/compat/rei/java/moe/nea/firmament/compat/rei/SBItemEntryDefinition.kt b/src/compat/rei/java/moe/nea/firmament/compat/rei/SBItemEntryDefinition.kt
index a242c1b..2b1700d 100644
--- a/src/compat/rei/java/moe/nea/firmament/compat/rei/SBItemEntryDefinition.kt
+++ b/src/compat/rei/java/moe/nea/firmament/compat/rei/SBItemEntryDefinition.kt
@@ -9,17 +9,15 @@ 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.ItemConvertible
import net.minecraft.item.ItemStack
import net.minecraft.registry.tag.TagKey
import net.minecraft.text.Text
import net.minecraft.util.Identifier
import moe.nea.firmament.compat.rei.FirmamentReiPlugin.Companion.asItemEntry
-import moe.nea.firmament.repo.PetData
import moe.nea.firmament.repo.RepoManager
import moe.nea.firmament.repo.SBItemStack
import moe.nea.firmament.util.SkyblockId
-import moe.nea.firmament.util.petData
-import moe.nea.firmament.util.skyBlockId
object SBItemEntryDefinition : EntryDefinition<SBItemStack> {
override fun equals(o1: SBItemStack, o2: SBItemStack, context: ComparisonContext): Boolean {
@@ -54,7 +52,7 @@ object SBItemEntryDefinition : EntryDefinition<SBItemStack> {
override fun wildcard(entry: EntryStack<SBItemStack>?, value: SBItemStack): SBItemStack {
return value.copy(stackSize = 1, petData = RepoManager.getPotentialStubPetData(value.skyblockId),
- stars = 0, extraLore = listOf())
+ stars = 0, extraLore = listOf(), reforge = null)
}
override fun normalize(entry: EntryStack<SBItemStack>?, value: SBItemStack): SBItemStack {
@@ -82,13 +80,8 @@ object SBItemEntryDefinition : EntryDefinition<SBItemStack> {
fun getEntry(ingredient: NEUIngredient): EntryStack<SBItemStack> =
getEntry(SkyblockId(ingredient.itemId), count = ingredient.amount.toInt())
+ fun getPassthrough(item: ItemConvertible) = getEntry(SBItemStack.passthrough(ItemStack(item.asItem())))
+
fun getEntry(stack: ItemStack): EntryStack<SBItemStack> =
- getEntry(
- SBItemStack(
- stack.skyBlockId ?: SkyblockId.NULL,
- RepoManager.getNEUItem(stack.skyBlockId ?: SkyblockId.NULL),
- stack.count,
- petData = stack.petData?.let { PetData.fromHypixel(it) }
- )
- )
+ getEntry(SBItemStack(stack))
}
diff --git a/src/compat/rei/java/moe/nea/firmament/compat/rei/SkyblockCraftingRecipeDynamicGenerator.kt b/src/compat/rei/java/moe/nea/firmament/compat/rei/SkyblockCraftingRecipeDynamicGenerator.kt
index f52f418..e80840f 100644
--- a/src/compat/rei/java/moe/nea/firmament/compat/rei/SkyblockCraftingRecipeDynamicGenerator.kt
+++ b/src/compat/rei/java/moe/nea/firmament/compat/rei/SkyblockCraftingRecipeDynamicGenerator.kt
@@ -1,11 +1,10 @@
-
-
package moe.nea.firmament.compat.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.NEUNpcShopRecipe
import io.github.moulberry.repo.data.NEURecipe
import java.util.Optional
import me.shedaniel.rei.api.client.registry.display.DynamicDisplayGenerator
@@ -17,49 +16,51 @@ import moe.nea.firmament.compat.rei.recipes.SBEssenceUpgradeRecipe
import moe.nea.firmament.compat.rei.recipes.SBForgeRecipe
import moe.nea.firmament.compat.rei.recipes.SBKatRecipe
import moe.nea.firmament.compat.rei.recipes.SBMobDropRecipe
+import moe.nea.firmament.compat.rei.recipes.SBShopRecipe
import moe.nea.firmament.repo.EssenceRecipeProvider
import moe.nea.firmament.repo.RepoManager
import moe.nea.firmament.repo.SBItemStack
val SkyblockCraftingRecipeDynamicGenerator =
- neuDisplayGenerator<SBCraftingRecipe, NEUCraftingRecipe> { SBCraftingRecipe(it) }
+ neuDisplayGenerator<SBCraftingRecipe, NEUCraftingRecipe> { SBCraftingRecipe(it) }
val SkyblockForgeRecipeDynamicGenerator =
- neuDisplayGenerator<SBForgeRecipe, NEUForgeRecipe> { SBForgeRecipe(it) }
+ neuDisplayGenerator<SBForgeRecipe, NEUForgeRecipe> { SBForgeRecipe(it) }
val SkyblockMobDropRecipeDynamicGenerator =
- neuDisplayGenerator<SBMobDropRecipe, NEUMobDropRecipe> { SBMobDropRecipe(it) }
-
+ neuDisplayGenerator<SBMobDropRecipe, NEUMobDropRecipe> { SBMobDropRecipe(it) }
+val SkyblockShopRecipeDynamicGenerator =
+ neuDisplayGenerator<SBShopRecipe, NEUNpcShopRecipe> { SBShopRecipe(it) }
val SkyblockKatRecipeDynamicGenerator =
- neuDisplayGenerator<SBKatRecipe, NEUKatUpgradeRecipe> { SBKatRecipe(it) }
+ neuDisplayGenerator<SBKatRecipe, NEUKatUpgradeRecipe> { SBKatRecipe(it) }
val SkyblockEssenceRecipeDynamicGenerator =
- neuDisplayGenerator<SBEssenceUpgradeRecipe, EssenceRecipeProvider.EssenceUpgradeRecipe> { SBEssenceUpgradeRecipe(it) }
+ neuDisplayGeneratorWithItem<SBEssenceUpgradeRecipe, EssenceRecipeProvider.EssenceUpgradeRecipe> { item, recipe ->
+ SBEssenceUpgradeRecipe(recipe, item)
+ }
inline fun <D : Display, reified T : NEURecipe> neuDisplayGenerator(crossinline mapper: (T) -> D) =
- object : DynamicDisplayGenerator<D> {
- override fun getRecipeFor(entry: EntryStack<*>): Optional<List<D>> {
- if (entry.type != SBItemEntryDefinition.type) return Optional.empty()
- val item = entry.castValue<SBItemStack>()
- val recipes = RepoManager.getRecipesFor(item.skyblockId)
- val craftingRecipes = recipes.filterIsInstance<T>()
- return Optional.of(craftingRecipes.map(mapper))
- }
+ neuDisplayGeneratorWithItem<D, T> { _, it -> mapper(it) }
- override fun generate(builder: ViewSearchBuilder): Optional<List<D>> {
- if (SBCraftingRecipe.Category.catIdentifier !in builder.categories) return Optional.empty()
- return Optional.of(
- RepoManager.getAllRecipes().filterIsInstance<T>().map { mapper(it) }
- .toList()
- )
- }
+inline fun <D : Display, reified T : NEURecipe> neuDisplayGeneratorWithItem(crossinline mapper: (SBItemStack, T) -> D) =
+ object : DynamicDisplayGenerator<D> {
+ override fun getRecipeFor(entry: EntryStack<*>): Optional<List<D>> {
+ if (entry.type != SBItemEntryDefinition.type) return Optional.empty()
+ val item = entry.castValue<SBItemStack>()
+ val recipes = RepoManager.getRecipesFor(item.skyblockId)
+ val craftingRecipes = recipes.filterIsInstance<T>()
+ return Optional.of(craftingRecipes.map { mapper(item, it) })
+ }
- override fun getUsageFor(entry: EntryStack<*>): Optional<List<D>> {
- if (entry.type != SBItemEntryDefinition.type) return Optional.empty()
- val item = entry.castValue<SBItemStack>()
- val recipes = RepoManager.getUsagesFor(item.skyblockId)
- val craftingRecipes = recipes.filterIsInstance<T>()
- return Optional.of(craftingRecipes.map(mapper))
+ override fun generate(builder: ViewSearchBuilder): Optional<List<D>> {
+ return Optional.empty() // TODO: allows searching without blocking getRecipeFor
+ }
- }
- }
+ override fun getUsageFor(entry: EntryStack<*>): Optional<List<D>> {
+ if (entry.type != SBItemEntryDefinition.type) return Optional.empty()
+ val item = entry.castValue<SBItemStack>()
+ val recipes = RepoManager.getUsagesFor(item.skyblockId)
+ val craftingRecipes = recipes.filterIsInstance<T>()
+ return Optional.of(craftingRecipes.map { mapper(item, it) })
+ }
+ }
diff --git a/src/compat/rei/java/moe/nea/firmament/compat/rei/SkyblockItemIdFocusedStackProvider.kt b/src/compat/rei/java/moe/nea/firmament/compat/rei/SkyblockItemIdFocusedStackProvider.kt
index cfb6f74..518f7b4 100644
--- a/src/compat/rei/java/moe/nea/firmament/compat/rei/SkyblockItemIdFocusedStackProvider.kt
+++ b/src/compat/rei/java/moe/nea/firmament/compat/rei/SkyblockItemIdFocusedStackProvider.kt
@@ -17,8 +17,7 @@ object SkyblockItemIdFocusedStackProvider : FocusedStackProvider {
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))
+ return CompoundEventResult.interruptTrue(SBItemEntryDefinition.getEntry(item))
}
override fun getPriority(): Double = 1_000_000.0
diff --git a/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBCraftingRecipe.kt b/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBCraftingRecipe.kt
index fd04abc..c02e078 100644
--- a/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBCraftingRecipe.kt
+++ b/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBCraftingRecipe.kt
@@ -16,17 +16,18 @@ import net.minecraft.block.Blocks
import net.minecraft.text.Text
import moe.nea.firmament.Firmament
import moe.nea.firmament.compat.rei.SBItemEntryDefinition
+import moe.nea.firmament.repo.SBItemStack
class SBCraftingRecipe(override val neuRecipe: NEUCraftingRecipe) : SBRecipe() {
override fun getCategoryIdentifier(): CategoryIdentifier<*> = Category.catIdentifier
object Category : DisplayCategory<SBCraftingRecipe> {
- val catIdentifier = CategoryIdentifier.of<SBCraftingRecipe>(Firmament.MOD_ID, "crafing_recipe")
+ val catIdentifier = CategoryIdentifier.of<SBCraftingRecipe>(Firmament.MOD_ID, "crafting_recipe")
override fun getCategoryIdentifier(): CategoryIdentifier<out SBCraftingRecipe> = catIdentifier
override fun getTitle(): Text = Text.literal("SkyBlock Crafting")
- override fun getIcon(): Renderer = EntryStacks.of(Blocks.CRAFTING_TABLE)
+ override fun getIcon(): Renderer = SBItemEntryDefinition.getPassthrough(Blocks.CRAFTING_TABLE)
override fun setupDisplay(display: SBCraftingRecipe, bounds: Rectangle): List<Widget> {
val point = Point(bounds.centerX - 58, bounds.centerY - 27)
return buildList {
@@ -39,7 +40,7 @@ class SBCraftingRecipe(override val neuRecipe: NEUCraftingRecipe) : SBRecipe() {
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
+ slot.entry(SBItemEntryDefinition.getEntry(item))
}
}
add(
diff --git a/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBEssenceUpgradeRecipe.kt b/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBEssenceUpgradeRecipe.kt
index ec71ec8..e0a3784 100644
--- a/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBEssenceUpgradeRecipe.kt
+++ b/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBEssenceUpgradeRecipe.kt
@@ -14,7 +14,8 @@ import moe.nea.firmament.repo.EssenceRecipeProvider
import moe.nea.firmament.repo.SBItemStack
import moe.nea.firmament.util.SkyblockId
-class SBEssenceUpgradeRecipe(override val neuRecipe: EssenceRecipeProvider.EssenceUpgradeRecipe) : SBRecipe() {
+class SBEssenceUpgradeRecipe(override val neuRecipe: EssenceRecipeProvider.EssenceUpgradeRecipe,
+ val sourceItem: SBItemStack) : SBRecipe() {
object Category : DisplayCategory<SBEssenceUpgradeRecipe> {
override fun getCategoryIdentifier(): CategoryIdentifier<SBEssenceUpgradeRecipe> =
CategoryIdentifier.of(Firmament.MOD_ID, "essence_upgrade")
@@ -33,13 +34,13 @@ class SBEssenceUpgradeRecipe(override val neuRecipe: EssenceRecipeProvider.Essen
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))))
+ .entry(SBItemEntryDefinition.getEntry(display.sourceItem.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))))
+ .entry(SBItemEntryDefinition.getEntry(display.sourceItem.copy(stars = recipe.starCountAfter))))
val extraItems = recipe.extraItems
list.add(Widgets.createArrow(Point(bounds.centerX - 24 / 2,
if (extraItems.isEmpty()) bounds.centerY - 17 / 2
diff --git a/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBForgeRecipe.kt b/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBForgeRecipe.kt
index 96af3fd..7a0ec78 100644
--- a/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBForgeRecipe.kt
+++ b/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBForgeRecipe.kt
@@ -8,7 +8,6 @@ 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
@@ -30,7 +29,7 @@ class SBForgeRecipe(override val neuRecipe: NEUForgeRecipe) : SBRecipe() {
return 104
}
- override fun getIcon(): Renderer = EntryStacks.of(Blocks.ANVIL)
+ override fun getIcon(): Renderer = SBItemEntryDefinition.getPassthrough(Blocks.ANVIL)
override fun setupDisplay(display: SBForgeRecipe, bounds: Rectangle): List<Widget> {
return buildList {
add(Widgets.createRecipeBase(bounds))
@@ -41,6 +40,7 @@ class SBForgeRecipe(override val neuRecipe: NEUForgeRecipe) : SBRecipe() {
Text.stringifiedTranslatable("firmament.recipe.forge.time",
display.neuRecipe.duration.seconds)))
val ingredientsCenter = Point(bounds.minX + 49 - 8, bounds.minY + 54 - 8)
+ add(Widgets.createBurningFire(ingredientsCenter).animationDurationTicks(25.0))
val count = display.neuRecipe.inputs.size
if (count == 1) {
add(
diff --git a/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBKatRecipe.kt b/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBKatRecipe.kt
index bafbdcc..cce1465 100644
--- a/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBKatRecipe.kt
+++ b/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBKatRecipe.kt
@@ -43,7 +43,7 @@ class SBKatRecipe(override val neuRecipe: NEUKatUpgradeRecipe) : SBRecipe() {
return 100
}
- override fun getIcon(): Renderer = EntryStacks.of(Items.BONE)
+ override fun getIcon(): Renderer = SBItemEntryDefinition.getPassthrough(Items.BONE)
override fun setupDisplay(display: SBKatRecipe, bounds: Rectangle): List<Widget> {
return buildList {
val arrowWidth = 24
diff --git a/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBMobDropRecipe.kt b/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBMobDropRecipe.kt
index b05c3c7..b595c23 100644
--- a/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBMobDropRecipe.kt
+++ b/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBMobDropRecipe.kt
@@ -29,7 +29,7 @@ class SBMobDropRecipe(override val neuRecipe: NEUMobDropRecipe) : SBRecipe() {
return 100
}
- override fun getIcon(): Renderer = EntryStacks.of(Items.DIAMOND_SWORD)
+ override fun getIcon(): Renderer = SBItemEntryDefinition.getPassthrough(Items.DIAMOND_SWORD)
override fun setupDisplay(display: SBMobDropRecipe, bounds: Rectangle): List<Widget> {
return buildList {
add(Widgets.createRecipeBase(bounds))
diff --git a/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBReforgeRecipe.kt b/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBReforgeRecipe.kt
new file mode 100644
index 0000000..b8313a6
--- /dev/null
+++ b/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBReforgeRecipe.kt
@@ -0,0 +1,212 @@
+package moe.nea.firmament.compat.rei.recipes
+
+import java.util.Optional
+import me.shedaniel.math.Dimension
+import me.shedaniel.math.FloatingDimension
+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.Label
+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.client.registry.display.DynamicDisplayGenerator
+import me.shedaniel.rei.api.client.view.ViewSearchBuilder
+import me.shedaniel.rei.api.common.category.CategoryIdentifier
+import me.shedaniel.rei.api.common.display.Display
+import me.shedaniel.rei.api.common.display.DisplaySerializer
+import me.shedaniel.rei.api.common.entry.EntryIngredient
+import me.shedaniel.rei.api.common.entry.EntryStack
+import net.minecraft.entity.EntityType
+import net.minecraft.entity.SpawnReason
+import net.minecraft.text.Text
+import net.minecraft.util.Identifier
+import net.minecraft.village.VillagerProfession
+import moe.nea.firmament.Firmament
+import moe.nea.firmament.compat.rei.EntityWidget
+import moe.nea.firmament.compat.rei.SBItemEntryDefinition
+import moe.nea.firmament.gui.entity.EntityRenderer
+import moe.nea.firmament.repo.Reforge
+import moe.nea.firmament.repo.ReforgeStore
+import moe.nea.firmament.repo.RepoItemTypeCache
+import moe.nea.firmament.repo.RepoManager
+import moe.nea.firmament.repo.SBItemStack
+import moe.nea.firmament.util.AprilFoolsUtil
+import moe.nea.firmament.util.FirmFormatters
+import moe.nea.firmament.util.SkyblockId
+import moe.nea.firmament.util.gold
+import moe.nea.firmament.util.grey
+import moe.nea.firmament.util.skyblock.ItemType
+import moe.nea.firmament.util.skyblock.Rarity
+import moe.nea.firmament.util.skyblock.SkyBlockItems
+import moe.nea.firmament.util.skyblockId
+import moe.nea.firmament.util.tr
+
+class SBReforgeRecipe(
+ val reforge: Reforge,
+ val limitToItem: SBItemStack?,
+) : Display {
+ companion object {
+ val catIdentifier = CategoryIdentifier.of<SBReforgeRecipe>(Firmament.MOD_ID, "reforge_recipe")
+ }
+
+ object Category : DisplayCategory<SBReforgeRecipe> {
+ override fun getCategoryIdentifier(): CategoryIdentifier<out SBReforgeRecipe> {
+ return catIdentifier
+ }
+
+ override fun getTitle(): Text {
+ return tr("firmament.recipecategory.reforge", "Reforge")
+ }
+
+ override fun getIcon(): Renderer {
+ return SBItemEntryDefinition.getEntry(SkyBlockItems.REFORGE_ANVIL)
+ }
+
+ override fun setupDisplay(display: SBReforgeRecipe, bounds: Rectangle): MutableList<Widget> {
+ val list = mutableListOf<Widget>()
+ list.add(Widgets.createRecipeBase(bounds))
+ val inputSlot = Widgets.createSlot(Point(bounds.minX + 10, bounds.centerY - 9))
+ .markInput().entries(display.inputItems)
+ list.add(inputSlot)
+ if (display.reforgeStone != null) {
+ list.add(Widgets.createSlot(Point(bounds.minX + 10 + 24, bounds.centerY - 9 - 10))
+ .markInput().entry(display.reforgeStone))
+ list.add(Widgets.withTooltip(
+ Widgets.withTranslate(Widgets.wrapRenderer(
+ Rectangle(Point(bounds.minX + 10 + 24, bounds.centerY - 9 + 10), Dimension(16, 16)),
+ SBItemEntryDefinition.getEntry(SkyBlockItems.REFORGE_ANVIL)), 0.0, 0.0, 150.0),
+ Rarity.entries.mapNotNull { rarity ->
+ display.reforge.reforgeCosts?.get(rarity)?.let { rarity to it }
+ }.map { (rarity, cost) ->
+ Text.literal("")
+ .append(rarity.text)
+ .append(": ")
+ .append(Text.literal("${FirmFormatters.formatCommas(cost, 0)} Coins").gold())
+ }
+ ))
+ } else {
+ val size = if (AprilFoolsUtil.isAprilFoolsDay) 1.2 else 0.6
+ val dimension =
+ FloatingDimension(EntityWidget.defaultSize.width * size, EntityWidget.defaultSize.height * size)
+ list.add(Widgets.withTooltip(
+ EntityWidget(
+ EntityType.VILLAGER.create(EntityRenderer.fakeWorld, SpawnReason.COMMAND)
+ ?.also { it.villagerData = it.villagerData.withProfession(VillagerProfession.WEAPONSMITH) },
+ Point(bounds.minX + 10 + 24 + 8 - dimension.width / 2, bounds.centerY - dimension.height / 2),
+ dimension
+ ),
+ tr("firmament.recipecategory.reforge.basic",
+ "This is a basic reforge, available at the Blacksmith.").grey()
+ ))
+ }
+ list.add(Widgets.createSlot(Point(bounds.minX + 10 + 24 + 24, bounds.centerY - 9))
+ .markInput().entries(display.outputItems))
+ val statToLineMappings = mutableListOf<Pair<String, Label>>()
+ for ((i, statId) in display.reforge.statUniverse.withIndex()) {
+ val label = Widgets.createLabel(
+ Point(bounds.minX + 10 + 24 + 24 + 20, bounds.minY + 8 + i * 11),
+ SBItemStack.Companion.StatLine(SBItemStack.statIdToName(statId), null).reconstitute(7))
+ .horizontalAlignment(Label.LEFT_ALIGNED)
+ statToLineMappings.add(statId to label)
+ list.add(label)
+ }
+ fun updateStatLines() {
+ val entry = inputSlot.currentEntry?.castValue<SBItemStack>() ?: return
+ val stats = display.reforge.reforgeStats?.get(entry.rarity) ?: mapOf()
+ for ((stat, label) in statToLineMappings) {
+ label.message =
+ SBItemStack.Companion.StatLine(
+ SBItemStack.statIdToName(stat), null,
+ valueNum = stats[stat]
+ ).reconstitute(7)
+ }
+ }
+ updateStatLines()
+ inputSlot.withEntriesListener { updateStatLines() }
+ return list
+ }
+ }
+
+ object DynamicGenerator : DynamicDisplayGenerator<SBReforgeRecipe> {
+ fun getRecipesForSBItemStack(item: SBItemStack): Optional<List<SBReforgeRecipe>> {
+ val reforgeRecipes = mutableListOf<SBReforgeRecipe>()
+ for (reforge in ReforgeStore.findEligibleForInternalName(item.skyblockId)) {
+ reforgeRecipes.add(SBReforgeRecipe(reforge, item))
+ }
+ for (reforge in ReforgeStore.findEligibleForItem(item.itemType ?: ItemType.NIL)) {
+ reforgeRecipes.add(SBReforgeRecipe(reforge, item))
+ }
+ if (reforgeRecipes.isEmpty()) return Optional.empty()
+ return Optional.of(reforgeRecipes)
+ }
+
+ override fun getRecipeFor(entry: EntryStack<*>): Optional<List<SBReforgeRecipe>> {
+ if (entry.type != SBItemEntryDefinition.type) return Optional.empty()
+ val item = entry.castValue<SBItemStack>()
+ return getRecipesForSBItemStack(item)
+ }
+
+ override fun getUsageFor(entry: EntryStack<*>): Optional<List<SBReforgeRecipe>> {
+ if (entry.type != SBItemEntryDefinition.type) return Optional.empty()
+ val item = entry.castValue<SBItemStack>()
+ ReforgeStore.byReforgeStone[item.skyblockId]?.let { stoneReforge ->
+ return Optional.of(listOf(SBReforgeRecipe(stoneReforge, null)))
+ }
+ return getRecipesForSBItemStack(item)
+ }
+
+ override fun generate(builder: ViewSearchBuilder): Optional<List<SBReforgeRecipe>> {
+ // TODO: check builder.recipesFor and such and optionally return all reforge recipes
+ return Optional.empty()
+ }
+ }
+
+ private val inputItems = run {
+ if (limitToItem != null) return@run listOf(SBItemEntryDefinition.getEntry(limitToItem))
+ val eligibleItems = reforge.eligibleItems.flatMap {
+ when (it) {
+ is Reforge.ReforgeEligibilityFilter.AllowsInternalName ->
+ listOfNotNull(RepoManager.getNEUItem(it.internalName))
+
+ is Reforge.ReforgeEligibilityFilter.AllowsItemType ->
+ ReforgeStore.resolveItemType(it.itemType)
+ .flatMapTo(mutableSetOf()) {
+ (RepoItemTypeCache.byItemType[it] ?: listOf()) +
+ (RepoItemTypeCache.byItemType[it.dungeonVariant] ?: listOf())
+ }.toList()
+
+ is Reforge.ReforgeEligibilityFilter.AllowsVanillaItemType -> {
+ listOf() // TODO: add filter support for this and potentially rework this to search for the declared item type in repo, instead of remapped item type
+ }
+ }
+ }
+ eligibleItems.map { SBItemEntryDefinition.getEntry(it.skyblockId) }
+ }
+ private val outputItems =
+ inputItems.map { SBItemEntryDefinition.getEntry(it.value.copy(reforge = reforge.reforgeId)) }
+ private val reforgeStone = reforge.reforgeStone?.let(SBItemEntryDefinition::getEntry)
+ private val inputEntries =
+ listOf(EntryIngredient.of(inputItems)) + listOfNotNull(reforgeStone?.let(EntryIngredient::of))
+ private val outputEntries = listOf(EntryIngredient.of(outputItems))
+
+ override fun getInputEntries(): List<EntryIngredient> {
+ return inputEntries
+ }
+
+ override fun getOutputEntries(): List<EntryIngredient> {
+ return outputEntries
+ }
+
+ override fun getCategoryIdentifier(): CategoryIdentifier<*> {
+ return catIdentifier
+ }
+
+ override fun getDisplayLocation(): Optional<Identifier> {
+ return Optional.empty()
+ }
+
+ override fun getSerializer(): DisplaySerializer<out Display>? {
+ return null
+ }
+}
diff --git a/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBShopRecipe.kt b/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBShopRecipe.kt
new file mode 100644
index 0000000..a252802
--- /dev/null
+++ b/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBShopRecipe.kt
@@ -0,0 +1,61 @@
+package moe.nea.firmament.compat.rei.recipes
+
+import io.github.moulberry.repo.data.NEUNpcShopRecipe
+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.entry.EntryIngredient
+import net.minecraft.item.Items
+import net.minecraft.text.Text
+import moe.nea.firmament.Firmament
+import moe.nea.firmament.compat.rei.SBItemEntryDefinition
+import moe.nea.firmament.util.skyblockId
+
+class SBShopRecipe(override val neuRecipe: NEUNpcShopRecipe) : SBRecipe() {
+ override fun getCategoryIdentifier(): CategoryIdentifier<*> = Category.catIdentifier
+ val merchant = SBItemEntryDefinition.getEntry(neuRecipe.isSoldBy.skyblockId)
+ override fun getInputEntries(): List<EntryIngredient> {
+ return listOf(EntryIngredient.of(merchant)) + super.getInputEntries()
+ }
+
+ object Category : DisplayCategory<SBShopRecipe> {
+ val catIdentifier = CategoryIdentifier.of<SBShopRecipe>(Firmament.MOD_ID, "npc_shopping")
+ override fun getCategoryIdentifier(): CategoryIdentifier<SBShopRecipe> = catIdentifier
+
+ override fun getTitle(): Text = Text.literal("SkyBlock NPC Shopping")
+
+ override fun getIcon(): Renderer = SBItemEntryDefinition.getPassthrough(Items.EMERALD)
+ override fun setupDisplay(display: SBShopRecipe, bounds: Rectangle): List<Widget> {
+ val point = Point(bounds.centerX, bounds.centerY)
+ return buildList {
+ add(Widgets.createRecipeBase(bounds))
+ add(Widgets.createSlot(Point(point.x - 2 - 18 / 2, point.y - 18 - 6))
+ .unmarkInputOrOutput()
+ .entry(display.merchant)
+ .disableBackground())
+ add(Widgets.createArrow(Point(point.x - 2 - 24 / 2, point.y - 6)))
+ val cost = display.neuRecipe.cost
+ for ((i, item) in cost.withIndex()) {
+ add(Widgets.createSlot(Point(
+ point.x - 14 - 18,
+ point.y + i * 18 - 18 * cost.size / 2))
+ .entry(SBItemEntryDefinition.getEntry(item))
+ .markInput())
+ // TODO: fix frame clipping
+ }
+ add(Widgets.createResultSlotBackground(Point(point.x + 18, point.y - 18 / 2)))
+ add(
+ Widgets.createSlot(Point(point.x + 18, point.y - 18 / 2))
+ .entry(SBItemEntryDefinition.getEntry(display.neuRecipe.result))
+ .disableBackground().markOutput()
+ )
+ }
+ }
+
+ }
+
+}
diff --git a/src/compat/rei/java/moe/nea/firmament/mixins/compat/HideREIRecipeWarning.java b/src/compat/rei/java/moe/nea/firmament/mixins/compat/HideREIRecipeWarning.java
new file mode 100644
index 0000000..14eeaf2
--- /dev/null
+++ b/src/compat/rei/java/moe/nea/firmament/mixins/compat/HideREIRecipeWarning.java
@@ -0,0 +1,20 @@
+package moe.nea.firmament.mixins.compat;
+
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Pseudo;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+@Mixin(targets = "me.shedaniel.rei.impl.client.gui.hints.ImportantWarningsWidget")
+@Pseudo
+public class HideREIRecipeWarning {
+ @Shadow
+ private boolean visible;
+
+ @Inject(method = "<init>", at = @At("TAIL"))
+ private void onCreateImportantWidget(CallbackInfo ci) {
+ visible = false;
+ }
+}
diff --git a/src/compat/wildfireGender/java/moe/nea/firmament/mixins/compat/wildfiregender/PatchArmorTexturesInGenderMod.java b/src/compat/wildfireGender/java/moe/nea/firmament/mixins/compat/wildfiregender/PatchArmorTexturesInGenderMod.java
index 723af59..c3e8950 100644
--- a/src/compat/wildfireGender/java/moe/nea/firmament/mixins/compat/wildfiregender/PatchArmorTexturesInGenderMod.java
+++ b/src/compat/wildfireGender/java/moe/nea/firmament/mixins/compat/wildfiregender/PatchArmorTexturesInGenderMod.java
@@ -1,14 +1,12 @@
package moe.nea.firmament.mixins.compat.wildfiregender;
-import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
-import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
+import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.sugar.Local;
import com.wildfire.render.GenderArmorLayer;
import moe.nea.firmament.features.texturepack.CustomGlobalArmorOverrides;
-import net.minecraft.item.ArmorItem;
-import net.minecraft.item.ArmorMaterial;
+import net.minecraft.component.type.EquippableComponent;
+import net.minecraft.entity.EquipmentSlot;
import net.minecraft.item.ItemStack;
-import net.minecraft.registry.entry.RegistryEntry;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Pseudo;
import org.spongepowered.asm.mixin.injection.At;
@@ -16,22 +14,10 @@ import org.spongepowered.asm.mixin.injection.At;
@Mixin(GenderArmorLayer.class)
@Pseudo
public class PatchArmorTexturesInGenderMod {
- @WrapOperation(method = "render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/entity/LivingEntity;FFFFFF)V",
- at = @At(value = "INVOKE", target = "Lnet/minecraft/item/ArmorItem;getMaterial()Lnet/minecraft/registry/entry/RegistryEntry;"))
- private RegistryEntry<ArmorMaterial> replaceArmorMaterial(ArmorItem instance, Operation<RegistryEntry<ArmorMaterial>> original, @Local ItemStack chestplate) {
- var entry = original.call(instance);
- var overrides = CustomGlobalArmorOverrides.overrideArmor(chestplate);
- if (overrides == null)
- return entry;
- var material = entry.value();
- return RegistryEntry.of(new ArmorMaterial(
- material.defense(),
- material.enchantability(),
- material.equipSound(),
- material.repairIngredient(),
- overrides,
- material.toughness(),
- material.knockbackResistance()
- ));
- }
+ @ModifyExpressionValue(method = "render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/client/render/entity/state/BipedEntityRenderState;FF)V",
+ at = @At(value = "INVOKE", target = "Lnet/minecraft/item/ItemStack;get(Lnet/minecraft/component/ComponentType;)Ljava/lang/Object;"))
+ private Object replaceArmorMaterial(Object original, @Local ItemStack chestplate) {
+ var overrides = CustomGlobalArmorOverrides.overrideArmor(chestplate, EquipmentSlot.CHEST);
+ return overrides.orElse((EquippableComponent) original);
+ }
}
diff --git a/src/compat/yacl/java/YaclIntegration.kt b/src/compat/yacl/java/YaclIntegration.kt
index 9aec501..45a0d02 100644
--- a/src/compat/yacl/java/YaclIntegration.kt
+++ b/src/compat/yacl/java/YaclIntegration.kt
@@ -11,9 +11,11 @@ import dev.isxander.yacl3.api.OptionGroup
import dev.isxander.yacl3.api.YetAnotherConfigLib
import dev.isxander.yacl3.api.controller.ControllerBuilder
import dev.isxander.yacl3.api.controller.DoubleSliderControllerBuilder
+import dev.isxander.yacl3.api.controller.EnumControllerBuilder
import dev.isxander.yacl3.api.controller.IntegerSliderControllerBuilder
import dev.isxander.yacl3.api.controller.StringControllerBuilder
import dev.isxander.yacl3.api.controller.TickBoxControllerBuilder
+import dev.isxander.yacl3.api.controller.ValueFormatter
import dev.isxander.yacl3.gui.YACLScreen
import dev.isxander.yacl3.gui.tab.ListHolderWidget
import kotlin.time.Duration
@@ -23,8 +25,10 @@ import net.minecraft.client.gui.Element
import net.minecraft.client.gui.screen.Screen
import net.minecraft.text.Text
import moe.nea.firmament.gui.config.BooleanHandler
+import moe.nea.firmament.gui.config.ChoiceHandler
import moe.nea.firmament.gui.config.ClickHandler
import moe.nea.firmament.gui.config.DurationHandler
+import moe.nea.firmament.gui.config.EnumRenderer
import moe.nea.firmament.gui.config.FirmamentConfigScreenProvider
import moe.nea.firmament.gui.config.HudMeta
import moe.nea.firmament.gui.config.HudMetaHandler
@@ -89,6 +93,10 @@ class YaclIntegration : FirmamentConfigScreenProvider {
}
.build()
+ is ChoiceHandler<*> -> return createDefaultBinding {
+ createChoiceBinding(handler as ChoiceHandler<*>, managedOption as ManagedOption<*>, it as Option<*>)
+ }.build()
+
is BooleanHandler -> return createDefaultBinding(TickBoxControllerBuilder::create).build()
is StringHandler -> return createDefaultBinding(StringControllerBuilder::create).build()
is IntegerHandler -> return createDefaultBinding {
@@ -114,6 +122,27 @@ class YaclIntegration : FirmamentConfigScreenProvider {
}
}
+ private enum class Sacrifice {}
+
+ private fun createChoiceBinding(
+ handler: ChoiceHandler<*>,
+ managedOption: ManagedOption<*>,
+ option: Option<*>
+ ): ControllerBuilder<Any> {
+ val b = EnumControllerBuilder.create(option as Option<Sacrifice>)
+ b.enumClass(handler.enumClass as Class<Sacrifice>)
+ /**
+ * This is a function with E to avoid realizing the Sacrifice outside of a `X<E>` wrapper.
+ */
+ fun <E : Enum<*>> makeValueFormatter(): ValueFormatter<E> {
+ return ValueFormatter<E> {
+ (handler.renderer as EnumRenderer<E>).getName(managedOption as ManagedOption<E>, it)
+ }
+ }
+ b.formatValue(makeValueFormatter())
+ return b as ControllerBuilder<Any>
+ }
+
fun buildConfig(): YetAnotherConfigLib {
return YetAnotherConfigLib.createBuilder()
diff --git a/src/main/java/moe/nea/firmament/init/AutoDiscoveryPlugin.java b/src/main/java/moe/nea/firmament/init/AutoDiscoveryPlugin.java
index 9b891a4..0713068 100644
--- a/src/main/java/moe/nea/firmament/init/AutoDiscoveryPlugin.java
+++ b/src/main/java/moe/nea/firmament/init/AutoDiscoveryPlugin.java
@@ -16,160 +16,168 @@ import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
public class AutoDiscoveryPlugin {
- private static final List<AutoDiscoveryPlugin> mixinPlugins = new ArrayList<>();
-
- public static List<AutoDiscoveryPlugin> 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<String> 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.
- * <p><b>This method cannot be called after mixin initialization.</p>
- *
- * @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<String> 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");
- var path = classRoot.resolve(getMixinBaseDir());
- if (!Files.exists(path)) return;
- try (Stream<Path> classes = Files.walk(path)) {
- 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);
- }
- }
+ public static List<String> getDefaultAllMixinClassesFQNs() {
+ var defaultName = "moe.nea.firmament.mixins";
+ var plugin = new AutoDiscoveryPlugin();
+ plugin.setMixinPackage(defaultName);
+ var mixins = plugin.getMixins();
+ return mixins.stream().map(it -> defaultName + "." + it).toList();
+ }
+
+ private static final List<AutoDiscoveryPlugin> mixinPlugins = new ArrayList<>();
+
+ public static List<AutoDiscoveryPlugin> 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<String> 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.
+ * <p><b>This method cannot be called after mixin initialization.</p>
+ *
+ * @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<String> 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");
+ var path = classRoot.resolve(getMixinBaseDir());
+ if (!Files.exists(path)) return;
+ try (Stream<Path> classes = Files.walk(path)) {
+ 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/EarlyRiser.java b/src/main/java/moe/nea/firmament/init/EarlyRiser.java
index 9734e94..5441255 100644
--- a/src/main/java/moe/nea/firmament/init/EarlyRiser.java
+++ b/src/main/java/moe/nea/firmament/init/EarlyRiser.java
@@ -7,6 +7,6 @@ public class EarlyRiser implements Runnable {
new ClientPlayerRiser().addTinkerers();
new HandledScreenRiser().addTinkerers();
new SectionBuilderRiser().addTinkerers();
- new ItemColorsSodiumRiser().addTinkerers();
+// TODO: new ItemColorsSodiumRiser().addTinkerers();
}
}
diff --git a/src/main/java/moe/nea/firmament/init/HandledScreenRiser.java b/src/main/java/moe/nea/firmament/init/HandledScreenRiser.java
index 1fbbe45..f7db18c 100644
--- a/src/main/java/moe/nea/firmament/init/HandledScreenRiser.java
+++ b/src/main/java/moe/nea/firmament/init/HandledScreenRiser.java
@@ -3,7 +3,6 @@ package moe.nea.firmament.init;
import me.shedaniel.mm.api.ClassTinkerers;
import net.minecraft.client.gui.Element;
-import net.minecraft.client.gui.ParentElement;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.ClassNode;
@@ -32,12 +31,18 @@ public class HandledScreenRiser extends RiserUtils {
String keyReleased = remapper.mapMethodName("intermediary", Intermediary.<Element>className(),
Intermediary.methodName(Element::keyReleased),
keyReleasedDesc.getDescriptor());
+ // public boolean charTyped(char chr, int modifiers)
+ Type charTypedDesc = Type.getMethodType(Type.BOOLEAN_TYPE, Type.CHAR_TYPE, Type.INT_TYPE);
+ String charTyped = remapper.mapMethodName("intermediary", Intermediary.<Element>className(),
+ Intermediary.methodName(Element::charTyped),
+ charTypedDesc.getDescriptor());
@Override
public void addTinkerers() {
ClassTinkerers.addTransformation(HandledScreen, this::addMouseScroll, true);
ClassTinkerers.addTransformation(HandledScreen, this::addKeyReleased, true);
+ ClassTinkerers.addTransformation(HandledScreen, this::addCharTyped, true);
}
/**
@@ -70,86 +75,77 @@ public class HandledScreenRiser extends RiserUtils {
}
void addKeyReleased(ClassNode classNode) {
- var keyReleasedNode = findMethod(classNode, keyReleased, keyReleasedDesc);
+ addSuperInjector(
+ classNode, keyReleased, keyReleasedDesc, "keyReleased_firmament",
+ insns -> {
+ // ALOAD 0, load this
+ insns.add(new VarInsnNode(Opcodes.ALOAD, 0));
+ // ILOAD 1-3, load args
+ insns.add(new VarInsnNode(Opcodes.ILOAD, 1));
+ insns.add(new VarInsnNode(Opcodes.ILOAD, 2));
+ insns.add(new VarInsnNode(Opcodes.ILOAD, 3));
+ });
+ }
+
+ void addCharTyped(ClassNode classNode) {
+ addSuperInjector(
+ classNode, charTyped, charTypedDesc, "charTyped_firmament",
+ insns -> {
+ // ALOAD 0, load this
+ insns.add(new VarInsnNode(Opcodes.ALOAD, 0));
+ // ILOAD 1-2, load args. chars = ints
+ insns.add(new VarInsnNode(Opcodes.ILOAD, 1));
+ insns.add(new VarInsnNode(Opcodes.ILOAD, 2));
+ });
+ }
+
+ void addSuperInjector(
+ ClassNode classNode,
+ String name,
+ Type desc,
+ String firmamentName,
+ Consumer<InsnList> loadArgs
+ ) {
+ var keyReleasedNode = findMethod(classNode, name, desc);
if (keyReleasedNode == null) {
keyReleasedNode = new MethodNode(
Modifier.PUBLIC,
- keyReleased,
- keyReleasedDesc.getDescriptor(),
+ name,
+ desc.getDescriptor(),
null,
new String[0]
);
var insns = keyReleasedNode.instructions;
- // ALOAD 0, load this
- insns.add(new VarInsnNode(Opcodes.ALOAD, 0));
- // ILOAD 1-3, load args
- insns.add(new VarInsnNode(Opcodes.ILOAD, 1));
- insns.add(new VarInsnNode(Opcodes.ILOAD, 2));
- insns.add(new VarInsnNode(Opcodes.ILOAD, 3));
+ loadArgs.accept(insns);
// INVOKESPECIAL call super method
insns.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, getTypeForClassName(Screen).getInternalName(),
- keyReleased, keyReleasedDesc.getDescriptor()));
+ name, desc.getDescriptor()));
// IRETURN return int on stack (booleans are int at runtime)
insns.add(new InsnNode(Opcodes.IRETURN));
classNode.methods.add(keyReleasedNode);
}
- insertTrueHandler(keyReleasedNode, insns -> {
- // ALOAD 0, load this
- insns.add(new VarInsnNode(Opcodes.ALOAD, 0));
- // ILOAD 1-3, load args
- insns.add(new VarInsnNode(Opcodes.ILOAD, 1));
- insns.add(new VarInsnNode(Opcodes.ILOAD, 2));
- insns.add(new VarInsnNode(Opcodes.ILOAD, 3));
- }, insns -> {
+ insertTrueHandler(keyReleasedNode, loadArgs, insns -> {
// INVOKEVIRTUAL call custom handler
insns.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
getTypeForClassName(HandledScreen).getInternalName(),
- "keyReleased_firmament",
- keyReleasedDesc.getDescriptor()));
+ firmamentName,
+ desc.getDescriptor()));
});
+
}
void addMouseScroll(ClassNode classNode) {
- MethodNode mouseScrolledNode = findMethod(classNode, mouseScrolled, mouseScrolledDesc);
- if (mouseScrolledNode == null) {
- mouseScrolledNode = new MethodNode(
- Modifier.PUBLIC,
- mouseScrolled,
- mouseScrolledDesc.getDescriptor(),
- null,
- new String[0]
- );
- var insns = mouseScrolledNode.instructions;
- // ALOAD 0, load this
- insns.add(new VarInsnNode(Opcodes.ALOAD, 0));
- // DLOAD 1-4, load the 4 argument doubles. Note that since doubles are two entries wide we skip 2 each time.
- insns.add(new VarInsnNode(Opcodes.DLOAD, 1));
- insns.add(new VarInsnNode(Opcodes.DLOAD, 3));
- insns.add(new VarInsnNode(Opcodes.DLOAD, 5));
- insns.add(new VarInsnNode(Opcodes.DLOAD, 7));
- // INVOKESPECIAL call super method
- insns.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, getTypeForClassName(Screen).getInternalName(), mouseScrolled, mouseScrolledDesc.getDescriptor()));
- // IRETURN return int on stack (booleans are int at runtime)
- insns.add(new InsnNode(Opcodes.IRETURN));
- classNode.methods.add(mouseScrolledNode);
- }
-
- insertTrueHandler(mouseScrolledNode, insns -> {
- // ALOAD 0, load this
- insns.add(new VarInsnNode(Opcodes.ALOAD, 0));
- // DLOAD 1-4, load the 4 argument doubles. Note that since doubles are two entries wide we skip 2 each time.
- insns.add(new VarInsnNode(Opcodes.DLOAD, 1));
- insns.add(new VarInsnNode(Opcodes.DLOAD, 3));
- insns.add(new VarInsnNode(Opcodes.DLOAD, 5));
- insns.add(new VarInsnNode(Opcodes.DLOAD, 7));
- }, insns -> {
- // INVOKEVIRTUAL call custom handler
- insns.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
- getTypeForClassName(HandledScreen).getInternalName(),
- "mouseScrolled_firmament",
- mouseScrolledDesc.getDescriptor()));
-
- });
+ addSuperInjector(
+ classNode, mouseScrolled, mouseScrolledDesc, "mouseScrolled_firmament",
+ insns -> {
+ // ALOAD 0, load this
+ insns.add(new VarInsnNode(Opcodes.ALOAD, 0));
+ // DLOAD 1-4, load the 4 argument doubles. Note that since doubles are two entries wide we skip 2 each time.
+ insns.add(new VarInsnNode(Opcodes.DLOAD, 1));
+ insns.add(new VarInsnNode(Opcodes.DLOAD, 3));
+ insns.add(new VarInsnNode(Opcodes.DLOAD, 5));
+ insns.add(new VarInsnNode(Opcodes.DLOAD, 7));
+ });
}
}
diff --git a/src/main/java/moe/nea/firmament/init/ItemColorsSodiumRiser.java b/src/main/java/moe/nea/firmament/init/ItemColorsSodiumRiser.java
deleted file mode 100644
index 80ee9aa..0000000
--- a/src/main/java/moe/nea/firmament/init/ItemColorsSodiumRiser.java
+++ /dev/null
@@ -1,64 +0,0 @@
-package moe.nea.firmament.init;
-
-import me.shedaniel.mm.api.ClassTinkerers;
-import moe.nea.firmament.util.ErrorUtil;
-import net.fabricmc.loader.api.FabricLoader;
-import net.minecraft.client.color.item.ItemColorProvider;
-import net.minecraft.client.color.item.ItemColors;
-import net.minecraft.item.ItemStack;
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.Type;
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.InsnList;
-import org.objectweb.asm.tree.InsnNode;
-import org.objectweb.asm.tree.MethodInsnNode;
-import org.objectweb.asm.tree.VarInsnNode;
-
-public class ItemColorsSodiumRiser extends RiserUtils {
- @IntermediaryName(ItemColors.class)
- String ItemColors;
- @IntermediaryName(ItemColorProvider.class)
- String ItemColorProvider;
- @IntermediaryName(ItemStack.class)
- String ItemStack;
- String getColorProvider = "sodium$getColorProvider";
- Type getColorProviderDesc = Type.getMethodType(getTypeForClassName(ItemColorProvider),
- getTypeForClassName(ItemStack));
-
- @Override
- public void addTinkerers() {
- ClassTinkerers.addTransformation(ItemColors, this::addSodiumOverride, true);
- }
-
- private void addSodiumOverride(ClassNode classNode) {
- var node = findMethod(classNode, getColorProvider, getColorProviderDesc);
- if (node == null) {
- if (!FabricLoader.getInstance().isModLoaded("sodium"))
- ErrorUtil.INSTANCE.softError("Sodium is present, but sodium color override could not be injected.");
- return;
- }
- var p = node.instructions.getFirst();
- while (p != null) {
- if (p.getOpcode() == Opcodes.ARETURN) {
- node.instructions.insertBefore(
- p,
- mkOverrideSodiumCall()
- );
- }
- p = p.getNext();
- }
- }
-
- private InsnList mkOverrideSodiumCall() {
- var insnList = new InsnList();
- insnList.add(new VarInsnNode(Opcodes.ALOAD, 0));
- insnList.add(new InsnNode(Opcodes.SWAP));
- insnList.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
- getTypeForClassName(ItemColors).getInternalName(),
- "overrideSodium_firmament",
- Type.getMethodType(getTypeForClassName(ItemColorProvider),
- getTypeForClassName(ItemColorProvider)).getDescriptor(),
- false));
- return insnList;
- }
-}
diff --git a/src/main/java/moe/nea/firmament/mixins/AlwaysDisplayFirmamentClientCommandErrors.java b/src/main/java/moe/nea/firmament/mixins/AlwaysDisplayFirmamentClientCommandErrors.java
new file mode 100644
index 0000000..59769c6
--- /dev/null
+++ b/src/main/java/moe/nea/firmament/mixins/AlwaysDisplayFirmamentClientCommandErrors.java
@@ -0,0 +1,18 @@
+package moe.nea.firmament.mixins;
+
+import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
+import com.llamalad7.mixinextras.sugar.Local;
+import net.fabricmc.fabric.impl.command.client.ClientCommandInternals;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+
+@Mixin(ClientCommandInternals.class)
+public class AlwaysDisplayFirmamentClientCommandErrors {
+ @ModifyExpressionValue(method = "executeCommand", at = @At(value = "INVOKE", target = "Lnet/fabricmc/fabric/impl/command/client/ClientCommandInternals;isIgnoredException(Lcom/mojang/brigadier/exceptions/CommandExceptionType;)Z"))
+ private static boolean markFirmamentExceptionsAsNotIgnores(boolean original, @Local(argsOnly = true) String command) {
+ if (command.startsWith("firm ") || command.equals("firm") || command.startsWith("firmament ") || command.equals("firmament")) {
+ return false;
+ }
+ return original;
+ }
+}
diff --git a/src/main/java/moe/nea/firmament/mixins/CustomModelEventPatch.java b/src/main/java/moe/nea/firmament/mixins/CustomModelEventPatch.java
deleted file mode 100644
index e0a7544..0000000
--- a/src/main/java/moe/nea/firmament/mixins/CustomModelEventPatch.java
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-package moe.nea.firmament.mixins;
-
-import moe.nea.firmament.events.CustomItemModelEvent;
-import moe.nea.firmament.features.texturepack.CustomGlobalTextures;
-import net.minecraft.client.render.item.ItemModels;
-import net.minecraft.client.render.model.BakedModel;
-import net.minecraft.client.render.model.BakedModelManager;
-import net.minecraft.item.Item;
-import net.minecraft.item.ItemStack;
-import net.minecraft.util.Identifier;
-import org.spongepowered.asm.mixin.Final;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.Shadow;
-import org.spongepowered.asm.mixin.injection.At;
-import org.spongepowered.asm.mixin.injection.Inject;
-import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
-
-import java.util.Map;
-
-@Mixin(ItemModels.class)
-public class CustomModelEventPatch {
-
- @Inject(method = "getModel(Lnet/minecraft/item/ItemStack;)Lnet/minecraft/client/render/model/BakedModel;", at = @At("HEAD"), cancellable = true)
- public void onGetModel(ItemStack stack, CallbackInfoReturnable<BakedModel> cir) {
- var $this = (ItemModels) (Object) this;
- var model = CustomItemModelEvent.getModel(stack, $this);
- if (model == null) {
- model = CustomGlobalTextures.replaceGlobalModel($this, stack);
- }
- if (model != null) {
- cir.setReturnValue(model);
- }
- }
-}
diff --git a/src/main/java/moe/nea/firmament/mixins/FirmKeybindsInVanillaControlsPatch.java b/src/main/java/moe/nea/firmament/mixins/FirmKeybindsInVanillaControlsPatch.java
index b386604..699d5b7 100644
--- a/src/main/java/moe/nea/firmament/mixins/FirmKeybindsInVanillaControlsPatch.java
+++ b/src/main/java/moe/nea/firmament/mixins/FirmKeybindsInVanillaControlsPatch.java
@@ -51,7 +51,7 @@ public class FirmKeybindsInVanillaControlsPatch {
var config = FirmamentKeyBindings.INSTANCE.getKeyBindings().get(binding);
if (config == null) return;
resetButton.active = false;
- editButton.setMessage(Text.translatable("firmament.keybinding.external", config.value.format()));
+ editButton.setMessage(Text.translatable("firmament.keybinding.external", config.getValue().format()));
ci.cancel();
}
diff --git a/src/main/java/moe/nea/firmament/mixins/HideStatusEffectsPatch.java b/src/main/java/moe/nea/firmament/mixins/HideStatusEffectsPatch.java
new file mode 100644
index 0000000..c5af8b6
--- /dev/null
+++ b/src/main/java/moe/nea/firmament/mixins/HideStatusEffectsPatch.java
@@ -0,0 +1,29 @@
+package moe.nea.firmament.mixins;
+
+import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
+import moe.nea.firmament.features.fixes.Fixes;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.gui.screen.ingame.InventoryScreen;
+import net.minecraft.client.gui.screen.ingame.StatusEffectsDisplay;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
+
+@Mixin(InventoryScreen.class)
+public abstract class HideStatusEffectsPatch {
+ @Shadow
+ public abstract boolean shouldHideStatusEffectHud();
+
+ @Inject(method = "shouldHideStatusEffectHud", at = @At("HEAD"), cancellable = true)
+ private void hideStatusEffects(CallbackInfoReturnable<Boolean> cir) {
+ cir.setReturnValue(!Fixes.TConfig.INSTANCE.getHidePotionEffects());
+ }
+
+ @WrapWithCondition(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/ingame/StatusEffectsDisplay;drawStatusEffects(Lnet/minecraft/client/gui/DrawContext;IIF)V"))
+ private boolean conditionalRenderStatuses(StatusEffectsDisplay instance, DrawContext context, int mouseX, int mouseY, float tickDelta) {
+ return shouldHideStatusEffectHud() || !Fixes.TConfig.INSTANCE.getHidePotionEffects();
+ }
+
+}
diff --git a/src/main/java/moe/nea/firmament/mixins/IncomingPacketListenerPatches.java b/src/main/java/moe/nea/firmament/mixins/IncomingPacketListenerPatches.java
index 80a9fd5..a7c3875 100644
--- a/src/main/java/moe/nea/firmament/mixins/IncomingPacketListenerPatches.java
+++ b/src/main/java/moe/nea/firmament/mixins/IncomingPacketListenerPatches.java
@@ -18,7 +18,6 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ClientPlayNetworkHandler.class)
public abstract class IncomingPacketListenerPatches {
-
@ModifyExpressionValue(method = "onCommandTree", at = @At(value = "NEW", target = "(Lcom/mojang/brigadier/tree/RootCommandNode;)Lcom/mojang/brigadier/CommandDispatcher;", remap = false))
public CommandDispatcher onOnCommandTree(CommandDispatcher dispatcher) {
MaskCommands.Companion.publish(new MaskCommands(dispatcher));
@@ -31,7 +30,7 @@ public abstract class IncomingPacketListenerPatches {
packet.getParameters(),
new Vec3d(packet.getX(), packet.getY(), packet.getZ()),
new Vector3f(packet.getOffsetX(), packet.getOffsetY(), packet.getOffsetZ()),
- packet.isLongDistance(),
+ packet.isImportant(),
packet.getCount(),
packet.getSpeed()
);
diff --git a/src/main/java/moe/nea/firmament/mixins/LenientProfileComponentPatch.java b/src/main/java/moe/nea/firmament/mixins/LenientProfileComponentPatch.java
deleted file mode 100644
index b1f9ee4..0000000
--- a/src/main/java/moe/nea/firmament/mixins/LenientProfileComponentPatch.java
+++ /dev/null
@@ -1,21 +0,0 @@
-
-package moe.nea.firmament.mixins;
-
-import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
-import com.mojang.serialization.Codec;
-import net.minecraft.component.type.ProfileComponent;
-import net.minecraft.util.Uuids;
-import org.objectweb.asm.Opcodes;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.injection.At;
-
-import java.util.UUID;
-
-@Mixin(ProfileComponent.class)
-public class LenientProfileComponentPatch {
- // lambda in RecordCodecBuilder.create for BASE_CODEC
- @ModifyExpressionValue(method = "method_57508", at = @At(value = "FIELD", opcode = Opcodes.GETSTATIC, target = "Lnet/minecraft/util/Uuids;INT_STREAM_CODEC:Lcom/mojang/serialization/Codec;"))
- private static Codec<UUID> onStaticInit(Codec<UUID> original) {
- return Uuids.CODEC;
- }
-}
diff --git a/src/main/java/moe/nea/firmament/mixins/MixinHandledScreen.java b/src/main/java/moe/nea/firmament/mixins/MixinHandledScreen.java
index 82f8f5d..43aec40 100644
--- a/src/main/java/moe/nea/firmament/mixins/MixinHandledScreen.java
+++ b/src/main/java/moe/nea/firmament/mixins/MixinHandledScreen.java
@@ -4,8 +4,11 @@ package moe.nea.firmament.mixins;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
-import com.llamalad7.mixinextras.sugar.Local;
-import moe.nea.firmament.events.*;
+import moe.nea.firmament.events.HandledScreenClickEvent;
+import moe.nea.firmament.events.HandledScreenForegroundEvent;
+import moe.nea.firmament.events.HandledScreenKeyPressedEvent;
+import moe.nea.firmament.events.IsSlotProtectedEvent;
+import moe.nea.firmament.events.SlotRenderEvents;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.screen.ingame.HandledScreen;
import net.minecraft.entity.player.PlayerInventory;
@@ -22,9 +25,6 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
-import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
-
-import java.util.Iterator;
@Mixin(value = HandledScreen.class, priority = 990)
public abstract class MixinHandledScreen<T extends ScreenHandler> {
@@ -62,10 +62,6 @@ public abstract class MixinHandledScreen<T extends ScreenHandler> {
}
}
- boolean keyReleased_firmament(int keyCode, int scanCode, int modifiers) {
- return HandledScreenKeyReleasedEvent.Companion.publish(new HandledScreenKeyReleasedEvent((HandledScreen<?>) (Object) this, keyCode, scanCode, modifiers)).getCancelled();
- }
-
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/ingame/HandledScreen;drawForeground(Lnet/minecraft/client/gui/DrawContext;II)V", shift = At.Shift.AFTER))
public void onAfterRenderForeground(DrawContext context, int mouseX, int mouseY, float delta, CallbackInfo ci) {
context.getMatrices().push();
@@ -78,17 +74,17 @@ public abstract class MixinHandledScreen<T extends ScreenHandler> {
public void onMouseClickedSlot(Slot slot, int slotId, int button, SlotActionType actionType, CallbackInfo ci) {
if (slotId == -999 && getScreenHandler() != null && actionType == SlotActionType.PICKUP) { // -999 is code for "clicked outside the main window"
ItemStack cursorStack = getScreenHandler().getCursorStack();
- if (cursorStack != null && IsSlotProtectedEvent.shouldBlockInteraction(slot, SlotActionType.THROW, cursorStack)) {
+ if (cursorStack != null && IsSlotProtectedEvent.shouldBlockInteraction(slot, SlotActionType.THROW, IsSlotProtectedEvent.MoveOrigin.INVENTORY_MOVE, cursorStack)) {
ci.cancel();
return;
}
}
- if (IsSlotProtectedEvent.shouldBlockInteraction(slot, actionType)) {
+ if (IsSlotProtectedEvent.shouldBlockInteraction(slot, actionType, IsSlotProtectedEvent.MoveOrigin.INVENTORY_MOVE)) {
ci.cancel();
return;
}
if (actionType == SlotActionType.SWAP && 0 <= button && button < 9) {
- if (IsSlotProtectedEvent.shouldBlockInteraction(new Slot(playerInventory, button, 0, 0), actionType)) {
+ if (IsSlotProtectedEvent.shouldBlockInteraction(new Slot(playerInventory, button, 0, 0), actionType, IsSlotProtectedEvent.MoveOrigin.INVENTORY_MOVE)) {
ci.cancel();
}
}
diff --git a/src/main/java/moe/nea/firmament/mixins/PlayerDropEventPatch.java b/src/main/java/moe/nea/firmament/mixins/PlayerDropEventPatch.java
index 9a4626f..b20c223 100644
--- a/src/main/java/moe/nea/firmament/mixins/PlayerDropEventPatch.java
+++ b/src/main/java/moe/nea/firmament/mixins/PlayerDropEventPatch.java
@@ -14,15 +14,15 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(ClientPlayerEntity.class)
public abstract class PlayerDropEventPatch extends PlayerEntity {
- public PlayerDropEventPatch() {
- super(null, null, 0, null);
- }
+ public PlayerDropEventPatch() {
+ super(null, null, 0, null);
+ }
- @Inject(method = "dropSelectedItem", at = @At("HEAD"), cancellable = true)
- public void onDropSelectedItem(boolean entireStack, CallbackInfoReturnable<Boolean> cir) {
- Slot fakeSlot = new Slot(getInventory(), getInventory().selectedSlot, 0, 0);
- if (IsSlotProtectedEvent.shouldBlockInteraction(fakeSlot, SlotActionType.THROW)) {
- cir.setReturnValue(false);
- }
- }
+ @Inject(method = "dropSelectedItem", at = @At("HEAD"), cancellable = true)
+ public void onDropSelectedItem(boolean entireStack, CallbackInfoReturnable<Boolean> cir) {
+ Slot fakeSlot = new Slot(getInventory(), getInventory().selectedSlot, 0, 0);
+ if (IsSlotProtectedEvent.shouldBlockInteraction(fakeSlot, SlotActionType.THROW, IsSlotProtectedEvent.MoveOrigin.DROP_FROM_HOTBAR)) {
+ cir.setReturnValue(false);
+ }
+ }
}
diff --git a/src/main/java/moe/nea/firmament/mixins/PropertySignatureIgnorePatch.java b/src/main/java/moe/nea/firmament/mixins/PropertySignatureIgnorePatch.java
deleted file mode 100644
index e7331c5..0000000
--- a/src/main/java/moe/nea/firmament/mixins/PropertySignatureIgnorePatch.java
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-package moe.nea.firmament.mixins;
-
-import com.mojang.authlib.properties.Property;
-import moe.nea.firmament.features.fixes.Fixes;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.injection.At;
-import org.spongepowered.asm.mixin.injection.Inject;
-import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
-
-import java.security.PublicKey;
-
-@Mixin(value = Property.class, remap = false)
-public class PropertySignatureIgnorePatch {
- @Inject(method = "isSignatureValid", cancellable = true, at = @At("HEAD"), remap = false)
- public void onValidateSignature(PublicKey publicKey, CallbackInfoReturnable<Boolean> cir) {
- if (Fixes.TConfig.INSTANCE.getFixUnsignedPlayerSkins()) {
- cir.setReturnValue(true);
- }
- }
-
- @Inject(method = "signature", cancellable = true, at = @At("HEAD"), remap = false)
- public void returnEmptySignatureInsteadOfNull(CallbackInfoReturnable<String> cir) {
- if (Fixes.TConfig.INSTANCE.getFixUnsignedPlayerSkins()) {
- cir.setReturnValue("");
- }
- }
-
- @Inject(method = "hasSignature", cancellable = true, at = @At("HEAD"), remap = false)
- public void onHasSignature(CallbackInfoReturnable<Boolean> cir) {
- if (Fixes.TConfig.INSTANCE.getFixUnsignedPlayerSkins()) {
- cir.setReturnValue(true);
- }
- }
-}
diff --git a/src/main/java/moe/nea/firmament/mixins/SaveOriginalCommandTreePacket.java b/src/main/java/moe/nea/firmament/mixins/SaveOriginalCommandTreePacket.java
new file mode 100644
index 0000000..2f2f188
--- /dev/null
+++ b/src/main/java/moe/nea/firmament/mixins/SaveOriginalCommandTreePacket.java
@@ -0,0 +1,17 @@
+package moe.nea.firmament.mixins;
+
+import moe.nea.firmament.features.chat.QuickCommands;
+import net.minecraft.client.network.ClientPlayNetworkHandler;
+import net.minecraft.network.packet.s2c.play.CommandTreeS2CPacket;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+@Mixin(ClientPlayNetworkHandler.class)
+public class SaveOriginalCommandTreePacket {
+ @Inject(method = "onCommandTree", at = @At(value = "RETURN"))
+ private void saveUnmodifiedCommandTree(CommandTreeS2CPacket packet, CallbackInfo ci) {
+ QuickCommands.INSTANCE.setLastReceivedTreePacket(packet);
+ }
+}
diff --git a/src/main/java/moe/nea/firmament/mixins/TolerateFirmamentTolerateRegistryOwners.java b/src/main/java/moe/nea/firmament/mixins/TolerateFirmamentTolerateRegistryOwners.java
new file mode 100644
index 0000000..ac6f614
--- /dev/null
+++ b/src/main/java/moe/nea/firmament/mixins/TolerateFirmamentTolerateRegistryOwners.java
@@ -0,0 +1,18 @@
+package moe.nea.firmament.mixins;
+
+import moe.nea.firmament.util.mc.TolerantRegistriesOps;
+import net.minecraft.registry.entry.RegistryEntryOwner;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
+
+@Mixin(RegistryEntryOwner.class)
+public interface TolerateFirmamentTolerateRegistryOwners<T> {
+ @Inject(method = "ownerEquals", at = @At("HEAD"), cancellable = true)
+ private void equalTolerantRegistryOwners(RegistryEntryOwner<T> other, CallbackInfoReturnable<Boolean> cir) {
+ if (other instanceof TolerantRegistriesOps.TolerantOwner<?>) {
+ cir.setReturnValue(true);
+ }
+ }
+}
diff --git a/src/main/java/moe/nea/firmament/mixins/customgui/PatchHandledScreen.java b/src/main/java/moe/nea/firmament/mixins/customgui/PatchHandledScreen.java
index 814f172..6e1090a 100644
--- a/src/main/java/moe/nea/firmament/mixins/customgui/PatchHandledScreen.java
+++ b/src/main/java/moe/nea/firmament/mixins/customgui/PatchHandledScreen.java
@@ -5,6 +5,7 @@ import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
+import moe.nea.firmament.events.HandledScreenKeyReleasedEvent;
import moe.nea.firmament.util.customgui.CoordRememberingSlot;
import moe.nea.firmament.util.customgui.CustomGui;
import moe.nea.firmament.util.customgui.HasCustomGui;
@@ -73,6 +74,16 @@ public class PatchHandledScreen<T extends ScreenHandler> extends Screen implemen
return override != null && override.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount);
}
+ public boolean keyReleased_firmament(int keyCode, int scanCode, int modifiers) {
+ if (HandledScreenKeyReleasedEvent.Companion.publish(new HandledScreenKeyReleasedEvent((HandledScreen<?>) (Object) this, keyCode, scanCode, modifiers)).getCancelled())
+ return true;
+ return override != null && override.keyReleased(keyCode, scanCode, modifiers);
+ }
+
+ public boolean charTyped_firmament(char chr, int modifiers) {
+ return override != null && override.charTyped(chr, modifiers);
+ }
+
@Inject(method = "init", at = @At("TAIL"))
private void onInit(CallbackInfo ci) {
if (override != null) {
@@ -179,6 +190,16 @@ public class PatchHandledScreen<T extends ScreenHandler> extends Screen implemen
}
}
+ @Inject(method = "keyPressed", at = @At("HEAD"), cancellable = true)
+ private void overrideKeyPressed(int keyCode, int scanCode, int modifiers, CallbackInfoReturnable<Boolean> cir) {
+ if (override != null) {
+ if (override.keyPressed(keyCode, scanCode, modifiers)) {
+ cir.setReturnValue(true);
+ }
+ }
+ }
+
+
@Inject(
method = "mouseReleased",
at = @At("HEAD"), cancellable = true)
diff --git a/src/main/java/moe/nea/firmament/mixins/custommodels/ApplyHeadModelInItemRenderer.java b/src/main/java/moe/nea/firmament/mixins/custommodels/ApplyHeadModelInItemRenderer.java
deleted file mode 100644
index dac65fe..0000000
--- a/src/main/java/moe/nea/firmament/mixins/custommodels/ApplyHeadModelInItemRenderer.java
+++ /dev/null
@@ -1,35 +0,0 @@
-
-package moe.nea.firmament.mixins.custommodels;
-
-import com.llamalad7.mixinextras.sugar.Local;
-import com.llamalad7.mixinextras.sugar.ref.LocalRef;
-import moe.nea.firmament.features.texturepack.BakedModelExtra;
-import net.minecraft.client.render.VertexConsumerProvider;
-import net.minecraft.client.render.item.ItemRenderer;
-import net.minecraft.client.render.model.BakedModel;
-import net.minecraft.client.util.math.MatrixStack;
-import net.minecraft.item.ItemStack;
-import net.minecraft.item.ModelTransformationMode;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.injection.At;
-import org.spongepowered.asm.mixin.injection.Inject;
-import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
-
-@Mixin(ItemRenderer.class)
-public class ApplyHeadModelInItemRenderer {
- @Inject(method = "renderItem(Lnet/minecraft/item/ItemStack;Lnet/minecraft/item/ModelTransformationMode;ZLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;IILnet/minecraft/client/render/model/BakedModel;ZF)V",
- at = @At("HEAD"))
- private void applyHeadModel(ItemStack stack, ModelTransformationMode transformationMode, boolean leftHanded,
- MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay,
- BakedModel model, boolean useInventoryModel, float z, CallbackInfo ci,
- @Local(argsOnly = true) LocalRef<BakedModel> modelMut
- ) {
- var extra = BakedModelExtra.cast(model);
- if (transformationMode == ModelTransformationMode.HEAD && extra != null) {
- var headModel = extra.getHeadModel_firmament();
- if (headModel != null) {
- modelMut.set(headModel);
- }
- }
- }
-}
diff --git a/src/main/java/moe/nea/firmament/mixins/custommodels/BakedModelDataHolderBasic.java b/src/main/java/moe/nea/firmament/mixins/custommodels/BakedModelDataHolderBasic.java
deleted file mode 100644
index 3ed2177..0000000
--- a/src/main/java/moe/nea/firmament/mixins/custommodels/BakedModelDataHolderBasic.java
+++ /dev/null
@@ -1,42 +0,0 @@
-
-package moe.nea.firmament.mixins.custommodels;
-
-import moe.nea.firmament.features.texturepack.BakedModelExtra;
-import moe.nea.firmament.features.texturepack.TintOverrides;
-import net.minecraft.client.render.model.BakedModel;
-import net.minecraft.client.render.model.BasicBakedModel;
-import org.jetbrains.annotations.Nullable;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.Unique;
-
-@Mixin(BasicBakedModel.class)
-public class BakedModelDataHolderBasic implements BakedModelExtra {
-
- @Unique
- private BakedModel headModel;
-
- @Unique
- @Nullable
- private TintOverrides tintOverrides;
-
- @Nullable
- @Override
- public BakedModel getHeadModel_firmament() {
- return headModel;
- }
-
- @Override
- public void setHeadModel_firmament(@Nullable BakedModel headModel) {
- this.headModel = headModel;
- }
-
- @Override
- public @Nullable TintOverrides getTintOverrides_firmament() {
- return tintOverrides;
- }
-
- @Override
- public void setTintOverrides_firmament(@Nullable TintOverrides tintOverrides) {
- this.tintOverrides = tintOverrides;
- }
-}
diff --git a/src/main/java/moe/nea/firmament/mixins/custommodels/BakedModelDataHolderBuiltin.java b/src/main/java/moe/nea/firmament/mixins/custommodels/BakedModelDataHolderBuiltin.java
deleted file mode 100644
index 87aecb1..0000000
--- a/src/main/java/moe/nea/firmament/mixins/custommodels/BakedModelDataHolderBuiltin.java
+++ /dev/null
@@ -1,43 +0,0 @@
-
-package moe.nea.firmament.mixins.custommodels;
-
-import moe.nea.firmament.features.texturepack.BakedModelExtra;
-import moe.nea.firmament.features.texturepack.TintOverrides;
-import net.minecraft.client.render.model.BakedModel;
-import net.minecraft.client.render.model.BuiltinBakedModel;
-import org.jetbrains.annotations.Nullable;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.Unique;
-
-@Mixin(BuiltinBakedModel.class)
-public class BakedModelDataHolderBuiltin implements BakedModelExtra {
-
- @Unique
- @Nullable
- private BakedModel headModel;
-
- @Unique
- @Nullable
- private TintOverrides tintOverrides;
-
- @Override
- public @Nullable TintOverrides getTintOverrides_firmament() {
- return tintOverrides;
- }
-
- @Override
- public void setTintOverrides_firmament(@Nullable TintOverrides tintOverrides) {
- this.tintOverrides = tintOverrides;
- }
-
- @Nullable
- @Override
- public BakedModel getHeadModel_firmament() {
- return headModel;
- }
-
- @Override
- public void setHeadModel_firmament(@Nullable BakedModel headModel) {
- this.headModel = headModel;
- }
-}
diff --git a/src/main/java/moe/nea/firmament/mixins/custommodels/BakedOverrideDataHolder.java b/src/main/java/moe/nea/firmament/mixins/custommodels/BakedOverrideDataHolder.java
deleted file mode 100644
index 26972b1..0000000
--- a/src/main/java/moe/nea/firmament/mixins/custommodels/BakedOverrideDataHolder.java
+++ /dev/null
@@ -1,28 +0,0 @@
-
-package moe.nea.firmament.mixins.custommodels;
-
-import moe.nea.firmament.features.texturepack.BakedOverrideData;
-import moe.nea.firmament.features.texturepack.FirmamentModelPredicate;
-import net.minecraft.client.render.model.json.ModelOverrideList;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.Unique;
-
-@Mixin(ModelOverrideList.BakedOverride.class)
-public class BakedOverrideDataHolder implements BakedOverrideData {
-
- @Unique
- private FirmamentModelPredicate[] firmamentOverrides;
-
- @Nullable
- @Override
- public FirmamentModelPredicate[] getFirmamentOverrides() {
- return firmamentOverrides;
- }
-
- @Override
- public void setFirmamentOverrides(@NotNull FirmamentModelPredicate[] overrides) {
- this.firmamentOverrides = overrides;
- }
-}
diff --git a/src/main/java/moe/nea/firmament/mixins/custommodels/HeadModelReplacerPatch.java b/src/main/java/moe/nea/firmament/mixins/custommodels/HeadModelReplacerPatch.java
deleted file mode 100644
index 26c331e..0000000
--- a/src/main/java/moe/nea/firmament/mixins/custommodels/HeadModelReplacerPatch.java
+++ /dev/null
@@ -1,57 +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 moe.nea.firmament.features.texturepack.BakedModelExtra;
-import net.minecraft.block.AbstractSkullBlock;
-import net.minecraft.block.Block;
-import net.minecraft.block.Blocks;
-import net.minecraft.client.render.VertexConsumerProvider;
-import net.minecraft.client.render.entity.LivingEntityRenderer;
-import net.minecraft.client.render.entity.feature.HeadFeatureRenderer;
-import net.minecraft.client.render.entity.model.EntityModel;
-import net.minecraft.client.render.entity.model.ModelWithHead;
-import net.minecraft.client.render.entity.state.LivingEntityRenderState;
-import net.minecraft.client.render.model.BakedModel;
-import net.minecraft.client.util.math.MatrixStack;
-import net.minecraft.entity.EquipmentSlot;
-import net.minecraft.item.BlockItem;
-import net.minecraft.item.ItemStack;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.injection.At;
-
-@Mixin(HeadFeatureRenderer.class)
-public class HeadModelReplacerPatch<S extends LivingEntityRenderState, M extends EntityModel<S> & ModelWithHead> {
- /**
- * This class serves to disable the replacing of head models with the vanilla block model. Vanilla first selects loads
- * the model containing the head model regularly in {@link LivingEntityRenderer#updateRenderState}, but then discards
- * the model in {@link HeadFeatureRenderer#render(MatrixStack, VertexConsumerProvider, int, LivingEntityRenderState, float, float)}
- * if it detects a skull block. This serves to disable that functionality if a head model override is present.
- */
- @WrapOperation(method = "render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/client/render/entity/state/LivingEntityRenderState;FF)V",
- at = @At(value = "INVOKE", target = "Lnet/minecraft/item/BlockItem;getBlock()Lnet/minecraft/block/Block;"))
- private Block replaceSkull(BlockItem instance, Operation<Block> original, @Local BakedModel bakedModel) {
- var oldBlock = original.call(instance);
- if (oldBlock instanceof AbstractSkullBlock) {
- var extra = BakedModelExtra.cast(bakedModel);
- if (extra != null && extra.getHeadModel_firmament() != null)
- return Blocks.ENCHANTING_TABLE; // Any non skull block. Let's choose the enchanting table because it is very distinct.
- }
- return oldBlock;
- }
-
- /**
- * We disable the has model override, since texture packs get precedent to server data.
- */
- @WrapOperation(method = "render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/client/render/entity/state/LivingEntityRenderState;FF)V",
- at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/entity/feature/ArmorFeatureRenderer;hasModel(Lnet/minecraft/item/ItemStack;Lnet/minecraft/entity/EquipmentSlot;)Z"))
- private boolean replaceHasModel(ItemStack stack, EquipmentSlot slot, Operation<Boolean> original,
- @Local BakedModel bakedModel) {
- var extra = BakedModelExtra.cast(bakedModel);
- if (extra != null && extra.getHeadModel_firmament() != null)
- return false;
- return original.call(stack, slot);
- }
-}
diff --git a/src/main/java/moe/nea/firmament/mixins/custommodels/ItemColorRemovalPatch.java b/src/main/java/moe/nea/firmament/mixins/custommodels/ItemColorRemovalPatch.java
deleted file mode 100644
index 8c76c60..0000000
--- a/src/main/java/moe/nea/firmament/mixins/custommodels/ItemColorRemovalPatch.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package moe.nea.firmament.mixins.custommodels;
-
-import moe.nea.firmament.features.texturepack.TintOverrides;
-import moe.nea.firmament.init.ItemColorsSodiumRiser;
-import net.minecraft.client.color.item.ItemColorProvider;
-import net.minecraft.client.color.item.ItemColors;
-import net.minecraft.item.ItemStack;
-import org.jetbrains.annotations.Nullable;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.injection.At;
-import org.spongepowered.asm.mixin.injection.Inject;
-import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
-
-@Mixin(ItemColors.class)
-public class ItemColorRemovalPatch {
-
- /**
- * @see ItemColorsSodiumRiser
- */
- private @Nullable ItemColorProvider overrideSodium_firmament(@Nullable ItemColorProvider original) {
- var tintOverrides = TintOverrides.Companion.getCurrentOverrides();
- if (!tintOverrides.hasOverrides()) return original;
- return (stack, tintIndex) -> {
- var override = tintOverrides.getOverride(tintIndex);
- if (override != null) return override;
- if (original != null) return original.getColor(stack, tintIndex);
- return -1;
- };
- }
-
-
- @Inject(method = "getColor", at = @At("HEAD"), cancellable = true)
- private void overrideGetColorCall(ItemStack item, int tintIndex, CallbackInfoReturnable<Integer> cir) {
- var tintOverrides = TintOverrides.Companion.getCurrentOverrides();
- var override = tintOverrides.getOverride(tintIndex);
- if (override != null)
- cir.setReturnValue(override);
- }
-}
diff --git a/src/main/java/moe/nea/firmament/mixins/custommodels/ItemModelGeneratorJsonUnbakedModelCopy.java b/src/main/java/moe/nea/firmament/mixins/custommodels/ItemModelGeneratorJsonUnbakedModelCopy.java
deleted file mode 100644
index 89d0411..0000000
--- a/src/main/java/moe/nea/firmament/mixins/custommodels/ItemModelGeneratorJsonUnbakedModelCopy.java
+++ /dev/null
@@ -1,22 +0,0 @@
-
-package moe.nea.firmament.mixins.custommodels;
-
-import com.llamalad7.mixinextras.injector.ModifyReturnValue;
-import com.llamalad7.mixinextras.sugar.Local;
-import moe.nea.firmament.features.texturepack.JsonUnbakedModelFirmExtra;
-import net.minecraft.client.render.model.json.ItemModelGenerator;
-import net.minecraft.client.render.model.json.JsonUnbakedModel;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.injection.At;
-
-@Mixin(ItemModelGenerator.class)
-public class ItemModelGeneratorJsonUnbakedModelCopy {
- @ModifyReturnValue(method = "create", at = @At("RETURN"))
- private JsonUnbakedModel copyExtraModelData(JsonUnbakedModel original, @Local(argsOnly = true) JsonUnbakedModel oldModel) {
- var extra = ((JsonUnbakedModelFirmExtra) original);
- var oldExtra = ((JsonUnbakedModelFirmExtra) oldModel);
- extra.setHeadModel_firmament(oldExtra.getHeadModel_firmament());
- extra.setTintOverrides_firmament(oldExtra.getTintOverrides_firmament());
- return original;
- }
-}
diff --git a/src/main/java/moe/nea/firmament/mixins/custommodels/ItemRendererTintContextPatch.java b/src/main/java/moe/nea/firmament/mixins/custommodels/ItemRendererTintContextPatch.java
deleted file mode 100644
index 8c5411b..0000000
--- a/src/main/java/moe/nea/firmament/mixins/custommodels/ItemRendererTintContextPatch.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package moe.nea.firmament.mixins.custommodels;
-
-import moe.nea.firmament.features.texturepack.BakedModelExtra;
-import moe.nea.firmament.features.texturepack.TintOverrides;
-import net.minecraft.client.render.VertexConsumerProvider;
-import net.minecraft.client.render.item.ItemRenderer;
-import net.minecraft.client.render.model.BakedModel;
-import net.minecraft.client.util.math.MatrixStack;
-import net.minecraft.item.ItemStack;
-import net.minecraft.item.ModelTransformationMode;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.injection.At;
-import org.spongepowered.asm.mixin.injection.Inject;
-import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
-
-@Mixin(value = ItemRenderer.class, priority = 1010)
-public class ItemRendererTintContextPatch {
- @Inject(method = "renderItem(Lnet/minecraft/item/ItemStack;Lnet/minecraft/item/ModelTransformationMode;ZLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;IILnet/minecraft/client/render/model/BakedModel;ZF)V",
- at = @At(value = "HEAD"), allow = 1)
- private void onStartRendering(ItemStack stack, ModelTransformationMode transformationMode, boolean leftHanded, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay, BakedModel model, boolean useInventoryModel, float z, CallbackInfo ci) {
- var extra = BakedModelExtra.cast(model);
- if (extra != null) {
- TintOverrides.Companion.enter(extra.getTintOverrides_firmament());
- }
- }
-
- @Inject(method = "renderItem(Lnet/minecraft/item/ItemStack;Lnet/minecraft/item/ModelTransformationMode;ZLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;IILnet/minecraft/client/render/model/BakedModel;ZF)V",
- at = @At("TAIL"), allow = 1)
- private void onEndRendering(ItemStack stack, ModelTransformationMode transformationMode, boolean leftHanded, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay, BakedModel model, boolean useInventoryModel, float z, CallbackInfo ci) {
- var extra = BakedModelExtra.cast(model);
- if (extra != null) {
- TintOverrides.Companion.exit(extra.getTintOverrides_firmament());
- }
- }
-}
diff --git a/src/main/java/moe/nea/firmament/mixins/custommodels/JsonUnbakedModelDataHolder.java b/src/main/java/moe/nea/firmament/mixins/custommodels/JsonUnbakedModelDataHolder.java
deleted file mode 100644
index a5bb34f..0000000
--- a/src/main/java/moe/nea/firmament/mixins/custommodels/JsonUnbakedModelDataHolder.java
+++ /dev/null
@@ -1,130 +0,0 @@
-package moe.nea.firmament.mixins.custommodels;
-
-import com.llamalad7.mixinextras.injector.ModifyReturnValue;
-import com.llamalad7.mixinextras.sugar.Local;
-import moe.nea.firmament.features.texturepack.BakedModelExtra;
-import moe.nea.firmament.features.texturepack.JsonUnbakedModelFirmExtra;
-import moe.nea.firmament.features.texturepack.TintOverrides;
-import moe.nea.firmament.util.ErrorUtil;
-import net.minecraft.client.render.model.BakedModel;
-import net.minecraft.client.render.model.Baker;
-import net.minecraft.client.render.model.ModelRotation;
-import net.minecraft.client.render.model.UnbakedModel;
-import net.minecraft.client.render.model.json.JsonUnbakedModel;
-import net.minecraft.util.Identifier;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.Shadow;
-import org.spongepowered.asm.mixin.Unique;
-import org.spongepowered.asm.mixin.injection.At;
-import org.spongepowered.asm.mixin.injection.Inject;
-import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
-
-import java.util.Objects;
-
-@Mixin(JsonUnbakedModel.class)
-public abstract class JsonUnbakedModelDataHolder implements JsonUnbakedModelFirmExtra {
- @Shadow
- @Nullable
- protected JsonUnbakedModel parent;
-
- @Shadow
- public abstract String toString();
-
- @Unique
- @Nullable
- public Identifier headModel;
- @Unique
- @Nullable
- public TintOverrides tintOverrides;
- @Unique
- @Nullable
- public TintOverrides mergedTintOverrides;
-
- @Override
- public void setTintOverrides_firmament(@Nullable TintOverrides tintOverrides) {
- this.tintOverrides = tintOverrides;
- this.mergedTintOverrides = null;
- }
-
- @Override
- public @NotNull TintOverrides getTintOverrides_firmament() {
- if (mergedTintOverrides != null)
- return mergedTintOverrides;
- var mergedTintOverrides = parent == null ? new TintOverrides()
- : ((JsonUnbakedModelFirmExtra) parent).getTintOverrides_firmament();
- if (tintOverrides != null)
- mergedTintOverrides = tintOverrides.mergeWithParent(mergedTintOverrides);
- this.mergedTintOverrides = mergedTintOverrides;
- return mergedTintOverrides;
- }
-
- @Override
- public void setHeadModel_firmament(@Nullable Identifier identifier) {
- this.headModel = identifier;
- }
-
- @Override
- public @Nullable Identifier getHeadModel_firmament() {
- if (this.headModel != null) return this.headModel;
- if (this.parent == null) return null;
- return ((JsonUnbakedModelFirmExtra) this.parent).getHeadModel_firmament();
- }
-
- @Inject(method = "resolve", at = @At("HEAD"))
- private void addDependencies(UnbakedModel.Resolver resolver, CallbackInfo ci) {
- var headModel = getHeadModel_firmament();
- if (headModel != null) {
- resolver.resolve(headModel);
- }
- }
-
- private void addExtraBakeInfo(BakedModel bakedModel, Baker baker) {
- if (!this.toString().contains("minecraft") && this.toString().contains("crimson")) {
- System.out.println("Found non minecraft model " + this);
- }
- var extra = BakedModelExtra.cast(bakedModel);
- if (extra != null) {
- var headModel = getHeadModel_firmament();
- if (headModel != null) {
- extra.setHeadModel_firmament(baker.bake(headModel, ModelRotation.X0_Y0));
- }
- if (getTintOverrides_firmament().hasOverrides()) {
- extra.setTintOverrides_firmament(getTintOverrides_firmament());
- }
- }
- }
-
- /**
- * @see ProvideBakerToJsonUnbakedModelPatch
- */
- @Override
- public void storeExtraBaker_firmament(@NotNull Baker baker) {
- this.storedBaker = baker;
- }
-
- @Unique
- private Baker storedBaker;
-
- @ModifyReturnValue(
- method = "bake(Ljava/util/function/Function;Lnet/minecraft/client/render/model/ModelBakeSettings;Z)Lnet/minecraft/client/render/model/BakedModel;",
- at = @At("RETURN"))
- private BakedModel bakeExtraInfoWithoutBaker(BakedModel original) {
- if (storedBaker != null) {
- addExtraBakeInfo(original, storedBaker);
- storedBaker = null;
- }
- return original;
- }
-
- @ModifyReturnValue(
- method = {
- "bake(Lnet/minecraft/client/render/model/Baker;Ljava/util/function/Function;Lnet/minecraft/client/render/model/ModelBakeSettings;)Lnet/minecraft/client/render/model/BakedModel;"
- },
- at = @At(value = "RETURN"))
- private BakedModel bakeExtraInfo(BakedModel original, @Local(argsOnly = true) Baker baker) {
- addExtraBakeInfo(original, baker);
- return original;
- }
-}
diff --git a/src/main/java/moe/nea/firmament/mixins/custommodels/ModelOverrideDataHolder.java b/src/main/java/moe/nea/firmament/mixins/custommodels/ModelOverrideDataHolder.java
deleted file mode 100644
index 5f9689a..0000000
--- a/src/main/java/moe/nea/firmament/mixins/custommodels/ModelOverrideDataHolder.java
+++ /dev/null
@@ -1,28 +0,0 @@
-
-package moe.nea.firmament.mixins.custommodels;
-
-import moe.nea.firmament.features.texturepack.FirmamentModelPredicate;
-import moe.nea.firmament.features.texturepack.ModelOverrideData;
-import net.minecraft.client.render.model.json.ModelOverride;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.Unique;
-
-@Mixin(ModelOverride.class)
-public class ModelOverrideDataHolder implements ModelOverrideData {
-
- @Unique
- private FirmamentModelPredicate[] overrides;
-
- @Nullable
- @Override
- public FirmamentModelPredicate[] getFirmamentOverrides() {
- return overrides;
- }
-
- @Override
- public void setFirmamentOverrides(@NotNull FirmamentModelPredicate[] overrides) {
- this.overrides = overrides;
- }
-}
diff --git a/src/main/java/moe/nea/firmament/mixins/custommodels/PatchJsonUnbakedModelDeserializer.java b/src/main/java/moe/nea/firmament/mixins/custommodels/PatchJsonUnbakedModelDeserializer.java
deleted file mode 100644
index d6c25b5..0000000
--- a/src/main/java/moe/nea/firmament/mixins/custommodels/PatchJsonUnbakedModelDeserializer.java
+++ /dev/null
@@ -1,31 +0,0 @@
-
-package moe.nea.firmament.mixins.custommodels;
-
-import com.google.gson.JsonObject;
-import com.google.gson.JsonPrimitive;
-import com.llamalad7.mixinextras.injector.ModifyReturnValue;
-import com.llamalad7.mixinextras.sugar.Local;
-import moe.nea.firmament.features.texturepack.JsonUnbakedModelFirmExtra;
-import moe.nea.firmament.features.texturepack.TintOverrides;
-import net.minecraft.client.render.model.json.JsonUnbakedModel;
-import net.minecraft.util.Identifier;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.injection.At;
-
-@Mixin(JsonUnbakedModel.Deserializer.class)
-public class PatchJsonUnbakedModelDeserializer {
- @ModifyReturnValue(method = "deserialize(Lcom/google/gson/JsonElement;Ljava/lang/reflect/Type;Lcom/google/gson/JsonDeserializationContext;)Lnet/minecraft/client/render/model/json/JsonUnbakedModel;",
- at = @At("RETURN"))
- private JsonUnbakedModel addHeadModel(JsonUnbakedModel original, @Local JsonObject jsonObject) {
- var headModel = jsonObject.get("firmament:head_model");
- var extra = ((JsonUnbakedModelFirmExtra) original);
- if (headModel instanceof JsonPrimitive prim && prim.isString()) {
- extra.setHeadModel_firmament(Identifier.of(prim.getAsString()));
- }
- var tintOverrides = jsonObject.get("firmament:tint_overrides");
- if (tintOverrides instanceof JsonObject object) {
- extra.setTintOverrides_firmament(TintOverrides.Companion.parse(object));
- }
- return original;
- }
-}
diff --git a/src/main/java/moe/nea/firmament/mixins/custommodels/PatchLegacyArmorLayerSupport.java b/src/main/java/moe/nea/firmament/mixins/custommodels/PatchLegacyArmorLayerSupport.java
deleted file mode 100644
index 8c0b3f8..0000000
--- a/src/main/java/moe/nea/firmament/mixins/custommodels/PatchLegacyArmorLayerSupport.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package moe.nea.firmament.mixins.custommodels;
-
-import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
-import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
-import moe.nea.firmament.features.texturepack.CustomGlobalArmorOverrides;
-import net.minecraft.client.render.entity.equipment.EquipmentModelLoader;
-import net.minecraft.client.render.entity.equipment.EquipmentRenderer;
-import net.minecraft.item.equipment.EquipmentModel;
-import net.minecraft.util.Identifier;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.injection.At;
-
-@Mixin(EquipmentRenderer.class)
-public class PatchLegacyArmorLayerSupport {
- @WrapOperation(method = "render(Lnet/minecraft/item/equipment/EquipmentModel$LayerType;Lnet/minecraft/util/Identifier;Lnet/minecraft/client/model/Model;Lnet/minecraft/item/ItemStack;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/util/Identifier;)V",
- at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/entity/equipment/EquipmentModelLoader;get(Lnet/minecraft/util/Identifier;)Lnet/minecraft/item/equipment/EquipmentModel;"))
- private EquipmentModel patchModelLayers(EquipmentModelLoader instance, Identifier id, Operation<EquipmentModel> original) {
- var modelOverride = CustomGlobalArmorOverrides.overrideArmorLayer(id);
- if (modelOverride != null) return modelOverride;
- return original.call(instance, id);
- }
-}
diff --git a/src/main/java/moe/nea/firmament/mixins/custommodels/PatchOverrideDeserializer.java b/src/main/java/moe/nea/firmament/mixins/custommodels/PatchOverrideDeserializer.java
deleted file mode 100644
index abb1792..0000000
--- a/src/main/java/moe/nea/firmament/mixins/custommodels/PatchOverrideDeserializer.java
+++ /dev/null
@@ -1,50 +0,0 @@
-
-package moe.nea.firmament.mixins.custommodels;
-
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonPrimitive;
-import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
-import com.llamalad7.mixinextras.injector.ModifyReturnValue;
-import com.llamalad7.mixinextras.sugar.Local;
-import moe.nea.firmament.features.texturepack.CustomModelOverrideParser;
-import moe.nea.firmament.features.texturepack.ModelOverrideData;
-import net.minecraft.client.render.model.json.ModelOverride;
-import net.minecraft.util.Identifier;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.injection.At;
-import org.spongepowered.asm.mixin.injection.Inject;
-import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
-
-import java.util.List;
-import java.util.Map;
-
-@Mixin(ModelOverride.Deserializer.class)
-public class PatchOverrideDeserializer {
-
- @ModifyReturnValue(
- method = "deserialize(Lcom/google/gson/JsonElement;Ljava/lang/reflect/Type;Lcom/google/gson/JsonDeserializationContext;)Lnet/minecraft/client/render/model/json/ModelOverride;",
- at = @At(value = "RETURN"))
- private ModelOverride addCustomOverrides(ModelOverride original, @Local JsonObject jsonObject) {
- var originalData = (ModelOverrideData) (Object) original;
- originalData.setFirmamentOverrides(CustomModelOverrideParser.parseCustomModelOverrides(jsonObject));
- return original;
- }
-
- @ModifyExpressionValue(
- method = "deserializeMinPropertyValues(Lcom/google/gson/JsonObject;)Ljava/util/List;",
- at = @At(value = "INVOKE", target = "Ljava/util/Map$Entry;getValue()Ljava/lang/Object;"))
- private Object removeFirmamentPredicatesFromJsonIteration(Object original, @Local Map.Entry<String, JsonElement> entry) {
- if (entry.getKey().startsWith("firmament:")) return new JsonPrimitive(0F);
- return original;
- }
-
- @Inject(
- method = "deserializeMinPropertyValues",
- at = @At(value = "INVOKE", target = "Ljava/util/Map;entrySet()Ljava/util/Set;")
- )
- private void whatever(JsonObject object, CallbackInfoReturnable<List<ModelOverride.Condition>> cir,
- @Local Map<Identifier, Float> maps) {
- maps.entrySet().removeIf(it -> it.getKey().getNamespace().equals("firmament"));
- }
-}
diff --git a/src/main/java/moe/nea/firmament/mixins/custommodels/ProvideBakerToJsonUnbakedModelPatch.java b/src/main/java/moe/nea/firmament/mixins/custommodels/ProvideBakerToJsonUnbakedModelPatch.java
deleted file mode 100644
index c1ac119..0000000
--- a/src/main/java/moe/nea/firmament/mixins/custommodels/ProvideBakerToJsonUnbakedModelPatch.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package moe.nea.firmament.mixins.custommodels;
-
-import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
-import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
-import moe.nea.firmament.features.texturepack.JsonUnbakedModelFirmExtra;
-import net.minecraft.client.render.model.BakedModel;
-import net.minecraft.client.render.model.Baker;
-import net.minecraft.client.render.model.ModelBakeSettings;
-import net.minecraft.client.render.model.json.JsonUnbakedModel;
-import net.minecraft.client.texture.Sprite;
-import net.minecraft.client.util.SpriteIdentifier;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.injection.At;
-
-import java.util.function.Function;
-
-/**
- * @see JsonUnbakedModelDataHolder#storeExtraBaker_firmament
- */
-@Mixin(targets = "net.minecraft.client.render.model.ModelBaker$BakerImpl")
-public abstract class ProvideBakerToJsonUnbakedModelPatch implements Baker {
- @WrapOperation(method = "bake(Lnet/minecraft/client/render/model/UnbakedModel;Lnet/minecraft/client/render/model/ModelBakeSettings;)Lnet/minecraft/client/render/model/BakedModel;", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/model/json/JsonUnbakedModel;bake(Ljava/util/function/Function;Lnet/minecraft/client/render/model/ModelBakeSettings;Z)Lnet/minecraft/client/render/model/BakedModel;"))
- private BakedModel provideExtraBakerToModel(JsonUnbakedModel instance, Function<SpriteIdentifier, Sprite> function, ModelBakeSettings modelBakeSettings, boolean bl, Operation<BakedModel> original) {
- ((JsonUnbakedModelFirmExtra) instance).storeExtraBaker_firmament(this);
- return original.call(instance, function, modelBakeSettings, bl);
- }
-}
diff --git a/src/main/java/moe/nea/firmament/mixins/custommodels/ReferenceCustomModelsPatch.java b/src/main/java/moe/nea/firmament/mixins/custommodels/ReferenceCustomModelsPatch.java
deleted file mode 100644
index bb9cd10..0000000
--- a/src/main/java/moe/nea/firmament/mixins/custommodels/ReferenceCustomModelsPatch.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package moe.nea.firmament.mixins.custommodels;
-
-import moe.nea.firmament.events.BakeExtraModelsEvent;
-import net.minecraft.client.render.model.BlockStatesLoader;
-import net.minecraft.client.render.model.ItemModel;
-import net.minecraft.client.render.model.ReferencedModelsCollector;
-import net.minecraft.client.render.model.UnbakedModel;
-import net.minecraft.client.util.ModelIdentifier;
-import net.minecraft.util.Identifier;
-import org.spongepowered.asm.mixin.Final;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.Shadow;
-import org.spongepowered.asm.mixin.injection.At;
-import org.spongepowered.asm.mixin.injection.Inject;
-import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
-
-import java.util.Map;
-
-@Mixin(ReferencedModelsCollector.class)
-public abstract class ReferenceCustomModelsPatch {
- @Shadow
- protected abstract void addTopLevelModel(ModelIdentifier modelId, UnbakedModel model);
-
- @Shadow
- @Final
- private Map<Identifier, UnbakedModel> inputs;
-
- @Inject(method = "addBlockStates", at = @At("RETURN"))
- private void addFirmamentReferencedModels(
- BlockStatesLoader.BlockStateDefinition definition, CallbackInfo ci
- ) {
- BakeExtraModelsEvent.Companion.publish(new BakeExtraModelsEvent(
- (modelIdentifier, identifier) -> addTopLevelModel(modelIdentifier, new ItemModel(identifier))));
-
- }
-}
diff --git a/src/main/java/moe/nea/firmament/mixins/custommodels/TestForFirmamentOverridePredicatesPatch.java b/src/main/java/moe/nea/firmament/mixins/custommodels/TestForFirmamentOverridePredicatesPatch.java
deleted file mode 100644
index 63f3cf0..0000000
--- a/src/main/java/moe/nea/firmament/mixins/custommodels/TestForFirmamentOverridePredicatesPatch.java
+++ /dev/null
@@ -1,68 +0,0 @@
-
-package moe.nea.firmament.mixins.custommodels;
-
-import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
-import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
-import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
-import com.llamalad7.mixinextras.sugar.Local;
-import moe.nea.firmament.Firmament;
-import moe.nea.firmament.features.texturepack.BakedOverrideData;
-import moe.nea.firmament.features.texturepack.CustomSkyBlockTextures;
-import moe.nea.firmament.features.texturepack.FirmamentModelPredicate;
-import moe.nea.firmament.features.texturepack.ModelOverrideData;
-import net.minecraft.client.render.model.json.ModelOverride;
-import net.minecraft.client.render.model.json.ModelOverrideList;
-import net.minecraft.item.ItemStack;
-import net.minecraft.util.Identifier;
-import org.objectweb.asm.Opcodes;
-import org.spongepowered.asm.mixin.Final;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.Shadow;
-import org.spongepowered.asm.mixin.injection.At;
-import org.spongepowered.asm.mixin.injection.ModifyArg;
-
-import java.util.List;
-import java.util.Objects;
-
-@Mixin(ModelOverrideList.class)
-public class TestForFirmamentOverridePredicatesPatch {
-
- @Shadow
- private Identifier[] conditionTypes;
-
- @ModifyArg(method = "<init>(Lnet/minecraft/client/render/model/Baker;Ljava/util/List;)V",
- at = @At(
- value = "INVOKE", target = "Ljava/util/List;add(Ljava/lang/Object;)Z"
- ))
- public Object onInit(
- Object element,
- @Local ModelOverride modelOverride
- ) {
- var bakedOverride = (ModelOverrideList.BakedOverride) element;
- var modelOverrideData = ModelOverrideData.cast(modelOverride);
- BakedOverrideData.cast(bakedOverride)
- .setFirmamentOverrides(modelOverrideData.getFirmamentOverrides());
- if (conditionTypes.length == 0 &&
- modelOverrideData.getFirmamentOverrides() != null &&
- modelOverrideData.getFirmamentOverrides().length > 0) {
- conditionTypes = new Identifier[]{Firmament.INSTANCE.identifier("sentinel/enforce_model_override_evaluation")};
- }
- return element;
- }
-
- @ModifyExpressionValue(method = "getModel", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/model/json/ModelOverrideList$BakedOverride;test([F)Z"))
- public boolean testFirmamentOverrides(boolean originalValue,
- @Local ModelOverrideList.BakedOverride bakedOverride,
- @Local(argsOnly = true) ItemStack stack) {
- if (!originalValue) return false;
- var overrideData = (BakedOverrideData) (Object) bakedOverride;
- var overrides = overrideData.getFirmamentOverrides();
- if (overrides == null) return true;
- if (!CustomSkyBlockTextures.TConfig.INSTANCE.getEnableModelOverrides()) return false;
- for (FirmamentModelPredicate firmamentOverride : overrides) {
- if (!firmamentOverride.test(stack))
- return false;
- }
- return true;
- }
-}
diff --git a/src/main/java/moe/nea/firmament/mixins/render/entitytints/ChangeColorOfLivingEntities.java b/src/main/java/moe/nea/firmament/mixins/render/entitytints/ChangeColorOfLivingEntities.java
new file mode 100644
index 0000000..2b96e5c
--- /dev/null
+++ b/src/main/java/moe/nea/firmament/mixins/render/entitytints/ChangeColorOfLivingEntities.java
@@ -0,0 +1,62 @@
+package moe.nea.firmament.mixins.render.entitytints;
+
+import com.llamalad7.mixinextras.injector.ModifyReturnValue;
+import com.llamalad7.mixinextras.sugar.Local;
+import moe.nea.firmament.events.EntityRenderTintEvent;
+import net.minecraft.client.render.VertexConsumerProvider;
+import net.minecraft.client.render.entity.LivingEntityRenderer;
+import net.minecraft.client.render.entity.model.EntityModel;
+import net.minecraft.client.render.entity.state.LivingEntityRenderState;
+import net.minecraft.client.util.math.MatrixStack;
+import net.minecraft.entity.LivingEntity;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.ModifyArg;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+/**
+ * Applies various rendering modifications from {@link EntityRenderTintEvent}
+ */
+@Mixin(LivingEntityRenderer.class)
+public class ChangeColorOfLivingEntities<T extends LivingEntity, S extends LivingEntityRenderState, M extends EntityModel<? super S>> {
+ @ModifyReturnValue(method = "getMixColor", at = @At("RETURN"))
+ private int changeColor(int original, @Local(argsOnly = true) S state) {
+ var tintState = EntityRenderTintEvent.HasTintRenderState.cast(state);
+ if (tintState.getHasTintOverride_firmament())
+ return tintState.getTint_firmament();
+ return original;
+ }
+
+ @ModifyArg(
+ method = "getOverlay",
+ at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/OverlayTexture;getU(F)I"),
+ allow = 1
+ )
+ private static float modifyLightOverlay(float originalWhiteOffset, @Local(argsOnly = true) LivingEntityRenderState state) {
+ var tintState = EntityRenderTintEvent.HasTintRenderState.cast(state);
+ if (tintState.getHasTintOverride_firmament() || tintState.getOverlayTexture_firmament() != null) {
+ return 1F; // TODO: add interpolation percentage to render state extension
+ }
+ return originalWhiteOffset;
+ }
+
+ @Inject(method = "render(Lnet/minecraft/client/render/entity/state/LivingEntityRenderState;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;I)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/util/math/MatrixStack;pop()V"))
+ private void afterRender(S livingEntityRenderState, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int i, CallbackInfo ci) {
+ var tintState = EntityRenderTintEvent.HasTintRenderState.cast(livingEntityRenderState);
+ var overlayTexture = tintState.getOverlayTexture_firmament();
+ if (overlayTexture != null && vertexConsumerProvider instanceof VertexConsumerProvider.Immediate imm) {
+ imm.drawCurrentLayer();
+ }
+ EntityRenderTintEvent.overlayOverride = null;
+ }
+
+ @Inject(method = "render(Lnet/minecraft/client/render/entity/state/LivingEntityRenderState;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;I)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/util/math/MatrixStack;push()V"))
+ private void beforeRender(S livingEntityRenderState, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int i, CallbackInfo ci) {
+ var tintState = EntityRenderTintEvent.HasTintRenderState.cast(livingEntityRenderState);
+ var overlayTexture = tintState.getOverlayTexture_firmament();
+ if (overlayTexture != null) {
+ EntityRenderTintEvent.overlayOverride = overlayTexture;
+ }
+ }
+}
diff --git a/src/main/java/moe/nea/firmament/mixins/render/entitytints/EntityRenderStateTint.java b/src/main/java/moe/nea/firmament/mixins/render/entitytints/EntityRenderStateTint.java
new file mode 100644
index 0000000..1019027
--- /dev/null
+++ b/src/main/java/moe/nea/firmament/mixins/render/entitytints/EntityRenderStateTint.java
@@ -0,0 +1,55 @@
+package moe.nea.firmament.mixins.render.entitytints;
+
+import moe.nea.firmament.events.EntityRenderTintEvent;
+import moe.nea.firmament.util.render.TintedOverlayTexture;
+import net.minecraft.client.render.entity.state.EntityRenderState;
+import org.jetbrains.annotations.Nullable;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Unique;
+
+@Mixin(EntityRenderState.class)
+public class EntityRenderStateTint implements EntityRenderTintEvent.HasTintRenderState {
+ @Unique
+ int tint = -1;
+ @Unique
+ TintedOverlayTexture overlayTexture;
+ @Unique
+ boolean hasTintOverride = false;
+
+ @Override
+ public int getTint_firmament() {
+ return tint;
+ }
+
+ @Override
+ public void setTint_firmament(int i) {
+ tint = i;
+ hasTintOverride = true;
+ }
+
+ @Override
+ public boolean getHasTintOverride_firmament() {
+ return hasTintOverride;
+ }
+
+ @Override
+ public void setHasTintOverride_firmament(boolean b) {
+ hasTintOverride = b;
+ }
+
+ @Override
+ public void reset_firmament() {
+ hasTintOverride = false;
+ overlayTexture = null;
+ }
+
+ @Override
+ public @Nullable TintedOverlayTexture getOverlayTexture_firmament() {
+ return overlayTexture;
+ }
+
+ @Override
+ public void setOverlayTexture_firmament(@Nullable TintedOverlayTexture tintedOverlayTexture) {
+ this.overlayTexture = tintedOverlayTexture;
+ }
+}
diff --git a/src/main/java/moe/nea/firmament/mixins/render/entitytints/InjectIntoRenderState.java b/src/main/java/moe/nea/firmament/mixins/render/entitytints/InjectIntoRenderState.java
new file mode 100644
index 0000000..7938340
--- /dev/null
+++ b/src/main/java/moe/nea/firmament/mixins/render/entitytints/InjectIntoRenderState.java
@@ -0,0 +1,30 @@
+package moe.nea.firmament.mixins.render.entitytints;
+
+import moe.nea.firmament.events.EntityRenderTintEvent;
+import net.minecraft.client.render.entity.EntityRenderer;
+import net.minecraft.client.render.entity.state.EntityRenderState;
+import net.minecraft.entity.Entity;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+/**
+ * Dispatches {@link EntityRenderTintEvent} to collect additional render state used by {@link ChangeColorOfLivingEntities}
+ */
+@Mixin(EntityRenderer.class)
+public class InjectIntoRenderState<T extends Entity, S extends EntityRenderState> {
+
+ @Inject(
+ method = "updateRenderState",
+ at = @At("RETURN"))
+ private void onUpdateRenderState(T entity, S state, float tickDelta, CallbackInfo ci) {
+ var renderState = EntityRenderTintEvent.HasTintRenderState.cast(state);
+ renderState.reset_firmament();
+ var tintEvent = new EntityRenderTintEvent(
+ entity,
+ renderState
+ );
+ EntityRenderTintEvent.Companion.publish(tintEvent);
+ }
+}
diff --git a/src/main/java/moe/nea/firmament/mixins/render/entitytints/ReplaceOverlayTexture.java b/src/main/java/moe/nea/firmament/mixins/render/entitytints/ReplaceOverlayTexture.java
new file mode 100644
index 0000000..61e5c65
--- /dev/null
+++ b/src/main/java/moe/nea/firmament/mixins/render/entitytints/ReplaceOverlayTexture.java
@@ -0,0 +1,24 @@
+package moe.nea.firmament.mixins.render.entitytints;
+
+import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
+import moe.nea.firmament.events.EntityRenderTintEvent;
+import net.minecraft.client.render.OverlayTexture;
+import net.minecraft.client.render.RenderLayer;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+
+/**
+ * Replaces the overlay texture used by rendering with the override specified in {@link EntityRenderTintEvent#overlayOverride}
+ */
+@Mixin(RenderLayer.Overlay.class)
+public class ReplaceOverlayTexture {
+ @ModifyExpressionValue(
+ method = {"method_23555", "method_23556"},
+ expect = 2,
+ at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/GameRenderer;getOverlayTexture()Lnet/minecraft/client/render/OverlayTexture;"))
+ private static OverlayTexture replaceOverlayTexture(OverlayTexture original) {
+ if (EntityRenderTintEvent.overlayOverride != null)
+ return EntityRenderTintEvent.overlayOverride;
+ return original;
+ }
+}
diff --git a/src/main/java/moe/nea/firmament/mixins/render/entitytints/UseOverlayableEquipmentRenderer.java b/src/main/java/moe/nea/firmament/mixins/render/entitytints/UseOverlayableEquipmentRenderer.java
new file mode 100644
index 0000000..d9c174c
--- /dev/null
+++ b/src/main/java/moe/nea/firmament/mixins/render/entitytints/UseOverlayableEquipmentRenderer.java
@@ -0,0 +1,34 @@
+package moe.nea.firmament.mixins.render.entitytints;
+
+import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
+import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
+import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
+import moe.nea.firmament.events.EntityRenderTintEvent;
+import net.minecraft.client.render.OverlayTexture;
+import net.minecraft.client.render.RenderLayer;
+import net.minecraft.client.render.entity.equipment.EquipmentRenderer;
+import net.minecraft.util.Identifier;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+
+/**
+ * Patch to make {@link EquipmentRenderer} use a {@link RenderLayer} that allows uses Minecraft's overlay texture, if a {@link EntityRenderTintEvent#overlayOverride} is specified.
+ */
+@Mixin(EquipmentRenderer.class)
+public class UseOverlayableEquipmentRenderer {
+ @WrapOperation(method = "render(Lnet/minecraft/client/render/entity/equipment/EquipmentModel$LayerType;Lnet/minecraft/registry/RegistryKey;Lnet/minecraft/client/model/Model;Lnet/minecraft/item/ItemStack;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/util/Identifier;)V",
+ at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/RenderLayer;getArmorCutoutNoCull(Lnet/minecraft/util/Identifier;)Lnet/minecraft/client/render/RenderLayer;"))
+ private RenderLayer replace(Identifier texture, Operation<RenderLayer> original) {
+ if (EntityRenderTintEvent.overlayOverride != null)
+ return RenderLayer.getEntityTranslucent(texture);
+ return original.call(texture);
+ }
+
+ @ModifyExpressionValue(method = "render(Lnet/minecraft/client/render/entity/equipment/EquipmentModel$LayerType;Lnet/minecraft/registry/RegistryKey;Lnet/minecraft/client/model/Model;Lnet/minecraft/item/ItemStack;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/util/Identifier;)V",
+ at = @At(value = "FIELD", target = "Lnet/minecraft/client/render/OverlayTexture;DEFAULT_UV:I"))
+ private int replaceUvIndex(int original) {
+ if (EntityRenderTintEvent.overlayOverride != null)
+ return OverlayTexture.packUv(15, 10); // TODO: store this info in a global alongside overlayOverride
+ return original;
+ }
+}
diff --git a/src/main/java/moe/nea/firmament/mixins/render/entitytints/UseOverlayableHeadFeatureRenderer.java b/src/main/java/moe/nea/firmament/mixins/render/entitytints/UseOverlayableHeadFeatureRenderer.java
new file mode 100644
index 0000000..07bc5cf
--- /dev/null
+++ b/src/main/java/moe/nea/firmament/mixins/render/entitytints/UseOverlayableHeadFeatureRenderer.java
@@ -0,0 +1,25 @@
+package moe.nea.firmament.mixins.render.entitytints;
+
+import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
+import moe.nea.firmament.events.EntityRenderTintEvent;
+import net.minecraft.client.render.OverlayTexture;
+import net.minecraft.client.render.RenderLayer;
+import net.minecraft.client.render.entity.feature.HeadFeatureRenderer;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+
+/**
+ * Patch to make {@link HeadFeatureRenderer} use a {@link RenderLayer} that allows uses Minecraft's overlay texture, if a {@link EntityRenderTintEvent#overlayOverride} is specified.
+ * @see UseOverlayableItemRenderer
+ */
+@Mixin(HeadFeatureRenderer.class)
+public class UseOverlayableHeadFeatureRenderer {
+
+ @ModifyExpressionValue(method = "render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/client/render/entity/state/LivingEntityRenderState;FF)V",
+ at = @At(value = "FIELD", target = "Lnet/minecraft/client/render/OverlayTexture;DEFAULT_UV:I"))
+ private int replaceUvIndex(int original) {
+ if (EntityRenderTintEvent.overlayOverride != null)
+ return OverlayTexture.packUv(15, 10); // TODO: store this info in a global alongside overlayOverride
+ return original;
+ }
+}
diff --git a/src/main/java/moe/nea/firmament/mixins/render/entitytints/UseOverlayableItemRenderer.java b/src/main/java/moe/nea/firmament/mixins/render/entitytints/UseOverlayableItemRenderer.java
new file mode 100644
index 0000000..620ab2c
--- /dev/null
+++ b/src/main/java/moe/nea/firmament/mixins/render/entitytints/UseOverlayableItemRenderer.java
@@ -0,0 +1,25 @@
+package moe.nea.firmament.mixins.render.entitytints;
+
+import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
+import moe.nea.firmament.events.EntityRenderTintEvent;
+import net.minecraft.client.render.OverlayTexture;
+import net.minecraft.client.render.RenderLayer;
+import net.minecraft.client.render.RenderPhase;
+import net.minecraft.client.render.item.ItemRenderState;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+
+/**
+ * Patch to make {@link ItemRenderState} use a {@link RenderLayer} that allows uses Minecraft's overlay texture.
+ *
+ * @see UseOverlayableHeadFeatureRenderer
+ */
+@Mixin(ItemRenderState.LayerRenderState.class)
+public class UseOverlayableItemRenderer {
+ @ModifyExpressionValue(method = "render", at = @At(value = "FIELD", target = "Lnet/minecraft/client/render/item/ItemRenderState$LayerRenderState;renderLayer:Lnet/minecraft/client/render/RenderLayer;"))
+ private RenderLayer replace(RenderLayer original) {
+ if (EntityRenderTintEvent.overlayOverride != null && original instanceof RenderLayer.MultiPhase multiPhase && multiPhase.phases.texture instanceof RenderPhase.Texture texture && texture.getId().isPresent())
+ return RenderLayer.getEntityTranslucent(texture.getId().get());
+ return original;
+ }
+}
diff --git a/src/main/java/moe/nea/firmament/mixins/render/entitytints/UseOverlayableSkullBlockEntityRenderer.java b/src/main/java/moe/nea/firmament/mixins/render/entitytints/UseOverlayableSkullBlockEntityRenderer.java
new file mode 100644
index 0000000..9905af1
--- /dev/null
+++ b/src/main/java/moe/nea/firmament/mixins/render/entitytints/UseOverlayableSkullBlockEntityRenderer.java
@@ -0,0 +1,25 @@
+package moe.nea.firmament.mixins.render.entitytints;
+
+import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
+import moe.nea.firmament.events.EntityRenderTintEvent;
+import net.minecraft.client.render.OverlayTexture;
+import net.minecraft.client.render.RenderLayer;
+import net.minecraft.client.render.block.entity.SkullBlockEntityRenderer;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+
+/**
+ * Patch to make {@link SkullBlockEntityRenderer} use a {@link RenderLayer} that allows uses Minecraft's overlay texture, if a {@link EntityRenderTintEvent#overlayOverride} is specified.
+ */
+
+@Mixin(SkullBlockEntityRenderer.class)
+public class UseOverlayableSkullBlockEntityRenderer {
+ @ModifyExpressionValue(method = "renderSkull",
+ at = @At(value = "FIELD", target = "Lnet/minecraft/client/render/OverlayTexture;DEFAULT_UV:I"))
+ private static int replaceUvIndex(int original) {
+ if (EntityRenderTintEvent.overlayOverride != null)
+ return OverlayTexture.packUv(15, 10); // TODO: store this info in a global alongside overlayOverride
+ return original;
+ }
+
+}
diff --git a/src/main/kotlin/Firmament.kt b/src/main/kotlin/Firmament.kt
index 2c2a6b7..01905c7 100644
--- a/src/main/kotlin/Firmament.kt
+++ b/src/main/kotlin/Firmament.kt
@@ -1,5 +1,6 @@
package moe.nea.firmament
+import com.google.gson.Gson
import com.mojang.brigadier.CommandDispatcher
import io.ktor.client.HttpClient
import io.ktor.client.plugins.UserAgent
@@ -70,6 +71,7 @@ object Firmament {
ignoreUnknownKeys = true
encodeDefaults = true
}
+ val gson = Gson()
val tightJson = Json(from = json) {
prettyPrint = false
}
diff --git a/src/main/kotlin/commands/Duration.kt b/src/main/kotlin/commands/Duration.kt
new file mode 100644
index 0000000..42f143d
--- /dev/null
+++ b/src/main/kotlin/commands/Duration.kt
@@ -0,0 +1,75 @@
+package moe.nea.firmament.commands
+
+import com.mojang.brigadier.StringReader
+import com.mojang.brigadier.arguments.ArgumentType
+import com.mojang.brigadier.context.CommandContext
+import com.mojang.brigadier.exceptions.DynamicCommandExceptionType
+import com.mojang.brigadier.suggestion.Suggestions
+import com.mojang.brigadier.suggestion.SuggestionsBuilder
+import java.util.concurrent.CompletableFuture
+import java.util.function.Function
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.seconds
+import kotlin.time.DurationUnit
+import kotlin.time.toDuration
+import moe.nea.firmament.util.tr
+
+object DurationArgumentType : ArgumentType<Duration> {
+ val unknownTimeCode = DynamicCommandExceptionType { timeCode ->
+ tr("firmament.command-argument.duration.error",
+ "Unknown time code '$timeCode'")
+ }
+
+ override fun parse(reader: StringReader): Duration {
+ val start = reader.cursor
+ val string = reader.readUnquotedString()
+ val matcher = regex.matcher(string)
+ var s = 0
+ var time = 0.seconds
+ fun createError(till: Int) {
+ throw unknownTimeCode.createWithContext(
+ reader.also { it.cursor = start + s },
+ string.substring(s, till))
+ }
+
+ while (matcher.find()) {
+ if (matcher.start() != s) {
+ createError(matcher.start())
+ }
+ s = matcher.end()
+ val amount = matcher.group("count").toDouble()
+ val what = timeSuffixes[matcher.group("what").single()]!!
+ time += amount.toDuration(what)
+ }
+ if (string.length != s) {
+ createError(string.length)
+ }
+ return time
+ }
+
+
+ override fun <S : Any?> listSuggestions(
+ context: CommandContext<S>,
+ builder: SuggestionsBuilder
+ ): CompletableFuture<Suggestions> {
+ val remaining = builder.remainingLowerCase.substringBefore(' ')
+ if (remaining.isEmpty()) return super.listSuggestions(context, builder)
+ if (remaining.last().isDigit()) {
+ for (timeSuffix in timeSuffixes.keys) {
+ builder.suggest(remaining + timeSuffix)
+ }
+ }
+ return builder.buildFuture()
+ }
+
+ val timeSuffixes = mapOf(
+ 'm' to DurationUnit.MINUTES,
+ 's' to DurationUnit.SECONDS,
+ 'h' to DurationUnit.HOURS,
+ )
+ val regex = "(?<count>[0-9]+)(?<what>[${timeSuffixes.keys.joinToString("")}])".toPattern()
+
+ override fun getExamples(): Collection<String> {
+ return listOf("3m", "20s", "1h45m")
+ }
+}
diff --git a/src/main/kotlin/commands/rome.kt b/src/main/kotlin/commands/rome.kt
index 13acb3c..5412792 100644
--- a/src/main/kotlin/commands/rome.kt
+++ b/src/main/kotlin/commands/rome.kt
@@ -254,6 +254,7 @@ fun firmamentCommand() = literal("firmament") {
val player = MC.player ?: return@thenExecute
player.world.getOtherEntities(player, player.boundingBox.expand(12.0))
.forEach(PowerUserTools::showEntity)
+ PowerUserTools.showEntity(player)
}
}
thenLiteral("callUrsa") {
diff --git a/src/main/kotlin/events/BakeExtraModelsEvent.kt b/src/main/kotlin/events/BakeExtraModelsEvent.kt
deleted file mode 100644
index adaa495..0000000
--- a/src/main/kotlin/events/BakeExtraModelsEvent.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-package moe.nea.firmament.events
-
-import java.util.function.BiConsumer
-import net.minecraft.client.render.model.ReferencedModelsCollector
-import net.minecraft.client.util.ModelIdentifier
-import net.minecraft.util.Identifier
-
-// TODO: Rename this event, since it is not really directly baking models anymore
-class BakeExtraModelsEvent(
- private val addAnyModel: BiConsumer<ModelIdentifier, Identifier>,
-) : FirmamentEvent() {
-
- fun addNonItemModel(modelIdentifier: ModelIdentifier, identifier: Identifier) {
- this.addAnyModel.accept(modelIdentifier, identifier)
- }
-
- fun addItemModel(modelIdentifier: ModelIdentifier) {
- addNonItemModel(
- modelIdentifier,
- modelIdentifier.id.withPrefixedPath(ReferencedModelsCollector.ITEM_DIRECTORY))
- }
-
- companion object : FirmamentEventBus<BakeExtraModelsEvent>()
-}
diff --git a/src/main/kotlin/events/CustomItemModelEvent.kt b/src/main/kotlin/events/CustomItemModelEvent.kt
index 4328d77..21ee326 100644
--- a/src/main/kotlin/events/CustomItemModelEvent.kt
+++ b/src/main/kotlin/events/CustomItemModelEvent.kt
@@ -2,37 +2,42 @@ package moe.nea.firmament.events
import java.util.Optional
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 moe.nea.firmament.util.ErrorUtil
+import net.minecraft.util.Identifier
import moe.nea.firmament.util.collections.WeakCache
+import moe.nea.firmament.util.mc.IntrospectableItemModelManager
+// TODO: assert an order on these events
data class CustomItemModelEvent(
val itemStack: ItemStack,
- var overrideModel: ModelIdentifier? = null,
+ val itemModelManager: IntrospectableItemModelManager,
+ var overrideModel: Identifier? = null,
) : FirmamentEvent() {
companion object : FirmamentEventBus<CustomItemModelEvent>() {
- val cache =
- WeakCache.memoize<ItemStack, ItemModels, Optional<BakedModel>>("CustomItemModels") { stack, models ->
- val modelId = getModelIdentifier(stack) ?: return@memoize Optional.empty()
- ErrorUtil.softCheck("Model Id needs to have an inventory variant", modelId.variant() == "inventory")
- val bakedModel = models.getModel(modelId.id)
- if (bakedModel == null || bakedModel === models.missingModelSupplier.get()) return@memoize Optional.empty()
- Optional.of(bakedModel)
- }
+ val cache = WeakCache.memoize("ItemModelIdentifier", ::getModelIdentifier0)
@JvmStatic
- fun getModelIdentifier(itemStack: ItemStack?): ModelIdentifier? {
+ fun getModelIdentifier(itemStack: ItemStack?, itemModelManager: IntrospectableItemModelManager): Identifier? {
if (itemStack == null) return null
- return publish(CustomItemModelEvent(itemStack)).overrideModel
+ return cache.invoke(itemStack, itemModelManager).getOrNull()
}
- @JvmStatic
- fun getModel(itemStack: ItemStack?, thing: ItemModels): BakedModel? {
- if (itemStack == null) return null
- return cache.invoke(itemStack, thing).getOrNull()
+ fun getModelIdentifier0(
+ itemStack: ItemStack,
+ itemModelManager: IntrospectableItemModelManager
+ ): Optional<Identifier> {
+ // TODO: add an error / warning if the model does not exist
+ return Optional.ofNullable(publish(CustomItemModelEvent(itemStack, itemModelManager)).overrideModel)
}
}
+
+ fun overrideIfExists(overrideModel: Identifier) {
+ if (itemModelManager.hasModel_firmament(overrideModel))
+ this.overrideModel = overrideModel
+ }
+
+ fun overrideIfEmpty(identifier: Identifier) {
+ if (overrideModel == null)
+ overrideModel = identifier
+ }
}
diff --git a/src/main/kotlin/events/EntityRenderTintEvent.kt b/src/main/kotlin/events/EntityRenderTintEvent.kt
new file mode 100644
index 0000000..29b888b
--- /dev/null
+++ b/src/main/kotlin/events/EntityRenderTintEvent.kt
@@ -0,0 +1,66 @@
+package moe.nea.firmament.events
+
+import net.minecraft.client.render.GameRenderer
+import net.minecraft.client.render.OverlayTexture
+import net.minecraft.client.render.entity.state.EntityRenderState
+import net.minecraft.entity.Entity
+import net.minecraft.entity.LivingEntity
+import moe.nea.firmament.util.render.TintedOverlayTexture
+
+/**
+ * Change the tint color of a [LivingEntity]
+ */
+class EntityRenderTintEvent(
+ val entity: Entity,
+ val renderState: HasTintRenderState
+) : FirmamentEvent.Cancellable() {
+ init {
+ if (entity !is LivingEntity) {
+ cancel()
+ }
+ }
+
+ companion object : FirmamentEventBus<EntityRenderTintEvent>() {
+ /**
+ * Static variable containing an override for [GameRenderer.getOverlayTexture]. Should be only set briefly.
+ *
+ * This variable only affects render layers that naturally make use of the overlay texture, have proper overlay UVs set (`overlay u != 0`), and have a shader that makes use of the overlay (does not have the `NO_OVERLAY` flag set in its json definition).
+ *
+ * Currently supported layers: [net.minecraft.client.render.entity.equipment.EquipmentRenderer], [net.minecraft.client.render.entity.model.PlayerEntityModel], as well as some others naturally.
+ *
+ * @see moe.nea.firmament.mixins.render.entitytints.ReplaceOverlayTexture
+ * @see TintedOverlayTexture
+ */
+ @JvmField
+ var overlayOverride: OverlayTexture? = null
+ }
+
+ @Suppress("PropertyName", "FunctionName")
+ interface HasTintRenderState {
+ /**
+ * Multiplicative tint applied before the overlay.
+ */
+ var tint_firmament: Int
+
+ /**
+ * Must be set for [tint_firmament] to have any effect.
+ */
+ var hasTintOverride_firmament: Boolean
+
+ // TODO: allow for more specific selection of which layers get tinted
+ /**
+ * Specify a [TintedOverlayTexture] to be used. This does not apply to render layers not using the overlay texture.
+ * @see overlayOverride
+ */
+ var overlayTexture_firmament: TintedOverlayTexture?
+ fun reset_firmament()
+
+ companion object {
+ @JvmStatic
+ fun cast(state: EntityRenderState): HasTintRenderState {
+ return state as HasTintRenderState
+ }
+ }
+ }
+
+}
diff --git a/src/main/kotlin/events/FirmamentEventBus.kt b/src/main/kotlin/events/FirmamentEventBus.kt
index 71331d1..af4e16a 100644
--- a/src/main/kotlin/events/FirmamentEventBus.kt
+++ b/src/main/kotlin/events/FirmamentEventBus.kt
@@ -3,6 +3,7 @@ package moe.nea.firmament.events
import java.util.concurrent.CopyOnWriteArrayList
import org.apache.commons.lang3.reflect.TypeUtils
import moe.nea.firmament.Firmament
+import moe.nea.firmament.util.ErrorUtil
import moe.nea.firmament.util.MC
/**
@@ -48,7 +49,7 @@ open class FirmamentEventBus<T : FirmamentEvent> {
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)
+ ErrorUtil.softError("Caught exception during processing event $event by $function", e)
}
}
}
diff --git a/src/main/kotlin/events/IsSlotProtectedEvent.kt b/src/main/kotlin/events/IsSlotProtectedEvent.kt
index cd2b676..eac2d9b 100644
--- a/src/main/kotlin/events/IsSlotProtectedEvent.kt
+++ b/src/main/kotlin/events/IsSlotProtectedEvent.kt
@@ -1,5 +1,3 @@
-
-
package moe.nea.firmament.events
import net.minecraft.item.ItemStack
@@ -10,37 +8,49 @@ 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,
+ val slot: Slot?,
+ val actionType: SlotActionType,
+ var isProtected: Boolean,
+ val itemStackOverride: ItemStack?,
+ val origin: MoveOrigin,
+ var silent: Boolean = false,
) : FirmamentEvent() {
- val itemStack get() = itemStackOverride ?: slot!!.stack
+ val itemStack get() = itemStackOverride ?: slot!!.stack
- fun protect() {
- isProtected = true
- }
+ fun protect() {
+ isProtected = true
+ silent = false
+ }
- fun protectSilent() {
- if (!isProtected) {
- silent = true
- }
- isProtected = true
- }
+ fun protectSilent() {
+ if (!isProtected) {
+ silent = true
+ }
+ isProtected = true
+ }
- companion object : FirmamentEventBus<IsSlotProtectedEvent>() {
- @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.sendChat(Text.translatable("firmament.protectitem").append(event.itemStack.name))
- CommonSoundEffects.playFailure()
- }
- return event.isProtected
- }
- }
+ enum class MoveOrigin {
+ DROP_FROM_HOTBAR,
+ SALVAGE,
+ INVENTORY_MOVE
+ ;
+ }
+ companion object : FirmamentEventBus<IsSlotProtectedEvent>() {
+ @JvmStatic
+ @JvmOverloads
+ fun shouldBlockInteraction(
+ slot: Slot?, action: SlotActionType,
+ origin: MoveOrigin,
+ itemStackOverride: ItemStack? = null,
+ ): Boolean {
+ if (slot == null && itemStackOverride == null) return false
+ val event = IsSlotProtectedEvent(slot, action, false, itemStackOverride, origin)
+ publish(event)
+ if (event.isProtected && !event.silent) {
+ MC.sendChat(Text.translatable("firmament.protectitem").append(event.itemStack.name))
+ CommonSoundEffects.playFailure()
+ }
+ return event.isProtected
+ }
+ }
}
diff --git a/src/main/kotlin/events/PartyMessageReceivedEvent.kt b/src/main/kotlin/events/PartyMessageReceivedEvent.kt
new file mode 100644
index 0000000..4688dfe
--- /dev/null
+++ b/src/main/kotlin/events/PartyMessageReceivedEvent.kt
@@ -0,0 +1,9 @@
+package moe.nea.firmament.events
+
+data class PartyMessageReceivedEvent(
+ val from: ProcessChatEvent,
+ val message: String,
+ val name: String,
+) : FirmamentEvent() {
+ companion object : FirmamentEventBus<PartyMessageReceivedEvent>()
+}
diff --git a/src/main/kotlin/events/UseItemEvent.kt b/src/main/kotlin/events/UseItemEvent.kt
new file mode 100644
index 0000000..e294bb1
--- /dev/null
+++ b/src/main/kotlin/events/UseItemEvent.kt
@@ -0,0 +1,11 @@
+package moe.nea.firmament.events
+
+import net.minecraft.entity.player.PlayerEntity
+import net.minecraft.item.ItemStack
+import net.minecraft.util.Hand
+import net.minecraft.world.World
+
+data class UseItemEvent(val playerEntity: PlayerEntity, val world: World, val hand: Hand) : FirmamentEvent.Cancellable() {
+ companion object : FirmamentEventBus<UseItemEvent>()
+ val item: ItemStack = playerEntity.getStackInHand(hand)
+}
diff --git a/src/main/kotlin/events/registration/ChatEvents.kt b/src/main/kotlin/events/registration/ChatEvents.kt
index 4c1c63f..1dcc91a 100644
--- a/src/main/kotlin/events/registration/ChatEvents.kt
+++ b/src/main/kotlin/events/registration/ChatEvents.kt
@@ -1,10 +1,9 @@
-
-
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.fabricmc.fabric.api.event.player.UseItemCallback
import net.minecraft.text.Text
import net.minecraft.util.ActionResult
import moe.nea.firmament.events.AllowChatEvent
@@ -12,43 +11,53 @@ import moe.nea.firmament.events.AttackBlockEvent
import moe.nea.firmament.events.ModifyChatEvent
import moe.nea.firmament.events.ProcessChatEvent
import moe.nea.firmament.events.UseBlockEvent
+import moe.nea.firmament.events.UseItemEvent
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))
- }
- })
+ 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
- })
+ 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
+ })
+ UseBlockCallback.EVENT.register(UseBlockCallback { player, world, hand, hitResult ->
+ if (UseItemEvent.publish(UseItemEvent(player, world, hand)).cancelled)
+ ActionResult.CONSUME
+ else ActionResult.PASS
+ })
+ UseItemCallback.EVENT.register(UseItemCallback { playerEntity, world, hand ->
+ if (UseItemEvent.publish(UseItemEvent(playerEntity, world, hand)).cancelled) ActionResult.CONSUME
+ else ActionResult.PASS
+ })
}
diff --git a/src/main/kotlin/features/FeatureManager.kt b/src/main/kotlin/features/FeatureManager.kt
index 2110d09..9a3cbf8 100644
--- a/src/main/kotlin/features/FeatureManager.kt
+++ b/src/main/kotlin/features/FeatureManager.kt
@@ -29,7 +29,6 @@ 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
@@ -46,10 +45,6 @@ object FeatureManager : DataHolder<FeatureManager.Config>(serializer(), "feature
private var hasAutoloaded = false
- init {
- autoload()
- }
-
fun autoload() {
synchronized(this) {
if (hasAutoloaded) return
@@ -70,7 +65,6 @@ object FeatureManager : DataHolder<FeatureManager.Config>(serializer(), "feature
loadFeature(QuickCommands)
loadFeature(PetFeatures)
loadFeature(SaveCursorPosition)
- loadFeature(CustomSkyBlockTextures)
loadFeature(PriceData)
loadFeature(Fixes)
loadFeature(DianaWaypoints)
diff --git a/src/main/kotlin/features/chat/ChatLinks.kt b/src/main/kotlin/features/chat/ChatLinks.kt
index 5bce3f4..f85825b 100644
--- a/src/main/kotlin/features/chat/ChatLinks.kt
+++ b/src/main/kotlin/features/chat/ChatLinks.kt
@@ -1,5 +1,3 @@
-
-
package moe.nea.firmament.features.chat
import io.ktor.client.request.get
@@ -7,16 +5,15 @@ import io.ktor.client.statement.bodyAsChannel
import io.ktor.utils.io.jvm.javaio.toInputStream
import java.net.URL
import java.util.Collections
+import java.util.concurrent.atomic.AtomicInteger
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.render.RenderLayer
import net.minecraft.client.texture.NativeImage
import net.minecraft.client.texture.NativeImageBackedTexture
-import net.minecraft.scoreboard.ScoreboardCriterion.RenderType
import net.minecraft.text.ClickEvent
import net.minecraft.text.HoverEvent
import net.minecraft.text.Style
@@ -35,130 +32,132 @@ import moe.nea.firmament.util.transformEachRecursively
import moe.nea.firmament.util.unformattedString
object ChatLinks : FirmamentFeature {
- override val identifier: String
- get() = "chat-links"
+ override val identifier: String
+ get() = "chat-links"
- object TConfig : ManagedConfig(identifier, Category.CHAT) {
- 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) }
- }
+ object TConfig : ManagedConfig(identifier, Category.CHAT) {
+ 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 isHostAllowed(host: String) =
+ TConfig.allowAllHosts || TConfig.actualAllowedHosts.any { it.equals(host, ignoreCase = true) }
- private fun isUrlAllowed(url: String) = isHostAllowed(url.removePrefix("https://").substringBefore("/"))
+ private fun isUrlAllowed(url: String) = isHostAllowed(url.removePrefix("https://").substringBefore("/"))
- override val config get() = TConfig
- val urlRegex = "https://[^. ]+\\.[^ ]+(\\.?( |$))".toRegex()
+ override val config get() = TConfig
+ val urlRegex = "https://[^. ]+\\.[^ ]+(\\.?( |$))".toRegex()
+ val nextTexId = AtomicInteger(0)
- data class Image(
- val texture: Identifier,
- val width: Int,
- val height: Int,
- )
+ data class Image(
+ val texture: Identifier,
+ val width: Int,
+ val height: Int,
+ )
- val imageCache: MutableMap<String, Deferred<Image?>> =
- Collections.synchronizedMap(mutableMapOf<String, Deferred<Image?>>())
+ val imageCache: MutableMap<String, Deferred<Image?>> =
+ Collections.synchronizedMap(mutableMapOf<String, Deferred<Image?>>())
- 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
- }
- }
- }
+ 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 texId = Firmament.identifier("dynamic_image_preview${nextTexId.getAndIncrement()}")
+ MC.textureManager.registerTexture(
+ texId,
+ NativeImageBackedTexture(image)
+ )
+ Image(texId, 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)
- }
+ 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
+ @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
- }
- }
+ @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/PartyCommands.kt b/src/main/kotlin/features/chat/PartyCommands.kt
new file mode 100644
index 0000000..de3a0d9
--- /dev/null
+++ b/src/main/kotlin/features/chat/PartyCommands.kt
@@ -0,0 +1,134 @@
+package moe.nea.firmament.features.chat
+
+import com.mojang.brigadier.CommandDispatcher
+import com.mojang.brigadier.StringReader
+import com.mojang.brigadier.exceptions.CommandSyntaxException
+import com.mojang.brigadier.tree.LiteralCommandNode
+import kotlin.time.Duration.Companion.seconds
+import net.minecraft.util.math.BlockPos
+import moe.nea.firmament.annotations.Subscribe
+import moe.nea.firmament.commands.CaseInsensitiveLiteralCommandNode
+import moe.nea.firmament.commands.thenExecute
+import moe.nea.firmament.events.CommandEvent
+import moe.nea.firmament.events.PartyMessageReceivedEvent
+import moe.nea.firmament.events.ProcessChatEvent
+import moe.nea.firmament.gui.config.ManagedConfig
+import moe.nea.firmament.util.ErrorUtil
+import moe.nea.firmament.util.MC
+import moe.nea.firmament.util.TimeMark
+import moe.nea.firmament.util.tr
+import moe.nea.firmament.util.useMatch
+
+object PartyCommands {
+
+ val messageInChannel = "(?<channel>Party|Guild) >([^:]+?)? (?<name>[^: ]+): (?<message>.+)".toPattern()
+
+ @Subscribe
+ fun onChat(event: ProcessChatEvent) {
+ messageInChannel.useMatch(event.unformattedString) {
+ val channel = group("channel")
+ val message = group("message")
+ val name = group("name")
+ if (channel == "Party") {
+ PartyMessageReceivedEvent.publish(PartyMessageReceivedEvent(
+ event, message, name
+ ))
+ }
+ }
+ }
+
+ val commandPrefixes = "!-?$.&#+~€\"@°_;:³²`'´ß\\,|".toSet()
+
+ data class PartyCommandContext(
+ val name: String
+ )
+
+ val dispatch = CommandDispatcher<PartyCommandContext>().also { dispatch ->
+ fun register(
+ name: String,
+ vararg alias: String,
+ block: CaseInsensitiveLiteralCommandNode.Builder<PartyCommandContext>.() -> Unit = {},
+ ): LiteralCommandNode<PartyCommandContext> {
+ val node =
+ dispatch.register(CaseInsensitiveLiteralCommandNode.Builder<PartyCommandContext>(name).also(block))
+ alias.forEach { register(it) { redirect(node) } }
+ return node
+ }
+
+ register("warp", "pw", "pwarp", "partywarp") {
+ executes {
+ // TODO: add check if you are the party leader
+ MC.sendCommand("p warp")
+ 0
+ }
+ }
+
+ register("transfer", "pt", "ptme") {
+ executes {
+ MC.sendCommand("p transfer ${it.source.name}")
+ 0
+ }
+ }
+
+ register("allinvite", "allinv") {
+ executes {
+ MC.sendCommand("p settings allinvite")
+ 0
+ }
+ }
+
+ register("coords") {
+ executes {
+ val p = MC.player?.blockPos ?: BlockPos.ORIGIN
+ MC.sendCommand("pc x: ${p.x}, y: ${p.y}, z: ${p.z}")
+ 0
+ }
+ }
+ // TODO: downtime tracker (display message again at end of dungeon)
+ // instance ends: kuudra, dungeons, bacte
+ // TODO: at TPS command
+ }
+
+ object TConfig : ManagedConfig("party-commands", Category.CHAT) {
+ val enable by toggle("enable") { false }
+ val cooldown by duration("cooldown", 0.seconds, 20.seconds) { 2.seconds }
+ val ignoreOwnCommands by toggle("ignore-own") { false }
+ }
+
+ var lastCommand = TimeMark.farPast()
+
+ @Subscribe
+ fun listPartyCommands(event: CommandEvent.SubCommand) {
+ event.subcommand("partycommands") {
+ thenExecute {
+ // TODO: Better help, including descriptions and redirect detection
+ MC.sendChat(tr("firmament.partycommands.help", "Available party commands: ${dispatch.root.children.map { it.name }}. Available prefixes: $commandPrefixes"))
+ }
+ }
+ }
+
+ @Subscribe
+ fun onPartyMessage(event: PartyMessageReceivedEvent) {
+ if (!TConfig.enable) return
+ if (event.message.firstOrNull() !in commandPrefixes) return
+ if (event.name == MC.playerName && TConfig.ignoreOwnCommands) return
+ if (lastCommand.passedTime() < TConfig.cooldown) {
+ MC.sendChat(tr("firmament.partycommands.cooldown", "Skipping party command. Cooldown not passed."))
+ return
+ }
+ // TODO: add trust levels
+ val commandLine = event.message.substring(1)
+ try {
+ dispatch.execute(StringReader(commandLine), PartyCommandContext(event.name))
+ } catch (ex: Exception) {
+ if (ex is CommandSyntaxException) {
+ MC.sendChat(tr("firmament.partycommands.unknowncommand", "Unknown party command."))
+ return
+ } else {
+ MC.sendChat(tr("firmament.partycommands.unknownerror", "Unknown error during command execution."))
+ ErrorUtil.softError("Unknown error during command execution.", ex)
+ }
+ }
+ lastCommand = TimeMark.now()
+ }
+}
diff --git a/src/main/kotlin/features/chat/QuickCommands.kt b/src/main/kotlin/features/chat/QuickCommands.kt
index 5944b92..7963171 100644
--- a/src/main/kotlin/features/chat/QuickCommands.kt
+++ b/src/main/kotlin/features/chat/QuickCommands.kt
@@ -1,8 +1,12 @@
-
-
package moe.nea.firmament.features.chat
+import com.mojang.brigadier.CommandDispatcher
import com.mojang.brigadier.context.CommandContext
+import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback
+import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource
+import net.fabricmc.fabric.impl.command.client.ClientCommandInternals
+import net.minecraft.command.CommandRegistryAccess
+import net.minecraft.network.packet.s2c.play.CommandTreeS2CPacket
import net.minecraft.text.Text
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.commands.DefaultSource
@@ -12,89 +16,139 @@ 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.gui.config.ManagedConfig
+import moe.nea.firmament.gui.config.ManagedOption
import moe.nea.firmament.util.MC
import moe.nea.firmament.util.SBData
+import moe.nea.firmament.util.grey
+import moe.nea.firmament.util.tr
object QuickCommands : FirmamentFeature {
- override val identifier: String
- get() = "quick-commands"
+ override val identifier: String
+ get() = "quick-commands"
+
+ object TConfig : ManagedConfig("quick-commands", Category.CHAT) {
+ val enableJoin by toggle("join") { true }
+ val enableDh by toggle("dh") { true }
+ override fun onChange(option: ManagedOption<*>) {
+ reloadCommands()
+ }
+ }
+
+ fun reloadCommands() {
+ val lastPacket = lastReceivedTreePacket ?: return
+ val network = MC.networkHandler ?: return
+ val fallback = ClientCommandInternals.getActiveDispatcher()
+ try {
+ val dispatcher = CommandDispatcher<FabricClientCommandSource>()
+ ClientCommandInternals.setActiveDispatcher(dispatcher)
+ ClientCommandRegistrationCallback.EVENT.invoker()
+ .register(dispatcher, CommandRegistryAccess.of(network.combinedDynamicRegistries,
+ network.enabledFeatures))
+ ClientCommandInternals.finalizeInit()
+ network.onCommandTree(lastPacket)
+ } catch (ex: Exception) {
+ ClientCommandInternals.setActiveDispatcher(fallback)
+ throw ex
+ }
+ }
+
+
+ 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
+ }
+
+ var lastReceivedTreePacket: CommandTreeS2CPacket? = null
- 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")
- val kuudraLevelNames = listOf("NORMAL", "HOT", "BURNING", "FIERY", "INFERNAL")
- val dungeonLevelNames = listOf("ONE", "TWO", "THREE", "FOUR", "FIVE", "SIX", "SEVEN")
+ @Subscribe
+ fun registerDh(event: CommandEvent) {
+ if (!TConfig.enableDh) return
+ event.register("dh") {
+ thenExecute {
+ MC.sendCommand("warp dhub")
+ }
+ }
+ event.register("dn") {
+ thenExecute {
+ MC.sendChat(tr("firmament.quickwarp.deez-nutz", "Warping to... Deez Nuts!").grey())
+ MC.sendCommand("warp dhub")
+ }
+ }
+ }
- @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"))
- }
- }
- }
+ @Subscribe
+ fun registerJoin(it: CommandEvent) {
+ if (!TConfig.enableJoin) return
+ 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<DefaultSource>.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
- }
+ fun CommandContext<DefaultSource>.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/PowerUserTools.kt b/src/main/kotlin/features/debug/PowerUserTools.kt
index 13320dc..8be5d5d 100644
--- a/src/main/kotlin/features/debug/PowerUserTools.kt
+++ b/src/main/kotlin/features/debug/PowerUserTools.kt
@@ -1,20 +1,31 @@
package moe.nea.firmament.features.debug
+import com.mojang.serialization.Codec
+import com.mojang.serialization.DynamicOps
import com.mojang.serialization.JsonOps
+import com.mojang.serialization.codecs.RecordCodecBuilder
import kotlin.jvm.optionals.getOrNull
import net.minecraft.block.SkullBlock
import net.minecraft.block.entity.SkullBlockEntity
import net.minecraft.component.DataComponentTypes
+import net.minecraft.component.type.ProfileComponent
import net.minecraft.entity.Entity
+import net.minecraft.entity.EntityType
import net.minecraft.entity.LivingEntity
import net.minecraft.item.ItemStack
import net.minecraft.item.Items
+import net.minecraft.nbt.NbtCompound
import net.minecraft.nbt.NbtOps
+import net.minecraft.nbt.NbtString
+import net.minecraft.predicate.NbtPredicate
import net.minecraft.text.Text
import net.minecraft.text.TextCodecs
+import net.minecraft.util.Identifier
import net.minecraft.util.hit.BlockHitResult
import net.minecraft.util.hit.EntityHitResult
import net.minecraft.util.hit.HitResult
+import net.minecraft.util.math.Position
+import net.minecraft.util.math.Vec3d
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.CustomItemModelEvent
import moe.nea.firmament.events.HandledScreenKeyPressedEvent
@@ -23,12 +34,13 @@ 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.mc.IntrospectableItemModelManager
+import moe.nea.firmament.util.mc.SNbtFormatter
import moe.nea.firmament.util.mc.SNbtFormatter.Companion.toPrettyString
import moe.nea.firmament.util.mc.displayNameAccordingToNbt
import moe.nea.firmament.util.mc.loreAccordingToNbt
@@ -86,6 +98,11 @@ object PowerUserTools : FirmamentFeature {
}
fun showEntity(target: Entity) {
+ val nbt = NbtPredicate.entityToNbt(target)
+ nbt.remove("Inventory")
+ nbt.put("StyledName", TextCodecs.CODEC.encodeStart(NbtOps.INSTANCE, target.styledDisplayName).orThrow)
+ println(SNbtFormatter.prettify(nbt))
+ ClipboardUtils.setTextContent(SNbtFormatter.prettify(nbt))
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))
@@ -101,6 +118,8 @@ object PowerUserTools : FirmamentFeature {
}
}
+ // TODO: leak this through some other way, maybe.
+ lateinit var getSkullId: (profile: ProfileComponent) -> Identifier?
@Subscribe
fun copyInventoryInfo(it: HandledScreenKeyPressedEvent) {
@@ -116,7 +135,11 @@ object PowerUserTools : FirmamentFeature {
lastCopiedStack =
Pair(item, Text.stringifiedTranslatable("firmament.tooltip.copied.skyblockid", sbId.neuItem))
} else if (it.matches(TConfig.copyTexturePackId)) {
- val model = CustomItemModelEvent.getModelIdentifier(item)
+ val model = CustomItemModelEvent.getModelIdentifier0(item, object : IntrospectableItemModelManager {
+ override fun hasModel_firmament(identifier: Identifier): Boolean {
+ return true
+ }
+ }).getOrNull() // TODO: remove global texture overrides, maybe
if (model == null) {
lastCopiedStack = Pair(item, Text.translatable("firmament.tooltip.copied.modelid.fail"))
return
@@ -146,7 +169,7 @@ object PowerUserTools : FirmamentFeature {
lastCopiedStack = Pair(item, Text.translatable("firmament.tooltip.copied.skull-id.fail.no-profile"))
return
}
- val skullTexture = CustomSkyBlockTextures.getSkullTexture(profile)
+ val skullTexture = getSkullId(profile)
if (skullTexture == null) {
lastCopiedStack = Pair(item, Text.translatable("firmament.tooltip.copied.skull-id.fail.no-texture"))
return
@@ -179,7 +202,7 @@ object PowerUserTools : FirmamentFeature {
MC.sendChat(Text.translatable("firmament.tooltip.copied.skull.fail"))
return
}
- val id = CustomSkyBlockTextures.getSkullTexture(entity.owner!!)
+ val id = getSkullId(entity.owner!!)
if (id == null) {
MC.sendChat(Text.translatable("firmament.tooltip.copied.skull.fail"))
} else {
diff --git a/src/main/kotlin/features/events/anniversity/CenturyRaffleFeatures.kt b/src/main/kotlin/features/events/anniversity/CenturyRaffleFeatures.kt
new file mode 100644
index 0000000..9935051
--- /dev/null
+++ b/src/main/kotlin/features/events/anniversity/CenturyRaffleFeatures.kt
@@ -0,0 +1,63 @@
+package moe.nea.firmament.features.events.anniversity
+
+import java.util.Optional
+import me.shedaniel.math.Color
+import kotlin.jvm.optionals.getOrNull
+import net.minecraft.entity.player.PlayerEntity
+import net.minecraft.text.Style
+import net.minecraft.util.Formatting
+import moe.nea.firmament.annotations.Subscribe
+import moe.nea.firmament.events.EntityRenderTintEvent
+import moe.nea.firmament.gui.config.ManagedConfig
+import moe.nea.firmament.util.MC
+import moe.nea.firmament.util.SkyblockId
+import moe.nea.firmament.util.render.TintedOverlayTexture
+import moe.nea.firmament.util.skyBlockId
+import moe.nea.firmament.util.skyblock.SkyBlockItems
+
+object CenturyRaffleFeatures {
+ object TConfig : ManagedConfig("centuryraffle", Category.EVENTS) {
+ val highlightPlayersForSlice by toggle("highlight-cake-players") { true }
+// val highlightAllPlayers by toggle("highlight-all-cake-players") { true }
+ }
+
+ val cakeIcon = "⛃"
+
+ val cakeColors = listOf(
+ CakeTeam(SkyBlockItems.SLICE_OF_BLUEBERRY_CAKE, Formatting.BLUE),
+ CakeTeam(SkyBlockItems.SLICE_OF_CHEESECAKE, Formatting.YELLOW),
+ CakeTeam(SkyBlockItems.SLICE_OF_GREEN_VELVET_CAKE, Formatting.GREEN),
+ CakeTeam(SkyBlockItems.SLICE_OF_RED_VELVET_CAKE, Formatting.RED),
+ CakeTeam(SkyBlockItems.SLICE_OF_STRAWBERRY_SHORTCAKE, Formatting.LIGHT_PURPLE),
+ )
+
+ data class CakeTeam(
+ val id: SkyblockId,
+ val formatting: Formatting,
+ ) {
+ val searchedTextRgb = formatting.colorValue!!
+ val brightenedRgb = Color.ofOpaque(searchedTextRgb)//.brighter(2.0)
+ val tintOverlay by lazy {
+ TintedOverlayTexture().setColor(brightenedRgb)
+ }
+ }
+
+ val sliceToColor = cakeColors.associateBy { it.id }
+
+ @Subscribe
+ fun onEntityRender(event: EntityRenderTintEvent) {
+ if (!TConfig.highlightPlayersForSlice) return
+ val requestedCakeTeam = sliceToColor[MC.stackInHand?.skyBlockId] ?: return
+ // TODO: cache the requested color
+ val player = event.entity as? PlayerEntity ?: return
+ val cakeColor: Style = player.styledDisplayName.visit(
+ { style, text ->
+ if (text == cakeIcon) Optional.of(style)
+ else Optional.empty()
+ }, Style.EMPTY).getOrNull() ?: return
+ if (cakeColor.color?.rgb == requestedCakeTeam.searchedTextRgb) {
+ event.renderState.overlayTexture_firmament = requestedCakeTeam.tintOverlay
+ }
+ }
+
+}
diff --git a/src/main/kotlin/features/fixes/Fixes.kt b/src/main/kotlin/features/fixes/Fixes.kt
index 5d70b1a..7030319 100644
--- a/src/main/kotlin/features/fixes/Fixes.kt
+++ b/src/main/kotlin/features/fixes/Fixes.kt
@@ -1,71 +1,67 @@
-
-
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"
+ override val identifier: String
+ get() = "fixes"
- object TConfig : ManagedConfig(identifier, Category.MISC) { // TODO: split this config
- 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")
- }
+ object TConfig : ManagedConfig(identifier, Category.MISC) { // TODO: split this config
+ 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")
+ val hidePotionEffects by toggle("hide-mob-effects") { false }
+ }
- override val config: ManagedConfig
- get() = TConfig
+ override val config: ManagedConfig
+ get() = TConfig
- fun handleIsPressed(
- keyBinding: KeyBinding,
- cir: CallbackInfoReturnable<Boolean>
- ) {
- if (keyBinding === MinecraftClient.getInstance().options.sprintKey && TConfig.autoSprint && MC.player?.isSprinting != true)
- cir.returnValue = true
- }
+ fun handleIsPressed(
+ keyBinding: KeyBinding,
+ cir: CallbackInfoReturnable<Boolean>
+ ) {
+ 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 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
- }
- }
+ @Subscribe
+ fun onWorldKeyboard(it: WorldKeyboardEvent) {
+ if (it.matches(TConfig.autoSprintKeyBinding)) {
+ TConfig.autoSprint = !TConfig.autoSprint
+ }
+ }
- fun shouldPeekChat(): Boolean {
- return TConfig.peekChat.isPressed(atLeast = true)
- }
+ fun shouldPeekChat(): Boolean {
+ return TConfig.peekChat.isPressed(atLeast = true)
+ }
}
diff --git a/src/main/kotlin/features/inventory/ItemRarityCosmetics.kt b/src/main/kotlin/features/inventory/ItemRarityCosmetics.kt
index d2c555b..fdc378a 100644
--- a/src/main/kotlin/features/inventory/ItemRarityCosmetics.kt
+++ b/src/main/kotlin/features/inventory/ItemRarityCosmetics.kt
@@ -29,18 +29,7 @@ object ItemRarityCosmetics : FirmamentFeature {
override val config: ManagedConfig
get() = TConfig
- private val rarityToColor = mapOf(
- Rarity.COMMON to Formatting.WHITE,
- Rarity.UNCOMMON to Formatting.GREEN,
- Rarity.RARE to Formatting.BLUE,
- Rarity.EPIC to Formatting.DARK_PURPLE,
- Rarity.LEGENDARY to Formatting.GOLD,
- Rarity.MYTHIC to Formatting.LIGHT_PURPLE,
- Rarity.DIVINE to Formatting.AQUA,
- Rarity.SPECIAL to Formatting.RED,
- Rarity.VERY_SPECIAL to Formatting.RED,
- Rarity.SUPREME to Formatting.DARK_RED,
- ).mapValues {
+ private val rarityToColor = Rarity.colourMap.mapValues {
val c = Color(it.value.colorValue!!)
c.rgb
}
diff --git a/src/main/kotlin/features/inventory/SlotLocking.kt b/src/main/kotlin/features/inventory/SlotLocking.kt
index fc09476..7a3a152 100644
--- a/src/main/kotlin/features/inventory/SlotLocking.kt
+++ b/src/main/kotlin/features/inventory/SlotLocking.kt
@@ -2,7 +2,6 @@
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
@@ -14,7 +13,9 @@ import net.minecraft.screen.GenericContainerScreenHandler
import net.minecraft.screen.slot.Slot
import net.minecraft.screen.slot.SlotActionType
import net.minecraft.util.Identifier
+import net.minecraft.util.StringIdentifiable
import moe.nea.firmament.annotations.Subscribe
+import moe.nea.firmament.events.FeaturesInitializedEvent
import moe.nea.firmament.events.HandledScreenForegroundEvent
import moe.nea.firmament.events.HandledScreenKeyPressedEvent
import moe.nea.firmament.events.HandledScreenKeyReleasedEvent
@@ -37,6 +38,7 @@ import moe.nea.firmament.util.mc.displayNameAccordingToNbt
import moe.nea.firmament.util.mc.loreAccordingToNbt
import moe.nea.firmament.util.render.GuiRenderLayers
import moe.nea.firmament.util.render.drawLine
+import moe.nea.firmament.util.skyblock.DungeonUtil
import moe.nea.firmament.util.skyblockUUID
import moe.nea.firmament.util.unformattedString
@@ -59,6 +61,18 @@ object SlotLocking : FirmamentFeature {
}
val slotBind by keyBinding("bind") { GLFW.GLFW_KEY_L }
val slotBindRequireShift by toggle("require-quick-move") { true }
+ val slotRenderLines by choice("bind-render") { SlotRenderLinesMode.ONLY_BOXES }
+ val allowDroppingInDungeons by toggle("drop-in-dungeons") { true }
+ }
+
+ enum class SlotRenderLinesMode : StringIdentifiable {
+ EVERYTHING,
+ ONLY_BOXES,
+ NOTHING;
+
+ override fun asString(): String {
+ return name
+ }
}
override val config: TConfig
@@ -95,7 +109,7 @@ object SlotLocking : FirmamentFeature {
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
+ if (sellItem.displayNameAccordingToNbt.unformattedString == "Sell Item") return true
val lore = sellItem.loreAccordingToNbt
return (lore.lastOrNull() ?: return false).unformattedString == "Click to buyback!"
}
@@ -104,12 +118,16 @@ object SlotLocking : FirmamentFeature {
fun onSalvageProtect(event: IsSlotProtectedEvent) {
if (event.slot == null) return
if (!event.slot.hasStack()) return
- if (event.slot.stack.displayNameAccordingToNbt?.unformattedString != "Salvage Items") 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))
+ if (IsSlotProtectedEvent.shouldBlockInteraction(null,
+ SlotActionType.THROW,
+ IsSlotProtectedEvent.MoveOrigin.SALVAGE,
+ stack)
+ )
anyBlocked = true
}
if (anyBlocked) {
@@ -145,6 +163,19 @@ object SlotLocking : FirmamentFeature {
}
@Subscribe
+ fun onEvent(event: FeaturesInitializedEvent) {
+ IsSlotProtectedEvent.subscribe(receivesCancelled = true, "SlotLocking:unlockInDungeons") {
+ if (it.isProtected
+ && it.origin == IsSlotProtectedEvent.MoveOrigin.DROP_FROM_HOTBAR
+ && DungeonUtil.isInActiveDungeon
+ && TConfig.allowDroppingInDungeons
+ ) {
+ it.isProtected = false
+ }
+ }
+ }
+
+ @Subscribe
fun onQuickMoveBoundSlot(it: IsSlotProtectedEvent) {
val boundSlots = DConfig.data?.boundSlots ?: mapOf()
val isValidAction =
@@ -227,23 +258,32 @@ object SlotLocking : FirmamentFeature {
val accScreen = event.screen as AccessorHandledScreen
val sx = accScreen.x_Firmament
val sy = accScreen.y_Firmament
- boundSlots.entries.forEach {
- val hotbarSlot = findByIndex(it.key) ?: return@forEach
- val inventorySlot = findByIndex(it.value) ?: return@forEach
+ for (it in boundSlots.entries) {
+ val hotbarSlot = findByIndex(it.key) ?: continue
+ val inventorySlot = findByIndex(it.value) ?: continue
val (hotX, hotY) = hotbarSlot.lineCenter()
val (invX, invY) = inventorySlot.lineCenter()
- event.context.drawLine(
- invX + sx, invY + sy,
- hotX + sx, hotY + sy,
+ val anyHovered = accScreen.focusedSlot_Firmament === hotbarSlot
+ || accScreen.focusedSlot_Firmament === inventorySlot
+ if (!anyHovered && TConfig.slotRenderLines == SlotRenderLinesMode.NOTHING)
+ continue
+ val color = if (anyHovered)
me.shedaniel.math.Color.ofOpaque(0x00FF00)
- )
+ else
+ me.shedaniel.math.Color.ofTransparent(0xc0a0f000.toInt())
+ if (TConfig.slotRenderLines == SlotRenderLinesMode.EVERYTHING || anyHovered)
+ event.context.drawLine(
+ invX + sx, invY + sy,
+ hotX + sx, hotY + sy,
+ color
+ )
event.context.drawBorder(hotbarSlot.x + sx,
hotbarSlot.y + sy,
- 16, 16, 0xFF00FF00u.toInt())
+ 16, 16, color.color)
event.context.drawBorder(inventorySlot.x + sx,
inventorySlot.y + sy,
- 16, 16, 0xFF00FF00u.toInt())
+ 16, 16, color.color)
}
}
diff --git a/src/main/kotlin/features/inventory/TimerInLore.kt b/src/main/kotlin/features/inventory/TimerInLore.kt
new file mode 100644
index 0000000..f1b77c6
--- /dev/null
+++ b/src/main/kotlin/features/inventory/TimerInLore.kt
@@ -0,0 +1,130 @@
+package moe.nea.firmament.features.inventory
+
+import java.time.ZoneId
+import java.time.ZonedDateTime
+import java.time.format.DateTimeFormatter
+import java.time.format.DateTimeFormatterBuilder
+import java.time.format.FormatStyle
+import java.time.format.TextStyle
+import java.time.temporal.ChronoField
+import net.minecraft.text.Text
+import net.minecraft.util.StringIdentifiable
+import moe.nea.firmament.annotations.Subscribe
+import moe.nea.firmament.events.ItemTooltipEvent
+import moe.nea.firmament.gui.config.ManagedConfig
+import moe.nea.firmament.util.SBData
+import moe.nea.firmament.util.aqua
+import moe.nea.firmament.util.grey
+import moe.nea.firmament.util.mc.displayNameAccordingToNbt
+import moe.nea.firmament.util.tr
+import moe.nea.firmament.util.unformattedString
+
+object TimerInLore {
+ object TConfig : ManagedConfig("lore-timers", Category.INVENTORY) {
+ val showTimers by toggle("show") { true }
+ val timerFormat by choice("format") { TimerFormat.SOCIALIST }
+ }
+
+ enum class TimerFormat(val formatter: DateTimeFormatter) : StringIdentifiable {
+ RFC(DateTimeFormatter.RFC_1123_DATE_TIME),
+ LOCAL(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)),
+ SOCIALIST(
+ {
+ appendText(ChronoField.DAY_OF_WEEK, TextStyle.SHORT)
+ appendLiteral(" ")
+ appendValue(ChronoField.DAY_OF_MONTH, 2)
+ appendLiteral(".")
+ appendValue(ChronoField.MONTH_OF_YEAR, 2)
+ appendLiteral(".")
+ appendValue(ChronoField.YEAR, 4)
+ appendLiteral(" ")
+ appendValue(ChronoField.HOUR_OF_DAY, 2)
+ appendLiteral(":")
+ appendValue(ChronoField.MINUTE_OF_HOUR, 2)
+ appendLiteral(":")
+ appendValue(ChronoField.SECOND_OF_MINUTE, 2)
+ }),
+ AMERICAN("EEEE, MMM d h:mm a yyyy"),
+ ;
+
+ constructor(block: DateTimeFormatterBuilder.() -> Unit)
+ : this(DateTimeFormatterBuilder().also(block).toFormatter())
+
+ constructor(format: String) : this(DateTimeFormatter.ofPattern(format))
+
+ override fun asString(): String {
+ return name
+ }
+ }
+
+ enum class CountdownTypes(
+ val match: String,
+ val label: String, // TODO: convert to a string
+ val isRelative: Boolean = false,
+ ) {
+ STARTING("Starting in:", "Starts at"),
+ STARTS("Starts in:", "Starts at"),
+ INTEREST("Interest in:", "Interest at"),
+ UNTILINTEREST("Until interest:", "Interest at"),
+ ENDS("Ends in:", "Ends at"),
+ REMAINING("Remaining:", "Ends at"),
+ DURATION("Duration:", "Finishes at"),
+ TIMELEFT("Time left:", "Ends at"),
+ EVENTTIMELEFT("Event lasts for", "Ends at", isRelative = true),
+ SHENSUCKS("Auction ends in:", "Auction ends at"),
+ ENDS_PET_LEVELING(
+ "Ends:",
+ "Finishes at"
+ ),
+ CALENDARDETAILS(" (§e", "Starts at"),
+ COMMUNITYPROJECTS("Contribute again", "Come back at"),
+ CHOCOLATEFACTORY("Next Charge", "Available at"),
+ STONKSAUCTION("Auction ends in", "Ends at"),
+ LIZSTONKREDEMPTION("Resets in:", "Resets at");
+ }
+
+ val regex =
+ "(?i)(?:(?<years>[0-9]+) ?(y|years?) )?(?:(?<days>[0-9]+) ?(d|days?))? ?(?:(?<hours>[0-9]+) ?(h|hours?))? ?(?:(?<minutes>[0-9]+) ?(m|minutes?))? ?(?:(?<seconds>[0-9]+) ?(s|seconds?))?\\b".toRegex()
+
+ @Subscribe
+ fun modifyLore(event: ItemTooltipEvent) {
+ if (!TConfig.showTimers) return
+ var lastTimer: ZonedDateTime? = null
+ for (i in event.lines.indices) {
+ val line = event.lines[i].unformattedString
+ val countdownType = CountdownTypes.entries.find { it.match in line } ?: continue
+ if (countdownType == CountdownTypes.CALENDARDETAILS
+ && !event.stack.displayNameAccordingToNbt.unformattedString.startsWith("Day ")
+ ) continue
+
+ val countdownMatch = regex.findAll(line).filter { it.value.isNotBlank() }.lastOrNull() ?: continue
+ val (years, days, hours, minutes, seconds) =
+ listOf("years", "days", "hours", "minutes", "seconds")
+ .map {
+ countdownMatch.groups[it]?.value?.toLong() ?: 0L
+ }
+ if (years + days + hours + minutes + seconds == 0L) continue
+ var baseLine = ZonedDateTime.now(SBData.hypixelTimeZone)
+ if (countdownType.isRelative) {
+ if (lastTimer == null) {
+ event.lines.add(i + 1,
+ tr("firmament.loretimer.missingrelative",
+ "Found a relative countdown with no baseline (Firmament)").grey())
+ continue
+ }
+ baseLine = lastTimer
+ }
+ val timer =
+ baseLine.plusYears(years).plusDays(days).plusHours(hours).plusMinutes(minutes).plusSeconds(seconds)
+ lastTimer = timer
+ val localTimer = timer.withZoneSameInstant(ZoneId.systemDefault())
+ // TODO: install approximate time stabilization algorithm
+ event.lines.add(i + 1,
+ Text.literal("${countdownType.label}: ")
+ .grey()
+ .append(Text.literal(TConfig.timerFormat.formatter.format(localTimer)).aqua())
+ )
+ }
+ }
+
+}
diff --git a/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayCustom.kt b/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayCustom.kt
index 2be798b..6092e26 100644
--- a/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayCustom.kt
+++ b/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayCustom.kt
@@ -66,6 +66,18 @@ class StorageOverlayCustom(
return overview.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
}
+ override fun keyReleased(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
+ return overview.keyReleased(keyCode, scanCode, modifiers)
+ }
+
+ override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
+ return overview.keyPressed(keyCode, scanCode, modifiers)
+ }
+
+ override fun charTyped(chr: Char, modifiers: Int): Boolean {
+ return overview.charTyped(chr, modifiers)
+ }
+
override fun mouseClick(mouseX: Double, mouseY: Double, button: Int): Boolean {
return overview.mouseClicked(mouseX, mouseY, button, (handler as? StorageBackingHandle.Page)?.storagePageSlot)
}
diff --git a/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayScreen.kt b/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayScreen.kt
index 4c624ef..633a8fe 100644
--- a/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayScreen.kt
+++ b/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayScreen.kt
@@ -1,15 +1,22 @@
package moe.nea.firmament.features.inventory.storageoverlay
+import io.github.notenoughupdates.moulconfig.common.IMinecraft
import io.github.notenoughupdates.moulconfig.gui.GuiContext
+import io.github.notenoughupdates.moulconfig.gui.KeyboardEvent
import io.github.notenoughupdates.moulconfig.gui.MouseEvent
import io.github.notenoughupdates.moulconfig.gui.component.ColumnComponent
import io.github.notenoughupdates.moulconfig.gui.component.PanelComponent
import io.github.notenoughupdates.moulconfig.gui.component.TextComponent
+import io.github.notenoughupdates.moulconfig.gui.component.TextFieldComponent
+import io.github.notenoughupdates.moulconfig.observer.GetSetter
+import io.github.notenoughupdates.moulconfig.observer.Property
+import java.util.TreeSet
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.client.gui.screen.ingame.HandledScreen
+import net.minecraft.item.ItemStack
import net.minecraft.screen.slot.Slot
import net.minecraft.text.Text
import net.minecraft.util.Identifier
@@ -20,10 +27,17 @@ import moe.nea.firmament.util.MC
import moe.nea.firmament.util.MoulConfigUtils.adopt
import moe.nea.firmament.util.MoulConfigUtils.clickMCComponentInPlace
import moe.nea.firmament.util.MoulConfigUtils.drawMCComponentInPlace
+import moe.nea.firmament.util.MoulConfigUtils.typeMCComponentInPlace
+import moe.nea.firmament.util.StringUtil.words
import moe.nea.firmament.util.assertTrueOr
import moe.nea.firmament.util.customgui.customGui
import moe.nea.firmament.util.mc.FakeSlot
+import moe.nea.firmament.util.mc.displayNameAccordingToNbt
+import moe.nea.firmament.util.mc.loreAccordingToNbt
import moe.nea.firmament.util.render.drawGuiTexture
+import moe.nea.firmament.util.render.enableScissorWithoutTranslation
+import moe.nea.firmament.util.tr
+import moe.nea.firmament.util.unformattedString
class StorageOverlayScreen : Screen(Text.literal("")) {
@@ -83,10 +97,13 @@ class StorageOverlayScreen : Screen(Text.literal("")) {
horizontalAmount: Double,
verticalAmount: Double
): Boolean {
- scroll = (scroll + StorageOverlay.adjustScrollSpeed(verticalAmount)).toFloat()
+ coerceScroll(StorageOverlay.adjustScrollSpeed(verticalAmount).toFloat())
+ return true
+ }
+ fun coerceScroll(offset: Float) {
+ scroll = (scroll + offset)
.coerceAtMost(getMaxScroll())
.coerceAtLeast(0F)
- return true
}
fun getMaxScroll() = lastRenderedInnerHeight.toFloat() - getScrollPanelInner().height
@@ -142,12 +159,26 @@ class StorageOverlayScreen : Screen(Text.literal("")) {
val guiContext = GuiContext(EmptyComponent())
private val knobStub = EmptyComponent()
- val editButton = PanelComponent(ColumnComponent(FirmButtonComponent(TextComponent("Edit"), action = ::editPages)),
- 8, PanelComponent.DefaultBackgroundRenderer.TRANSPARENT)
+ val editButton = FirmButtonComponent(TextComponent(tr("firmament.storage-overlay.edit-pages", "Edit Pages").string), action = ::editPages)
+ val searchText = Property.of("") // TODO: sync with REI
+ val searchField = TextFieldComponent(searchText, 100, GetSetter.constant(true),
+ tr("firmament.storage-overlay.search.suggestion", "Search...").string,
+ IMinecraft.instance.defaultFontRenderer)
+ val controlComponent = PanelComponent(
+ ColumnComponent(
+ searchField,
+ editButton,
+ ),
+ 8, PanelComponent.DefaultBackgroundRenderer.TRANSPARENT
+ )
init {
- guiContext.adopt(editButton)
+ searchText.addObserver { _, _ ->
+ layoutedForEach(StorageOverlay.Data.data ?: StorageData(), { _, _, _ -> })
+ coerceScroll(0F)
+ }
guiContext.adopt(knobStub)
+ guiContext.adopt(controlComponent)
}
fun drawControls(context: DrawContext, mouseX: Int, mouseY: Int) {
@@ -157,7 +188,7 @@ class StorageOverlayScreen : Screen(Text.literal("")) {
measurements.controlY,
CONTROL_BACKGROUND_WIDTH, CONTROL_HEIGHT)
context.drawMCComponentInPlace(
- editButton,
+ controlComponent,
measurements.controlX, measurements.controlY,
CONTROL_WIDTH, CONTROL_HEIGHT,
mouseX, mouseY)
@@ -211,9 +242,9 @@ class StorageOverlayScreen : Screen(Text.literal("")) {
fun createScissors(context: DrawContext) {
val rect = getScrollPanelInner()
- context.enableScissor(
- rect.minX, rect.minY,
- rect.maxX, rect.maxY
+ context.enableScissorWithoutTranslation(
+ rect.minX.toFloat(), rect.minY.toFloat(),
+ rect.maxX.toFloat(), rect.maxY.toFloat(),
)
}
@@ -251,7 +282,7 @@ class StorageOverlayScreen : Screen(Text.literal("")) {
knobGrabbed = false
return true
}
- if (clickMCComponentInPlace(editButton,
+ if (clickMCComponentInPlace(controlComponent,
measurements.controlX, measurements.controlY,
CONTROL_WIDTH, CONTROL_HEIGHT,
mouseX.toInt(), mouseY.toInt(),
@@ -272,6 +303,7 @@ class StorageOverlayScreen : Screen(Text.literal("")) {
}
fun mouseClicked(mouseX: Double, mouseY: Double, button: Int, activePage: StoragePageSlot?): Boolean {
+ guiContext.setFocusedElement(null) // Blur all elements. They will be refocused by clickMCComponentInPlace if in doubt, and we don't have any double click components.
if (getScrollPanelInner().contains(mouseX, mouseY)) {
val data = StorageOverlay.Data.data ?: StorageData()
layoutedForEach(data) { rect, page, _ ->
@@ -290,7 +322,7 @@ class StorageOverlayScreen : Screen(Text.literal("")) {
knobGrabbed = true
return true
}
- if (clickMCComponentInPlace(editButton,
+ if (clickMCComponentInPlace(controlComponent,
measurements.controlX, measurements.controlY,
CONTROL_WIDTH, CONTROL_HEIGHT,
mouseX.toInt(), mouseY.toInt(),
@@ -299,6 +331,78 @@ class StorageOverlayScreen : Screen(Text.literal("")) {
return false
}
+ override fun charTyped(chr: Char, modifiers: Int): Boolean {
+ if (typeMCComponentInPlace(
+ controlComponent,
+ measurements.controlX, measurements.controlY,
+ CONTROL_WIDTH, CONTROL_HEIGHT,
+ KeyboardEvent.CharTyped(chr)
+ )
+ ) {
+ return true
+ }
+ return super.charTyped(chr, modifiers)
+ }
+
+ override fun keyReleased(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
+ if (typeMCComponentInPlace(
+ controlComponent,
+ measurements.controlX, measurements.controlY,
+ CONTROL_WIDTH, CONTROL_HEIGHT,
+ KeyboardEvent.KeyPressed(keyCode, false)
+ )
+ ) {
+ return true
+ }
+ return super.keyReleased(keyCode, scanCode, modifiers)
+ }
+
+ override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
+ if (typeMCComponentInPlace(
+ controlComponent,
+ measurements.controlX, measurements.controlY,
+ CONTROL_WIDTH, CONTROL_HEIGHT,
+ KeyboardEvent.KeyPressed(keyCode, true)
+ )
+ ) {
+ return true
+ }
+ return super.keyPressed(keyCode, scanCode, modifiers)
+ }
+
+
+ var searchCache: String? = null
+ var filteredPagesCache = setOf<StoragePageSlot>()
+
+ fun getFilteredPages(): Set<StoragePageSlot> {
+ val searchValue = searchText.get()
+ val data = StorageOverlay.Data.data ?: return filteredPagesCache // Do not update cache if data is missing
+ if (searchCache == searchValue) return filteredPagesCache
+ val result =
+ data.storageInventories
+ .entries.asSequence()
+ .filter { it.value.inventory?.stacks?.any { matchesSearch(it, searchValue) } ?: true }
+ .map { it.key }
+ .toSet()
+ searchCache = searchValue
+ filteredPagesCache = result
+ return result
+ }
+
+
+ fun matchesSearch(itemStack: ItemStack, search: String): Boolean {
+ val searchWords = search.words().toCollection(TreeSet())
+ fun removePrefixes(value: String) {
+ searchWords.removeIf { value.contains(it, ignoreCase = true) }
+ }
+ itemStack.displayNameAccordingToNbt.unformattedString.words().forEach(::removePrefixes)
+ if (searchWords.isEmpty()) return true
+ itemStack.loreAccordingToNbt.forEach {
+ it.unformattedString.words().forEach(::removePrefixes)
+ }
+ return searchWords.isEmpty()
+ }
+
private inline fun layoutedForEach(
data: StorageData,
func: (
@@ -309,7 +413,9 @@ class StorageOverlayScreen : Screen(Text.literal("")) {
var yOffset = -scroll.toInt()
var xOffset = 0
var maxHeight = 0
+ val filter = getFilteredPages()
for ((page, inventory) in data.storageInventories.entries) {
+ if (page !in filter) continue
val currentHeight = inventory.inventory?.let { it.rows * SLOT_SIZE + 4 + textRenderer.fontHeight }
?: 18
maxHeight = maxOf(maxHeight, currentHeight)
diff --git a/src/main/kotlin/features/inventory/storageoverlay/VirtualInventory.kt b/src/main/kotlin/features/inventory/storageoverlay/VirtualInventory.kt
index e07df8a..3b86184 100644
--- a/src/main/kotlin/features/inventory/storageoverlay/VirtualInventory.kt
+++ b/src/main/kotlin/features/inventory/storageoverlay/VirtualInventory.kt
@@ -1,5 +1,3 @@
-
-
package moe.nea.firmament.features.inventory.storageoverlay
import io.ktor.util.decodeBase64Bytes
@@ -19,47 +17,59 @@ import net.minecraft.nbt.NbtIo
import net.minecraft.nbt.NbtList
import net.minecraft.nbt.NbtOps
import net.minecraft.nbt.NbtSizeTracker
+import net.minecraft.registry.RegistryOps
+import moe.nea.firmament.util.ErrorUtil
+import moe.nea.firmament.util.MC
+import moe.nea.firmament.util.mc.TolerantRegistriesOps
@Serializable(with = VirtualInventory.Serializer::class)
data class VirtualInventory(
- val stacks: List<ItemStack>
+ val stacks: List<ItemStack>
) {
- val rows = stacks.size / 9
+ val rows = stacks.size / 9
+
+ init {
+ assert(stacks.size % 9 == 0)
+ assert(stacks.size / 9 in 1..5)
+ }
- init {
- assert(stacks.size % 9 == 0)
- assert(stacks.size / 9 in 1..5)
- }
+ object Serializer : KSerializer<VirtualInventory> {
+ const val INVENTORY = "INVENTORY"
+ override val descriptor: SerialDescriptor
+ get() = PrimitiveSerialDescriptor("VirtualInventory", PrimitiveKind.STRING)
- object Serializer : KSerializer<VirtualInventory> {
- 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())
+ val ops = getOps()
+ return VirtualInventory(items.map {
+ it as NbtCompound
+ if (it.isEmpty) ItemStack.EMPTY
+ else ErrorUtil.catch("Could not deserialize item") {
+ ItemStack.CODEC.parse(ops, it).orThrow
+ }.or { ItemStack.EMPTY }
+ })
+ }
- 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 }
- })
- }
+ fun getOps() = TolerantRegistriesOps(NbtOps.INSTANCE, MC.currentOrDefaultRegistries)
- 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())
- }
- }
+ override fun serialize(encoder: Encoder, value: VirtualInventory) {
+ val list = NbtList()
+ val ops = getOps()
+ value.stacks.forEach {
+ if (it.isEmpty) list.add(NbtCompound())
+ else list.add(ErrorUtil.catch("Could not serialize item") {
+ ItemStack.CODEC.encode(it,
+ ops,
+ NbtCompound()).orThrow
+ }
+ .or { 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/PickaxeAbility.kt b/src/main/kotlin/features/mining/PickaxeAbility.kt
index 4fcf8a7..1737969 100644
--- a/src/main/kotlin/features/mining/PickaxeAbility.kt
+++ b/src/main/kotlin/features/mining/PickaxeAbility.kt
@@ -7,11 +7,13 @@ import net.minecraft.item.ItemStack
import net.minecraft.util.DyeColor
import net.minecraft.util.Hand
import net.minecraft.util.Identifier
+import net.minecraft.util.StringIdentifiable
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.HudRenderEvent
import moe.nea.firmament.events.ProcessChatEvent
import moe.nea.firmament.events.ProfileSwitchEvent
import moe.nea.firmament.events.SlotClickEvent
+import moe.nea.firmament.events.UseItemEvent
import moe.nea.firmament.events.WorldReadyEvent
import moe.nea.firmament.features.FirmamentFeature
import moe.nea.firmament.gui.config.ManagedConfig
@@ -27,10 +29,13 @@ import moe.nea.firmament.util.mc.displayNameAccordingToNbt
import moe.nea.firmament.util.mc.loreAccordingToNbt
import moe.nea.firmament.util.parseShortNumber
import moe.nea.firmament.util.parseTimePattern
+import moe.nea.firmament.util.red
import moe.nea.firmament.util.render.RenderCircleProgress
import moe.nea.firmament.util.render.lerp
import moe.nea.firmament.util.skyblock.AbilityUtils
+import moe.nea.firmament.util.skyblock.ItemType
import moe.nea.firmament.util.toShedaniel
+import moe.nea.firmament.util.tr
import moe.nea.firmament.util.unformattedString
import moe.nea.firmament.util.useMatch
@@ -43,6 +48,21 @@ object PickaxeAbility : FirmamentFeature {
val cooldownEnabled by toggle("ability-cooldown") { false }
val cooldownScale by integer("ability-scale", 16, 64) { 16 }
val drillFuelBar by toggle("fuel-bar") { true }
+ val blockOnPrivateIsland by choice(
+ "block-on-dynamic",
+ ) {
+ BlockPickaxeAbility.ONLY_DESTRUCTIVE
+ }
+ }
+
+ enum class BlockPickaxeAbility : StringIdentifiable {
+ NEVER,
+ ALWAYS,
+ ONLY_DESTRUCTIVE;
+
+ override fun asString(): String {
+ return name
+ }
}
var lobbyJoinTime = TimeMark.farPast()
@@ -56,6 +76,8 @@ object PickaxeAbility : FirmamentFeature {
"Maniac Miner" to 59.seconds,
"Vein Seeker" to 60.seconds
)
+ val destructiveAbilities = setOf("Pickobulus")
+ val pickaxeTypes = setOf(ItemType.PICKAXE, ItemType.DRILL, ItemType.GAUNTLET)
override val config: ManagedConfig
get() = TConfig
@@ -74,6 +96,27 @@ object PickaxeAbility : FirmamentFeature {
}
@Subscribe
+ fun onPickaxeRightClick(event: UseItemEvent) {
+ if (TConfig.blockOnPrivateIsland == BlockPickaxeAbility.NEVER) return
+ if (SBData.skyblockLocation != SkyBlockIsland.PRIVATE_ISLAND && SBData.skyblockLocation != SkyBlockIsland.GARDEN) return
+ val itemType = ItemType.fromItemStack(event.item)
+ if (itemType !in pickaxeTypes) return
+ val ability = AbilityUtils.getAbilities(event.item)
+ val shouldBlock = when (TConfig.blockOnPrivateIsland) {
+ BlockPickaxeAbility.NEVER -> false
+ BlockPickaxeAbility.ALWAYS -> ability.any()
+ BlockPickaxeAbility.ONLY_DESTRUCTIVE -> ability.any { it.name in destructiveAbilities }
+ }
+ if (shouldBlock) {
+ MC.sendChat(tr("firmament.pickaxe.blocked",
+ "Firmament blocked a pickaxe ability from being used on a private island.")
+ .red() // TODO: .clickCommand("firm confignavigate ${TConfig.identifier} block-on-dynamic")
+ )
+ event.cancel()
+ }
+ }
+
+ @Subscribe
fun onSlotClick(it: SlotClickEvent) {
if (MC.screen?.title?.unformattedString == "Heart of the Mountain") {
val name = it.stack.displayNameAccordingToNbt.unformattedString
@@ -113,10 +156,21 @@ object PickaxeAbility : FirmamentFeature {
fun onChatMessage(it: ProcessChatEvent) {
abilityUsePattern.useMatch(it.unformattedString) {
lastUsage[group("name")] = TimeMark.now()
+ abilityOverride = group("name")
}
abilitySwitchPattern.useMatch(it.unformattedString) {
abilityOverride = group("ability")
}
+ pickaxeAbilityCooldownPattern.useMatch(it.unformattedString) {
+ val ability = abilityOverride ?: return@useMatch
+ val remainingCooldown = parseTimePattern(group("remainingCooldown"))
+ val length = defaultAbilityDurations[ability] ?: return@useMatch
+ lastUsage[ability] = TimeMark.ago(length - remainingCooldown)
+ }
+ nowAvailable.useMatch(it.unformattedString) {
+ val ability = group("name")
+ lastUsage[ability] = TimeMark.farPast()
+ }
}
@Subscribe
@@ -136,6 +190,7 @@ object PickaxeAbility : FirmamentFeature {
val fuelPattern = Pattern.compile("Fuel: .*/(?<maxFuel>$SHORT_NUMBER_FORMAT)")
val pickaxeAbilityCooldownPattern =
Pattern.compile("Your pickaxe ability is on cooldown for (?<remainingCooldown>$TIME_PATTERN)\\.")
+ val nowAvailable = Pattern.compile("(?<name>[a-zA-Z0-9 ]+) is now available!")
data class PickaxeAbilityData(
val name: String,
diff --git a/src/main/kotlin/features/misc/TimerFeature.kt b/src/main/kotlin/features/misc/TimerFeature.kt
new file mode 100644
index 0000000..7c4833d
--- /dev/null
+++ b/src/main/kotlin/features/misc/TimerFeature.kt
@@ -0,0 +1,124 @@
+package moe.nea.firmament.features.misc
+
+import com.mojang.brigadier.arguments.IntegerArgumentType
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.seconds
+import moe.nea.firmament.Firmament
+import moe.nea.firmament.annotations.Subscribe
+import moe.nea.firmament.commands.DurationArgumentType
+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.events.TickEvent
+import moe.nea.firmament.util.CommonSoundEffects
+import moe.nea.firmament.util.FirmFormatters
+import moe.nea.firmament.util.MC
+import moe.nea.firmament.util.MinecraftDispatcher
+import moe.nea.firmament.util.TimeMark
+import moe.nea.firmament.util.clickCommand
+import moe.nea.firmament.util.lime
+import moe.nea.firmament.util.red
+import moe.nea.firmament.util.tr
+import moe.nea.firmament.util.yellow
+
+object TimerFeature {
+ data class Timer(
+ val start: TimeMark,
+ val duration: Duration,
+ val message: String,
+ val timerId: Int,
+ ) {
+ fun timeLeft() = (duration - start.passedTime()).coerceAtLeast(0.seconds)
+ fun isDone() = start.passedTime() >= duration
+ }
+
+ // Theoretically for optimal performance this could be a treeset keyed to the end time
+ val timers = mutableListOf<Timer>()
+
+ @Subscribe
+ fun tick(event: TickEvent) {
+ timers.removeAll {
+ if (it.isDone()) {
+ MC.sendChat(tr("firmament.timer.finished",
+ "The timer you set ${FirmFormatters.formatTimespan(it.duration)} ago just went off: ${it.message}")
+ .yellow())
+ Firmament.coroutineScope.launch {
+ withContext(MinecraftDispatcher) {
+ repeat(5) {
+ CommonSoundEffects.playSuccess()
+ delay(0.2.seconds)
+ }
+ }
+ }
+ true
+ } else {
+ false
+ }
+ }
+ }
+
+ fun startTimer(duration: Duration, message: String) {
+ val timerId = createTimerId++
+ timers.add(Timer(TimeMark.now(), duration, message, timerId))
+ MC.sendChat(
+ tr("firmament.timer.start",
+ "Timer started for $message in ${FirmFormatters.formatTimespan(duration)}.").lime()
+ .append(" ")
+ .append(
+ tr("firmament.timer.cancelbutton",
+ "Click here to cancel the timer."
+ ).clickCommand("/firm timer clear $timerId").red()
+ )
+ )
+ }
+
+ fun clearTimer(timerId: Int) {
+ val timer = timers.indexOfFirst { it.timerId == timerId }
+ if (timer < 0) {
+ MC.sendChat(tr("firmament.timer.cancel.fail",
+ "Could not cancel that timer. Maybe it was already cancelled?").red())
+ } else {
+ val timerData = timers[timer]
+ timers.removeAt(timer)
+ MC.sendChat(tr("firmament.timer.cancel.done",
+ "Cancelled timer ${timerData.message}. It would have been done in ${
+ FirmFormatters.formatTimespan(timerData.timeLeft())
+ }.").lime())
+ }
+ }
+
+ var createTimerId = 0
+
+ @Subscribe
+ fun onCommands(event: CommandEvent.SubCommand) {
+ event.subcommand("cleartimer") {
+ thenArgument("timerId", IntegerArgumentType.integer(0)) { timerId ->
+ thenExecute {
+ clearTimer(this[timerId])
+ }
+ }
+ thenExecute {
+ timers.map { it.timerId }.forEach {
+ clearTimer(it)
+ }
+ }
+ }
+ event.subcommand("timer") {
+ thenArgument("time", DurationArgumentType) { duration ->
+ thenExecute {
+ startTimer(this[duration], "no message")
+ }
+ thenArgument("message", RestArgumentType) { message ->
+ thenExecute {
+ startTimer(this[duration], this[message])
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/kotlin/features/texturepack/BakedModelExtra.kt b/src/main/kotlin/features/texturepack/BakedModelExtra.kt
deleted file mode 100644
index 6305748..0000000
--- a/src/main/kotlin/features/texturepack/BakedModelExtra.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-package moe.nea.firmament.features.texturepack
-
-import net.fabricmc.fabric.api.renderer.v1.model.WrapperBakedModel as WrapperBakedModelFabric
-import net.minecraft.client.render.model.BakedModel
-import net.minecraft.client.render.model.WrapperBakedModel
-import moe.nea.firmament.util.ErrorUtil
-
-interface BakedModelExtra {
- companion object {
- @JvmStatic
- fun cast(originalModel: BakedModel): BakedModelExtra? {
- var p = originalModel
- for (i in 0..256) {
- p = when (p) {
- is BakedModelExtra -> return p
- is WrapperBakedModel -> p.wrapped
- is WrapperBakedModelFabric -> WrapperBakedModelFabric.unwrap(p)
- else -> break
- }
- }
- ErrorUtil.softError("Could not find a baked model for $originalModel")
- return null
- }
- }
-
- var tintOverrides_firmament: TintOverrides?
-
- 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
deleted file mode 100644
index e9391f1..0000000
--- a/src/main/kotlin/features/texturepack/BakedOverrideData.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-
-package moe.nea.firmament.features.texturepack
-
-import net.minecraft.client.render.model.json.ModelOverrideList
-
-interface BakedOverrideData {
- fun getFirmamentOverrides(): Array<FirmamentModelPredicate>?
- fun setFirmamentOverrides(overrides: Array<FirmamentModelPredicate>?)
- companion object{
- @Suppress("CAST_NEVER_SUCCEEDS")
- @JvmStatic
- fun cast(bakedOverride: ModelOverrideList.BakedOverride): BakedOverrideData = bakedOverride as BakedOverrideData
- }
-}
diff --git a/src/main/kotlin/features/texturepack/CustomModelOverrideParser.kt b/src/main/kotlin/features/texturepack/CustomModelOverrideParser.kt
deleted file mode 100644
index c5fc20b..0000000
--- a/src/main/kotlin/features/texturepack/CustomModelOverrideParser.kt
+++ /dev/null
@@ -1,82 +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 moe.nea.firmament.features.texturepack.predicates.AndPredicate
-import moe.nea.firmament.features.texturepack.predicates.DisplayNamePredicate
-import moe.nea.firmament.features.texturepack.predicates.ExtraAttributesPredicate
-import moe.nea.firmament.features.texturepack.predicates.ItemPredicate
-import moe.nea.firmament.features.texturepack.predicates.LorePredicate
-import moe.nea.firmament.features.texturepack.predicates.NotPredicate
-import moe.nea.firmament.features.texturepack.predicates.OrPredicate
-import moe.nea.firmament.features.texturepack.predicates.PetPredicate
-import net.minecraft.item.ItemStack
-import net.minecraft.util.Identifier
-
-object CustomModelOverrideParser {
- object FirmamentRootPredicateSerializer : KSerializer<FirmamentModelPredicate> {
- 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<Identifier, FirmamentModelPredicateParser>()
-
-
- 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<FirmamentModelPredicate> {
- val parsedPredicates = mutableListOf<FirmamentModelPredicate>()
- 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<FirmamentModelPredicate>? {
- 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
deleted file mode 100644
index 627d39a..0000000
--- a/src/main/kotlin/features/texturepack/CustomSkyBlockTextures.kt
+++ /dev/null
@@ -1,135 +0,0 @@
-package moe.nea.firmament.features.texturepack
-
-import com.mojang.authlib.minecraft.MinecraftProfileTexture
-import com.mojang.authlib.properties.Property
-import java.util.Optional
-import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable
-import kotlin.jvm.optionals.getOrNull
-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.FinalizeResourceManagerEvent
-import moe.nea.firmament.events.TickEvent
-import moe.nea.firmament.features.FirmamentFeature
-import moe.nea.firmament.gui.config.ManagedConfig
-import moe.nea.firmament.util.collections.WeakCache
-import moe.nea.firmament.util.mc.decodeProfileTextureProperty
-import moe.nea.firmament.util.skyBlockId
-
-object CustomSkyBlockTextures : FirmamentFeature {
- override val identifier: String
- get() = "custom-skyblock-textures"
-
- object TConfig : ManagedConfig(identifier, Category.INTEGRATIONS) { // TODO: should this be its own thing?
- val enabled by toggle("enabled") { true }
- val skullsEnabled by toggle("skulls-enabled") { true }
- val cacheForever by toggle("cache-forever") { true }
- val cacheDuration by integer("cache-duration", 0, 100) { 1 }
- val enableModelOverrides by toggle("model-overrides") { true }
- val enableArmorOverrides by toggle("armor-overrides") { true }
- val enableBlockOverrides by toggle("block-overrides") { true }
- val enableLegacyCIT by toggle("legacy-cit") { true }
- val allowRecoloringUiText by toggle("recolor-text") { true }
- }
-
- override val config: ManagedConfig
- get() = TConfig
-
- val allItemCaches by lazy {
- listOf(
- CustomItemModelEvent.cache.cache,
- skullTextureCache.cache,
- CustomGlobalTextures.overrideCache.cache,
- CustomGlobalArmorOverrides.overrideCache.cache
- )
- }
-
- fun clearAllCaches() {
- allItemCaches.forEach(WeakCache<*, *, *>::clear)
- }
-
- @Subscribe
- fun onTick(it: TickEvent) {
- if (TConfig.cacheForever) return
- if (TConfig.cacheDuration < 1 || it.tickCount % TConfig.cacheDuration == 0) {
- clearAllCaches()
- }
- }
-
- @Subscribe
- fun onStart(event: FinalizeResourceManagerEvent) {
- event.registerOnApply("Clear firmament CIT caches") {
- clearAllCaches()
- }
- }
-
- @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 =
- WeakCache.memoize<ProfileComponent, Optional<Identifier>>("SkullTextureCache") { component ->
- val id = getSkullTexture(component) ?: return@memoize Optional.empty()
- if (!MinecraftClient.getInstance().resourceManager.getResource(id).isPresent) {
- return@memoize Optional.empty()
- }
- return@memoize Optional.of(id)
- }
-
- 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<RenderLayer>
- ) {
- if (type != SkullBlock.Type.PLAYER) return
- if (!TConfig.skullsEnabled) return
- if (component == null) return
-
- val n = skullTextureCache.invoke(component).getOrNull() ?: return
- cir.returnValue = RenderLayer.getEntityTranslucent(n)
- }
-}
diff --git a/src/main/kotlin/features/texturepack/FirmamentModelPredicate.kt b/src/main/kotlin/features/texturepack/FirmamentModelPredicate.kt
deleted file mode 100644
index d11fec0..0000000
--- a/src/main/kotlin/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/features/texturepack/JsonUnbakedModelFirmExtra.kt b/src/main/kotlin/features/texturepack/JsonUnbakedModelFirmExtra.kt
deleted file mode 100644
index 9f641b8..0000000
--- a/src/main/kotlin/features/texturepack/JsonUnbakedModelFirmExtra.kt
+++ /dev/null
@@ -1,16 +0,0 @@
-
-package moe.nea.firmament.features.texturepack
-
-import net.minecraft.client.render.model.Baker
-import net.minecraft.util.Identifier
-
-interface JsonUnbakedModelFirmExtra {
- fun storeExtraBaker_firmament(baker: Baker)
-
- fun setHeadModel_firmament(identifier: Identifier?)
- fun getHeadModel_firmament(): Identifier?
-
- fun setTintOverrides_firmament(tintOverrides: TintOverrides?)
- fun getTintOverrides_firmament(): TintOverrides
-
-}
diff --git a/src/main/kotlin/features/texturepack/ModelOverrideData.kt b/src/main/kotlin/features/texturepack/ModelOverrideData.kt
deleted file mode 100644
index 29d9192..0000000
--- a/src/main/kotlin/features/texturepack/ModelOverrideData.kt
+++ /dev/null
@@ -1,15 +0,0 @@
-package moe.nea.firmament.features.texturepack
-
-import net.minecraft.client.render.model.json.ModelOverride
-
-interface ModelOverrideData {
- companion object {
-
- @JvmStatic
- @Suppress("CAST_NEVER_SUCCEEDS")
- fun cast(override: ModelOverride) = override as ModelOverrideData
- }
-
- fun getFirmamentOverrides(): Array<FirmamentModelPredicate>?
- fun setFirmamentOverrides(overrides: Array<FirmamentModelPredicate>?)
-}
diff --git a/src/main/kotlin/features/world/Waypoints.kt b/src/main/kotlin/features/world/Waypoints.kt
index 16db059..3ebfe70 100644
--- a/src/main/kotlin/features/world/Waypoints.kt
+++ b/src/main/kotlin/features/world/Waypoints.kt
@@ -4,6 +4,7 @@ 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 kotlinx.serialization.encodeToString
import kotlin.collections.component1
import kotlin.collections.component2
import kotlin.collections.set
@@ -32,6 +33,7 @@ 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
+import moe.nea.firmament.util.tr
object Waypoints : FirmamentFeature {
override val identifier: String
@@ -198,11 +200,22 @@ object Waypoints : FirmamentFeature {
}
}
}
+ thenLiteral("export") {
+ thenExecute {
+ val data = Firmament.tightJson.encodeToString<List<ColeWeightWaypoint>>(waypoints.map {
+ ColeWeightWaypoint(it.x,
+ it.y,
+ it.z)
+ })
+ ClipboardUtils.setTextContent(data)
+ source.sendFeedback(tr("firmament.command.waypoint.export", "Copied ${waypoints.size} waypoints to clipboard"))
+ }
+ }
thenLiteral("import") {
thenExecute {
val contents = ClipboardUtils.getTextContents()
val data = try {
- Firmament.json.decodeFromString<List<ColeWeightWaypoint>>(contents)
+ Firmament.tightJson.decodeFromString<List<ColeWeightWaypoint>>(contents)
} catch (ex: Exception) {
Firmament.logger.error("Could not load waypoints from clipboard", ex)
source.sendError(Text.translatable("firmament.command.waypoint.import.error"))
diff --git a/src/main/kotlin/gui/CheckboxComponent.kt b/src/main/kotlin/gui/CheckboxComponent.kt
new file mode 100644
index 0000000..761c086
--- /dev/null
+++ b/src/main/kotlin/gui/CheckboxComponent.kt
@@ -0,0 +1,56 @@
+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.MouseEvent
+import io.github.notenoughupdates.moulconfig.observer.GetSetter
+import io.github.notenoughupdates.moulconfig.platform.ModernRenderContext
+import net.minecraft.client.render.RenderLayer
+import moe.nea.firmament.Firmament
+
+class CheckboxComponent<T>(
+ val state: GetSetter<T>,
+ val value: T,
+) : GuiComponent() {
+ override fun getWidth(): Int {
+ return 16
+ }
+
+ override fun getHeight(): Int {
+ return 16
+ }
+
+ fun isEnabled(): Boolean {
+ return state.get() == value
+ }
+
+ override fun render(context: GuiImmediateContext) {
+ val ctx = (context.renderContext as ModernRenderContext).drawContext
+ ctx.drawGuiTexture(
+ RenderLayer::getGuiTextured,
+ if (isEnabled()) Firmament.identifier("firmament:widget/checkbox_checked")
+ else Firmament.identifier("firmament:widget/checkbox_unchecked"),
+ 0, 0,
+ 16, 16
+ )
+ }
+
+ var isClicking = false
+
+ override fun mouseEvent(mouseEvent: MouseEvent, context: GuiImmediateContext): Boolean {
+ if (mouseEvent is MouseEvent.Click) {
+ if (isClicking && !mouseEvent.mouseState && mouseEvent.mouseButton == 0) {
+ isClicking = false
+ if (context.isHovered)
+ state.set(value)
+ return true
+ }
+ if (mouseEvent.mouseState && mouseEvent.mouseButton == 0 && context.isHovered) {
+ requestFocus()
+ isClicking = true
+ return true
+ }
+ }
+ return false
+ }
+}
diff --git a/src/main/kotlin/gui/config/ChoiceHandler.kt b/src/main/kotlin/gui/config/ChoiceHandler.kt
new file mode 100644
index 0000000..2ea3efc
--- /dev/null
+++ b/src/main/kotlin/gui/config/ChoiceHandler.kt
@@ -0,0 +1,48 @@
+package moe.nea.firmament.gui.config
+
+import io.github.notenoughupdates.moulconfig.gui.HorizontalAlign
+import io.github.notenoughupdates.moulconfig.gui.VerticalAlign
+import io.github.notenoughupdates.moulconfig.gui.component.AlignComponent
+import io.github.notenoughupdates.moulconfig.gui.component.RowComponent
+import io.github.notenoughupdates.moulconfig.gui.component.TextComponent
+import kotlinx.serialization.json.JsonElement
+import kotlin.jvm.optionals.getOrNull
+import net.minecraft.util.StringIdentifiable
+import moe.nea.firmament.gui.CheckboxComponent
+import moe.nea.firmament.util.ErrorUtil
+import moe.nea.firmament.util.json.KJsonOps
+
+class ChoiceHandler<E>(
+ val enumClass: Class<E>,
+ val universe: List<E>,
+) : ManagedConfig.OptionHandler<E> where E : Enum<E>, E : StringIdentifiable {
+ val codec = StringIdentifiable.createCodec {
+ @Suppress("UNCHECKED_CAST", "PLATFORM_CLASS_MAPPED_TO_KOTLIN")
+ (universe as java.util.List<*>).toArray(arrayOfNulls<Enum<E>>(0)) as Array<E>
+ }
+ val renderer = EnumRenderer.default<E>()
+
+ override fun toJson(element: E): JsonElement? {
+ return codec.encodeStart(KJsonOps.INSTANCE, element)
+ .promotePartial { ErrorUtil.softError("Failed to encode json element '$element': $it") }.result()
+ .getOrNull()
+ }
+
+ override fun fromJson(element: JsonElement): E {
+ return codec.decode(KJsonOps.INSTANCE, element)
+ .promotePartial { ErrorUtil.softError("Failed to decode json element '$element': $it") }
+ .result()
+ .get()
+ .first
+ }
+
+ override fun emitGuiElements(opt: ManagedOption<E>, guiAppender: GuiAppender) {
+ guiAppender.appendFullRow(TextComponent(opt.labelText.string))
+ for (e in universe) {
+ guiAppender.appendFullRow(RowComponent(
+ AlignComponent(CheckboxComponent(opt, e), { HorizontalAlign.LEFT }, { VerticalAlign.CENTER }),
+ TextComponent(renderer.getName(opt, e).string)
+ ))
+ }
+ }
+}
diff --git a/src/main/kotlin/gui/config/EnumRenderer.kt b/src/main/kotlin/gui/config/EnumRenderer.kt
new file mode 100644
index 0000000..3b80b7e
--- /dev/null
+++ b/src/main/kotlin/gui/config/EnumRenderer.kt
@@ -0,0 +1,15 @@
+package moe.nea.firmament.gui.config
+
+import net.minecraft.text.Text
+
+interface EnumRenderer<E : Any> {
+ fun getName(option: ManagedOption<E>, value: E): Text
+
+ companion object {
+ fun <E : Enum<E>> default() = object : EnumRenderer<E> {
+ override fun getName(option: ManagedOption<E>, value: E): Text {
+ return Text.translatable(option.rawLabelText + ".choice." + value.name.lowercase())
+ }
+ }
+ }
+}
diff --git a/src/main/kotlin/gui/config/ManagedConfig.kt b/src/main/kotlin/gui/config/ManagedConfig.kt
index 8222a46..7ddda9e 100644
--- a/src/main/kotlin/gui/config/ManagedConfig.kt
+++ b/src/main/kotlin/gui/config/ManagedConfig.kt
@@ -1,5 +1,6 @@
package moe.nea.firmament.gui.config
+import com.mojang.serialization.Codec
import io.github.notenoughupdates.moulconfig.gui.CloseEventListener
import io.github.notenoughupdates.moulconfig.gui.GuiComponentWrapper
import io.github.notenoughupdates.moulconfig.gui.GuiContext
@@ -20,6 +21,7 @@ import kotlin.io.path.writeText
import kotlin.time.Duration
import net.minecraft.client.gui.screen.Screen
import net.minecraft.text.Text
+import net.minecraft.util.StringIdentifiable
import moe.nea.firmament.Firmament
import moe.nea.firmament.gui.FirmButtonComponent
import moe.nea.firmament.keybindings.SavedKeyBinding
@@ -113,6 +115,41 @@ abstract class ManagedConfig(
return option(propertyName, default, BooleanHandler(this))
}
+ protected fun <E> choice(
+ propertyName: String,
+ enumClass: Class<E>,
+ default: () -> E
+ ): ManagedOption<E> where E : Enum<E>, E : StringIdentifiable {
+ return option(propertyName, default, ChoiceHandler(enumClass, enumClass.enumConstants.toList()))
+ }
+
+ protected inline fun <reified E> choice(
+ propertyName: String,
+ noinline default: () -> E
+ ): ManagedOption<E> where E : Enum<E>, E : StringIdentifiable {
+ return choice(propertyName, E::class.java, default)
+ }
+
+ private fun <E> createStringIdentifiable(x: () -> Array<out E>): Codec<E> where E : Enum<E>, E : StringIdentifiable {
+ return StringIdentifiable.createCodec { x() }
+ }
+
+ // TODO: wait on https://youtrack.jetbrains.com/issue/KT-73434
+// protected inline fun <reified E> choice(
+// propertyName: String,
+// noinline default: () -> E
+// ): ManagedOption<E> where E : Enum<E>, E : StringIdentifiable {
+// return choice(
+// propertyName,
+// enumEntries<E>().toList(),
+// StringIdentifiable.createCodec { enumValues<E>() },
+// EnumRenderer.default(),
+// default
+// )
+// }
+ open fun onChange(option: ManagedOption<*>) {
+ }
+
protected fun duration(
propertyName: String,
min: Duration,
diff --git a/src/main/kotlin/gui/config/ManagedOption.kt b/src/main/kotlin/gui/config/ManagedOption.kt
index d1aba83..383f392 100644
--- a/src/main/kotlin/gui/config/ManagedOption.kt
+++ b/src/main/kotlin/gui/config/ManagedOption.kt
@@ -6,7 +6,6 @@ import kotlinx.serialization.json.JsonObject
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
import net.minecraft.text.Text
-import moe.nea.firmament.Firmament
import moe.nea.firmament.util.ErrorUtil
class ManagedOption<T : Any>(
@@ -28,7 +27,13 @@ class ManagedOption<T : Any>(
val descriptionTranslationKey = "firmament.config.${element.name}.${propertyName}.description"
val labelDescription: Text = Text.translatable(descriptionTranslationKey)
- lateinit var value: T
+ private var actualValue: T? = null
+ var value: T
+ get() = actualValue ?: error("Lateinit variable not initialized")
+ set(value) {
+ actualValue = value
+ element.onChange(this)
+ }
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
this.value = value
diff --git a/src/main/kotlin/gui/entity/EntityRenderer.kt b/src/main/kotlin/gui/entity/EntityRenderer.kt
index 022b9a3..fd7a0c4 100644
--- a/src/main/kotlin/gui/entity/EntityRenderer.kt
+++ b/src/main/kotlin/gui/entity/EntityRenderer.kt
@@ -111,10 +111,14 @@ object EntityRenderer {
renderContext: DrawContext,
posX: Int,
posY: Int,
- mouseX: Float,
- mouseY: Float
+ // TODO: Add width, height properties here
+ width: Double,
+ height: Double,
+ mouseX: Double,
+ mouseY: Double,
+ entityScale: Double = (height - 10.0) / 2.0
) {
- var bottomOffset = 0.0F
+ var bottomOffset = 0.0
var currentEntity = entity
val maxSize = entity.iterate { it.firstPassenger as? LivingEntity }
.map { it.height }
@@ -125,9 +129,9 @@ object EntityRenderer {
renderContext,
posX,
posY,
- posX + 50,
- posY + 80,
- minOf(2F / maxSize, 1F) * 30,
+ (posX + width).toInt(),
+ (posY + height).toInt(),
+ minOf(2F / maxSize, 1F) * entityScale,
-bottomOffset,
mouseX,
mouseY,
@@ -146,17 +150,19 @@ object EntityRenderer {
y1: Int,
x2: Int,
y2: Int,
- size: Float,
- bottomOffset: Float,
- mouseX: Float,
- mouseY: Float,
+ size: Double,
+ bottomOffset: Double,
+ mouseX: Double,
+ mouseY: Double,
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 hw = (x2 - x1) / 2
+ val hh = (y2 - y1) / 2
+ val targetYaw = atan(((centerX - mouseX) / hw)).toFloat()
+ val targetPitch = atan(((centerY - mouseY) / hh)).toFloat()
val rotateToFaceTheFront = Quaternionf().rotateZ(Math.PI.toFloat())
val rotateToFaceTheCamera = Quaternionf().rotateX(targetPitch * 20.0f * (Math.PI.toFloat() / 180))
rotateToFaceTheFront.mul(rotateToFaceTheCamera)
@@ -170,12 +176,12 @@ object EntityRenderer {
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)
+ val vector3f = Vector3f(0.0f, (entity.height / 2.0f + bottomOffset).toFloat(), 0.0f)
InventoryScreen.drawEntity(
context,
centerX,
centerY,
- size,
+ size.toFloat(),
vector3f,
rotateToFaceTheFront,
rotateToFaceTheCamera,
diff --git a/src/main/kotlin/gui/entity/FakeWorld.kt b/src/main/kotlin/gui/entity/FakeWorld.kt
index 7ec385c..ccf6b60 100644
--- a/src/main/kotlin/gui/entity/FakeWorld.kt
+++ b/src/main/kotlin/gui/entity/FakeWorld.kt
@@ -8,6 +8,7 @@ import net.minecraft.block.BlockState
import net.minecraft.client.gui.screen.world.SelectWorldScreen
import net.minecraft.component.type.MapIdComponent
import net.minecraft.entity.Entity
+import net.minecraft.entity.boss.dragon.EnderDragonPart
import net.minecraft.entity.damage.DamageSource
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.fluid.Fluid
@@ -262,6 +263,10 @@ class FakeWorld(
return null
}
+ override fun getEnderDragonParts(): MutableCollection<EnderDragonPart> {
+ return mutableListOf()
+ }
+
override fun getTickManager(): TickManager {
return TickManager()
}
diff --git a/src/main/kotlin/keybindings/FirmamentKeyBindings.kt b/src/main/kotlin/keybindings/FirmamentKeyBindings.kt
index e2bed8d..59b131a 100644
--- a/src/main/kotlin/keybindings/FirmamentKeyBindings.kt
+++ b/src/main/kotlin/keybindings/FirmamentKeyBindings.kt
@@ -1,26 +1,25 @@
-
-
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
+import moe.nea.firmament.util.TestUtil
object FirmamentKeyBindings {
- fun registerKeyBinding(name: String, config: ManagedOption<SavedKeyBinding>) {
- val vanillaKeyBinding = KeyBindingHelper.registerKeyBinding(
- KeyBinding(
- name,
- InputUtil.Type.KEYSYM,
- -1,
- "firmament.key.category"
- )
- )
- keyBindings[vanillaKeyBinding] = config
- }
+ fun registerKeyBinding(name: String, config: ManagedOption<SavedKeyBinding>) {
+ val vanillaKeyBinding = KeyBinding(
+ name,
+ InputUtil.Type.KEYSYM,
+ -1,
+ "firmament.key.category"
+ )
+ if (!TestUtil.isInTest) {
+ KeyBindingHelper.registerKeyBinding(vanillaKeyBinding)
+ }
+ keyBindings[vanillaKeyBinding] = config
+ }
- val keyBindings = mutableMapOf<KeyBinding, ManagedOption<SavedKeyBinding>>()
+ val keyBindings = mutableMapOf<KeyBinding, ManagedOption<SavedKeyBinding>>()
}
diff --git a/src/main/kotlin/repo/BetterRepoRecipeCache.kt b/src/main/kotlin/repo/BetterRepoRecipeCache.kt
index 91a6b50..6d18223 100644
--- a/src/main/kotlin/repo/BetterRepoRecipeCache.kt
+++ b/src/main/kotlin/repo/BetterRepoRecipeCache.kt
@@ -1,28 +1,31 @@
-
package moe.nea.firmament.repo
import io.github.moulberry.repo.IReloadable
import io.github.moulberry.repo.NEURepository
+import io.github.moulberry.repo.data.NEUNpcShopRecipe
import io.github.moulberry.repo.data.NEURecipe
import moe.nea.firmament.util.SkyblockId
+import moe.nea.firmament.util.skyblockId
-class BetterRepoRecipeCache(val essenceRecipeProvider: EssenceRecipeProvider) : IReloadable {
- var usages: Map<SkyblockId, Set<NEURecipe>> = mapOf()
- var recipes: Map<SkyblockId, Set<NEURecipe>> = mapOf()
+class BetterRepoRecipeCache(vararg val extraProviders: ExtraRecipeProvider) : IReloadable {
+ var usages: Map<SkyblockId, Set<NEURecipe>> = mapOf()
+ var recipes: Map<SkyblockId, Set<NEURecipe>> = mapOf()
- override fun reload(repository: NEURepository) {
- val usages = mutableMapOf<SkyblockId, MutableSet<NEURecipe>>()
- val recipes = mutableMapOf<SkyblockId, MutableSet<NEURecipe>>()
- 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
- }
+ override fun reload(repository: NEURepository) {
+ val usages = mutableMapOf<SkyblockId, MutableSet<NEURecipe>>()
+ val recipes = mutableMapOf<SkyblockId, MutableSet<NEURecipe>>()
+ val baseRecipes = repository.items.items.values
+ .asSequence()
+ .flatMap { it.recipes }
+ (baseRecipes + extraProviders.flatMap { it.provideExtraRecipes() })
+ .forEach { recipe ->
+ if (recipe is NEUNpcShopRecipe) {
+ usages.getOrPut(recipe.isSoldBy.skyblockId, ::mutableSetOf).add(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
index 1833258..38559d5 100644
--- a/src/main/kotlin/repo/EssenceRecipeProvider.kt
+++ b/src/main/kotlin/repo/EssenceRecipeProvider.kt
@@ -1,4 +1,3 @@
-
package moe.nea.firmament.repo
import io.github.moulberry.repo.IReloadable
@@ -7,44 +6,46 @@ 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<NEUIngredient>,
- ) : NEURecipe {
- val essenceIngredient= NEUIngredient.fromString("${essenceType}:$essenceCost")
- val allUpgradeComponents = listOf(essenceIngredient) + extraItems
+class EssenceRecipeProvider : IReloadable, ExtraRecipeProvider {
+ data class EssenceUpgradeRecipe(
+ val itemId: SkyblockId,
+ val starCountAfter: Int,
+ val essenceCost: Int,
+ val essenceType: String, // TODO: replace with proper type
+ val extraItems: List<NEUIngredient>,
+ ) : NEURecipe {
+ val essenceIngredient = NEUIngredient.fromString("${essenceType}:$essenceCost")
+ val allUpgradeComponents = listOf(essenceIngredient) + extraItems
+
+ override fun getAllInputs(): Collection<NEUIngredient> {
+ return listOf(NEUIngredient.fromString(itemId.neuItem + ":1")) + allUpgradeComponents
+ }
- override fun getAllInputs(): Collection<NEUIngredient> {
- return listOf(NEUIngredient.fromString(itemId.neuItem + ":1")) + allUpgradeComponents
- }
+ override fun getAllOutputs(): Collection<NEUIngredient> {
+ return listOf(NEUIngredient.fromString(itemId.neuItem + ":1"))
+ }
+ }
- override fun getAllOutputs(): Collection<NEUIngredient> {
- return listOf(NEUIngredient.fromString(itemId.neuItem + ":1"))
- }
- }
+ var recipes = listOf<EssenceUpgradeRecipe>()
+ private set
- var recipes = listOf<EssenceUpgradeRecipe>()
- private set
+ override fun provideExtraRecipes(): Iterable<NEURecipe> = recipes
- override fun reload(repository: NEURepository) {
- val recipes = mutableListOf<EssenceUpgradeRecipe>()
- 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
- }
+ override fun reload(repository: NEURepository) {
+ val recipes = mutableListOf<EssenceUpgradeRecipe>()
+ 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/ExtraRecipeProvider.kt b/src/main/kotlin/repo/ExtraRecipeProvider.kt
new file mode 100644
index 0000000..9d3b5a0
--- /dev/null
+++ b/src/main/kotlin/repo/ExtraRecipeProvider.kt
@@ -0,0 +1,7 @@
+package moe.nea.firmament.repo
+
+import io.github.moulberry.repo.data.NEURecipe
+
+interface ExtraRecipeProvider {
+ fun provideExtraRecipes(): Iterable<NEURecipe>
+}
diff --git a/src/main/kotlin/repo/ItemCache.kt b/src/main/kotlin/repo/ItemCache.kt
index 014de7d..9fa0083 100644
--- a/src/main/kotlin/repo/ItemCache.kt
+++ b/src/main/kotlin/repo/ItemCache.kt
@@ -24,21 +24,28 @@ import net.minecraft.nbt.NbtCompound
import net.minecraft.nbt.NbtElement
import net.minecraft.nbt.NbtOps
import net.minecraft.nbt.NbtString
+import net.minecraft.text.MutableText
+import net.minecraft.text.Style
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.repo.RepoManager.initialize
+import moe.nea.firmament.util.LegacyFormattingCode
import moe.nea.firmament.util.LegacyTagParser
import moe.nea.firmament.util.MC
import moe.nea.firmament.util.SkyblockId
import moe.nea.firmament.util.TestUtil
+import moe.nea.firmament.util.directLiteralStringContent
import moe.nea.firmament.util.mc.FirmamentDataComponentTypes
import moe.nea.firmament.util.mc.appendLore
+import moe.nea.firmament.util.mc.displayNameAccordingToNbt
+import moe.nea.firmament.util.mc.loreAccordingToNbt
import moe.nea.firmament.util.mc.modifyLore
import moe.nea.firmament.util.mc.setCustomName
import moe.nea.firmament.util.mc.setSkullOwner
+import moe.nea.firmament.util.transformEachRecursively
object ItemCache : IReloadable {
private val cache: MutableMap<String, ItemStack> = ConcurrentHashMap()
@@ -70,6 +77,12 @@ object ItemCache : IReloadable {
val ItemStack.isBroken
get() = get(FirmamentDataComponentTypes.IS_BROKEN) ?: false
+
+ fun ItemStack.withFallback(fallback: ItemStack?): ItemStack {
+ if (isBroken && fallback != null) return fallback
+ return this
+ }
+
fun brokenItemStack(neuItem: NEUItem?, idHint: SkyblockId? = null): ItemStack {
return ItemStack(Items.PAINTING).apply {
setCustomName(Text.literal(neuItem?.displayName ?: idHint?.neuItem ?: "null"))
@@ -88,6 +101,35 @@ object ItemCache : IReloadable {
}
}
+ fun un189Lore(lore: String): MutableText {
+ val base = Text.literal("")
+ base.setStyle(Style.EMPTY.withItalic(false))
+ var lastColorCode = Style.EMPTY
+ var readOffset = 0
+ while (readOffset < lore.length) {
+ var nextCode = lore.indexOf('§', readOffset)
+ if (nextCode < 0) {
+ nextCode = lore.length
+ }
+ val text = lore.substring(readOffset, nextCode)
+ if (text.isNotEmpty()) {
+ base.append(Text.literal(text).setStyle(lastColorCode))
+ }
+ readOffset = nextCode + 2
+ if (nextCode + 1 < lore.length) {
+ val colorCode = lore[nextCode + 1]
+ val formatting = LegacyFormattingCode.byCode[colorCode.lowercaseChar()] ?: LegacyFormattingCode.RESET
+ val modernFormatting = formatting.modern
+ if (modernFormatting.isColor) {
+ lastColorCode = Style.EMPTY.withColor(modernFormatting)
+ } else {
+ lastColorCode = lastColorCode.withFormatting(modernFormatting)
+ }
+ }
+ }
+ return base
+ }
+
private fun NEUItem.asItemStackNow(): ItemStack {
try {
val oldItemTag = get10809CompoundTag()
@@ -95,6 +137,8 @@ object ItemCache : IReloadable {
?: return brokenItemStack(this)
val itemInstance =
ItemStack.fromNbt(MC.defaultRegistries, modernItemTag).getOrNull() ?: return brokenItemStack(this)
+ itemInstance.loreAccordingToNbt = lore.map { un189Lore(it) }
+ itemInstance.displayNameAccordingToNbt = un189Lore(displayName)
val extraAttributes = oldItemTag.getCompound("tag").getCompound("ExtraAttributes")
if (extraAttributes != null)
itemInstance.set(DataComponentTypes.CUSTOM_DATA, NbtComponent.of(extraAttributes))
@@ -129,12 +173,13 @@ object ItemCache : IReloadable {
}
fun Text.applyLoreReplacements(loreReplacements: Map<String, String>): Text {
- assert(this.siblings.isEmpty())
- var string = this.string
- loreReplacements.forEach { (find, replace) ->
- string = string.replace("{$find}", replace)
+ return this.transformEachRecursively {
+ var string = it.directLiteralStringContent ?: return@transformEachRecursively it
+ loreReplacements.forEach { (find, replace) ->
+ string = string.replace("{$find}", replace)
+ }
+ Text.literal(string).setStyle(it.style)
}
- return Text.literal(string).styled { this.style }
}
var job: Job? = null
diff --git a/src/main/kotlin/repo/Reforge.kt b/src/main/kotlin/repo/Reforge.kt
new file mode 100644
index 0000000..dc0d93d
--- /dev/null
+++ b/src/main/kotlin/repo/Reforge.kt
@@ -0,0 +1,160 @@
+package moe.nea.firmament.repo
+
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.builtins.MapSerializer
+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
+import kotlinx.serialization.json.JsonObject
+import kotlinx.serialization.json.JsonPrimitive
+import kotlinx.serialization.serializer
+import net.minecraft.item.Item
+import net.minecraft.registry.RegistryKey
+import net.minecraft.registry.RegistryKeys
+import net.minecraft.util.Identifier
+import moe.nea.firmament.util.ReforgeId
+import moe.nea.firmament.util.SkyblockId
+import moe.nea.firmament.util.skyblock.ItemType
+import moe.nea.firmament.util.skyblock.Rarity
+
+@Serializable
+data class Reforge(
+ val reforgeName: String,
+ @SerialName("internalName") val reforgeStone: SkyblockId? = null,
+ val nbtModifier: ReforgeId? = null,
+ val requiredRarities: List<Rarity>? = null,
+ val itemTypes: @Serializable(with = ReforgeEligibilityFilter.ItemTypesSerializer::class) List<ReforgeEligibilityFilter>? = null,
+ val allowOn: List<ReforgeEligibilityFilter>? = null,
+ val reforgeCosts: RarityMapped<Double>? = null,
+ val reforgeAbility: RarityMapped<String>? = null,
+ val reforgeStats: RarityMapped<Map<String, Double>>? = null,
+) {
+ val eligibleItems get() = allowOn ?: itemTypes ?: listOf()
+
+ val statUniverse: Set<String> = Rarity.entries.flatMapTo(mutableSetOf()) {
+ reforgeStats?.get(it)?.keys ?: emptySet()
+ }
+
+ @Serializable(with = ReforgeEligibilityFilter.Serializer::class)
+ sealed interface ReforgeEligibilityFilter {
+ object ItemTypesSerializer : KSerializer<List<ReforgeEligibilityFilter>> {
+ override val descriptor: SerialDescriptor
+ get() = JsonElement.serializer().descriptor
+
+ override fun deserialize(decoder: Decoder): List<ReforgeEligibilityFilter> {
+ decoder as JsonDecoder
+ val jsonElement = decoder.decodeJsonElement()
+ if (jsonElement is JsonPrimitive && jsonElement.isString) {
+ return jsonElement.content.split("/").map { AllowsItemType(ItemType.ofName(it)) }
+ }
+ if (jsonElement is JsonArray) {
+ return decoder.json.decodeFromJsonElement(serializer<List<ReforgeEligibilityFilter>>(), jsonElement)
+ }
+ jsonElement as JsonObject
+ val filters = mutableListOf<ReforgeEligibilityFilter>()
+ jsonElement["internalName"]?.let {
+ decoder.json.decodeFromJsonElement(serializer<List<SkyblockId>>(), it).forEach {
+ filters.add(AllowsInternalName(it))
+ }
+ }
+ jsonElement["itemId"]?.let {
+ decoder.json.decodeFromJsonElement(serializer<List<String>>(), it).forEach {
+ val ident = Identifier.tryParse(it)
+ if (ident != null)
+ filters.add(AllowsVanillaItemType(RegistryKey.of(RegistryKeys.ITEM, ident)))
+ }
+ }
+ return filters
+ }
+
+ override fun serialize(encoder: Encoder, value: List<ReforgeEligibilityFilter>) {
+ TODO("Not yet implemented")
+ }
+ }
+
+ object Serializer : KSerializer<ReforgeEligibilityFilter> {
+ override val descriptor: SerialDescriptor
+ get() = serializer<JsonElement>().descriptor
+
+ override fun deserialize(decoder: Decoder): ReforgeEligibilityFilter {
+ val jsonObject = serializer<JsonObject>().deserialize(decoder)
+ jsonObject["internalName"]?.let {
+ return AllowsInternalName(SkyblockId((it as JsonPrimitive).content))
+ }
+ jsonObject["itemType"]?.let {
+ return AllowsItemType(ItemType.ofName((it as JsonPrimitive).content))
+ }
+ jsonObject["minecraftId"]?.let {
+ return AllowsVanillaItemType(RegistryKey.of(RegistryKeys.ITEM,
+ Identifier.of((it as JsonPrimitive).content)))
+ }
+ error("Unknown item type")
+ }
+
+ override fun serialize(encoder: Encoder, value: ReforgeEligibilityFilter) {
+ TODO("Not yet implemented")
+ }
+
+ }
+
+ data class AllowsItemType(val itemType: ItemType) : ReforgeEligibilityFilter
+ data class AllowsInternalName(val internalName: SkyblockId) : ReforgeEligibilityFilter
+ data class AllowsVanillaItemType(val minecraftId: RegistryKey<Item>) : ReforgeEligibilityFilter
+ }
+
+
+ val reforgeId get() = nbtModifier ?: ReforgeId(reforgeName.lowercase())
+
+ @Serializable(with = RarityMapped.Serializer::class)
+ sealed interface RarityMapped<T> {
+ fun get(rarity: Rarity?): T?
+
+ class Serializer<T>(
+ val values: KSerializer<T>
+ ) : KSerializer<RarityMapped<T>> {
+ override val descriptor: SerialDescriptor
+ get() = JsonElement.serializer().descriptor
+
+ val indirect = MapSerializer(Rarity.serializer(), values)
+ override fun deserialize(decoder: Decoder): RarityMapped<T> {
+ decoder as JsonDecoder
+ val element = decoder.decodeJsonElement()
+ if (element is JsonObject) {
+ return PerRarity(decoder.json.decodeFromJsonElement(indirect, element))
+ } else {
+ return Direct(decoder.json.decodeFromJsonElement(values, element))
+ }
+ }
+
+ override fun serialize(encoder: Encoder, value: RarityMapped<T>) {
+ when (value) {
+ is Direct<T> ->
+ values.serialize(encoder, value.value)
+
+ is PerRarity<T> ->
+ indirect.serialize(encoder, value.values)
+ }
+ }
+ }
+
+ @Serializable
+ data class Direct<T>(val value: T) : RarityMapped<T> {
+ override fun get(rarity: Rarity?): T {
+ return value
+ }
+ }
+
+ @Serializable
+ data class PerRarity<T>(val values: Map<Rarity, T>) : RarityMapped<T> {
+ override fun get(rarity: Rarity?): T? {
+ return values[rarity]
+ }
+ }
+ }
+
+}
diff --git a/src/main/kotlin/repo/ReforgeStore.kt b/src/main/kotlin/repo/ReforgeStore.kt
new file mode 100644
index 0000000..4c01974
--- /dev/null
+++ b/src/main/kotlin/repo/ReforgeStore.kt
@@ -0,0 +1,125 @@
+package moe.nea.firmament.repo
+
+import com.google.gson.JsonElement
+import com.mojang.serialization.JsonOps
+import io.github.moulberry.repo.IReloadable
+import io.github.moulberry.repo.NEURepoFile
+import io.github.moulberry.repo.NEURepository
+import io.github.moulberry.repo.NEURepositoryException
+import io.github.moulberry.repo.data.NEURecipe
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.serializer
+import net.minecraft.item.Item
+import net.minecraft.registry.RegistryKey
+import moe.nea.firmament.Firmament
+import moe.nea.firmament.util.ReforgeId
+import moe.nea.firmament.util.SkyblockId
+import moe.nea.firmament.util.json.KJsonOps
+import moe.nea.firmament.util.skyblock.ItemType
+
+object ReforgeStore : ExtraRecipeProvider, IReloadable {
+ override fun provideExtraRecipes(): Iterable<NEURecipe> {
+ return emptyList()
+ }
+
+ var byType: Map<ItemType, List<Reforge>> = mapOf()
+ var byVanilla: Map<RegistryKey<Item>, List<Reforge>> = mapOf()
+ var byInternalName: Map<SkyblockId, List<Reforge>> = mapOf()
+ var modifierLut = mapOf<ReforgeId, Reforge>()
+ var byReforgeStone = mapOf<SkyblockId, Reforge>()
+ var allReforges = listOf<Reforge>()
+
+ fun findEligibleForItem(itemType: ItemType): List<Reforge> {
+ return byType[itemType] ?: listOf()
+ }
+
+ fun findEligibleForInternalName(internalName: SkyblockId): List<Reforge> {
+ return byInternalName[internalName] ?: listOf()
+ }
+
+ //TODO: return byVanillla
+ override fun reload(repo: NEURepository) {
+ val basicReforges =
+ repo.file("constants/reforges.json")
+ ?.kJson(serializer<Map<String, Reforge>>())
+ ?.values ?: emptyList()
+ val advancedReforges =
+ repo.file("constants/reforgestones.json")
+ ?.kJson(serializer<Map<String, Reforge>>())
+ ?.values ?: emptyList()
+ val allReforges = (basicReforges + advancedReforges)
+ modifierLut = allReforges.associateBy { it.reforgeId }
+ byReforgeStone = allReforges.filter { it.reforgeStone != null }
+ .associateBy { it.reforgeStone!! }
+ val byType = mutableMapOf<ItemType, MutableList<Reforge>>()
+ val byVanilla = mutableMapOf<RegistryKey<Item>, MutableList<Reforge>>()
+ val byInternalName = mutableMapOf<SkyblockId, MutableList<Reforge>>()
+ this.byType = byType
+ this.byVanilla = byVanilla
+ this.byInternalName = byInternalName
+ for (reforge in allReforges) {
+ for (eligibleItem in reforge.eligibleItems) {
+ when (eligibleItem) {
+ is Reforge.ReforgeEligibilityFilter.AllowsInternalName -> {
+ byInternalName.getOrPut(eligibleItem.internalName, ::mutableListOf).add(reforge)
+ }
+
+ is Reforge.ReforgeEligibilityFilter.AllowsItemType -> {
+ val actualItemTypes = resolveItemType(eligibleItem.itemType)
+ for (itemType in actualItemTypes) {
+ byType.getOrPut(itemType, ::mutableListOf).add(reforge)
+ byType.getOrPut(itemType.dungeonVariant, ::mutableListOf).add(reforge)
+ }
+ }
+
+ is Reforge.ReforgeEligibilityFilter.AllowsVanillaItemType -> {
+ byVanilla.getOrPut(eligibleItem.minecraftId, ::mutableListOf).add(reforge)
+ }
+ }
+ }
+ }
+ this.allReforges = allReforges
+ }
+
+ fun resolveItemType(itemType: ItemType): List<ItemType> {
+ if (ItemType.SWORD == itemType) {
+ return listOf(
+ ItemType.SWORD,
+ ItemType.GAUNTLET,
+ ItemType.LONGSWORD,// TODO: check name
+ ItemType.FISHING_WEAPON,// TODO: check name
+ )
+ }
+ if (itemType == ItemType.ofName("ARMOR")) {
+ return listOf(
+ ItemType.CHESTPLATE,
+ ItemType.LEGGINGS,
+ ItemType.HELMET,
+ ItemType.BOOTS,
+ )
+ }
+ if (itemType == ItemType.EQUIPMENT) {
+ return listOf(
+ ItemType.CLOAK,
+ ItemType.BRACELET,
+ ItemType.NECKLACE,
+ ItemType.BELT,
+ ItemType.GLOVES,
+ )
+ }
+ if (itemType == ItemType.ROD) {
+ return listOf(ItemType.FISHING_ROD, ItemType.FISHING_WEAPON)
+ }
+ return listOf(itemType)
+ }
+
+ fun <T> NEURepoFile.kJson(serializer: KSerializer<T>): T {
+ val rawJson = json(JsonElement::class.java)
+ try {
+ val kJsonElement = JsonOps.INSTANCE.convertTo(KJsonOps.INSTANCE, rawJson)
+ return Firmament.json.decodeFromJsonElement(serializer, kJsonElement)
+ } catch (ex: Exception) {
+ throw NEURepositoryException(path, "Could not decode kotlin JSON element", ex)
+ }
+ }
+}
diff --git a/src/main/kotlin/repo/RepoItemTypeCache.kt b/src/main/kotlin/repo/RepoItemTypeCache.kt
new file mode 100644
index 0000000..414ec09
--- /dev/null
+++ b/src/main/kotlin/repo/RepoItemTypeCache.kt
@@ -0,0 +1,15 @@
+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 moe.nea.firmament.util.skyblock.ItemType
+
+object RepoItemTypeCache : IReloadable {
+
+ var byItemType: Map<ItemType?, List<NEUItem>> = mapOf()
+
+ override fun reload(repository: NEURepository) {
+ byItemType = repository.items.items.values.groupBy { ItemType.fromEscapeCodeLore(it.lore.lastOrNull() ?: "") }
+ }
+}
diff --git a/src/main/kotlin/repo/RepoManager.kt b/src/main/kotlin/repo/RepoManager.kt
index 667ab73..6d9ba14 100644
--- a/src/main/kotlin/repo/RepoManager.kt
+++ b/src/main/kotlin/repo/RepoManager.kt
@@ -53,13 +53,16 @@ object RepoManager {
var recentlyFailedToUpdateItemList = false
val essenceRecipeProvider = EssenceRecipeProvider()
- val recipeCache = BetterRepoRecipeCache(essenceRecipeProvider)
+ val recipeCache = BetterRepoRecipeCache(essenceRecipeProvider, ReforgeStore)
fun makeNEURepository(path: Path): NEURepository {
return NEURepository.of(path).apply {
registerReloadListener(ItemCache)
+ registerReloadListener(RepoItemTypeCache)
registerReloadListener(ExpLadders)
registerReloadListener(ItemNameLookup)
+ registerReloadListener(ReforgeStore)
+ registerReloadListener(essenceRecipeProvider)
ReloadRegistrationEvent.publish(ReloadRegistrationEvent(this))
registerReloadListener {
if (TestUtil.isInTest) return@registerReloadListener
@@ -70,7 +73,6 @@ object RepoManager {
}
}
}
- registerReloadListener(essenceRecipeProvider)
registerReloadListener(recipeCache)
}
}
diff --git a/src/main/kotlin/repo/RepoModResourcePack.kt b/src/main/kotlin/repo/RepoModResourcePack.kt
index f92fe4f..617efec 100644
--- a/src/main/kotlin/repo/RepoModResourcePack.kt
+++ b/src/main/kotlin/repo/RepoModResourcePack.kt
@@ -1,4 +1,3 @@
-
package moe.nea.firmament.repo
import java.io.InputStream
@@ -21,86 +20,86 @@ 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.resource.metadata.ResourceMetadataSerializer
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<in ModResourcePack>) {
- Firmament.logger.info("Registering mod resource pack")
- packs.add(RepoModResourcePack(RepoDownloadManager.repoSavedLocation))
- }
+ companion object {
+ fun append(packs: MutableList<in ModResourcePack>) {
+ Firmament.logger.info("Registering mod resource pack")
+ packs.add(RepoModResourcePack(RepoDownloadManager.repoSavedLocation))
+ }
- fun createResourceDirectly(identifier: Identifier): Optional<Resource> {
- 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)
- }
- )
- }
- }
+ fun createResourceDirectly(identifier: Identifier): Optional<Resource> {
+ 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 close() {
+ }
- override fun openRoot(vararg segments: String): InputSupplier<InputStream>? {
- return getFile(segments)?.let { InputSupplier.create(it) }
- }
+ override fun openRoot(vararg segments: String): InputSupplier<InputStream>? {
+ return getFile(segments)?.let { InputSupplier.create(it) }
+ }
- fun getFile(segments: Array<out String>): Path? {
- PathUtil.validatePath(*segments)
- val path = segments.fold(basePath, Path::resolve)
- if (!path.isRegularFile()) return null
- return path
- }
+ fun getFile(segments: Array<out String>): 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<InputStream>? {
- 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 open(type: ResourceType?, id: Identifier): InputSupplier<InputStream>? {
+ 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
+ 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))
- }
- }
+ 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<String> {
- if (type != ResourceType.CLIENT_RESOURCES) return emptySet()
- return setOf("neurepo")
- }
+ override fun getNamespaces(type: ResourceType?): Set<String> {
+ if (type != ResourceType.CLIENT_RESOURCES) return emptySet()
+ return setOf("neurepo")
+ }
- override fun <T> parseMetadata(metaReader: ResourceMetadataReader<T>): T? {
- return AbstractFileResourcePack.parseMetadata(
- metaReader, """
+ override fun <T : Any?> parseMetadata(metadataSerializer: ResourceMetadataSerializer<T>?): T? {
+ return AbstractFileResourcePack.parseMetadata(
+ metadataSerializer, """
{
"pack": {
"pack_format": 12,
@@ -108,19 +107,20 @@ class RepoModResourcePack(val basePath: Path) : ModResourcePack {
}
}
""".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 getInfo(): ResourcePackInfo {
+ return ResourcePackInfo("neurepo", Text.literal("NEU Repo"), ResourcePackSource.BUILTIN, Optional.empty())
+ }
- override fun createOverlay(overlay: String): ModResourcePack {
- return RepoModResourcePack(basePath.resolve(overlay))
- }
+ 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/repo/SBItemStack.kt b/src/main/kotlin/repo/SBItemStack.kt
index 18126ee..da34707 100644
--- a/src/main/kotlin/repo/SBItemStack.kt
+++ b/src/main/kotlin/repo/SBItemStack.kt
@@ -9,17 +9,37 @@ import net.minecraft.item.ItemStack
import net.minecraft.network.RegistryByteBuf
import net.minecraft.network.codec.PacketCodec
import net.minecraft.network.codec.PacketCodecs
+import net.minecraft.text.Style
import net.minecraft.text.Text
+import net.minecraft.text.TextColor
import net.minecraft.util.Formatting
import moe.nea.firmament.repo.ItemCache.asItemStack
+import moe.nea.firmament.repo.ItemCache.withFallback
import moe.nea.firmament.util.FirmFormatters
import moe.nea.firmament.util.LegacyFormattingCode
+import moe.nea.firmament.util.MC
+import moe.nea.firmament.util.ReforgeId
import moe.nea.firmament.util.SkyblockId
+import moe.nea.firmament.util.blue
+import moe.nea.firmament.util.directLiteralStringContent
+import moe.nea.firmament.util.extraAttributes
+import moe.nea.firmament.util.getReforgeId
+import moe.nea.firmament.util.getUpgradeStars
+import moe.nea.firmament.util.grey
import moe.nea.firmament.util.mc.appendLore
import moe.nea.firmament.util.mc.displayNameAccordingToNbt
+import moe.nea.firmament.util.mc.loreAccordingToNbt
+import moe.nea.firmament.util.mc.modifyLore
+import moe.nea.firmament.util.modifyExtraAttributes
import moe.nea.firmament.util.petData
+import moe.nea.firmament.util.prepend
+import moe.nea.firmament.util.reconstitute
import moe.nea.firmament.util.skyBlockId
+import moe.nea.firmament.util.skyblock.ItemType
+import moe.nea.firmament.util.skyblock.Rarity
import moe.nea.firmament.util.skyblockId
+import moe.nea.firmament.util.unformattedString
+import moe.nea.firmament.util.useMatch
import moe.nea.firmament.util.withColor
data class SBItemStack constructor(
@@ -28,8 +48,9 @@ data class SBItemStack constructor(
private var stackSize: Int,
private var petData: PetData?,
val extraLore: List<Text> = emptyList(),
- // TODO: grab this star data from nbt if possible
val stars: Int = 0,
+ val fallback: ItemStack? = null,
+ val reforge: ReforgeId? = null,
) {
fun getStackSize() = stackSize
@@ -66,7 +87,9 @@ data class SBItemStack constructor(
skyblockId,
RepoManager.getNEUItem(skyblockId),
itemStack.count,
- petData = itemStack.petData?.let { PetData.fromHypixel(it) }
+ petData = itemStack.petData?.let { PetData.fromHypixel(it) },
+ stars = itemStack.getUpgradeStars(),
+ reforge = itemStack.getReforgeId()
)
}
@@ -77,6 +100,168 @@ data class SBItemStack constructor(
}
return SBItemStack(neuIngredient.skyblockId, neuIngredient.amount.toInt())
}
+
+ fun passthrough(itemStack: ItemStack): SBItemStack {
+ return SBItemStack(SkyblockId.NULL, null, itemStack.count, null, fallback = itemStack)
+ }
+
+ fun parseStatBlock(itemStack: ItemStack): List<StatLine> {
+ return itemStack.loreAccordingToNbt
+ .map { parseStatLine(it) }
+ .takeWhile { it != null }
+ .filterNotNull()
+ }
+
+ fun appendEnhancedStats(
+ itemStack: ItemStack,
+ reforgeStats: Map<String, Double>,
+ buffKind: BuffKind,
+ ) {
+ val namedReforgeStats = reforgeStats
+ .mapKeysTo(mutableMapOf()) { statIdToName(it.key) }
+ val loreMut = itemStack.loreAccordingToNbt.toMutableList()
+ var statBlockLastIndex = -1
+ for (i in loreMut.indices) {
+ val statLine = parseStatLine(loreMut[i])
+ if (statLine == null && statBlockLastIndex >= 0) {
+ break
+ }
+ if (statLine == null) {
+ continue
+ }
+ statBlockLastIndex = i
+ val statBuff = namedReforgeStats.remove(statLine.statName) ?: continue
+ loreMut[i] = statLine.addStat(statBuff, buffKind).reconstitute()
+ }
+ if (namedReforgeStats.isNotEmpty() && statBlockLastIndex == -1) {
+ loreMut.add(0, Text.literal(""))
+ }
+ // If there is no stat block the statBlockLastIndex falls through to -1
+ // TODO: this is good enough for some items. some other items might have their stats at a different place.
+ for ((statName, statBuff) in namedReforgeStats) {
+ val statLine = StatLine(statName, null).addStat(statBuff, buffKind)
+ loreMut.add(statBlockLastIndex + 1, statLine.reconstitute())
+ }
+ itemStack.loreAccordingToNbt = loreMut
+ }
+
+ data class StatFormatting(
+ val postFix: String,
+ val color: Formatting,
+ val isStarAffected: Boolean = true,
+ )
+
+ val formattingOverrides = mapOf(
+ "Sea Creature Chance" to StatFormatting("%", Formatting.RED),
+ "Strength" to StatFormatting("", Formatting.RED),
+ "Damage" to StatFormatting("", Formatting.RED),
+ "Bonus Attack Speed" to StatFormatting("%", Formatting.RED),
+ "Shot Cooldown" to StatFormatting("s", Formatting.GREEN, false),
+ "Ability Damage" to StatFormatting("%", Formatting.RED),
+ "Crit Damage" to StatFormatting("%", Formatting.RED),
+ "Crit Chance" to StatFormatting("%", Formatting.RED),
+ "Ability Damage" to StatFormatting("%", Formatting.RED),
+ "Trophy Fish Chance" to StatFormatting("%", Formatting.GREEN),
+ "Health" to StatFormatting("", Formatting.GREEN),
+ "Defense" to StatFormatting("", Formatting.GREEN),
+ "Fishing Speed" to StatFormatting("", Formatting.GREEN),
+ "Double Hook Chance" to StatFormatting("%", Formatting.GREEN),
+ "Mining Speed" to StatFormatting("", Formatting.GREEN),
+ "Mining Fortune" to StatFormatting("", Formatting.GREEN),
+ "Heat Resistance" to StatFormatting("", Formatting.GREEN),
+ "Swing Range" to StatFormatting("", Formatting.GREEN),
+ "Rift Time" to StatFormatting("", Formatting.GREEN),
+ "Speed" to StatFormatting("", Formatting.GREEN),
+ "Farming Fortune" to StatFormatting("", Formatting.GREEN),
+ "True Defense" to StatFormatting("", Formatting.GREEN),
+ "Mending" to StatFormatting("", Formatting.GREEN),
+ "Foraging Wisdom" to StatFormatting("", Formatting.GREEN),
+ "Farming Wisdom" to StatFormatting("", Formatting.GREEN),
+ "Foraging Fortune" to StatFormatting("", Formatting.GREEN),
+ "Magic Find" to StatFormatting("", Formatting.GREEN),
+ "Ferocity" to StatFormatting("", Formatting.GREEN),
+ "Bonus Pest Chance" to StatFormatting("%", Formatting.GREEN),
+ "Cold Resistance" to StatFormatting("", Formatting.GREEN),
+ "Pet Luck" to StatFormatting("", Formatting.GREEN),
+ "Fear" to StatFormatting("", Formatting.GREEN),
+ "Mana Regen" to StatFormatting("%", Formatting.GREEN),
+ "Rift Damage" to StatFormatting("", Formatting.GREEN),
+ "Hearts" to StatFormatting("", Formatting.GREEN),
+ "Vitality" to StatFormatting("", Formatting.GREEN),
+ // TODO: make this a repo json
+ )
+
+
+ private val statLabelRegex = "(?<statName>.*): ".toPattern()
+
+ enum class BuffKind(
+ val color: Formatting,
+ val prefix: String,
+ val postFix: String,
+ val isHidden: Boolean,
+ ) {
+ REFORGE(Formatting.BLUE, "(", ")", false),
+ STAR_BUFF(Formatting.RESET, "", "", true),
+ CATA_STAR_BUFF(Formatting.DARK_GRAY, "(", ")", false),
+ ;
+ }
+
+ data class StatLine(
+ val statName: String,
+ val value: Text?,
+ val rest: List<Text> = listOf(),
+ val valueNum: Double? = value?.directLiteralStringContent?.trim(' ', 's', '%', '+')?.toDoubleOrNull()
+ ) {
+ fun addStat(amount: Double, buffKind: BuffKind): StatLine {
+ val formattedAmount = FirmFormatters.formatCommas(amount, 1, includeSign = true)
+ return copy(
+ valueNum = (valueNum ?: 0.0) + amount,
+ value = null,
+ rest = rest +
+ if (buffKind.isHidden) emptyList()
+ else listOf(
+ Text.literal(
+ buffKind.prefix + formattedAmount +
+ statFormatting.postFix +
+ buffKind.postFix + " ")
+ .withColor(buffKind.color)))
+ }
+
+ fun formatValue() =
+ Text.literal(FirmFormatters.formatCommas(valueNum ?: 0.0,
+ 1,
+ includeSign = true) + statFormatting.postFix + " ")
+ .setStyle(Style.EMPTY.withColor(statFormatting.color))
+
+ val statFormatting = formattingOverrides[statName] ?: StatFormatting("", Formatting.GREEN)
+ private fun abbreviate(abbreviateTo: Int): String {
+ if (abbreviateTo >= statName.length) return statName
+ val segments = statName.split(" ")
+ return segments.joinToString(" ") {
+ it.substring(0, maxOf(1, abbreviateTo / segments.size))
+ }
+ }
+
+ fun reconstitute(abbreviateTo: Int = Int.MAX_VALUE): Text =
+ Text.literal("").setStyle(Style.EMPTY.withItalic(false))
+ .append(Text.literal("${abbreviate(abbreviateTo)}: ").grey())
+ .append(value ?: formatValue())
+ .also { rest.forEach(it::append) }
+ }
+
+ fun statIdToName(statId: String): String {
+ val segments = statId.split("_")
+ return segments.joinToString(" ") { it.replaceFirstChar { it.uppercaseChar() } }
+ }
+
+ private fun parseStatLine(line: Text): StatLine? {
+ val sibs = line.siblings
+ val stat = sibs.firstOrNull() ?: return null
+ if (stat.style.color != TextColor.fromFormatting(Formatting.GRAY)) return null
+ val statLabel = stat.directLiteralStringContent ?: return null
+ val statName = statLabelRegex.useMatch(statLabel) { group("statName") } ?: return null
+ return StatLine(statName, sibs[1], sibs.subList(2, sibs.size))
+ }
}
constructor(skyblockId: SkyblockId, petData: PetData) : this(
@@ -127,6 +312,41 @@ data class SBItemStack constructor(
}
+ private fun appendReforgeInfo(
+ itemStack: ItemStack,
+ ) {
+ val rarity = Rarity.fromItem(itemStack) ?: return
+ val reforgeId = this.reforge ?: return
+ val reforge = ReforgeStore.modifierLut[reforgeId] ?: return
+ val reforgeStats = reforge.reforgeStats?.get(rarity) ?: mapOf()
+ itemStack.displayNameAccordingToNbt = itemStack.displayNameAccordingToNbt.copy()
+ .prepend(Text.literal(reforge.reforgeName + " ").formatted(Rarity.colourMap[rarity] ?: Formatting.WHITE))
+ val data = itemStack.extraAttributes.copy()
+ data.putString("modifier", reforgeId.id)
+ itemStack.extraAttributes = data
+ appendEnhancedStats(itemStack, reforgeStats, BuffKind.REFORGE)
+ reforge.reforgeAbility?.get(rarity)?.let { reforgeAbility ->
+ val formattedReforgeAbility = ItemCache.un189Lore(reforgeAbility)
+ .grey()
+ itemStack.modifyLore {
+ val lastBlank = it.indexOfLast { it.unformattedString.isBlank() }
+ val newList = mutableListOf<Text>()
+ newList.addAll(it.subList(0, lastBlank))
+ newList.add(Text.literal(""))
+ newList.add(Text.literal("${reforge.reforgeName} Bonus").blue())
+ MC.font.textHandler.wrapLines(formattedReforgeAbility, 180, Style.EMPTY).mapTo(newList) {
+ it.reconstitute()
+ }
+ newList.addAll(it.subList(lastBlank, it.size))
+ return@modifyLore newList
+ }
+ }
+ }
+
+ // TODO: avoid instantiating the item stack here
+ val itemType: ItemType? get() = ItemType.fromItemStack(asImmutableItemStack())
+ val rarity: Rarity? get() = Rarity.fromItem(asImmutableItemStack())
+
private var itemStack_: ItemStack? = null
private val itemStack: ItemStack
@@ -138,10 +358,14 @@ data class SBItemStack constructor(
return@run ItemStack.EMPTY
val replacementData = mutableMapOf<String, String>()
injectReplacementDataForPets(replacementData)
- return@run neuItem.asItemStack(idHint = skyblockId, replacementData)
+ val baseItem = neuItem.asItemStack(idHint = skyblockId, replacementData)
+ .withFallback(fallback)
.copyWithCount(stackSize)
- .also { it.appendLore(extraLore) }
- .also { enhanceStatsByStars(it, stars) }
+ val baseStats = parseStatBlock(baseItem)
+ appendReforgeInfo(baseItem)
+ baseItem.appendLore(extraLore)
+ enhanceStatsByStars(baseItem, stars, baseStats)
+ return@run baseItem
}
if (itemStack_ == null)
itemStack_ = itemStack
@@ -151,6 +375,7 @@ data class SBItemStack constructor(
private fun starString(stars: Int): Text {
if (stars <= 0) return Text.empty()
+ // TODO: idk master stars
val tiers = listOf(
LegacyFormattingCode.GOLD,
LegacyFormattingCode.LIGHT_PURPLE,
@@ -170,11 +395,23 @@ data class SBItemStack constructor(
return starString
}
- private fun enhanceStatsByStars(itemStack: ItemStack, stars: Int) {
+ private fun enhanceStatsByStars(itemStack: ItemStack, stars: Int, baseStats: List<StatLine>) {
if (stars == 0) return
// TODO: increase stats and add the star level into the nbt data so star displays work
+ itemStack.modifyExtraAttributes {
+ it.putInt("upgrade_level", stars)
+ }
itemStack.displayNameAccordingToNbt = itemStack.displayNameAccordingToNbt.copy()
.append(starString(stars))
+ val isDungeon = ItemType.fromItemStack(itemStack)?.isDungeon ?: true
+ val truncatedStarCount = if (isDungeon) minOf(5, stars) else stars
+ appendEnhancedStats(itemStack,
+ baseStats
+ .filter { it.statFormatting.isStarAffected }
+ .associate {
+ it.statName to ((it.valueNum ?: 0.0) * (truncatedStarCount * 0.02))
+ },
+ BuffKind.STAR_BUFF)
}
fun asImmutableItemStack(): ItemStack {
diff --git a/src/main/kotlin/util/AprilFoolsUtil.kt b/src/main/kotlin/util/AprilFoolsUtil.kt
new file mode 100644
index 0000000..a940fa1
--- /dev/null
+++ b/src/main/kotlin/util/AprilFoolsUtil.kt
@@ -0,0 +1,10 @@
+package moe.nea.firmament.util
+
+import java.time.LocalDateTime
+import java.time.Month
+
+object AprilFoolsUtil {
+ val isAprilFoolsDay = LocalDateTime.now().let {
+ it.dayOfMonth == 1 && it.month == Month.APRIL
+ }
+}
diff --git a/src/main/kotlin/util/FirmFormatters.kt b/src/main/kotlin/util/FirmFormatters.kt
index 92fb9e5..acb7102 100644
--- a/src/main/kotlin/util/FirmFormatters.kt
+++ b/src/main/kotlin/util/FirmFormatters.kt
@@ -9,27 +9,59 @@ import kotlin.io.path.isReadable
import kotlin.io.path.isRegularFile
import kotlin.io.path.listDirectoryEntries
import kotlin.math.absoluteValue
+import kotlin.math.roundToInt
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
import net.minecraft.text.Text
object FirmFormatters {
+
+ private inline fun shortIf(
+ value: Double, breakpoint: Double, char: String,
+ return_: (String) -> Nothing
+ ) {
+ if (value >= breakpoint) {
+ val broken = (value / breakpoint * 10).roundToInt()
+ if (broken > 99)
+ return_((broken / 10).toString() + char)
+ val decimals = broken.toString()
+ decimals.singleOrNull()?.let {
+ return_("0.$it$char")
+ }
+ return_("${decimals[0]}.${decimals[1]}$char")
+ }
+ }
+
+ fun shortFormat(double: Double): String {
+ if (double < 0) return "-" + shortFormat(-double)
+ shortIf(double, 1_000_000_000_000.0, "t") { return it }
+ shortIf(double, 1_000_000_000.0, "b") { return it }
+ shortIf(double, 1_000_000.0, "m") { return it }
+ shortIf(double, 1_000.0, "k") { return it }
+ shortIf(double, 1.0, "") { return it }
+ return double.toString()
+ }
+
fun formatCommas(int: Int, segments: Int = 3): String = formatCommas(int.toLong(), segments)
- fun formatCommas(long: Long, segments: Int = 3): String {
+ fun formatCommas(long: Long, segments: Int = 3, includeSign: Boolean = false): String {
+ if (long < 0 && long != Long.MIN_VALUE) {
+ return "-" + formatCommas(-long, segments, false)
+ }
+ val prefix = if (includeSign) "+" else ""
val α = long / 1000
if (α != 0L) {
- return formatCommas(α, segments) + "," + (long - α * 1000).toString().padStart(3, '0')
+ return prefix + formatCommas(α, segments) + "," + (long - α * 1000).toString().padStart(3, '0')
}
- return long.toString()
+ return prefix + long.toString()
}
fun formatCommas(float: Float, fractionalDigits: Int): String = formatCommas(float.toDouble(), fractionalDigits)
- fun formatCommas(double: Double, fractionalDigits: Int): String {
+ fun formatCommas(double: Double, fractionalDigits: Int, includeSign: Boolean = false): 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")
+ return formatCommas(long, includeSign = includeSign) + (if (digits.isEmpty()) "" else ".$digits")
}
fun formatDistance(distance: Double): String {
diff --git a/src/main/kotlin/util/JvmUtil.kt b/src/main/kotlin/util/JvmUtil.kt
new file mode 100644
index 0000000..5be5ebd
--- /dev/null
+++ b/src/main/kotlin/util/JvmUtil.kt
@@ -0,0 +1,32 @@
+package moe.nea.firmament.util
+
+import com.sun.tools.attach.VirtualMachine
+import java.lang.management.ManagementFactory
+import java.nio.file.Path
+import kotlin.io.path.absolutePathString
+
+object JvmUtil {
+ fun guessJVMPid(): String {
+ val name = ManagementFactory.getRuntimeMXBean().name
+ val pid = name.substringBefore('@')
+ ErrorUtil.softCheck("Not a valid PID: $pid", pid.toIntOrNull() != null)
+ return pid
+ }
+
+ fun getVM(): VirtualMachine {
+ return VirtualMachine.attach(guessJVMPid())
+ }
+
+ fun useVM(block: (VirtualMachine) -> Unit) {
+ val vm = getVM()
+ block(vm)
+ vm.detach()
+ }
+
+ fun loadAgent(jarPath: Path, options: String? = null) {
+ useVM {
+ it.loadAgent(jarPath.absolutePathString(), options)
+ }
+ }
+
+}
diff --git a/src/main/kotlin/util/LegacyFormattingCode.kt b/src/main/kotlin/util/LegacyFormattingCode.kt
index 44bacfc..1a5d1dd 100644
--- a/src/main/kotlin/util/LegacyFormattingCode.kt
+++ b/src/main/kotlin/util/LegacyFormattingCode.kt
@@ -1,35 +1,37 @@
-
-
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);
+ 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);
+
+ companion object {
+ val byCode = entries.associateBy { it.char }
+ }
- val modern = Formatting.byCode(char)!!
+ val modern = Formatting.byCode(char)!!
- val formattingCode = "§$char"
+ val formattingCode = "§$char"
}
diff --git a/src/main/kotlin/util/MC.kt b/src/main/kotlin/util/MC.kt
index f7c81da..ca3742d 100644
--- a/src/main/kotlin/util/MC.kt
+++ b/src/main/kotlin/util/MC.kt
@@ -7,11 +7,13 @@ import net.minecraft.client.gui.hud.InGameHud
import net.minecraft.client.gui.screen.Screen
import net.minecraft.client.gui.screen.ingame.HandledScreen
import net.minecraft.client.network.ClientPlayerEntity
+import net.minecraft.client.render.GameRenderer
import net.minecraft.client.render.WorldRenderer
import net.minecraft.client.render.item.ItemRenderer
import net.minecraft.client.world.ClientWorld
import net.minecraft.entity.Entity
import net.minecraft.item.Item
+import net.minecraft.item.ItemStack
import net.minecraft.network.packet.c2s.play.CommandExecutionC2SPacket
import net.minecraft.registry.BuiltinRegistries
import net.minecraft.registry.RegistryKeys
@@ -64,6 +66,8 @@ object MC {
}
fun sendCommand(command: String) {
+ // TODO: add a queue to this and sendServerChat
+ ErrorUtil.softCheck("Server commands have an implied /", !command.startsWith("/"))
player?.networkHandler?.sendCommand(command)
}
@@ -83,6 +87,7 @@ object MC {
inline val resourceManager get() = (instance.resourceManager as ReloadableResourceManagerImpl)
inline val itemRenderer: ItemRenderer get() = instance.itemRenderer
inline val worldRenderer: WorldRenderer get() = instance.worldRenderer
+ inline val gameRenderer: GameRenderer get() = instance.gameRenderer
inline val networkHandler get() = player?.networkHandler
inline val instance get() = MinecraftClient.getInstance()
inline val keyboard get() = instance.keyboard
@@ -92,12 +97,14 @@ object MC {
inline val inGameHud: InGameHud get() = instance.inGameHud
inline val font get() = instance.textRenderer
inline val soundManager get() = instance.soundManager
- inline val player: ClientPlayerEntity? get() = instance.player
+ inline val player: ClientPlayerEntity? get() = TestUtil.unlessTesting { instance.player }
inline val camera: Entity? get() = instance.cameraEntity
+ inline val stackInHand: ItemStack? get() = player?.inventory?.mainHandStack
inline val guiAtlasManager get() = instance.guiAtlasManager
- inline val world: ClientWorld? get() = instance.world
+ inline val world: ClientWorld? get() = TestUtil.unlessTesting { instance.world }
+ inline val playerName: String? get() = player?.name?.unformattedString
inline var screen: Screen?
- get() = instance.currentScreen
+ get() = TestUtil.unlessTesting { instance.currentScreen }
set(value) = instance.setScreen(value)
val screenName get() = screen?.title?.unformattedString?.trim()
inline val handledScreen: HandledScreen<*>? get() = instance.currentScreen as? HandledScreen<*>
@@ -105,7 +112,7 @@ object MC {
inline val currentRegistries: RegistryWrapper.WrapperLookup? get() = world?.registryManager
val defaultRegistries: RegistryWrapper.WrapperLookup by lazy { BuiltinRegistries.createWrapperLookup() }
inline val currentOrDefaultRegistries get() = currentRegistries ?: defaultRegistries
- val defaultItems: RegistryWrapper.Impl<Item> = defaultRegistries.getOrThrow(RegistryKeys.ITEM)
+ val defaultItems: RegistryWrapper.Impl<Item> by lazy { defaultRegistries.getOrThrow(RegistryKeys.ITEM) }
var lastWorld: World? = null
get() {
field = world ?: field
diff --git a/src/main/kotlin/util/MoulConfigUtils.kt b/src/main/kotlin/util/MoulConfigUtils.kt
index 2e52092..362a4d9 100644
--- a/src/main/kotlin/util/MoulConfigUtils.kt
+++ b/src/main/kotlin/util/MoulConfigUtils.kt
@@ -7,6 +7,7 @@ import io.github.notenoughupdates.moulconfig.gui.GuiComponent
import io.github.notenoughupdates.moulconfig.gui.GuiComponentWrapper
import io.github.notenoughupdates.moulconfig.gui.GuiContext
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 io.github.notenoughupdates.moulconfig.platform.ModernRenderContext
@@ -24,6 +25,7 @@ import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
import net.minecraft.client.gui.DrawContext
import net.minecraft.client.gui.screen.Screen
+import net.minecraft.client.util.InputUtil
import moe.nea.firmament.gui.BarComponent
import moe.nea.firmament.gui.FirmButtonComponent
import moe.nea.firmament.gui.FirmHoverComponent
@@ -247,6 +249,28 @@ object MoulConfigUtils {
}
}
+ fun typeMCComponentInPlace(
+ component: GuiComponent,
+ x: Int,
+ y: Int,
+ w: Int,
+ h: Int,
+ keyboardEvent: KeyboardEvent
+ ): Boolean {
+ val immContext = createInPlaceFullContext(null, IMinecraft.instance.mouseX, IMinecraft.instance.mouseY)
+ if (component.keyboardEvent(keyboardEvent, immContext.translated(x, y, w, h)))
+ return true
+ if (component.context.getFocusedElement() != null) {
+ if (keyboardEvent is KeyboardEvent.KeyPressed
+ && keyboardEvent.pressed && keyboardEvent.keycode == InputUtil.GLFW_KEY_ESCAPE
+ ) {
+ component.context.setFocusedElement(null)
+ }
+ return true
+ }
+ return false
+ }
+
fun clickMCComponentInPlace(
component: GuiComponent,
x: Int,
diff --git a/src/main/kotlin/util/SBData.kt b/src/main/kotlin/util/SBData.kt
index 051d070..b2f9449 100644
--- a/src/main/kotlin/util/SBData.kt
+++ b/src/main/kotlin/util/SBData.kt
@@ -1,5 +1,6 @@
package moe.nea.firmament.util
+import java.time.ZoneId
import java.util.UUID
import net.hypixel.modapi.HypixelModAPI
import net.hypixel.modapi.packet.impl.clientbound.event.ClientboundLocationPacket
@@ -10,63 +11,75 @@ import moe.nea.firmament.events.ProcessChatEvent
import moe.nea.firmament.events.ProfileSwitchEvent
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 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
+ get() {
+ // TODO: allow unfiltered access to this somehow
+ if (!isOnSkyblock) return null
+ return field
+ }
- 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 profileIdCommandDebounce = TimeMark.farPast()
- fun init() {
- ServerConnectedEvent.subscribe("SBData:onServerConnected") {
- 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, locraw))
- profileIdCommandDebounce = TimeMark.now()
- }
- }
- SkyblockServerUpdateEvent.subscribe("SBData:sendProfileId") {
- if (!hasReceivedProfile && isOnSkyblock && profileIdCommandDebounce.passedTime() > 10.seconds) {
- profileIdCommandDebounce = TimeMark.now()
- MC.sendServerCommand("profileid")
- }
- }
- AllowChatEvent.subscribe("SBData:hideProfileSuggest") { event ->
- if (event.unformattedString in profileSuggestTexts && profileIdCommandDebounce.passedTime() < 5.seconds) {
- event.cancel()
- }
- }
- ProcessChatEvent.subscribe(receivesCancelled = true, "SBData:loadProfile") { event ->
- val profileMatch = profileRegex.matchEntire(event.unformattedString)
- if (profileMatch != null) {
- val oldProfile = profileId
- try {
- profileId = UUID.fromString(profileMatch.groupValues[1])
- hasReceivedProfile = true
- } catch (e: IllegalArgumentException) {
- profileId = null
- e.printStackTrace()
- }
- if (oldProfile != profileId) {
- ProfileSwitchEvent.publish(ProfileSwitchEvent(oldProfile, profileId))
- }
- }
- }
- }
+ /**
+ * Source: https://hypixel-skyblock.fandom.com/wiki/Time_Systems
+ */
+ val hypixelTimeZone = ZoneId.of("US/Eastern")
+ 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 profileIdCommandDebounce = TimeMark.farPast()
+ fun init() {
+ ServerConnectedEvent.subscribe("SBData:onServerConnected") {
+ HypixelModAPI.getInstance().subscribeToEventPacket(ClientboundLocationPacket::class.java)
+ }
+ HypixelModAPI.getInstance().createHandler(ClientboundLocationPacket::class.java) {
+ MC.onMainThread {
+ val lastLocraw = locraw
+ val oldProfileId = profileId
+ locraw = Locraw(it.serverName,
+ it.serverType.getOrNull()?.name?.uppercase(),
+ it.mode.getOrNull(),
+ it.map.getOrNull())
+ SkyblockServerUpdateEvent.publish(SkyblockServerUpdateEvent(lastLocraw, locraw))
+ if(oldProfileId != profileId) {
+ ProfileSwitchEvent.publish(ProfileSwitchEvent(oldProfileId, profileId))
+ }
+ profileIdCommandDebounce = TimeMark.now()
+ }
+ }
+ SkyblockServerUpdateEvent.subscribe("SBData:sendProfileId") {
+ if (!hasReceivedProfile && isOnSkyblock && profileIdCommandDebounce.passedTime() > 10.seconds) {
+ profileIdCommandDebounce = TimeMark.now()
+ MC.sendServerCommand("profileid")
+ }
+ }
+ AllowChatEvent.subscribe("SBData:hideProfileSuggest") { event ->
+ if (event.unformattedString in profileSuggestTexts && profileIdCommandDebounce.passedTime() < 5.seconds) {
+ event.cancel()
+ }
+ }
+ ProcessChatEvent.subscribe(receivesCancelled = true, "SBData:loadProfile") { event ->
+ val profileMatch = profileRegex.matchEntire(event.unformattedString)
+ if (profileMatch != null) {
+ val oldProfile = profileId
+ try {
+ profileId = UUID.fromString(profileMatch.groupValues[1])
+ hasReceivedProfile = true
+ } catch (e: IllegalArgumentException) {
+ profileId = null
+ e.printStackTrace()
+ }
+ if (oldProfile != profileId) {
+ ProfileSwitchEvent.publish(ProfileSwitchEvent(oldProfile, profileId))
+ }
+ }
+ }
+ }
}
diff --git a/src/main/kotlin/util/ScoreboardUtil.kt b/src/main/kotlin/util/ScoreboardUtil.kt
index 4311971..0970892 100644
--- a/src/main/kotlin/util/ScoreboardUtil.kt
+++ b/src/main/kotlin/util/ScoreboardUtil.kt
@@ -1,8 +1,6 @@
-
-
package moe.nea.firmament.util
-import java.util.*
+import java.util.Optional
import net.minecraft.client.gui.hud.InGameHud
import net.minecraft.scoreboard.ScoreboardDisplaySlot
import net.minecraft.scoreboard.Team
@@ -10,36 +8,48 @@ import net.minecraft.text.StringVisitable
import net.minecraft.text.Style
import net.minecraft.text.Text
import net.minecraft.util.Formatting
+import moe.nea.firmament.annotations.Subscribe
+import moe.nea.firmament.events.TickEvent
-fun getScoreboardLines(): List<Text> {
- 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)
- }
-}
+object ScoreboardUtil {
+ var scoreboardLines: List<Text> = listOf()
+ var simplifiedScoreboardLines: List<String> = listOf()
+ @Subscribe
+ fun onTick(event: TickEvent) {
+ scoreboardLines = getScoreboardLinesUncached()
+ simplifiedScoreboardLines = scoreboardLines.map { it.unformattedString }
+ }
+
+ private fun getScoreboardLinesUncached(): List<Text> {
+ 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<Unit> { 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(), "")
+ val sb = StringBuilder()
+ visit(StringVisitable.StyledVisitor<Unit> { 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/SkyBlockIsland.kt b/src/main/kotlin/util/SkyBlockIsland.kt
index c42a55c..a86543c 100644
--- a/src/main/kotlin/util/SkyBlockIsland.kt
+++ b/src/main/kotlin/util/SkyBlockIsland.kt
@@ -35,6 +35,8 @@ private constructor(
val PRIVATE_ISLAND = forMode("dynamic")
val RIFT = forMode("rift")
val MINESHAFT = forMode("mineshaft")
+ val GARDEN = forMode("garden")
+ val DUNGEON = forMode("dungeon")
}
val userFriendlyName
diff --git a/src/main/kotlin/util/SkyblockId.kt b/src/main/kotlin/util/SkyblockId.kt
index 1c1aa77..a99afda 100644
--- a/src/main/kotlin/util/SkyblockId.kt
+++ b/src/main/kotlin/util/SkyblockId.kt
@@ -106,7 +106,10 @@ data class HypixelPetInfo(
private val jsonparser = Json { ignoreUnknownKeys = true }
-val ItemStack.extraAttributes: NbtCompound
+var ItemStack.extraAttributes: NbtCompound
+ set(value) {
+ set(DataComponentTypes.CUSTOM_DATA, NbtComponent.of(value))
+ }
get() {
val customData = get(DataComponentTypes.CUSTOM_DATA) ?: run {
val component = NbtComponent.of(NbtCompound())
@@ -116,6 +119,12 @@ val ItemStack.extraAttributes: NbtCompound
return customData.nbt
}
+fun ItemStack.modifyExtraAttributes(block: (NbtCompound) -> Unit) {
+ val baseNbt = get(DataComponentTypes.CUSTOM_DATA)?.copyNbt() ?: NbtCompound()
+ block(baseNbt)
+ set(DataComponentTypes.CUSTOM_DATA, NbtComponent.of(baseNbt))
+}
+
val ItemStack.skyblockUUIDString: String?
get() = extraAttributes.getString("uuid")?.takeIf { it.isNotBlank() }
@@ -125,10 +134,26 @@ val ItemStack.skyblockUUID: UUID?
private val petDataCache = WeakCache.memoize<ItemStack, Optional<HypixelPetInfo>>("PetInfo") {
val jsonString = it.extraAttributes.getString("petInfo")
if (jsonString.isNullOrBlank()) return@memoize Optional.empty()
- ErrorUtil.catch<HypixelPetInfo?>("Could not decode hypixel pet info") { jsonparser.decodeFromString<HypixelPetInfo>(jsonString) }
+ ErrorUtil.catch<HypixelPetInfo?>("Could not decode hypixel pet info") {
+ jsonparser.decodeFromString<HypixelPetInfo>(jsonString)
+ }
.or { null }.intoOptional()
}
+fun ItemStack.getUpgradeStars(): Int {
+ return extraAttributes.getInt("upgrade_level").takeIf { it > 0 }
+ ?: extraAttributes.getInt("dungeon_item_level").takeIf { it > 0 }
+ ?: 0
+}
+
+@Serializable
+@JvmInline
+value class ReforgeId(val id: String)
+
+fun ItemStack.getReforgeId(): ReforgeId? {
+ return extraAttributes.getString("modifier").takeIf { it.isNotBlank() }?.let(::ReforgeId)
+}
+
val ItemStack.petData: HypixelPetInfo?
get() = petDataCache(this).getOrNull()
diff --git a/src/main/kotlin/util/StringUtil.kt b/src/main/kotlin/util/StringUtil.kt
index f080d5a..68e161a 100644
--- a/src/main/kotlin/util/StringUtil.kt
+++ b/src/main/kotlin/util/StringUtil.kt
@@ -10,4 +10,13 @@ object StringUtil {
}
fun Iterable<String>.unwords() = joinToString(" ")
+ fun nextLexicographicStringOfSameLength(string: String): String {
+ val next = StringBuilder(string)
+ while (next.lastOrNull() == Character.MAX_VALUE) next.setLength(next.length - 1)
+ if (next.isEmpty()) return "" // There is no upper bound. Fall back to the empty string
+ val lastIdx = next.indices.last
+ next[lastIdx] = (next[lastIdx] + 1)
+ return next.toString()
+ }
+
}
diff --git a/src/main/kotlin/util/TestUtil.kt b/src/main/kotlin/util/TestUtil.kt
index 68a406f..45e3dde 100644
--- a/src/main/kotlin/util/TestUtil.kt
+++ b/src/main/kotlin/util/TestUtil.kt
@@ -1,5 +1,9 @@
package moe.nea.firmament.util
object TestUtil {
- val isInTest = Thread.currentThread().stackTrace.any { it.className.startsWith("org.junit.") }
+ inline fun <T> unlessTesting(block: () -> T): T? = if (isInTest) null else block()
+ val isInTest =
+ Thread.currentThread().stackTrace.any {
+ it.className.startsWith("org.junit.") || it.className.startsWith("io.kotest.")
+ }
}
diff --git a/src/main/kotlin/util/customgui/CustomGui.kt b/src/main/kotlin/util/customgui/CustomGui.kt
index 5224448..35c60ac 100644
--- a/src/main/kotlin/util/customgui/CustomGui.kt
+++ b/src/main/kotlin/util/customgui/CustomGui.kt
@@ -76,4 +76,16 @@ abstract class CustomGui {
open fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean {
return false
}
+
+ open fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
+ return false
+ }
+
+ open fun charTyped(chr: Char, modifiers: Int): Boolean {
+ return false
+ }
+
+ open fun keyReleased(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
+ return false
+ }
}
diff --git a/src/main/kotlin/util/json/FirmCodecs.kt b/src/main/kotlin/util/json/FirmCodecs.kt
new file mode 100644
index 0000000..c0863bc
--- /dev/null
+++ b/src/main/kotlin/util/json/FirmCodecs.kt
@@ -0,0 +1,20 @@
+package moe.nea.firmament.util.json
+
+import com.mojang.serialization.Codec
+import com.mojang.serialization.DataResult
+import com.mojang.serialization.Lifecycle
+import com.mojang.util.UndashedUuid
+import net.minecraft.util.Uuids
+
+object FirmCodecs {
+ @JvmField
+ val UUID_LENIENT_PREFER_INT_STREAM = Codec.withAlternative(Uuids.INT_STREAM_CODEC, Codec.STRING.comapFlatMap(
+ {
+ try {
+ DataResult.success(UndashedUuid.fromStringLenient(it), Lifecycle.stable())
+ } catch (ex: IllegalArgumentException) {
+ DataResult.error { "Invalid UUID $it: ${ex.message}" }
+ }
+ },
+ UndashedUuid::toString))
+}
diff --git a/src/main/kotlin/util/json/KJsonOps.kt b/src/main/kotlin/util/json/KJsonOps.kt
new file mode 100644
index 0000000..404ea5e
--- /dev/null
+++ b/src/main/kotlin/util/json/KJsonOps.kt
@@ -0,0 +1,131 @@
+package moe.nea.firmament.util.json
+
+import com.google.gson.internal.LazilyParsedNumber
+import com.mojang.datafixers.util.Pair
+import com.mojang.serialization.DataResult
+import com.mojang.serialization.DynamicOps
+import java.util.stream.Stream
+import kotlinx.serialization.json.JsonArray
+import kotlinx.serialization.json.JsonElement
+import kotlinx.serialization.json.JsonNull
+import kotlinx.serialization.json.JsonObject
+import kotlinx.serialization.json.JsonPrimitive
+import kotlinx.serialization.json.boolean
+import kotlinx.serialization.json.booleanOrNull
+import kotlin.streams.asSequence
+
+class KJsonOps : DynamicOps<JsonElement> {
+ companion object {
+ val INSTANCE = KJsonOps()
+ }
+
+ override fun empty(): JsonElement {
+ return JsonNull
+ }
+
+ override fun createNumeric(num: Number): JsonElement {
+ return JsonPrimitive(num)
+ }
+
+ override fun createString(str: String): JsonElement {
+ return JsonPrimitive(str)
+ }
+
+ override fun remove(input: JsonElement, key: String): JsonElement {
+ if (input is JsonObject) {
+ return JsonObject(input.filter { it.key != key })
+ } else {
+ return input
+ }
+ }
+
+ override fun createList(stream: Stream<JsonElement>): JsonElement {
+ return JsonArray(stream.toList())
+ }
+
+ override fun getStream(input: JsonElement): DataResult<Stream<JsonElement>> {
+ if (input is JsonArray)
+ return DataResult.success(input.stream())
+ return DataResult.error { "Not a json array: $input" }
+ }
+
+ override fun createMap(map: Stream<Pair<JsonElement, JsonElement>>): JsonElement {
+ return JsonObject(map.asSequence()
+ .map { ((it.first as JsonPrimitive).content) to it.second }
+ .toMap())
+ }
+
+ override fun getMapValues(input: JsonElement): DataResult<Stream<Pair<JsonElement, JsonElement>>> {
+ if (input is JsonObject) {
+ return DataResult.success(input.entries.stream().map { Pair.of(createString(it.key), it.value) })
+ }
+ return DataResult.error { "Not a JSON object: $input" }
+ }
+
+ override fun mergeToMap(map: JsonElement, key: JsonElement, value: JsonElement): DataResult<JsonElement> {
+ if (key !is JsonPrimitive || key.isString) {
+ return DataResult.error { "key is not a string: $key" }
+ }
+ val jKey = key.content
+ val extra = mapOf(jKey to value)
+ if (map == empty()) {
+ return DataResult.success(JsonObject(extra))
+ }
+ if (map is JsonObject) {
+ return DataResult.success(JsonObject(map + extra))
+ }
+ return DataResult.error { "mergeToMap called with not a map: $map" }
+ }
+
+ override fun mergeToList(list: JsonElement, value: JsonElement): DataResult<JsonElement> {
+ if (list == empty())
+ return DataResult.success(JsonArray(listOf(value)))
+ if (list is JsonArray) {
+ return DataResult.success(JsonArray(list + value))
+ }
+ return DataResult.error { "mergeToList called with not a list: $list" }
+ }
+
+ override fun getStringValue(input: JsonElement): DataResult<String> {
+ if (input is JsonPrimitive && input.isString) {
+ return DataResult.success(input.content)
+ }
+ return DataResult.error { "Not a string: $input" }
+ }
+
+ override fun getNumberValue(input: JsonElement): DataResult<Number> {
+ if (input is JsonPrimitive && !input.isString && input.booleanOrNull == null)
+ return DataResult.success(LazilyParsedNumber(input.content))
+ return DataResult.error { "not a number: $input" }
+ }
+
+ override fun createBoolean(value: Boolean): JsonElement {
+ return JsonPrimitive(value)
+ }
+
+ override fun getBooleanValue(input: JsonElement): DataResult<Boolean> {
+ if (input is JsonPrimitive) {
+ if (input.booleanOrNull != null)
+ return DataResult.success(input.boolean)
+ return super.getBooleanValue(input)
+ }
+ return DataResult.error { "Not a boolean: $input" }
+ }
+
+ override fun <U : Any?> convertTo(output: DynamicOps<U>, input: JsonElement): U {
+ if (input is JsonObject)
+ return output.createMap(
+ input.entries.stream().map { Pair.of(output.createString(it.key), convertTo(output, it.value)) })
+ if (input is JsonArray)
+ return output.createList(input.stream().map { convertTo(output, it) })
+ if (input is JsonNull)
+ return output.empty()
+ if (input is JsonPrimitive) {
+ if (input.isString)
+ return output.createString(input.content)
+ if (input.booleanOrNull != null)
+ return output.createBoolean(input.boolean)
+ }
+ error("Unknown json value: $input")
+ }
+}
diff --git a/src/main/kotlin/util/mc/IntrospectableItemModelManager.kt b/src/main/kotlin/util/mc/IntrospectableItemModelManager.kt
new file mode 100644
index 0000000..e546fd3
--- /dev/null
+++ b/src/main/kotlin/util/mc/IntrospectableItemModelManager.kt
@@ -0,0 +1,7 @@
+package moe.nea.firmament.util.mc
+
+import net.minecraft.util.Identifier
+
+interface IntrospectableItemModelManager {
+ fun hasModel_firmament(identifier: Identifier): Boolean
+}
diff --git a/src/main/kotlin/util/mc/NbtItemData.kt b/src/main/kotlin/util/mc/NbtItemData.kt
index e8a908f..0c49862 100644
--- a/src/main/kotlin/util/mc/NbtItemData.kt
+++ b/src/main/kotlin/util/mc/NbtItemData.kt
@@ -5,8 +5,8 @@ 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()
+var ItemStack.loreAccordingToNbt: List<Text>
+ get() = get(DataComponentTypes.LORE)?.lines ?: listOf()
set(value) {
set(DataComponentTypes.LORE, LoreComponent(value))
}
diff --git a/src/main/kotlin/util/mc/TolerantRegistriesOps.kt b/src/main/kotlin/util/mc/TolerantRegistriesOps.kt
new file mode 100644
index 0000000..ce596a0
--- /dev/null
+++ b/src/main/kotlin/util/mc/TolerantRegistriesOps.kt
@@ -0,0 +1,29 @@
+package moe.nea.firmament.util.mc
+
+import com.mojang.serialization.DynamicOps
+import java.util.Optional
+import net.minecraft.registry.Registry
+import net.minecraft.registry.RegistryKey
+import net.minecraft.registry.RegistryOps
+import net.minecraft.registry.RegistryWrapper
+import net.minecraft.registry.entry.RegistryEntryOwner
+
+class TolerantRegistriesOps<T>(
+ delegate: DynamicOps<T>,
+ registryInfoGetter: RegistryInfoGetter
+) : RegistryOps<T>(delegate, registryInfoGetter) {
+ constructor(delegate: DynamicOps<T>, registry: RegistryWrapper.WrapperLookup) :
+ this(delegate, CachedRegistryInfoGetter(registry))
+
+ class TolerantOwner<E> : RegistryEntryOwner<E> {
+ override fun ownerEquals(other: RegistryEntryOwner<E>?): Boolean {
+ return true
+ }
+ }
+
+ override fun <E : Any?> getOwner(registryRef: RegistryKey<out Registry<out E>>?): Optional<RegistryEntryOwner<E>> {
+ return super.getOwner(registryRef).map {
+ TolerantOwner()
+ }
+ }
+}
diff --git a/src/main/kotlin/util/render/TintedOverlayTexture.kt b/src/main/kotlin/util/render/TintedOverlayTexture.kt
new file mode 100644
index 0000000..a02eccc
--- /dev/null
+++ b/src/main/kotlin/util/render/TintedOverlayTexture.kt
@@ -0,0 +1,44 @@
+package moe.nea.firmament.util.render
+
+import com.mojang.blaze3d.platform.GlConst
+import com.mojang.blaze3d.systems.RenderSystem
+import me.shedaniel.math.Color
+import net.minecraft.client.render.OverlayTexture
+import net.minecraft.util.math.ColorHelper
+import moe.nea.firmament.util.ErrorUtil
+
+class TintedOverlayTexture : OverlayTexture() {
+ companion object {
+ val size = 16
+ }
+
+ private var lastColor: Color? = null
+ fun setColor(color: Color): TintedOverlayTexture {
+ val image = ErrorUtil.notNullOr(texture.image, "Disposed TintedOverlayTexture written to") { return this }
+ if (color == lastColor) return this
+ lastColor = color
+
+ for (i in 0..<size) {
+ for (j in 0..<size) {
+ if (i < 8) {
+ image.setColorArgb(j, i, 0xB2FF0000.toInt())
+ } else {
+ val k = ((1F - j / 15F * 0.75F) * 255F).toInt()
+ image.setColorArgb(j, i, ColorHelper.withAlpha(k, color.color))
+ }
+ }
+ }
+
+ RenderSystem.activeTexture(GlConst.GL_TEXTURE1)
+ texture.bindTexture()
+ texture.setFilter(false, false)
+ texture.setClamp(true)
+ image.upload(0,
+ 0, 0,
+ 0, 0,
+ image.width, image.height,
+ false)
+ RenderSystem.activeTexture(GlConst.GL_TEXTURE0)
+ return this
+ }
+}
diff --git a/src/main/kotlin/util/render/TranslatedScissors.kt b/src/main/kotlin/util/render/TranslatedScissors.kt
index c1e6544..8f8bdcf 100644
--- a/src/main/kotlin/util/render/TranslatedScissors.kt
+++ b/src/main/kotlin/util/render/TranslatedScissors.kt
@@ -1,11 +1,15 @@
package moe.nea.firmament.util.render
+import org.joml.Matrix4f
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
+ enableScissor(x1.toInt(), y1.toInt(), x2.toInt(), y2.toInt())
+}
+fun DrawContext.enableScissorWithoutTranslation(x1: Float, y1: Float, x2: Float, y2: Float) {
+ val pMat = matrices.peek().positionMatrix.invert(Matrix4f())
val target = Vector4f()
target.set(x1, y1, 0f, 1f)
diff --git a/src/main/kotlin/util/skyblock/AbilityUtils.kt b/src/main/kotlin/util/skyblock/AbilityUtils.kt
index 0f0adbe..0d7d2b7 100644
--- a/src/main/kotlin/util/skyblock/AbilityUtils.kt
+++ b/src/main/kotlin/util/skyblock/AbilityUtils.kt
@@ -131,6 +131,7 @@ object AbilityUtils {
return abilities
}
+ // TODO: memoize
fun getAbilities(itemStack: ItemStack): List<ItemAbility> {
return getAbilities(itemStack.loreAccordingToNbt)
}
diff --git a/src/main/kotlin/util/skyblock/DungeonUtil.kt b/src/main/kotlin/util/skyblock/DungeonUtil.kt
new file mode 100644
index 0000000..488b158
--- /dev/null
+++ b/src/main/kotlin/util/skyblock/DungeonUtil.kt
@@ -0,0 +1,33 @@
+package moe.nea.firmament.util.skyblock
+
+import moe.nea.firmament.util.SBData
+import moe.nea.firmament.util.ScoreboardUtil
+import moe.nea.firmament.util.SkyBlockIsland
+import moe.nea.firmament.util.TIME_PATTERN
+
+object DungeonUtil {
+ val isInDungeonIsland get() = SBData.skyblockLocation == SkyBlockIsland.DUNGEON
+ private val timeElapsedRegex = "Time Elapsed: $TIME_PATTERN".toRegex()
+ val isInActiveDungeon get() = isInDungeonIsland && ScoreboardUtil.simplifiedScoreboardLines.any { it.matches(
+ timeElapsedRegex) }
+
+/*Title:
+
+§f§lSKYBLOCK§B§L CO-OP
+
+' Late Spring 7th'
+' §75:20am'
+' §7⏣ §cThe Catacombs §7(M3)'
+' §7♲ §7Ironman'
+' '
+'Keys: §c■ §c✗ §8■ §a1x'
+'Time Elapsed: §a46s'
+'Cleared: §660% §8(105)'
+' '
+'§e[B] §b151_Dragon §e2,062§c❤'
+'§e[A] §6Lennart0312 §a17,165§c'
+'§e[T] §b187i §a14,581§c❤'
+'§e[H] §bFlameeke §a8,998§c❤'
+' '
+'§ewww.hypixel.net'*/
+}
diff --git a/src/main/kotlin/util/skyblock/ItemType.kt b/src/main/kotlin/util/skyblock/ItemType.kt
index 7d53c0d..7a776b5 100644
--- a/src/main/kotlin/util/skyblock/ItemType.kt
+++ b/src/main/kotlin/util/skyblock/ItemType.kt
@@ -1,21 +1,75 @@
package moe.nea.firmament.util.skyblock
-import kotlin.properties.ReadOnlyProperty
-import kotlin.reflect.KProperty
+import net.minecraft.item.ItemStack
+import moe.nea.firmament.util.directLiteralStringContent
+import moe.nea.firmament.util.mc.loreAccordingToNbt
+import moe.nea.firmament.util.petData
-class ItemType(val name: String) {
+@JvmInline
+value class ItemType private constructor(val name: String) {
companion object {
- private val generated = object : ReadOnlyProperty<Any?, ItemType> {
- override fun getValue(thisRef: Any?, property: KProperty<*>): ItemType {
- return ItemType.ofName(property.name)
- }
- }
-
fun ofName(name: String): ItemType {
return ItemType(name)
}
- val SWORD by generated
+ private val obfuscatedRegex = "§[kK].*?(§[0-9a-fA-FrR]|$)".toRegex()
+ fun fromEscapeCodeLore(lore: String): ItemType? {
+ return lore.replace(obfuscatedRegex, "").trim().substringAfter(" ", "")
+ .takeIf { it.isNotEmpty() }
+ ?.let(::ofName)
+ }
+
+ fun fromItemStack(itemStack: ItemStack): ItemType? {
+ if (itemStack.petData != null)
+ return PET
+ val typeText =
+ itemStack.loreAccordingToNbt.lastOrNull()
+ ?.siblings?.find {
+ !it.style.isObfuscated && !it.directLiteralStringContent.isNullOrBlank()
+ }?.directLiteralStringContent
+ if (typeText != null) {
+ val type = typeText.substringAfter(' ', missingDelimiterValue = "").trim()
+ if (type.isEmpty()) return null
+ return ofName(type)
+ }
+ return itemStack.loreAccordingToNbt.lastOrNull()?.directLiteralStringContent?.let(::fromEscapeCodeLore)
+ }
+
+ // TODO: some of those are not actual in game item types, but rather ones included in the repository to splat to multiple in game types. codify those somehow
+
+ val SWORD = ofName("SWORD")
+ val DRILL = ofName("DRILL")
+ val PICKAXE = ofName("PICKAXE")
+ val GAUNTLET = ofName("GAUNTLET")
+ val LONGSWORD = ofName("LONG SWORD")
+ val EQUIPMENT = ofName("EQUIPMENT")
+ val FISHING_WEAPON = ofName("FISHING WEAPON")
+ val CLOAK = ofName("CLOAK")
+ val BELT = ofName("BELT")
+ val NECKLACE = ofName("NECKLACE")
+ val BRACELET = ofName("BRACELET")
+ val GLOVES = ofName("GLOVES")
+ val ROD = ofName("ROD")
+ val FISHING_ROD = ofName("FISHING ROD")
+ val VACUUM = ofName("VACUUM")
+ val CHESTPLATE = ofName("CHESTPLATE")
+ val LEGGINGS = ofName("LEGGINGS")
+ val HELMET = ofName("HELMET")
+ val BOOTS = ofName("BOOTS")
+ val NIL = ofName("__NIL")
+
+ /**
+ * This one is not really official (it never shows up in game).
+ */
+ val PET = ofName("PET")
+ }
+
+ val dungeonVariant get() = ofName("DUNGEON $name")
+
+ val isDungeon get() = name.startsWith("DUNGEON ")
+
+ override fun toString(): String {
+ return name
}
}
diff --git a/src/main/kotlin/util/skyblock/Rarity.kt b/src/main/kotlin/util/skyblock/Rarity.kt
index f26cefe..b19f371 100644
--- a/src/main/kotlin/util/skyblock/Rarity.kt
+++ b/src/main/kotlin/util/skyblock/Rarity.kt
@@ -1,7 +1,16 @@
package moe.nea.firmament.util.skyblock
+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.text.Style
import net.minecraft.text.Text
+import net.minecraft.util.Formatting
import moe.nea.firmament.util.StringUtil.words
import moe.nea.firmament.util.collections.lastNotNullOfOrNull
import moe.nea.firmament.util.mc.loreAccordingToNbt
@@ -10,6 +19,7 @@ import moe.nea.firmament.util.unformattedString
typealias RepoRarity = io.github.moulberry.repo.data.Rarity
+@Serializable(with = Rarity.Serializer::class)
enum class Rarity(vararg altNames: String) {
COMMON,
UNCOMMON,
@@ -24,11 +34,37 @@ enum class Rarity(vararg altNames: String) {
UNKNOWN
;
- val names = setOf(name) + altNames
+ object Serializer : KSerializer<Rarity> {
+ override val descriptor: SerialDescriptor
+ get() = PrimitiveSerialDescriptor(Rarity::class.java.name, PrimitiveKind.STRING)
+
+ override fun deserialize(decoder: Decoder): Rarity {
+ return valueOf(decoder.decodeString().replace(" ", "_"))
+ }
+ override fun serialize(encoder: Encoder, value: Rarity) {
+ encoder.encodeString(value.name)
+ }
+ }
+
+ val names = setOf(name) + altNames
+ val text: Text get() = Text.literal(name).setStyle(Style.EMPTY.withColor(colourMap[this]))
val neuRepoRarity: RepoRarity? = RepoRarity.entries.find { it.name == name }
companion object {
+ // TODO: inline those formattings as fields
+ val colourMap = mapOf(
+ Rarity.COMMON to Formatting.WHITE,
+ Rarity.UNCOMMON to Formatting.GREEN,
+ Rarity.RARE to Formatting.BLUE,
+ Rarity.EPIC to Formatting.DARK_PURPLE,
+ Rarity.LEGENDARY to Formatting.GOLD,
+ Rarity.MYTHIC to Formatting.LIGHT_PURPLE,
+ Rarity.DIVINE to Formatting.AQUA,
+ Rarity.SPECIAL to Formatting.RED,
+ Rarity.VERY_SPECIAL to Formatting.RED,
+ Rarity.SUPREME to Formatting.DARK_RED,
+ )
val byName = entries.flatMap { en -> en.names.map { it to en } }.toMap()
val fromNeuRepo = entries.associateBy { it.neuRepoRarity }
diff --git a/src/main/kotlin/util/skyblock/SkyBlockItems.kt b/src/main/kotlin/util/skyblock/SkyBlockItems.kt
index c94ebfe..ca2b17b 100644
--- a/src/main/kotlin/util/skyblock/SkyBlockItems.kt
+++ b/src/main/kotlin/util/skyblock/SkyBlockItems.kt
@@ -7,4 +7,10 @@ object SkyBlockItems {
val ENCHANTED_DIAMOND = SkyblockId("ENCHANTED_DIAMOND")
val DIAMOND = SkyblockId("DIAMOND")
val ANCESTRAL_SPADE = SkyblockId("ANCESTRAL_SPADE")
+ val REFORGE_ANVIL = SkyblockId("REFORGE_ANVIL")
+ val SLICE_OF_BLUEBERRY_CAKE = SkyblockId("SLICE_OF_BLUEBERRY_CAKE")
+ val SLICE_OF_CHEESECAKE = SkyblockId("SLICE_OF_CHEESECAKE")
+ val SLICE_OF_GREEN_VELVET_CAKE = SkyblockId("SLICE_OF_GREEN_VELVET_CAKE")
+ val SLICE_OF_RED_VELVET_CAKE = SkyblockId("SLICE_OF_RED_VELVET_CAKE")
+ val SLICE_OF_STRAWBERRY_SHORTCAKE = SkyblockId("SLICE_OF_STRAWBERRY_SHORTCAKE")
}
diff --git a/src/main/kotlin/util/textutil.kt b/src/main/kotlin/util/textutil.kt
index 5d95d7a..c295ae0 100644
--- a/src/main/kotlin/util/textutil.kt
+++ b/src/main/kotlin/util/textutil.kt
@@ -1,72 +1,18 @@
package moe.nea.firmament.util
+import java.util.Optional
import net.minecraft.text.ClickEvent
import net.minecraft.text.MutableText
+import net.minecraft.text.OrderedText
import net.minecraft.text.PlainTextContent
+import net.minecraft.text.StringVisitable
+import net.minecraft.text.Style
import net.minecraft.text.Text
import net.minecraft.text.TextColor
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<Text>,
- 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('§')
@@ -89,6 +35,47 @@ fun CharSequence.removeColorCodes(keepNonColorCodes: Boolean = false): String {
return stringBuffer.toString()
}
+fun OrderedText.reconstitute(): MutableText {
+ val base = Text.literal("")
+ base.setStyle(Style.EMPTY.withItalic(false))
+ var lastColorCode = Style.EMPTY
+ val text = StringBuilder()
+ this.accept { index, style, codePoint ->
+ if (style != lastColorCode) {
+ if (text.isNotEmpty())
+ base.append(Text.literal(text.toString()).setStyle(lastColorCode))
+ lastColorCode = style
+ text.clear()
+ }
+ text.append(codePoint.toChar())
+ true
+ }
+ if (text.isNotEmpty())
+ base.append(Text.literal(text.toString()).setStyle(lastColorCode))
+ return base
+
+}
+fun StringVisitable.reconstitute(): MutableText {
+ val base = Text.literal("")
+ base.setStyle(Style.EMPTY.withItalic(false))
+ var lastColorCode = Style.EMPTY
+ val text = StringBuilder()
+ this.visit({ style, string ->
+ if (style != lastColorCode) {
+ if (text.isNotEmpty())
+ base.append(Text.literal(text.toString()).setStyle(lastColorCode))
+ lastColorCode = style
+ text.clear()
+ }
+ text.append(string)
+ Optional.empty<Unit>()
+ }, Style.EMPTY)
+ if (text.isNotEmpty())
+ base.append(Text.literal(text.toString()).setStyle(lastColorCode))
+ return base
+
+}
+
val Text.unformattedString: String
get() = string.removeColorCodes() // TODO: maybe shortcircuit this with .visit
@@ -133,7 +120,9 @@ fun MutableText.darkGreen() = withColor(Formatting.DARK_GREEN)
fun MutableText.purple() = withColor(Formatting.DARK_PURPLE)
fun MutableText.pink() = withColor(Formatting.LIGHT_PURPLE)
fun MutableText.yellow() = withColor(Formatting.YELLOW)
+fun MutableText.gold() = withColor(Formatting.GOLD)
fun MutableText.grey() = withColor(Formatting.GRAY)
+fun MutableText.darkGrey() = withColor(Formatting.DARK_GRAY)
fun MutableText.red() = withColor(Formatting.RED)
fun MutableText.white() = withColor(Formatting.WHITE)
fun MutableText.bold(): MutableText = styled { it.withBold(true) }
@@ -142,11 +131,15 @@ fun MutableText.bold(): MutableText = styled { it.withBold(true) }
fun MutableText.clickCommand(command: String): MutableText {
require(command.startsWith("/"))
return this.styled {
- it.withClickEvent(ClickEvent(ClickEvent.Action.RUN_COMMAND,
- "/firm disablereiwarning"))
+ it.withClickEvent(ClickEvent(ClickEvent.Action.RUN_COMMAND, command))
}
}
+fun MutableText.prepend(text: Text): MutableText {
+ siblings.addFirst(text)
+ return this
+}
+
fun Text.transformEachRecursively(function: (Text) -> Text): Text {
val c = this.content
if (c is TranslatableTextContent) {
diff --git a/src/main/resources/assets/firmament/textures/gui/sprites/widget/checkbox_checked.png b/src/main/resources/assets/firmament/textures/gui/sprites/widget/checkbox_checked.png
new file mode 100644
index 0000000..1b87c55
--- /dev/null
+++ b/src/main/resources/assets/firmament/textures/gui/sprites/widget/checkbox_checked.png
Binary files differ
diff --git a/src/main/resources/assets/firmament/textures/gui/sprites/widget/checkbox_unchecked.png b/src/main/resources/assets/firmament/textures/gui/sprites/widget/checkbox_unchecked.png
new file mode 100644
index 0000000..dcd9aa4
--- /dev/null
+++ b/src/main/resources/assets/firmament/textures/gui/sprites/widget/checkbox_unchecked.png
Binary files differ
diff --git a/src/main/resources/firmament.accesswidener b/src/main/resources/firmament.accesswidener
index c542fc8..1805721 100644
--- a/src/main/resources/firmament.accesswidener
+++ b/src/main/resources/firmament.accesswidener
@@ -3,13 +3,10 @@ accessible class net/minecraft/client/render/RenderLayer$MultiPhase
accessible class net/minecraft/client/render/RenderLayer$MultiPhaseParameters
accessible class net/minecraft/client/font/TextRenderer$Drawer
accessible field net/minecraft/client/gui/hud/InGameHud SCOREBOARD_ENTRY_COMPARATOR Ljava/util/Comparator;
+accessible field net/minecraft/client/network/ClientPlayNetworkHandler combinedDynamicRegistries Lnet/minecraft/registry/DynamicRegistryManager$Immutable;
+accessible method net/minecraft/registry/RegistryOps <init> (Lcom/mojang/serialization/DynamicOps;Lnet/minecraft/registry/RegistryOps$RegistryInfoGetter;)V
+accessible class net/minecraft/registry/RegistryOps$CachedRegistryInfoGetter
-accessible field net/minecraft/client/render/item/HeldItemRenderer itemRenderer Lnet/minecraft/client/render/item/ItemRenderer;
-accessible field net/minecraft/client/render/item/ItemModels missingModelSupplier Ljava/util/function/Supplier;
-mutable field net/minecraft/client/render/model/json/ModelOverrideList conditionTypes [Lnet/minecraft/util/Identifier;
-
-accessible class net/minecraft/client/render/model/json/ModelOverride$Deserializer
-accessible class net/minecraft/client/render/model/json/ModelOverrideList$BakedOverride
accessible field net/minecraft/entity/mob/CreeperEntity CHARGED Lnet/minecraft/entity/data/TrackedData;
accessible method net/minecraft/entity/decoration/ArmorStandEntity setSmall (Z)V
accessible field net/minecraft/entity/passive/AbstractHorseEntity items Lnet/minecraft/inventory/SimpleInventory;
@@ -23,14 +20,8 @@ mutable field net/minecraft/screen/slot/Slot y I
accessible field net/minecraft/entity/player/PlayerEntity PLAYER_MODEL_PARTS Lnet/minecraft/entity/data/TrackedData;
accessible field net/minecraft/client/render/WorldRenderer chunks Lnet/minecraft/client/render/BuiltChunkStorage;
+accessible field net/minecraft/client/render/OverlayTexture texture Lnet/minecraft/client/texture/NativeImageBackedTexture;
-# Fix package-private access methods
-accessible method net/minecraft/registry/entry/RegistryEntry$Reference setRegistryKey (Lnet/minecraft/registry/RegistryKey;)V
-accessible method net/minecraft/entity/LivingEntity getHitbox ()Lnet/minecraft/util/math/Box;
-accessible method net/minecraft/registry/entry/RegistryEntryList$Named <init> (Lnet/minecraft/registry/entry/RegistryEntryOwner;Lnet/minecraft/registry/tag/TagKey;)V
-accessible method net/minecraft/registry/entry/RegistryEntry$Reference setValue (Ljava/lang/Object;)V
-accessible field net/minecraft/client/render/model/WrapperBakedModel wrapped Lnet/minecraft/client/render/model/BakedModel;
-accessible method net/minecraft/entity/passive/TameableEntity isInSameTeam (Lnet/minecraft/entity/Entity;)Z
-accessible method net/minecraft/entity/Entity isInSameTeam (Lnet/minecraft/entity/Entity;)Z
-accessible method net/minecraft/registry/entry/RegistryEntry$Reference setTags (Ljava/util/Collection;)V
-accessible method net/minecraft/registry/entry/RegistryEntryList$Named setEntries (Ljava/util/List;)V
+accessible method net/minecraft/client/render/RenderPhase$Texture getId ()Ljava/util/Optional;
+accessible field net/minecraft/client/render/RenderLayer$MultiPhase phases Lnet/minecraft/client/render/RenderLayer$MultiPhaseParameters;
+accessible field net/minecraft/client/render/RenderLayer$MultiPhaseParameters texture Lnet/minecraft/client/render/RenderPhase$TextureBase;
diff --git a/src/test/kotlin/testutil/ItemResources.kt b/src/test/kotlin/testutil/ItemResources.kt
index ee2a322..107b565 100644
--- a/src/test/kotlin/testutil/ItemResources.kt
+++ b/src/test/kotlin/testutil/ItemResources.kt
@@ -2,11 +2,14 @@ package moe.nea.firmament.test.testutil
import net.minecraft.item.ItemStack
import net.minecraft.nbt.NbtCompound
+import net.minecraft.nbt.NbtElement
import net.minecraft.nbt.NbtOps
import net.minecraft.nbt.StringNbtReader
+import net.minecraft.registry.RegistryOps
import net.minecraft.text.Text
import net.minecraft.text.TextCodecs
import moe.nea.firmament.test.FirmTestBootstrap
+import moe.nea.firmament.util.MC
object ItemResources {
init {
@@ -23,15 +26,16 @@ object ItemResources {
fun loadSNbt(path: String): NbtCompound {
return StringNbtReader.parse(loadString(path))
}
+ fun getNbtOps(): RegistryOps<NbtElement> = MC.currentOrDefaultRegistries.getOps(NbtOps.INSTANCE)
fun loadText(name: String): Text {
- return TextCodecs.CODEC.parse(NbtOps.INSTANCE, loadSNbt("testdata/chat/$name.snbt"))
+ return TextCodecs.CODEC.parse(getNbtOps(), loadSNbt("testdata/chat/$name.snbt"))
.getOrThrow { IllegalStateException("Could not load test chat '$name': $it") }
}
fun loadItem(name: String): ItemStack {
// TODO: make the load work with enchantments
- return ItemStack.CODEC.parse(NbtOps.INSTANCE, loadSNbt("testdata/items/$name.snbt"))
+ return ItemStack.CODEC.parse(getNbtOps(), loadSNbt("testdata/items/$name.snbt"))
.getOrThrow { IllegalStateException("Could not load test item '$name': $it") }
}
}
diff --git a/src/test/kotlin/testutil/KotestPlugin.kt b/src/test/kotlin/testutil/KotestPlugin.kt
new file mode 100644
index 0000000..6db50fb
--- /dev/null
+++ b/src/test/kotlin/testutil/KotestPlugin.kt
@@ -0,0 +1,16 @@
+package moe.nea.firmament.test.testutil
+
+import io.kotest.core.config.AbstractProjectConfig
+import io.kotest.core.extensions.Extension
+import moe.nea.firmament.test.FirmTestBootstrap
+
+class KotestPlugin : AbstractProjectConfig() {
+ override fun extensions(): List<Extension> {
+ return listOf()
+ }
+
+ override suspend fun beforeProject() {
+ FirmTestBootstrap.bootstrapMinecraft()
+ super.beforeProject()
+ }
+}
diff --git a/src/test/kotlin/util/ColorCodeTest.kt b/src/test/kotlin/util/ColorCodeTest.kt
index d9de36a..949749e 100644
--- a/src/test/kotlin/util/ColorCodeTest.kt
+++ b/src/test/kotlin/util/ColorCodeTest.kt
@@ -1,25 +1,23 @@
package moe.nea.firmament.test.util
+import io.kotest.core.spec.style.AnnotationSpec
import org.junit.jupiter.api.Assertions
-import org.junit.jupiter.api.Test
-import net.minecraft.Bootstrap
-import net.minecraft.SharedConstants
import moe.nea.firmament.util.removeColorCodes
-class ColorCodeTest {
+class ColorCodeTest : AnnotationSpec() {
@Test
fun testWhatever() {
- Assertions.assertEquals("", "".removeColorCodes().toString())
- Assertions.assertEquals("", "§".removeColorCodes().toString())
- Assertions.assertEquals("", "§a".removeColorCodes().toString())
- Assertions.assertEquals("ab", "a§ab".removeColorCodes().toString())
- Assertions.assertEquals("ab", "a§ab§§".removeColorCodes().toString())
- Assertions.assertEquals("abc", "a§ab§§c".removeColorCodes().toString())
- Assertions.assertEquals("bc", "§ab§§c".removeColorCodes().toString())
- Assertions.assertEquals("b§lc", "§ab§l§§c".removeColorCodes(true).toString())
- Assertions.assertEquals("b§lc§l", "§ab§l§§c§l".removeColorCodes(true).toString())
- Assertions.assertEquals("§lb§lc", "§l§ab§l§§c".removeColorCodes(true).toString())
+ Assertions.assertEquals("", "".removeColorCodes())
+ Assertions.assertEquals("", "§".removeColorCodes())
+ Assertions.assertEquals("", "§a".removeColorCodes())
+ Assertions.assertEquals("ab", "a§ab".removeColorCodes())
+ Assertions.assertEquals("ab", "a§ab§§".removeColorCodes())
+ Assertions.assertEquals("abc", "a§ab§§c".removeColorCodes())
+ Assertions.assertEquals("bc", "§ab§§c".removeColorCodes())
+ Assertions.assertEquals("b§lc", "§ab§l§§c".removeColorCodes(true))
+ Assertions.assertEquals("b§lc§l", "§ab§l§§c§l".removeColorCodes(true))
+ Assertions.assertEquals("§lb§lc", "§l§ab§l§§c".removeColorCodes(true))
}
@Test
diff --git a/src/test/kotlin/util/TextUtilText.kt b/src/test/kotlin/util/TextUtilText.kt
index 7091f4e..46ed3b4 100644
--- a/src/test/kotlin/util/TextUtilText.kt
+++ b/src/test/kotlin/util/TextUtilText.kt
@@ -1,15 +1,16 @@
package moe.nea.firmament.test.util
+import io.kotest.core.spec.style.AnnotationSpec
import org.junit.jupiter.api.Assertions
-import org.junit.jupiter.api.Test
import moe.nea.firmament.test.testutil.ItemResources
import moe.nea.firmament.util.getLegacyFormatString
-class TextUtilText {
+class TextUtilText : AnnotationSpec() {
@Test
fun testThing() {
// TODO: add more tests that are directly validated with 1.8.9 code
val text = ItemResources.loadText("all-chat")
- Assertions.assertEquals("§r§r§8[§r§9302§r§8] §r§6♫ §r§b[MVP§r§d+§r§b] lrg89§r§f: test§r", text.getLegacyFormatString())
+ Assertions.assertEquals("§r§r§8[§r§9302§r§8] §r§6♫ §r§b[MVP§r§d+§r§b] lrg89§r§f: test§r",
+ text.getLegacyFormatString())
}
}
diff --git a/src/test/kotlin/util/skyblock/AbilityUtilsTest.kt b/src/test/kotlin/util/skyblock/AbilityUtilsTest.kt
index abe739d..206a357 100644
--- a/src/test/kotlin/util/skyblock/AbilityUtilsTest.kt
+++ b/src/test/kotlin/util/skyblock/AbilityUtilsTest.kt
@@ -1,7 +1,7 @@
package moe.nea.firmament.test.util.skyblock
+import io.kotest.core.spec.style.AnnotationSpec
import org.junit.jupiter.api.Assertions
-import org.junit.jupiter.api.Test
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds
import net.minecraft.text.Text
@@ -9,7 +9,7 @@ import moe.nea.firmament.test.testutil.ItemResources
import moe.nea.firmament.util.skyblock.AbilityUtils
import moe.nea.firmament.util.unformattedString
-class AbilityUtilsTest {
+class AbilityUtilsTest : AnnotationSpec() {
fun List<AbilityUtils.ItemAbility>.stripDescriptions() = map {
it.copy(descriptionLines = it.descriptionLines.map { Text.literal(it.unformattedString) })
diff --git a/src/test/kotlin/util/skyblock/ItemTypeTest.kt b/src/test/kotlin/util/skyblock/ItemTypeTest.kt
new file mode 100644
index 0000000..cca3d13
--- /dev/null
+++ b/src/test/kotlin/util/skyblock/ItemTypeTest.kt
@@ -0,0 +1,26 @@
+package moe.nea.firmament.test.util.skyblock
+
+import io.kotest.core.spec.style.ShouldSpec
+import io.kotest.matchers.shouldBe
+import moe.nea.firmament.test.testutil.ItemResources
+import moe.nea.firmament.util.skyblock.ItemType
+
+class ItemTypeTest
+ : ShouldSpec(
+ {
+ context("ItemType.fromItemstack") {
+ listOf(
+ "pets/lion-item" to ItemType.PET,
+ "pets/rabbit-selected" to ItemType.PET,
+ "pets/mithril-golem-not-selected" to ItemType.PET,
+ "aspect-of-the-void" to ItemType.SWORD,
+ "titanium-drill" to ItemType.DRILL,
+ "diamond-pickaxe" to ItemType.PICKAXE,
+ "gemstone-gauntlet" to ItemType.GAUNTLET,
+ ).forEach { (name, typ) ->
+ should("return $typ for $name") {
+ ItemType.fromItemStack(ItemResources.loadItem(name)) shouldBe typ
+ }
+ }
+ }
+ })
diff --git a/src/test/kotlin/util/skyblock/SackUtilTest.kt b/src/test/kotlin/util/skyblock/SackUtilTest.kt
index e0e3e63..f93cd2b 100644
--- a/src/test/kotlin/util/skyblock/SackUtilTest.kt
+++ b/src/test/kotlin/util/skyblock/SackUtilTest.kt
@@ -1,12 +1,12 @@
package moe.nea.firmament.test.util.skyblock
+import io.kotest.core.spec.style.AnnotationSpec
import org.junit.jupiter.api.Assertions
-import org.junit.jupiter.api.Test
import moe.nea.firmament.test.testutil.ItemResources
import moe.nea.firmament.util.skyblock.SackUtil
import moe.nea.firmament.util.skyblock.SkyBlockItems
-class SackUtilTest {
+class SackUtilTest : AnnotationSpec() {
@Test
fun testOneRottenFlesh() {
Assertions.assertEquals(
diff --git a/src/test/resources/testdata/items/books/feather_falling.snbt b/src/test/resources/testdata/items/books/feather_falling.snbt
new file mode 100644
index 0000000..1de4632
--- /dev/null
+++ b/src/test/resources/testdata/items/books/feather_falling.snbt
@@ -0,0 +1,36 @@
+{
+ components: {
+ "minecraft:attribute_modifiers": {
+ modifiers: [
+ ],
+ show_in_tooltip: 0b
+ },
+ "minecraft:custom_data": {
+ enchantments: {
+ feather_falling: 6
+ },
+ id: "ENCHANTED_BOOK",
+ timestamp: 1737123521091L,
+ uuid: "b8128489-9ed0-4a1a-94c0-d3279ffe45ac"
+ },
+ "minecraft:custom_name": '{"extra":[{"color":"blue","text":"Enchanted Book"}],"italic":false,"text":""}',
+ "minecraft:hide_additional_tooltip": {
+ },
+ "minecraft:lore": [
+ '{"extra":[{"color":"blue","text":"Feather Falling VI"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Increases how high you can fall"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"before taking fall damage by "},{"color":"green","text":"6"},{"color":"gray","text":" and"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"reduces fall damage by "},{"color":"green","text":"30%"},{"color":"gray","text":"."}],"italic":false,"text":""}',
+ '{"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Applicable on: "},{"color":"blue","text":"Boots"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":""},{"color":"gray","text":"Apply Cost: "},{"color":"dark_aqua","text":"60 Exp Levels"}],"italic":false,"text":""}',
+ '{"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Use this on an item in an Anvil to"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"apply it!"}],"italic":false,"text":""}',
+ '{"italic":false,"text":""}',
+ '{"extra":[{"bold":true,"color":"blue","text":"RARE"}],"italic":false,"text":""}'
+ ]
+ },
+ count: 1,
+ id: "minecraft:enchanted_book"
+}
diff --git a/src/test/resources/testdata/items/gemstone-gauntlet.snbt b/src/test/resources/testdata/items/gemstone-gauntlet.snbt
new file mode 100644
index 0000000..92ce739
--- /dev/null
+++ b/src/test/resources/testdata/items/gemstone-gauntlet.snbt
@@ -0,0 +1,103 @@
+{
+ components: {
+ "minecraft:attribute_modifiers": {
+ modifiers: [
+ ],
+ show_in_tooltip: 0b
+ },
+ "minecraft:custom_data": {
+ compact_blocks: 287507,
+ donated_museum: 1b,
+ enchantments: {
+ compact: 8,
+ critical: 5,
+ efficiency: 5,
+ experience: 3,
+ first_strike: 4,
+ fortune: 4,
+ giant_killer: 5,
+ pristine: 3,
+ scavenger: 3,
+ sharpness: 5,
+ telekinesis: 1
+ },
+ gems: {
+ AMBER_0: "FINE",
+ AMETHYST_0: "FINE",
+ JADE_0: "FINE",
+ SAPPHIRE_0: "FINE",
+ TOPAZ_0: "FINE"
+ },
+ id: "GEMSTONE_GAUNTLET",
+ modifier: "auspicious",
+ originTag: "QUICK_CRAFTING",
+ timestamp: 1642718160000L,
+ uuid: "af56dd7b-c4b1-4e26-8d09-1854680a93c3"
+ },
+ "minecraft:custom_name": '{"extra":[{"color":"gold","text":"Auspicious Gemstone Gauntlet"}],"italic":false,"text":""}',
+ "minecraft:enchantments": {
+ levels: {
+ "minecraft:efficiency": 5
+ }
+ },
+ "minecraft:hide_additional_tooltip": {
+ },
+ "minecraft:lore": [
+ '{"extra":[{"color":"dark_gray","text":"Breaking Power 8"}],"italic":false,"text":""}',
+ '{"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Damage: "},{"color":"red","text":"+300"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Strength: "},{"color":"red","text":"+50"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Crit Damage: "},{"color":"red","text":"+50%"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Defense: "},{"color":"green","text":"+10 "},{"color":"light_purple","text":"(+10)"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Intelligence: "},{"color":"green","text":"+11 "},{"color":"light_purple","text":"(+11)"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Mining Speed: "},{"color":"green","text":"+886 "},{"color":"blue","text":"(+50) "},{"color":"light_purple","text":"(+36)"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Pristine: "},{"color":"green","text":"+2.7 "},{"color":"light_purple","text":"(+1.2)"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Mining Fortune: "},{"color":"green","text":"+81 "},{"color":"blue","text":"(+16) "},{"color":"light_purple","text":"(+20)"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Mining Wisdom: "},{"color":"green","text":"+8"}],"italic":false,"text":""}',
+ '{"extra":[" ",{"color":"blue","text":"["},{"color":"green","text":"☘"},{"color":"blue","text":"] "},{"color":"blue","text":"["},{"color":"gold","text":"⸕"},{"color":"blue","text":"] "},{"color":"blue","text":"["},{"color":"yellow","text":"✧"},{"color":"blue","text":"] "},{"color":"blue","text":"["},{"color":"aqua","text":"✎"},{"color":"blue","text":"] "},{"color":"blue","text":"["},{"color":"dark_purple","text":"❈"},{"color":"blue","text":"]"}],"italic":false,"text":""}',
+ '{"italic":false,"text":""}',
+ '{"extra":[{"color":"blue","text":"Compact VIII"},{"color":"blue","text":", "},{"color":"blue","text":"Critical V"},{"color":"blue","text":", "},{"color":"blue","text":"Efficiency V"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"blue","text":"Experience III"},{"color":"blue","text":", "},{"color":"blue","text":"First Strike IV"},{"color":"blue","text":", "},{"color":"blue","text":"Fortune IV"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"blue","text":"Giant Killer V"},{"color":"blue","text":", "},{"color":"blue","text":"Prismatic III"},{"color":"blue","text":", "},{"color":"blue","text":"Scavenger III"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"blue","text":"Sharpness V"}],"italic":false,"text":""}',
+ '{"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":""},{"color":"gold","text":"Ability: Reduced To Atoms"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Mobs killed on "},{"color":"aqua","text":"Mining Islands "},{"color":"gray","text":"drop the same"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":""},{"color":"light_purple","text":"Gemstones "},{"color":"gray","text":"as your filled Gemstone Slots."}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":""},{"color":"dark_gray","text":"(2s cooldown)"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":""}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":""},{"color":"gold","text":"Ability: Kinetic"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Killing mobs on "},{"color":"aqua","text":"Mining Islands "},{"color":"gray","text":"reduces your"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":""},{"color":"gold","text":"Forge Timers "},{"color":"gray","text":"by "},{"color":"green","text":"0s"},{"color":"gray","text":"."}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":""},{"color":"dark_gray","text":"(+0.5s per Perfect Gemstone)"}],"italic":false,"text":""}',
+ '{"italic":false,"text":""}',
+ '{"extra":[{"color":"gold","text":"Ability: Mining Speed Boost "},{"bold":true,"color":"yellow","text":"RIGHT CLICK"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Grants "},{"color":"gold","text":"+200% "},{"color":"gold","text":"⸕ Mining Speed "},{"color":"gray","text":"for"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":""},{"color":"green","text":"10s"},{"color":"gray","text":"."}],"italic":false,"text":""}',
+ '{"extra":[{"color":"dark_gray","text":"Cooldown: "},{"color":"green","text":"120s"}],"italic":false,"text":""}',
+ '{"italic":false,"text":""}',
+ '{"extra":[{"color":"blue","text":"Auspicious Bonus"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Grants "},{"color":"gold","text":"+0.8% "},{"color":"gold","text":"☘ Mining Fortune"},{"color":"gray","text":"."}],"italic":false,"text":""}',
+ '{"italic":false,"text":""}',
+ '{"extra":[{"bold":true,"color":"dark_gray","text":"* "},{"color":"dark_gray","text":"Co-op Soulbound "},{"bold":true,"color":"dark_gray","text":"*"}],"italic":false,"text":""}',
+ '{"extra":[{"bold":true,"color":"gold","text":"LEGENDARY GAUNTLET"}],"italic":false,"text":""}'
+ ],
+ "minecraft:profile": {
+ id: [I;
+ -861744046,
+ -959235637,
+ -1231724855,
+ 724395817
+ ],
+ properties: [
+ {
+ name: "textures",
+ signature: "",
+ value: "ewogICJ0aW1lc3RhbXAiIDogMTYxODUyMTY2MzY1NCwKICAicHJvZmlsZUlkIiA6ICIxZDUyMzNkMzg4NjI0YmFmYjAwZTMxNTBhN2FhM2E4OSIsCiAgInByb2ZpbGVOYW1lIiA6ICIwMDAwMDAwMDAwMDAwMDBKIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzdiZjAxYzE5OGY2ZTE2OTY1ZTIzMDIzNWNkMjJhNWE5ZjRhNDBlNDA5NDEyMzQ0Nzg5NDhmZjlhNTZlNTE3NzUiLAogICAgICAibWV0YWRhdGEiIDogewogICAgICAgICJtb2RlbCIgOiAic2xpbSIKICAgICAgfQogICAgfQogIH0KfQ=="
+ }
+ ]
+ }
+ },
+ count: 1,
+ id: "minecraft:player_head"
+}
diff --git a/src/test/resources/testdata/items/hyperion.snbt b/src/test/resources/testdata/items/hyperion.snbt
new file mode 100644
index 0000000..c57d457
--- /dev/null
+++ b/src/test/resources/testdata/items/hyperion.snbt
@@ -0,0 +1,96 @@
+{
+ components: {
+ "minecraft:attribute_modifiers": {
+ modifiers: [
+ ],
+ show_in_tooltip: 0b
+ },
+ "minecraft:custom_data": {
+ ability_scroll: [
+ "IMPLOSION_SCROLL",
+ "WITHER_SHIELD_SCROLL",
+ "SHADOW_WARP_SCROLL"
+ ],
+ art_of_war_count: 1,
+ champion_combat_xp: 1.3556020889209766E7d,
+ donated_museum: 1b,
+ enchantments: {
+ champion: 10,
+ cleave: 5,
+ critical: 6,
+ cubism: 5,
+ ender_slayer: 6,
+ execute: 5,
+ experience: 3,
+ fire_aspect: 2,
+ first_strike: 4,
+ giant_killer: 6,
+ impaling: 3,
+ lethality: 5,
+ looting: 4,
+ luck: 6,
+ scavenger: 4,
+ smite: 7,
+ syphon: 4,
+ thunderlord: 6,
+ ultimate_wise: 5,
+ vampirism: 5,
+ venomous: 5
+ },
+ hot_potato_count: 15,
+ id: "HYPERION",
+ modifier: "heroic",
+ rarity_upgrades: 1,
+ stats_book: 65934,
+ timestamp: 1658091600000L,
+ upgrade_level: 5,
+ uuid: "a45337aa-9eaa-4e6f-aa27-26a42f8eca95"
+ },
+ "minecraft:custom_name": '{"extra":[{"color":"light_purple","text":"Heroic Hyperion "},{"color":"gold","text":"✪✪✪✪✪"}],"italic":false,"text":""}',
+ "minecraft:enchantment_glint_override": 1b,
+ "minecraft:hide_additional_tooltip": {
+ },
+ "minecraft:lore": [
+ '{"extra":[{"color":"gray","text":"Gear Score: "},{"color":"light_purple","text":"1145 "},{"color":"dark_gray","text":"(4271)"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Damage: "},{"color":"red","text":"+355 "},{"color":"yellow","text":"(+30) "},{"color":"dark_gray","text":"(+1,490.37)"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Strength: "},{"color":"red","text":"+250 "},{"color":"yellow","text":"(+30) "},{"color":"gold","text":"[+5] "},{"color":"blue","text":"(+50) "},{"color":"dark_gray","text":"(+1,064.55)"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Crit Damage: "},{"color":"red","text":"+70% "},{"color":"dark_gray","text":"(+317.1%)"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Bonus Attack Speed: "},{"color":"red","text":"+7% "},{"color":"blue","text":"(+7%) "},{"color":"dark_gray","text":"(+10.5%)"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Intelligence: "},{"color":"green","text":"+588 "},{"color":"blue","text":"(+125) "},{"color":"dark_gray","text":"(+2,505.09)"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Ferocity: "},{"color":"green","text":"+33 "},{"color":"dark_gray","text":"(+45)"}],"italic":false,"text":""}',
+ '{"extra":[" ",{"color":"dark_gray","text":"["},{"color":"dark_gray","text":"✎"},{"color":"dark_gray","text":"] "},{"color":"dark_gray","text":"["},{"color":"dark_gray","text":"⚔"},{"color":"dark_gray","text":"]"}],"italic":false,"text":""}',
+ '{"italic":false,"text":""}',
+ '{"extra":[{"bold":true,"color":"light_purple","text":""},{"bold":true,"color":"light_purple","text":"Ultimate Wise V"},{"color":"blue","text":", "},{"color":"blue","text":"Champion X"},{"color":"blue","text":", "},{"color":"blue","text":"Cleave V"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"blue","text":"Critical VI"},{"color":"blue","text":", "},{"color":"blue","text":"Cubism V"},{"color":"blue","text":", "},{"color":"blue","text":"Ender Slayer VI"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"blue","text":"Execute V"},{"color":"blue","text":", "},{"color":"blue","text":"Experience III"},{"color":"blue","text":", "},{"color":"blue","text":"Fire Aspect II"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"blue","text":"First Strike IV"},{"color":"blue","text":", "},{"color":"blue","text":"Giant Killer VI"},{"color":"blue","text":", "},{"color":"blue","text":"Impaling III"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"blue","text":"Lethality V"},{"color":"blue","text":", "},{"color":"blue","text":"Looting IV"},{"color":"blue","text":", "},{"color":"blue","text":"Luck VI"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"blue","text":"Scavenger IV"},{"color":"blue","text":", "},{"color":"blue","text":"Smite VII"},{"color":"blue","text":", "},{"color":"blue","text":"Syphon IV"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"blue","text":"Thunderlord VI"},{"color":"blue","text":", "},{"color":"blue","text":"Vampirism V"},{"color":"blue","text":", "},{"color":"blue","text":"Venomous V"}],"italic":false,"text":""}',
+ '{"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Deals "},{"color":"red","text":"+50% "},{"color":"gray","text":"damage to Withers."}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Grants "},{"color":"red","text":"+1 "},{"color":"red","text":"❁ Damage "},{"color":"gray","text":"and "},{"color":"green","text":"+2 "},{"color":"aqua","text":"✎"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"aqua","text":"Intelligence "},{"color":"gray","text":"per "},{"color":"red","text":"Catacombs "},{"color":"gray","text":"level."}],"italic":false,"text":""}',
+ '{"italic":false,"text":""}',
+ '{"extra":[{"color":"green","text":"Scroll Abilities:"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gold","text":"Ability: Wither Impact "},{"bold":true,"color":"yellow","text":"RIGHT CLICK"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Teleport "},{"color":"green","text":"10 blocks"},{"color":"gray","text":" ahead of you."}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Then implode dealing "},{"color":"red","text":"21,658 "},{"color":"gray","text":"damage"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"to nearby enemies. Also applies the"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"wither shield scroll ability reducing"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"damage taken and granting an"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"absorption shield for "},{"color":"yellow","text":"5 "},{"color":"gray","text":"seconds."}],"italic":false,"text":""}',
+ '{"extra":[{"color":"dark_gray","text":"Mana Cost: "},{"color":"dark_aqua","text":"150"}],"italic":false,"text":""}',
+ '{"italic":false,"text":""}',
+ '{"extra":[{"color":"white","text":"Kills: "},{"color":"gold","text":"65,934"}],"italic":false,"text":""}',
+ '{"italic":false,"text":""}',
+ '{"extra":[{"bold":true,"color":"dark_gray","text":"* "},{"color":"dark_gray","text":"Co-op Soulbound "},{"bold":true,"color":"dark_gray","text":"*"}],"italic":false,"text":""}',
+ '{"extra":[{"bold":true,"color":"light_purple","obfuscated":true,"text":"a"},"",{"bold":false,"extra":[" "],"italic":false,"obfuscated":false,"strikethrough":false,"text":"","underlined":false},{"bold":true,"color":"light_purple","text":"MYTHIC DUNGEON SWORD "},{"bold":true,"color":"light_purple","obfuscated":true,"text":"a"}],"italic":false,"text":""}'
+ ],
+ "minecraft:unbreakable": {
+ show_in_tooltip: 0b
+ }
+ },
+ count: 1,
+ id: "minecraft:iron_sword"
+}
diff --git a/src/test/resources/testdata/items/implosion-belt.snbt b/src/test/resources/testdata/items/implosion-belt.snbt
new file mode 100644
index 0000000..b73542d
--- /dev/null
+++ b/src/test/resources/testdata/items/implosion-belt.snbt
@@ -0,0 +1,105 @@
+{
+ components: {
+ "minecraft:attribute_modifiers": {
+ modifiers: [
+ ],
+ show_in_tooltip: 0b
+ },
+ "minecraft:custom_data": {
+ attributes: {
+ dominance: 1,
+ experience: 1
+ },
+ id: "IMPLOSION_BELT",
+ timestamp: "12/5/22 5:17 PM",
+ uuid: "5c04f47e-7c6c-4ced-96b1-b8f83187b0a5"
+ },
+ "minecraft:custom_name": '{"extra":[{"color":"dark_purple","text":"Implosion Belt"}],"italic":false,"text":""}',
+ "minecraft:hide_additional_tooltip": {
+ },
+ "minecraft:lore": [
+ '{"extra":[{"color":"gray","text":"Defense: "},{"color":"green","text":"+70"}],"italic":false,"text":""}',
+ '{"italic":false,"text":""}',
+ '{"extra":[{"color":"red","text":"Dominance I ✖"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Gain "},{"color":"red","text":"+1.5% "},{"color":"gray","text":"damage when at full health."}],"italic":false,"text":""}',
+ '{"extra":[{"color":"aqua","text":"Experience I"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Gain "},{"color":"green","text":"+10% "},{"color":"gray","text":"more experience orbs"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"from killing mobs."}],"italic":false,"text":""}',
+ '{"italic":false,"text":""}',
+ '{"extra":[{"color":"gold","text":"Ability: Consolidated "}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":""},{"color":"gray","text":"Increases all explosion damage dealt by "},{"color":"green","text":"25%"},{"color":"gray","text":"."}],"italic":false,"text":""}',
+ '{"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":""},{"color":"dark_gray","text":"This item can be reforged!"}],"italic":false,"text":""}',
+ '{"extra":[{"bold":true,"color":"dark_purple","text":"EPIC BELT"}],"italic":false,"text":""}'
+ ],
+ "minecraft:profile": {
+ id: [I;
+ -896440193,
+ -59755884,
+ -1280665573,
+ -1297214643
+ ],
+ properties: [
+ {
+ name: "textures",
+ signature: "",
+ value: "ewogICJ0aW1lc3RhbXAiIDogMTY0MzYwMjI5OTA2MSwKICAicHJvZmlsZUlkIiA6ICI0ZTMwZjUwZTdiYWU0M2YzYWZkMmE3NDUyY2ViZTI5YyIsCiAgInByb2ZpbGVOYW1lIiA6ICJfdG9tYXRvel8iLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZjFkMmIwMzZkZDY2NGJiOTBjOWQ0NDNjMTk5OGZiNTI2Mzk4YWI0ZGRkZWI3OWI4NDAxYjE2YjlhNGQxMGJhMyIsCiAgICAgICJtZXRhZGF0YSIgOiB7CiAgICAgICAgIm1vZGVsIiA6ICJzbGltIgogICAgICB9CiAgICB9CiAgfQp9"
+ }
+ ]
+ }
+ },
+ count: 1,
+ id: "minecraft:player_head"
+}{
+ components: {
+ "minecraft:attribute_modifiers": {
+ modifiers: [
+ ],
+ show_in_tooltip: 0b
+ },
+ "minecraft:custom_data": {
+ attributes: {
+ dominance: 1,
+ experience: 1
+ },
+ id: "IMPLOSION_BELT",
+ timestamp: "12/5/22 5:17 PM",
+ uuid: "5c04f47e-7c6c-4ced-96b1-b8f83187b0a5"
+ },
+ "minecraft:custom_name": '{"extra":[{"color":"dark_purple","text":"Implosion Belt"}],"italic":false,"text":""}',
+ "minecraft:hide_additional_tooltip": {
+ },
+ "minecraft:lore": [
+ '{"extra":[{"color":"gray","text":"Defense: "},{"color":"green","text":"+70"}],"italic":false,"text":""}',
+ '{"italic":false,"text":""}',
+ '{"extra":[{"color":"red","text":"Dominance I ✖"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Gain "},{"color":"red","text":"+1.5% "},{"color":"gray","text":"damage when at full health."}],"italic":false,"text":""}',
+ '{"extra":[{"color":"aqua","text":"Experience I"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Gain "},{"color":"green","text":"+10% "},{"color":"gray","text":"more experience orbs"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"from killing mobs."}],"italic":false,"text":""}',
+ '{"italic":false,"text":""}',
+ '{"extra":[{"color":"gold","text":"Ability: Consolidated "}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":""},{"color":"gray","text":"Increases all explosion damage dealt by "},{"color":"green","text":"25%"},{"color":"gray","text":"."}],"italic":false,"text":""}',
+ '{"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":""},{"color":"dark_gray","text":"This item can be reforged!"}],"italic":false,"text":""}',
+ '{"extra":[{"bold":true,"color":"dark_purple","text":"EPIC BELT"}],"italic":false,"text":""}'
+ ],
+ "minecraft:profile": {
+ id: [I;
+ -896440193,
+ -59755884,
+ -1280665573,
+ -1297214643
+ ],
+ properties: [
+ {
+ name: "textures",
+ signature: "",
+ value: "ewogICJ0aW1lc3RhbXAiIDogMTY0MzYwMjI5OTA2MSwKICAicHJvZmlsZUlkIiA6ICI0ZTMwZjUwZTdiYWU0M2YzYWZkMmE3NDUyY2ViZTI5YyIsCiAgInByb2ZpbGVOYW1lIiA6ICJfdG9tYXRvel8iLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZjFkMmIwMzZkZDY2NGJiOTBjOWQ0NDNjMTk5OGZiNTI2Mzk4YWI0ZGRkZWI3OWI4NDAxYjE2YjlhNGQxMGJhMyIsCiAgICAgICJtZXRhZGF0YSIgOiB7CiAgICAgICAgIm1vZGVsIiA6ICJzbGltIgogICAgICB9CiAgICB9CiAgfQp9"
+ }
+ ]
+ }
+ },
+ count: 1,
+ id: "minecraft:player_head"
+ }
diff --git a/src/test/resources/testdata/items/pets/lion-item.snbt b/src/test/resources/testdata/items/pets/lion-item.snbt
new file mode 100644
index 0000000..6e92685
--- /dev/null
+++ b/src/test/resources/testdata/items/pets/lion-item.snbt
@@ -0,0 +1,62 @@
+{
+ components: {
+ "minecraft:attribute_modifiers": {
+ modifiers: [
+ ],
+ show_in_tooltip: 0b
+ },
+ "minecraft:custom_data": {
+ id: "PET",
+ petInfo: '{"type":"LION","active":false,"exp":0.0,"tier":"LEGENDARY","hideInfo":false,"candyUsed":0,"uuid":"c7f57149-458e-4fde-a9bc-fcc14932310a","uniqueId":"d668f085-26a6-48fe-b75b-c7b8227b5ac8","hideRightClick":false,"noMove":false}',
+ timestamp: 1732719293542L,
+ uuid: "c7f57149-458e-4fde-a9bc-fcc14932310a"
+ },
+ "minecraft:custom_name": '{"extra":[{"color":"gray","text":"[Lvl 1] "},{"color":"gold","text":"Lion"}],"italic":false,"text":""}',
+ "minecraft:hide_additional_tooltip": {
+ },
+ "minecraft:lore": [
+ '{"extra":[{"color":"dark_gray","text":"Foraging Pet"}],"italic":false,"text":""}',
+ '{"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Speed: "},{"color":"green","text":"+0.25"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Strength: "},{"color":"red","text":"+0.5"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Ferocity: "},{"color":"green","text":"+0.05"}],"italic":false,"text":""}',
+ '{"italic":false,"text":""}',
+ '{"extra":[{"color":"gold","text":"Primal Force"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":""},{"color":"gray","text":"Adds "},{"color":"red","text":"+0.2 "},{"color":"red","text":"❁ Damage "},{"color":"gray","text":"and "},{"color":"red","text":"+0.2 "},{"color":"red","text":"❁"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"red","text":"Strength "},{"color":"gray","text":"to your weapons."}],"italic":false,"text":""}',
+ '{"italic":false,"text":""}',
+ '{"extra":[{"color":"gold","text":"First Pounce"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":""},{"color":"gray","text":"First Strike"},{"color":"gray","text":", Triple-Strike"},{"color":"gray","text":", and"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":""},{"bold":true,"color":"light_purple","text":"Combo "},{"color":"gray","text":"are "},{"color":"green","text":"1% "},{"color":"gray","text":"more effective."}],"italic":false,"text":""}',
+ '{"italic":false,"text":""}',
+ '{"extra":[{"color":"gold","text":"King of the Jungle"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":""},{"color":"gray","text":"Deal "},{"color":"red","text":"+1.5% "},{"color":"red","text":"❁ Damage "},{"color":"gray","text":"against mobs"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"that have attacked you."}],"italic":false,"text":""}',
+ '{"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Progress to Level 2: "},{"color":"yellow","text":"0%"}],"italic":false,"text":""}',
+ '{"extra":[{"bold":true,"color":"white","strikethrough":true,"text":" "},"",{"bold":false,"extra":[" "],"italic":false,"obfuscated":false,"strikethrough":false,"text":"","underlined":false},{"color":"yellow","text":"0"},{"color":"gold","text":"/"},{"color":"yellow","text":"660"}],"italic":false,"text":""}',
+ '{"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":""},{"color":"yellow","text":"Right-click to add this pet to your"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"yellow","text":"pet menu!"}],"italic":false,"text":""}',
+ '{"italic":false,"text":""}',
+ '{"extra":[{"bold":true,"color":"gold","text":"LEGENDARY"}],"italic":false,"text":""}'
+ ],
+ "minecraft:profile": {
+ id: [I;
+ 100496274,
+ -783272221,
+ -1215808471,
+ -1437268588
+ ],
+ properties: [
+ {
+ name: "textures",
+ signature: "",
+ value: "eyJ0aW1lc3RhbXAiOjE1MDMzMTY0Njg1ODAsInByb2ZpbGVJZCI6ImUzYjQ0NWM4NDdmNTQ4ZmI4YzhmYTNmMWY3ZWZiYThlIiwicHJvZmlsZU5hbWUiOiJNaW5pRGlnZ2VyVGVzdCIsInNpZ25hdHVyZVJlcXVpcmVkIjp0cnVlLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMzhmZjQ3M2JkNTJiNGRiMmMwNmYxYWM4N2ZlMTM2N2JjZTc1NzRmYWMzMzBmZmFjNzk1NjIyOWY4MmVmYmExIn19fQ=="
+ }
+ ]
+ }
+ },
+ count: 1,
+ id: "minecraft:player_head"
+}
diff --git a/src/test/resources/testdata/items/pets/mithril-golem-not-selected.snbt b/src/test/resources/testdata/items/pets/mithril-golem-not-selected.snbt
new file mode 100644
index 0000000..c0ef585
--- /dev/null
+++ b/src/test/resources/testdata/items/pets/mithril-golem-not-selected.snbt
@@ -0,0 +1,52 @@
+{
+ components: {
+ "minecraft:custom_data": {
+ id: "PET",
+ petInfo: '{"type":"MITHRIL_GOLEM","active":false,"exp":9232689.767527012,"tier":"LEGENDARY","hideInfo":false,"candyUsed":0,"uuid":"2ebbdaff-c9b6-4fe1-8b87-44905eb5867d","uniqueId":"26be73f1-2955-4003-9e6a-72848cea8e08","hideRightClick":false,"noMove":false}',
+ uuid: "2ebbdaff-c9b6-4fe1-8b87-44905eb5867d"
+ },
+ "minecraft:custom_name": '{"extra":[{"color":"gray","text":"[Lvl 86] "},{"color":"gold","text":"Mithril Golem"}],"italic":false,"text":""}',
+ "minecraft:lore": [
+ '{"extra":[{"color":"dark_gray","text":"Mining Pet"}],"italic":false,"text":""}',
+ '{"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"True Defense: "},{"color":"green","text":"+43"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Mining Fortune: "},{"color":"green","text":"+21.5"}],"italic":false,"text":""}',
+ '{"italic":false,"text":""}',
+ '{"extra":[{"color":"gold","text":"Mithril Affinity"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":""},{"color":"gray","text":"Grants "},{"color":"gold","text":"+172⸕ Mining Speed "},{"color":"gray","text":"when"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"mining "},{"color":"dark_green","text":"Mithril"},{"color":"gray","text":"."}],"italic":false,"text":""}',
+ '{"italic":false,"text":""}',
+ '{"extra":[{"color":"gold","text":"Subterranean Battler"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":""},{"color":"gray","text":"Grants "},{"color":"green","text":"+17.2% "},{"color":"gray","text":"of most combat stats"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"while on "},{"color":"aqua","text":"Mining Islands"},{"color":"gray","text":"."}],"italic":false,"text":""}',
+ '{"italic":false,"text":""}',
+ '{"extra":[{"color":"gold","text":"The Smell Of Powder"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":""},{"color":"gray","text":"Grants "},{"color":"dark_green","text":"+17.2% ᠅ Mithril Powder "},{"color":"gray","text":"from"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"all sources."}],"italic":false,"text":""}',
+ '{"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Progress to Level 87: "},{"color":"yellow","text":"89%"}],"italic":false,"text":""}',
+ '{"extra":[{"bold":true,"color":"dark_green","strikethrough":true,"text":" "},{"bold":true,"color":"white","strikethrough":true,"text":" "},"",{"bold":false,"extra":[" "],"italic":false,"obfuscated":false,"strikethrough":false,"text":"","underlined":false},{"color":"yellow","text":"593,259.8"},{"color":"gold","text":"/"},{"color":"yellow","text":"666.7k"}],"italic":false,"text":""}',
+ '{"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":""},{"color":"yellow","text":"Left-click to summon!"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":""},{"color":"yellow","text":"Shift Left-click to toggle as favorite!"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"yellow","text":"Right-click to convert to an item!"}],"italic":false,"text":""}'
+ ],
+ "minecraft:profile": {
+ id: [I;
+ 972784821,
+ 1926443553,
+ -1284265423,
+ 1586851459
+ ],
+ properties: [
+ {
+ name: "textures",
+ signature: "",
+ value: "ewogICJ0aW1lc3RhbXAiIDogMTYxMDY0OTEwODI4NCwKICAicHJvZmlsZUlkIiA6ICI2ZmU4OTUxZDVhY2M0NDc3OWI2ZmYxMmU3YzFlOTQ2MyIsCiAgInByb2ZpbGVOYW1lIiA6ICJlcGhlbXJhIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlL2MxYjJkZmU4ZWQ1ZGZmYzViMTY4N2JjMWMyNDljMzlkZTJkOGE2YzNkOTAzMDVjOTVmNmQxYTFhMzMwYTBiMSIKICAgIH0KICB9Cn0="
+ }
+ ]
+ }
+ },
+ count: 1,
+ id: "minecraft:player_head"
+}
diff --git a/src/test/resources/testdata/items/pets/rabbit-selected.snbt b/src/test/resources/testdata/items/pets/rabbit-selected.snbt
new file mode 100644
index 0000000..48a6f6f
--- /dev/null
+++ b/src/test/resources/testdata/items/pets/rabbit-selected.snbt
@@ -0,0 +1,60 @@
+{
+ components: {
+ "minecraft:custom_data": {
+ id: "PET",
+ petInfo: '{"type":"RABBIT","active":true,"exp":3.429132435816353E7,"tier":"MYTHIC","hideInfo":false,"heldItem":"YELLOW_BANDANA","candyUsed":0,"uuid":"30a05aae-2ccd-41c0-a686-5bce0df15e2d","uniqueId":"71a8949b-7444-4ead-9464-999fe549e703","hideRightClick":false,"noMove":false}',
+ uuid: "30a05aae-2ccd-41c0-a686-5bce0df15e2d"
+ },
+ "minecraft:custom_name": '{"extra":[{"color":"gray","text":"[Lvl 100] "},{"color":"light_purple","text":"Rabbit"}],"italic":false,"text":""}',
+ "minecraft:lore": [
+ '{"extra":[{"color":"dark_gray","text":"Farming Pet"}],"italic":false,"text":""}',
+ '{"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Health: "},{"color":"green","text":"+100"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Speed: "},{"color":"green","text":"+20"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Farming Fortune: "},{"color":"green","text":"+30"}],"italic":false,"text":""}',
+ '{"italic":false,"text":""}',
+ '{"extra":[{"color":"gold","text":"Happy Feet"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":""},{"color":"gray","text":"Jump potions also give "},{"color":"green","text":"+50 "},{"color":"gray","text":"speed"}],"italic":false,"text":""}',
+ '{"italic":false,"text":""}',
+ '{"extra":[{"color":"gold","text":"Farming Wisdom Boost"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":""},{"color":"gray","text":""},{"color":"gray","text":"Grants "},{"color":"dark_aqua","text":"+30☯ Farming Wisdom"},{"color":"gray","text":"."}],"italic":false,"text":""}',
+ '{"italic":false,"text":""}',
+ '{"extra":[{"color":"gold","text":"Efficient Farming"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":""},{"color":"gray","text":"Farming minions work "},{"color":"green","text":"30% "},{"color":"gray","text":"faster"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"while on your island"}],"italic":false,"text":""}',
+ '{"italic":false,"text":""}',
+ '{"extra":[{"color":"gold","text":"Chocolate Injections"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":""},{"color":"gray","text":"Increases "},{"color":"gold","text":"Chocolate Factory"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gold","text":""},{"color":"gray","text":"production by "},{"color":"green","text":"+0.05x"},{"color":"gray","text":". Duplicate"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":""},{"color":"green","text":"Chocolate Rabbits "},{"color":"gray","text":"that you find"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"grant "},{"color":"gold","text":"+33% Chocolate"},{"color":"gray","text":"."}],"italic":false,"text":""}',
+ '{"italic":false,"text":""}',
+ '{"extra":[{"color":"gold","text":"Held Item: "},{"color":"blue","text":"Yellow Bandana"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Grants "},{"color":"gold","text":"+30☘ Farming Fortune"},{"color":"gray","text":"."}],"italic":false,"text":""}',
+ '{"italic":false,"text":""}',
+ '{"extra":[{"bold":true,"color":"aqua","text":"MAX LEVEL"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"dark_gray","text":"▸ 34,291,324 XP"}],"italic":false,"text":""}',
+ '{"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":""},{"color":"red","text":"Click to despawn!"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":""},{"color":"yellow","text":"Shift Left-click to toggle as favorite!"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"yellow","text":"Right-click to convert to an item!"}],"italic":false,"text":""}'
+ ],
+ "minecraft:profile": {
+ id: [I;
+ -363097213,
+ -1822870885,
+ -1670199288,
+ -1615169037
+ ],
+ properties: [
+ {
+ name: "textures",
+ signature: "",
+ value: "ewogICJ0aW1lc3RhbXAiIDogMTYwNDU4NjI3ODg1NywKICAicHJvZmlsZUlkIiA6ICI0ZTMwZjUwZTdiYWU0M2YzYWZkMmE3NDUyY2ViZTI5YyIsCiAgInByb2ZpbGVOYW1lIiA6ICJfdG9tYXRvel8iLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNjM0Mzg1NTVlODk5YmQ5YTA1MWE5NWRiZWE0OWViMmVjZmE1MmE2OWRiYmE4OTk4ZjM2NzM4MTllMjc3ZmRmNSIKICAgIH0KICB9Cn0="
+ }
+ ]
+ }
+ },
+ count: 1,
+ id: "minecraft:player_head"
+}
diff --git a/src/texturePacks/README.md b/src/texturePacks/README.md
new file mode 100644
index 0000000..8932817
--- /dev/null
+++ b/src/texturePacks/README.md
@@ -0,0 +1,13 @@
+<!--
+SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe>
+
+SPDX-License-Identifier: CC0-1.0
+-->
+
+# Technical Notes for the texture pack implementation
+
+Relevant classes:
+
+`ItemModelManager` can be used to select an `ItemModel`. This is done from the `ITEM_MODEL` component which is defaulted by the `Item` class.
+
+The list of available `ItemModel`s (as in `Identifier` -> `ItemModel` maps) is loaded by `BakedModelManager`. To this end, item models in particular are loaded from `ItemAssetsLoader#load`. Those `ItemAssets` are found in `assets/<ns>/items/` directly (not in the model folder) and can be used to select other models, similar to how predicates used to work
diff --git a/src/main/kotlin/features/texturepack/CustomBlockTextures.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomBlockTextures.kt
index 2f7f084..dc3b109 100644
--- a/src/main/kotlin/features/texturepack/CustomBlockTextures.kt
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomBlockTextures.kt
@@ -30,7 +30,6 @@ 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
@@ -46,9 +45,9 @@ import moe.nea.firmament.util.json.SingletonSerializableList
object CustomBlockTextures {
@Serializable
data class CustomBlockOverride(
- val modes: @Serializable(SingletonSerializableList::class) List<String>,
- val area: List<Area>? = null,
- val replacements: Map<Identifier, Replacement>,
+ val modes: @Serializable(SingletonSerializableList::class) List<String>,
+ val area: List<Area>? = null,
+ val replacements: Map<Identifier, Replacement>,
)
@Serializable(with = Replacement.Serializer::class)
@@ -135,8 +134,8 @@ object CustomBlockTextures {
)
data class BlockReplacement(
- val checks: List<Area>?,
- val replacement: Replacement,
+ val checks: List<Area>?,
+ val replacement: Replacement,
) {
val roughCheck by lazy(LazyThreadSafetyMode.NONE) {
if (checks == null || checks.size < 3) return@lazy null
@@ -238,15 +237,6 @@ object CustomBlockTextures {
{ 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, it.id) }
- }
-
private fun prepare(manager: ResourceManager): BakedReplacements {
val resources = manager.findResources("overrides/blocks") {
it.namespace == "firmskyblock" && it.path.endsWith(".json")
diff --git a/src/main/kotlin/features/texturepack/CustomGlobalArmorOverrides.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomGlobalArmorOverrides.kt
index 54e9e11..aafc85a 100644
--- a/src/main/kotlin/features/texturepack/CustomGlobalArmorOverrides.kt
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomGlobalArmorOverrides.kt
@@ -8,8 +8,12 @@ import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import kotlinx.serialization.UseSerializers
+import net.minecraft.client.render.entity.equipment.EquipmentModel
+import net.minecraft.component.type.EquippableComponent
+import net.minecraft.entity.EquipmentSlot
import net.minecraft.item.ItemStack
-import net.minecraft.item.equipment.EquipmentModel
+import net.minecraft.item.equipment.EquipmentAssetKeys
+import net.minecraft.registry.RegistryKey
import net.minecraft.resource.ResourceManager
import net.minecraft.resource.SinglePreparationResourceReloader
import net.minecraft.util.Identifier
@@ -20,6 +24,7 @@ import moe.nea.firmament.events.FinalizeResourceManagerEvent
import moe.nea.firmament.features.texturepack.CustomGlobalTextures.logger
import moe.nea.firmament.util.IdentifierSerializer
import moe.nea.firmament.util.collections.WeakCache
+import moe.nea.firmament.util.intoOptional
import moe.nea.firmament.util.skyBlockId
object CustomGlobalArmorOverrides {
@@ -33,9 +38,9 @@ object CustomGlobalArmorOverrides {
) {
@Transient
lateinit var modelIdentifier: Identifier
- fun bake() {
+ fun bake(manager: ResourceManager) {
modelIdentifier = bakeModel(model, layers)
- overrides.forEach { it.bake() }
+ overrides.forEach { it.bake(manager) }
}
init {
@@ -64,22 +69,33 @@ object CustomGlobalArmorOverrides {
@Transient
lateinit var modelIdentifier: Identifier
- fun bake() {
+ fun bake(manager: ResourceManager) {
modelIdentifier = bakeModel(model, layers)
}
}
- val overrideCache = WeakCache.memoize<ItemStack, Optional<Identifier>>("ArmorOverrides") { stack ->
- val id = stack.skyBlockId ?: return@memoize Optional.empty()
- val override = overrides[id.neuItem] ?: return@memoize Optional.empty()
- for (suboverride in override.overrides) {
- if (suboverride.predicate.test(stack)) {
- return@memoize Optional.of(suboverride.modelIdentifier)
+ private fun resolveComponent(slot: EquipmentSlot, model: Identifier): EquippableComponent {
+ return EquippableComponent(
+ slot,
+ null,
+ Optional.of(RegistryKey.of(EquipmentAssetKeys.REGISTRY_KEY, model)),
+ Optional.empty(),
+ Optional.empty(), false, false, false
+ )
+ }
+
+ val overrideCache =
+ WeakCache.memoize<ItemStack, EquipmentSlot, Optional<EquippableComponent>>("ArmorOverrides") { stack, slot ->
+ val id = stack.skyBlockId ?: return@memoize Optional.empty()
+ val override = overrides[id.neuItem] ?: return@memoize Optional.empty()
+ for (suboverride in override.overrides) {
+ if (suboverride.predicate.test(stack)) {
+ return@memoize resolveComponent(slot, suboverride.modelIdentifier).intoOptional()
+ }
}
+ return@memoize resolveComponent(slot, override.modelIdentifier).intoOptional()
}
- return@memoize Optional.of(override.modelIdentifier)
- }
var overrides: Map<String, ArmorOverride> = mapOf()
private var bakedOverrides: MutableMap<Identifier, EquipmentModel> = mutableMapOf()
@@ -91,7 +107,7 @@ object CustomGlobalArmorOverrides {
return model
} else if (layers != null) {
val idNumber = sentinelFirmRunning.incrementAndGet()
- val identifier = Identifier.of("firmament:sentinel/$idNumber")
+ val identifier = Identifier.of("firmament:sentinel/armor/$idNumber")
val equipmentLayers = layers.map {
EquipmentModel.Layer(
it.identifier, if (it.tint) {
@@ -129,26 +145,28 @@ object CustomGlobalArmorOverrides {
null
}
}
+ bakedOverrides.clear()
val associatedMap = overrides.flatMap { obj -> obj.itemIds.map { it to obj } }
.toMap()
+ associatedMap.forEach { it.value.bake(manager) }
return associatedMap
}
override fun apply(prepared: Map<String, ArmorOverride>, manager: ResourceManager, profiler: Profiler) {
- bakedOverrides.clear()
- prepared.forEach { it.value.bake() }
overrides = prepared
}
})
}
@JvmStatic
- fun overrideArmor(itemStack: ItemStack): Optional<Identifier> {
- return overrideCache.invoke(itemStack)
+ fun overrideArmor(itemStack: ItemStack, slot: EquipmentSlot): Optional<EquippableComponent> {
+ if (!CustomSkyBlockTextures.TConfig.enableArmorOverrides) return Optional.empty()
+ return overrideCache.invoke(itemStack, slot)
}
@JvmStatic
fun overrideArmorLayer(id: Identifier): EquipmentModel? {
+ if (!CustomSkyBlockTextures.TConfig.enableArmorOverrides) return null
return bakedOverrides[id]
}
diff --git a/src/main/kotlin/features/texturepack/CustomGlobalTextures.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomGlobalTextures.kt
index 9ad8bc1..20f1fb6 100644
--- a/src/main/kotlin/features/texturepack/CustomGlobalTextures.kt
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomGlobalTextures.kt
@@ -1,18 +1,14 @@
-@file:UseSerializers(IdentifierSerializer::class, CustomModelOverrideParser.FirmamentRootPredicateSerializer::class)
+@file:UseSerializers(IdentifierSerializer::class, FirmamentRootPredicateSerializer::class)
package moe.nea.firmament.features.texturepack
-import java.util.Optional
import java.util.concurrent.CompletableFuture
import org.slf4j.LoggerFactory
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
@@ -20,16 +16,15 @@ 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.CustomItemModelEvent
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.ErrorUtil
import moe.nea.firmament.util.IdentifierSerializer
import moe.nea.firmament.util.MC
-import moe.nea.firmament.util.collections.WeakCache
-import moe.nea.firmament.util.intoOptional
import moe.nea.firmament.util.json.SingletonSerializableList
import moe.nea.firmament.util.runNull
@@ -73,15 +68,6 @@ object CustomGlobalTextures : SinglePreparationResourceReloader<CustomGlobalText
}, 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<CustomGuiTextureOverride> = CompletableFuture.completedFuture(
CustomGuiTextureOverride(listOf()))
@@ -91,7 +77,7 @@ object CustomGlobalTextures : SinglePreparationResourceReloader<CustomGlobalText
}
override fun apply(prepared: CustomGuiTextureOverride, manager: ResourceManager?, profiler: Profiler?) {
- this.guiClassOverrides = prepared
+ guiClassOverrides = prepared
}
val logger = LoggerFactory.getLogger(CustomGlobalTextures::class.java)
@@ -100,7 +86,7 @@ object CustomGlobalTextures : SinglePreparationResourceReloader<CustomGlobalText
manager.findResources("overrides/item") { it.namespace == "firmskyblock" && it.path.endsWith(".json") }
.mapNotNull {
Firmament.tryDecodeJsonFromStream<GlobalItemOverride>(it.value.inputStream).getOrElse { ex ->
- logger.error("Failed to load global item override at ${it.key}", ex)
+ ErrorUtil.softError("Failed to load global item override at ${it.key}", ex)
null
}
}
@@ -114,12 +100,12 @@ object CustomGlobalTextures : SinglePreparationResourceReloader<CustomGlobalText
manager.getResource(Identifier.of(key.namespace, "filters/screen/${key.path}.json"))
.getOrNull()
?: return@mapNotNull runNull {
- logger.error("Failed to locate screen filter at $key")
+ ErrorUtil.softError("Failed to locate screen filter at $key used by ${it.value.map { it.first }}")
}
val screenFilter =
Firmament.tryDecodeJsonFromStream<ScreenFilter>(guiClassResource.inputStream)
.getOrElse { ex ->
- logger.error("Failed to load screen filter at $key", ex)
+ ErrorUtil.softError("Failed to load screen filter at $key used by ${it.value.map { it.first }}", ex)
return@mapNotNull null
}
ItemOverrideCollection(screenFilter, it.value.map { it.second })
@@ -139,25 +125,19 @@ object CustomGlobalTextures : SinglePreparationResourceReloader<CustomGlobalText
.filterTo(mutableSetOf()) { it.screenFilter.title.matches(newTitle) }
}
- val overrideCache =
- WeakCache.memoize<ItemStack, ItemModels, Optional<BakedModel>>("CustomGlobalTextureModelOverrides") { stack, models ->
- matchingOverrides
- .firstNotNullOfOrNull {
- it.overrides
- .asSequence()
- .filter { it.predicate.test(stack) }
- .map { models.getModel(it.model) }
- .firstOrNull()
- }
- .intoOptional()
- }
-
- @JvmStatic
- fun replaceGlobalModel(
- models: ItemModels,
- stack: ItemStack,
- ): BakedModel? {
- return overrideCache.invoke(stack, models).getOrNull()
+ @Subscribe
+ fun replaceGlobalModel(event: CustomItemModelEvent) {
+ val override = matchingOverrides
+ .firstNotNullOfOrNull {
+ it.overrides
+ .asSequence()
+ .filter { it.predicate.test(event.itemStack) }
+ .map { it.model }
+ .firstOrNull()
+ }
+
+ if (override != null)
+ event.overrideIfExists(override)
}
diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomModelOverrideParser.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomModelOverrideParser.kt
new file mode 100644
index 0000000..4529d1d
--- /dev/null
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomModelOverrideParser.kt
@@ -0,0 +1,115 @@
+package moe.nea.firmament.features.texturepack
+
+import com.google.gson.JsonObject
+import com.mojang.datafixers.util.Pair
+import com.mojang.serialization.Codec
+import com.mojang.serialization.DataResult
+import com.mojang.serialization.Decoder
+import com.mojang.serialization.DynamicOps
+import com.mojang.serialization.Encoder
+import net.minecraft.client.render.item.model.ItemModelTypes
+import net.minecraft.item.ItemStack
+import net.minecraft.util.Identifier
+import moe.nea.firmament.Firmament
+import moe.nea.firmament.annotations.Subscribe
+import moe.nea.firmament.events.FinalizeResourceManagerEvent
+import moe.nea.firmament.features.texturepack.predicates.AndPredicate
+import moe.nea.firmament.features.texturepack.predicates.CastPredicate
+import moe.nea.firmament.features.texturepack.predicates.DisplayNamePredicate
+import moe.nea.firmament.features.texturepack.predicates.ExtraAttributesPredicate
+import moe.nea.firmament.features.texturepack.predicates.ItemPredicate
+import moe.nea.firmament.features.texturepack.predicates.LorePredicate
+import moe.nea.firmament.features.texturepack.predicates.NotPredicate
+import moe.nea.firmament.features.texturepack.predicates.OrPredicate
+import moe.nea.firmament.features.texturepack.predicates.PetPredicate
+import moe.nea.firmament.features.texturepack.predicates.PullingPredicate
+import moe.nea.firmament.util.json.KJsonOps
+
+object CustomModelOverrideParser {
+
+ val LEGACY_CODEC: Codec<FirmamentModelPredicate> =
+ Codec.of(
+ Encoder.error("cannot encode legacy firmament model predicates"),
+ object : Decoder<FirmamentModelPredicate> {
+ override fun <T : Any?> decode(
+ ops: DynamicOps<T>,
+ input: T
+ ): DataResult<Pair<FirmamentModelPredicate, T>> {
+ try {
+ val pred = Firmament.json.decodeFromJsonElement(
+ FirmamentRootPredicateSerializer,
+ ops.convertTo(KJsonOps.INSTANCE, input))
+ return DataResult.success(Pair.of(pred, ops.empty()))
+ } catch (ex: Exception) {
+ return DataResult.error { "Could not deserialize ${ex.message}" }
+ }
+ }
+ }
+ )
+
+ val predicateParsers = mutableMapOf<Identifier, FirmamentModelPredicateParser>()
+
+
+ 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<FirmamentModelPredicate> {
+ if (predicates == null) return neverPredicate
+ val parsedPredicates = mutableListOf<FirmamentModelPredicate>()
+ for (predicateName in predicates.keySet()) {
+ if (predicateName == "cast") { // 1.21.4
+ parsedPredicates.add(CastPredicate.Parser.parse(predicates[predicateName]) ?: return neverPredicate)
+ }
+ if (predicateName == "pull") {
+ parsedPredicates.add(PullingPredicate.Parser.parse(predicates[predicateName]) ?: return neverPredicate)
+ }
+ if (predicateName == "pulling") {
+ parsedPredicates.add(PullingPredicate.AnyPulling)
+ }
+ 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<FirmamentModelPredicate>? {
+ val predicates = (jsonObject["predicate"] as? JsonObject) ?: return null
+ val parsedPredicates = parsePredicates(predicates)
+ if (parsedPredicates.isEmpty())
+ return null
+ return parsedPredicates.toTypedArray()
+ }
+
+ @Subscribe
+ fun finalizeResources(event: FinalizeResourceManagerEvent) {
+ ItemModelTypes.ID_MAPPER.put(
+ Firmament.identifier("predicates/legacy"),
+ PredicateModel.Unbaked.CODEC
+ )
+ }
+
+}
diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomSkyBlockTextures.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomSkyBlockTextures.kt
new file mode 100644
index 0000000..bef52d2
--- /dev/null
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomSkyBlockTextures.kt
@@ -0,0 +1,118 @@
+package moe.nea.firmament.features.texturepack
+
+import com.mojang.authlib.minecraft.MinecraftProfileTexture
+import com.mojang.authlib.properties.Property
+import java.util.Optional
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable
+import kotlin.jvm.optionals.getOrNull
+import net.minecraft.block.SkullBlock
+import net.minecraft.client.MinecraftClient
+import net.minecraft.client.render.RenderLayer
+import net.minecraft.component.type.ProfileComponent
+import net.minecraft.util.Identifier
+import moe.nea.firmament.annotations.Subscribe
+import moe.nea.firmament.events.CustomItemModelEvent
+import moe.nea.firmament.events.FinalizeResourceManagerEvent
+import moe.nea.firmament.events.TickEvent
+import moe.nea.firmament.features.FirmamentFeature
+import moe.nea.firmament.features.debug.PowerUserTools
+import moe.nea.firmament.gui.config.ManagedConfig
+import moe.nea.firmament.util.collections.WeakCache
+import moe.nea.firmament.util.mc.decodeProfileTextureProperty
+import moe.nea.firmament.util.skyBlockId
+
+object CustomSkyBlockTextures : FirmamentFeature {
+ override val identifier: String
+ get() = "custom-skyblock-textures"
+
+ object TConfig : ManagedConfig(identifier, Category.INTEGRATIONS) { // TODO: should this be its own thing?
+ val enabled by toggle("enabled") { true }
+ val skullsEnabled by toggle("skulls-enabled") { true }
+ val cacheForever by toggle("cache-forever") { true }
+ val cacheDuration by integer("cache-duration", 0, 100) { 1 }
+ val enableModelOverrides by toggle("model-overrides") { true }
+ val enableArmorOverrides by toggle("armor-overrides") { true }
+ val enableBlockOverrides by toggle("block-overrides") { true }
+ val enableLegacyMinecraftCompat by toggle("legacy-minecraft-path-support") { true }
+ val enableLegacyCIT by toggle("legacy-cit") { true }
+ val allowRecoloringUiText by toggle("recolor-text") { true }
+ }
+
+ override val config: ManagedConfig
+ get() = TConfig
+
+ val allItemCaches by lazy {
+ listOf(
+ skullTextureCache.cache,
+ CustomItemModelEvent.cache.cache,
+ CustomGlobalArmorOverrides.overrideCache.cache
+ )
+ }
+
+ init {
+ PowerUserTools.getSkullId = ::getSkullTexture
+ }
+
+ fun clearAllCaches() {
+ allItemCaches.forEach(WeakCache<*, *, *>::clear)
+ }
+
+ @Subscribe
+ fun onTick(it: TickEvent) {
+ if (TConfig.cacheForever) return
+ if (TConfig.cacheDuration < 1 || it.tickCount % TConfig.cacheDuration == 0) {
+ clearAllCaches()
+ }
+ }
+
+ @Subscribe
+ fun onStart(event: FinalizeResourceManagerEvent) {
+ event.registerOnApply("Clear firmament CIT caches") {
+ clearAllCaches()
+ }
+ }
+
+ @Subscribe
+ fun onCustomModelId(it: CustomItemModelEvent) {
+ if (!TConfig.enabled) return
+ val id = it.itemStack.skyBlockId ?: return
+ it.overrideIfEmpty(Identifier.of("firmskyblock", id.identifier.path))
+ }
+
+ private val skullTextureCache =
+ WeakCache.memoize<ProfileComponent, Optional<Identifier>>("SkullTextureCache") { component ->
+ val id = getSkullTexture(component) ?: return@memoize Optional.empty()
+ if (!MinecraftClient.getInstance().resourceManager.getResource(id).isPresent) {
+ return@memoize Optional.empty()
+ }
+ return@memoize Optional.of(id)
+ }
+
+ 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<RenderLayer>
+ ) {
+ if (type != SkullBlock.Type.PLAYER) return
+ if (!TConfig.skullsEnabled) return
+ if (component == null) return
+
+ val n = skullTextureCache.invoke(component).getOrNull() ?: return
+ cir.returnValue = RenderLayer.getEntityTranslucent(n)
+ }
+}
diff --git a/src/main/kotlin/features/texturepack/CustomTextColors.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomTextColors.kt
index 4ca1796..4ca1796 100644
--- a/src/main/kotlin/features/texturepack/CustomTextColors.kt
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomTextColors.kt
diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/FirmamentModelPredicate.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/FirmamentModelPredicate.kt
new file mode 100644
index 0000000..6cef4ca
--- /dev/null
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/FirmamentModelPredicate.kt
@@ -0,0 +1,9 @@
+package moe.nea.firmament.features.texturepack
+
+import net.minecraft.entity.LivingEntity
+import net.minecraft.item.ItemStack
+
+interface FirmamentModelPredicate {
+ fun test(stack: ItemStack, holder: LivingEntity?): Boolean = test(stack)
+ fun test(stack: ItemStack): Boolean = test(stack, null)
+}
diff --git a/src/main/kotlin/features/texturepack/FirmamentModelPredicateParser.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/FirmamentModelPredicateParser.kt
index 3ed0c67..3ed0c67 100644
--- a/src/main/kotlin/features/texturepack/FirmamentModelPredicateParser.kt
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/FirmamentModelPredicateParser.kt
diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/FirmamentRootPredicateSerializer.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/FirmamentRootPredicateSerializer.kt
new file mode 100644
index 0000000..0b8ae8e
--- /dev/null
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/FirmamentRootPredicateSerializer.kt
@@ -0,0 +1,23 @@
+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 moe.nea.firmament.features.texturepack.predicates.AndPredicate
+
+object FirmamentRootPredicateSerializer : KSerializer<FirmamentModelPredicate> {
+ 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(CustomModelOverrideParser.parsePredicates(json).toTypedArray())
+ }
+
+ override fun serialize(encoder: Encoder, value: FirmamentModelPredicate) {
+ TODO("Cannot serialize firmament predicates")
+ }
+}
diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/PredicateModel.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/PredicateModel.kt
new file mode 100644
index 0000000..0edad4c
--- /dev/null
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/PredicateModel.kt
@@ -0,0 +1,106 @@
+package moe.nea.firmament.features.texturepack
+
+import com.google.gson.JsonObject
+import com.mojang.serialization.Codec
+import com.mojang.serialization.MapCodec
+import com.mojang.serialization.codecs.RecordCodecBuilder
+import net.minecraft.client.item.ItemModelManager
+import net.minecraft.client.render.item.ItemRenderState
+import net.minecraft.client.render.item.model.BasicItemModel
+import net.minecraft.client.render.item.model.ItemModel
+import net.minecraft.client.render.item.model.ItemModelTypes
+import net.minecraft.client.render.item.tint.TintSource
+import net.minecraft.client.render.model.ResolvableModel
+import net.minecraft.client.world.ClientWorld
+import net.minecraft.entity.LivingEntity
+import net.minecraft.item.ItemStack
+import net.minecraft.item.ModelTransformationMode
+import net.minecraft.util.Identifier
+import moe.nea.firmament.features.texturepack.predicates.AndPredicate
+
+class PredicateModel {
+ data class Baked(
+ val fallback: ItemModel,
+ val overrides: List<Override>
+ ) : ItemModel {
+ data class Override(
+ val model: ItemModel,
+ val predicate: FirmamentModelPredicate,
+ )
+
+ override fun update(
+ state: ItemRenderState,
+ stack: ItemStack,
+ resolver: ItemModelManager,
+ transformationMode: ModelTransformationMode,
+ world: ClientWorld?,
+ user: LivingEntity?,
+ seed: Int
+ ) {
+ val model =
+ overrides
+ .findLast { it.predicate.test(stack, user) }
+ ?.model
+ ?: fallback
+ model.update(state, stack, resolver, transformationMode, world, user, seed)
+ }
+ }
+
+ data class Unbaked(
+ val fallback: ItemModel.Unbaked,
+ val overrides: List<Override>,
+ ) : ItemModel.Unbaked {
+ companion object {
+ @JvmStatic
+ fun fromLegacyJson(jsonObject: JsonObject, fallback: ItemModel.Unbaked): ItemModel.Unbaked {
+ val legacyOverrides = jsonObject.getAsJsonArray("overrides") ?: return fallback
+ val newOverrides = ArrayList<Override>()
+ for (legacyOverride in legacyOverrides) {
+ legacyOverride as JsonObject
+ val overrideModel = Identifier.tryParse(legacyOverride.get("model")?.asString ?: continue) ?: continue
+ val predicate = CustomModelOverrideParser.parsePredicates(legacyOverride.getAsJsonObject("predicate"))
+ newOverrides.add(Override(
+ BasicItemModel.Unbaked(overrideModel, listOf()),
+ AndPredicate(predicate.toTypedArray())
+ ))
+ }
+ return Unbaked(fallback, newOverrides)
+ }
+
+ val OVERRIDE_CODEC: Codec<Override> = RecordCodecBuilder.create {
+ it.group(
+ ItemModelTypes.CODEC.fieldOf("model").forGetter(Override::model),
+ CustomModelOverrideParser.LEGACY_CODEC.fieldOf("predicate").forGetter(Override::predicate),
+ ).apply(it, Unbaked::Override)
+ }
+ val CODEC: MapCodec<Unbaked> =
+ RecordCodecBuilder.mapCodec {
+ it.group(
+ ItemModelTypes.CODEC.fieldOf("fallback").forGetter(Unbaked::fallback),
+ OVERRIDE_CODEC.listOf().fieldOf("overrides").forGetter(Unbaked::overrides),
+ ).apply(it, ::Unbaked)
+ }
+ }
+
+ data class Override(
+ val model: ItemModel.Unbaked,
+ val predicate: FirmamentModelPredicate,
+ )
+
+ override fun resolve(resolver: ResolvableModel.Resolver) {
+ fallback.resolve(resolver)
+ overrides.forEach { it.model.resolve(resolver) }
+ }
+
+ override fun getCodec(): MapCodec<out Unbaked> {
+ return CODEC
+ }
+
+ override fun bake(context: ItemModel.BakeContext): ItemModel {
+ return Baked(
+ fallback.bake(context),
+ overrides.map { Baked.Override(it.model.bake(context), it.predicate) }
+ )
+ }
+ }
+}
diff --git a/src/main/kotlin/features/texturepack/RarityMatcher.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/RarityMatcher.kt
index 634a171..634a171 100644
--- a/src/main/kotlin/features/texturepack/RarityMatcher.kt
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/RarityMatcher.kt
diff --git a/src/main/kotlin/features/texturepack/StringMatcher.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/StringMatcher.kt
index 5eb86ac..2b13284 100644
--- a/src/main/kotlin/features/texturepack/StringMatcher.kt
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/StringMatcher.kt
@@ -70,7 +70,7 @@ interface StringMatcher {
}
override fun serialize(encoder: Encoder, value: StringMatcher) {
- encoder.encodeSerializableValue(delegateSerializer, Companion.serialize(value).intoKotlinJson())
+ encoder.encodeSerializableValue(delegateSerializer, serialize(value).intoKotlinJson())
}
}
diff --git a/src/main/kotlin/features/texturepack/TintOverrides.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/TintOverrides.kt
index 85fcae4..53df184 100644
--- a/src/main/kotlin/features/texturepack/TintOverrides.kt
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/TintOverrides.kt
@@ -43,7 +43,7 @@ data class TintOverrides(
val override = (value as? JsonPrimitive)
?.takeIf(JsonPrimitive::isNumber)
?.asInt
- ?.let(::Fixed)
+ ?.let(TintOverrides::Fixed)
if (override == null) {
ErrorUtil.softError("Invalid tint override for a layer: $value")
continue
diff --git a/src/main/kotlin/features/texturepack/predicates/AlwaysPredicate.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/AlwaysPredicate.kt
index 7e0ddb1..7e0ddb1 100644
--- a/src/main/kotlin/features/texturepack/predicates/AlwaysPredicate.kt
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/AlwaysPredicate.kt
diff --git a/src/main/kotlin/features/texturepack/predicates/AndPredicate.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/AndPredicate.kt
index 99abaaa..70eb814 100644
--- a/src/main/kotlin/features/texturepack/predicates/AndPredicate.kt
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/AndPredicate.kt
@@ -3,15 +3,16 @@ package moe.nea.firmament.features.texturepack.predicates
import com.google.gson.JsonArray
import com.google.gson.JsonElement
import com.google.gson.JsonObject
+import net.minecraft.entity.LivingEntity
import moe.nea.firmament.features.texturepack.CustomModelOverrideParser
import moe.nea.firmament.features.texturepack.FirmamentModelPredicate
import moe.nea.firmament.features.texturepack.FirmamentModelPredicateParser
import net.minecraft.item.ItemStack
class AndPredicate(val children: Array<FirmamentModelPredicate>) : FirmamentModelPredicate {
- override fun test(stack: ItemStack): Boolean {
- return children.all { it.test(stack) }
- }
+ override fun test(stack: ItemStack, holder: LivingEntity?): Boolean {
+ return children.all { it.test(stack, holder) }
+ }
object Parser : FirmamentModelPredicateParser {
override fun parse(jsonElement: JsonElement): FirmamentModelPredicate {
diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/CastPredicate.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/CastPredicate.kt
new file mode 100644
index 0000000..2b79c1a
--- /dev/null
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/CastPredicate.kt
@@ -0,0 +1,25 @@
+package moe.nea.firmament.features.texturepack.predicates
+
+import com.google.gson.JsonElement
+import net.minecraft.entity.LivingEntity
+import net.minecraft.entity.player.PlayerEntity
+import net.minecraft.item.ItemStack
+import moe.nea.firmament.features.texturepack.FirmamentModelPredicate
+import moe.nea.firmament.features.texturepack.FirmamentModelPredicateParser
+
+class CastPredicate : FirmamentModelPredicate {
+ object Parser : FirmamentModelPredicateParser {
+ override fun parse(jsonElement: JsonElement): FirmamentModelPredicate? {
+ if (jsonElement.asDouble >= 1) return CastPredicate()
+ return NotPredicate(arrayOf(CastPredicate()))
+ }
+ }
+
+ override fun test(stack: ItemStack, holder: LivingEntity?): Boolean {
+ return (holder as? PlayerEntity)?.fishHook != null && holder.activeItem === stack
+ }
+
+ override fun test(stack: ItemStack): Boolean {
+ return false
+ }
+}
diff --git a/src/main/kotlin/features/texturepack/predicates/DisplayNamePredicate.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/DisplayNamePredicate.kt
index 04c7a2b..04c7a2b 100644
--- a/src/main/kotlin/features/texturepack/predicates/DisplayNamePredicate.kt
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/DisplayNamePredicate.kt
diff --git a/src/main/kotlin/features/texturepack/predicates/ExtraAttributesPredicate.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/ExtraAttributesPredicate.kt
index 3c8023d..3c8023d 100644
--- a/src/main/kotlin/features/texturepack/predicates/ExtraAttributesPredicate.kt
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/ExtraAttributesPredicate.kt
diff --git a/src/main/kotlin/features/texturepack/predicates/ItemPredicate.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/ItemPredicate.kt
index 3cb80c7..4833dc0 100644
--- a/src/main/kotlin/features/texturepack/predicates/ItemPredicate.kt
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/ItemPredicate.kt
@@ -17,7 +17,7 @@ class ItemPredicate(
val item: Item
) : FirmamentModelPredicate {
override fun test(stack: ItemStack): Boolean {
- return stack.item == item
+ return stack.isOf(item)
}
object Parser : FirmamentModelPredicateParser {
diff --git a/src/main/kotlin/features/texturepack/predicates/LorePredicate.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/LorePredicate.kt
index f0b4737..f0b4737 100644
--- a/src/main/kotlin/features/texturepack/predicates/LorePredicate.kt
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/LorePredicate.kt
diff --git a/src/main/kotlin/features/texturepack/predicates/NotPredicate.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/NotPredicate.kt
index 4986ad9..4986ad9 100644
--- a/src/main/kotlin/features/texturepack/predicates/NotPredicate.kt
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/NotPredicate.kt
diff --git a/src/main/kotlin/features/texturepack/predicates/NumberMatcher.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/NumberMatcher.kt
index b0d5178..b0d5178 100644
--- a/src/main/kotlin/features/texturepack/predicates/NumberMatcher.kt
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/NumberMatcher.kt
diff --git a/src/main/kotlin/features/texturepack/predicates/OrPredicate.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/OrPredicate.kt
index e3093cd..e3093cd 100644
--- a/src/main/kotlin/features/texturepack/predicates/OrPredicate.kt
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/OrPredicate.kt
diff --git a/src/main/kotlin/features/texturepack/predicates/PetPredicate.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/PetPredicate.kt
index b30b7c9..b30b7c9 100644
--- a/src/main/kotlin/features/texturepack/predicates/PetPredicate.kt
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/PetPredicate.kt
diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/PullingPredicate.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/PullingPredicate.kt
new file mode 100644
index 0000000..fa46a70
--- /dev/null
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/PullingPredicate.kt
@@ -0,0 +1,26 @@
+package moe.nea.firmament.features.texturepack.predicates
+
+import com.google.gson.JsonElement
+import net.minecraft.entity.LivingEntity
+import net.minecraft.item.BowItem
+import net.minecraft.item.ItemStack
+import moe.nea.firmament.features.texturepack.FirmamentModelPredicate
+import moe.nea.firmament.features.texturepack.FirmamentModelPredicateParser
+
+class PullingPredicate(val percentage: Double) : FirmamentModelPredicate {
+ companion object {
+ val AnyPulling = PullingPredicate(0.1)
+ }
+
+ object Parser : FirmamentModelPredicateParser {
+ override fun parse(jsonElement: JsonElement): FirmamentModelPredicate? {
+ return PullingPredicate(jsonElement.asDouble)
+ }
+ }
+
+ override fun test(stack: ItemStack, holder: LivingEntity?): Boolean {
+ if (holder == null) return false
+ return BowItem.getPullProgress(holder.itemUseTime) >= percentage
+ }
+
+}
diff --git a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ApplyHeadModelInItemRenderer.java b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ApplyHeadModelInItemRenderer.java
new file mode 100644
index 0000000..4665829
--- /dev/null
+++ b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ApplyHeadModelInItemRenderer.java
@@ -0,0 +1,23 @@
+
+package moe.nea.firmament.mixins.custommodels;
+
+import net.minecraft.client.render.entity.LivingEntityRenderer;
+import net.minecraft.client.render.entity.model.EntityModel;
+import net.minecraft.client.render.entity.state.LivingEntityRenderState;
+import net.minecraft.entity.LivingEntity;
+import net.minecraft.entity.decoration.DisplayEntity;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+@Mixin(LivingEntityRenderer.class)
+public class ApplyHeadModelInItemRenderer<T extends LivingEntity, S extends LivingEntityRenderState, M extends EntityModel<? super S>> {
+ // TODO: replace head_model with a condition model (if possible, automatically)
+ // TODO: ItemAsset.CODEC should upgrade partials
+ @Inject(method = "updateRenderState(Lnet/minecraft/entity/LivingEntity;Lnet/minecraft/client/render/entity/state/LivingEntityRenderState;F)V",
+ at = @At("TAIL"))
+ private void updateHeadState(T livingEntity, S livingEntityRenderState, float f, CallbackInfo ci) {
+
+ }
+}
diff --git a/src/main/java/moe/nea/firmament/mixins/CustomSkullTexturePatch.java b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/CustomSkullTexturePatch.java
index f3b616a..fede766 100644
--- a/src/main/java/moe/nea/firmament/mixins/CustomSkullTexturePatch.java
+++ b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/CustomSkullTexturePatch.java
@@ -1,12 +1,13 @@
-package moe.nea.firmament.mixins;
+package moe.nea.firmament.mixins.custommodels;
import moe.nea.firmament.features.texturepack.CustomSkyBlockTextures;
import net.minecraft.block.SkullBlock;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.client.render.block.entity.SkullBlockEntityRenderer;
import net.minecraft.component.type.ProfileComponent;
+import net.minecraft.util.Identifier;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
@@ -14,8 +15,12 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(SkullBlockEntityRenderer.class)
public class CustomSkullTexturePatch {
- @Inject(method = "getRenderLayer", at = @At("HEAD"), cancellable = true)
- private static void onGetRenderLayer(SkullBlock.SkullType type, ProfileComponent profile, CallbackInfoReturnable<RenderLayer> cir) {
+ @Inject(
+ method = "getRenderLayer(Lnet/minecraft/block/SkullBlock$SkullType;Lnet/minecraft/component/type/ProfileComponent;Lnet/minecraft/util/Identifier;)Lnet/minecraft/client/render/RenderLayer;",
+ at = @At("HEAD"),
+ cancellable = true
+ )
+ private static void onGetRenderLayer(SkullBlock.SkullType type, ProfileComponent profile, Identifier texture, CallbackInfoReturnable<RenderLayer> cir) {
CustomSkyBlockTextures.INSTANCE.modifySkullTexture(type, profile, cir);
}
}
diff --git a/src/main/java/moe/nea/firmament/mixins/custommodels/PatchArmorTexture.java b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/PatchArmorTexture.java
index 4468150..669da63 100644
--- a/src/main/java/moe/nea/firmament/mixins/custommodels/PatchArmorTexture.java
+++ b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/PatchArmorTexture.java
@@ -1,29 +1,30 @@
package moe.nea.firmament.mixins.custommodels;
+import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import moe.nea.firmament.features.texturepack.CustomGlobalArmorOverrides;
import net.minecraft.client.render.entity.feature.ArmorFeatureRenderer;
+import net.minecraft.component.ComponentType;
import net.minecraft.component.type.EquippableComponent;
+import net.minecraft.entity.EquipmentSlot;
import net.minecraft.item.ItemStack;
-import net.minecraft.util.Identifier;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
-import java.util.Optional;
-
@Mixin(ArmorFeatureRenderer.class)
public class PatchArmorTexture {
- @WrapOperation(
+ @ModifyExpressionValue(
method = "renderArmor",
- at = @At(value = "INVOKE", target = "Lnet/minecraft/component/type/EquippableComponent;model()Ljava/util/Optional;"))
- private Optional<Identifier> overrideLayers(
- EquippableComponent instance, Operation<Optional<Identifier>> original, @Local(argsOnly = true) ItemStack itemStack
+ at = @At(
+ value = "INVOKE",
+ target = "Lnet/minecraft/item/ItemStack;get(Lnet/minecraft/component/ComponentType;)Ljava/lang/Object;"))
+ private Object overrideLayers(
+ Object original, @Local(argsOnly = true) ItemStack itemStack, @Local(argsOnly = true) EquipmentSlot slot
) {
- // TODO: check that all armour items are naturally equippable and have the equppable component. otherwise our call here will not be reached.
- var overrides = CustomGlobalArmorOverrides.overrideArmor(itemStack);
- return overrides.or(() -> original.call(instance));
+ var overrides = CustomGlobalArmorOverrides.overrideArmor(itemStack, slot);
+ return overrides.orElse((EquippableComponent) original);
}
}
diff --git a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/PatchLegacyArmorLayerSupport.java b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/PatchLegacyArmorLayerSupport.java
new file mode 100644
index 0000000..81ea6cd
--- /dev/null
+++ b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/PatchLegacyArmorLayerSupport.java
@@ -0,0 +1,23 @@
+package moe.nea.firmament.mixins.custommodels;
+
+import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
+import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
+import moe.nea.firmament.features.texturepack.CustomGlobalArmorOverrides;
+import net.minecraft.client.render.entity.equipment.EquipmentModel;
+import net.minecraft.client.render.entity.equipment.EquipmentModelLoader;
+import net.minecraft.client.render.entity.equipment.EquipmentRenderer;
+import net.minecraft.item.equipment.EquipmentAsset;
+import net.minecraft.registry.RegistryKey;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+
+// TODO: auto import legacy models, maybe!!! in a later patch tho
+@Mixin(EquipmentRenderer.class)
+public class PatchLegacyArmorLayerSupport {
+ @WrapOperation(method = "render(Lnet/minecraft/client/render/entity/equipment/EquipmentModel$LayerType;Lnet/minecraft/registry/RegistryKey;Lnet/minecraft/client/model/Model;Lnet/minecraft/item/ItemStack;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/util/Identifier;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/entity/equipment/EquipmentModelLoader;get(Lnet/minecraft/registry/RegistryKey;)Lnet/minecraft/client/render/entity/equipment/EquipmentModel;"))
+ private EquipmentModel patchModelLayers(EquipmentModelLoader instance, RegistryKey<EquipmentAsset> assetKey, Operation<EquipmentModel> original) {
+ var modelOverride = CustomGlobalArmorOverrides.overrideArmorLayer(assetKey.getValue());
+ if (modelOverride != null) return modelOverride;
+ return original.call(instance, assetKey);
+ }
+}
diff --git a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/PatchLegacyTexturePathsIntoArmorLayers.java b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/PatchLegacyTexturePathsIntoArmorLayers.java
new file mode 100644
index 0000000..f829da0
--- /dev/null
+++ b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/PatchLegacyTexturePathsIntoArmorLayers.java
@@ -0,0 +1,38 @@
+package moe.nea.firmament.mixins.custommodels;
+
+
+import moe.nea.firmament.features.texturepack.CustomSkyBlockTextures;
+import moe.nea.firmament.util.MC;
+import net.minecraft.client.render.entity.equipment.EquipmentModel;
+import net.minecraft.util.Identifier;
+import org.spongepowered.asm.mixin.Final;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
+
+@Mixin(EquipmentModel.Layer.class)
+public class PatchLegacyTexturePathsIntoArmorLayers {
+ @Shadow
+ @Final
+ private Identifier textureId;
+
+ @Inject(method = "getFullTextureId", at = @At("HEAD"), cancellable = true)
+ private void replaceWith1201TextureIfExists(EquipmentModel.LayerType layerType, CallbackInfoReturnable<Identifier> cir) {
+ if (!CustomSkyBlockTextures.TConfig.INSTANCE.getEnableLegacyMinecraftCompat())
+ return;
+ var resourceManager = MC.INSTANCE.getResourceManager();
+ // legacy format: "assets/{identifier.namespace}/textures/models/armor/{identifier.path}_layer_{isLegs ? 2 : 1}{suffix}.png"
+ // suffix is sadly not available to us here. this means leather armor will look a bit shite
+ var legacyIdentifier = this.textureId.withPath((textureName) -> {
+ String var10000 = layerType.asString();
+ return "textures/models/armor/" + textureName + "_layer_" +
+ (layerType == EquipmentModel.LayerType.HUMANOID_LEGGINGS ? 2 : 1)
+ + ".png";
+ });
+ if (resourceManager.getResource(legacyIdentifier).isPresent()) {
+ cir.setReturnValue(legacyIdentifier);
+ }
+ }
+}
diff --git a/src/main/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockBreakSoundPatch.java b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockBreakSoundPatch.java
index 9401889..9401889 100644
--- a/src/main/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockBreakSoundPatch.java
+++ b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockBreakSoundPatch.java
diff --git a/src/main/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockHitSoundPatch.java b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockHitSoundPatch.java
index f9a1d0d..f9a1d0d 100644
--- a/src/main/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockHitSoundPatch.java
+++ b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockHitSoundPatch.java
diff --git a/src/main/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockRenderManagerBlockModel.java b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockRenderManagerBlockModel.java
index 711b2af..711b2af 100644
--- a/src/main/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockRenderManagerBlockModel.java
+++ b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockRenderManagerBlockModel.java
diff --git a/src/main/java/moe/nea/firmament/mixins/custommodels/ReplaceFallbackBlockModel.java b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceFallbackBlockModel.java
index 53ab74a..53ab74a 100644
--- a/src/main/java/moe/nea/firmament/mixins/custommodels/ReplaceFallbackBlockModel.java
+++ b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceFallbackBlockModel.java
diff --git a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceItemModelPatch.java b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceItemModelPatch.java
new file mode 100644
index 0000000..97abd1f
--- /dev/null
+++ b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceItemModelPatch.java
@@ -0,0 +1,43 @@
+package moe.nea.firmament.mixins.custommodels;
+
+
+import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
+import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
+import moe.nea.firmament.events.CustomItemModelEvent;
+import moe.nea.firmament.util.mc.IntrospectableItemModelManager;
+import net.minecraft.client.item.ItemModelManager;
+import net.minecraft.client.render.item.model.ItemModel;
+import net.minecraft.client.render.item.model.MissingItemModel;
+import net.minecraft.component.ComponentType;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.Identifier;
+import org.jetbrains.annotations.NotNull;
+import org.spongepowered.asm.mixin.Final;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.injection.At;
+
+import java.util.function.Function;
+
+@Mixin(ItemModelManager.class)
+public class ReplaceItemModelPatch implements IntrospectableItemModelManager {
+ @Shadow
+ @Final
+ private Function<Identifier, ItemModel> modelGetter;
+
+ @WrapOperation(
+ method = "update(Lnet/minecraft/client/render/item/ItemRenderState;Lnet/minecraft/item/ItemStack;Lnet/minecraft/item/ModelTransformationMode;Lnet/minecraft/world/World;Lnet/minecraft/entity/LivingEntity;I)V",
+ at = @At(value = "INVOKE", target = "Lnet/minecraft/item/ItemStack;get(Lnet/minecraft/component/ComponentType;)Ljava/lang/Object;"))
+ private Object replaceItemModelByIdentifier(ItemStack instance, ComponentType componentType, Operation<Object> original) {
+ var override = CustomItemModelEvent.getModelIdentifier(instance, this);
+ if (override != null && hasModel_firmament(override)) {
+ return override;
+ }
+ return original.call(instance, componentType);
+ }
+
+ @Override
+ public boolean hasModel_firmament(@NotNull Identifier identifier) {
+ return !(modelGetter.apply(identifier) instanceof MissingItemModel);
+ }
+}
diff --git a/src/main/java/moe/nea/firmament/mixins/ReplaceTextColorInHandledScreen.java b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceTextColorInHandledScreen.java
index c9fb073..e4834e9 100644
--- a/src/main/java/moe/nea/firmament/mixins/ReplaceTextColorInHandledScreen.java
+++ b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceTextColorInHandledScreen.java
@@ -1,4 +1,4 @@
-package moe.nea.firmament.mixins;
+package moe.nea.firmament.mixins.custommodels;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
diff --git a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/SupplyFakeModelPatch.java b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/SupplyFakeModelPatch.java
new file mode 100644
index 0000000..850ea53
--- /dev/null
+++ b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/SupplyFakeModelPatch.java
@@ -0,0 +1,95 @@
+package moe.nea.firmament.mixins.custommodels;
+
+import com.google.gson.JsonObject;
+import com.llamalad7.mixinextras.injector.ModifyReturnValue;
+import com.llamalad7.mixinextras.sugar.Local;
+import moe.nea.firmament.Firmament;
+import moe.nea.firmament.features.texturepack.CustomSkyBlockTextures;
+import moe.nea.firmament.features.texturepack.PredicateModel;
+import moe.nea.firmament.util.ErrorUtil;
+import net.minecraft.client.item.ItemAsset;
+import net.minecraft.client.item.ItemAssetsLoader;
+import net.minecraft.client.render.item.model.BasicItemModel;
+import net.minecraft.client.render.item.model.ItemModel;
+import net.minecraft.resource.Resource;
+import net.minecraft.resource.ResourceManager;
+import net.minecraft.resource.ResourcePack;
+import net.minecraft.util.Identifier;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+
+import java.io.InputStreamReader;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+import java.util.stream.Collector;
+import java.util.stream.Collectors;
+
+@Mixin(ItemAssetsLoader.class)
+public class SupplyFakeModelPatch {
+
+ @ModifyReturnValue(
+ method = "load",
+ at = @At("RETURN")
+ )
+ private static CompletableFuture<ItemAssetsLoader.Result> injectFakeGeneratedModels(
+ CompletableFuture<ItemAssetsLoader.Result> original,
+ @Local(argsOnly = true) ResourceManager resourceManager,
+ @Local(argsOnly = true) Executor executor
+ ) {
+ return original.thenCompose(oldModels -> CompletableFuture.supplyAsync(() -> supplyExtraModels(resourceManager, oldModels), executor));
+ }
+
+ private static ItemAssetsLoader.Result supplyExtraModels(ResourceManager resourceManager, ItemAssetsLoader.Result oldModels) {
+ if (!CustomSkyBlockTextures.TConfig.INSTANCE.getEnableLegacyMinecraftCompat()) return oldModels;
+ Map<Identifier, ItemAsset> newModels = new HashMap<>(oldModels.contents());
+ var resources = resourceManager.findResources(
+ "models/item",
+ id -> (id.getNamespace().equals("firmskyblock") || id.getNamespace().equals("cittofirmgenerated"))
+ && id.getPath().endsWith(".json"));
+ for (Map.Entry<Identifier, Resource> model : resources.entrySet()) {
+ var resource = model.getValue();
+ var itemModelId = model.getKey().withPath(it -> it.substring("models/item/".length(), it.length() - ".json".length()));
+ var genericModelId = itemModelId.withPrefixedPath("item/");
+ var itemAssetId = itemModelId.withPrefixedPath("items/");
+ // TODO: inject tint indexes based on the json data here
+ ItemModel.Unbaked unbakedModel = new BasicItemModel.Unbaked(genericModelId, List.of());
+ // TODO: add a filter using the pack.mcmeta to opt out of this behaviour
+ try (var is = resource.getInputStream()) {
+ var jsonObject = Firmament.INSTANCE.getGson().fromJson(new InputStreamReader(is), JsonObject.class);
+ unbakedModel = PredicateModel.Unbaked.fromLegacyJson(jsonObject, unbakedModel);
+ } catch (Exception e) {
+ ErrorUtil.INSTANCE.softError("Could not create resource for fake model supplication: " + model.getKey(), e);
+ }
+ if (resourceManager.getResource(itemAssetId.withSuffixedPath(".json"))
+ .map(Resource::getPack)
+ .map(it -> isResourcePackNewer(resourceManager, it, resource.getPack()))
+ .orElse(true)) {
+ newModels.put(itemModelId, new ItemAsset(
+ unbakedModel,
+ new ItemAsset.Properties(true)
+ ));
+ }
+ }
+ return new ItemAssetsLoader.Result(newModels);
+ }
+
+ private static boolean isResourcePackNewer(
+ ResourceManager manager,
+ ResourcePack null_, ResourcePack proposal) {
+ var pack = manager.streamResourcePacks()
+ .filter(it -> it == null_ || it == proposal)
+ .collect(findLast());
+ return pack.orElse(null_) != null_;
+ }
+
+ private static <T> Collector<T, ?, Optional<T>> findLast() {
+ return Collectors.reducing(Optional.empty(), Optional::of,
+ (left, right) -> right.isPresent() ? right : left);
+
+ }
+
+}