Add in Alwinfy's Iris' Gambit

This commit is contained in:
Talia-12 2023-06-02 22:35:30 +10:00
commit 5d90b58219
12 changed files with 206 additions and 50 deletions

View file

@ -275,11 +275,10 @@ fun List<Iota>.getLongOrList(idx: Int, argc: Int = 0): Either<Long, SpellList> {
)
}
fun evaluatable(datum: Iota, reverseIdx: Int): Either<HexPattern, SpellList> =
fun evaluatable(datum: Iota, reverseIdx: Int): Either<Iota, SpellList> =
when (datum) {
is PatternIota -> Either.left(datum.pattern)
is ListIota -> Either.right(datum.list)
else -> throw MishapInvalidIota(
else -> if (datum.executable()) Either.left(datum) else throw MishapInvalidIota(
datum,
reverseIdx,
"hexcasting.mishap.invalid_value.evaluatable".asTranslatedComponent

View file

@ -48,7 +48,7 @@ sealed interface ContinuationFrame {
@JvmStatic
fun fromNBT(tag: CompoundTag, world: ServerLevel): ContinuationFrame {
return when (tag.getString("type")) {
"eval" -> FrameEvaluate(
"evaluate" -> FrameEvaluate(
HexIotaTypes.LIST.deserialize(
tag.getList("patterns", Tag.TAG_COMPOUND),
world

View file

@ -1,5 +1,11 @@
package at.petrak.hexcasting.api.casting.eval.vm
import at.petrak.hexcasting.api.utils.NBTBuilder
import at.petrak.hexcasting.api.utils.getList
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 +15,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<CompoundTag> {
var self = this
val frames = mutableListOf<CompoundTag>()
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
}
}
}

View file

@ -0,0 +1,77 @@
package at.petrak.hexcasting.api.casting.iota;
import at.petrak.hexcasting.api.casting.eval.CastResult;
import at.petrak.hexcasting.api.casting.eval.ResolvedPatternType;
import at.petrak.hexcasting.api.casting.eval.vm.CastingVM;
import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation;
import at.petrak.hexcasting.api.utils.HexUtils;
import at.petrak.hexcasting.common.lib.hex.HexEvalSounds;
import at.petrak.hexcasting.common.lib.hex.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;
import java.util.List;
/**
* 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();
}
@Override
public @NotNull CastResult execute(CastingVM vm, ServerLevel world, SpellContinuation continuation) {
return new CastResult(this.getContinuation(), vm.getImage(), List.of(), ResolvedPatternType.EVALUATED, HexEvalSounds.HERMES);
}
@Override
public boolean executable() {
return true;
}
public static IotaType<ContinuationIota> TYPE = new IotaType<>() {
@Override
public @NotNull 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;
}
};
}

View file

@ -67,6 +67,13 @@ public abstract class Iota {
);
}
/**
* Returns whether this iota is possible to execute (i.e. whether {@link Iota#execute} has been overridden.
*/
public boolean executable() {
return false;
}
/**
* This method is called to determine whether the iota is above the max serialisation depth/serialisation count
* limits. It should return every "iota" that is a subelement of this iota.

View file

@ -5,7 +5,6 @@ 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.OperationResult;
import at.petrak.hexcasting.api.casting.eval.ResolvedPatternType;
import at.petrak.hexcasting.api.casting.eval.sideeffects.OperatorSideEffect;
import at.petrak.hexcasting.api.casting.eval.vm.CastingVM;
@ -69,42 +68,38 @@ public class PatternIota extends Iota {
public @NotNull CastResult execute(CastingVM vm, ServerLevel world, SpellContinuation continuation) {
@Nullable Component castedName = null;
try {
OperationResult result = null; // HexArithmetics.ENGINE.operate(this.getPattern(), vm.getEnv(), vm.getImage(), continuation);
var lookup = PatternRegistryManifest.matchPattern(this.getPattern(), world, false);
vm.getEnv().precheckAction(lookup);
if (result == null) {
var lookup = PatternRegistryManifest.matchPattern(this.getPattern(), world, false);
vm.getEnv().precheckAction(lookup);
Action action;
if (lookup instanceof PatternShapeMatch.Normal || lookup instanceof PatternShapeMatch.PerWorld) {
ResourceKey<ActionRegistryEntry> key;
if (lookup instanceof PatternShapeMatch.Normal normal) {
key = normal.key;
} else {
PatternShapeMatch.PerWorld perWorld = (PatternShapeMatch.PerWorld) lookup;
key = perWorld.key;
}
Action action;
if (lookup instanceof PatternShapeMatch.Normal || lookup instanceof PatternShapeMatch.PerWorld) {
ResourceKey<ActionRegistryEntry> key;
if (lookup instanceof PatternShapeMatch.Normal normal) {
key = normal.key;
} else {
PatternShapeMatch.PerWorld perWorld = (PatternShapeMatch.PerWorld) lookup;
key = perWorld.key;
}
var reqsEnlightenment = isOfTag(IXplatAbstractions.INSTANCE.getActionRegistry(), key,
HexTags.Actions.REQUIRES_ENLIGHTENMENT);
var reqsEnlightenment = isOfTag(IXplatAbstractions.INSTANCE.getActionRegistry(), key,
HexTags.Actions.REQUIRES_ENLIGHTENMENT);
castedName = HexAPI.instance().getActionI18n(key, reqsEnlightenment);
castedName = HexAPI.instance().getActionI18n(key, reqsEnlightenment);
action = Objects.requireNonNull(IXplatAbstractions.INSTANCE.getActionRegistry().get(key)).action();
} else if (lookup instanceof PatternShapeMatch.Special special) {
castedName = special.handler.getName();
action = special.handler.act();
} else if (lookup instanceof PatternShapeMatch.Nothing) {
throw new MishapInvalidPattern();
} else throw new IllegalStateException();
action = Objects.requireNonNull(IXplatAbstractions.INSTANCE.getActionRegistry().get(key)).action();
} else if (lookup instanceof PatternShapeMatch.Special special) {
castedName = special.handler.getName();
action = special.handler.act();
} else if (lookup instanceof PatternShapeMatch.Nothing) {
throw new MishapInvalidPattern();
} else throw new IllegalStateException();
// do the actual calculation!!
result = action.operate(
vm.getEnv(),
vm.getImage(),
continuation
);
}
// do the actual calculation!!
var result = action.operate(
vm.getEnv(),
vm.getImage(),
continuation
);
if (result.getNewImage().getOpsConsumed() > HexConfig.server().maxOpCount()) {
throw new MishapEvalTooMuch();
@ -133,6 +128,11 @@ public class PatternIota extends Iota {
}
}
@Override
public boolean executable() {
return true;
}
public static IotaType<PatternIota> TYPE = new IotaType<>() {
@Override
public PatternIota deserialize(Tag tag, ServerLevel world) throws IllegalArgumentException {

View file

@ -9,7 +9,7 @@ import at.petrak.hexcasting.api.casting.eval.vm.FrameEvaluate
import at.petrak.hexcasting.api.casting.eval.vm.FrameFinishEval
import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation
import at.petrak.hexcasting.api.casting.evaluatable
import at.petrak.hexcasting.api.casting.iota.PatternIota
import at.petrak.hexcasting.api.casting.iota.Iota
import at.petrak.hexcasting.api.casting.mishaps.MishapNotEnoughArgs
import at.petrak.hexcasting.common.lib.hex.HexEvalSounds
@ -17,22 +17,23 @@ object OpEval : Action {
override fun operate(env: CastingEnvironment, image: CastingImage, continuation: SpellContinuation): OperationResult {
val stack = image.stack.toMutableList()
val iota = stack.removeLastOrNull() ?: throw MishapNotEnoughArgs(1, 0)
// TODO: use the new iota eval stuff
val instrs = evaluatable(iota, 0)
return exec(env, image, continuation, stack, iota)
}
fun exec(env: CastingEnvironment, image: CastingImage, continuation: SpellContinuation, newStack: MutableList<Iota>, iota: Iota): OperationResult {
// also, never make a break boundary when evaluating just one pattern
val instrs = evaluatable(iota, 0)
val newCont =
if (instrs.left().isPresent || (continuation is SpellContinuation.NotDone && continuation.frame is FrameFinishEval)) {
continuation
} else {
continuation.pushFrame(FrameFinishEval) // install a break-boundary after eval
}
if (instrs.left().isPresent || (continuation is SpellContinuation.NotDone && continuation.frame is FrameFinishEval)) {
continuation
} else {
continuation.pushFrame(FrameFinishEval) // 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 = FrameEvaluate(instrsList, true)
val image2 = image.withUsedOp().copy(stack = stack)
val image2 = image.withUsedOp().copy(stack = newStack)
return OperationResult(image2, listOf(), newCont.pushFrame(frame), HexEvalSounds.HERMES)
}
}

View file

@ -0,0 +1,20 @@
package at.petrak.hexcasting.common.casting.actions.eval
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.iota.ContinuationIota
import at.petrak.hexcasting.api.casting.mishaps.MishapNotEnoughArgs
object OpEvalBreakable : Action {
override fun operate(env: CastingEnvironment,
image: CastingImage,
continuation: SpellContinuation): OperationResult {
val stack = image.stack.toMutableList()
val iota = stack.removeLastOrNull() ?: throw MishapNotEnoughArgs(1, 0)
stack.add(ContinuationIota(continuation))
return OpEval.exec(env, image, continuation, stack, iota)
}
}

View file

@ -26,6 +26,7 @@ import at.petrak.hexcasting.common.casting.actions.spells.*;
import at.petrak.hexcasting.common.casting.actions.spells.great.*;
import at.petrak.hexcasting.common.casting.actions.spells.sentinel.*;
import at.petrak.hexcasting.common.casting.actions.stack.*;
import at.petrak.hexcasting.common.casting.actions.eval.OpEvalBreakable;
import at.petrak.hexcasting.common.lib.HexItems;
import at.petrak.hexcasting.interop.pehkui.*;
import at.petrak.hexcasting.xplat.IXplatAbstractions;
@ -367,9 +368,10 @@ public class HexActions {
// eval being a space filling curve feels apt doesn't it
public static final ActionRegistryEntry EVAL = make("eval",
new ActionRegistryEntry(HexPattern.fromAngles("deaqq", HexDir.SOUTH_EAST), OpEval.INSTANCE));
public static final ActionRegistryEntry EVAL$CC = make("eval/cc",
new ActionRegistryEntry(HexPattern.fromAngles("qwaqde", HexDir.NORTH_WEST), OpEvalBreakable.INSTANCE));
public static final ActionRegistryEntry HALT = make("halt",
new ActionRegistryEntry(HexPattern.fromAngles("aqdee", HexDir.SOUTH_WEST), OpHalt.INSTANCE));
// TODO: install Iris' gambit
public static final ActionRegistryEntry READ = make("read",
new ActionRegistryEntry(HexPattern.fromAngles("aqqqqq", HexDir.EAST), OpRead.INSTANCE));

View file

@ -41,6 +41,7 @@ public class HexIotaTypes {
public static final IotaType<PatternIota> PATTERN = type("pattern", PatternIota.TYPE);
public static final IotaType<GarbageIota> GARBAGE = type("garbage", GarbageIota.TYPE);
public static final IotaType<Vec3Iota> VEC3 = type("vec3", Vec3Iota.TYPE);
public static final IotaType<ContinuationIota> CONTINUATION = type("continuation", ContinuationIota.TYPE);
private static <U extends Iota, T extends IotaType<U>> T type(String name, T type) {

View file

@ -300,6 +300,7 @@
"list_contents": "[%s]",
"null_iota": "Null",
"jump_iota": "[Jump]",
"pattern_iota": "HexPattern(%s)"
},
// ^ tooltip
@ -529,8 +530,9 @@
"teleport/great": "Greater Teleport",
"brainsweep": "Flay Mind",
"sentinel/create/great": "Summon Greater Sentinel",
"eval": "Hermes' Gambit",
"eval/cc": "Iris' Gambit",
"for_each": "Thoth's Gambit",
"halt": "Charon's Gambit",
@ -1295,7 +1297,8 @@
"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.",
"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.",
"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.",
"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. ",
"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.",
"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.",
"circle_patterns.circle/impetus_pos": "Returns the position of the $(l:greatwork/impetus)$(item)Impetus/$ of this spell circle.",

View file

@ -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",