diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cd50c09c..eded816ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,9 +22,11 @@ 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 +- Drawing Evanition with nothing left to do will now undo the opening Introspection ([#1047](https://github.com/FallingColors/HexMod/pull/1047)) @Robotgiggle - Updated Inline dependency from 1.0.1 to 1.2.2 ([#1043](https://github.com/FallingColors/HexMod/pull/1043)) @Robotgiggle - Updated Fabric Language Kotlin dependency from 1.9.4 to 1.13.7 ([#1043](https://github.com/FallingColors/HexMod/pull/1043)) @Robotgiggle - Updated Kotlin for Forge dependency from 4.3.0 to 4.12.0 ([#1043](https://github.com/FallingColors/HexMod/pull/1043)) @Robotgiggle @@ -43,6 +45,8 @@ 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). +- Updated Inline dependency from 1.0.1 to 1.2.2, by Robotgiggle in [#1043](https://github.com/FallingColors/HexMod/pull/1043). - The mod now uses Fabric Loom 1.9, Gradle 8.11, and Kotlin 2.0.20 ([#1043](https://github.com/FallingColors/HexMod/pull/1043)) @Robotgiggle - Changed the internal implementation of Thoth's Gambit to use a `TreeList` for more efficiency ([#1031](https://github.com/FallingColors/HexMod/pull/1031)) @s5bug - Deprecated the version of `matchPattern` that takes a boolean argument since it always raises an exception ([#1002](https://github.com/FallingColors/HexMod/pull/1002)) @beholderface @@ -50,6 +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 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 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..1526fed93 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/HexAPI.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/HexAPI.java @@ -63,8 +63,9 @@ default String getSpecialHandlerI18nKey(ResourceKey> a } /** - * Currently introspection/retrospection/consideration are hardcoded, but at least their names won't be + * @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); } @@ -79,6 +80,10 @@ 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 a3963d764..c30280a86 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,17 @@ 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 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 +47,29 @@ interface Action { continuation: SpellContinuation ): OperationResult + /** + * The behavior of this action when inside parentheses (meaning `image.parenCount` will always be greater than 0). + * By default, this just adds the pattern to the parenthesized list without updating the op count or performing any of its effects. + * + * Note that behavior defined here can throw mishaps as usual. However, mishapping here will not affect the paren count, + * so the caster will still be in list-building mode after the mishap resolves. + */ + @Throws(Mishap::class) + fun operateInParens( + env: CastingEnvironment, + image: CastingImage, + continuation: SpellContinuation, + thisIota: Iota, + ): ParenthesizedOperationResult { + return ParenthesizedOperationResult( + image.withNewParenthesized(thisIota), + listOf(), + continuation, + HexEvalSounds.NORMAL_EXECUTE, + 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/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/SpecialPatterns.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/SpecialPatterns.java index f87fcfdfa..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,10 @@ 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); 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 cf887a117..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 @@ -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" @@ -72,6 +76,15 @@ data class CastingImage private constructor( */ fun withResetEscape() = this.copy(parenCount = 0, parenthesized = listOf(), escapeNext = false) + /** + * Returns a copy of this with the provided iota added to the parenthesized list. + */ + fun withNewParenthesized(iota: Iota): CastingImage { + val newParens = this.parenthesized.toMutableList() + newParens.add(ParenthesizedIota(iota, false)) + 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 3d03a9a84..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 @@ -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 cad6baee8..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 @@ -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} with {@link MishapUnescapedValue} to indicate that an error has occurred. */ public @NotNull CastResult execute(CastingVM vm, ServerLevel world, SpellContinuation continuation) { return new CastResult( @@ -67,6 +67,23 @@ protected Iota(@NotNull IotaType type, @NotNull Object payload) { HexEvalSounds.MISHAP); } + /** + * 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( + this, + continuation, + vm.getImage().withNewParenthesized(this), + 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 e3aa1c76f..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 @@ -4,14 +4,12 @@ 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.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.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 +67,28 @@ public boolean toleratesOther(Iota that) { @Override public @NotNull CastResult execute(CastingVM vm, ServerLevel world, SpellContinuation continuation) { + return lookupAndOperate(vm, continuation, false); + } + + @Override + public @NotNull CastResult executeInParens(CastingVM vm, ServerLevel world, SpellContinuation continuation) { + return lookupAndOperate(vm, 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, SpellContinuation continuation, boolean inParens) { Supplier<@Nullable Component> castedName = () -> null; try { var lookup = PatternRegistryManifest.matchPattern(this.getPattern(), vm.getEnv()); @@ -85,7 +105,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 +118,41 @@ 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), + 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( + IOperationResult result; + ResolvedPatternType resolutionType; + if (inParens) { + // handle parenthesized behavior + result = action.operateInParens( + vm.getEnv(), + vm.getImage(), + continuation, + this + ); + resolutionType = ((ParenthesizedOperationResult)result).getResolutionType(); + } 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 +163,7 @@ public boolean toleratesOther(Iota that) { cont2, result.getNewImage(), sideEffects, - ResolvedPatternType.EVALUATED, + resolutionType, result.getSound()); } catch (Mishap mishap) { @@ -131,11 +177,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 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/client/gui/GuiSpellcasting.kt b/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt index 208af39ea..4759c50a4 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt +++ b/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt @@ -19,6 +19,7 @@ import at.petrak.hexcasting.client.render.* import at.petrak.hexcasting.client.sound.GridSoundInstance import at.petrak.hexcasting.common.lib.HexAttributes import at.petrak.hexcasting.common.lib.HexSounds +import at.petrak.hexcasting.common.lib.hex.HexActions import at.petrak.hexcasting.common.msgs.MsgNewSpellPatternC2S import at.petrak.hexcasting.xplat.IClientXplatAbstractions import com.mojang.blaze3d.systems.RenderSystem @@ -71,7 +72,12 @@ class GuiSpellcasting constructor( // TODO this is the kinda hacky bit if (info.resolutionType == ResolvedPatternType.UNDONE) { - this.patterns.reversed().drop(1).firstOrNull { it.type == ResolvedPatternType.ESCAPED }?.let { it.type = ResolvedPatternType.UNDONE } + // find the last escaped pattern (or the opening paren if there's nothing else) and set it to UNDONE + this.patterns.reversed().drop(1).firstOrNull { + it.type == ResolvedPatternType.ESCAPED || + (it.type == ResolvedPatternType.EVALUATED && it.pattern.angles == HexActions.OPEN_PAREN.prototype.angles) + }?.let { it.type = ResolvedPatternType.UNDONE } + // use the normal EVALUATED coloring for the Evanition that was just drawn this.patterns.getOrNull(index)?.let { it.type = ResolvedPatternType.EVALUATED } } else this.patterns.getOrNull(index)?.let { it.type = info.resolutionType 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..066632cff --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpCloseParen.kt @@ -0,0 +1,44 @@ +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 +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): ParenthesizedOperationResult { + 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 ParenthesizedOperationResult(image2, listOf(), continuation, HexEvalSounds.NORMAL_EXECUTE, ResolvedPatternType.EVALUATED) + } 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 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 new file mode 100644 index 000000000..5c3a6e825 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpEscape.kt @@ -0,0 +1,27 @@ +package at.petrak.hexcasting.common.casting.actions.escaping + +import at.petrak.hexcasting.api.casting.castables.Action +import at.petrak.hexcasting.api.casting.eval.CastingEnvironment +import at.petrak.hexcasting.api.casting.eval.OperationResult +import at.petrak.hexcasting.api.casting.eval.ParenthesizedOperationResult +import at.petrak.hexcasting.api.casting.eval.ResolvedPatternType +import at.petrak.hexcasting.api.casting.eval.vm.CastingImage +import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation +import at.petrak.hexcasting.api.casting.iota.Iota +import at.petrak.hexcasting.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): ParenthesizedOperationResult { + val image2 = image.copy( + escapeNext = true + ) + 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 new file mode 100644 index 000000000..87fddccdb --- /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.ParenthesizedOperationResult +import at.petrak.hexcasting.api.casting.eval.ResolvedPatternType +import at.petrak.hexcasting.api.casting.eval.vm.CastingImage +import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation +import at.petrak.hexcasting.api.casting.iota.Iota +import at.petrak.hexcasting.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): 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)) + val image2 = image.copy( + parenthesized = newParens, + parenCount = image.parenCount + 1 + ) + 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 new file mode 100644 index 000000000..c8f1b3aed --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpUndo.kt @@ -0,0 +1,41 @@ +package at.petrak.hexcasting.common.casting.actions.escaping + +import at.petrak.hexcasting.api.casting.castables.Action +import at.petrak.hexcasting.api.casting.eval.CastingEnvironment +import at.petrak.hexcasting.api.casting.eval.OperationResult +import at.petrak.hexcasting.api.casting.eval.ParenthesizedOperationResult +import at.petrak.hexcasting.api.casting.eval.ResolvedPatternType +import at.petrak.hexcasting.api.casting.eval.vm.CastingImage +import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation +import at.petrak.hexcasting.api.casting.iota.Iota +import at.petrak.hexcasting.api.casting.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): ParenthesizedOperationResult { + val newParens = image.parenthesized.toMutableList() + val last = newParens.removeLastOrNull() + var newParenCount = image.parenCount + if (last == null) { + // if there was nothing in the parenthesized list, undo the initial open paren + newParenCount-- + } else if (last.iota is PatternIota && !last.escaped) { + // adjust paren count if undoing a non-escaped open or close paren + when (last.iota.pattern.angles) { + HexActions.OPEN_PAREN.prototype.angles -> newParenCount-- + HexActions.CLOSE_PAREN.prototype.angles -> newParenCount++ + } + } + val image2 = image.copy( + parenthesized = newParens, + parenCount = newParenCount + ) + return ParenthesizedOperationResult(image2, listOf(), continuation, HexEvalSounds.NORMAL_EXECUTE, ResolvedPatternType.UNDONE) + } +} \ 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 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..b7c6fa130 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,9 +1507,9 @@ "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",