diff --git a/CHANGELOG.md b/CHANGELOG.md index 95f4e19d1..866a04cfa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - Taxonomer's Distillation for entities - Sorter's Distillation and Stacking Distillation for items - Added Contemplation for escaping without the exponential growth of Consideration ([#1102](https://github.com/FallingColors/HexMod/pull/1102)) @IridescentVoid +- Added Interjection, Meditation, and Recollection for advanced pattern-list creation ([#1103](https://github.com/FallingColors/HexMod/pull/1103)) @Robotgiggle ### Changed diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/castables/Action.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/castables/Action.kt index c30280a86..9f5257dd0 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/castables/Action.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/castables/Action.kt @@ -39,6 +39,8 @@ interface Action { * * The userdata tag is copied for you, so you don't need to worry about mutation messing up things * behind the scenes. + * + * Note that `image.parenCount` will always be 0 here - if it's greater, [operateInParens] is used instead. */ @Throws(Mishap::class) fun operate( @@ -51,8 +53,8 @@ interface Action { * The behavior of this action when inside parentheses (meaning `image.parenCount` will always be greater than 0). * By default, this just adds the pattern to the parenthesized list without updating the op count or performing any of its effects. * - * Note that behavior defined here can throw mishaps as usual. However, mishapping here will not affect the paren count, - * so the caster will still be in list-building mode after the mishap resolves. + * Note that behavior defined here can throw mishaps as usual. However, mishapping here while staffcasting will not + * affect the paren count, so the caster will still be in list-building mode after the mishap resolves. */ @Throws(Mishap::class) fun operateInParens( 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 8e92c3505..267c0a1ba 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingImage.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingImage.kt @@ -77,11 +77,13 @@ data class CastingImage private constructor( fun withResetEscape() = this.copy(parenCount = 0, parenthesized = listOf(), escapeNext = false) /** - * Returns a copy of this with the provided iota added to the parenthesized list. + * Returns a copy of this with the provided iota added to the parenthesized list. `escaped` is used by + * [OpUndo][at.petrak.hexcasting.common.casting.actions.escaping.OpUndo] to determine whether undoing the + * parenthesized iota should adjust the paren count (escaped parens do not affect the count). */ - fun withNewParenthesized(iota: Iota): CastingImage { + fun withNewParenthesized(iota: Iota, escaped: Boolean = false): CastingImage { val newParens = this.parenthesized.toMutableList() - newParens.add(ParenthesizedIota(iota, false)) + newParens.add(ParenthesizedIota(iota, escaped)) return this.copy(parenthesized = newParens) } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingVM.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingVM.kt index 4daa13388..d243af7e2 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingVM.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingVM.kt @@ -117,11 +117,9 @@ class CastingVM(var image: CastingImage, val env: CastingEnvironment) { if (this.image.parenCount > 0) { // if we're inside parentheses, add the iota to the list with escaped set to true val newParens = this.image.parenthesized.toMutableList() - newParens.add(ParenthesizedIota(iota, true)) newImage = this.image.copy( - escapeNext = false, - parenthesized = newParens - ) + escapeNext = false + ).withNewParenthesized(iota, escaped = true) } else { // if we're not in parentheses, just push the iota to the stack val newStack = this.image.stack.toMutableList() diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameEvaluate.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameEvaluate.kt index 141148dfd..5de016d14 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameEvaluate.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameEvaluate.kt @@ -17,7 +17,7 @@ import net.minecraft.server.level.ServerLevel /** * A list of patterns to be evaluated in sequence. * @property list the *remaining* list of patterns to be evaluated - * @property isMetacasting only for sound effects, if this is being cast from a hermes / iris + * @property isMetacasting if this is being cast from a meta-evaluation pattern */ data class FrameEvaluate(val list: SpellList, val isMetacasting: Boolean) : ContinuationFrame { // Discard this frame and keep discarding frames. diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/Iota.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/Iota.java index 66c3b5395..df110ed2c 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 @@ -79,7 +79,7 @@ protected Iota(@NotNull IotaType type, @NotNull Object payload) { return new CastResult( this, continuation, - vm.getImage().withNewParenthesized(this), + vm.getImage().withNewParenthesized(this, false), List.of(), ResolvedPatternType.ESCAPED, HexEvalSounds.NORMAL_EXECUTE); diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/PatternIota.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/PatternIota.java index e24648df4..593e46a21 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/PatternIota.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/PatternIota.java @@ -7,6 +7,7 @@ import at.petrak.hexcasting.api.casting.eval.*; import at.petrak.hexcasting.api.casting.eval.sideeffects.OperatorSideEffect; import at.petrak.hexcasting.api.casting.eval.vm.CastingVM; +import at.petrak.hexcasting.api.casting.eval.vm.FrameEvaluate; import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation; import at.petrak.hexcasting.api.casting.math.HexPattern; import at.petrak.hexcasting.api.casting.mishaps.Mishap; @@ -123,7 +124,7 @@ public boolean executable() { return new CastResult( this, continuation, - vm.getImage().withNewParenthesized(this), + vm.getImage().withNewParenthesized(this, false), List.of(), ResolvedPatternType.ESCAPED, HexEvalSounds.NORMAL_EXECUTE); @@ -167,10 +168,16 @@ public boolean executable() { result.getSound()); } catch (Mishap mishap) { + // If a mishap happens mid-parens at the top level (ie when staffcasting), keep any in-progress parens. + // If a mishap happens mid-parens while metacasting, wipe the parens - if we didn't do this, any open paren + // levels would remain open even though the metacast itself has mishapped and thus been aborted. + boolean wipeParens = continuation instanceof SpellContinuation.NotDone cnd + && cnd.getFrame() instanceof FrameEvaluate frameEval + && frameEval.isMetacasting(); return new CastResult( this, continuation, - null, + wipeParens ? vm.getImage().withResetEscape() : null, List.of(new OperatorSideEffect.DoMishap(mishap, new Mishap.Context(this.getPattern(), castedName.get()))), mishap.resolutionType(vm.getEnv()), HexEvalSounds.MISHAP); 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 4759c50a4..5b1c37055 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt +++ b/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt @@ -72,11 +72,8 @@ class GuiSpellcasting constructor( // TODO this is the kinda hacky bit if (info.resolutionType == ResolvedPatternType.UNDONE) { - // find the last escaped pattern (or the opening paren if there's nothing else) and set it to UNDONE - this.patterns.reversed().drop(1).firstOrNull { - it.type == ResolvedPatternType.ESCAPED || - (it.type == ResolvedPatternType.EVALUATED && it.pattern.angles == HexActions.OPEN_PAREN.prototype.angles) - }?.let { it.type = ResolvedPatternType.UNDONE } + // find the last undo-able pattern and set its coloring to UNDONE + this.patterns.reversed().drop(1).firstOrNull { canBeUndone(it) }?.let { it.type = ResolvedPatternType.UNDONE } // use the normal EVALUATED coloring for the Evanition that was just drawn this.patterns.getOrNull(index)?.let { it.type = ResolvedPatternType.EVALUATED } } else this.patterns.getOrNull(index)?.let { @@ -88,6 +85,22 @@ class GuiSpellcasting constructor( this.calculateIotaDisplays() } + fun canBeUndone(resolvedPat: ResolvedPattern): Boolean { + return when (resolvedPat.type) { + // standard escaped patterns can always be undone + ResolvedPatternType.ESCAPED -> true + // everything else cannot be undone, with three exceptions: + // - unescaped Introspection and Meditation can be undone if there's nothing else left to undo + // - Interjection can always be undone (undoing its inserted iota) despite using the EVALUATED coloring + ResolvedPatternType.EVALUATED -> { + resolvedPat.pattern.angles == HexActions.OPEN_PAREN.prototype.angles || + resolvedPat.pattern.angles == HexActions.OPEN_N_PARENS.prototype.angles || + resolvedPat.pattern.angles == HexActions.READ_INTO_PARENS.prototype.angles + } + else -> false + } + } + fun calculateIotaDisplays() { val mc = Minecraft.getInstance() val width = (this.width * LHS_IOTAS_ALLOCATION).toInt() diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpCloseAllParens.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpCloseAllParens.kt new file mode 100644 index 000000000..abddbaf46 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpCloseAllParens.kt @@ -0,0 +1,32 @@ +package at.petrak.hexcasting.common.casting.actions.escaping + +import at.petrak.hexcasting.api.casting.castables.Action +import at.petrak.hexcasting.api.casting.eval.CastingEnvironment +import at.petrak.hexcasting.api.casting.eval.OperationResult +import at.petrak.hexcasting.api.casting.eval.ParenthesizedOperationResult +import at.petrak.hexcasting.api.casting.eval.ResolvedPatternType +import at.petrak.hexcasting.api.casting.eval.vm.CastingImage +import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation +import at.petrak.hexcasting.api.casting.iota.DoubleIota +import at.petrak.hexcasting.api.casting.iota.Iota +import at.petrak.hexcasting.api.casting.iota.ListIota +import at.petrak.hexcasting.api.casting.mishaps.MishapNeedsParens +import at.petrak.hexcasting.common.lib.hex.HexEvalSounds + +object OpCloseAllParens : Action { + override fun operate(env: CastingEnvironment, image: CastingImage, continuation: SpellContinuation): OperationResult { + throw MishapNeedsParens() + } + + override fun operateInParens(env: CastingEnvironment, image: CastingImage, continuation: SpellContinuation, thisIota: Iota): ParenthesizedOperationResult { + val newStack = image.stack.toMutableList() + newStack.add(DoubleIota(image.parenCount.toDouble())) + newStack.add(ListIota(image.parenthesized.toList().map { it.iota })) + val image2 = image.withUsedOp().copy( + stack = newStack, + parenCount = 0, + parenthesized = listOf() + ) + return ParenthesizedOperationResult(image2, listOf(), continuation, HexEvalSounds.NORMAL_EXECUTE, ResolvedPatternType.EVALUATED) + } +} \ No newline at end of file diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpOpenNParens.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpOpenNParens.kt new file mode 100644 index 000000000..3ad174eee --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpOpenNParens.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.vm.CastingImage +import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation +import at.petrak.hexcasting.api.casting.getPositiveInt +import at.petrak.hexcasting.api.casting.mishaps.MishapNotEnoughArgs +import at.petrak.hexcasting.common.lib.hex.HexEvalSounds + +object OpOpenNParens : Action { + override fun operate(env: CastingEnvironment, image: CastingImage, continuation: SpellContinuation): OperationResult { + val newStack = image.stack.toMutableList() + val layers = newStack.getPositiveInt(newStack.lastIndex) + newStack.removeLast() + val image2 = image.withUsedOp().copy( + stack = newStack, + parenCount = layers + ) + return OperationResult(image2, listOf(), continuation, HexEvalSounds.NORMAL_EXECUTE) + } + + // Since there's no nice way to determine how many new layers it should open when drawn inside parens, we don't + // override operateInParens() at all. This pattern is just treated as any other pattern when parenthesized. +} \ No newline at end of file diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpReadIntoParens.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpReadIntoParens.kt new file mode 100644 index 000000000..d250d9ff9 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpReadIntoParens.kt @@ -0,0 +1,46 @@ +package at.petrak.hexcasting.common.casting.actions.escaping + +import at.petrak.hexcasting.api.casting.castables.Action +import at.petrak.hexcasting.api.casting.eval.CastingEnvironment +import at.petrak.hexcasting.api.casting.eval.OperationResult +import at.petrak.hexcasting.api.casting.eval.ParenthesizedOperationResult +import at.petrak.hexcasting.api.casting.eval.ResolvedPatternType +import at.petrak.hexcasting.api.casting.eval.vm.CastingImage +import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation +import at.petrak.hexcasting.api.casting.iota.Iota +import at.petrak.hexcasting.api.casting.mishaps.MishapBadOffhandItem +import at.petrak.hexcasting.api.casting.mishaps.MishapNeedsParens +import at.petrak.hexcasting.common.lib.hex.HexEvalSounds +import at.petrak.hexcasting.xplat.IXplatAbstractions + +object OpReadIntoParens : Action { + override fun operate(env: CastingEnvironment, image: CastingImage, continuation: SpellContinuation): OperationResult { + throw MishapNeedsParens() + } + + override fun operateInParens(env: CastingEnvironment, image: CastingImage, continuation: SpellContinuation, thisIota: Iota): ParenthesizedOperationResult { + val (handStack) = env.getHeldItemToOperateOn { + val dataHolder = IXplatAbstractions.INSTANCE.findDataHolder(it) + dataHolder != null && (dataHolder.readIota(env.world) != null || dataHolder.emptyIota() != null) + } ?: env.getHeldItemToOperateOn { + // If there are no data holders that are readable, find a data holder that isn't readable + // so that the error message is more helpful. + val dataHolder = IXplatAbstractions.INSTANCE.findDataHolder(it) + dataHolder != null + } ?: throw MishapBadOffhandItem.of(null, "iota.read") + + val datumHolder = IXplatAbstractions.INSTANCE.findDataHolder(handStack) + ?: throw MishapBadOffhandItem.of(handStack, "iota.read") + + val datum = datumHolder.readIota(env.world) + ?: datumHolder.emptyIota() + ?: throw MishapBadOffhandItem.of(handStack, "iota.read") + + return ParenthesizedOperationResult( + image.withUsedOp().withNewParenthesized(datum, escaped = true), // inserted parens should not adjust paren count + 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/OpUndo.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpUndo.kt index 40158299f..0bd6364dc 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpUndo.kt +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/escaping/OpUndo.kt @@ -23,8 +23,9 @@ object OpUndo : Action { val last = newParens.removeLastOrNull() var newParenCount = image.parenCount if (last == null) { - // if there was nothing in the parenthesized list, undo the initial open paren - newParenCount-- + // If there was nothing in the parenthesized list, undo the initial open paren. + // This gets set to 0 rather than just decremented in case we started with open-n-parens. + newParenCount = 0 } else if (last.iota is PatternIota && !last.escaped) { // adjust paren count if undoing a non-escaped open or close paren when (last.iota.pattern.angles) { diff --git a/Common/src/main/java/at/petrak/hexcasting/common/lib/hex/HexActions.java b/Common/src/main/java/at/petrak/hexcasting/common/lib/hex/HexActions.java index dfdb73fa3..0eddf0b7e 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 @@ -399,6 +399,12 @@ public class HexActions { new ActionRegistryEntry(HexPattern.fromAngles("qqq", HexDir.WEST), OpOpenParen.INSTANCE)); public static final ActionRegistryEntry CLOSE_PAREN = make("close_paren", new ActionRegistryEntry(HexPattern.fromAngles("eee", HexDir.EAST), OpCloseParen.INSTANCE)); + public static final ActionRegistryEntry OPEN_N_PARENS = make("open_n_parens", + new ActionRegistryEntry(HexPattern.fromAngles("qdaqadq", HexDir.WEST), OpOpenNParens.INSTANCE)); + public static final ActionRegistryEntry CLOSE_ALL_PARENS = make("close_all_parens", + new ActionRegistryEntry(HexPattern.fromAngles("eadedae", HexDir.EAST), OpCloseAllParens.INSTANCE)); + public static final ActionRegistryEntry READ_INTO_PARENS = make("read_into_parens", + new ActionRegistryEntry(HexPattern.fromAngles("aqqqqqwded", HexDir.EAST), OpReadIntoParens.INSTANCE)); public static final ActionRegistryEntry UNDO = make("undo", new ActionRegistryEntry(HexPattern.fromAngles("eeedw", HexDir.EAST), OpUndo.INSTANCE)); diff --git a/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 b/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 index c5d47be7f..e8ce2e5f0 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 @@ -809,7 +809,7 @@ "lenient": "Sorter's Distillation", "strict": "Stacking Distillation", }, - + swap: "Jester's Gambit", rotate: "Rotation Gambit", rotate_reverse: "Rotation Gambit II", @@ -940,6 +940,9 @@ runtime_escape: "Contemplation", open_paren: "Introspection", close_paren: "Retrospection", + open_n_parens: "Meditation", + close_all_parens: "Recollection", + read_into_parens: "Interjection", undo: "Evanition", eval: "Hermes' Gambit", @@ -1332,6 +1335,7 @@ types: "Types", lists: "List Manipulation", patterns_as_iotas: "Escaping Patterns", + advanced_escaping: "Advanced Escaping", readwrite: "Reading and Writing", meta: "Meta-Evaluation", circle_patterns: "Spell Circle Patterns", @@ -1489,9 +1493,9 @@ whats_a_hex: { "1": "Conceptually, a _Hex is exactly as described in $(l:casting/101)Hex Casting 101/$: a sequence of actions, usually done with the goal of performing some kind of effect in the world by means of one or more spells.$(br2)This idea of a _Hex is fine for on-the-fly casting, but if I wish to record my work for posterity (or simply for my own reuse) I may need something a little more concrete.", "2": "Typically, this takes the form of a list of pattern iotas, as created by the techniques described in $(l:patterns/patterns_as_iotas)Escaping Patterns/$. With my _Hex placed onto the stack this way, I can cast it using meta-evaluation patterns like $(l:patterns/meta#hexcasting:eval)Hermes' Gambit/$, or infuse it into a $(l:items/hexcasting)Casting Item/$ item for easy reuse.$(br2)Note that this list is no different from any other list iota - I can manipulate it, save it into $(l:items/thought_knot)permanent/$ $(l:items/focus)storage/$, or even incorporate it into another _Hex.", - "3": "Note also that this list need not be composed exclusively of pattern iotas. Inserting iotas of other types into my _Hexes won't normally end well, but $(l:patterns/patterns_as_iotas#embedding)certain techniques/$ allow me to do it safely. Furthermore, some $(l:patterns/meta#hexcasting:eval/cc)special iotas/$ can be directly evaluated just like a pattern.$(br2)With this in mind, $(o)any/$ list of iotas can in theory be a _Hex - the only requirement is that I can successfully cast it.", + "3": "Note also that this list need not be composed exclusively of pattern iotas. Inserting iotas of other types into my _Hexes won't normally end well, but $(l:patterns/patterns_as_iotas#iota_embedding)certain techniques/$ allow me to do it safely. Furthermore, some $(l:patterns/meta#hexcasting:eval/cc)special iotas/$ can be directly evaluated just like a pattern.$(br2)With this in mind, $(o)any/$ list of iotas can in theory be a _Hex - the only requirement is that I can successfully cast it.", }, - + vectors: { "1": "It seems I will need to be adroit with vectors if I am to get anywhere in my studies. A vector is a set of three numbers that can be used to represent a variety of things, usually either a position in the world, an offset from one position to another, or a direction in space. The following pages provide a basic summary of how vectors work and what I can do with them.", "2": "All the vectors I will be working with as a $(hex)Hexcaster/$ are three-dimensional, since the world I inhabit has three dimensions.$(br2)Each vector has an X component, a Y component, and a Z component in that order, corresponding to the three coordinate axes of the world: the X axis runs east and west, the Y axis runs up and down, and the Z axis runs north and south.", @@ -1543,7 +1547,7 @@ incorrect_block: "The action requires some sort of block at a target location, but the block supplied was not suitable.$(br2)Causes bright green sparks, and causes an ephemeral explosion at the given location. The explosion doesn't seem to harm me, the world, or anything else though; it's just startling.", "needs_parens.title": "Absent Introspection", - needs_parens: "I attempted to draw $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospection/$ or $(l:patterns/patterns_as_iotas#hexcasting:undo)$(action)Evanition/$ without first drawing $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)Introspection/$.$(br2)Causes orange sparks, and pushes the pattern I tried to draw to the stack as an iota.", + needs_parens: "I attempted to draw a pattern that only works while $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)introspecting/$ without first drawing $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)Introspection/$.$(br2)Causes orange sparks, and pushes the pattern I tried to draw to the stack as an iota.", "too_many_patterns.title": "Lost in Thought", too_many_patterns: "I attempted to evaluate too many patterns in one _Hex. Often, this happens because I've accidentally created an infinite loop.$(br2)Causes dark blue sparks, and chokes all the air out of me.", @@ -1570,7 +1574,7 @@ "2": "$(li)A $(thing)Reflection/$ pops nothing and pushes one iota.$(li)A $(thing)Purification/$ pops one and pushes one.$(li)A $(thing)Distillation/$ pops two and pushes one.$(li)An $(thing)Exaltation/$ pops three or more and pushes one.$(li)A $(thing)Decomposition/$ pops one argument and pushes two.$(li)A $(thing)Disintegration/$ pops one and pushes three or more.$(li)Finally, a $(thing)Gambit/$ pushes or pops some other number (or rearranges the stack in some other manner).", "3": "Spells seem to be exempt from this nomenclature and are more or less named after what they do-- after all, why call it a $(action)Demoman's Gambit/$ when you could just say $(l:patterns/spells/basic#hexcasting:explode)$(action)Explosion/$?", }, - + mishaps2: { "1": "I have discovered new and horrifying modes of failure. I must not succumb to them.", @@ -1982,7 +1986,7 @@ remarks: "The way Nature interprets an entity given to $(l:patterns/types#hexcasting:compare_item/lenient)$(action)Sorter's Dstl./$ or $(l:patterns/types#hexcasting:compare_item/strict)$(action)Stacking Dstl./$ depends on its type:$(li)Item entities resolve to the items they represents$(li)Item frames resolve to the item they contain, and mishap if they are empty$(li)Players resolve to the item they are currently holding, prioritizing their primary hand" }, }, - + lists: { index: "Remove the number at the top of the stack, then replace the list at the top with the nth element of that list (where n is the number you removed). Replaces the list with $(thing)Null/$ if the number is out of bounds.", slice: "Remove the two numbers at the top of the stack, then take a sublist of the list at the top of the stack between those indices, lower bound inclusive, upper bound exclusive. For example, the 0, 2 sublist of [0, 1, 2, 3, 4] would be [0, 1].", @@ -2004,25 +2008,32 @@ patterns_as_iotas: { "1": "One of the many peculiarities of this art is that $(italic)patterns themselves/$ can act as iotas-- I can even put them onto my stack when casting.$(br2)This raises a fairly obvious question: how do I express them? If I simply drew a pattern, it would hardly tell Nature to add it to my stack-- rather, it would simply be matched to an action.", - "2": "Fortunately, Nature has provided me with a set of special patterns that I can use to work with other patterns directly.$(br2)In short, $(l:patterns/patterns_as_iotas#hexcasting:runtime_escape)$(action)Contemplation/$ and $(l:patterns/patterns_as_iotas#hexcasting:escape)$(action)Consideration/$ lets me add one pattern to the stack, and $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)Introspection/$ and $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospection/$ let me add a whole list.", + "2": "Fortunately, Nature has provided me with a set of special patterns that I can use to work with other patterns directly.$(br2)In short, $(l:patterns/patterns_as_iotas#hexcasting:runtime_escape)$(action)Contemplation/$ and $(l:patterns/patterns_as_iotas#hexcasting:escape)$(action)Consideration/$ let me add one pattern to the stack, $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)Introspection/$ and $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospection/$ let me add a whole list, and $(l:patterns/patterns_as_iotas#hexcasting:undo)$(action)Evanition/$ lets me undo mistakes during the creation of such a list.", runtime_escape: { "1": "To use $(l:patterns/patterns_as_iotas#hexcasting:runtime_escape)$(action)Contemplation/$, I draw it, then another arbitrary pattern. That second pattern is added to the stack.", "2": "One may find it helpful to think of this as \"escaping\" the pattern onto the stack, if they happen to be familiar with the science of computers.$(br2)The usual use for this is to copy the pattern to a $(l:items/scroll)$(item)Scroll/$ or $(l:items/slate)$(item)Slate/$ using $(l:patterns/readwrite#hexcasting:write)$(action)Scribe's Gambit/$, and then perhaps decorating with them.", }, parens: { - "1": "Drawing $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)Introspection/$ makes my drawing of patterns act differently, for a time. Until I draw $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)Retrospection/$, the patterns I draw are saved. Then, when I draw $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospection/$, they are added to the stack as a list iota.", + "1": "Drawing $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)Introspection/$ makes my drawing of patterns act differently, for a time. Until I draw $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospection/$, the patterns I draw are saved. Then, when I draw $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospection/$, they are added to the stack as a list iota.", "2": "If I draw another $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Introspection/$, it'll still be saved to the list, but I'll then have to draw $(italic)two/$ $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospections/$ to get back to normal casting.", }, - undo: "Finally, if I make a mistake while drawing patterns inside $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)Intro-/$ and $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospection/$ I can draw $(l:patterns/patterns_as_iotas#hexcasting:undo)$(action)Evanition/$ to remove the last pattern that I drew from the pattern list that is being constructed.", + undo: "Finally, if I make a mistake while drawing patterns inside $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)Intro-/$ and $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospection/$, I can draw $(l:patterns/patterns_as_iotas#hexcasting:undo)$(action)Evanition/$ to remove the last pattern that I drew from the pattern list that is being constructed.", escape: "Similar to $(l:patterns/patterns_as_iotas#hexcasting:runtime_escape)$(action)Contemplation/$, but acts even when drawn between $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)Intro-/$ and $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospection/$. This can \"escape\" other patterns that work between $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)Intro-/$ and $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospection/$, putting them into the list without any other effects.", further_notes: { title: "Further Notes", "1": "Because $(l:patterns/patterns_as_iotas#hexcasting:escape)$(action)Consideration/$ acts immediately within $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)Intro-/$ and $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospection/$, I need to draw two of them if I want to actually put $(l:patterns/patterns_as_iotas#hexcasting:escape)$(action)Consideration/$ into a pattern list. The first $(l:patterns/patterns_as_iotas#hexcasting:escape)$(action)Consideration/$ escapes the special behavior of the second, allowing it to go into the list.$(br2)$(l:patterns/patterns_as_iotas#hexcasting:runtime_escape)$(action)Contemplation/$, on the other hand, only needs to be drawn once no matter the depth.", "2": "If an iota other than a pattern is present in a list to be executed by $(l:patterns/meta#hexcasting:eval)$(action)Hermes' Gambit/$ or any other meta-evaluation pattern, it will normally result in a mishap. However, this can be avoided using the patterns described in this section.$(br2)Just as with pattern iotas, the patterns described here can be used to \"escape\" $(o)any other$() kind of iota, causing it to be pushed to the stack when something tries to evaluate it instead of causing a mishap.", - "3": "This technique may be useful if I want a _Hex to be able to reference a specific iota, such as a complicated vector or an entity reference, without having to construct or obtain it each time.$(br2)The process of getting such an iota into a list of patterns in the first place may be somewhat involved. The simplest method would be to draw a placeholder pattern when assembling the list, and then make use of $(l:patterns/lists#hexcasting:replace)$(action)Surgeon's Exaltation/$ to replace it with my desired iota.", + "3": "This technique may be useful if I want a _Hex to be able to reference a specific iota, such as a complicated vector or an entity reference, without having to construct or obtain it each time.$(br2)To get such an iota into a list of patterns in the first place, I can either use $(l:patterns/advanced_escaping#hexcasting:read_into_parens)$(action)Interjection/$ while creating the list, or draw a placeholder pattern and then use $(l:patterns/lists#hexcasting:replace)$(action)Surgeon's Exaltation/$ to replace it once the list has been created.", }, }, + advanced_escaping: { + "1": "$(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)Intro-/$ and $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospection/$ are very useful, but when building complex _Hexes I may need more advanced techniques.$(br2)This section describes such techniques: $(l:patterns/advanced_escaping#hexcasting:read_into_parens)$(action)Interjection/$ lets me insert iotas directly into an in-progress list, while $(l:patterns/advanced_escaping#hexcasting:open_n_parens)$(action)Meditation/$ and $(l:patterns/advanced_escaping#hexcasting:close_all_parens)$(action)Recollection/$ let me start and finish the list creation process with arbitrary levels of nesting depth.", + read_into_parens: "Only works while $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)introspecting/$. Reads an iota from my offhand, and adds it to the in-progress list. For iotas other than patterns, I may need to $(l:patterns/patterns_as_iotas#iota_embedding)$(action)take steps/$ to avoid a mishap when executing the list.", + open_n_parens: "Removes a number from the stack, then acts as though I'd drawn that many $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)Introspections/$. Treated as a normal pattern while $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)introspecting/$, so nesting this may require $(l:patterns/patterns_as_iotas#hexcasting:escape)$(action)tricks/$ to draw extra $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospections/$.", + close_all_parens: "Works like $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospection/$, but closes $(italic)all/$ open $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)Introspections/$ at once. The number of layers closed, along with the list of saved patterns, are then added to the stack.", + }, + readwrite: { "1": "This section deals with the storage of $(thing)Iotas/$ in a more permanent medium. Nearly any iota can be stored to a suitable item, such as a $(l:items/focus)$(item)Focus/$ or $(l:items/spellbook)$(item)Spellbook/$, and read back later. Certain items, such as an $(l:items/abacus)$(item)Abacus/$, can only be read from.$(br2)Iotas are usually read and written from the other hand, but it is also possible to read and write with an item when it is sitting on the ground as an item entity, or when in an item frame.", "2": "There may be other entities I can interact with in this way. For example, a $(l:items/scroll)$(item)Scroll/$ hung on the wall can have its pattern read off of it.$(br2)However, it seems I am unable to save a reference to another player, only me. I suppose an entity reference is similar to the idea of a True Name; perhaps Nature is helping to keep our Names out of the hands of enemies. If I want a friend to have my Name I can make a $(l:items/focus)$(item)Focus/$ for them.", diff --git a/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/advanced_escaping.json b/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/advanced_escaping.json new file mode 100644 index 000000000..faa138e3c --- /dev/null +++ b/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/advanced_escaping.json @@ -0,0 +1,34 @@ +{ + "name": "hexcasting.entry.advanced_escaping", + "category": "hexcasting:patterns", + "icon": "minecraft:emerald_block", + "sortnum": 10, + "advancement": "hexcasting:root", + "read_by_default": true, + "pages": [ + { + "type": "patchouli:text", + "text": "hexcasting.page.advanced_escaping.1" + }, + { + "type": "hexcasting:pattern", + "op_id": "hexcasting:read_into_parens", + "anchor": "hexcasting:read_into_parens", + "text": "hexcasting.page.advanced_escaping.read_into_parens" + }, + { + "type": "hexcasting:pattern", + "op_id": "hexcasting:open_n_parens", + "anchor": "hexcasting:open_n_parens", + "input": "num", + "text": "hexcasting.page.advanced_escaping.open_n_parens" + }, + { + "type": "hexcasting:pattern", + "op_id": "hexcasting:close_all_parens", + "anchor": "hexcasting:close_all_parens", + "output": "num, list", + "text": "hexcasting.page.advanced_escaping.close_all_parens" + } + ] +} \ No newline at end of file diff --git a/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/patterns_as_iotas.json b/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/patterns_as_iotas.json index ec2e8c1a8..7a7fbbbe5 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 @@ -55,7 +55,7 @@ }, { "type": "patchouli:text", - "anchor": "embedding", + "anchor": "iota_embedding", "text": "hexcasting.page.patterns_as_iotas.further_notes.2" }, {