From 3c5e8071ef29c4255056d3f88e97eae41ee7623c Mon Sep 17 00:00:00 2001 From: Robotgiggle <88736742+Robotgiggle@users.noreply.github.com> Date: Tue, 25 Aug 2026 19:42:44 -0400 Subject: [PATCH 01/17] Initial armor stats (may need rebalancing) # Conflicts: # Common/src/main/java/at/petrak/hexcasting/common/lib/HexItems.java --- .../common/items/armor/ItemRobes.java | 43 +++++++++++++++++++ .../hexcasting/common/lib/HexAttributes.java | 2 +- .../hexcasting/common/lib/HexItems.java | 35 ++++++++++++--- 3 files changed, 74 insertions(+), 6 deletions(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/common/items/armor/ItemRobes.java b/Common/src/main/java/at/petrak/hexcasting/common/items/armor/ItemRobes.java index 0e3d52798f..020d3c4625 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/items/armor/ItemRobes.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/items/armor/ItemRobes.java @@ -5,7 +5,9 @@ import at.petrak.hexcasting.api.item.VariantItem; import at.petrak.hexcasting.client.model.HexModelLayers; import at.petrak.hexcasting.client.model.HexRobesModel; +import at.petrak.hexcasting.common.items.ItemLens; import at.petrak.hexcasting.common.lib.HexArmorMaterials; +import at.petrak.hexcasting.common.lib.HexAttributes; import net.minecraft.client.Minecraft; import net.minecraft.client.model.geom.EntityModelSet; import net.minecraft.core.Holder; @@ -13,11 +15,17 @@ import net.minecraft.resources.ResourceLocation; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.EquipmentSlotGroup; +import net.minecraft.world.entity.ai.attributes.AttributeModifier; +import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.item.ArmorItem; import net.minecraft.world.item.ArmorMaterial; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.component.ItemAttributeModifiers; import org.jetbrains.annotations.Nullable; +import static at.petrak.hexcasting.api.HexAPI.modLoc; + /** * To get the armor model in; * On forge: client item extension in ForgeHexClientInitializer (line 161) @@ -27,6 +35,41 @@ public class ItemRobes extends ArmorItem implements VariantItem { public final Type type; private @Nullable HexRobesModel[] models; + public static ItemAttributeModifiers HOOD_MODIFIERS = ItemAttributeModifiers.builder() + .add(HexAttributes.SCRY_SIGHT, ItemLens.SCRY_SIGHT, EquipmentSlotGroup.HEAD) + .add(HexAttributes.GRID_ZOOM, ItemLens.GRID_ZOOM, EquipmentSlotGroup.HEAD) + .add(Attributes.ARMOR, new AttributeModifier( + modLoc("robes_hood_armor"), 3.0, AttributeModifier.Operation.ADD_VALUE + ), EquipmentSlotGroup.HEAD) + .build(); + + public static ItemAttributeModifiers TUNIC_MODIFIERS = ItemAttributeModifiers.builder() + .add(HexAttributes.MEDIA_CONSUMPTION_MODIFIER, new AttributeModifier( + modLoc("robes_tunic_discount"), -0.1, AttributeModifier.Operation.ADD_MULTIPLIED_BASE + ), EquipmentSlotGroup.CHEST) + .add(Attributes.ARMOR, new AttributeModifier( + modLoc("robes_tunic_armor"), 7.0, AttributeModifier.Operation.ADD_VALUE + ), EquipmentSlotGroup.CHEST) + .build(); + + public static ItemAttributeModifiers LEGS_MODIFIERS = ItemAttributeModifiers.builder() + .add(HexAttributes.AMBIT_RADIUS, new AttributeModifier( + modLoc("robes_legs_ambit"), 4.0, AttributeModifier.Operation.ADD_VALUE + ), EquipmentSlotGroup.LEGS) + .add(Attributes.ARMOR, new AttributeModifier( + modLoc("robes_legs_armor"), 6.0, AttributeModifier.Operation.ADD_VALUE + ), EquipmentSlotGroup.LEGS) + .build(); + + public static ItemAttributeModifiers BOOTS_MODIFIERS = ItemAttributeModifiers.builder() + .add(HexAttributes.SENTINEL_RADIUS, new AttributeModifier( + modLoc("robes_boots_sentinel_ambit"), 2.0, AttributeModifier.Operation.ADD_VALUE + ), EquipmentSlotGroup.FEET) + .add(Attributes.ARMOR, new AttributeModifier( + modLoc("robes_boots_armor"), 2.0, AttributeModifier.Operation.ADD_VALUE + ), EquipmentSlotGroup.FEET) + .build(); + public ItemRobes(Type type, Properties properties) { super(HexArmorMaterials.ROBES, type, properties); this.type = type; diff --git a/Common/src/main/java/at/petrak/hexcasting/common/lib/HexAttributes.java b/Common/src/main/java/at/petrak/hexcasting/common/lib/HexAttributes.java index 707ec6544c..e5469995b6 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/lib/HexAttributes.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/lib/HexAttributes.java @@ -33,7 +33,7 @@ public static void register() { //a multiplier to adjust media consumption across the board public static final Holder MEDIA_CONSUMPTION_MODIFIER = REGISTER.registerHolder("media_consumption", () -> new RangedAttribute( - MOD_ID + ".attributes.media_consumption", 1.0, 0.0, Double.MAX_VALUE).setSyncable(true)); + MOD_ID + ".attributes.media_consumption", 1.0, 0.0, Double.MAX_VALUE).setSyncable(true).setSentiment(Attribute.Sentiment.NEGATIVE)); public static final Holder AMBIT_RADIUS = REGISTER.registerHolder("ambit_radius", () -> new RangedAttribute( MOD_ID + ".attributes.ambit_radius", PlayerBasedCastEnv.DEFAULT_AMBIT_RADIUS, 0.0, Double.MAX_VALUE).setSyncable(true)); diff --git a/Common/src/main/java/at/petrak/hexcasting/common/lib/HexItems.java b/Common/src/main/java/at/petrak/hexcasting/common/lib/HexItems.java index a471511be3..f4f21daf35 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/lib/HexItems.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/lib/HexItems.java @@ -1,5 +1,6 @@ package at.petrak.hexcasting.common.lib; +import at.petrak.hexcasting.api.HexAPI; import at.petrak.hexcasting.api.casting.ActionRegistryEntry; import at.petrak.hexcasting.api.misc.MediaConstants; import at.petrak.hexcasting.api.mod.HexTags; @@ -76,11 +77,6 @@ public static void registerItemsForCreativeTab(ResourceKey tabK IXplatAbstractions.INSTANCE.addEquipSlotFabric(EquipmentSlot.HEAD) .stacksTo(1).attributes(ItemLens.MODIFIERS))); - public static final Supplier ROBES_HOOD = make("robes/hood", () -> new ItemRobes(ArmorItem.Type.HELMET, unstackable().rarity(Rarity.UNCOMMON))); - public static final Supplier ROBES_TUNIC = make("robes/tunic", () -> new ItemRobes(ArmorItem.Type.CHESTPLATE, unstackable().rarity(Rarity.UNCOMMON))); - public static final Supplier ROBES_LEGS = make("robes/legs", () -> new ItemRobes(ArmorItem.Type.LEGGINGS, unstackable().rarity(Rarity.UNCOMMON))); - public static final Supplier ROBES_BOOTS = make("robes/boots", () -> new ItemRobes(ArmorItem.Type.BOOTS, unstackable().rarity(Rarity.UNCOMMON))); - public static final Supplier ABACUS = make("abacus", () -> new ItemAbacus(unstackable())); public static final Supplier THOUGHT_KNOT = make("thought_knot", () -> new ItemThoughtKnot(unstackable())); public static final Supplier FOCUS = make("focus", () -> new ItemFocus(unstackable())); @@ -108,6 +104,35 @@ public static void registerItemsForCreativeTab(ResourceKey tabK public static final Supplier NEURAL_FIBER = make("neural_fiber", () -> new Item(props().rarity(Rarity.UNCOMMON))); + public static final Supplier ROBES_HOOD = make("robes/hood", () -> + new ItemRobes(ArmorItem.Type.HELMET, unstackable() + .rarity(Rarity.UNCOMMON) + .durability(270) + .attributes(ItemRobes.HOOD_MODIFIERS) + ) + ); + public static final Supplier ROBES_TUNIC = make("robes/tunic", () -> + new ItemRobes(ArmorItem.Type.CHESTPLATE, unstackable() + .rarity(Rarity.UNCOMMON) + .durability(400) + .attributes(ItemRobes.TUNIC_MODIFIERS) + ) + ); + public static final Supplier ROBES_LEGS = make("robes/legs", () -> + new ItemRobes(ArmorItem.Type.LEGGINGS, unstackable() + .rarity(Rarity.UNCOMMON) + .durability(350) + .attributes(ItemRobes.LEGS_MODIFIERS) + ) + ); + public static final Supplier ROBES_BOOTS = make("robes/boots", () -> + new ItemRobes(ArmorItem.Type.BOOTS, unstackable() + .rarity(Rarity.UNCOMMON) + .durability(315) + .attributes(ItemRobes.BOOTS_MODIFIERS) + ) + ); + public static final Supplier SCROLL_SMOL = make("scroll_small", () -> new ItemScroll(props(), 1)); public static final Supplier SCROLL_MEDIUM = make("scroll_medium", () -> new ItemScroll(props(), 2)); public static final Supplier SCROLL_LARGE = make("scroll", () -> new ItemScroll(props(), 3)); From 1d688bcddf0b84c4395e122bc323e7217d296ada Mon Sep 17 00:00:00 2001 From: Robotgiggle Date: Wed, 26 Aug 2026 22:27:12 -0400 Subject: [PATCH 02/17] Basic grid panning system --- .../hexcasting/client/gui/GuiSpellcasting.kt | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt b/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt index 30cd7676bb..619e397f10 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt +++ b/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt @@ -53,6 +53,8 @@ class GuiSpellcasting constructor( private var drawState: PatternDrawState = PatternDrawState.BetweenPatterns private val usedSpots: MutableSet = HashSet() + private var panOffset: Vec2 = Vec2.ZERO + private var ambianceSoundInstance: GridSoundInstance? = null private val randSrc = SoundInstance.createUnseededRandom() @@ -150,11 +152,11 @@ class GuiSpellcasting constructor( } if (HexConfig.client().clickingTogglesDrawing()) { return if (this.drawState is PatternDrawState.BetweenPatterns) - drawStart(mxOut, myOut) + pButton != 1 && drawStart(mxOut, myOut) else drawEnd() } - return drawStart(mxOut, myOut) + return pButton != 1 && drawStart(mxOut, myOut) } private fun drawStart(mxOut: Double, myOut: Double): Boolean { @@ -195,9 +197,19 @@ class GuiSpellcasting constructor( } if (HexConfig.client().clickingTogglesDrawing()) return false + if (pButton == 1) { + return panGrid(pDragX, pDragY) + } return drawMove(mxOut, myOut) } + private fun panGrid(pDragX: Double, pDragY: Double): Boolean { + val shift = Vec2(pDragX.toFloat(), pDragY.toFloat()) + this.panOffset = this.panOffset.add(shift) + this.drawState = PatternDrawState.BetweenPatterns + return false; + } + private fun drawMove(mxOut: Double, myOut: Double): Boolean { val mx = Mth.clamp(mxOut, 0.0, this.width.toDouble()) val my = Mth.clamp(myOut, 0.0, this.height.toDouble()) @@ -527,7 +539,7 @@ class GuiSpellcasting constructor( return (baseScale / scaleModifier).toFloat() } - fun coordsOffset(): Vec2 = Vec2(this.width.toFloat() * 0.5f, this.height.toFloat() * 0.5f) + fun coordsOffset(): Vec2 = Vec2(this.width.toFloat() * 0.5f, this.height.toFloat() * 0.5f).add(this.panOffset) fun coordToPx(coord: HexCoord) = at.petrak.hexcasting.api.utils.coordToPx(coord, this.hexSize(), this.coordsOffset()) @@ -536,7 +548,7 @@ class GuiSpellcasting constructor( private sealed class PatternDrawState { - /** We're waiting on the player to right-click again */ + /** We're waiting on the player to left-click again */ object BetweenPatterns : PatternDrawState() /** We just started drawing and haven't drawn the first line yet. */ From 2a4635bf6c4ed79f5f251591f1dbfda8fb43d157 Mon Sep 17 00:00:00 2001 From: Robotgiggle Date: Wed, 26 Aug 2026 22:59:37 -0400 Subject: [PATCH 03/17] Make grid-pan button configurable --- .../main/java/at/petrak/hexcasting/api/mod/HexConfig.java | 3 +++ .../at/petrak/hexcasting/client/gui/GuiSpellcasting.kt | 6 +++--- .../resources/assets/hexcasting/lang/en_us.flatten.json5 | 4 ++++ .../java/at/petrak/hexcasting/fabric/FabricHexConfig.java | 6 ++++++ .../java/at/petrak/hexcasting/forge/ForgeHexConfig.java | 7 +++++++ 5 files changed, 23 insertions(+), 3 deletions(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/mod/HexConfig.java b/Common/src/main/java/at/petrak/hexcasting/api/mod/HexConfig.java index 5a63fdef8a..060dbe5572 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/mod/HexConfig.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/mod/HexConfig.java @@ -51,6 +51,8 @@ public interface ClientConfigAccess { boolean clickingTogglesDrawing(); + int gridPanMouseButton(); + boolean advancedTooltipsShowsIotaNBT(); boolean staticActiveSlates(); @@ -61,6 +63,7 @@ public interface ClientConfigAccess { boolean DEFAULT_INVERT_ABACUS_SCROLL = false; double DEFAULT_GRID_SNAP_THRESHOLD = 0.5; boolean DEFAULT_CLICKING_TOGGLES_DRAWING = false; + int DEFAULT_GRID_PAN_MOUSE_BUTTON = 1; boolean DEFAULT_ADVANCED_TOOLTIPS_SHOWS_IOTA_NBT = false; boolean DEFAULT_STATIC_ACTIVE_SLATES = false; } diff --git a/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt b/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt index 619e397f10..898d28735a 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt +++ b/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt @@ -152,11 +152,11 @@ class GuiSpellcasting constructor( } if (HexConfig.client().clickingTogglesDrawing()) { return if (this.drawState is PatternDrawState.BetweenPatterns) - pButton != 1 && drawStart(mxOut, myOut) + drawStart(mxOut, myOut) && pButton != HexConfig.client().gridPanMouseButton() else drawEnd() } - return pButton != 1 && drawStart(mxOut, myOut) + return drawStart(mxOut, myOut) && pButton != HexConfig.client().gridPanMouseButton() } private fun drawStart(mxOut: Double, myOut: Double): Boolean { @@ -197,7 +197,7 @@ class GuiSpellcasting constructor( } if (HexConfig.client().clickingTogglesDrawing()) return false - if (pButton == 1) { + if (pButton == HexConfig.client().gridPanMouseButton() && this.drawState is PatternDrawState.BetweenPatterns) { return panGrid(pDragX, pDragY) } return drawMove(mxOut, myOut) diff --git a/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 b/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 index 2ca034e0fe..44cb51a551 100644 --- a/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 +++ b/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 @@ -375,6 +375,10 @@ "": "Clicking Toggles Drawing", "@Tooltip": "Whether you click to start and stop drawing instead of clicking and dragging to draw", }, + gridPanMouseButton: { + "": "Grid Pan Mouse Button", + "@Tooltip": "Which mouse button is used to pan the hex grid (once that ability is unlocked)", + }, advancedTooltipsShowsIotaNBT: { "": "Advanced Tooltips Shows Iota NBT", "@Tooltip": "Whether enabling advanced tooltips (F3+H) should display the full NBT of iotas stored in items like foci and spellbooks", diff --git a/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexConfig.java b/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexConfig.java index 09dd3727e7..aaf82542ff 100644 --- a/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexConfig.java +++ b/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexConfig.java @@ -140,6 +140,8 @@ public static final class Client implements HexConfig.ClientConfigAccess, Config @ConfigEntry.Gui.Tooltip private boolean clickingTogglesDrawing = DEFAULT_CLICKING_TOGGLES_DRAWING; @ConfigEntry.Gui.Tooltip + private int gridPanMouseButton = DEFAULT_GRID_PAN_MOUSE_BUTTON; + @ConfigEntry.Gui.Tooltip private boolean advancedTooltipsShowsIotaNBT = DEFAULT_ADVANCED_TOOLTIPS_SHOWS_IOTA_NBT; @ConfigEntry.Gui.Tooltip private boolean staticActiveSlates = DEFAULT_STATIC_ACTIVE_SLATES; @@ -147,6 +149,7 @@ public static final class Client implements HexConfig.ClientConfigAccess, Config @Override public void validatePostLoad() throws ValidationException { this.gridSnapThreshold = Mth.clamp(this.gridSnapThreshold, 0.5, 1.0); + this.gridPanMouseButton = Math.max(this.gridPanMouseButton, 1); } @Override @@ -179,6 +182,9 @@ public boolean clickingTogglesDrawing() { return clickingTogglesDrawing; } + @Override + public int gridPanMouseButton() { return gridPanMouseButton; } + @Override public boolean advancedTooltipsShowsIotaNBT() { return advancedTooltipsShowsIotaNBT; diff --git a/Neoforge/src/main/java/at/petrak/hexcasting/forge/ForgeHexConfig.java b/Neoforge/src/main/java/at/petrak/hexcasting/forge/ForgeHexConfig.java index a9a48d4201..c582804ecb 100644 --- a/Neoforge/src/main/java/at/petrak/hexcasting/forge/ForgeHexConfig.java +++ b/Neoforge/src/main/java/at/petrak/hexcasting/forge/ForgeHexConfig.java @@ -86,6 +86,7 @@ public static class Client implements HexConfig.ClientConfigAccess { private static ModConfigSpec.BooleanValue invertAbacusScrollDirection; private static ModConfigSpec.DoubleValue gridSnapThreshold; private static ModConfigSpec.BooleanValue clickingTogglesDrawing; + private static ModConfigSpec.IntValue gridPanMouseButton; private static ModConfigSpec.BooleanValue disableInworldScrolling; private static ModConfigSpec.BooleanValue advancedTooltipsShowsIotaNBT; private static ModConfigSpec.BooleanValue staticActiveSlates; @@ -111,6 +112,9 @@ public Client(ModConfigSpec.Builder builder) { clickingTogglesDrawing = builder.comment( "Whether you click to start and stop drawing instead of clicking and dragging") .define("clickingTogglesDrawing", DEFAULT_CLICKING_TOGGLES_DRAWING); + gridPanMouseButton = builder.comment( + "Which mouse button is used to pan the hex grid (once that ability is unlocked)") + .defineInRange("gridPanMouseButton", DEFAULT_GRID_PAN_MOUSE_BUTTON, 1, Integer.MAX_VALUE); advancedTooltipsShowsIotaNBT = builder.comment( "Whether enabling advanced tooltips (F3+H) should display the full NBT of iotas stored in items " + "like foci and spellbooks") @@ -150,6 +154,9 @@ public boolean clickingTogglesDrawing() { return clickingTogglesDrawing.get(); } + @Override + public int gridPanMouseButton() { return gridPanMouseButton.get(); } + @Override public boolean advancedTooltipsShowsIotaNBT() { return advancedTooltipsShowsIotaNBT.get(); From 25bdfb7e27fd63da7126b46295a2576cacdcfa5e Mon Sep 17 00:00:00 2001 From: Robotgiggle Date: Wed, 26 Aug 2026 23:00:13 -0400 Subject: [PATCH 04/17] Fix various edge cases --- .../at/petrak/hexcasting/client/gui/GuiSpellcasting.kt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt b/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt index 898d28735a..e85e5e336c 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt +++ b/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt @@ -150,13 +150,15 @@ class GuiSpellcasting constructor( if (super.mouseClicked(mxOut, myOut, pButton)) { return true } + if (pButton == HexConfig.client().gridPanMouseButton()) + return false if (HexConfig.client().clickingTogglesDrawing()) { return if (this.drawState is PatternDrawState.BetweenPatterns) - drawStart(mxOut, myOut) && pButton != HexConfig.client().gridPanMouseButton() + drawStart(mxOut, myOut) else drawEnd() } - return drawStart(mxOut, myOut) && pButton != HexConfig.client().gridPanMouseButton() + return drawStart(mxOut, myOut) } private fun drawStart(mxOut: Double, myOut: Double): Boolean { @@ -206,7 +208,6 @@ class GuiSpellcasting constructor( private fun panGrid(pDragX: Double, pDragY: Double): Boolean { val shift = Vec2(pDragX.toFloat(), pDragY.toFloat()) this.panOffset = this.panOffset.add(shift) - this.drawState = PatternDrawState.BetweenPatterns return false; } @@ -282,6 +283,8 @@ class GuiSpellcasting constructor( } if (HexConfig.client().clickingTogglesDrawing()) return false + if (pButton == HexConfig.client().gridPanMouseButton()) + return false return drawEnd() } From ffbea39e7837d92adcafa4686ed3a41ee2359c2e Mon Sep 17 00:00:00 2001 From: Robotgiggle Date: Fri, 28 Aug 2026 02:05:24 -0400 Subject: [PATCH 05/17] Scale grid blur based on pan distance --- .../hexcasting/client/gui/GuiSpellcasting.kt | 7 +++++ .../mixin/client/MixinGameRenderer.java | 28 +++++++++++++++++++ Common/src/main/resources/hexplat.mixins.json | 3 +- 3 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 Common/src/main/java/at/petrak/hexcasting/mixin/client/MixinGameRenderer.java diff --git a/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt b/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt index e85e5e336c..9432285592 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt +++ b/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt @@ -66,6 +66,10 @@ class GuiSpellcasting constructor( this.calculateIotaDisplays() } + fun getPanDistance(): Float { + return panOffset.length() + } + fun recvServerUpdate(info: ExecutionClientView, index: Int) { if (info.isStackClear) { this.minecraft?.setScreen(null) @@ -368,6 +372,9 @@ class GuiSpellcasting constructor( super.onClose() } + override fun renderBackground(guiGraphics: GuiGraphics, i: Int, j: Int, f: Float) { + this.renderBlurredBackground(f) + } override fun render(graphics: GuiGraphics, pMouseX: Int, pMouseY: Int, pPartialTick: Float) { super.render(graphics, pMouseX, pMouseY, pPartialTick) diff --git a/Common/src/main/java/at/petrak/hexcasting/mixin/client/MixinGameRenderer.java b/Common/src/main/java/at/petrak/hexcasting/mixin/client/MixinGameRenderer.java new file mode 100644 index 0000000000..14ea119a6f --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/mixin/client/MixinGameRenderer.java @@ -0,0 +1,28 @@ +package at.petrak.hexcasting.mixin.client; + +import at.petrak.hexcasting.api.HexAPI; +import at.petrak.hexcasting.client.gui.GuiSpellcasting; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.client.renderer.PostChain; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(GameRenderer.class) +public abstract class MixinGameRenderer { + @WrapOperation( + method = "processBlurEffect", + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/PostChain;setUniform(Ljava/lang/String;F)V") + ) + private void scaleBlurWhenPanningGrid(PostChain instance, String string, float f, Operation original) { + GameRenderer renderer = (GameRenderer)(Object)this; + Screen screen = renderer.getMinecraft().screen; + if (screen instanceof GuiSpellcasting grid) { + original.call(instance, string, grid.getPanDistance() / 10); + } else { + original.call(instance, string, f); + } + } +} diff --git a/Common/src/main/resources/hexplat.mixins.json b/Common/src/main/resources/hexplat.mixins.json index 4916e585bd..e6f110c913 100644 --- a/Common/src/main/resources/hexplat.mixins.json +++ b/Common/src/main/resources/hexplat.mixins.json @@ -27,6 +27,7 @@ "accessor.client.AccessorRenderStateShard", "accessor.client.AccessorRenderType", "client.MixinClientLevel", - "client.MixinPlayerRenderer" + "client.MixinPlayerRenderer", + "client.MixinGameRenderer" ] } From c6db60eee82748b4655cf0c151f3e19d3cc0f198 Mon Sep 17 00:00:00 2001 From: Robotgiggle Date: Mon, 31 Aug 2026 01:58:43 -0400 Subject: [PATCH 06/17] Tint background purple while panned --- .../hexcasting/client/gui/GuiSpellcasting.kt | 10 ++++++++-- .../mixin/client/MixinGameRenderer.java | 2 +- .../hexcasting/textures/gui/casting_bg.png | Bin 0 -> 4767 bytes 3 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 Common/src/main/resources/assets/hexcasting/textures/gui/casting_bg.png diff --git a/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt b/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt index 9432285592..747f352c7f 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt +++ b/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt @@ -1,5 +1,6 @@ package at.petrak.hexcasting.client.gui +import at.petrak.hexcasting.api.HexAPI import at.petrak.hexcasting.api.casting.eval.ExecutionClientView import at.petrak.hexcasting.api.casting.eval.ResolvedPattern import at.petrak.hexcasting.api.casting.eval.ResolvedPatternType @@ -31,6 +32,7 @@ import net.minecraft.client.gui.screens.Screen import net.minecraft.client.renderer.GameRenderer import net.minecraft.client.resources.sounds.SimpleSoundInstance import net.minecraft.client.resources.sounds.SoundInstance +import net.minecraft.resources.ResourceLocation import net.minecraft.sounds.SoundSource import net.minecraft.util.FormattedCharSequence import net.minecraft.util.Mth @@ -53,7 +55,8 @@ class GuiSpellcasting constructor( private var drawState: PatternDrawState = PatternDrawState.BetweenPatterns private val usedSpots: MutableSet = HashSet() - private var panOffset: Vec2 = Vec2.ZERO + private var panOffset = Vec2.ZERO + private val bgLocation = HexAPI.modLoc("textures/gui/casting_bg.png") private var ambianceSoundInstance: GridSoundInstance? = null @@ -67,7 +70,7 @@ class GuiSpellcasting constructor( } fun getPanDistance(): Float { - return panOffset.length() + return panOffset.length() / 20 } fun recvServerUpdate(info: ExecutionClientView, index: Int) { @@ -373,6 +376,9 @@ class GuiSpellcasting constructor( } override fun renderBackground(guiGraphics: GuiGraphics, i: Int, j: Int, f: Float) { + guiGraphics.setColor(1f,1f,1f, (getPanDistance() / 15).coerceAtMost(1f)) + renderMenuBackgroundTexture(guiGraphics, bgLocation, 0, 0, 0f, 0f, this.width, this.height) + guiGraphics.setColor(1f, 1f, 1f, 1f) this.renderBlurredBackground(f) } diff --git a/Common/src/main/java/at/petrak/hexcasting/mixin/client/MixinGameRenderer.java b/Common/src/main/java/at/petrak/hexcasting/mixin/client/MixinGameRenderer.java index 14ea119a6f..9f1e1e2c34 100644 --- a/Common/src/main/java/at/petrak/hexcasting/mixin/client/MixinGameRenderer.java +++ b/Common/src/main/java/at/petrak/hexcasting/mixin/client/MixinGameRenderer.java @@ -20,7 +20,7 @@ private void scaleBlurWhenPanningGrid(PostChain instance, String string, float f GameRenderer renderer = (GameRenderer)(Object)this; Screen screen = renderer.getMinecraft().screen; if (screen instanceof GuiSpellcasting grid) { - original.call(instance, string, grid.getPanDistance() / 10); + original.call(instance, string, Math.min(grid.getPanDistance(), 12f)); } else { original.call(instance, string, f); } diff --git a/Common/src/main/resources/assets/hexcasting/textures/gui/casting_bg.png b/Common/src/main/resources/assets/hexcasting/textures/gui/casting_bg.png new file mode 100644 index 0000000000000000000000000000000000000000..7f94529482adf1f1b7f3f3cec70f72ca3191cf16 GIT binary patch literal 4767 zcmeHKc~BGS5+7kXjIaWVfP%z;Crpw_0wg0rxrD$55Tb%7lbHz&ki%pkfQz!a2!aQ? zt`}Z-tf(jtul44kh*zmBqOOaJimrI93O>9Z3hzrmz;853ulwuD zw6sd>T0y0dOgckDGRbsG z0YT}%Jzq2Hz=JVXId|jEdG?c7F8QMw-=nZN^}#RxyzYmhwe=qbQxLL{{MP2*+3wC2 z?VIkq&XoD)?s#V4fcr-LP9 z-EO?!P=}AJ52#o?;%3e8t!ua6pBnlr+w5HVp7CqWOjH)fKk8l|= z%Fkq&)uXEiHnFR7+J0I)wfN$wVDk$HT9=9Y-z~2hexz#no5bZSEs8w`Rb_u{8u_7h zk?7V`X8!yp#$2R~a9bLzy7AbYB`DkRG@^O?$=L;av$yW|EQ6y9e@zP$?CgjzxxLlQ z#s2v*i;LAGvj<~|!CZGHl;j?^ej75(*6s-;vqi%j%}sV(GWFNmU5tNla&h(ONhdr! z&br(?kD1$zi?d3}m;EzLQa;~PbTewoFFW?cNoN(@^c0a-L^EdR%Du`5e*+h-pHcp_ z@#2kxMJF7}atiL%)U7J8=j^P@M?6-0oZkO9D*3@E*vlIBNp1++>vDP5uKi;Dvxe}p zMY&cRUEKx_^Se}H;Sw7bg0^1Bb(z0Y<{cClv$xPd~qet$?aW*yhy-WSd#V0&P2{>%p-(E= z%@TTLn}@2hc92hdj20_y8*%LAorcN7o+Xs1>g__aSS{eIL>0U2!k3-&z1c{c@la(o{a6eZEq&K!i#QMx9-Zmd~um& z{9u#Cwm+`hJ-W8QDSdg_ym#+19qz|9G_^Ztg*6b_k?x!Er`|^t4_NXR9fc1fSA~z7=Css#KuJK( z!*`V(_w+w2u%uckqdNPALw&oWX=d7Yir|<`?5CrK~7JT-kS@BUsV^Z zKJ}vI#l!@4$7bsFThpTlwP)|zhPQ$tl}3SK6f6A(#nnnSMyTZ^J6)*(!wQ0YC!}jI zJej1Kax#HZiCHgCRIr#7A!beUl5(Y*Kr)dE&D4@HnGrHPGZ_~VtO)CofTdhstAR>{7!{u>!JQ!%ex(pSKrNb(nn*pK|BZ$=DT1rDx zY8BIfiOJQew3x*L>&#E_DK%1QH@r&M#RA}ila6UP2%F1MDmlF^baZeU0O<5!EApAk5Q#LZ+rmIW0;SX# ztpM4dY0{LUhpf+HGprcX=^Y3#@5cR1`%~^lWuPUMqCsjr)exQ}NX#<$M+r4f5vcK% zFT&*l93fz?z{d*~;3OZGixeVQ$jAA7FAP`k`QE*tBq|+^sc_N&1;E)9z#(~Z9|0l6 zV7?EZ4-2@64=j`ue3&5RJcNhgxSSC5f{4^opewPI-ccE#2mmF=d3-NI?gf+H2o4Jn zkpPwpF(ItLNdXu45h(=TolpdhPEu=?7$_&D#1cr3MwMXnVIUm!kCKR4JT|vS5|x6{ z3Sa>C0Hq?-dR@ z(gDJ%}{5ERp3*}W_U8;ZbaEC{1X0LtZgqg)}%M`T3lzgU zQ_x{)Bw>sbV0A9xiI^&Z1oNY-VL!>Kzi0*?mmm?Y5L6HmC}06!h`@3J!(f4&=Oq{N z5k%D04BhBDwSv}TTGBrO@Cdj94Qk|yIi{;rV?U2ZpGX?&04RgGLb!)AK8w?pEXOc0 zK4t66`7b_vjS9U+46y5zfzbsfA*Xv3cJT#b{X2hMb@)47FqwT$zKGvGy87t)A_l%l zxv#tW==vfCzDT*RyZ&!asimvgSnCDiiWVSpkp%h6gqLx;E}V+r3UQGr%SqOb fUh#F=m2wE$cBY^1`ABPt0a_9q5wt%be&K%r_>l1A literal 0 HcmV?d00001 From 853ee28e5484542daa5771e4d2ab5c02e443123d Mon Sep 17 00:00:00 2001 From: Robotgiggle Date: Mon, 31 Aug 2026 04:04:44 -0400 Subject: [PATCH 07/17] Save pan offset between staff usages --- .../petrak/hexcasting/api/utils/HexUtils.kt | 32 +++++++++++++++-- .../hexcasting/client/gui/GuiSpellcasting.kt | 16 +++++++-- .../hexcasting/common/items/ItemStaff.java | 3 +- .../common/msgs/MsgOpenSpellGuiS2C.java | 8 +++-- .../common/msgs/MsgPannedGridC2S.java | 36 +++++++++++++++++++ .../xplat/DummyXplatAbstractions.kt | 3 ++ .../hexcasting/xplat/IXplatAbstractions.java | 6 +++- .../hexcasting/fabric/cc/CCPanOffset.java | 33 +++++++++++++++++ .../fabric/cc/HexCardinalComponents.java | 3 ++ .../fabric/network/FabricPacketHandler.java | 3 ++ .../fabric/xplat/FabricXplatImpl.java | 14 ++++++++ Fabric/src/main/resources/fabric.mod.json | 1 + .../forge/network/ForgePacketHandler.java | 2 ++ .../forge/xplat/ForgeXplatImpl.java | 14 +++++++- 14 files changed, 164 insertions(+), 10 deletions(-) create mode 100644 Common/src/main/java/at/petrak/hexcasting/common/msgs/MsgPannedGridC2S.java create mode 100644 Fabric/src/main/java/at/petrak/hexcasting/fabric/cc/CCPanOffset.java diff --git a/Common/src/main/java/at/petrak/hexcasting/api/utils/HexUtils.kt b/Common/src/main/java/at/petrak/hexcasting/api/utils/HexUtils.kt index fdd0e828ba..a8c383416d 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/utils/HexUtils.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/utils/HexUtils.kt @@ -10,13 +10,17 @@ import at.petrak.hexcasting.api.casting.math.HexCoord import at.petrak.hexcasting.api.casting.validateSubIotas import at.petrak.hexcasting.api.mod.HexTags import com.mojang.datafixers.util.Function6 +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder import net.minecraft.ChatFormatting import net.minecraft.core.HolderLookup import net.minecraft.core.Registry import net.minecraft.nbt.* +import net.minecraft.network.RegistryFriendlyByteBuf import net.minecraft.network.chat.Component import net.minecraft.network.chat.MutableComponent import net.minecraft.network.chat.Style +import net.minecraft.network.codec.ByteBufCodecs import net.minecraft.network.codec.StreamCodec import net.minecraft.resources.ResourceKey import net.minecraft.resources.ResourceLocation @@ -61,14 +65,24 @@ fun vecFromNBT(tag: CompoundTag): Vec3 { Vec3(tag.getDouble("x"), tag.getDouble("y"), tag.getDouble("z")) } -fun Vec2.serializeToNBT(): LongArrayTag = - LongArrayTag(longArrayOf(this.x.toDouble().toRawBits(), this.y.toDouble().toRawBits())) +fun Vec2.serializeToNBT(): CompoundTag { + val tag = CompoundTag() + tag.putFloat("x", this.x) + tag.putFloat("y", this.y) + return tag +} fun vec2FromNBT(tag: LongArray): Vec2 = if (tag.size != 2) Vec2.ZERO else Vec2( Double.fromBits(tag[0]).toFloat(), Double.fromBits(tag[1]).toFloat(), ) +fun vec2FromNBT(tag: CompoundTag): Vec2 { + return if (!tag.contains("x") || !tag.contains("y")) + Vec2.ZERO + else + Vec2(tag.getFloat("x"), tag.getFloat("y")) +} fun otherHand(hand: InteractionHand) = if (hand == InteractionHand.MAIN_HAND) InteractionHand.OFF_HAND else InteractionHand.MAIN_HAND @@ -333,6 +347,20 @@ fun validateIotaList(iotaList: TreeList, serverLevel: ServerLevel) return iotaList.map { validateIota(it, serverLevel) } } +// why is there not already a codec defined for vec2 +@JvmField +val VEC2_CODEC: Codec = RecordCodecBuilder.create({ instance -> + instance.group( + Codec.FLOAT.fieldOf("x").forGetter { it.x }, + Codec.FLOAT.fieldOf("y").forGetter { it.y } + ).apply(instance, ::Vec2) +}).orElseGet({ Vec2.ZERO }) +@JvmField +val VEC2_STREAM_CODEC: StreamCodec = StreamCodec.composite( + ByteBufCodecs.FLOAT, Vec2::x, + ByteBufCodecs.FLOAT, Vec2::y, + ::Vec2 +) // vanilla's StreamCodec.composite() only supports up to six fields in 1.21 fun compositeCodecSeven( diff --git a/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt b/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt index 747f352c7f..f879076152 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt +++ b/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt @@ -23,6 +23,7 @@ import at.petrak.hexcasting.common.lib.HexAttributes import at.petrak.hexcasting.common.lib.HexSounds import at.petrak.hexcasting.common.lib.hex.HexActions import at.petrak.hexcasting.common.msgs.MsgNewSpellPatternC2S +import at.petrak.hexcasting.common.msgs.MsgPannedGridC2S import at.petrak.hexcasting.xplat.IClientXplatAbstractions import com.mojang.blaze3d.systems.RenderSystem import com.mojang.blaze3d.vertex.PoseStack @@ -32,7 +33,6 @@ import net.minecraft.client.gui.screens.Screen import net.minecraft.client.renderer.GameRenderer import net.minecraft.client.resources.sounds.SimpleSoundInstance import net.minecraft.client.resources.sounds.SoundInstance -import net.minecraft.resources.ResourceLocation import net.minecraft.sounds.SoundSource import net.minecraft.util.FormattedCharSequence import net.minecraft.util.Mth @@ -47,6 +47,7 @@ class GuiSpellcasting constructor( private var cachedStack: List, private var cachedRavenmind: Iota?, private var parenCount: Int, + private var panOffset: Vec2, ) : Screen("gui.hexcasting.spellcasting".asTranslatedComponent) { private var stackDescs: List = listOf() private var parenDescs: List = listOf() @@ -55,7 +56,7 @@ class GuiSpellcasting constructor( private var drawState: PatternDrawState = PatternDrawState.BetweenPatterns private val usedSpots: MutableSet = HashSet() - private var panOffset = Vec2.ZERO + private var prevPanOffset = Vec2.ZERO private val bgLocation = HexAPI.modLoc("textures/gui/casting_bg.png") private var ambianceSoundInstance: GridSoundInstance? = null @@ -215,6 +216,10 @@ class GuiSpellcasting constructor( private fun panGrid(pDragX: Double, pDragY: Double): Boolean { val shift = Vec2(pDragX.toFloat(), pDragY.toFloat()) this.panOffset = this.panOffset.add(shift) + if (panOffset.distanceToSqr(prevPanOffset) > 40000) { + IClientXplatAbstractions.INSTANCE.sendPacketToServer(MsgPannedGridC2S(panOffset)) + prevPanOffset = panOffset; + } return false; } @@ -290,8 +295,13 @@ class GuiSpellcasting constructor( } if (HexConfig.client().clickingTogglesDrawing()) return false - if (pButton == HexConfig.client().gridPanMouseButton()) + if (pButton == HexConfig.client().gridPanMouseButton()) { + if (panOffset != prevPanOffset) { + IClientXplatAbstractions.INSTANCE.sendPacketToServer(MsgPannedGridC2S(panOffset)) + prevPanOffset = panOffset; + } return false + } return drawEnd() } diff --git a/Common/src/main/java/at/petrak/hexcasting/common/items/ItemStaff.java b/Common/src/main/java/at/petrak/hexcasting/common/items/ItemStaff.java index 7ad1f4b5af..ee79020e44 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/items/ItemStaff.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/items/ItemStaff.java @@ -46,13 +46,14 @@ public InteractionResultHolder use(Level world, Player player, Intera if (!world.isClientSide() && player instanceof ServerPlayer serverPlayer) { var vm = IXplatAbstractions.INSTANCE.getStaffcastVM(serverPlayer, hand); var patterns = IXplatAbstractions.INSTANCE.getPatternsSavedInUi(serverPlayer); + var panOffset = IXplatAbstractions.INSTANCE.getPanOffset(serverPlayer); @Nullable Iota ravenmind = vm.getImage().ravenmind().orElse(null); IXplatAbstractions.INSTANCE.sendPacketToPlayer(serverPlayer, new MsgOpenSpellGuiS2C(hand, patterns, vm.getImage().getStack(), ravenmind, - 0)); // TODO: Fix! + 0, panOffset)); // TODO: Fix! } player.awardStat(Stats.ITEM_USED.get(this)); diff --git a/Common/src/main/java/at/petrak/hexcasting/common/msgs/MsgOpenSpellGuiS2C.java b/Common/src/main/java/at/petrak/hexcasting/common/msgs/MsgOpenSpellGuiS2C.java index db261af133..11f988d77b 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/msgs/MsgOpenSpellGuiS2C.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/msgs/MsgOpenSpellGuiS2C.java @@ -4,6 +4,7 @@ import at.petrak.hexcasting.api.casting.eval.ResolvedPattern; import at.petrak.hexcasting.api.casting.iota.Iota; import at.petrak.hexcasting.api.casting.iota.IotaType; +import at.petrak.hexcasting.api.utils.HexUtils; import at.petrak.hexcasting.client.gui.GuiSpellcasting; import net.minecraft.client.Minecraft; import net.minecraft.network.RegistryFriendlyByteBuf; @@ -11,6 +12,7 @@ import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; import net.minecraft.world.InteractionHand; +import net.minecraft.world.phys.Vec2; import org.jetbrains.annotations.Nullable; import java.util.List; @@ -23,7 +25,8 @@ public record MsgOpenSpellGuiS2C(InteractionHand hand, List pat List stack, @Nullable Iota ravenmind, - int parenCount + int parenCount, + Vec2 panOffset ) implements CustomPacketPayload { public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(HexAPI.modLoc("cgui")); @@ -40,6 +43,7 @@ public record MsgOpenSpellGuiS2C(InteractionHand hand, List pat Optional::ofNullable ), MsgOpenSpellGuiS2C::ravenmind, ByteBufCodecs.VAR_INT, MsgOpenSpellGuiS2C::parenCount, + HexUtils.VEC2_STREAM_CODEC, MsgOpenSpellGuiS2C::panOffset, MsgOpenSpellGuiS2C::new ); @@ -59,7 +63,7 @@ public static void handle(MsgOpenSpellGuiS2C msg) { var mc = Minecraft.getInstance(); mc.setScreen( new GuiSpellcasting(msg.hand(), msg.patterns(), msg.stack, msg.ravenmind, - msg.parenCount)); + msg.parenCount, msg.panOffset)); }); } } diff --git a/Common/src/main/java/at/petrak/hexcasting/common/msgs/MsgPannedGridC2S.java b/Common/src/main/java/at/petrak/hexcasting/common/msgs/MsgPannedGridC2S.java new file mode 100644 index 0000000000..8a96b08216 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/common/msgs/MsgPannedGridC2S.java @@ -0,0 +1,36 @@ +package at.petrak.hexcasting.common.msgs; + +import at.petrak.hexcasting.api.HexAPI; +import at.petrak.hexcasting.api.casting.eval.env.StaffCastEnv; +import at.petrak.hexcasting.api.utils.HexUtils; +import at.petrak.hexcasting.client.gui.GuiSpellcasting; +import at.petrak.hexcasting.xplat.IXplatAbstractions; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.phys.Vec2; +import org.joml.Vector3f; + +/** + * Sent client->server when the player pans the casting grid. + */ +public record MsgPannedGridC2S(Vec2 panOffset) implements CustomPacketPayload { + public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(HexAPI.modLoc("pan_cs")); + + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + HexUtils.VEC2_STREAM_CODEC, MsgPannedGridC2S::panOffset, + MsgPannedGridC2S::new + ); + + @Override + public Type type() { + return TYPE; + } + + public void handle(MinecraftServer server, ServerPlayer sender) { + server.execute(() -> IXplatAbstractions.INSTANCE.setPanOffset(sender, panOffset)); + } +} diff --git a/Common/src/main/java/at/petrak/hexcasting/xplat/DummyXplatAbstractions.kt b/Common/src/main/java/at/petrak/hexcasting/xplat/DummyXplatAbstractions.kt index f21675d6dc..4d36722bff 100644 --- a/Common/src/main/java/at/petrak/hexcasting/xplat/DummyXplatAbstractions.kt +++ b/Common/src/main/java/at/petrak/hexcasting/xplat/DummyXplatAbstractions.kt @@ -56,6 +56,7 @@ import net.minecraft.world.level.block.entity.BlockEntityType import net.minecraft.world.level.block.state.BlockState import net.minecraft.world.level.material.Fluid import net.minecraft.world.level.storage.loot.predicates.LootItemCondition +import net.minecraft.world.phys.Vec2 import net.minecraft.world.phys.Vec3 import java.util.function.BiFunction import java.util.function.Supplier @@ -88,12 +89,14 @@ internal class DummyXplatAbstractions: IXplatAbstractions { override fun setAltiora(target: Player, altiora: AltioraAbility?) = error("Found use of DummyXplatAbstractions.") override fun setStaffcastImage(target: ServerPlayer, image: CastingImage?) = error("Found use of DummyXplatAbstractions.") override fun setPatterns(target: ServerPlayer, patterns: List) = error("Found use of DummyXplatAbstractions.") + override fun setPanOffset(target: ServerPlayer, panOffset: Vec2) = error("Found use of DummyXplatAbstractions.") override fun getFlight(player: ServerPlayer): FlightAbility? = error("Found use of DummyXplatAbstractions.") override fun getAltiora(player: Player): AltioraAbility? = error("Found use of DummyXplatAbstractions.") override fun getPigment(player: Player): FrozenPigment = error("Found use of DummyXplatAbstractions.") override fun getSentinel(player: Player): Sentinel? = error("Found use of DummyXplatAbstractions.") override fun getStaffcastVM(player: ServerPlayer, hand: InteractionHand): CastingVM = error("Found use of DummyXplatAbstractions.") override fun getPatternsSavedInUi(player: ServerPlayer): List = error("Found use of DummyXplatAbstractions.") + override fun getPanOffset(player: ServerPlayer): Vec2? = error("Found use of DummyXplatAbstractions.") override fun clearCastingData(player: ServerPlayer) = error("Found use of DummyXplatAbstractions.") override fun findMediaHolder(stack: ItemStack): ADMediaHolder? = error("Found use of DummyXplatAbstractions.") override fun findDataHolder(stack: ItemStack): ADIotaHolder? = error("Found use of DummyXplatAbstractions.") diff --git a/Common/src/main/java/at/petrak/hexcasting/xplat/IXplatAbstractions.java b/Common/src/main/java/at/petrak/hexcasting/xplat/IXplatAbstractions.java index c2cc4ec351..d64a582211 100644 --- a/Common/src/main/java/at/petrak/hexcasting/xplat/IXplatAbstractions.java +++ b/Common/src/main/java/at/petrak/hexcasting/xplat/IXplatAbstractions.java @@ -38,7 +38,6 @@ import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.Tier; import net.minecraft.world.item.crafting.Ingredient; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Block; @@ -47,6 +46,7 @@ import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.material.Fluid; import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; +import net.minecraft.world.phys.Vec2; import net.minecraft.world.phys.Vec3; import org.jetbrains.annotations.Nullable; @@ -103,6 +103,8 @@ public interface IXplatAbstractions { void setPatterns(ServerPlayer target, List patterns); + void setPanOffset(ServerPlayer target, Vec2 panOffset); + @Nullable FlightAbility getFlight(ServerPlayer player); @Nullable AltioraAbility getAltiora(Player player); @@ -115,6 +117,8 @@ public interface IXplatAbstractions { List getPatternsSavedInUi(ServerPlayer player); + Vec2 getPanOffset(ServerPlayer player); + void clearCastingData(ServerPlayer player); @Nullable diff --git a/Fabric/src/main/java/at/petrak/hexcasting/fabric/cc/CCPanOffset.java b/Fabric/src/main/java/at/petrak/hexcasting/fabric/cc/CCPanOffset.java new file mode 100644 index 0000000000..6af7bed9b0 --- /dev/null +++ b/Fabric/src/main/java/at/petrak/hexcasting/fabric/cc/CCPanOffset.java @@ -0,0 +1,33 @@ +package at.petrak.hexcasting.fabric.cc; + +import at.petrak.hexcasting.api.utils.HexUtils; +import net.minecraft.core.HolderLookup; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.phys.Vec2; +import org.ladysnake.cca.api.v3.component.Component; +import org.ladysnake.cca.api.v3.component.sync.AutoSyncedComponent; + +public class CCPanOffset implements Component, AutoSyncedComponent { + public static final String TAG_PAN_OFFSET = "pan_offset"; + + private final Player owner; + private Vec2 panOffset = Vec2.ZERO; + + public CCPanOffset(ServerPlayer owner) { + this.owner = owner; + } + + public Vec2 getPanOffset() { return panOffset; } + public void setPanOffset(Vec2 newOffset) { this.panOffset = newOffset; } + + @Override + public void readFromNbt(CompoundTag tag, HolderLookup.Provider registryLookup) { + this.panOffset = HexUtils.vec2FromNBT(tag.getCompound(TAG_PAN_OFFSET)); + } + + public void writeToNbt(CompoundTag tag, HolderLookup.Provider registryLookup) { + tag.put(TAG_PAN_OFFSET, HexUtils.serializeToNBT(this.panOffset)); + } +} diff --git a/Fabric/src/main/java/at/petrak/hexcasting/fabric/cc/HexCardinalComponents.java b/Fabric/src/main/java/at/petrak/hexcasting/fabric/cc/HexCardinalComponents.java index b60bc9a1c6..9dd5c9e10e 100644 --- a/Fabric/src/main/java/at/petrak/hexcasting/fabric/cc/HexCardinalComponents.java +++ b/Fabric/src/main/java/at/petrak/hexcasting/fabric/cc/HexCardinalComponents.java @@ -41,6 +41,8 @@ public class HexCardinalComponents implements EntityComponentInitializer, ItemCo CCStaffcastImage.class); public static final ComponentKey PATTERNS = ComponentRegistry.getOrCreate(modLoc("patterns"), CCPatterns.class); + public static final ComponentKey PAN_OFFSET = ComponentRegistry.getOrCreate(modLoc("pan_offset"), + CCPanOffset.class); public static final ComponentKey CLIENT_CASTING_STACK = ComponentRegistry.getOrCreate(modLoc("client_casting_stack"), CCClientCastingStack.class); @@ -78,6 +80,7 @@ public void registerEntityComponentFactories(EntityComponentFactoryRegistry regi registry.registerFor(ServerPlayer.class, FLIGHT, CCFlight::new); registry.registerFor(ServerPlayer.class, STAFFCAST_IMAGE, CCStaffcastImage::new); registry.registerFor(ServerPlayer.class, PATTERNS, CCPatterns::new); + registry.registerFor(ServerPlayer.class, PAN_OFFSET, CCPanOffset::new); registry.registerFor(ItemEntity.class, IOTA_HOLDER, wrapItemEntityDelegate( diff --git a/Fabric/src/main/java/at/petrak/hexcasting/fabric/network/FabricPacketHandler.java b/Fabric/src/main/java/at/petrak/hexcasting/fabric/network/FabricPacketHandler.java index c7e2346e9b..8ee9e166a5 100644 --- a/Fabric/src/main/java/at/petrak/hexcasting/fabric/network/FabricPacketHandler.java +++ b/Fabric/src/main/java/at/petrak/hexcasting/fabric/network/FabricPacketHandler.java @@ -16,6 +16,7 @@ public static void initPackets() { PayloadTypeRegistry.playC2S().register(MsgShiftScrollC2S.TYPE, MsgShiftScrollC2S.STREAM_CODEC); PayloadTypeRegistry.playC2S().register(MsgNewSpellPatternC2S.TYPE, MsgNewSpellPatternC2S.STREAM_CODEC); PayloadTypeRegistry.playS2C().register(MsgNewSpellPatternS2C.TYPE, MsgNewSpellPatternS2C.STREAM_CODEC); + PayloadTypeRegistry.playC2S().register(MsgPannedGridC2S.TYPE, MsgPannedGridC2S.STREAM_CODEC); PayloadTypeRegistry.playS2C().register(MsgOpenSpellGuiS2C.TYPE, MsgOpenSpellGuiS2C.STREAM_CODEC); PayloadTypeRegistry.playS2C().register(MsgBeepS2C.TYPE, MsgBeepS2C.STREAM_CODEC); PayloadTypeRegistry.playS2C().register(MsgShiftScrollC2S.TYPE, MsgShiftScrollC2S.STREAM_CODEC); @@ -31,6 +32,8 @@ public static void init() { makeServerBoundHandler(MsgShiftScrollC2S::handle)); ServerPlayNetworking.registerGlobalReceiver(MsgNewSpellPatternC2S.TYPE, makeServerBoundHandler(MsgNewSpellPatternC2S::handle)); + ServerPlayNetworking.registerGlobalReceiver(MsgPannedGridC2S.TYPE, + makeServerBoundHandler(MsgPannedGridC2S::handle)); } private static ServerPlayNetworking.PlayPayloadHandler makeServerBoundHandler( diff --git a/Fabric/src/main/java/at/petrak/hexcasting/fabric/xplat/FabricXplatImpl.java b/Fabric/src/main/java/at/petrak/hexcasting/fabric/xplat/FabricXplatImpl.java index fbad939683..eda1e88482 100644 --- a/Fabric/src/main/java/at/petrak/hexcasting/fabric/xplat/FabricXplatImpl.java +++ b/Fabric/src/main/java/at/petrak/hexcasting/fabric/xplat/FabricXplatImpl.java @@ -80,6 +80,7 @@ import net.minecraft.world.level.storage.loot.predicates.AnyOfCondition; import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; import net.minecraft.world.level.storage.loot.predicates.MatchTool; +import net.minecraft.world.phys.Vec2; import net.minecraft.world.phys.Vec3; import org.jetbrains.annotations.Nullable; import virtuoel.pehkui.api.ScaleTypes; @@ -189,6 +190,12 @@ public void setPatterns(ServerPlayer target, List patterns) { cc.setPatterns(patterns); } + @Override + public void setPanOffset(ServerPlayer target, Vec2 panOffset) { + var cc = HexCardinalComponents.PAN_OFFSET.get(target); + cc.setPanOffset(panOffset); + } + @Override public boolean isBrainswept(Mob mob) { var cc = HexCardinalComponents.BRAINSWEPT.get(mob); @@ -231,10 +238,17 @@ public List getPatternsSavedInUi(ServerPlayer player) { return cc.getPatterns(); } + @Override + public Vec2 getPanOffset(ServerPlayer player) { + var cc = HexCardinalComponents.PAN_OFFSET.get(player); + return cc.getPanOffset(); + } + @Override public void clearCastingData(ServerPlayer player) { this.setStaffcastImage(player, null); this.setPatterns(player, List.of()); + this.setPanOffset(player, Vec2.ZERO); } @Override diff --git a/Fabric/src/main/resources/fabric.mod.json b/Fabric/src/main/resources/fabric.mod.json index 053571f52b..798dec41db 100644 --- a/Fabric/src/main/resources/fabric.mod.json +++ b/Fabric/src/main/resources/fabric.mod.json @@ -77,6 +77,7 @@ "hexcasting:altiora", "hexcasting:harness", "hexcasting:patterns", + "hexcasting:pan_offset", "hexcasting:client_casting_stack", "hexcasting:pigment", "hexcasting:iota_holder", diff --git a/Neoforge/src/main/java/at/petrak/hexcasting/forge/network/ForgePacketHandler.java b/Neoforge/src/main/java/at/petrak/hexcasting/forge/network/ForgePacketHandler.java index c53a796a4e..5c95098709 100644 --- a/Neoforge/src/main/java/at/petrak/hexcasting/forge/network/ForgePacketHandler.java +++ b/Neoforge/src/main/java/at/petrak/hexcasting/forge/network/ForgePacketHandler.java @@ -21,6 +21,8 @@ public static void init(IEventBus modBus) { // Client -> server registar.playToServer(MsgNewSpellPatternC2S.TYPE, MsgNewSpellPatternC2S.STREAM_CODEC, makeServerBoundHandler(MsgNewSpellPatternC2S::handle)); + registar.playToServer(MsgPannedGridC2S.TYPE, MsgPannedGridC2S.STREAM_CODEC, + makeServerBoundHandler(MsgPannedGridC2S::handle)); registar.playToServer(MsgShiftScrollC2S.TYPE, MsgShiftScrollC2S.STREAM_CODEC, makeServerBoundHandler(MsgShiftScrollC2S::handle)); diff --git a/Neoforge/src/main/java/at/petrak/hexcasting/forge/xplat/ForgeXplatImpl.java b/Neoforge/src/main/java/at/petrak/hexcasting/forge/xplat/ForgeXplatImpl.java index 19d0b90813..33112f9120 100644 --- a/Neoforge/src/main/java/at/petrak/hexcasting/forge/xplat/ForgeXplatImpl.java +++ b/Neoforge/src/main/java/at/petrak/hexcasting/forge/xplat/ForgeXplatImpl.java @@ -60,7 +60,6 @@ import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.Tier; import net.minecraft.world.item.crafting.Ingredient; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Block; @@ -70,6 +69,7 @@ import net.minecraft.world.level.material.Fluid; import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.Vec2; import net.minecraft.world.phys.Vec3; import net.neoforged.api.distmarker.Dist; import net.neoforged.fml.ModContainer; @@ -232,6 +232,11 @@ public void setPatterns(ServerPlayer player, List patterns) { player.getPersistentData().put(TAG_PATTERNS, ResolvedPattern.CODEC.listOf().encodeStart(NbtOps.INSTANCE, patterns).getOrThrow()); } + @Override + public void setPanOffset(ServerPlayer player, Vec2 panOffset) { + player.getPersistentData().put(TAG_PAN_OFFSET, HexUtils.VEC2_CODEC.encodeStart(NbtOps.INSTANCE, panOffset).getOrThrow()); + } + @Override public boolean isBrainswept(Mob e) { return e.getPersistentData().getBoolean(TAG_BRAINSWEPT); @@ -295,10 +300,16 @@ public List getPatternsSavedInUi(ServerPlayer player) { return ResolvedPattern.CODEC.listOf().parse(NbtOps.INSTANCE, player.getPersistentData().getList(TAG_PATTERNS, Tag.TAG_COMPOUND)).getOrThrow(); } + @Override + public Vec2 getPanOffset(ServerPlayer player) { + return HexUtils.VEC2_CODEC.parse(NbtOps.INSTANCE, player.getPersistentData().getCompound(TAG_PAN_OFFSET)).getOrThrow(); + } + @Override public void clearCastingData(ServerPlayer player) { player.getPersistentData().remove(TAG_VM); player.getPersistentData().remove(TAG_PATTERNS); + player.getPersistentData().remove(TAG_PAN_OFFSET); } @Override @@ -572,4 +583,5 @@ public void setScale(Entity e, float scale) { public static final String TAG_VM = "hexcasting:spell_harness"; public static final String TAG_PATTERNS = "hexcasting:spell_patterns"; + public static final String TAG_PAN_OFFSET = "hexcasting:pan_offset"; } From dc80bb798ef00bf795b7bd5a5d5a5fd5625f79ca Mon Sep 17 00:00:00 2001 From: Robotgiggle Date: Mon, 31 Aug 2026 15:13:41 -0400 Subject: [PATCH 08/17] Cull offscreen patterns --- .../at/petrak/hexcasting/client/gui/GuiSpellcasting.kt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt b/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt index f879076152..c63a06a749 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt +++ b/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt @@ -438,12 +438,13 @@ class GuiSpellcasting constructor( for ((idx, elts) in this.patterns.withIndex()) { val (pat, origin, valid) = elts + val points = pat.toLines(this.hexSize(), this.coordToPx(origin)) + val center = Vec2(graphics.guiWidth() / 2f, graphics.guiHeight() / 2f) + if (points.all{ point -> point.distanceToSqr(center) > center.lengthSquared()+2500 }) + continue // don't render the pattern if it's completely offscreen drawPatternFromPoints( mat, - pat.toLines( - this.hexSize(), - this.coordToPx(origin) - ), + points, findDupIndices(pat.positions()), true, valid.color or (0xC8 shl 24), From c89444968c369cc0290813c9ae2ebed61573cb1d Mon Sep 17 00:00:00 2001 From: Robotgiggle Date: Mon, 31 Aug 2026 20:17:07 -0400 Subject: [PATCH 09/17] Dissociation debuff when panning too far # Conflicts: # Common/src/generated/resources/data/minecraft/tags/damage_type/no_knockback.json # Common/src/main/java/at/petrak/hexcasting/common/lib/HexMobEffects.java # Common/src/main/java/at/petrak/hexcasting/mixin/MixinLivingEntity.java # Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 # Neoforge/src/generated/resources/data/minecraft/tags/damage_type/no_knockback.json --- .../damage_type/forgot_to_breathe.json | 5 ++ .../tags/damage_type/bypasses_armor.json | 3 +- .../tags/damage_type/bypasses_effects.json | 3 +- .../tags/damage_type/no_knockback.json | 3 +- .../hexcasting/client/gui/GuiSpellcasting.kt | 28 ++++++---- .../common/effects/DissociationEffect.java | 52 ++++++++++++++++++ .../hexcasting/common/lib/HexDamageTypes.java | 7 +++ .../hexcasting/common/lib/HexMobEffects.java | 4 +- .../common/msgs/MsgPannedGridC2S.java | 17 +++++- .../datagen/tag/HexDamageTypeTagProvider.java | 5 ++ .../hexcasting/mixin/MixinLivingEntity.java | 15 +++-- .../hexcasting/lang/en_us.flatten.json5 | 7 ++- .../textures/mob_effect/dissociation.png | Bin 0 -> 15689 bytes Common/src/main/resources/hexplat.mixins.json | 1 + .../fabric/FabricHexClientInitializer.kt | 4 +- .../damage_type/forgot_to_breathe.json | 5 ++ .../tags/damage_type/bypasses_armor.json | 3 +- .../tags/damage_type/bypasses_effects.json | 3 +- .../tags/damage_type/no_knockback.json | 3 +- .../forge/ForgeHexClientInitializer.java | 2 + 20 files changed, 144 insertions(+), 26 deletions(-) create mode 100644 Common/src/generated/resources/data/hexcasting/damage_type/forgot_to_breathe.json create mode 100644 Common/src/main/java/at/petrak/hexcasting/common/effects/DissociationEffect.java create mode 100644 Common/src/main/resources/assets/hexcasting/textures/mob_effect/dissociation.png create mode 100644 Neoforge/src/generated/resources/data/hexcasting/damage_type/forgot_to_breathe.json diff --git a/Common/src/generated/resources/data/hexcasting/damage_type/forgot_to_breathe.json b/Common/src/generated/resources/data/hexcasting/damage_type/forgot_to_breathe.json new file mode 100644 index 0000000000..d3a0ca991f --- /dev/null +++ b/Common/src/generated/resources/data/hexcasting/damage_type/forgot_to_breathe.json @@ -0,0 +1,5 @@ +{ + "exhaustion": 0.0, + "message_id": "hexcasting.forgot_to_breathe", + "scaling": "when_caused_by_living_non_player" +} \ No newline at end of file diff --git a/Common/src/generated/resources/data/minecraft/tags/damage_type/bypasses_armor.json b/Common/src/generated/resources/data/minecraft/tags/damage_type/bypasses_armor.json index 4209c89aa3..319bb24df4 100644 --- a/Common/src/generated/resources/data/minecraft/tags/damage_type/bypasses_armor.json +++ b/Common/src/generated/resources/data/minecraft/tags/damage_type/bypasses_armor.json @@ -1,5 +1,6 @@ { "values": [ - "hexcasting:overcast" + "hexcasting:overcast", + "hexcasting:forgot_to_breathe" ] } \ No newline at end of file diff --git a/Common/src/generated/resources/data/minecraft/tags/damage_type/bypasses_effects.json b/Common/src/generated/resources/data/minecraft/tags/damage_type/bypasses_effects.json index 4209c89aa3..319bb24df4 100644 --- a/Common/src/generated/resources/data/minecraft/tags/damage_type/bypasses_effects.json +++ b/Common/src/generated/resources/data/minecraft/tags/damage_type/bypasses_effects.json @@ -1,5 +1,6 @@ { "values": [ - "hexcasting:overcast" + "hexcasting:overcast", + "hexcasting:forgot_to_breathe" ] } \ No newline at end of file diff --git a/Common/src/generated/resources/data/minecraft/tags/damage_type/no_knockback.json b/Common/src/generated/resources/data/minecraft/tags/damage_type/no_knockback.json index 4209c89aa3..319bb24df4 100644 --- a/Common/src/generated/resources/data/minecraft/tags/damage_type/no_knockback.json +++ b/Common/src/generated/resources/data/minecraft/tags/damage_type/no_knockback.json @@ -1,5 +1,6 @@ { "values": [ - "hexcasting:overcast" + "hexcasting:overcast", + "hexcasting:forgot_to_breathe" ] } \ No newline at end of file diff --git a/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt b/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt index c63a06a749..73f0477713 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt +++ b/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt @@ -74,6 +74,13 @@ class GuiSpellcasting constructor( return panOffset.length() / 20 } + fun syncPanDistance() { + if (panOffset != prevPanOffset || ClientTickCounter.ticksInGame % 10 == 0L) { + IClientXplatAbstractions.INSTANCE.sendPacketToServer(MsgPannedGridC2S(panOffset)) + prevPanOffset = panOffset; + } + } + fun recvServerUpdate(info: ExecutionClientView, index: Int) { if (info.isStackClear) { this.minecraft?.setScreen(null) @@ -215,11 +222,11 @@ class GuiSpellcasting constructor( private fun panGrid(pDragX: Double, pDragY: Double): Boolean { val shift = Vec2(pDragX.toFloat(), pDragY.toFloat()) - this.panOffset = this.panOffset.add(shift) - if (panOffset.distanceToSqr(prevPanOffset) > 40000) { - IClientXplatAbstractions.INSTANCE.sendPacketToServer(MsgPannedGridC2S(panOffset)) - prevPanOffset = panOffset; - } + val newOffset = this.panOffset.add(shift) + if (newOffset.lengthSquared() < 900*900) + this.panOffset = newOffset + else + this.panOffset = newOffset.normalized().scale(900f); return false; } @@ -295,13 +302,8 @@ class GuiSpellcasting constructor( } if (HexConfig.client().clickingTogglesDrawing()) return false - if (pButton == HexConfig.client().gridPanMouseButton()) { - if (panOffset != prevPanOffset) { - IClientXplatAbstractions.INSTANCE.sendPacketToServer(MsgPannedGridC2S(panOffset)) - prevPanOffset = panOffset; - } + if (pButton == HexConfig.client().gridPanMouseButton()) return false - } return drawEnd() } @@ -632,5 +634,9 @@ class GuiSpellcasting constructor( renderQuad(ps, x, y, w, h, 0x50_303030) renderQuad(ps, x + leftMargin, y + 2.5f, w - leftMargin - 2.5f, h - 5f, 0x50_303030) } + + fun clientTickEnd(screen: Screen?) { + if (screen is GuiSpellcasting) screen.syncPanDistance() + } } } diff --git a/Common/src/main/java/at/petrak/hexcasting/common/effects/DissociationEffect.java b/Common/src/main/java/at/petrak/hexcasting/common/effects/DissociationEffect.java new file mode 100644 index 0000000000..3cec7286de --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/common/effects/DissociationEffect.java @@ -0,0 +1,52 @@ +package at.petrak.hexcasting.common.effects; + +import at.petrak.hexcasting.api.HexAPI; +import at.petrak.hexcasting.common.lib.HexDamageTypes; +import net.minecraft.world.effect.MobEffect; +import net.minecraft.world.effect.MobEffectCategory; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.ai.attributes.AttributeModifier; +import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.entity.player.Player; + +public class DissociationEffect extends MobEffect { + public static final int AMP_0_DURATION = 300; + public static final int AMP_1_DURATION = 600; + + public DissociationEffect() { + super(MobEffectCategory.HARMFUL, 0x8932b8); + this.addAttributeModifier(Attributes.MOVEMENT_SPEED, HexAPI.modLoc("dissociation.slow"), -0.2, AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL); + this.addAttributeModifier(Attributes.BLOCK_BREAK_SPEED, HexAPI.modLoc("dissociation.fatigue.a"), -0.45, AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL); + this.addAttributeModifier(Attributes.ATTACK_SPEED, HexAPI.modLoc("dissociation.fatigue.b"), -0.1, AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL); + this.addAttributeModifier(Attributes.ATTACK_DAMAGE, HexAPI.modLoc("dissociation.weak"), -4.0, AttributeModifier.Operation.ADD_VALUE); + } + + public static boolean shouldPreventBreathing(int duration, int amplifier) { + return switch (amplifier) { + case 0 -> duration > AMP_0_DURATION - 20; + case 1 -> duration > AMP_1_DURATION - 20; + default -> false; + }; + } + + @Override + public boolean applyEffectTick(LivingEntity entity, int amplifier) { + if (((Player)entity).getAbilities().invulnerable) return true; + entity.setAirSupply(entity.getAirSupply() - 1); + if (entity.getAirSupply() == -4) { + entity.setAirSupply(0); + entity.hurt(entity.damageSources().source(HexDamageTypes.FORGOT_TO_BREATHE), 2.0F); + } + return true; + } + + @Override + public boolean shouldApplyEffectTickThisTick(int duration, int amplifier) { + if (!shouldPreventBreathing(duration, amplifier)) return false; + return switch (amplifier) { + case 0 -> duration % 10 == 0; + case 1 -> duration % 5 == 0; + default -> false; + }; + } +} diff --git a/Common/src/main/java/at/petrak/hexcasting/common/lib/HexDamageTypes.java b/Common/src/main/java/at/petrak/hexcasting/common/lib/HexDamageTypes.java index 6bd0f900fc..cdb9c5bb9a 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/lib/HexDamageTypes.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/lib/HexDamageTypes.java @@ -3,6 +3,7 @@ import net.minecraft.core.registries.Registries; import net.minecraft.data.worldgen.BootstrapContext; import net.minecraft.resources.ResourceKey; +import net.minecraft.world.damagesource.DamageEffects; import net.minecraft.world.damagesource.DamageScaling; import net.minecraft.world.damagesource.DamageType; @@ -10,6 +11,7 @@ public class HexDamageTypes { public static final ResourceKey OVERCAST = ResourceKey.create(Registries.DAMAGE_TYPE, modLoc("overcast")); + public static final ResourceKey FORGOT_TO_BREATHE = ResourceKey.create(Registries.DAMAGE_TYPE, modLoc("forgot_to_breathe")); public static void bootstrap(BootstrapContext ctx) { ctx.register(OVERCAST, new DamageType( @@ -17,5 +19,10 @@ public static void bootstrap(BootstrapContext ctx) { DamageScaling.WHEN_CAUSED_BY_LIVING_NON_PLAYER, 0f )); + ctx.register(FORGOT_TO_BREATHE, new DamageType( + "hexcasting.forgot_to_breathe", + DamageScaling.WHEN_CAUSED_BY_LIVING_NON_PLAYER, + 0f + )); } } diff --git a/Common/src/main/java/at/petrak/hexcasting/common/lib/HexMobEffects.java b/Common/src/main/java/at/petrak/hexcasting/common/lib/HexMobEffects.java index 30fc8f0299..3a5586c616 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/lib/HexMobEffects.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/lib/HexMobEffects.java @@ -1,6 +1,7 @@ package at.petrak.hexcasting.common.lib; import at.petrak.hexcasting.api.HexAPI; +import at.petrak.hexcasting.common.effects.DissociationEffect; import at.petrak.hexcasting.common.misc.HexMobEffect; import at.petrak.hexcasting.common.particles.ConjureParticleOptions; import at.petrak.hexcasting.xplat.IXplatAbstractions; @@ -22,10 +23,11 @@ public static void register() { public static final Holder ENLARGE_GRID = REGISTER.registerHolder("enlarge_grid", () -> new HexMobEffect(MobEffectCategory.BENEFICIAL, 0xc875ff).addAttributeModifier(HexAttributes.GRID_ZOOM, HexAPI.modLoc("enlarge_grid"), - 0.25, AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL)); + 0.25, AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL)); public static final Holder SHRINK_GRID = REGISTER.registerHolder("shrink_grid", () -> new HexMobEffect(MobEffectCategory.HARMFUL, 0xc0e660).addAttributeModifier(HexAttributes.GRID_ZOOM, HexAPI.modLoc("shrink_grid"), -0.2, AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL)); public static final Holder CRYSTALLIZED = REGISTER.registerHolder("crystallized", () -> new HexMobEffect(MobEffectCategory.HARMFUL, 0x8932b8, new ConjureParticleOptions(0x8932b8, true))); + public static final Holder DISSOCIATION = REGISTER.registerHolder("dissociation", DissociationEffect::new); } diff --git a/Common/src/main/java/at/petrak/hexcasting/common/msgs/MsgPannedGridC2S.java b/Common/src/main/java/at/petrak/hexcasting/common/msgs/MsgPannedGridC2S.java index 8a96b08216..68949ff028 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/msgs/MsgPannedGridC2S.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/msgs/MsgPannedGridC2S.java @@ -4,6 +4,9 @@ import at.petrak.hexcasting.api.casting.eval.env.StaffCastEnv; import at.petrak.hexcasting.api.utils.HexUtils; import at.petrak.hexcasting.client.gui.GuiSpellcasting; +import at.petrak.hexcasting.common.effects.DissociationEffect; +import at.petrak.hexcasting.common.lib.HexMobEffects; +import at.petrak.hexcasting.common.misc.HexMobEffect; import at.petrak.hexcasting.xplat.IXplatAbstractions; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.codec.ByteBufCodecs; @@ -11,6 +14,7 @@ import net.minecraft.network.protocol.common.custom.CustomPacketPayload; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.effect.MobEffectInstance; import net.minecraft.world.phys.Vec2; import org.joml.Vector3f; @@ -31,6 +35,17 @@ public Type type() { } public void handle(MinecraftServer server, ServerPlayer sender) { - server.execute(() -> IXplatAbstractions.INSTANCE.setPanOffset(sender, panOffset)); + server.execute(() -> { + IXplatAbstractions.INSTANCE.setPanOffset(sender, panOffset); + var inst = sender.getEffect(HexMobEffects.DISSOCIATION); + int currentDur = (inst != null) ? inst.getDuration() : 0; + if (panOffset.lengthSquared() > 600*600) { + int newDur = Math.min(currentDur + 80, DissociationEffect.AMP_1_DURATION); + sender.addEffect(new MobEffectInstance(HexMobEffects.DISSOCIATION, newDur, 1, false, false, true)); + } else if (panOffset.lengthSquared() > 300*300) { + int newDur = Math.min(currentDur + 40, DissociationEffect.AMP_0_DURATION); + sender.addEffect(new MobEffectInstance(HexMobEffects.DISSOCIATION, newDur, 0, false, false, true)); + } + }); } } diff --git a/Common/src/main/java/at/petrak/hexcasting/datagen/tag/HexDamageTypeTagProvider.java b/Common/src/main/java/at/petrak/hexcasting/datagen/tag/HexDamageTypeTagProvider.java index 220598df0c..77be411044 100644 --- a/Common/src/main/java/at/petrak/hexcasting/datagen/tag/HexDamageTypeTagProvider.java +++ b/Common/src/main/java/at/petrak/hexcasting/datagen/tag/HexDamageTypeTagProvider.java @@ -25,6 +25,11 @@ protected void addTags(@NotNull HolderLookup.Provider provider) { DamageTypeTags.BYPASSES_SHIELD, DamageTypeTags.NO_KNOCKBACK ); + add(HexDamageTypes.FORGOT_TO_BREATHE, + DamageTypeTags.BYPASSES_ARMOR, + DamageTypeTags.BYPASSES_EFFECTS, + DamageTypeTags.NO_KNOCKBACK + ); } @SafeVarargs diff --git a/Common/src/main/java/at/petrak/hexcasting/mixin/MixinLivingEntity.java b/Common/src/main/java/at/petrak/hexcasting/mixin/MixinLivingEntity.java index 4b85154bc5..38d215f17c 100644 --- a/Common/src/main/java/at/petrak/hexcasting/mixin/MixinLivingEntity.java +++ b/Common/src/main/java/at/petrak/hexcasting/mixin/MixinLivingEntity.java @@ -1,13 +1,9 @@ package at.petrak.hexcasting.mixin; -import at.petrak.hexcasting.api.HexAPI; import at.petrak.hexcasting.common.lib.HexItems; import at.petrak.hexcasting.common.lib.HexMobEffects; -import at.petrak.hexcasting.common.misc.HexMobEffect; -import at.petrak.hexcasting.xplat.IXplatAbstractions; +import at.petrak.hexcasting.common.effects.DissociationEffect; import net.minecraft.world.entity.LivingEntity; -import net.minecraft.world.entity.Mob; -import net.minecraft.world.entity.ai.Brain; import net.minecraft.world.entity.npc.Villager; import net.minecraft.world.item.ItemStack; import org.spongepowered.asm.mixin.Mixin; @@ -37,4 +33,13 @@ private void dropCrystallizedLoot(CallbackInfo ci) { self.spawnAtLocation(new ItemStack(HexItems.AMETHYST_DUST.get(), dust + extra)); } } + + @Inject(method = "increaseAirSupply", at = @At("HEAD"), cancellable = true) + private void forgetToBreathe(int i, CallbackInfoReturnable cir) { + var self = (LivingEntity) (Object) this; + var inst = self.getEffect(HexMobEffects.DISSOCIATION); + if (inst != null && DissociationEffect.shouldPreventBreathing(inst.getDuration(), inst.getAmplifier())) { + cir.setReturnValue(i); + } + } } diff --git a/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 b/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 index 44cb51a551..0c6206b14a 100644 --- a/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 +++ b/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 @@ -229,7 +229,8 @@ "effect.hexcasting": { enlarge_grid: "Clarity", shrink_grid: "Clouding", - crystallized: "Crystallized" + crystallized: "Crystallized", + dissociation: "Dissociation", }, "item.minecraft.potion.effect": { @@ -538,6 +539,10 @@ "": "%s's mind was subsumed into energy", player: "%s's mind was subsumed into energy while fighting %s", }, + forgot_to_breathe: { + "": "%s forgot to breathe", + player: "%s forgot to breathe while fighting %s", + } }, "command.hexcasting": { diff --git a/Common/src/main/resources/assets/hexcasting/textures/mob_effect/dissociation.png b/Common/src/main/resources/assets/hexcasting/textures/mob_effect/dissociation.png new file mode 100644 index 0000000000000000000000000000000000000000..2d90323597330048fccf095a1345a6c6b36d2c11 GIT binary patch literal 15689 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 ClientTickCounter.clientTickEnd() Keybinds.clientTickEnd() ShiftScrollListener.clientTickEnd() + GuiSpellcasting.clientTickEnd(ctx.screen) } TooltipComponentCallback.EVENT.register(PatternTooltipComponent::tryConvert) ClientPlayConnectionEvents.JOIN.register { _, _, _ -> diff --git a/Neoforge/src/generated/resources/data/hexcasting/damage_type/forgot_to_breathe.json b/Neoforge/src/generated/resources/data/hexcasting/damage_type/forgot_to_breathe.json new file mode 100644 index 0000000000..d3a0ca991f --- /dev/null +++ b/Neoforge/src/generated/resources/data/hexcasting/damage_type/forgot_to_breathe.json @@ -0,0 +1,5 @@ +{ + "exhaustion": 0.0, + "message_id": "hexcasting.forgot_to_breathe", + "scaling": "when_caused_by_living_non_player" +} \ No newline at end of file diff --git a/Neoforge/src/generated/resources/data/minecraft/tags/damage_type/bypasses_armor.json b/Neoforge/src/generated/resources/data/minecraft/tags/damage_type/bypasses_armor.json index 4209c89aa3..319bb24df4 100644 --- a/Neoforge/src/generated/resources/data/minecraft/tags/damage_type/bypasses_armor.json +++ b/Neoforge/src/generated/resources/data/minecraft/tags/damage_type/bypasses_armor.json @@ -1,5 +1,6 @@ { "values": [ - "hexcasting:overcast" + "hexcasting:overcast", + "hexcasting:forgot_to_breathe" ] } \ No newline at end of file diff --git a/Neoforge/src/generated/resources/data/minecraft/tags/damage_type/bypasses_effects.json b/Neoforge/src/generated/resources/data/minecraft/tags/damage_type/bypasses_effects.json index 4209c89aa3..319bb24df4 100644 --- a/Neoforge/src/generated/resources/data/minecraft/tags/damage_type/bypasses_effects.json +++ b/Neoforge/src/generated/resources/data/minecraft/tags/damage_type/bypasses_effects.json @@ -1,5 +1,6 @@ { "values": [ - "hexcasting:overcast" + "hexcasting:overcast", + "hexcasting:forgot_to_breathe" ] } \ No newline at end of file diff --git a/Neoforge/src/generated/resources/data/minecraft/tags/damage_type/no_knockback.json b/Neoforge/src/generated/resources/data/minecraft/tags/damage_type/no_knockback.json index 4209c89aa3..319bb24df4 100644 --- a/Neoforge/src/generated/resources/data/minecraft/tags/damage_type/no_knockback.json +++ b/Neoforge/src/generated/resources/data/minecraft/tags/damage_type/no_knockback.json @@ -1,5 +1,6 @@ { "values": [ - "hexcasting:overcast" + "hexcasting:overcast", + "hexcasting:forgot_to_breathe" ] } \ No newline at end of file diff --git a/Neoforge/src/main/java/at/petrak/hexcasting/forge/ForgeHexClientInitializer.java b/Neoforge/src/main/java/at/petrak/hexcasting/forge/ForgeHexClientInitializer.java index b6233f91bf..484ba185b1 100644 --- a/Neoforge/src/main/java/at/petrak/hexcasting/forge/ForgeHexClientInitializer.java +++ b/Neoforge/src/main/java/at/petrak/hexcasting/forge/ForgeHexClientInitializer.java @@ -4,6 +4,7 @@ import at.petrak.hexcasting.client.Keybinds; import at.petrak.hexcasting.client.RegisterClientStuff; import at.petrak.hexcasting.client.ShiftScrollListener; +import at.petrak.hexcasting.client.gui.GuiSpellcasting; import at.petrak.hexcasting.client.gui.PatternTooltipComponent; import at.petrak.hexcasting.client.model.AltioraLayer; import at.petrak.hexcasting.client.model.HexModelLayers; @@ -86,6 +87,7 @@ public static void clientInit(FMLClientSetupEvent evt) { ClientTickCounter.clientTickEnd(); Keybinds.clientTickEnd(); ShiftScrollListener.clientTickEnd(); + GuiSpellcasting.Companion.clientTickEnd(Minecraft.getInstance().screen); ClientLevel level = Minecraft.getInstance().level; if (level != null) { for (Player player : level.players()) { From 72acc7975e648ccdb100a9a75e8623bc4ac96578 Mon Sep 17 00:00:00 2001 From: Robotgiggle Date: Mon, 31 Aug 2026 20:31:28 -0400 Subject: [PATCH 10/17] Add doc comments for dissociation and the new packet type --- .../at/petrak/hexcasting/client/gui/GuiSpellcasting.kt | 4 ++-- .../hexcasting/common/effects/DissociationEffect.java | 7 +++++++ .../at/petrak/hexcasting/common/msgs/MsgPannedGridC2S.java | 4 +++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt b/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt index 73f0477713..b23f72943e 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt +++ b/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt @@ -74,7 +74,7 @@ class GuiSpellcasting constructor( return panOffset.length() / 20 } - fun syncPanDistance() { + fun syncPanOffset() { if (panOffset != prevPanOffset || ClientTickCounter.ticksInGame % 10 == 0L) { IClientXplatAbstractions.INSTANCE.sendPacketToServer(MsgPannedGridC2S(panOffset)) prevPanOffset = panOffset; @@ -636,7 +636,7 @@ class GuiSpellcasting constructor( } fun clientTickEnd(screen: Screen?) { - if (screen is GuiSpellcasting) screen.syncPanDistance() + if (screen is GuiSpellcasting) screen.syncPanOffset() } } } diff --git a/Common/src/main/java/at/petrak/hexcasting/common/effects/DissociationEffect.java b/Common/src/main/java/at/petrak/hexcasting/common/effects/DissociationEffect.java index 3cec7286de..c426e97936 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/effects/DissociationEffect.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/effects/DissociationEffect.java @@ -9,6 +9,13 @@ import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.entity.player.Player; +/** + * Applied when the casting grid is panned too far. During the first second of duration (ie continuously, so long as + * the effect is constantly being topped up) the player slowly loses oxygen. Once the grid is closed or panned back + * to center, the effect can start to tick down, and the oxygen drain stops. + *

+ * Make sure to use the provided constants for effect duration, or the oxygen drain system may behave oddly. + */ public class DissociationEffect extends MobEffect { public static final int AMP_0_DURATION = 300; public static final int AMP_1_DURATION = 600; diff --git a/Common/src/main/java/at/petrak/hexcasting/common/msgs/MsgPannedGridC2S.java b/Common/src/main/java/at/petrak/hexcasting/common/msgs/MsgPannedGridC2S.java index 68949ff028..b9efb19b7f 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/msgs/MsgPannedGridC2S.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/msgs/MsgPannedGridC2S.java @@ -19,7 +19,9 @@ import org.joml.Vector3f; /** - * Sent client->server when the player pans the casting grid. + * Sent client->server to sync the player's casting grid pan offset and potentially apply the Dissociation debuff + * if they've panned too far. Sent whenever the pan offset is upated, and also every 10 ticks while the GUI is + * open so that the debuff can be kept active. */ public record MsgPannedGridC2S(Vec2 panOffset) implements CustomPacketPayload { public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(HexAPI.modLoc("pan_cs")); From 45cbd6b629bee8ae1e0531277bbf0aec5452ce57 Mon Sep 17 00:00:00 2001 From: Robotgiggle Date: Sat, 5 Sep 2026 21:23:00 -0400 Subject: [PATCH 11/17] Only allow grid panning while wearing the armor --- .../hexcasting/client/gui/GuiSpellcasting.kt | 27 ++++++++++++++----- .../common/items/armor/ItemRobes.java | 13 ++++++++- .../fabric/FabricHexClientInitializer.kt | 1 - .../forge/ForgeHexClientInitializer.java | 1 - 4 files changed, 33 insertions(+), 9 deletions(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt b/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt index b23f72943e..985da5d2d9 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt +++ b/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt @@ -19,6 +19,7 @@ import at.petrak.hexcasting.client.ShiftScrollListener import at.petrak.hexcasting.client.ktxt.accumulatedScroll import at.petrak.hexcasting.client.render.* import at.petrak.hexcasting.client.sound.GridSoundInstance +import at.petrak.hexcasting.common.items.armor.ItemRobes import at.petrak.hexcasting.common.lib.HexAttributes import at.petrak.hexcasting.common.lib.HexSounds import at.petrak.hexcasting.common.lib.hex.HexActions @@ -30,6 +31,7 @@ import com.mojang.blaze3d.vertex.PoseStack import net.minecraft.client.Minecraft import net.minecraft.client.gui.GuiGraphics import net.minecraft.client.gui.screens.Screen +import net.minecraft.client.player.LocalPlayer import net.minecraft.client.renderer.GameRenderer import net.minecraft.client.resources.sounds.SimpleSoundInstance import net.minecraft.client.resources.sounds.SoundInstance @@ -56,7 +58,8 @@ class GuiSpellcasting constructor( private var drawState: PatternDrawState = PatternDrawState.BetweenPatterns private val usedSpots: MutableSet = HashSet() - private var prevPanOffset = Vec2.ZERO + private var panningAllowed = false + private var prevPanOffset = panOffset private val bgLocation = HexAPI.modLoc("textures/gui/casting_bg.png") private var ambianceSoundInstance: GridSoundInstance? = null @@ -81,6 +84,14 @@ class GuiSpellcasting constructor( } } + fun validatePanAbility(player: LocalPlayer) { + panningAllowed = ItemRobes.isWearingFullSet(player) + if (!panningAllowed && panOffset != Vec2.ZERO) { + panOffset = Vec2.ZERO + syncPanOffset() + } + } + fun recvServerUpdate(info: ExecutionClientView, index: Int) { if (info.isStackClear) { this.minecraft?.setScreen(null) @@ -146,6 +157,7 @@ class GuiSpellcasting constructor( if (player != null) { this.ambianceSoundInstance = GridSoundInstance(player) soundManager.play(this.ambianceSoundInstance!!) + this.validatePanAbility(player) } this.calculateIotaDisplays() @@ -158,6 +170,10 @@ class GuiSpellcasting constructor( val heldItem = player.getItemInHand(handOpenedWith) if (heldItem.isEmpty || !heldItem.`is`(HexTags.Items.STAVES) || player.getAttributeValue(HexAttributes.FEEBLE_MIND) > 0) closeForReal() + validatePanAbility(player) + if (this.panningAllowed) { + syncPanOffset() + } } } @@ -214,7 +230,10 @@ class GuiSpellcasting constructor( } if (HexConfig.client().clickingTogglesDrawing()) return false - if (pButton == HexConfig.client().gridPanMouseButton() && this.drawState is PatternDrawState.BetweenPatterns) { + if (pButton == HexConfig.client().gridPanMouseButton() + && this.drawState is PatternDrawState.BetweenPatterns + && this.panningAllowed + ) { return panGrid(pDragX, pDragY) } return drawMove(mxOut, myOut) @@ -634,9 +653,5 @@ class GuiSpellcasting constructor( renderQuad(ps, x, y, w, h, 0x50_303030) renderQuad(ps, x + leftMargin, y + 2.5f, w - leftMargin - 2.5f, h - 5f, 0x50_303030) } - - fun clientTickEnd(screen: Screen?) { - if (screen is GuiSpellcasting) screen.syncPanOffset() - } } } diff --git a/Common/src/main/java/at/petrak/hexcasting/common/items/armor/ItemRobes.java b/Common/src/main/java/at/petrak/hexcasting/common/items/armor/ItemRobes.java index 020d3c4625..a39c9086dd 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/items/armor/ItemRobes.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/items/armor/ItemRobes.java @@ -16,6 +16,7 @@ import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.entity.EquipmentSlotGroup; +import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.ai.attributes.AttributeModifier; import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.item.ArmorItem; @@ -24,6 +25,8 @@ import net.minecraft.world.item.component.ItemAttributeModifiers; import org.jetbrains.annotations.Nullable; +import java.util.List; + import static at.petrak.hexcasting.api.HexAPI.modLoc; /** @@ -45,7 +48,7 @@ public class ItemRobes extends ArmorItem implements VariantItem { public static ItemAttributeModifiers TUNIC_MODIFIERS = ItemAttributeModifiers.builder() .add(HexAttributes.MEDIA_CONSUMPTION_MODIFIER, new AttributeModifier( - modLoc("robes_tunic_discount"), -0.1, AttributeModifier.Operation.ADD_MULTIPLIED_BASE + modLoc("robes_tunic_discount"), -0.1, AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL ), EquipmentSlotGroup.CHEST) .add(Attributes.ARMOR, new AttributeModifier( modLoc("robes_tunic_armor"), 7.0, AttributeModifier.Operation.ADD_VALUE @@ -75,6 +78,14 @@ public ItemRobes(Type type, Properties properties) { this.type = type; } + public static boolean isWearingFullSet(LivingEntity entity) { + for (var slot : List.of(EquipmentSlot.HEAD, EquipmentSlot.CHEST, EquipmentSlot.LEGS, EquipmentSlot.FEET)) { + if (!(entity.getItemBySlot(slot).getItem() instanceof ItemRobes)) + return false; + } + return true; + } + public static HexRobesModel[] provideArmorModelsForSlot(EquipmentSlot slot) { EntityModelSet models = Minecraft.getInstance().getEntityModels(); return new HexRobesModel[] { diff --git a/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexClientInitializer.kt b/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexClientInitializer.kt index a27dc7af4d..a42dc2ebbe 100644 --- a/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexClientInitializer.kt +++ b/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexClientInitializer.kt @@ -43,7 +43,6 @@ object FabricHexClientInitializer : ClientModInitializer { ClientTickCounter.clientTickEnd() Keybinds.clientTickEnd() ShiftScrollListener.clientTickEnd() - GuiSpellcasting.clientTickEnd(ctx.screen) } TooltipComponentCallback.EVENT.register(PatternTooltipComponent::tryConvert) ClientPlayConnectionEvents.JOIN.register { _, _, _ -> diff --git a/Neoforge/src/main/java/at/petrak/hexcasting/forge/ForgeHexClientInitializer.java b/Neoforge/src/main/java/at/petrak/hexcasting/forge/ForgeHexClientInitializer.java index 484ba185b1..4234e2af13 100644 --- a/Neoforge/src/main/java/at/petrak/hexcasting/forge/ForgeHexClientInitializer.java +++ b/Neoforge/src/main/java/at/petrak/hexcasting/forge/ForgeHexClientInitializer.java @@ -87,7 +87,6 @@ public static void clientInit(FMLClientSetupEvent evt) { ClientTickCounter.clientTickEnd(); Keybinds.clientTickEnd(); ShiftScrollListener.clientTickEnd(); - GuiSpellcasting.Companion.clientTickEnd(Minecraft.getInstance().screen); ClientLevel level = Minecraft.getInstance().level; if (level != null) { for (Player player : level.players()) { From d5ce73e57accbf683d2ca53fbfccef838638266b Mon Sep 17 00:00:00 2001 From: Robotgiggle Date: Tue, 8 Sep 2026 01:24:00 -0400 Subject: [PATCH 12/17] Book entry for the armor # Conflicts: # Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 # Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/greatwork/neural_fiber.json --- .../hexcasting/client/gui/GuiSpellcasting.kt | 2 +- .../hexcasting/lang/en_us.flatten.json5 | 8 +++++- .../en_us/entries/greatwork/caster_robes.json | 28 +++++++++++++++++++ .../en_us/entries/greatwork/neural_fiber.json | 3 +- 4 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/greatwork/caster_robes.json diff --git a/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt b/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt index 985da5d2d9..766730ec35 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt +++ b/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt @@ -80,7 +80,7 @@ class GuiSpellcasting constructor( fun syncPanOffset() { if (panOffset != prevPanOffset || ClientTickCounter.ticksInGame % 10 == 0L) { IClientXplatAbstractions.INSTANCE.sendPacketToServer(MsgPannedGridC2S(panOffset)) - prevPanOffset = panOffset; + prevPanOffset = panOffset } } diff --git a/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 b/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 index 0c6206b14a..658265bc30 100644 --- a/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 +++ b/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 @@ -1338,6 +1338,7 @@ akashiclib: "Akashic Libraries", quenching_allays: "Quenching Allays", neural_fiber: "Neural Inversion", + caster_robes: "Caster's Garments", fanciful_staves: "Fanciful Staves", // and the actions @@ -1803,7 +1804,12 @@ "2": "Viscera suffused with micro-fractal splinters of cognition, living and thinking and-- USELESS! Nothing but a mass of meat and _media, in the end.$(br2)But the nerves... the nerves become conduits, skeins of thought reified in shimmering fiber. These I can USE. Free them from the flesh and they are $(o)mine/$.$(br2)I could weave them into myself -- more $(o)space/$ to think in -- no. I am not being rational.", "3": "The process itself is simple: apply $(l:items/potions)Clarity/$ to render the body receptive, cast $(l:greatwork/brainsweeping)$(action)Flay Mind/$ with the $(l:patterns/basics#hexcasting:entity_pos/eye)$(action)subject’s own head/$ as the destination, then dissect to harvest the fiber. Any villager will do, though a more developed mind will provide a higher yield.$(br2)I wonder... does it understand, even excised? Does it feel?", }, - + + caster_robes: { + "1": "With $(l:greatwork/neural_fiber)Neural Fiber/$, I finally have the key to augmenting my own skill! Robes woven from the fibers streamline the _media around me -- I can cast further, cheaper, $(o)greater/$.$(br2)But I would not settle for that alone. With these robes I can think $(o)outside/$ myself, mind shared between thinking flesh and thinking fabric, threads woven as thoughts are woven as Nature is-- ENOUGH.", + "2": "So long as I wear the full set of robes, I gain the ability to $(o)pan/$ the hex grid with right-click. This vastly expands the number of patterns I can draw at once, though not without risks. Drifting further into this mental space pulls my awareness away from my body -- go too far, and I might even forget to breathe.$(br2)The nature of the robes also renders them receptive to $(l:patterns/spells/cyclevariant)$(action)Caster's Glamour/$, should I wish to style myself differently.",//499 + }, + "fanciful_staves.1": "It is only right as I shed the husk of ignorance I replace my tools, my palm-polished staves. These new constructions of mine have no additional properties -- but they are so glorious, oh so Glorious... They match the radiance winking at the corners of my sight.", // Patterns diff --git a/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/greatwork/caster_robes.json b/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/greatwork/caster_robes.json new file mode 100644 index 0000000000..7556b6bb9c --- /dev/null +++ b/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/greatwork/caster_robes.json @@ -0,0 +1,28 @@ +{ + "name": "hexcasting.entry.caster_robes", + "category": "hexcasting:greatwork", + "icon": "hexcasting:robes/hood", + "advancement": "hexcasting:enlightenment", + "entry_color": "54398a", + "sortnum": 7, + "pages": [ + { + "type": "patchouli:text", + "text": "hexcasting.page.caster_robes.1" + }, + { + "type": "patchouli:text", + "text": "hexcasting.page.caster_robes.2" + }, + { + "type": "patchouli:crafting", + "recipe": "hexcasting:robes/hood", + "recipe2": "hexcasting:robes/tunic" + }, + { + "type": "patchouli:crafting", + "recipe": "hexcasting:robes/legs", + "recipe2": "hexcasting:robes/boots" + } + ] +} \ No newline at end of file diff --git a/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/greatwork/neural_fiber.json b/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/greatwork/neural_fiber.json index 1c085ec08c..55eede5e9f 100644 --- a/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/greatwork/neural_fiber.json +++ b/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/greatwork/neural_fiber.json @@ -17,7 +17,8 @@ { "type": "patchouli:spotlight", "item": "hexcasting:neural_fiber", - "text": "hexcasting.page.neural_fiber.3" + "text": "hexcasting.page.neural_fiber.3", + "link_recipe": true } ] } \ No newline at end of file From 15335f8a2e1c4edf960e061a7266d323105a4498 Mon Sep 17 00:00:00 2001 From: Robotgiggle Date: Tue, 8 Sep 2026 17:31:13 -0400 Subject: [PATCH 13/17] Fix panning with clickingTogglesDrawing enabled --- .../at/petrak/hexcasting/client/gui/GuiSpellcasting.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt b/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt index 766730ec35..7215d61f0b 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt +++ b/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt @@ -225,17 +225,16 @@ class GuiSpellcasting constructor( } override fun mouseDragged(mxOut: Double, myOut: Double, pButton: Int, pDragX: Double, pDragY: Double): Boolean { - if (super.mouseDragged(mxOut, myOut, pButton, pDragX, pDragY)) { + if (super.mouseDragged(mxOut, myOut, pButton, pDragX, pDragY)) return true - } - if (HexConfig.client().clickingTogglesDrawing()) - return false if (pButton == HexConfig.client().gridPanMouseButton() && this.drawState is PatternDrawState.BetweenPatterns && this.panningAllowed ) { return panGrid(pDragX, pDragY) } + if (HexConfig.client().clickingTogglesDrawing()) + return false return drawMove(mxOut, myOut) } From 25d1b83523d41f83c0e7429f6211f050071bf62c Mon Sep 17 00:00:00 2001 From: Robotgiggle Date: Tue, 8 Sep 2026 13:06:50 -0400 Subject: [PATCH 14/17] Allow drawing with grid-pan button if panning isn't enabled --- .../java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt b/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt index 7215d61f0b..517ebe7b38 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt +++ b/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt @@ -181,7 +181,7 @@ class GuiSpellcasting constructor( if (super.mouseClicked(mxOut, myOut, pButton)) { return true } - if (pButton == HexConfig.client().gridPanMouseButton()) + if (pButton == HexConfig.client().gridPanMouseButton() && this.panningAllowed) return false if (HexConfig.client().clickingTogglesDrawing()) { return if (this.drawState is PatternDrawState.BetweenPatterns) @@ -320,7 +320,7 @@ class GuiSpellcasting constructor( } if (HexConfig.client().clickingTogglesDrawing()) return false - if (pButton == HexConfig.client().gridPanMouseButton()) + if (pButton == HexConfig.client().gridPanMouseButton() && this.panningAllowed) return false return drawEnd() } From bb8eea36ef32ce43200bda97e7d4363d27069c92 Mon Sep 17 00:00:00 2001 From: Robotgiggle Date: Tue, 8 Sep 2026 17:35:47 -0400 Subject: [PATCH 15/17] Update changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3379b6631..749747dd9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ### Added - Updated to Minecraft 1.21.1 ([#985](https://github.com/FallingColors/HexMod/pull/985)) @SuperKnux @slava110 +- Added Neural Fiber, a new endgame material harvested from villagers using a modified mindflaying process ([#1295](https://github.com/FallingColors/HexMod/pull/1295)) @Robotgiggle @Falkory220 +- Added Grand Caster Robes, a hex-based armor set with a variety of abilities ([#1295](https://github.com/FallingColors/HexMod/pull/1295)) @Robotgiggle @Falkory220 + - Provides defense values midway between iron and diamond + - Provides the scrying lens effect, increased ambit, and a casting cost discount + - Allows you to pan the hex grid to fit in significantly more patterns - Added Simulate, which causes the next pattern drawn to be simulated (to check for mishaps) rather than executed ([#1194](https://github.com/FallingColors/HexMod/pull/1194)) @Robotgiggle - Added the `hex_unbreakable` tag for blocks that should be immune to Break Block regardless of the configured mining tier ([#1186](https://github.com/FallingColors/HexMod/pull/1186)) @Robotgiggle @slava110 - Added a new Ancient Cypher hex that impulses nearby items towards the caster ([#1106](https://github.com/FallingColors/HexMod/pull/1106)) @IridescentVoid From 28b8b7e6b259b7264caefdffef9fa28a89d5c2b5 Mon Sep 17 00:00:00 2001 From: Robotgiggle Date: Tue, 8 Sep 2026 18:19:50 -0400 Subject: [PATCH 16/17] Fix PR links in changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 749747dd9c..57563dd97e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ### Added - Updated to Minecraft 1.21.1 ([#985](https://github.com/FallingColors/HexMod/pull/985)) @SuperKnux @slava110 -- Added Neural Fiber, a new endgame material harvested from villagers using a modified mindflaying process ([#1295](https://github.com/FallingColors/HexMod/pull/1295)) @Robotgiggle @Falkory220 -- Added Grand Caster Robes, a hex-based armor set with a variety of abilities ([#1295](https://github.com/FallingColors/HexMod/pull/1295)) @Robotgiggle @Falkory220 +- Added Neural Fiber, a new endgame material harvested from villagers using a modified mindflaying process ([#1296](https://github.com/FallingColors/HexMod/pull/1296)) @Robotgiggle @Falkory220 +- Added Grand Caster Robes, a hex-based armor set with a variety of abilities ([#1296](https://github.com/FallingColors/HexMod/pull/1296)) @Robotgiggle @Falkory220 - Provides defense values midway between iron and diamond - Provides the scrying lens effect, increased ambit, and a casting cost discount - Allows you to pan the hex grid to fit in significantly more patterns From dc32d40cb2cf299b7675590d57b07beab11784c7 Mon Sep 17 00:00:00 2001 From: Robotgiggle Date: Tue, 8 Sep 2026 21:58:41 -0400 Subject: [PATCH 17/17] Add grid-pan override key --- .../at/petrak/hexcasting/client/Keybinds.java | 8 +++++- .../hexcasting/client/gui/GuiSpellcasting.kt | 27 ++++++++++++++----- .../hexcasting/lang/en_us.flatten.json5 | 5 +++- .../en_us/entries/greatwork/caster_robes.json | 5 ++++ 4 files changed, 37 insertions(+), 8 deletions(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/client/Keybinds.java b/Common/src/main/java/at/petrak/hexcasting/client/Keybinds.java index 46fe6d5d03..6f1ec3267d 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/Keybinds.java +++ b/Common/src/main/java/at/petrak/hexcasting/client/Keybinds.java @@ -20,7 +20,13 @@ public class Keybinds { CATEGORY ); - public static List ALL_BINDS = List.of(spellbookPrev, spellbookNext); + public static KeyMapping gridPanOverride = new KeyMapping( + "key.hexcasting.grid_pan_override", + InputConstants.UNKNOWN.getValue(), + CATEGORY + ); + + public static List ALL_BINDS = List.of(spellbookPrev, spellbookNext, gridPanOverride); public static void clientTickEnd() { // because of how mouse scrolling works (scrolling upward moves the page down), a positive diff --git a/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt b/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt index 517ebe7b38..c6c3236111 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt +++ b/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt @@ -59,6 +59,7 @@ class GuiSpellcasting constructor( private val usedSpots: MutableSet = HashSet() private var panningAllowed = false + private var forcePanning = false private var prevPanOffset = panOffset private val bgLocation = HexAPI.modLoc("textures/gui/casting_bg.png") @@ -92,6 +93,10 @@ class GuiSpellcasting constructor( } } + fun matchesPanInput(pButton: Int): Boolean { + return forcePanning || pButton == HexConfig.client().gridPanMouseButton() + } + fun recvServerUpdate(info: ExecutionClientView, index: Int) { if (info.isStackClear) { this.minecraft?.setScreen(null) @@ -181,7 +186,7 @@ class GuiSpellcasting constructor( if (super.mouseClicked(mxOut, myOut, pButton)) { return true } - if (pButton == HexConfig.client().gridPanMouseButton() && this.panningAllowed) + if (matchesPanInput(pButton) && this.panningAllowed) return false if (HexConfig.client().clickingTogglesDrawing()) { return if (this.drawState is PatternDrawState.BetweenPatterns) @@ -227,10 +232,8 @@ class GuiSpellcasting constructor( override fun mouseDragged(mxOut: Double, myOut: Double, pButton: Int, pDragX: Double, pDragY: Double): Boolean { if (super.mouseDragged(mxOut, myOut, pButton, pDragX, pDragY)) return true - if (pButton == HexConfig.client().gridPanMouseButton() - && this.drawState is PatternDrawState.BetweenPatterns - && this.panningAllowed - ) { + if (matchesPanInput(pButton) && this.panningAllowed + && this.drawState is PatternDrawState.BetweenPatterns) { return panGrid(pDragX, pDragY) } if (HexConfig.client().clickingTogglesDrawing()) @@ -320,7 +323,7 @@ class GuiSpellcasting constructor( } if (HexConfig.client().clickingTogglesDrawing()) return false - if (pButton == HexConfig.client().gridPanMouseButton() && this.panningAllowed) + if (matchesPanInput(pButton) && this.panningAllowed) return false return drawEnd() } @@ -387,6 +390,18 @@ class GuiSpellcasting constructor( } else if (Keybinds.spellbookNext.matches(key, scancode)) { ShiftScrollListener.onScroll(-1.0, false, false) return true + } else if (Keybinds.gridPanOverride.matches(key, scancode)) { + forcePanning = true + return true + } + + return false + } + + override fun keyReleased(key: Int, scancode: Int, modifiers: Int): Boolean { + if (Keybinds.gridPanOverride.matches(key, scancode)) { + forcePanning = false + return true } return false diff --git a/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 b/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 index 658265bc30..eff6c008f5 100644 --- a/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 +++ b/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 @@ -285,6 +285,7 @@ "key.hexcasting": { "spellbook_prev": "Previous Spellbook Page", "spellbook_next": "Next Spellbook Page", + "grid_pan_override": "Grid Pan Override", }, "tag.hexcasting": { @@ -1807,7 +1808,9 @@ caster_robes: { "1": "With $(l:greatwork/neural_fiber)Neural Fiber/$, I finally have the key to augmenting my own skill! Robes woven from the fibers streamline the _media around me -- I can cast further, cheaper, $(o)greater/$.$(br2)But I would not settle for that alone. With these robes I can think $(o)outside/$ myself, mind shared between thinking flesh and thinking fabric, threads woven as thoughts are woven as Nature is-- ENOUGH.", - "2": "So long as I wear the full set of robes, I gain the ability to $(o)pan/$ the hex grid with right-click. This vastly expands the number of patterns I can draw at once, though not without risks. Drifting further into this mental space pulls my awareness away from my body -- go too far, and I might even forget to breathe.$(br2)The nature of the robes also renders them receptive to $(l:patterns/spells/cyclevariant)$(action)Caster's Glamour/$, should I wish to style myself differently.",//499 + "2": "So long as I wear the full set of robes, I gain the ability to $(o)pan/$ the hex grid with right-click. This vastly expands the number of patterns I can draw at once, though not without risks. Drifting further into this mental space pulls my awareness away from my body -- go too far, and I might even forget to breathe.$(br2)The nature of the robes also renders them receptive to $(l:patterns/spells/cyclevariant)$(action)Caster's Glamour/$, should I wish to style myself differently.", + "3": "Should I wish to pan the grid using something other than right-click, I can change which mouse button is required using the client config.$(br2)I can also hold $(k:hexcasting.grid_pan_override) to make $(o)all/$ mouse buttons work to pan the grid rather than drawing patterns.", + "3.header": "Alternate Controls", }, "fanciful_staves.1": "It is only right as I shed the husk of ignorance I replace my tools, my palm-polished staves. These new constructions of mine have no additional properties -- but they are so glorious, oh so Glorious... They match the radiance winking at the corners of my sight.", diff --git a/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/greatwork/caster_robes.json b/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/greatwork/caster_robes.json index 7556b6bb9c..bcb48bcfde 100644 --- a/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/greatwork/caster_robes.json +++ b/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/greatwork/caster_robes.json @@ -23,6 +23,11 @@ "type": "patchouli:crafting", "recipe": "hexcasting:robes/legs", "recipe2": "hexcasting:robes/boots" + }, + { + "type": "patchouli:text", + "title": "hexcasting.page.caster_robes.3.header", + "text": "hexcasting.page.caster_robes.3" } ] } \ No newline at end of file