diff --git a/CHANGELOG.md b/CHANGELOG.md index 05c834cd67..1efa67d444 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 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 45af9fee79..4009d12fd5 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/castables/Action.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/castables/Action.kt index a3963d7640..631fdd1d06 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,25 @@ 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 { + return OperationResult( + image.withNewParenthesized(thisIota, false), + 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 f87fcfdfae..b50a5552eb 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/CastingImage.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingImage.kt index cf887a117b..66688c8276 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/eval/vm/CastingVM.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingVM.kt index 3d03a9a843..4daa133881 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,31 +109,38 @@ 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) } - 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() @@ -173,133 +180,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/Iota.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/Iota.java index cad6baee82..659c4f0d47 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,8 +49,8 @@ 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 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) { return new CastResult( @@ -67,6 +67,21 @@ protected Iota(@NotNull IotaType type, @NotNull Object payload) { 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); + } + /** * Returns whether this iota is possible to execute (i.e. whether {@link Iota#execute} has been overridden. */ 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 e3aa1c76f0..bfb2182234 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,13 +5,15 @@ 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; +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; 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; @@ -69,6 +71,28 @@ 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()); @@ -85,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(); @@ -98,15 +122,42 @@ 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(); - // do the actual calculation!! - var result = action.operate( + 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 +168,7 @@ public boolean toleratesOther(Iota that) { cont2, result.getNewImage(), sideEffects, - ResolvedPatternType.EVALUATED, + resolutionType, result.getSound()); } catch (Mishap mishap) { @@ -131,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 { 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 dfa159bda7..e6ec2a5053 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 new file mode 100644 index 0000000000..18ed2163df --- /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.MishapNeedsParens +import at.petrak.hexcasting.common.lib.hex.HexEvalSounds + +object OpCloseParen : Action { + override fun operate(env: CastingEnvironment, image: CastingImage, continuation: SpellContinuation): OperationResult { + throw MishapNeedsParens() + } + + 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 MishapNeedsParens() + } 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 0000000000..2c3656fb93 --- /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 0000000000..a0c826087b --- /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 0000000000..30f92efe08 --- /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.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 { + throw MishapNeedsParens() + } + + 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 + ) + // 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 + } +} \ 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 62dabec81f..95918ef011 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 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 dd39d0e0c5..54c4042942 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 5a01a51556..45fdc0aa70 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 1f8cc778af..0d7e834aeb 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",