From 19014c2d84b7a8e847a8eaf46cb02e2fe43c853b Mon Sep 17 00:00:00 2001 From: Robotgiggle <88736742+Robotgiggle@users.noreply.github.com> Date: Fri, 5 Jun 2026 21:49:20 -0400 Subject: [PATCH 01/10] Adapt #1046 for new system --- CHANGELOG.md | 1 + .../api/casting/castables/Action.kt | 2 ++ .../actions/escaping/OpCloseAllParens.kt | 32 +++++++++++++++++++ .../casting/actions/escaping/OpOpenNParens.kt | 28 ++++++++++++++++ .../common/casting/actions/escaping/OpUndo.kt | 2 +- .../hexcasting/common/lib/hex/HexActions.java | 4 +++ .../hexcasting/lang/en_us.flatten.json5 | 12 +++++-- .../entries/patterns/patterns_as_iotas.json | 15 +++++++++ 8 files changed, 92 insertions(+), 4 deletions(-) create mode 100644 Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpCloseAllParens.kt create mode 100644 Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpOpenNParens.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 6de6fbd79b..67fae2abea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - Added a config toggle (default false) for the effect that makes iota-holding items display their entire NBT data when Advanced Tooltips is enabled ([#1021](https://github.com/FallingColors/HexMod/pull/1021)) @Robotgiggle - Added a pattern display overlay for pattern-holding items (ie scrolls or slates) while holding shift in the inventory ([#879](https://github.com/FallingColors/HexMod/pull/879)) @SamsTheNerd - Added connected textures for Akashic Ligatures when using Continuity or Optifine ([#885](https://github.com/FallingColors/HexMod/pull/885)) @kineticneticat +- Added Meditation and Recollection for advanced pattern-list creation ([#1046](https://github.com/FallingColors/HexMod/pull/1046)) @Robotgiggle ### Changed diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/castables/Action.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/castables/Action.kt index c30280a868..132b298b21 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/castables/Action.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/castables/Action.kt @@ -39,6 +39,8 @@ interface Action { * * The userdata tag is copied for you, so you don't need to worry about mutation messing up things * behind the scenes. + * + * Note that `image.parenCount` will always be 0 here - if it's greater, [operateInParens] is used instead. */ @Throws(Mishap::class) fun operate( diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpCloseAllParens.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpCloseAllParens.kt new file mode 100644 index 0000000000..2c0121dae2 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpCloseAllParens.kt @@ -0,0 +1,32 @@ +package at.petrak.hexcasting.common.casting.actions.escaping + +import at.petrak.hexcasting.api.casting.castables.Action +import at.petrak.hexcasting.api.casting.eval.CastingEnvironment +import at.petrak.hexcasting.api.casting.eval.OperationResult +import at.petrak.hexcasting.api.casting.eval.ParenthesizedOperationResult +import at.petrak.hexcasting.api.casting.eval.ResolvedPatternType +import at.petrak.hexcasting.api.casting.eval.vm.CastingImage +import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation +import at.petrak.hexcasting.api.casting.iota.DoubleIota +import at.petrak.hexcasting.api.casting.iota.Iota +import at.petrak.hexcasting.api.casting.iota.ListIota +import at.petrak.hexcasting.api.casting.mishaps.MishapNeedsParens +import at.petrak.hexcasting.common.lib.hex.HexEvalSounds + +object OpCloseAllParens : Action { + override fun operate(env: CastingEnvironment, image: CastingImage, continuation: SpellContinuation): OperationResult { + throw MishapNeedsParens() + } + + override fun operateInParens(env: CastingEnvironment, image: CastingImage, continuation: SpellContinuation, thisIota: Iota): ParenthesizedOperationResult { + val newStack = image.stack.toMutableList() + newStack.add(DoubleIota(image.parenCount.toDouble())) + newStack.add(ListIota(image.parenthesized.toList().map { it.iota })) + val image2 = image.copy( + stack = newStack, + parenCount = 0, + parenthesized = listOf() + ) + return ParenthesizedOperationResult(image2, listOf(), continuation, HexEvalSounds.NORMAL_EXECUTE, ResolvedPatternType.EVALUATED) + } +} \ No newline at end of file diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpOpenNParens.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpOpenNParens.kt new file mode 100644 index 0000000000..9fdb9bceee --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpOpenNParens.kt @@ -0,0 +1,28 @@ +package at.petrak.hexcasting.common.casting.actions.escaping + +import at.petrak.hexcasting.api.casting.castables.Action +import at.petrak.hexcasting.api.casting.eval.CastingEnvironment +import at.petrak.hexcasting.api.casting.eval.OperationResult +import at.petrak.hexcasting.api.casting.eval.vm.CastingImage +import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation +import at.petrak.hexcasting.api.casting.getPositiveInt +import at.petrak.hexcasting.api.casting.mishaps.MishapNotEnoughArgs +import at.petrak.hexcasting.common.lib.hex.HexEvalSounds + +object OpOpenNParens : Action { + override fun operate(env: CastingEnvironment, image: CastingImage, continuation: SpellContinuation): OperationResult { + val newStack = image.stack.toMutableList() + val layers = when (val topIota = newStack.removeLastOrNull()) { + null -> throw MishapNotEnoughArgs(1, 0) + else -> listOf(topIota).getPositiveInt(0) + } + val image2 = image.copy( + stack = newStack, + parenCount = layers + ) + return OperationResult(image2, listOf(), continuation, HexEvalSounds.NORMAL_EXECUTE) + } + + // Since there's no nice way to determine how many new layers it should open when drawn inside parens, we don't + // override operateInParens() at all. This pattern is just treated as any other pattern when parenthesized. +} \ No newline at end of file diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpUndo.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpUndo.kt index c8f1b3aed3..57a234ad57 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpUndo.kt +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpUndo.kt @@ -24,7 +24,7 @@ object OpUndo : Action { var newParenCount = image.parenCount if (last == null) { // if there was nothing in the parenthesized list, undo the initial open paren - newParenCount-- + newParenCount = 0 } else if (last.iota is PatternIota && !last.escaped) { // adjust paren count if undoing a non-escaped open or close paren when (last.iota.pattern.angles) { diff --git a/Common/src/main/java/at/petrak/hexcasting/common/lib/hex/HexActions.java b/Common/src/main/java/at/petrak/hexcasting/common/lib/hex/HexActions.java index 95918ef011..7d21c9c1a1 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/lib/hex/HexActions.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/lib/hex/HexActions.java @@ -389,6 +389,10 @@ public class HexActions { new ActionRegistryEntry(HexPattern.fromAngles("qqq", HexDir.WEST), OpOpenParen.INSTANCE)); public static final ActionRegistryEntry CLOSE_PAREN = make("close_paren", new ActionRegistryEntry(HexPattern.fromAngles("eee", HexDir.EAST), OpCloseParen.INSTANCE)); + public static final ActionRegistryEntry OPEN_N_PARENS = make("open_n_parens", + new ActionRegistryEntry(HexPattern.fromAngles("eqqqe", HexDir.SOUTH_WEST), OpOpenNParens.INSTANCE)); + public static final ActionRegistryEntry CLOSE_ALL_PARENS = make("close_all_parens", + new ActionRegistryEntry(HexPattern.fromAngles("qeeeq", HexDir.SOUTH_EAST), OpCloseAllParens.INSTANCE)); public static final ActionRegistryEntry UNDO = make("undo", new ActionRegistryEntry(HexPattern.fromAngles("eeedw", HexDir.EAST), OpUndo.INSTANCE)); 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 20be0f5b46..ca2c3b44d1 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 @@ -919,6 +919,8 @@ escape: "Consideration", open_paren: "Introspection", close_paren: "Retrospection", + open_n_parens: "Meditation", + close_all_parens: "Recollection", undo: "Evanition", eval: "Hermes' Gambit", @@ -1958,19 +1960,23 @@ patterns_as_iotas: { "1": "One of the many peculiarities of this art is that $(italic)patterns themselves/$ can act as iotas-- I can even put them onto my stack when casting.$(br2)This raises a fairly obvious question: how do I express them? If I simply drew a pattern, it would hardly tell Nature to add it to my stack-- rather, it would simply be matched to an action.", - "2": "Fortunately, Nature has provided me with a set of $(l:casting/influences)influences/$ that I can use to work with patterns directly.$(br2)In short, $(l:patterns/patterns_as_iotas#hexcasting:escape)$(action)Consideration/$ lets me add one pattern to the stack, and $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)Introspection/$ and $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospection/$ let me add a whole list.", + "2": "Fortunately, Nature has provided me with a set of $(l:casting/influences)influences/$ that I can use to work with patterns directly.$(br2)To summarize, $(l:patterns/patterns_as_iotas#hexcasting:escape)$(action)Consideration/$ lets me add one pattern to the stack, and $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)Introspection/$ and $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospection/$ let me add a whole list, $(l:patterns/patterns_as_iotas#hexcasting:open_n_parens)$(action)Meditation/$ and $(l:patterns/patterns_as_iotas#hexcasting:close_all_parens)$(action)Recollection/$ allow for more advanced list creation, and $(l:patterns/patterns_as_iotas#hexcasting:undo)$(action)Evanition/$ lets me undo mistakes made with the others.", escape: { "1": "To use $(l:patterns/patterns_as_iotas#hexcasting:escape)$(action)Consideration/$, I draw it, then another arbitrary pattern. That second pattern is added to the stack.", "2": "One may find it helpful to think of this as \"escaping\" the pattern onto the stack, if they happen to be familiar with the science of computers.$(br2)The usual use for this is to copy the pattern to a $(l:items/scroll)$(item)Scroll/$ or $(l:items/slate)$(item)Slate/$ using $(l:patterns/readwrite#hexcasting:write)$(action)Scribe's Gambit/$, and then perhaps decorating with them.", }, parens: { - "1": "Drawing $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)Introspection/$ makes my drawing of patterns act differently, for a time. Until I draw $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)Retrospection/$, the patterns I draw are saved. Then, when I draw $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospection/$, they are added to the stack as a list iota.", + "1": "Drawing $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)Introspection/$ makes my drawing of patterns act differently, for a time. Until I draw $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospection/$, the patterns I draw are saved. Then, when I draw $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospection/$, they are added to the stack as a list iota.", "2": "If I draw another $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Introspection/$, it'll still be saved to the list, but I'll then have to draw $(italic)two/$ $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospections/$ to get back to normal casting.", }, + advanced_parens: { + "1": "Removes a number from the stack, then acts as though I'd drawn that many $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)Introspections/$. Treated as a normal pattern while $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)introspecting/$, so nesting this may require $(l:patterns/patterns_as_iotas#further_notes)$(action)tricks/$ to draw extra $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospections/$.", + "2": "Works like $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospection/$, but closes $(italic)all/$ open $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)Introspections/$ at once. The number of layers closed, along with the list of saved patterns, are then added to the stack.", + }, undo: "Finally, if I make a mistake while drawing patterns inside $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)Intro-/$ and $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospection/$ I can draw $(l:patterns/patterns_as_iotas#hexcasting:undo)$(action)Evanition/$ to remove the last pattern that I drew from the pattern list that is being constructed.", further_notes: { title: "Further Notes", - "1": "I can escape the special behavior of $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)Intro-/$ and $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospection/$ by drawing a $(l:patterns/patterns_as_iotas#hexcasting:escape)$(action)Consideration/$ before them, which will simply add them to the list without affecting which the number of $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospections/$ I need to return to casting.$(br2)If I draw two $(l:patterns/patterns_as_iotas#hexcasting:escape)$(action)Considerations/$ in a row while $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)introspecting/$, it will add a single $(l:patterns/patterns_as_iotas#hexcasting:escape)$(action)Consideration/$ to the list.", + "1": "I can escape the special behavior of patterns like $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)Intro-/$ and $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospection/$ by drawing a $(l:patterns/patterns_as_iotas#hexcasting:escape)$(action)Consideration/$ before them, which will simply add them to the list without affecting which the number of $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospections/$ I need to return to casting.$(br2)If I draw two $(l:patterns/patterns_as_iotas#hexcasting:escape)$(action)Considerations/$ in a row while $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)introspecting/$, it will add a single $(l:patterns/patterns_as_iotas#hexcasting:escape)$(action)Consideration/$ to the list.", "2": "If an iota other than a pattern is present in a list to be executed by $(l:patterns/meta#hexcasting:eval)$(action)Hermes' Gambit/$ or any other meta-evaluation pattern, it will normally result in a mishap. However, this can be avoided using the patterns described in this section.$(br2)Just as with pattern iotas, the patterns described here can be used to \"escape\" $(o)any other$() kind of iota, causing it to be pushed to the stack when something tries to evaluate it instead of causing a mishap.", "3": "This technique may be useful if I want a _Hex to be able to reference a specific iota, such as a complicated vector or an entity reference, without having to construct or obtain it each time.$(br2)The process of getting such an iota into a list of patterns in the first place may be somewhat involved. The simplest method would be to draw a placeholder pattern when assembling the list, and then make use of $(l:patterns/lists#hexcasting:replace)$(action)Surgeon's Exaltation/$ to replace it with my desired iota.", }, diff --git a/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/patterns_as_iotas.json b/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/patterns_as_iotas.json index 0d7e834aeb..46e75839aa 100644 --- a/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/patterns_as_iotas.json +++ b/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/patterns_as_iotas.json @@ -36,6 +36,20 @@ "anchor": "hexcasting:close_paren", "text": "hexcasting.page.patterns_as_iotas.parens.2" }, + { + "type": "hexcasting:pattern", + "op_id": "hexcasting:open_n_parens", + "anchor": "hexcasting:open_n_parens", + "input": "num", + "text": "hexcasting.page.patterns_as_iotas.advanced_parens.1" + }, + { + "type": "hexcasting:pattern", + "op_id": "hexcasting:close_all_parens", + "anchor": "hexcasting:close_all_parens", + "output": "num, [pattern]", + "text": "hexcasting.page.patterns_as_iotas.advanced_parens.2" + }, { "type": "hexcasting:pattern", "op_id": "hexcasting:undo", @@ -44,6 +58,7 @@ }, { "type": "patchouli:text", + "anchor": "further_notes", "title": "hexcasting.page.patterns_as_iotas.further_notes.title", "text": "hexcasting.page.patterns_as_iotas.further_notes.1" }, From 1685ca96db937c00566e6e37c10a19f3e3fefac1 Mon Sep 17 00:00:00 2001 From: Robotgiggle <88736742+Robotgiggle@users.noreply.github.com> Date: Fri, 5 Jun 2026 23:52:38 -0400 Subject: [PATCH 02/10] Add Interjection to read from offhand straight into parens --- .../hexcasting/client/gui/GuiSpellcasting.kt | 22 +++++++-- .../actions/escaping/OpReadIntoParens.kt | 47 +++++++++++++++++++ .../hexcasting/common/lib/hex/HexActions.java | 2 + .../hexcasting/lang/en_us.flatten.json5 | 6 ++- .../entries/patterns/patterns_as_iotas.json | 6 +++ 5 files changed, 76 insertions(+), 7 deletions(-) create mode 100644 Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpReadIntoParens.kt 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 4759c50a4b..1cefc3899f 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 @@ -72,11 +72,8 @@ class GuiSpellcasting constructor( // TODO this is the kinda hacky bit if (info.resolutionType == ResolvedPatternType.UNDONE) { - // find the last escaped pattern (or the opening paren if there's nothing else) and set it to UNDONE - this.patterns.reversed().drop(1).firstOrNull { - it.type == ResolvedPatternType.ESCAPED || - (it.type == ResolvedPatternType.EVALUATED && it.pattern.angles == HexActions.OPEN_PAREN.prototype.angles) - }?.let { it.type = ResolvedPatternType.UNDONE } + // find the last undo-able pattern and set its coloring to UNDONE + this.patterns.reversed().drop(1).firstOrNull { canBeUndone(it) }?.let { it.type = ResolvedPatternType.UNDONE } // use the normal EVALUATED coloring for the Evanition that was just drawn this.patterns.getOrNull(index)?.let { it.type = ResolvedPatternType.EVALUATED } } else this.patterns.getOrNull(index)?.let { @@ -88,6 +85,21 @@ class GuiSpellcasting constructor( this.calculateIotaDisplays() } + fun canBeUndone(resPat: ResolvedPattern): Boolean { + // standard escaped patterns can always be undone + if (resPat.type == ResolvedPatternType.ESCAPED) return true + // everything else cannot be undone, with three exceptions: + // - unescaped Introspection and Meditation can be undone if there's nothing else left to undo + // - Interjection can always be undone (undoing its inserted iota) despite using the EVALUATED coloring + if (resPat.type == ResolvedPatternType.EVALUATED) { + if (resPat.pattern.angles == HexActions.OPEN_PAREN.prototype.angles || + resPat.pattern.angles == HexActions.OPEN_N_PARENS.prototype.angles || + resPat.pattern.angles == HexActions.READ_INTO_PARENS.prototype.angles) + return true + } + return false + } + fun calculateIotaDisplays() { val mc = Minecraft.getInstance() val width = (this.width * LHS_IOTAS_ALLOCATION).toInt() diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpReadIntoParens.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpReadIntoParens.kt new file mode 100644 index 0000000000..1a1177b8f4 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpReadIntoParens.kt @@ -0,0 +1,47 @@ +package at.petrak.hexcasting.common.casting.actions.escaping + +import at.petrak.hexcasting.api.casting.castables.Action +import at.petrak.hexcasting.api.casting.eval.CastingEnvironment +import at.petrak.hexcasting.api.casting.eval.OperationResult +import at.petrak.hexcasting.api.casting.eval.ParenthesizedOperationResult +import at.petrak.hexcasting.api.casting.eval.ResolvedPatternType +import at.petrak.hexcasting.api.casting.eval.vm.CastingImage +import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation +import at.petrak.hexcasting.api.casting.iota.Iota +import at.petrak.hexcasting.api.casting.mishaps.MishapBadOffhandItem +import at.petrak.hexcasting.api.casting.mishaps.MishapNeedsParens +import at.petrak.hexcasting.common.lib.hex.HexEvalSounds +import at.petrak.hexcasting.xplat.IXplatAbstractions + +object OpReadIntoParens : Action { + override fun operate(env: CastingEnvironment, image: CastingImage, continuation: SpellContinuation): OperationResult { + throw MishapNeedsParens() + } + + override fun operateInParens(env: CastingEnvironment, image: CastingImage, continuation: SpellContinuation, thisIota: Iota): ParenthesizedOperationResult { + val (handStack) = env.getHeldItemToOperateOn { + val dataHolder = IXplatAbstractions.INSTANCE.findDataHolder(it) + dataHolder != null && (dataHolder.readIota(env.world) != null || dataHolder.emptyIota() != null) + } + // If there are no data holders that are readable, find a data holder that isn't readable + // so that the error message is more helpful. + ?: env.getHeldItemToOperateOn { + val dataHolder = IXplatAbstractions.INSTANCE.findDataHolder(it) + dataHolder != null + } ?: throw MishapBadOffhandItem.of(null, "iota.read") + + val datumHolder = IXplatAbstractions.INSTANCE.findDataHolder(handStack) + ?: throw MishapBadOffhandItem.of(handStack, "iota.read") + + val datum = datumHolder.readIota(env.world) + ?: datumHolder.emptyIota() + ?: throw MishapBadOffhandItem.of(handStack, "iota.read") + + return ParenthesizedOperationResult( + image.withNewParenthesized(datum), + listOf(), continuation, + HexEvalSounds.NORMAL_EXECUTE, + ResolvedPatternType.EVALUATED + ) + } +} \ No newline at end of file diff --git a/Common/src/main/java/at/petrak/hexcasting/common/lib/hex/HexActions.java b/Common/src/main/java/at/petrak/hexcasting/common/lib/hex/HexActions.java index 7d21c9c1a1..2b3be072d1 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/lib/hex/HexActions.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/lib/hex/HexActions.java @@ -393,6 +393,8 @@ public class HexActions { new ActionRegistryEntry(HexPattern.fromAngles("eqqqe", HexDir.SOUTH_WEST), OpOpenNParens.INSTANCE)); public static final ActionRegistryEntry CLOSE_ALL_PARENS = make("close_all_parens", new ActionRegistryEntry(HexPattern.fromAngles("qeeeq", HexDir.SOUTH_EAST), OpCloseAllParens.INSTANCE)); + public static final ActionRegistryEntry READ_INTO_PARENS = make("read_into_parens", + new ActionRegistryEntry(HexPattern.fromAngles("aqqqqqwded", HexDir.EAST), OpReadIntoParens.INSTANCE)); public static final ActionRegistryEntry UNDO = make("undo", new ActionRegistryEntry(HexPattern.fromAngles("eeedw", HexDir.EAST), OpUndo.INSTANCE)); 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 ca2c3b44d1..71590b76c9 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 @@ -921,6 +921,7 @@ close_paren: "Retrospection", open_n_parens: "Meditation", close_all_parens: "Recollection", + read_into_parens: "Interjection", undo: "Evanition", eval: "Hermes' Gambit", @@ -1510,7 +1511,7 @@ incorrect_block: "The action requires some sort of block at a target location, but the block supplied was not suitable.$(br2)Causes bright green sparks, and causes an ephemeral explosion at the given location. The explosion doesn't seem to harm me, the world, or anything else though; it's just startling.", "needs_parens.title": "Absent Introspection", - needs_parens: "I attempted to draw $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospection/$ or $(l:patterns/patterns_as_iotas#hexcasting:undo)$(action)Evanition/$ without first drawing $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)Introspection/$.$(br2)Causes orange sparks, and pushes the pattern I tried to draw to the stack as an iota.", + needs_parens: "I attempted to draw a pattern that only works while $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)introspecting/$ without first drawing $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)Introspection/$.$(br2)Causes orange sparks, and pushes the pattern I tried to draw to the stack as an iota.", "too_many_patterns.title": "Lost in Thought", too_many_patterns: "I attempted to evaluate too many patterns in one _Hex. Often, this happens because I've accidentally created an infinite loop.$(br2)Causes dark blue sparks, and chokes all the air out of me.", @@ -1972,8 +1973,9 @@ advanced_parens: { "1": "Removes a number from the stack, then acts as though I'd drawn that many $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)Introspections/$. Treated as a normal pattern while $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)introspecting/$, so nesting this may require $(l:patterns/patterns_as_iotas#further_notes)$(action)tricks/$ to draw extra $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospections/$.", "2": "Works like $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospection/$, but closes $(italic)all/$ open $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)Introspections/$ at once. The number of layers closed, along with the list of saved patterns, are then added to the stack.", + "3": "[placeholder]", }, - undo: "Finally, if I make a mistake while drawing patterns inside $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)Intro-/$ and $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospection/$ I can draw $(l:patterns/patterns_as_iotas#hexcasting:undo)$(action)Evanition/$ to remove the last pattern that I drew from the pattern list that is being constructed.", + undo: "Finally, if I make a mistake while drawing patterns inside $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)Intro-/$ and $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospection/$, I can draw $(l:patterns/patterns_as_iotas#hexcasting:undo)$(action)Evanition/$ to remove the last pattern that I drew from the pattern list that is being constructed.", further_notes: { title: "Further Notes", "1": "I can escape the special behavior of patterns like $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)Intro-/$ and $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospection/$ by drawing a $(l:patterns/patterns_as_iotas#hexcasting:escape)$(action)Consideration/$ before them, which will simply add them to the list without affecting which the number of $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospections/$ I need to return to casting.$(br2)If I draw two $(l:patterns/patterns_as_iotas#hexcasting:escape)$(action)Considerations/$ in a row while $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)introspecting/$, it will add a single $(l:patterns/patterns_as_iotas#hexcasting:escape)$(action)Consideration/$ to the list.", diff --git a/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/patterns_as_iotas.json b/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/patterns_as_iotas.json index 46e75839aa..a13fa7c7c2 100644 --- a/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/patterns_as_iotas.json +++ b/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/patterns_as_iotas.json @@ -50,6 +50,12 @@ "output": "num, [pattern]", "text": "hexcasting.page.patterns_as_iotas.advanced_parens.2" }, + { + "type": "hexcasting:pattern", + "op_id": "hexcasting:read_into_parens", + "anchor": "hexcasting:read_into_parens", + "text": "hexcasting.page.patterns_as_iotas.advanced_parens.3" + }, { "type": "hexcasting:pattern", "op_id": "hexcasting:undo", From 1f01f3c8150293350f02d3790d92bef19362febf Mon Sep 17 00:00:00 2001 From: Robotgiggle <88736742+Robotgiggle@users.noreply.github.com> Date: Fri, 5 Jun 2026 23:52:51 -0400 Subject: [PATCH 03/10] Fix mid-parens mishap jank --- .../hexcasting/api/casting/eval/vm/FrameEvaluate.kt | 2 +- .../hexcasting/api/casting/iota/PatternIota.java | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameEvaluate.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameEvaluate.kt index 141148dfdc..85f2cbd8fe 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameEvaluate.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameEvaluate.kt @@ -17,7 +17,7 @@ import net.minecraft.server.level.ServerLevel /** * A list of patterns to be evaluated in sequence. * @property list the *remaining* list of patterns to be evaluated - * @property isMetacasting only for sound effects, if this is being cast from a hermes / iris + * @property isMetacasting if this is being cast from a hermes / iris / thoth */ data class FrameEvaluate(val list: SpellList, val isMetacasting: Boolean) : ContinuationFrame { // Discard this frame and keep discarding frames. diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/PatternIota.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/PatternIota.java index e24648df41..342addf372 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/PatternIota.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/PatternIota.java @@ -7,6 +7,7 @@ import at.petrak.hexcasting.api.casting.eval.*; import at.petrak.hexcasting.api.casting.eval.sideeffects.OperatorSideEffect; import at.petrak.hexcasting.api.casting.eval.vm.CastingVM; +import at.petrak.hexcasting.api.casting.eval.vm.FrameEvaluate; import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation; import at.petrak.hexcasting.api.casting.math.HexPattern; import at.petrak.hexcasting.api.casting.mishaps.Mishap; @@ -167,10 +168,19 @@ public boolean executable() { result.getSound()); } catch (Mishap mishap) { + // if a mishap happens mid-parens at the top level (ie when staffcasting), keep any in-progress parens + boolean wipeParens = false; + // if a mishap happens mid-parens while metacasting, wipe the parens - if we didn't do this, any open paren + // levels would remain open even though the metacast itself has mishapped and thus been aborted + if (continuation instanceof SpellContinuation.NotDone cnd && cnd.getFrame() instanceof FrameEvaluate frameEval) { + if (frameEval.isMetacasting()) { + wipeParens = true; + } + } return new CastResult( this, continuation, - null, + wipeParens ? vm.getImage().withResetEscape() : null, List.of(new OperatorSideEffect.DoMishap(mishap, new Mishap.Context(this.getPattern(), castedName.get()))), mishap.resolutionType(vm.getEnv()), HexEvalSounds.MISHAP); From 183d613490f63889e2b1cbe43ccdc0aac13b9895 Mon Sep 17 00:00:00 2001 From: Robotgiggle <88736742+Robotgiggle@users.noreply.github.com> Date: Sat, 6 Jun 2026 00:56:00 -0400 Subject: [PATCH 04/10] Move big-parens docs to new entry, document interjection --- .../hexcasting/lang/en_us.flatten.json5 | 15 ++++---- .../entries/patterns/advanced_escaping.json | 34 +++++++++++++++++++ .../entries/patterns/patterns_as_iotas.json | 23 ++----------- .../en_us/entries/patterns/readwrite.json | 2 +- 4 files changed, 46 insertions(+), 28 deletions(-) create mode 100644 Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/advanced_escaping.json 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 71590b76c9..3a385f9912 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 @@ -1313,6 +1313,7 @@ entities: "Entities", lists: "List Manipulation", patterns_as_iotas: "Escaping Patterns", + advanced_escaping: "Advanced Escaping", readwrite: "Reading and Writing", meta: "Meta-Evaluation", circle_patterns: "Spell Circle Patterns", @@ -1961,7 +1962,7 @@ patterns_as_iotas: { "1": "One of the many peculiarities of this art is that $(italic)patterns themselves/$ can act as iotas-- I can even put them onto my stack when casting.$(br2)This raises a fairly obvious question: how do I express them? If I simply drew a pattern, it would hardly tell Nature to add it to my stack-- rather, it would simply be matched to an action.", - "2": "Fortunately, Nature has provided me with a set of $(l:casting/influences)influences/$ that I can use to work with patterns directly.$(br2)To summarize, $(l:patterns/patterns_as_iotas#hexcasting:escape)$(action)Consideration/$ lets me add one pattern to the stack, and $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)Introspection/$ and $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospection/$ let me add a whole list, $(l:patterns/patterns_as_iotas#hexcasting:open_n_parens)$(action)Meditation/$ and $(l:patterns/patterns_as_iotas#hexcasting:close_all_parens)$(action)Recollection/$ allow for more advanced list creation, and $(l:patterns/patterns_as_iotas#hexcasting:undo)$(action)Evanition/$ lets me undo mistakes made with the others.", + "2": "Fortunately, Nature has provided me with a set of $(l:casting/influences)influences/$ that I can use to work with patterns directly.$(br2)In short, $(l:patterns/patterns_as_iotas#hexcasting:escape)$(action)Consideration/$ lets me add one pattern to the stack, $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)Introspection/$ and $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospection/$ let me add a whole list, and $(l:patterns/patterns_as_iotas#hexcasting:undo)$(action)Evanition/$ lets me undo mistakes during the creation of such a list.", escape: { "1": "To use $(l:patterns/patterns_as_iotas#hexcasting:escape)$(action)Consideration/$, I draw it, then another arbitrary pattern. That second pattern is added to the stack.", "2": "One may find it helpful to think of this as \"escaping\" the pattern onto the stack, if they happen to be familiar with the science of computers.$(br2)The usual use for this is to copy the pattern to a $(l:items/scroll)$(item)Scroll/$ or $(l:items/slate)$(item)Slate/$ using $(l:patterns/readwrite#hexcasting:write)$(action)Scribe's Gambit/$, and then perhaps decorating with them.", @@ -1970,11 +1971,6 @@ "1": "Drawing $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)Introspection/$ makes my drawing of patterns act differently, for a time. Until I draw $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospection/$, the patterns I draw are saved. Then, when I draw $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospection/$, they are added to the stack as a list iota.", "2": "If I draw another $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Introspection/$, it'll still be saved to the list, but I'll then have to draw $(italic)two/$ $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospections/$ to get back to normal casting.", }, - advanced_parens: { - "1": "Removes a number from the stack, then acts as though I'd drawn that many $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)Introspections/$. Treated as a normal pattern while $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)introspecting/$, so nesting this may require $(l:patterns/patterns_as_iotas#further_notes)$(action)tricks/$ to draw extra $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospections/$.", - "2": "Works like $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospection/$, but closes $(italic)all/$ open $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)Introspections/$ at once. The number of layers closed, along with the list of saved patterns, are then added to the stack.", - "3": "[placeholder]", - }, undo: "Finally, if I make a mistake while drawing patterns inside $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)Intro-/$ and $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospection/$, I can draw $(l:patterns/patterns_as_iotas#hexcasting:undo)$(action)Evanition/$ to remove the last pattern that I drew from the pattern list that is being constructed.", further_notes: { title: "Further Notes", @@ -1983,6 +1979,13 @@ "3": "This technique may be useful if I want a _Hex to be able to reference a specific iota, such as a complicated vector or an entity reference, without having to construct or obtain it each time.$(br2)The process of getting such an iota into a list of patterns in the first place may be somewhat involved. The simplest method would be to draw a placeholder pattern when assembling the list, and then make use of $(l:patterns/lists#hexcasting:replace)$(action)Surgeon's Exaltation/$ to replace it with my desired iota.", }, }, + + advanced_escaping: { + "1": "$(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)Intro-/$ and $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospection/$ are very useful, but when building complex _Hexes I may need more advanced techniques.$(br2)This section describes such techniques: $(l:patterns/advanced_escaping#hexcasting:read_into_parens)$(action)Interjection/$ lets me insert iotas directly into an in-progress list, while $(l:patterns/advanced_escaping#hexcasting:open_n_parens)$(action)Meditation/$ and $(l:patterns/advanced_escaping#hexcasting:close_all_parens)$(action)Recollection/$ let me start and finish the list creation process with arbitrary levels of nesting depth.", + read_into_parens: "Only works while $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)introspecting/$. Reads an iota from my offhand, and adds it to the in-progress list. For iotas other than patterns, I may need to $(l:patterns/patterns_as_iotas#iota_embedding)$(action)take steps/$ to avoid a mishap when executing the list.", + open_n_parens: "Removes a number from the stack, then acts as though I'd drawn that many $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)Introspections/$. Treated as a normal pattern while $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)introspecting/$, so nesting this may require $(l:patterns/patterns_as_iotas#escaping_escapers)$(action)tricks/$ to draw extra $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospections/$.", + close_all_parens: "Works like $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospection/$, but closes $(italic)all/$ open $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)Introspections/$ at once. The number of layers closed, along with the list of saved patterns, are then added to the stack.", + }, readwrite: { "1": "This section deals with the storage of $(thing)Iotas/$ in a more permanent medium. Nearly any iota can be stored to a suitable item, such as a $(l:items/focus)$(item)Focus/$ or $(l:items/spellbook)$(item)Spellbook/$, and read back later. Certain items, such as an $(l:items/abacus)$(item)Abacus/$, can only be read from.$(br2)Iotas are usually read and written from the other hand, but it is also possible to read and write with an item when it is sitting on the ground as an item entity, or when in an item frame.", diff --git a/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/advanced_escaping.json b/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/advanced_escaping.json new file mode 100644 index 0000000000..b971507b17 --- /dev/null +++ b/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/advanced_escaping.json @@ -0,0 +1,34 @@ +{ + "name": "hexcasting.entry.advanced_escaping", + "category": "hexcasting:patterns", + "icon": "minecraft:emerald_block", + "sortnum": 9, + "advancement": "hexcasting:root", + "read_by_default": true, + "pages": [ + { + "type": "patchouli:text", + "text": "hexcasting.page.advanced_escaping.1" + }, + { + "type": "hexcasting:pattern", + "op_id": "hexcasting:read_into_parens", + "anchor": "hexcasting:read_into_parens", + "text": "hexcasting.page.advanced_escaping.read_into_parens" + }, + { + "type": "hexcasting:pattern", + "op_id": "hexcasting:open_n_parens", + "anchor": "hexcasting:open_n_parens", + "input": "num", + "text": "hexcasting.page.advanced_escaping.open_n_parens" + }, + { + "type": "hexcasting:pattern", + "op_id": "hexcasting:close_all_parens", + "anchor": "hexcasting:close_all_parens", + "output": "num, [pattern]", + "text": "hexcasting.page.advanced_escaping.close_all_parens" + } + ] +} \ No newline at end of file diff --git a/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/patterns_as_iotas.json b/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/patterns_as_iotas.json index a13fa7c7c2..c5aafed351 100644 --- a/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/patterns_as_iotas.json +++ b/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/patterns_as_iotas.json @@ -36,26 +36,6 @@ "anchor": "hexcasting:close_paren", "text": "hexcasting.page.patterns_as_iotas.parens.2" }, - { - "type": "hexcasting:pattern", - "op_id": "hexcasting:open_n_parens", - "anchor": "hexcasting:open_n_parens", - "input": "num", - "text": "hexcasting.page.patterns_as_iotas.advanced_parens.1" - }, - { - "type": "hexcasting:pattern", - "op_id": "hexcasting:close_all_parens", - "anchor": "hexcasting:close_all_parens", - "output": "num, [pattern]", - "text": "hexcasting.page.patterns_as_iotas.advanced_parens.2" - }, - { - "type": "hexcasting:pattern", - "op_id": "hexcasting:read_into_parens", - "anchor": "hexcasting:read_into_parens", - "text": "hexcasting.page.patterns_as_iotas.advanced_parens.3" - }, { "type": "hexcasting:pattern", "op_id": "hexcasting:undo", @@ -64,12 +44,13 @@ }, { "type": "patchouli:text", - "anchor": "further_notes", + "anchor": "escaping_escapers", "title": "hexcasting.page.patterns_as_iotas.further_notes.title", "text": "hexcasting.page.patterns_as_iotas.further_notes.1" }, { "type": "patchouli:text", + "anchor": "iota_embedding", "text": "hexcasting.page.patterns_as_iotas.further_notes.2" }, { diff --git a/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/readwrite.json b/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/readwrite.json index c661d10d73..495702887f 100644 --- a/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/readwrite.json +++ b/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/readwrite.json @@ -2,7 +2,7 @@ "name": "hexcasting.entry.readwrite", "category": "hexcasting:patterns", "icon": "minecraft:writable_book", - "sortnum": 9, + "sortnum": 10, "advancement": "hexcasting:root", "read_by_default": true, "pages": [ From 366338da966d99906d0c618b71639ad955e23ceb Mon Sep 17 00:00:00 2001 From: Robotgiggle <88736742+Robotgiggle@users.noreply.github.com> Date: Sat, 6 Jun 2026 00:56:06 -0400 Subject: [PATCH 05/10] Update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67fae2abea..fa3c73fb4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - Added a config toggle (default false) for the effect that makes iota-holding items display their entire NBT data when Advanced Tooltips is enabled ([#1021](https://github.com/FallingColors/HexMod/pull/1021)) @Robotgiggle - Added a pattern display overlay for pattern-holding items (ie scrolls or slates) while holding shift in the inventory ([#879](https://github.com/FallingColors/HexMod/pull/879)) @SamsTheNerd - Added connected textures for Akashic Ligatures when using Continuity or Optifine ([#885](https://github.com/FallingColors/HexMod/pull/885)) @kineticneticat -- Added Meditation and Recollection for advanced pattern-list creation ([#1046](https://github.com/FallingColors/HexMod/pull/1046)) @Robotgiggle +- Added Interjection, Meditation, and Recollection for advanced pattern-list creation ([#1103](https://github.com/FallingColors/HexMod/pull/1103)) @Robotgiggle ### Changed From 838fff3df34dd56529add617f59cfe897c001c08 Mon Sep 17 00:00:00 2001 From: Robotgiggle <88736742+Robotgiggle@users.noreply.github.com> Date: Sun, 7 Jun 2026 20:06:24 -0400 Subject: [PATCH 06/10] Mention interjection in embedding section --- .../main/resources/assets/hexcasting/lang/en_us.flatten.json5 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 3a385f9912..66f6d3bb0a 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 @@ -1976,7 +1976,7 @@ title: "Further Notes", "1": "I can escape the special behavior of patterns like $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)Intro-/$ and $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospection/$ by drawing a $(l:patterns/patterns_as_iotas#hexcasting:escape)$(action)Consideration/$ before them, which will simply add them to the list without affecting which the number of $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospections/$ I need to return to casting.$(br2)If I draw two $(l:patterns/patterns_as_iotas#hexcasting:escape)$(action)Considerations/$ in a row while $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)introspecting/$, it will add a single $(l:patterns/patterns_as_iotas#hexcasting:escape)$(action)Consideration/$ to the list.", "2": "If an iota other than a pattern is present in a list to be executed by $(l:patterns/meta#hexcasting:eval)$(action)Hermes' Gambit/$ or any other meta-evaluation pattern, it will normally result in a mishap. However, this can be avoided using the patterns described in this section.$(br2)Just as with pattern iotas, the patterns described here can be used to \"escape\" $(o)any other$() kind of iota, causing it to be pushed to the stack when something tries to evaluate it instead of causing a mishap.", - "3": "This technique may be useful if I want a _Hex to be able to reference a specific iota, such as a complicated vector or an entity reference, without having to construct or obtain it each time.$(br2)The process of getting such an iota into a list of patterns in the first place may be somewhat involved. The simplest method would be to draw a placeholder pattern when assembling the list, and then make use of $(l:patterns/lists#hexcasting:replace)$(action)Surgeon's Exaltation/$ to replace it with my desired iota.", + "3": "This technique may be useful if I want a _Hex to be able to reference a specific iota, such as a complicated vector or an entity reference, without having to construct or obtain it each time.$(br2)To get such an iota into a list of patterns in the first place, I can either use $(l:patterns/advanced_escaping#hexcasting:read_into_parens)$(action)Interjection/$ while creating the list, or draw a placeholder pattern and then use $(l:patterns/lists#hexcasting:replace)$(action)Surgeon's Exaltation/$ to replace it once the list has been created.", }, }, From 990cbe1e256261c90fe70c47f160faf8249ce0b1 Mon Sep 17 00:00:00 2001 From: Robotgiggle <88736742+Robotgiggle@users.noreply.github.com> Date: Thu, 11 Jun 2026 23:33:51 -0400 Subject: [PATCH 07/10] Address comments --- .../api/casting/eval/vm/FrameEvaluate.kt | 2 +- .../api/casting/iota/PatternIota.java | 15 +++++------ .../hexcasting/client/gui/GuiSpellcasting.kt | 25 ++++++++++--------- .../casting/actions/escaping/OpOpenNParens.kt | 6 ++--- .../actions/escaping/OpReadIntoParens.kt | 11 ++++---- .../common/casting/actions/escaping/OpUndo.kt | 3 ++- 6 files changed, 29 insertions(+), 33 deletions(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameEvaluate.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameEvaluate.kt index 85f2cbd8fe..5de016d147 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameEvaluate.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameEvaluate.kt @@ -17,7 +17,7 @@ import net.minecraft.server.level.ServerLevel /** * A list of patterns to be evaluated in sequence. * @property list the *remaining* list of patterns to be evaluated - * @property isMetacasting if this is being cast from a hermes / iris / thoth + * @property isMetacasting if this is being cast from a meta-evaluation pattern */ data class FrameEvaluate(val list: SpellList, val isMetacasting: Boolean) : ContinuationFrame { // Discard this frame and keep discarding frames. diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/PatternIota.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/PatternIota.java index 342addf372..776d64438f 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/PatternIota.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/PatternIota.java @@ -168,15 +168,12 @@ public boolean executable() { result.getSound()); } catch (Mishap mishap) { - // if a mishap happens mid-parens at the top level (ie when staffcasting), keep any in-progress parens - boolean wipeParens = false; - // if a mishap happens mid-parens while metacasting, wipe the parens - if we didn't do this, any open paren - // levels would remain open even though the metacast itself has mishapped and thus been aborted - if (continuation instanceof SpellContinuation.NotDone cnd && cnd.getFrame() instanceof FrameEvaluate frameEval) { - if (frameEval.isMetacasting()) { - wipeParens = true; - } - } + // If a mishap happens mid-parens at the top level (ie when staffcasting), keep any in-progress parens. + // If a mishap happens mid-parens while metacasting, wipe the parens - if we didn't do this, any open paren + // levels would remain open even though the metacast itself has mishapped and thus been aborted. + boolean wipeParens = continuation instanceof SpellContinuation.NotDone cnd + && cnd.getFrame() instanceof FrameEvaluate frameEval + && frameEval.isMetacasting(); return new CastResult( this, continuation, 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 1cefc3899f..5b1c370559 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 @@ -85,19 +85,20 @@ class GuiSpellcasting constructor( this.calculateIotaDisplays() } - fun canBeUndone(resPat: ResolvedPattern): Boolean { - // standard escaped patterns can always be undone - if (resPat.type == ResolvedPatternType.ESCAPED) return true - // everything else cannot be undone, with three exceptions: - // - unescaped Introspection and Meditation can be undone if there's nothing else left to undo - // - Interjection can always be undone (undoing its inserted iota) despite using the EVALUATED coloring - if (resPat.type == ResolvedPatternType.EVALUATED) { - if (resPat.pattern.angles == HexActions.OPEN_PAREN.prototype.angles || - resPat.pattern.angles == HexActions.OPEN_N_PARENS.prototype.angles || - resPat.pattern.angles == HexActions.READ_INTO_PARENS.prototype.angles) - return true + fun canBeUndone(resolvedPat: ResolvedPattern): Boolean { + return when (resolvedPat.type) { + // standard escaped patterns can always be undone + ResolvedPatternType.ESCAPED -> true + // everything else cannot be undone, with three exceptions: + // - unescaped Introspection and Meditation can be undone if there's nothing else left to undo + // - Interjection can always be undone (undoing its inserted iota) despite using the EVALUATED coloring + ResolvedPatternType.EVALUATED -> { + resolvedPat.pattern.angles == HexActions.OPEN_PAREN.prototype.angles || + resolvedPat.pattern.angles == HexActions.OPEN_N_PARENS.prototype.angles || + resolvedPat.pattern.angles == HexActions.READ_INTO_PARENS.prototype.angles + } + else -> false } - return false } fun calculateIotaDisplays() { diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpOpenNParens.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpOpenNParens.kt index 9fdb9bceee..2e0dafee68 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpOpenNParens.kt +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpOpenNParens.kt @@ -12,10 +12,8 @@ import at.petrak.hexcasting.common.lib.hex.HexEvalSounds object OpOpenNParens : Action { override fun operate(env: CastingEnvironment, image: CastingImage, continuation: SpellContinuation): OperationResult { val newStack = image.stack.toMutableList() - val layers = when (val topIota = newStack.removeLastOrNull()) { - null -> throw MishapNotEnoughArgs(1, 0) - else -> listOf(topIota).getPositiveInt(0) - } + val layers = newStack.getPositiveInt(newStack.lastIndex) + newStack.removeLast() val image2 = image.copy( stack = newStack, parenCount = layers diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpReadIntoParens.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpReadIntoParens.kt index 1a1177b8f4..fe4f3c1056 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpReadIntoParens.kt +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpReadIntoParens.kt @@ -22,12 +22,11 @@ object OpReadIntoParens : Action { val (handStack) = env.getHeldItemToOperateOn { val dataHolder = IXplatAbstractions.INSTANCE.findDataHolder(it) dataHolder != null && (dataHolder.readIota(env.world) != null || dataHolder.emptyIota() != null) - } - // If there are no data holders that are readable, find a data holder that isn't readable - // so that the error message is more helpful. - ?: env.getHeldItemToOperateOn { - val dataHolder = IXplatAbstractions.INSTANCE.findDataHolder(it) - dataHolder != null + } ?: env.getHeldItemToOperateOn { + // If there are no data holders that are readable, find a data holder that isn't readable + // so that the error message is more helpful. + val dataHolder = IXplatAbstractions.INSTANCE.findDataHolder(it) + dataHolder != null } ?: throw MishapBadOffhandItem.of(null, "iota.read") val datumHolder = IXplatAbstractions.INSTANCE.findDataHolder(handStack) diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpUndo.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpUndo.kt index 57a234ad57..fa9a236a0b 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpUndo.kt +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpUndo.kt @@ -23,7 +23,8 @@ object OpUndo : Action { val last = newParens.removeLastOrNull() var newParenCount = image.parenCount if (last == null) { - // if there was nothing in the parenthesized list, undo the initial open paren + // If there was nothing in the parenthesized list, undo the initial open paren. + // This gets set to 0 rather than just decremented in case we started with open-n-parens. newParenCount = 0 } else if (last.iota is PatternIota && !last.escaped) { // adjust paren count if undoing a non-escaped open or close paren From e2b7905c5c4eb0ca388103b4b7addf0dfa66e64d Mon Sep 17 00:00:00 2001 From: Robotgiggle <88736742+Robotgiggle@users.noreply.github.com> Date: Thu, 11 Jun 2026 23:46:22 -0400 Subject: [PATCH 08/10] Set escaped boolean when interjecting --- .../petrak/hexcasting/api/casting/eval/vm/CastingImage.kt | 8 +++++--- .../at/petrak/hexcasting/api/casting/eval/vm/CastingVM.kt | 6 ++---- .../java/at/petrak/hexcasting/api/casting/iota/Iota.java | 2 +- .../petrak/hexcasting/api/casting/iota/PatternIota.java | 2 +- .../common/casting/actions/escaping/OpReadIntoParens.kt | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingImage.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingImage.kt index 8e92c35053..267c0a1bab 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingImage.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingImage.kt @@ -77,11 +77,13 @@ data class CastingImage private constructor( fun withResetEscape() = this.copy(parenCount = 0, parenthesized = listOf(), escapeNext = false) /** - * Returns a copy of this with the provided iota added to the parenthesized list. + * Returns a copy of this with the provided iota added to the parenthesized list. `escaped` is used by + * [OpUndo][at.petrak.hexcasting.common.casting.actions.escaping.OpUndo] to determine whether undoing the + * parenthesized iota should adjust the paren count (escaped parens do not affect the count). */ - fun withNewParenthesized(iota: Iota): CastingImage { + fun withNewParenthesized(iota: Iota, escaped: Boolean = false): CastingImage { val newParens = this.parenthesized.toMutableList() - newParens.add(ParenthesizedIota(iota, false)) + newParens.add(ParenthesizedIota(iota, escaped)) return this.copy(parenthesized = newParens) } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingVM.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingVM.kt index 4daa133881..d243af7e21 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingVM.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingVM.kt @@ -117,11 +117,9 @@ class CastingVM(var image: CastingImage, val env: CastingEnvironment) { if (this.image.parenCount > 0) { // if we're inside parentheses, add the iota to the list with escaped set to true val newParens = this.image.parenthesized.toMutableList() - newParens.add(ParenthesizedIota(iota, true)) newImage = this.image.copy( - escapeNext = false, - parenthesized = newParens - ) + escapeNext = false + ).withNewParenthesized(iota, escaped = true) } else { // if we're not in parentheses, just push the iota to the stack val newStack = this.image.stack.toMutableList() diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/Iota.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/Iota.java index c884b052ce..12716ff48f 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/Iota.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/Iota.java @@ -78,7 +78,7 @@ protected Iota(@NotNull IotaType type, @NotNull Object payload) { return new CastResult( this, continuation, - vm.getImage().withNewParenthesized(this), + vm.getImage().withNewParenthesized(this, false), List.of(), ResolvedPatternType.ESCAPED, HexEvalSounds.NORMAL_EXECUTE); diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/PatternIota.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/PatternIota.java index 776d64438f..593e46a212 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/PatternIota.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/PatternIota.java @@ -124,7 +124,7 @@ public boolean executable() { return new CastResult( this, continuation, - vm.getImage().withNewParenthesized(this), + vm.getImage().withNewParenthesized(this, false), List.of(), ResolvedPatternType.ESCAPED, HexEvalSounds.NORMAL_EXECUTE); diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpReadIntoParens.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpReadIntoParens.kt index fe4f3c1056..8f70ba052e 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpReadIntoParens.kt +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpReadIntoParens.kt @@ -37,7 +37,7 @@ object OpReadIntoParens : Action { ?: throw MishapBadOffhandItem.of(handStack, "iota.read") return ParenthesizedOperationResult( - image.withNewParenthesized(datum), + image.withNewParenthesized(datum, escaped = true), // inserted parens should not adjust paren count listOf(), continuation, HexEvalSounds.NORMAL_EXECUTE, ResolvedPatternType.EVALUATED From bedf9b11e40d01ca2cbb71f142fe27eb5a360313 Mon Sep 17 00:00:00 2001 From: Robotgiggle <88736742+Robotgiggle@users.noreply.github.com> Date: Thu, 11 Jun 2026 23:51:25 -0400 Subject: [PATCH 09/10] Clarify mishap-in-parens behavior in operateInParens doc comment --- .../java/at/petrak/hexcasting/api/casting/castables/Action.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/castables/Action.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/castables/Action.kt index 132b298b21..9f5257dd06 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/castables/Action.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/castables/Action.kt @@ -53,8 +53,8 @@ interface Action { * The behavior of this action when inside parentheses (meaning `image.parenCount` will always be greater than 0). * By default, this just adds the pattern to the parenthesized list without updating the op count or performing any of its effects. * - * Note that behavior defined here can throw mishaps as usual. However, mishapping here will not affect the paren count, - * so the caster will still be in list-building mode after the mishap resolves. + * Note that behavior defined here can throw mishaps as usual. However, mishapping here while staffcasting will not + * affect the paren count, so the caster will still be in list-building mode after the mishap resolves. */ @Throws(Mishap::class) fun operateInParens( From dc4c5c65a13d31c986c503d60cf98eade869b7f3 Mon Sep 17 00:00:00 2001 From: Robotgiggle <88736742+Robotgiggle@users.noreply.github.com> Date: Wed, 17 Jun 2026 20:46:06 -0400 Subject: [PATCH 10/10] Increment op count in new actions --- .../common/casting/actions/escaping/OpCloseAllParens.kt | 2 +- .../hexcasting/common/casting/actions/escaping/OpOpenNParens.kt | 2 +- .../common/casting/actions/escaping/OpReadIntoParens.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpCloseAllParens.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpCloseAllParens.kt index 2c0121dae2..abddbaf468 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpCloseAllParens.kt +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpCloseAllParens.kt @@ -22,7 +22,7 @@ object OpCloseAllParens : Action { val newStack = image.stack.toMutableList() newStack.add(DoubleIota(image.parenCount.toDouble())) newStack.add(ListIota(image.parenthesized.toList().map { it.iota })) - val image2 = image.copy( + val image2 = image.withUsedOp().copy( stack = newStack, parenCount = 0, parenthesized = listOf() diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpOpenNParens.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpOpenNParens.kt index 2e0dafee68..3ad174eeef 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpOpenNParens.kt +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpOpenNParens.kt @@ -14,7 +14,7 @@ object OpOpenNParens : Action { val newStack = image.stack.toMutableList() val layers = newStack.getPositiveInt(newStack.lastIndex) newStack.removeLast() - val image2 = image.copy( + val image2 = image.withUsedOp().copy( stack = newStack, parenCount = layers ) diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpReadIntoParens.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpReadIntoParens.kt index 8f70ba052e..d250d9ff9d 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpReadIntoParens.kt +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpReadIntoParens.kt @@ -37,7 +37,7 @@ object OpReadIntoParens : Action { ?: throw MishapBadOffhandItem.of(handStack, "iota.read") return ParenthesizedOperationResult( - image.withNewParenthesized(datum, escaped = true), // inserted parens should not adjust paren count + image.withUsedOp().withNewParenthesized(datum, escaped = true), // inserted parens should not adjust paren count listOf(), continuation, HexEvalSounds.NORMAL_EXECUTE, ResolvedPatternType.EVALUATED