From 81e9ad1312aafb74657c55ec4a48eb57d99086fd Mon Sep 17 00:00:00 2001 From: Robotgiggle <88736742+Robotgiggle@users.noreply.github.com> Date: Thu, 14 May 2026 18:52:29 -0400 Subject: [PATCH 01/12] Make escape patterns into proper Actions --- .../api/casting/castables/Action.kt | 26 +++ .../api/casting/eval/SpecialPatterns.java | 1 + .../api/casting/eval/vm/CastingVM.kt | 171 +++--------------- .../api/casting/iota/PatternIota.java | 35 +++- .../casting/actions/escaping/OpCloseParen.kt | 45 +++++ .../casting/actions/escaping/OpEscape.kt | 26 +++ .../casting/actions/escaping/OpOpenParen.kt | 31 ++++ .../common/casting/actions/escaping/OpUndo.kt | 36 ++++ .../hexcasting/common/lib/hex/HexActions.java | 11 +- 9 files changed, 225 insertions(+), 157 deletions(-) create mode 100644 Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpCloseParen.kt create mode 100644 Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpEscape.kt create mode 100644 Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpOpenParen.kt create mode 100644 Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpUndo.kt 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 a3963d764..63ad950f3 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 @@ -1,11 +1,16 @@ package at.petrak.hexcasting.api.casting.castables +import at.petrak.hexcasting.api.casting.eval.CastResult import at.petrak.hexcasting.api.casting.eval.CastingEnvironment import at.petrak.hexcasting.api.casting.eval.OperationResult +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.CastingImage.ParenthesizedIota import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation import at.petrak.hexcasting.api.casting.iota.Iota +import at.petrak.hexcasting.api.casting.math.HexPattern import at.petrak.hexcasting.api.casting.mishaps.Mishap +import at.petrak.hexcasting.common.lib.hex.HexEvalSounds import net.minecraft.world.phys.Vec3 import java.text.DecimalFormat @@ -41,6 +46,27 @@ interface Action { continuation: SpellContinuation ): OperationResult + /** + * The behavior of this action when inside parentheses. By default, this is just to add the pattern + * to the parenthesized list without updating the op count or performing any of its usual effects. + */ + @Throws(Mishap::class) + fun operateInParens( + env: CastingEnvironment, + image: CastingImage, + continuation: SpellContinuation, + thisIota: Iota, + ): Pair { + val newParens = image.parenthesized.toMutableList() + newParens.add(ParenthesizedIota(thisIota, false)) + return OperationResult( + image.copy(parenthesized = newParens), + listOf(), + continuation, + HexEvalSounds.NORMAL_EXECUTE + ) to ResolvedPatternType.ESCAPED + } + companion object { // I see why vazkii did this: you can't raycast out to infinity! const val RAYCAST_DISTANCE: Double = 32.0 diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/SpecialPatterns.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/SpecialPatterns.java index f87fcfdfa..b50a5552e 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/SpecialPatterns.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/SpecialPatterns.java @@ -3,6 +3,7 @@ import at.petrak.hexcasting.api.casting.math.HexDir; import at.petrak.hexcasting.api.casting.math.HexPattern; +@Deprecated(since = "0.11.4") public final class SpecialPatterns { public static final HexPattern INTROSPECTION = HexPattern.fromAngles("qqq", HexDir.WEST); public static final HexPattern RETROSPECTION = HexPattern.fromAngles("eee", HexDir.EAST); 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 3d03a9a84..52d712c50 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 @@ -109,30 +109,32 @@ class CastingVM(var image: CastingImage, val env: CastingEnvironment) { try { // TODO we can have a special intro/retro sound // ALSO TODO need to add reader macro-style things - try { - this.handleParentheses(iota)?.let { (data, resolutionType) -> - return@executeInner CastResult(iota, continuation, data, listOf(), resolutionType, HexEvalSounds.NORMAL_EXECUTE) + + // Handle single-iota escaping (ie via Consideration) + // This is here rather than in Iota since this behavior should not be overriden. + if (this.image.escapeNext) { + val newImage: CastingImage + 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 + ) + } else { + // if we're not in parentheses, just push the iota to the stack + val newStack = this.image.stack.toMutableList() + newStack.add(iota) + newImage = this.image.copy( + stack = newStack, + escapeNext = false, + ) } - } catch (e: MishapTooManyCloseParens) { - // This is ridiculous and needs to be fixed - return CastResult( - iota, - continuation, - null, - listOf( - OperatorSideEffect.DoMishap( - e, - Mishap.Context( - (iota as? PatternIota)?.pattern ?: HexPattern(HexDir.WEST), - HexAPI.instance().getRawHookI18n(HexAPI.modLoc("close_paren")) - ) - ) - ), - ResolvedPatternType.ERRORED, - HexEvalSounds.MISHAP - ) + return CastResult(iota, continuation, newImage, listOf(), ResolvedPatternType.ESCAPED, HexEvalSounds.NORMAL_EXECUTE) } + // Handle normal behavior (including parens escaping) return iota.execute(this, world, continuation) } catch (exception: Exception) { // This means something very bad has happened @@ -173,133 +175,6 @@ class CastingVM(var image: CastingImage, val env: CastingEnvironment) { return Pair(stackDescs, ravenmind) } - /** - * Return a non-null value if we handled this in some sort of parenthesey way, - * either escaping it onto the stack or changing the parenthese-handling state. - */ - @Throws(MishapTooManyCloseParens::class) - private fun handleParentheses(iota: Iota): Pair? { - val sig = (iota as? PatternIota)?.pattern?.angles - - var displayDepth = this.image.parenCount - - val out = if (displayDepth > 0) { - if (this.image.escapeNext) { - val newParens = this.image.parenthesized.toMutableList() - newParens.add(ParenthesizedIota(iota, true)) - this.image.copy( - escapeNext = false, - parenthesized = newParens - ) to ResolvedPatternType.ESCAPED - } else { - - when (sig) { - SpecialPatterns.CONSIDERATION.angles -> { - this.image.copy( - escapeNext = true, - ) to ResolvedPatternType.EVALUATED - } - - SpecialPatterns.EVANITION.angles -> { - val newParens = this.image.parenthesized.toMutableList() - val last = newParens.removeLastOrNull() - val newParenCount = this.image.parenCount + if (last == null || last.escaped || last.iota !is PatternIota) 0 else when (last.iota.pattern) { - SpecialPatterns.INTROSPECTION -> -1 - SpecialPatterns.RETROSPECTION -> 1 - else -> 0 - } - this.image.copy(parenthesized = newParens, parenCount = newParenCount) to if (last == null) ResolvedPatternType.ERRORED else ResolvedPatternType.UNDONE - } - - SpecialPatterns.INTROSPECTION.angles -> { - // we have escaped the parens onto the stack; we just also record our count. - val newParens = this.image.parenthesized.toMutableList() - newParens.add(ParenthesizedIota(iota, false)) - this.image.copy( - parenthesized = newParens, - parenCount = this.image.parenCount + 1 - ) to if (this.image.parenCount == 0) ResolvedPatternType.EVALUATED else ResolvedPatternType.ESCAPED - } - - SpecialPatterns.RETROSPECTION.angles -> { - val newParenCount = this.image.parenCount - 1 - displayDepth-- - if (newParenCount == 0) { - val newStack = this.image.stack.toMutableList() - newStack.add(ListIota(this.image.parenthesized.toList().map { it.iota })) - this.image.copy( - stack = newStack, - parenCount = newParenCount, - parenthesized = listOf() - ) to ResolvedPatternType.EVALUATED - } else if (newParenCount < 0) { - throw MishapTooManyCloseParens() - } else { - // we have this situation: "(()" - // we need to add the close paren - val newParens = this.image.parenthesized.toMutableList() - newParens.add(ParenthesizedIota(iota, false)) - this.image.copy( - parenCount = newParenCount, - parenthesized = newParens - ) to ResolvedPatternType.ESCAPED - } - } - - else -> { - val newParens = this.image.parenthesized.toMutableList() - newParens.add(ParenthesizedIota(iota, false)) - this.image.copy( - parenthesized = newParens - ) to ResolvedPatternType.ESCAPED - } - } - } - } else if (this.image.escapeNext) { - val newStack = this.image.stack.toMutableList() - newStack.add(iota) - this.image.copy( - stack = newStack, - escapeNext = false, - ) to ResolvedPatternType.ESCAPED - } else { - when (sig) { - SpecialPatterns.CONSIDERATION.angles -> { - this.image.copy( - escapeNext = true - ) to ResolvedPatternType.EVALUATED - } - - SpecialPatterns.INTROSPECTION.angles -> { - this.image.copy( - parenCount = this.image.parenCount + 1 - ) to ResolvedPatternType.EVALUATED - } - - SpecialPatterns.RETROSPECTION.angles -> { - throw MishapTooManyCloseParens() - } - - else -> { - null - } - } - } - - // TODO: replace this once we can read things from the client - /* - if (out != null) { - val display = if (iota is PatternIota) { - PatternNameHelper.representationForPattern(iota.pattern) - .copy() - .withStyle(if (out.second == ResolvedPatternType.ESCAPED) ChatFormatting.YELLOW else ChatFormatting.AQUA) - } else iota.display() - displayPatternDebug(this.escapeNext, displayDepth, display) - } - */ - return out - } - data class TempControllerInfo( var earlyExit: Boolean, ) 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 e3aa1c76f..f05723d09 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 @@ -5,8 +5,10 @@ import at.petrak.hexcasting.api.casting.PatternShapeMatch; import at.petrak.hexcasting.api.casting.castables.Action; import at.petrak.hexcasting.api.casting.eval.CastResult; +import at.petrak.hexcasting.api.casting.eval.OperationResult; import at.petrak.hexcasting.api.casting.eval.ResolvedPatternType; import at.petrak.hexcasting.api.casting.eval.sideeffects.OperatorSideEffect; +import at.petrak.hexcasting.api.casting.eval.vm.CastingImage; import at.petrak.hexcasting.api.casting.eval.vm.CastingVM; import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation; import at.petrak.hexcasting.api.casting.math.HexPattern; @@ -27,10 +29,12 @@ import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Style; import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.function.Supplier; @@ -74,6 +78,8 @@ public boolean toleratesOther(Iota that) { var lookup = PatternRegistryManifest.matchPattern(this.getPattern(), vm.getEnv()); vm.getEnv().precheckAction(lookup); + var inParens = vm.getImage().getParenCount() > 0; + Action action; if (lookup instanceof PatternShapeMatch.Normal || lookup instanceof PatternShapeMatch.PerWorld) { ResourceKey key; @@ -101,12 +107,27 @@ public boolean toleratesOther(Iota that) { throw new MishapInvalidPattern(this.getPattern()); } else throw new IllegalStateException(); - // do the actual calculation!! - var result = action.operate( - vm.getEnv(), - vm.getImage(), - continuation - ); + OperationResult result; + ResolvedPatternType resolutionType; + if (inParens) { + // handle parenthetized behavior + var resultAndType = action.operateInParens( + vm.getEnv(), + vm.getImage(), + continuation, + this + ); + result = resultAndType.getFirst(); + resolutionType = resultAndType.getSecond(); + } else { + // do the actual calculation!! + result = action.operate( + vm.getEnv(), + vm.getImage(), + continuation + ); + resolutionType = ResolvedPatternType.EVALUATED; + } var cont2 = result.getNewContinuation(); // TODO parens also break prescience @@ -117,7 +138,7 @@ public boolean toleratesOther(Iota that) { cont2, result.getNewImage(), sideEffects, - ResolvedPatternType.EVALUATED, + resolutionType, result.getSound()); } catch (Mishap mishap) { diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpCloseParen.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpCloseParen.kt new file mode 100644 index 000000000..8ee157286 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpCloseParen.kt @@ -0,0 +1,45 @@ +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.ResolvedPatternType +import at.petrak.hexcasting.api.casting.eval.vm.CastingImage +import at.petrak.hexcasting.api.casting.eval.vm.CastingImage.ParenthesizedIota +import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation +import at.petrak.hexcasting.api.casting.iota.Iota +import at.petrak.hexcasting.api.casting.iota.ListIota +import at.petrak.hexcasting.api.casting.mishaps.MishapTooManyCloseParens +import at.petrak.hexcasting.common.lib.hex.HexEvalSounds + +object OpCloseParen : Action { + override fun operate(env: CastingEnvironment, image: CastingImage, continuation: SpellContinuation): OperationResult { + throw MishapTooManyCloseParens() + } + + override fun operateInParens(env: CastingEnvironment, image: CastingImage, continuation: SpellContinuation, thisIota: Iota): Pair { + val newParenCount = image.parenCount - 1 + if (newParenCount == 0) { + val newStack = image.stack.toMutableList() + newStack.add(ListIota(image.parenthesized.toList().map { it.iota })) + val image2 = image.copy( + stack = newStack, + parenCount = newParenCount, + parenthesized = listOf() + ) + return OperationResult(image2, listOf(), continuation, HexEvalSounds.NORMAL_EXECUTE) to ResolvedPatternType.EVALUATED + } else if (newParenCount < 0) { + throw MishapTooManyCloseParens() + } else { + // we have this situation: "(()" + // we need to add the close paren + val newParens = image.parenthesized.toMutableList() + newParens.add(ParenthesizedIota(thisIota, false)) + val image2 = image.copy( + parenCount = newParenCount, + parenthesized = newParens + ) + return OperationResult(image2, listOf(), continuation, HexEvalSounds.NORMAL_EXECUTE) to ResolvedPatternType.ESCAPED + } + } +} \ No newline at end of file diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpEscape.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpEscape.kt new file mode 100644 index 000000000..2c3656fb9 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpEscape.kt @@ -0,0 +1,26 @@ +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.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.common.lib.hex.HexEvalSounds + +object OpEscape : Action { + override fun operate(env: CastingEnvironment, image: CastingImage, continuation: SpellContinuation): OperationResult { + val image2 = image.copy( + escapeNext = true + ) + return OperationResult(image2, listOf(), continuation, HexEvalSounds.NORMAL_EXECUTE) + } + + override fun operateInParens(env: CastingEnvironment, image: CastingImage, continuation: SpellContinuation, thisIota: Iota): Pair { + val image2 = image.copy( + escapeNext = true + ) + return OperationResult(image2, listOf(), continuation, HexEvalSounds.NORMAL_EXECUTE) to ResolvedPatternType.EVALUATED + } +} \ No newline at end of file diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpOpenParen.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpOpenParen.kt new file mode 100644 index 000000000..a0c826087 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpOpenParen.kt @@ -0,0 +1,31 @@ +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.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.common.lib.hex.HexEvalSounds + +object OpOpenParen : Action { + override fun operate(env: CastingEnvironment, image: CastingImage, continuation: SpellContinuation): OperationResult { + val image2 = image.copy( + parenCount = image.parenCount + 1 + ) + return OperationResult(image2, listOf(), continuation, HexEvalSounds.NORMAL_EXECUTE) + } + + override fun operateInParens(env: CastingEnvironment, image: CastingImage, continuation: SpellContinuation, thisIota: Iota): Pair { + // we have escaped the parens onto the stack; we just also record our count. + val newParens = image.parenthesized.toMutableList() + newParens.add(CastingImage.ParenthesizedIota(thisIota, false)) + val image2 = image.copy( + parenthesized = newParens, + parenCount = image.parenCount + 1 + ) + val resolutionType = if (image.parenCount == 0) ResolvedPatternType.EVALUATED else ResolvedPatternType.ESCAPED + return OperationResult(image2, listOf(), continuation, HexEvalSounds.NORMAL_EXECUTE) to resolutionType + } +} \ 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 new file mode 100644 index 000000000..0565f91f6 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpUndo.kt @@ -0,0 +1,36 @@ +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.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.iota.PatternIota +import at.petrak.hexcasting.api.casting.mishaps.MishapTooManyCloseParens +import at.petrak.hexcasting.common.lib.hex.HexActions +import at.petrak.hexcasting.common.lib.hex.HexEvalSounds + +object OpUndo : Action { + override fun operate(env: CastingEnvironment, image: CastingImage, continuation: SpellContinuation): OperationResult { + // TODO: this should have its own mishap + throw MishapTooManyCloseParens() + } + + override fun operateInParens(env: CastingEnvironment, image: CastingImage, continuation: SpellContinuation, thisIota: Iota): Pair { + val newParens = image.parenthesized.toMutableList() + val last = newParens.removeLastOrNull() + val newParenCount = image.parenCount + if (last == null || last.escaped || last.iota !is PatternIota) 0 else when (last.iota.pattern.angles) { + HexActions.OPEN_PAREN.prototype.angles -> -1 + HexActions.CLOSE_PAREN.prototype.angles -> 1 + else -> 0 + } + val image2 = image.copy( + parenthesized = newParens, + parenCount = newParenCount + ) + val resolutionType = if (last == null) ResolvedPatternType.ERRORED else ResolvedPatternType.UNDONE + return OperationResult(image2, listOf(), continuation, HexEvalSounds.NORMAL_EXECUTE) to resolutionType + } +} \ 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 62dabec81..95918ef01 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 @@ -16,6 +16,7 @@ import at.petrak.hexcasting.common.casting.actions.circles.OpCircleBounds; import at.petrak.hexcasting.common.casting.actions.circles.OpImpetusDir; import at.petrak.hexcasting.common.casting.actions.circles.OpImpetusPos; +import at.petrak.hexcasting.common.casting.actions.escaping.*; import at.petrak.hexcasting.common.casting.actions.eval.*; import at.petrak.hexcasting.common.casting.actions.lists.OpEmptyList; import at.petrak.hexcasting.common.casting.actions.lists.OpLastNToList; @@ -382,8 +383,14 @@ public class HexActions { // == Meta stuff == - // Intro/Retro/Consideration are now special-form-likes and aren't even ops. - // TODO should there be a registry for these too + public static final ActionRegistryEntry ESCAPE = make("escape", + new ActionRegistryEntry(HexPattern.fromAngles("qqqaw", HexDir.WEST), OpEscape.INSTANCE)); + public static final ActionRegistryEntry OPEN_PAREN = make("open_paren", + 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 UNDO = make("undo", + new ActionRegistryEntry(HexPattern.fromAngles("eeedw", HexDir.EAST), OpUndo.INSTANCE)); // http://www.toroidalsnark.net/mkss3-pix/CalderheadJMM2014.pdf // eval being a space filling curve feels apt doesn't it From 8f2d934f9efc502bf31175249835ee79b4180df4 Mon Sep 17 00:00:00 2001 From: Robotgiggle <88736742+Robotgiggle@users.noreply.github.com> Date: Wed, 13 May 2026 21:44:38 -0400 Subject: [PATCH 02/12] Fix iota embedding and invalid patterns in intro/retro --- .../api/casting/castables/Action.kt | 4 +- .../api/casting/eval/vm/CastingImage.kt | 10 +++++ .../hexcasting/api/casting/iota/Iota.java | 40 ++++++++++++------- .../api/casting/iota/PatternIota.java | 14 ++++++- 4 files changed, 50 insertions(+), 18 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 63ad950f3..631fdd1d0 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 @@ -57,10 +57,8 @@ interface Action { continuation: SpellContinuation, thisIota: Iota, ): Pair { - val newParens = image.parenthesized.toMutableList() - newParens.add(ParenthesizedIota(thisIota, false)) return OperationResult( - image.copy(parenthesized = newParens), + image.withNewParenthesized(thisIota, false), listOf(), continuation, HexEvalSounds.NORMAL_EXECUTE 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 cf887a117..66688c827 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 @@ -72,6 +72,16 @@ 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 parenthetized list. + * [escaped] determines whether the iota should be considered escaped or not. + */ + fun withNewParenthesized(iota: Iota, escaped: Boolean): CastingImage { + val newParens = this.parenthesized.toMutableList() + newParens.add(ParenthesizedIota(iota, escaped)) + return this.copy(parenthesized = newParens) + } + fun serializeToNbt() = NBTBuilder { TAG_STACK %= stack.serializeToNBT() 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 cad6baee8..154b5644d 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 @@ -49,22 +49,34 @@ protected Iota(@NotNull IotaType type, @NotNull Object payload) { abstract public @NotNull Tag serialize(); /** - * This method is called when this iota is executed (i.e. Hermes is run on a list containing it, unescaped). - * By default it will return a {@link CastResult} indicating an error has occurred. + * This method is called when this iota is executed (i.e. Hermes is run on a list containing it). + * By default, if the iota is not within parentheses, it will return a {@link CastResult} indicating an error has occurred. + * If the iota *is* within parentheses, it will instead be added to the parenthesized list. + * Iotas can also be escaped using Consideration, but that's handled in {@link CastingVM#executeInner}. */ public @NotNull CastResult execute(CastingVM vm, ServerLevel world, SpellContinuation continuation) { - return new CastResult( - this, - continuation, - null, // Should never matter - List.of( - new OperatorSideEffect.DoMishap( - new MishapUnescapedValue(this), - new Mishap.Context(new HexPattern(HexDir.WEST, List.of()), null) - ) - ), - ResolvedPatternType.INVALID, - HexEvalSounds.MISHAP); + if (vm.getImage().getParenCount() > 0) { + return new CastResult( + this, + continuation, + vm.getImage().withNewParenthesized(this, false), + List.of(), + ResolvedPatternType.ESCAPED, + HexEvalSounds.NORMAL_EXECUTE); + } else { + return new CastResult( + this, + continuation, + null, // Should never matter + List.of( + new OperatorSideEffect.DoMishap( + new MishapUnescapedValue(this), + new Mishap.Context(new HexPattern(HexDir.WEST, List.of()), null) + ) + ), + ResolvedPatternType.INVALID, + HexEvalSounds.MISHAP); + } } /** 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 f05723d09..98ca1114c 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 @@ -104,7 +104,19 @@ public boolean toleratesOther(Iota that) { castedName = special.handler::getName; action = special.handler.act(); } else if (lookup instanceof PatternShapeMatch.Nothing) { - throw new MishapInvalidPattern(this.getPattern()); + if (inParens) { + // invalid patterns still get added to the list when in parens + return new CastResult( + this, + continuation, + vm.getImage().withNewParenthesized(this, false), + List.of(), + ResolvedPatternType.ESCAPED, + HexEvalSounds.NORMAL_EXECUTE); + } else { + // if you draw something invalid outside parens, it mishaps + throw new MishapInvalidPattern(this.getPattern()); + } } else throw new IllegalStateException(); OperationResult result; From fe0fe44d51da60da857ac6771b67ad62395ab999 Mon Sep 17 00:00:00 2001 From: Robotgiggle <88736742+Robotgiggle@users.noreply.github.com> Date: Thu, 14 May 2026 18:55:40 -0400 Subject: [PATCH 03/12] Update lang and mishap description --- .../java/at/petrak/hexcasting/api/HexAPI.java | 5 +-- ...anyCloseParens.kt => MishapNeedsParens.kt} | 4 +- .../casting/actions/escaping/OpCloseParen.kt | 6 +-- .../common/casting/actions/escaping/OpUndo.kt | 6 +-- .../hexcasting/lang/en_us.flatten.json5 | 18 ++++----- .../en_us/entries/casting/mishaps.json | 4 +- .../entries/patterns/patterns_as_iotas.json | 40 ++++++------------- 7 files changed, 32 insertions(+), 51 deletions(-) rename Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/{MishapTooManyCloseParens.kt => MishapNeedsParens.kt} (90%) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/HexAPI.java b/Common/src/main/java/at/petrak/hexcasting/api/HexAPI.java index 45af9fee7..4009d12fd 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/HexAPI.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/HexAPI.java @@ -62,9 +62,7 @@ default String getSpecialHandlerI18nKey(ResourceKey> a return "hexcasting.special.%s".formatted(action.location().toString()); } - /** - * Currently introspection/retrospection/consideration are hardcoded, but at least their names won't be - */ + @Deprecated(since = "0.11.4") default String getRawHookI18nKey(ResourceLocation name) { return "hexcasting.rawhook.%s".formatted(name); } @@ -79,6 +77,7 @@ default Component getSpecialHandlerI18n(ResourceKey> k .withStyle(ChatFormatting.LIGHT_PURPLE); } + @Deprecated(since = "0.11.4") default Component getRawHookI18n(ResourceLocation name) { return Component.translatable(getRawHookI18nKey(name)).withStyle(ChatFormatting.LIGHT_PURPLE); } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapTooManyCloseParens.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapNeedsParens.kt similarity index 90% rename from Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapTooManyCloseParens.kt rename to Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapNeedsParens.kt index dfa159bda..e6ec2a505 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapTooManyCloseParens.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapNeedsParens.kt @@ -6,7 +6,7 @@ import at.petrak.hexcasting.api.casting.iota.PatternIota import at.petrak.hexcasting.api.pigment.FrozenPigment import net.minecraft.world.item.DyeColor -class MishapTooManyCloseParens : Mishap() { +class MishapNeedsParens : Mishap() { override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = dyeColor(DyeColor.ORANGE) @@ -17,5 +17,5 @@ class MishapTooManyCloseParens : Mishap() { } override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = - error("too_many_close_parens") + error("needs_parens") } diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpCloseParen.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpCloseParen.kt index 8ee157286..18ed2163d 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpCloseParen.kt +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpCloseParen.kt @@ -9,12 +9,12 @@ import at.petrak.hexcasting.api.casting.eval.vm.CastingImage.ParenthesizedIota import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation import at.petrak.hexcasting.api.casting.iota.Iota import at.petrak.hexcasting.api.casting.iota.ListIota -import at.petrak.hexcasting.api.casting.mishaps.MishapTooManyCloseParens +import at.petrak.hexcasting.api.casting.mishaps.MishapNeedsParens import at.petrak.hexcasting.common.lib.hex.HexEvalSounds object OpCloseParen : Action { override fun operate(env: CastingEnvironment, image: CastingImage, continuation: SpellContinuation): OperationResult { - throw MishapTooManyCloseParens() + throw MishapNeedsParens() } override fun operateInParens(env: CastingEnvironment, image: CastingImage, continuation: SpellContinuation, thisIota: Iota): Pair { @@ -29,7 +29,7 @@ object OpCloseParen : Action { ) return OperationResult(image2, listOf(), continuation, HexEvalSounds.NORMAL_EXECUTE) to ResolvedPatternType.EVALUATED } else if (newParenCount < 0) { - throw MishapTooManyCloseParens() + throw MishapNeedsParens() } else { // we have this situation: "(()" // we need to add the close paren 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 0565f91f6..30f92efe0 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 @@ -8,14 +8,13 @@ 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.iota.PatternIota -import at.petrak.hexcasting.api.casting.mishaps.MishapTooManyCloseParens +import at.petrak.hexcasting.api.casting.mishaps.MishapNeedsParens import at.petrak.hexcasting.common.lib.hex.HexActions import at.petrak.hexcasting.common.lib.hex.HexEvalSounds object OpUndo : Action { override fun operate(env: CastingEnvironment, image: CastingImage, continuation: SpellContinuation): OperationResult { - // TODO: this should have its own mishap - throw MishapTooManyCloseParens() + throw MishapNeedsParens() } override fun operateInParens(env: CastingEnvironment, image: CastingImage, continuation: SpellContinuation, thisIota: Iota): Pair { @@ -30,6 +29,7 @@ object OpUndo : Action { parenthesized = newParens, parenCount = newParenCount ) + // TODO: this should properly mishap if there was nothing to remove val resolutionType = if (last == null) ResolvedPatternType.ERRORED else ResolvedPatternType.UNDONE return OperationResult(image2, listOf(), continuation, HexEvalSounds.NORMAL_EXECUTE) to resolutionType } 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 dd39d0e0c..54c404294 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 @@ -915,6 +915,11 @@ create_lava: "Create Lava", "teleport/great": "Greater Teleport", brainsweep: "Flay Mind", + + escape: "Consideration", + open_paren: "Introspection", + close_paren: "Retrospection", + undo: "Evanition", eval: "Hermes' Gambit", "eval/cc": "Iris' Gambit", @@ -986,13 +991,6 @@ number: "Numerical Reflection: %s", mask: "Bookkeeper's Gambit: %s", }, - - "rawhook.hexcasting:": { - open_paren: "Introspection", - close_paren: "Retrospection", - escape: "Consideration", - undo: "Evanition", - }, "iota.hexcasting:": { null: { @@ -1042,7 +1040,7 @@ not_enough_args: "expected %s or more arguments but the stack was only %s tall", no_args: "expected %s or more arguments but the stack was empty", - too_many_close_parens: "Did not first use Introspection", + needs_parens: "Did not first use Introspection", wrong_dimension: "cannot see %s from %s", entity_too_far: "%s is out of range", @@ -1509,8 +1507,8 @@ "incorrect_block.title": "Incorrect Block", 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.", - "retrospection.title": "Hasty Retrospection", - retrospection: "I attempted to draw $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospection/$ without first drawing $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)Introspection/$.$(br2)Causes orange sparks, and pushes the pattern for $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospection/$ to the stack as a pattern iota.", + "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.", "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.", diff --git a/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/casting/mishaps.json b/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/casting/mishaps.json index 5a01a5155..45fdc0aa7 100644 --- a/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/casting/mishaps.json +++ b/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/casting/mishaps.json @@ -70,8 +70,8 @@ }, { "type": "patchouli:text", - "title": "hexcasting.page.mishaps.retrospection.title", - "text": "hexcasting.page.mishaps.retrospection" + "title": "hexcasting.page.mishaps.needs_parens.title", + "text": "hexcasting.page.mishaps.needs_parens" }, { "type": "patchouli:text", 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 1f8cc778a..0d7e834ae 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 @@ -15,48 +15,32 @@ "text": "hexcasting.page.patterns_as_iotas.2" }, { - "type": "hexcasting:manual_pattern", - "header": "hexcasting.rawhook.hexcasting:escape", + "type": "hexcasting:pattern", + "op_id": "hexcasting:escape", "anchor": "hexcasting:escape", - "text": "hexcasting.page.patterns_as_iotas.escape.1", - "patterns": { - "startdir": "WEST", - "signature": "qqqaw" - } + "text": "hexcasting.page.patterns_as_iotas.escape.1" }, { "type": "patchouli:text", "text": "hexcasting.page.patterns_as_iotas.escape.2" }, { - "type": "hexcasting:manual_pattern", - "header": "hexcasting.rawhook.hexcasting:open_paren", + "type": "hexcasting:pattern", + "op_id": "hexcasting:open_paren", "anchor": "hexcasting:open_paren", - "text": "hexcasting.page.patterns_as_iotas.parens.1", - "patterns": { - "startdir": "WEST", - "signature": "qqq" - } + "text": "hexcasting.page.patterns_as_iotas.parens.1" }, { - "type": "hexcasting:manual_pattern", - "header": "hexcasting.rawhook.hexcasting:close_paren", + "type": "hexcasting:pattern", + "op_id": "hexcasting:close_paren", "anchor": "hexcasting:close_paren", - "text": "hexcasting.page.patterns_as_iotas.parens.2", - "patterns": { - "startdir": "EAST", - "signature": "eee" - } + "text": "hexcasting.page.patterns_as_iotas.parens.2" }, { - "type": "hexcasting:manual_pattern", - "header": "hexcasting.rawhook.hexcasting:undo", + "type": "hexcasting:pattern", + "op_id": "hexcasting:undo", "anchor": "hexcasting:undo", - "text": "hexcasting.page.patterns_as_iotas.undo", - "patterns": { - "startdir": "EAST", - "signature": "eeedw" - } + "text": "hexcasting.page.patterns_as_iotas.undo" }, { "type": "patchouli:text", From 6b605c90fe1c908c6a6826f20b808b26002c406f Mon Sep 17 00:00:00 2001 From: Robotgiggle <88736742+Robotgiggle@users.noreply.github.com> Date: Thu, 14 May 2026 00:13:14 -0400 Subject: [PATCH 04/12] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05c834cd6..1efa67d44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ### Internal - The mod now uses Fabric Loom 1.9, Gradle 8.11, and Kotlin 2.0.20, by Robotgiggle in [#1043](https://github.com/FallingColors/HexMod/pull/1043). +- Added a method in `Action` to define behavior when inside parentheses, and de-hardcoded the iota-escaping patterns, by Robotgiggle in [#1047](https://github.com/FallingColors/HexMod/pull/1047). - Updated Inline dependency from 1.0.1 to 1.2.2, by Robotgiggle in [#1043](https://github.com/FallingColors/HexMod/pull/1043). ## `0.11.3` - 2025-11-22 From 4bfb4272568ab95f4f57dcbb0b108ba21f190788 Mon Sep 17 00:00:00 2001 From: Robotgiggle <88736742+Robotgiggle@users.noreply.github.com> Date: Thu, 14 May 2026 20:25:50 -0400 Subject: [PATCH 05/12] Add Iota.executeInParens to handle parens behavior --- .../api/casting/eval/vm/CastingVM.kt | 9 +++- .../hexcasting/api/casting/iota/Iota.java | 43 ++++++++-------- .../api/casting/iota/PatternIota.java | 49 ++++++++++++------- 3 files changed, 61 insertions(+), 40 deletions(-) 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 52d712c50..4daa13388 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 @@ -134,8 +134,13 @@ class CastingVM(var image: CastingImage, val env: CastingEnvironment) { return CastResult(iota, continuation, newImage, listOf(), ResolvedPatternType.ESCAPED, HexEvalSounds.NORMAL_EXECUTE) } - // Handle normal behavior (including parens escaping) - return iota.execute(this, world, continuation) + if (this.image.parenCount > 0) { + // Handle parens escaping + return iota.executeInParens(this, world, continuation) + } else { + // Handle normal execution behavior + return iota.execute(this, world, continuation) + } } catch (exception: Exception) { // This means something very bad has happened exception.printStackTrace() 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 154b5644d..659c4f0d4 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 @@ -49,34 +49,37 @@ protected Iota(@NotNull IotaType type, @NotNull Object payload) { abstract public @NotNull Tag serialize(); /** - * This method is called when this iota is executed (i.e. Hermes is run on a list containing it). - * By default, if the iota is not within parentheses, it will return a {@link CastResult} indicating an error has occurred. - * If the iota *is* within parentheses, it will instead be added to the parenthesized list. - * Iotas can also be escaped using Consideration, but that's handled in {@link CastingVM#executeInner}. + * This method is called when this iota is directly executed (i.e. Hermes is run on a list containing it, unescaped). + * By default, it will return a {@link CastResult} indicating an error has occurred. */ public @NotNull CastResult execute(CastingVM vm, ServerLevel world, SpellContinuation continuation) { - if (vm.getImage().getParenCount() > 0) { - return new CastResult( + return new CastResult( + this, + continuation, + null, // Should never matter + List.of( + new OperatorSideEffect.DoMishap( + new MishapUnescapedValue(this), + new Mishap.Context(new HexPattern(HexDir.WEST, List.of()), null) + ) + ), + ResolvedPatternType.INVALID, + HexEvalSounds.MISHAP); + } + + /** + * This method is called when this iota is executed inside parentheses (i.e. Hermes is run on a list containing Introspection, this, Retrospection). + * By default, the iota will be added to the in-progress parenthesized list instead of causing a mishap. + * This is specifically for parentheses-based escaping, Consideration escaping is handled in {@link CastingVM#executeInner}. + */ + public @NotNull CastResult executeInParens(CastingVM vm, ServerLevel world, SpellContinuation continuation) { + return new CastResult( this, continuation, vm.getImage().withNewParenthesized(this, false), List.of(), ResolvedPatternType.ESCAPED, HexEvalSounds.NORMAL_EXECUTE); - } else { - return new CastResult( - this, - continuation, - null, // Should never matter - List.of( - new OperatorSideEffect.DoMishap( - new MishapUnescapedValue(this), - new Mishap.Context(new HexPattern(HexDir.WEST, List.of()), null) - ) - ), - ResolvedPatternType.INVALID, - HexEvalSounds.MISHAP); - } } /** 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 98ca1114c..bfb218223 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 @@ -5,6 +5,7 @@ import at.petrak.hexcasting.api.casting.PatternShapeMatch; import at.petrak.hexcasting.api.casting.castables.Action; import at.petrak.hexcasting.api.casting.eval.CastResult; +import at.petrak.hexcasting.api.casting.eval.CastingEnvironment; import at.petrak.hexcasting.api.casting.eval.OperationResult; import at.petrak.hexcasting.api.casting.eval.ResolvedPatternType; import at.petrak.hexcasting.api.casting.eval.sideeffects.OperatorSideEffect; @@ -13,7 +14,6 @@ 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; -import at.petrak.hexcasting.api.casting.mishaps.MishapEvalTooMuch; import at.petrak.hexcasting.api.casting.mishaps.MishapInvalidPattern; import at.petrak.hexcasting.api.casting.mishaps.MishapUnenlightened; import at.petrak.hexcasting.api.mod.HexTags; @@ -29,12 +29,10 @@ import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Style; import net.minecraft.resources.ResourceKey; -import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.function.Supplier; @@ -73,13 +71,33 @@ public boolean toleratesOther(Iota that) { @Override public @NotNull CastResult execute(CastingVM vm, ServerLevel world, SpellContinuation continuation) { + return lookupAndOperate(vm, world, continuation, false); + } + + @Override + public @NotNull CastResult executeInParens(CastingVM vm, ServerLevel world, SpellContinuation continuation) { + return lookupAndOperate(vm, world, continuation, true); + } + + @Override + public boolean executable() { + return true; + } + + /** + * Look up this iota's pattern in the action registry, and attempt to invoke the behavior of the resulting Action. + * If {@code inParens} is false, the {@link Action#operate} method is used, and patterns lacking a matching Action will mishap. + * If {@code inParens} is true, the {@link Action#operateInParens} method is used instead, and patterns lacking a matching Action + * will be added to the in-progress parenthesized list as usual. + * In either case, any mishaps thrown during the lookup or operation process will be caught, and an appropriate + * CastResult will be returned. + */ + private @NotNull CastResult lookupAndOperate(CastingVM vm, ServerLevel world, SpellContinuation continuation, boolean inParens) { Supplier<@Nullable Component> castedName = () -> null; try { var lookup = PatternRegistryManifest.matchPattern(this.getPattern(), vm.getEnv()); vm.getEnv().precheckAction(lookup); - var inParens = vm.getImage().getParenCount() > 0; - Action action; if (lookup instanceof PatternShapeMatch.Normal || lookup instanceof PatternShapeMatch.PerWorld) { ResourceKey key; @@ -91,7 +109,7 @@ public boolean toleratesOther(Iota that) { } var reqsEnlightenment = isOfTag(IXplatAbstractions.INSTANCE.getActionRegistry(), key, - HexTags.Actions.REQUIRES_ENLIGHTENMENT); + HexTags.Actions.REQUIRES_ENLIGHTENMENT); castedName = () -> HexAPI.instance().getActionI18n(key, reqsEnlightenment); action = Objects.requireNonNull(IXplatAbstractions.INSTANCE.getActionRegistry().get(key)).action(); @@ -124,19 +142,19 @@ public boolean toleratesOther(Iota that) { if (inParens) { // handle parenthetized behavior var resultAndType = action.operateInParens( - vm.getEnv(), - vm.getImage(), - continuation, - this + vm.getEnv(), + vm.getImage(), + continuation, + this ); result = resultAndType.getFirst(); resolutionType = resultAndType.getSecond(); } else { // do the actual calculation!! result = action.operate( - vm.getEnv(), - vm.getImage(), - continuation + vm.getEnv(), + vm.getImage(), + continuation ); resolutionType = ResolvedPatternType.EVALUATED; } @@ -164,11 +182,6 @@ public boolean toleratesOther(Iota that) { } } - @Override - public boolean executable() { - return true; - } - public static IotaType TYPE = new IotaType<>() { @Override public PatternIota deserialize(Tag tag, ServerLevel world) throws IllegalArgumentException { From c39e057189a856e058d8fa0549757d5642fe3f68 Mon Sep 17 00:00:00 2001 From: Robotgiggle <88736742+Robotgiggle@users.noreply.github.com> Date: Wed, 27 May 2026 00:01:12 -0400 Subject: [PATCH 06/12] New changelog format --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1efa67d44..6b3f3a7bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ### Internal - The mod now uses Fabric Loom 1.9, Gradle 8.11, and Kotlin 2.0.20, by Robotgiggle in [#1043](https://github.com/FallingColors/HexMod/pull/1043). -- Added a method in `Action` to define behavior when inside parentheses, and de-hardcoded the iota-escaping patterns, by Robotgiggle in [#1047](https://github.com/FallingColors/HexMod/pull/1047). +- Added a method in `Action` to define behavior when inside parentheses, and de-hardcoded the iota-escaping patterns ([#1047](https://github.com/FallingColors/HexMod/pull/1047)) @Robotgiggle - Updated Inline dependency from 1.0.1 to 1.2.2, by Robotgiggle in [#1043](https://github.com/FallingColors/HexMod/pull/1043). ## `0.11.3` - 2025-11-22 From 6be85dcc05ac10b4d1d13769d033a16fe674c2f2 Mon Sep 17 00:00:00 2001 From: Robotgiggle <88736742+Robotgiggle@users.noreply.github.com> Date: Wed, 27 May 2026 14:36:39 -0400 Subject: [PATCH 07/12] Deprecation descriptions, update javadocs --- .../src/main/java/at/petrak/hexcasting/api/HexAPI.java | 6 ++++++ .../petrak/hexcasting/api/casting/castables/Action.kt | 4 ++-- .../hexcasting/api/casting/eval/SpecialPatterns.java | 3 +++ .../hexcasting/api/casting/eval/vm/CastingImage.kt | 4 ++++ .../at/petrak/hexcasting/api/casting/iota/Iota.java | 10 ++++++---- 5 files changed, 21 insertions(+), 6 deletions(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/HexAPI.java b/Common/src/main/java/at/petrak/hexcasting/api/HexAPI.java index 4009d12fd..1526fed93 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/HexAPI.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/HexAPI.java @@ -62,6 +62,9 @@ default String getSpecialHandlerI18nKey(ResourceKey> a return "hexcasting.special.%s".formatted(action.location().toString()); } + /** + * @deprecated Used to translate the names of patterns outside the standard Action system, none of which exist anymore. + */ @Deprecated(since = "0.11.4") default String getRawHookI18nKey(ResourceLocation name) { return "hexcasting.rawhook.%s".formatted(name); @@ -77,6 +80,9 @@ default Component getSpecialHandlerI18n(ResourceKey> k .withStyle(ChatFormatting.LIGHT_PURPLE); } + /** + * @deprecated Used to translate the names of patterns outside the standard Action system, none of which exist anymore. + */ @Deprecated(since = "0.11.4") default Component getRawHookI18n(ResourceLocation name) { return Component.translatable(getRawHookI18nKey(name)).withStyle(ChatFormatting.LIGHT_PURPLE); 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 631fdd1d0..8aae72611 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 @@ -47,8 +47,8 @@ interface Action { ): OperationResult /** - * The behavior of this action when inside parentheses. By default, this is just to add the pattern - * to the parenthesized list without updating the op count or performing any of its usual effects. + * 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. */ @Throws(Mishap::class) fun operateInParens( diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/SpecialPatterns.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/SpecialPatterns.java index b50a5552e..ac9da0a13 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/SpecialPatterns.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/SpecialPatterns.java @@ -3,6 +3,9 @@ import at.petrak.hexcasting.api.casting.math.HexDir; import at.petrak.hexcasting.api.casting.math.HexPattern; +/** + * @deprecated Stores angle signatures for non-Action patterns, all of which have since been made into Actions. + */ @Deprecated(since = "0.11.4") public final class SpecialPatterns { public static final HexPattern INTROSPECTION = HexPattern.fromAngles("qqq", HexDir.WEST); 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 66688c827..440661061 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 @@ -28,6 +28,10 @@ data class CastingImage private constructor( ) { constructor() : this(listOf(), 0, listOf(), false, 0, CompoundTag()) + /** + * `escaped` is used by [OpUndo][at.petrak.hexcasting.common.casting.actions.escaping.OpUndo] to determine whether the paren count + * needs to be adjusted when undoing an open or close paren pattern (if the pattern was escaped, no need to change anything). + */ data class ParenthesizedIota(val iota: Iota, val escaped: Boolean) { companion object { const val TAG_IOTAS = "iotas" 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 659c4f0d4..12716ff48 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 @@ -50,7 +50,7 @@ protected Iota(@NotNull IotaType type, @NotNull Object payload) { /** * This method is called when this iota is directly executed (i.e. Hermes is run on a list containing it, unescaped). - * By default, it will return a {@link CastResult} indicating an error has occurred. + * By default, it will return a {@link CastResult} with {@link MishapUnescapedValue} to indicate that an error has occurred. */ public @NotNull CastResult execute(CastingVM vm, ServerLevel world, SpellContinuation continuation) { return new CastResult( @@ -68,9 +68,11 @@ protected Iota(@NotNull IotaType type, @NotNull Object payload) { } /** - * This method is called when this iota is executed inside parentheses (i.e. Hermes is run on a list containing Introspection, this, Retrospection). - * By default, the iota will be added to the in-progress parenthesized list instead of causing a mishap. - * This is specifically for parentheses-based escaping, Consideration escaping is handled in {@link CastingVM#executeInner}. + * This method is called when this iota is executed inside parentheses (e.g. Hermes is run on a list of [Introspection, this, Retrospection]). + * Consequently, {@code vm.image.parenCount} will always be greater than 0. + * By default, the iota will be added to the in-progress parenthesized list rather than causing {@link MishapUnescapedValue}. + *

+ * This is specifically for parentheses-based escaping. Consideration escaping is handled in {@link CastingVM#executeInner}, and cannot be overridden. */ public @NotNull CastResult executeInParens(CastingVM vm, ServerLevel world, SpellContinuation continuation) { return new CastResult( From 79319191d3edeb88df76f55061fee32918b0b5d2 Mon Sep 17 00:00:00 2001 From: Robotgiggle <88736742+Robotgiggle@users.noreply.github.com> Date: Wed, 27 May 2026 14:39:27 -0400 Subject: [PATCH 08/12] ParenthesizedActionResult, withNewParenthesized tweak --- .../api/casting/castables/Action.kt | 12 ++++---- .../api/casting/eval/OperationResult.kt | 28 +++++++++++++++---- .../api/casting/eval/vm/CastingImage.kt | 7 ++--- .../hexcasting/api/casting/iota/Iota.java | 2 +- .../api/casting/iota/PatternIota.java | 17 ++++------- 5 files changed, 40 insertions(+), 26 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 8aae72611..c14e14b5d 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 @@ -3,6 +3,7 @@ package at.petrak.hexcasting.api.casting.castables import at.petrak.hexcasting.api.casting.eval.CastResult 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.CastingImage.ParenthesizedIota @@ -56,13 +57,14 @@ interface Action { image: CastingImage, continuation: SpellContinuation, thisIota: Iota, - ): Pair { - return OperationResult( - image.withNewParenthesized(thisIota, false), + ): ParenthesizedOperationResult { + return ParenthesizedOperationResult( + image.withNewParenthesized(thisIota), listOf(), continuation, - HexEvalSounds.NORMAL_EXECUTE - ) to ResolvedPatternType.ESCAPED + HexEvalSounds.NORMAL_EXECUTE, + ResolvedPatternType.ESCAPED + ) } companion object { diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/OperationResult.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/OperationResult.kt index 3a0f066fb..4dfd59977 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/OperationResult.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/OperationResult.kt @@ -5,12 +5,30 @@ import at.petrak.hexcasting.api.casting.eval.sideeffects.OperatorSideEffect import at.petrak.hexcasting.api.casting.eval.vm.CastingImage import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation +interface IOperationResult { + val newImage: CastingImage + val sideEffects: List + val newContinuation: SpellContinuation + val sound: EvalSound +} + /** * What happens when an operator is through? */ data class OperationResult( - val newImage: CastingImage, - val sideEffects: List, - val newContinuation: SpellContinuation, - val sound: EvalSound, -) + override val newImage: CastingImage, + override val sideEffects: List, + override val newContinuation: SpellContinuation, + override val sound: EvalSound, +) : IOperationResult + +/** + * What happens when an operator is through while parenthesized? + */ +data class ParenthesizedOperationResult( + override val newImage: CastingImage, + override val sideEffects: List, + override val newContinuation: SpellContinuation, + override val sound: EvalSound, + val resolutionType: ResolvedPatternType, +) : IOperationResult 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 440661061..8e92c3505 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,12 +77,11 @@ 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 parenthetized list. - * [escaped] determines whether the iota should be considered escaped or not. + * Returns a copy of this with the provided iota added to the parenthesized list. */ - fun withNewParenthesized(iota: Iota, escaped: Boolean): CastingImage { + fun withNewParenthesized(iota: Iota): CastingImage { val newParens = this.parenthesized.toMutableList() - newParens.add(ParenthesizedIota(iota, escaped)) + newParens.add(ParenthesizedIota(iota, false)) return this.copy(parenthesized = newParens) } 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 12716ff48..c884b052c 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, false), + vm.getImage().withNewParenthesized(this), 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 bfb218223..5211e0044 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 @@ -4,12 +4,8 @@ import at.petrak.hexcasting.api.casting.ActionRegistryEntry; import at.petrak.hexcasting.api.casting.PatternShapeMatch; import at.petrak.hexcasting.api.casting.castables.Action; -import at.petrak.hexcasting.api.casting.eval.CastResult; -import at.petrak.hexcasting.api.casting.eval.CastingEnvironment; -import at.petrak.hexcasting.api.casting.eval.OperationResult; -import at.petrak.hexcasting.api.casting.eval.ResolvedPatternType; +import at.petrak.hexcasting.api.casting.eval.*; import at.petrak.hexcasting.api.casting.eval.sideeffects.OperatorSideEffect; -import at.petrak.hexcasting.api.casting.eval.vm.CastingImage; import at.petrak.hexcasting.api.casting.eval.vm.CastingVM; import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation; import at.petrak.hexcasting.api.casting.math.HexPattern; @@ -127,7 +123,7 @@ public boolean executable() { return new CastResult( this, continuation, - vm.getImage().withNewParenthesized(this, false), + vm.getImage().withNewParenthesized(this), List.of(), ResolvedPatternType.ESCAPED, HexEvalSounds.NORMAL_EXECUTE); @@ -137,18 +133,17 @@ public boolean executable() { } } else throw new IllegalStateException(); - OperationResult result; + IOperationResult result; ResolvedPatternType resolutionType; if (inParens) { - // handle parenthetized behavior - var resultAndType = action.operateInParens( + // handle parenthesized behavior + result = action.operateInParens( vm.getEnv(), vm.getImage(), continuation, this ); - result = resultAndType.getFirst(); - resolutionType = resultAndType.getSecond(); + resolutionType = ((ParenthesizedOperationResult)result).getResolutionType(); } else { // do the actual calculation!! result = action.operate( From 10130d0193a7f136fb85672e80f05c84d926761e Mon Sep 17 00:00:00 2001 From: Robotgiggle <88736742+Robotgiggle@users.noreply.github.com> Date: Wed, 27 May 2026 14:39:43 -0400 Subject: [PATCH 09/12] Other assorted cleanup --- .../hexcasting/api/casting/iota/PatternIota.java | 6 +++--- .../common/casting/actions/escaping/OpCloseParen.kt | 2 -- .../common/casting/actions/escaping/OpOpenParen.kt | 3 +-- .../common/casting/actions/escaping/OpUndo.kt | 11 +++++++---- 4 files changed, 11 insertions(+), 11 deletions(-) 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 5211e0044..e24648df4 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 @@ -67,12 +67,12 @@ public boolean toleratesOther(Iota that) { @Override public @NotNull CastResult execute(CastingVM vm, ServerLevel world, SpellContinuation continuation) { - return lookupAndOperate(vm, world, continuation, false); + return lookupAndOperate(vm, continuation, false); } @Override public @NotNull CastResult executeInParens(CastingVM vm, ServerLevel world, SpellContinuation continuation) { - return lookupAndOperate(vm, world, continuation, true); + return lookupAndOperate(vm, continuation, true); } @Override @@ -88,7 +88,7 @@ public boolean executable() { * In either case, any mishaps thrown during the lookup or operation process will be caught, and an appropriate * CastResult will be returned. */ - private @NotNull CastResult lookupAndOperate(CastingVM vm, ServerLevel world, SpellContinuation continuation, boolean inParens) { + private @NotNull CastResult lookupAndOperate(CastingVM vm, SpellContinuation continuation, boolean inParens) { Supplier<@Nullable Component> castedName = () -> null; try { var lookup = PatternRegistryManifest.matchPattern(this.getPattern(), vm.getEnv()); diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpCloseParen.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpCloseParen.kt index 18ed2163d..492650635 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpCloseParen.kt +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpCloseParen.kt @@ -28,8 +28,6 @@ object OpCloseParen : Action { parenthesized = listOf() ) return OperationResult(image2, listOf(), continuation, HexEvalSounds.NORMAL_EXECUTE) to ResolvedPatternType.EVALUATED - } else if (newParenCount < 0) { - throw MishapNeedsParens() } else { // we have this situation: "(()" // we need to add the close paren diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpOpenParen.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpOpenParen.kt index a0c826087..114f8c5ef 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpOpenParen.kt +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpOpenParen.kt @@ -25,7 +25,6 @@ object OpOpenParen : Action { parenthesized = newParens, parenCount = image.parenCount + 1 ) - val resolutionType = if (image.parenCount == 0) ResolvedPatternType.EVALUATED else ResolvedPatternType.ESCAPED - return OperationResult(image2, listOf(), continuation, HexEvalSounds.NORMAL_EXECUTE) to resolutionType + return OperationResult(image2, listOf(), continuation, HexEvalSounds.NORMAL_EXECUTE) to ResolvedPatternType.ESCAPED } } \ 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 30f92efe0..9d3043933 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 @@ -20,10 +20,13 @@ object OpUndo : Action { override fun operateInParens(env: CastingEnvironment, image: CastingImage, continuation: SpellContinuation, thisIota: Iota): Pair { val newParens = image.parenthesized.toMutableList() val last = newParens.removeLastOrNull() - val newParenCount = image.parenCount + if (last == null || last.escaped || last.iota !is PatternIota) 0 else when (last.iota.pattern.angles) { - HexActions.OPEN_PAREN.prototype.angles -> -1 - HexActions.CLOSE_PAREN.prototype.angles -> 1 - else -> 0 + var newParenCount = image.parenCount + if (last?.iota is PatternIota && !last.escaped) { + // adjust paren count if undoing an open or close paren + when (last.iota.pattern.angles) { + HexActions.OPEN_PAREN.prototype.angles -> newParenCount-- + HexActions.CLOSE_PAREN.prototype.angles -> newParenCount++ + } } val image2 = image.copy( parenthesized = newParens, From 0b9b599ea61cc6a2cfeb42e145c121ff47f98ddb Mon Sep 17 00:00:00 2001 From: Robotgiggle <88736742+Robotgiggle@users.noreply.github.com> Date: Wed, 27 May 2026 14:49:13 -0400 Subject: [PATCH 10/12] Actually use ParenthesizedOperationResult --- .../common/casting/actions/escaping/OpCloseParen.kt | 7 ++++--- .../hexcasting/common/casting/actions/escaping/OpEscape.kt | 5 +++-- .../common/casting/actions/escaping/OpOpenParen.kt | 5 +++-- .../hexcasting/common/casting/actions/escaping/OpUndo.kt | 7 +++---- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpCloseParen.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpCloseParen.kt index 492650635..066632cff 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpCloseParen.kt +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpCloseParen.kt @@ -3,6 +3,7 @@ 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.CastingImage.ParenthesizedIota @@ -17,7 +18,7 @@ object OpCloseParen : Action { throw MishapNeedsParens() } - override fun operateInParens(env: CastingEnvironment, image: CastingImage, continuation: SpellContinuation, thisIota: Iota): Pair { + override fun operateInParens(env: CastingEnvironment, image: CastingImage, continuation: SpellContinuation, thisIota: Iota): ParenthesizedOperationResult { val newParenCount = image.parenCount - 1 if (newParenCount == 0) { val newStack = image.stack.toMutableList() @@ -27,7 +28,7 @@ object OpCloseParen : Action { parenCount = newParenCount, parenthesized = listOf() ) - return OperationResult(image2, listOf(), continuation, HexEvalSounds.NORMAL_EXECUTE) to ResolvedPatternType.EVALUATED + return ParenthesizedOperationResult(image2, listOf(), continuation, HexEvalSounds.NORMAL_EXECUTE, ResolvedPatternType.EVALUATED) } else { // we have this situation: "(()" // we need to add the close paren @@ -37,7 +38,7 @@ object OpCloseParen : Action { parenCount = newParenCount, parenthesized = newParens ) - return OperationResult(image2, listOf(), continuation, HexEvalSounds.NORMAL_EXECUTE) to ResolvedPatternType.ESCAPED + return ParenthesizedOperationResult(image2, listOf(), continuation, HexEvalSounds.NORMAL_EXECUTE, ResolvedPatternType.ESCAPED) } } } \ No newline at end of file diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpEscape.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpEscape.kt index 2c3656fb9..5c3a6e825 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpEscape.kt +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpEscape.kt @@ -3,6 +3,7 @@ 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 @@ -17,10 +18,10 @@ object OpEscape : Action { return OperationResult(image2, listOf(), continuation, HexEvalSounds.NORMAL_EXECUTE) } - override fun operateInParens(env: CastingEnvironment, image: CastingImage, continuation: SpellContinuation, thisIota: Iota): Pair { + override fun operateInParens(env: CastingEnvironment, image: CastingImage, continuation: SpellContinuation, thisIota: Iota): ParenthesizedOperationResult { val image2 = image.copy( escapeNext = true ) - return OperationResult(image2, listOf(), continuation, HexEvalSounds.NORMAL_EXECUTE) to ResolvedPatternType.EVALUATED + 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/OpOpenParen.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpOpenParen.kt index 114f8c5ef..87fddccdb 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpOpenParen.kt +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpOpenParen.kt @@ -3,6 +3,7 @@ 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 @@ -17,7 +18,7 @@ object OpOpenParen : Action { return OperationResult(image2, listOf(), continuation, HexEvalSounds.NORMAL_EXECUTE) } - override fun operateInParens(env: CastingEnvironment, image: CastingImage, continuation: SpellContinuation, thisIota: Iota): Pair { + override fun operateInParens(env: CastingEnvironment, image: CastingImage, continuation: SpellContinuation, thisIota: Iota): ParenthesizedOperationResult { // we have escaped the parens onto the stack; we just also record our count. val newParens = image.parenthesized.toMutableList() newParens.add(CastingImage.ParenthesizedIota(thisIota, false)) @@ -25,6 +26,6 @@ object OpOpenParen : Action { parenthesized = newParens, parenCount = image.parenCount + 1 ) - return OperationResult(image2, listOf(), continuation, HexEvalSounds.NORMAL_EXECUTE) to ResolvedPatternType.ESCAPED + return ParenthesizedOperationResult(image2, listOf(), continuation, HexEvalSounds.NORMAL_EXECUTE, ResolvedPatternType.ESCAPED) } } \ 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 9d3043933..e7c3ddd96 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 @@ -3,6 +3,7 @@ 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 @@ -17,7 +18,7 @@ object OpUndo : Action { throw MishapNeedsParens() } - override fun operateInParens(env: CastingEnvironment, image: CastingImage, continuation: SpellContinuation, thisIota: Iota): Pair { + override fun operateInParens(env: CastingEnvironment, image: CastingImage, continuation: SpellContinuation, thisIota: Iota): ParenthesizedOperationResult { val newParens = image.parenthesized.toMutableList() val last = newParens.removeLastOrNull() var newParenCount = image.parenCount @@ -32,8 +33,6 @@ object OpUndo : Action { parenthesized = newParens, parenCount = newParenCount ) - // TODO: this should properly mishap if there was nothing to remove - val resolutionType = if (last == null) ResolvedPatternType.ERRORED else ResolvedPatternType.UNDONE - return OperationResult(image2, listOf(), continuation, HexEvalSounds.NORMAL_EXECUTE) to resolutionType + return ParenthesizedOperationResult(image2, listOf(), continuation, HexEvalSounds.NORMAL_EXECUTE, ResolvedPatternType.UNDONE) } } \ No newline at end of file From e58dd41c17e1c4cf6788d27f88a89f161e89d384 Mon Sep 17 00:00:00 2001 From: Robotgiggle <88736742+Robotgiggle@users.noreply.github.com> Date: Wed, 27 May 2026 15:29:00 -0400 Subject: [PATCH 11/12] Evanition mishap (fixes #1077) --- CHANGELOG.md | 1 + .../api/casting/castables/Action.kt | 4 ++++ .../casting/mishaps/MishapNothingToUndo.kt | 20 +++++++++++++++++++ .../common/casting/actions/escaping/OpUndo.kt | 7 ++++--- .../hexcasting/lang/en_us.flatten.json5 | 6 +++++- .../en_us/entries/casting/mishaps.json | 5 +++++ 6 files changed, 39 insertions(+), 4 deletions(-) create mode 100644 Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapNothingToUndo.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b3f3a7bd..d9eb92de1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ### Added - Added the `cannot_modify_cost` tag for patterns that should ignore the `media_consumption` attribute when calculating cost, by Robotgiggle in [987](https://github.com/FallingColors/HexMod/pull/987). +- Added the Overeager Evanition mishap when trying to use Evanition with nothing to undo ([#1047](https://github.com/FallingColors/HexMod/pull/1047)) @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 c14e14b5d..a8aa1fdcb 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 @@ -50,6 +50,10 @@ 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, such as when [OpUndo][at.petrak.hexcasting.common.casting.actions.escaping.OpUndo] + * tries to remove a pattern but there aren't any left. Mishapping here 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( diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapNothingToUndo.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapNothingToUndo.kt new file mode 100644 index 000000000..6b65f892a --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapNothingToUndo.kt @@ -0,0 +1,20 @@ +package at.petrak.hexcasting.api.casting.mishaps + +import at.petrak.hexcasting.api.casting.eval.CastingEnvironment +import at.petrak.hexcasting.api.casting.iota.Iota +import at.petrak.hexcasting.api.casting.iota.PatternIota +import at.petrak.hexcasting.api.pigment.FrozenPigment +import net.minecraft.world.item.DyeColor + +class MishapNothingToUndo : Mishap() { + override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = + dyeColor(DyeColor.ORANGE) + + override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { + // no patterns to remove, so remove health instead >:) + env.mishapEnvironment.damage(0.05f) + } + + override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = + error("nothing_to_undo") +} \ 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 e7c3ddd96..3dd90ceac 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 @@ -10,6 +10,7 @@ import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation import at.petrak.hexcasting.api.casting.iota.Iota import at.petrak.hexcasting.api.casting.iota.PatternIota import at.petrak.hexcasting.api.casting.mishaps.MishapNeedsParens +import at.petrak.hexcasting.api.casting.mishaps.MishapNothingToUndo import at.petrak.hexcasting.common.lib.hex.HexActions import at.petrak.hexcasting.common.lib.hex.HexEvalSounds @@ -20,10 +21,10 @@ object OpUndo : Action { override fun operateInParens(env: CastingEnvironment, image: CastingImage, continuation: SpellContinuation, thisIota: Iota): ParenthesizedOperationResult { val newParens = image.parenthesized.toMutableList() - val last = newParens.removeLastOrNull() + val last = newParens.removeLastOrNull() ?: throw MishapNothingToUndo() var newParenCount = image.parenCount - if (last?.iota is PatternIota && !last.escaped) { - // adjust paren count if undoing an open or close paren + if (last.iota is PatternIota && !last.escaped) { + // adjust paren count if undoing a non-escaped open or close paren when (last.iota.pattern.angles) { HexActions.OPEN_PAREN.prototype.angles -> newParenCount-- HexActions.CLOSE_PAREN.prototype.angles -> newParenCount++ 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 54c404294..c140ea355 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 @@ -1041,6 +1041,7 @@ not_enough_args: "expected %s or more arguments but the stack was only %s tall", no_args: "expected %s or more arguments but the stack was empty", needs_parens: "Did not first use Introspection", + nothing_to_undo: "No patterns left to remove", wrong_dimension: "cannot see %s from %s", entity_too_far: "%s is out of range", @@ -1509,7 +1510,10 @@ "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.", - + + "nothing_to_undo.title": "Overeager Evanition", + nothing_to_undo: "I attempted to draw $(l:patterns/patterns_as_iotas#hexcasting:undo)$(action)Evanition/$ without any patterns for it to remove.$(br2)Causes orange sparks, and removes some of my health instead.", + "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.", diff --git a/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/casting/mishaps.json b/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/casting/mishaps.json index 45fdc0aa7..768444b11 100644 --- a/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/casting/mishaps.json +++ b/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/casting/mishaps.json @@ -73,6 +73,11 @@ "title": "hexcasting.page.mishaps.needs_parens.title", "text": "hexcasting.page.mishaps.needs_parens" }, + { + "type": "patchouli:text", + "title": "hexcasting.page.mishaps.nothing_to_undo.title", + "text": "hexcasting.page.mishaps.nothing_to_undo" + }, { "type": "patchouli:text", "title": "hexcasting.page.mishaps.too_many_patterns.title", From 76b31186fe1d9ef78eb7b8186435ce7a60a9a7c6 Mon Sep 17 00:00:00 2001 From: Robotgiggle <88736742+Robotgiggle@users.noreply.github.com> Date: Wed, 27 May 2026 15:36:08 -0400 Subject: [PATCH 12/12] Update changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 249dbb6ac..eeac25aed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ### Added -- Added the `cannot_modify_cost` tag for patterns that should ignore the `media_consumption` attribute when calculating cost, by Robotgiggle in [987](https://github.com/FallingColors/HexMod/pull/987). - Added the `cannot_modify_cost` tag for patterns that should ignore the `media_consumption` attribute when calculating cost ([#987](https://github.com/FallingColors/HexMod/pull/987)) @Robotgiggle - Added a config toggle for the pattern wobble animation on active slates to improve performance ([#1017](https://github.com/FallingColors/HexMod/pull/1017)) @aloaloolaola - Added keybinds to flip forward and backwards in a spellbook ([#1028](https://github.com/FallingColors/HexMod/pull/1028)) @pythonmcpi @@ -24,6 +23,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - Changed the `media_consumption` attribute to only apply to player-based casting ([#987](https://github.com/FallingColors/HexMod/pull/987)) @Robotgiggle - Changed Wayfarer's Flight and Anchorite's Flight to both cost 2 dust per unit, and enforced a minimum cost for Anchorite's Flight ([#1040](https://github.com/FallingColors/HexMod/pull/1040)) @Robotgiggle - Changed the pattern limit to also include execution of non-pattern iotas like jumps ([#1035](https://github.com/FallingColors/HexMod/pull/1035)) @pythonmcpi +- Changed the Hasty Retrospection mishap to Absent Introspection as it's now used for anything that only works while parenthesized ([#1047](https://github.com/FallingColors/HexMod/pull/1047)) @Robotgiggle - Updated the Flay Mind recipe display in EMI and JEI to cycle through all valid entities if the input is an entity tag ([#1023](https://github.com/FallingColors/HexMod/pull/1023)) @YukkuriC - Re-implemented the ability to extract stored media from items in trinket/curio slots ([#996](https://github.com/FallingColors/HexMod/pull/996)) @YukkuriC - Patterns involving entity look direction now compensate for the vanilla bug that causes projectiles and phantoms to report the wrong direction ([#1025](https://github.com/FallingColors/HexMod/pull/1025)) @Robotgiggle @@ -54,7 +54,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - Made the `getUsableStacks` and `getPrimaryStacks` methods in `CastingEnvironment` and its subclasses public ([#907](https://github.com/FallingColors/HexMod/pull/907)) @miyucomics - Improved handling for duplicate pattern signatures in hexdoc ([#1007](https://github.com/FallingColors/HexMod/pull/1007)) @object-Object - `CircleExecutionState` now stores the shape of the spell circle using two corners rather than an entire list of positions ([#908](https://github.com/FallingColors/HexMod/pull/908)) @Stick404 -- Added a method in `Action` to define behavior when inside parentheses, and de-hardcoded the iota-escaping patterns ([#1047](https://github.com/FallingColors/HexMod/pull/1047)) @Robotgiggle +- Added methods in `Action` and `Iota` to define behavior when inside parentheses, and de-hardcoded the iota-escaping patterns ([#1047](https://github.com/FallingColors/HexMod/pull/1047)) @Robotgiggle ## `0.11.3` - 2025-11-22