diff --git a/Common/src/main/java/at/petrak/hexcasting/api/spell/OperatorUtils.kt b/Common/src/main/java/at/petrak/hexcasting/api/spell/OperatorUtils.kt index 6389208f..dc3bcfdb 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/spell/OperatorUtils.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/spell/OperatorUtils.kt @@ -275,9 +275,10 @@ fun List.getLongOrList(idx: Int, argc: Int = 0): Either { ) } -fun evaluatable(datum: Iota, reverseIdx: Int): Either = +fun evaluatable(datum: Iota, reverseIdx: Int): Either = when (datum) { - is PatternIota -> Either.left(datum.pattern) + is PatternIota -> Either.left(datum) + is ContinuationIota -> Either.left(datum) is ListIota -> Either.right(datum.list) else -> throw MishapInvalidIota( datum, diff --git a/Common/src/main/java/at/petrak/hexcasting/api/spell/casting/CastingHarness.kt b/Common/src/main/java/at/petrak/hexcasting/api/spell/casting/CastingHarness.kt index fc62f6b2..d9a1f9db 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/spell/casting/CastingHarness.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/spell/casting/CastingHarness.kt @@ -15,6 +15,7 @@ import at.petrak.hexcasting.api.spell.SpellList import at.petrak.hexcasting.api.spell.iota.Iota import at.petrak.hexcasting.api.spell.iota.ListIota import at.petrak.hexcasting.api.spell.iota.PatternIota +import at.petrak.hexcasting.api.spell.iota.ContinuationIota import at.petrak.hexcasting.api.spell.math.HexDir import at.petrak.hexcasting.api.spell.math.HexPattern import at.petrak.hexcasting.api.spell.mishaps.* @@ -150,6 +151,13 @@ class CastingHarness private constructor( return if (iota is PatternIota) { updateWithPattern(iota.pattern, world, continuation) + } else if (iota is ContinuationIota) { + CastResult( + iota.continuation, + null, + ResolvedPatternType.EVALUATED, + listOf() + ) } else { CastResult( continuation, diff --git a/Common/src/main/java/at/petrak/hexcasting/api/spell/casting/SpellContinuation.kt b/Common/src/main/java/at/petrak/hexcasting/api/spell/casting/SpellContinuation.kt index b1fc6efe..dfee470c 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/spell/casting/SpellContinuation.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/spell/casting/SpellContinuation.kt @@ -1,5 +1,12 @@ package at.petrak.hexcasting.api.spell.casting +import at.petrak.hexcasting.api.utils.NBTBuilder +import at.petrak.hexcasting.api.utils.getList +import at.petrak.hexcasting.api.utils.serializeToNBT +import net.minecraft.nbt.CompoundTag +import net.minecraft.nbt.Tag +import net.minecraft.server.level.ServerLevel + /** * A continuation during the execution of a spell. */ @@ -9,4 +16,32 @@ sealed interface SpellContinuation { data class NotDone(val frame: ContinuationFrame, val next: SpellContinuation) : SpellContinuation fun pushFrame(frame: ContinuationFrame): SpellContinuation = NotDone(frame, this) + + fun serializeToNBT() = NBTBuilder { + TAG_FRAME %= list(getNBTFrames()) + } + fun getNBTFrames(): List { + var self = this + val frames = mutableListOf() + while (self is NotDone) { + frames.add(self.frame.serializeToNBT()) + self = self.next + } + return frames + } + companion object { + const val TAG_FRAME = "frame" + + @JvmStatic + fun fromNBT(nbt: CompoundTag, world: ServerLevel): SpellContinuation { + val frames = nbt.getList(TAG_FRAME, Tag.TAG_COMPOUND) + var result: SpellContinuation = Done + for (frame in frames.asReversed()) { + if (frame is CompoundTag) { + result = result.pushFrame(ContinuationFrame.fromNBT(frame, world)) + } + } + return result + } + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/spell/iota/ContinuationIota.java b/Common/src/main/java/at/petrak/hexcasting/api/spell/iota/ContinuationIota.java new file mode 100644 index 00000000..fb447dc4 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/api/spell/iota/ContinuationIota.java @@ -0,0 +1,62 @@ +package at.petrak.hexcasting.api.spell.iota; + +import at.petrak.hexcasting.api.spell.casting.SpellContinuation; +import at.petrak.hexcasting.api.utils.HexUtils; +import at.petrak.hexcasting.common.lib.HexIotaTypes; +import net.minecraft.ChatFormatting; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.Tag; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerLevel; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * An iota storing a continuation (in essence an execution state). + */ +public class ContinuationIota extends Iota { + public static final Component DISPLAY = Component.translatable("hexcasting.tooltip.jump_iota").withStyle(ChatFormatting.RED); + + public ContinuationIota(SpellContinuation cont) { + super(HexIotaTypes.CONTINUATION, cont); + } + + public SpellContinuation getContinuation() { + return (SpellContinuation) this.payload; + } + + @Override + public boolean isTruthy() { + return true; + } + + @Override + public boolean toleratesOther(Iota that) { + return typesMatch(this, that) && that instanceof ContinuationIota cont && cont.getContinuation().equals(getContinuation()); + } + + @Override + public @NotNull + Tag serialize() { + return getContinuation().serializeToNBT(); + } + + public static IotaType TYPE = new IotaType<>() { + @Nullable + @Override + public ContinuationIota deserialize(Tag tag, ServerLevel world) throws IllegalArgumentException { + var compoundTag = HexUtils.downcast(tag, CompoundTag.TYPE); + return new ContinuationIota(SpellContinuation.fromNBT(compoundTag, world)); + } + + @Override + public Component display(Tag tag) { + return DISPLAY; + } + + @Override + public int color() { + return 0xff_cc0000; + } + }; +} diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/RegisterPatterns.java b/Common/src/main/java/at/petrak/hexcasting/common/casting/RegisterPatterns.java index d875493a..84dc4509 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/RegisterPatterns.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/RegisterPatterns.java @@ -18,6 +18,7 @@ import at.petrak.hexcasting.common.casting.operators.circles.OpCircleBounds; import at.petrak.hexcasting.common.casting.operators.circles.OpImpetusDir; import at.petrak.hexcasting.common.casting.operators.circles.OpImpetusPos; import at.petrak.hexcasting.common.casting.operators.eval.OpEval; +import at.petrak.hexcasting.common.casting.operators.eval.OpEvalBreakable; import at.petrak.hexcasting.common.casting.operators.eval.OpForEach; import at.petrak.hexcasting.common.casting.operators.eval.OpHalt; import at.petrak.hexcasting.common.casting.operators.lists.*; @@ -343,6 +344,8 @@ public class RegisterPatterns { // eval being a space filling curve feels apt doesn't it PatternRegistry.mapPattern(HexPattern.fromAngles("deaqq", HexDir.SOUTH_EAST), modLoc("eval"), OpEval.INSTANCE); + PatternRegistry.mapPattern(HexPattern.fromAngles("deaqqdaa", HexDir.SOUTH_EAST), modLoc("eval/cc"), + OpEvalBreakable.INSTANCE); PatternRegistry.mapPattern(HexPattern.fromAngles("aqdee", HexDir.SOUTH_WEST), modLoc("halt"), OpHalt.INSTANCE); diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/operators/eval/OpEval.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/operators/eval/OpEval.kt index 66349963..7ae7aec4 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/operators/eval/OpEval.kt +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/operators/eval/OpEval.kt @@ -33,7 +33,7 @@ object OpEval : Action { continuation.pushFrame(ContinuationFrame.FinishEval) // install a break-boundary after eval } - val instrsList = instrs.map({ SpellList.LList(0, listOf(PatternIota(it))) }, { it }) + val instrsList = instrs.map({ SpellList.LList(0, listOf(it)) }, { it }) val frame = ContinuationFrame.Evaluate(instrsList) return OperationResult(newCont.pushFrame(frame), stack, ravenmind, listOf()) } diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/operators/eval/OpEvalBreakable.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/operators/eval/OpEvalBreakable.kt new file mode 100644 index 00000000..394fca89 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/operators/eval/OpEvalBreakable.kt @@ -0,0 +1,41 @@ +package at.petrak.hexcasting.common.casting.operators.eval + +import at.petrak.hexcasting.api.spell.Action +import at.petrak.hexcasting.api.spell.OperationResult +import at.petrak.hexcasting.api.spell.SpellList +import at.petrak.hexcasting.api.spell.casting.CastingContext +import at.petrak.hexcasting.api.spell.casting.ContinuationFrame +import at.petrak.hexcasting.api.spell.casting.SpellContinuation +import at.petrak.hexcasting.api.spell.evaluatable +import at.petrak.hexcasting.api.spell.iota.Iota +import at.petrak.hexcasting.api.spell.iota.ContinuationIota + +object OpEvalBreakable : Action { + override fun operate( + continuation: SpellContinuation, + stack: MutableList, + ravenmind: Iota?, + ctx: CastingContext + ): OperationResult { + val datum = stack.removeLast() + val instrs = evaluatable(datum, 0) + + instrs.ifRight { + ctx.incDepth() + } + + // if not installed already... + // also, never make a break boundary when evaluating just one pattern + val newCont = + if (instrs.left().isPresent || (continuation is SpellContinuation.NotDone && continuation.frame is ContinuationFrame.FinishEval)) { + continuation + } else { + continuation.pushFrame(ContinuationFrame.FinishEval) // install a break-boundary after eval + } + + val instrsList = instrs.map({ SpellList.LList(0, listOf(it)) }, { it }) + val frame = ContinuationFrame.Evaluate(instrsList) + stack.add(ContinuationIota(continuation)) + return OperationResult(newCont.pushFrame(frame), stack, ravenmind, listOf()) + } +} diff --git a/Common/src/main/java/at/petrak/hexcasting/common/lib/HexIotaTypes.java b/Common/src/main/java/at/petrak/hexcasting/common/lib/HexIotaTypes.java index 5f6cb251..38dede74 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/lib/HexIotaTypes.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/lib/HexIotaTypes.java @@ -188,6 +188,7 @@ public class HexIotaTypes { public static final IotaType PATTERN = type("pattern", PatternIota.TYPE); public static final IotaType GARBAGE = type("garbage", GarbageIota.TYPE); public static final IotaType VEC3 = type("vec3", Vec3Iota.TYPE); + public static final IotaType CONTINUATION = type("continuation", ContinuationIota.TYPE); private static > T type(String name, T type) { diff --git a/Common/src/main/resources/assets/hexcasting/lang/en_us.json b/Common/src/main/resources/assets/hexcasting/lang/en_us.json index 0bdf1d9b..04ff528b 100644 --- a/Common/src/main/resources/assets/hexcasting/lang/en_us.json +++ b/Common/src/main/resources/assets/hexcasting/lang/en_us.json @@ -149,6 +149,7 @@ "hexcasting.tooltip.list_contents": "[%s]", "hexcasting.tooltip.pattern_iota": "HexPattern(%s)", "hexcasting.tooltip.null_iota": "NULL", + "hexcasting.tooltip.jump_iota": "[JUMP]", "hexcasting.tooltip.boolean_true": "True", "hexcasting.tooltip.boolean_false": "False", @@ -448,6 +449,7 @@ "hexcasting.spell.hexcasting:close_paren": "Retrospection", "hexcasting.spell.hexcasting:escape": "Consideration", "hexcasting.spell.hexcasting:eval": "Hermes' Gambit", + "hexcasting.spell.hexcasting:eval/cc": "Iris' Gambit", "hexcasting.spell.hexcasting:for_each": "Thoth's Gambit", "hexcasting.spell.hexcasting:halt": "Charon's Gambit", "hexcasting.spell.hexcasting:number": "Numerical Reflection: %s", @@ -994,6 +996,8 @@ "hexcasting.page.meta.for_each.2": "More specifically, for each element in the second list, it will:$(li)Create a new stack, with everything on the current stack plus that element$(li)Draw all the patterns in the first list$(li)Save all the iotas remaining on the stack to a list$(br)Then, after all is said and done, pushes the list of saved iotas onto the main stack.$(br2)No wonder all the practitioners of this art go mad.", "hexcasting.page.meta.halt.1": "This pattern forcibly halts a _Hex. This is mostly useless on its own, as I could simply just stop writing patterns, or put down my staff.", "hexcasting.page.meta.halt.2": "But when combined with $(l:patterns/meta#hexcasting:eval)$(action)Hermes'/$ or $(l:patterns/meta#hexcasting:for_each)$(action)Thoth's Gambits/$, it becomes $(italics)far/$ more interesting. Those patterns serve to 'contain' that halting, and rather than ending the entire _Hex, those gambits end instead. This can be used to cause $(l:patterns/meta#hexcasting:for_each)$(action)Thoth's Gambit/$ not to operate on every iota it's given. An escape from the madness, as it were.", + "hexcasting.page.meta.eval/cc.1": "Cast a pattern or list of patterns from the stack exactly like $(l:patterns/meta#hexcasting:eval)$(action)Hermes' Gambit/$, except that a unique \"Jump\" iota is pushed to the stack beforehand. ", + "hexcasting.page.meta.eval/cc.2": "When the \"Jump\"-iota is executed, it'll skip the rest of the patterns and jump directly to the end of the pattern list.$(p)While this may seem redundant given $(l:patterns/meta#hexcasting:halt)$(action)Charon's Gambit/$ exists, this allows you to exit $(italic)nested/$ $(l:patterns/meta#hexcasting:eval)$(action)Hermes'/$ invocations in a controlled way, where Charon only allows you to exit one.$(p)The \"Jump\" iota will apparently stay on the stack even after execution is finished... better not think about the implications of that.", "hexcasting.entry.circle_patterns": "Spell Circle Patterns", "hexcasting.page.circle_patterns.disclaimer": "These patterns must be cast from a $(l:greatwork/spellcircles)$(item)Spell Circle/$; trying to cast them through a $(l:items/staff)$(item)Staff/$ will fail rather spectacularly.", diff --git a/Common/src/main/resources/data/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/meta.json b/Common/src/main/resources/data/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/meta.json index b38c35e0..4c022573 100644 --- a/Common/src/main/resources/data/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/meta.json +++ b/Common/src/main/resources/data/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/meta.json @@ -18,6 +18,18 @@ "type": "patchouli:text", "text": "hexcasting.page.meta.eval.2" }, + { + "type": "hexcasting:pattern", + "op_id": "hexcasting:eval/cc", + "anchor": "hexcasting:eval/cc", + "input": "[pattern] | pattern", + "output": "many", + "text": "hexcasting.page.meta.eval/cc.1" + }, + { + "type": "patchouli:text", + "text": "hexcasting.page.meta.eval/cc.2" + }, { "type": "hexcasting:pattern", "op_id": "hexcasting:for_each",