From 24367669e5dc7cd42613b4f1d7418a8b60d42c74 Mon Sep 17 00:00:00 2001 From: "petrak@" Date: Sat, 4 Feb 2023 11:35:26 -0600 Subject: [PATCH] aaaaaugh. also document things --- .../main/java/at/petrak/hexcasting/README.md | 2 +- .../java/at/petrak/hexcasting/api/HexAPI.java | 5 + .../hexcasting/api/addldata/ADIotaHolder.java | 4 +- .../circle/BlockEntityAbstractImpetus.java | 14 +- .../api/casting/eval/CastingEnvironment.java | 5 +- .../api/casting/eval/ControllerInfo.kt | 16 - .../api/casting/eval/ExecutionClientView.kt | 15 + .../hexcasting/api/casting/eval/README.md | 74 +++++ .../eval/sideeffects/OperatorSideEffect.kt | 14 +- .../api/casting/eval/vm/CastingImage.kt | 78 +++++ .../{CastingHarness.kt => vm/CastingVM.kt} | 288 +++--------------- .../api/casting/eval/vm/ContinuationFrame.kt | 3 +- .../api/casting/eval/vm/FrameEvaluate.kt | 5 +- .../api/casting/eval/vm/FrameFinishEval.kt | 3 +- .../api/casting/eval/vm/FrameForEach.kt | 3 +- .../hexcasting/api/casting/iota/Iota.java | 2 +- .../hexcasting/api/casting/iota/IotaType.java | 157 ++++++++++ .../hexcasting/api/casting/iota/ListIota.java | 6 +- .../hexcasting/api/item/IotaHolderItem.java | 7 +- .../api/misc/DiscoveryHandlers.java | 11 +- .../petrak/hexcasting/api/utils/HexUtils.kt | 4 +- .../client/RegisterClientStuff.java | 6 +- .../hexcasting/client/gui/GuiSpellcasting.kt | 16 +- .../blocks/akashic/BlockAkashicRecord.java | 4 +- .../akashic/BlockEntityAkashicBookshelf.java | 4 +- .../common/casting/env/StaffCastEnv.java | 8 +- .../hexcasting/common/items/ItemAbacus.java | 8 +- .../hexcasting/common/items/ItemFocus.java | 4 +- .../common/items/ItemSpellbook.java | 10 +- .../common/items/magic/ItemPackagedHex.java | 14 +- .../common/lib/hex/HexIotaTypes.java | 158 +--------- .../common/network/MsgNewSpellPatternAck.java | 6 +- .../common/network/MsgShiftScrollSyn.java | 4 +- .../hexcasting/xplat/IXplatAbstractions.java | 6 +- .../hexcasting/fabric/cc/CCHarness.java | 10 +- .../fabric/cc/adimpl/CCItemIotaHolder.java | 6 +- .../fabric/xplat/FabricXplatImpl.java | 6 +- .../forge/cap/adimpl/CapStaticIotaHolder.java | 4 +- .../forge/xplat/ForgeXplatImpl.java | 8 +- 39 files changed, 483 insertions(+), 515 deletions(-) delete mode 100644 Common/src/main/java/at/petrak/hexcasting/api/casting/eval/ControllerInfo.kt create mode 100644 Common/src/main/java/at/petrak/hexcasting/api/casting/eval/ExecutionClientView.kt create mode 100644 Common/src/main/java/at/petrak/hexcasting/api/casting/eval/README.md create mode 100644 Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingImage.kt rename Common/src/main/java/at/petrak/hexcasting/api/casting/eval/{CastingHarness.kt => vm/CastingVM.kt} (55%) diff --git a/Common/src/main/java/at/petrak/hexcasting/README.md b/Common/src/main/java/at/petrak/hexcasting/README.md index a96585f0..ffbae014 100644 --- a/Common/src/main/java/at/petrak/hexcasting/README.md +++ b/Common/src/main/java/at/petrak/hexcasting/README.md @@ -2,7 +2,7 @@ Hello, intrepid Github reader! The "flavor text" words for things in this mod and the internal names are different. (Sorry.) -- A "Hex" is a `Cast`, cast through a [`CastingHarness`](api/casting/eval/CastingHarness.kt) +- A "Hex" is a `Cast`, cast through a [`CastingHarness`](api/casting/eval/vm/CastingVM.kt) - A "Pattern" is a [`HexPattern`](api/casting/math/HexPattern.kt) - An "Action" is an [`Operator`](api/casting/castables/Action.kt) - An action that pushes a spell is a [`Spell`](api/casting/castables/SpellAction.kt) 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 1436b797..32294f0b 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/HexAPI.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/HexAPI.java @@ -108,6 +108,11 @@ public interface HexAPI { return FrozenColorizer.DEFAULT.get(); } + /** + * Location in the userdata of the ravenmind + */ + ResourceLocation RAVENMIND_USERDATA = modLoc("ravenmind"); + static HexAPI instance() { return INSTANCE.get(); } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/addldata/ADIotaHolder.java b/Common/src/main/java/at/petrak/hexcasting/api/addldata/ADIotaHolder.java index 4fc1e2c5..45e3e404 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/addldata/ADIotaHolder.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/addldata/ADIotaHolder.java @@ -1,7 +1,7 @@ package at.petrak.hexcasting.api.addldata; import at.petrak.hexcasting.api.casting.iota.Iota; -import at.petrak.hexcasting.common.lib.hex.HexIotaTypes; +import at.petrak.hexcasting.api.casting.iota.IotaType; import net.minecraft.nbt.CompoundTag; import net.minecraft.server.level.ServerLevel; import org.jetbrains.annotations.Nullable; @@ -14,7 +14,7 @@ public interface ADIotaHolder { default Iota readIota(ServerLevel world) { var tag = readIotaTag(); if (tag != null) { - return HexIotaTypes.deserialize(tag, world); + return IotaType.deserialize(tag, world); } else { return null; } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/block/circle/BlockEntityAbstractImpetus.java b/Common/src/main/java/at/petrak/hexcasting/api/block/circle/BlockEntityAbstractImpetus.java index 7d931f82..6c0ee74c 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/block/circle/BlockEntityAbstractImpetus.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/block/circle/BlockEntityAbstractImpetus.java @@ -1,14 +1,14 @@ package at.petrak.hexcasting.api.block.circle; import at.petrak.hexcasting.api.block.HexBlockEntity; +import at.petrak.hexcasting.api.casting.ParticleSpray; +import at.petrak.hexcasting.api.casting.eval.CastingEnvironment; +import at.petrak.hexcasting.api.casting.eval.SpellCircleContext; +import at.petrak.hexcasting.api.casting.eval.vm.CastingVM; +import at.petrak.hexcasting.api.casting.iota.PatternIota; import at.petrak.hexcasting.api.misc.FrozenColorizer; import at.petrak.hexcasting.api.misc.MediaConstants; import at.petrak.hexcasting.api.mod.HexConfig; -import at.petrak.hexcasting.api.casting.ParticleSpray; -import at.petrak.hexcasting.api.casting.eval.CastingEnvironment; -import at.petrak.hexcasting.api.casting.eval.CastingHarness; -import at.petrak.hexcasting.api.casting.eval.SpellCircleContext; -import at.petrak.hexcasting.api.casting.iota.PatternIota; import at.petrak.hexcasting.api.utils.MediaHelper; import at.petrak.hexcasting.common.items.magic.ItemCreativeUnlocker; import at.petrak.hexcasting.common.lib.HexItems; @@ -284,7 +284,7 @@ public abstract class BlockEntityAbstractImpetus extends HexBlockEntity implemen var ctx = new CastingEnvironment(splayer, InteractionHand.MAIN_HAND, new SpellCircleContext(this.getBlockPos(), bounds, this.activatorAlwaysInRange())); - var harness = new CastingHarness(ctx); + var harness = new CastingVM(ctx); BlockPos erroredPos = null; for (var tracked : this.trackedBlocks) { @@ -292,7 +292,7 @@ public abstract class BlockEntityAbstractImpetus extends HexBlockEntity implemen if (bs.getBlock() instanceof BlockCircleComponent cc) { var newPattern = cc.getPattern(tracked, bs, this.level); if (newPattern != null) { - var info = harness.executeIota(new PatternIota(newPattern), splayer.getLevel()); + var info = harness.queueAndExecuteIota(new PatternIota(newPattern), splayer.getLevel()); if (!info.getResolutionType().getSuccess()) { erroredPos = tracked; break; diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/CastingEnvironment.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/CastingEnvironment.java index e8787fd9..6ab913c4 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/CastingEnvironment.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/CastingEnvironment.java @@ -1,7 +1,7 @@ package at.petrak.hexcasting.api.casting.eval; -import at.petrak.hexcasting.api.casting.ActionRegistryEntry; import at.petrak.hexcasting.api.casting.ParticleSpray; +import at.petrak.hexcasting.api.casting.PatternShapeMatch; import at.petrak.hexcasting.api.casting.mishaps.Mishap; import at.petrak.hexcasting.api.casting.mishaps.MishapDisallowedSpell; import at.petrak.hexcasting.api.casting.mishaps.MishapEntityTooFarAway; @@ -9,7 +9,6 @@ import at.petrak.hexcasting.api.casting.mishaps.MishapLocationTooFarAway; import at.petrak.hexcasting.api.misc.FrozenColorizer; import at.petrak.hexcasting.api.mod.HexConfig; import net.minecraft.core.BlockPos; -import net.minecraft.resources.ResourceKey; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.Entity; @@ -57,7 +56,7 @@ public abstract class CastingEnvironment { *

* This is used for stuff like requiring enlightenment and pattern denylists */ - public void precheckAction(ResourceKey key) throws Mishap { + public void precheckAction(PatternShapeMatch match) throws Mishap { if (!HexConfig.server().isActionAllowed(key.location())) { throw new MishapDisallowedSpell(); } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/ControllerInfo.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/ControllerInfo.kt deleted file mode 100644 index c17e096c..00000000 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/ControllerInfo.kt +++ /dev/null @@ -1,16 +0,0 @@ -package at.petrak.hexcasting.api.casting.eval - -import net.minecraft.nbt.CompoundTag - -/** - * Information for the sake of the GUI. - */ -data class ControllerInfo( - val isStackClear: Boolean, - val resolutionType: ResolvedPatternType, - val stack: List, - val parenthesized: List, - val ravenmind: CompoundTag?, - val parenCount: Int, -) - diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/ExecutionClientView.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/ExecutionClientView.kt new file mode 100644 index 00000000..5534653a --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/ExecutionClientView.kt @@ -0,0 +1,15 @@ +package at.petrak.hexcasting.api.casting.eval + +import net.minecraft.network.chat.Component + +/** + * Information sent back to the client + */ +data class ExecutionClientView( + val isStackClear: Boolean, + val resolutionType: ResolvedPatternType, + + val stackDescs: List, + val ravenmind: Component?, +) + diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/README.md b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/README.md new file mode 100644 index 00000000..039c0738 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/README.md @@ -0,0 +1,74 @@ +# How Casting Works + +because I keep forgetting. + +- [`CastingVM`][] is the casting virtual machine. It is what figures out what to do with an incoming iota to be + executed, + producing a functional state update. This is transient and is reconstructed every time the whole state needs to be + saved and loaded. +- [`CastingImage`][] is the state of the cast itself. If [`CastingVM`][] is an emulator, [`CastingImage`][] is a + snapshot of the + emulator's memory. This is the only thing serialized to NBT. +- [`CastingEnvironment`][] is what is doing the casting, abstractly. Stuff like "the player with a staff," "the player + with a trinket," "a spell circle." This is an abstract class that Hexcasting (and addons!) make subclasses of. + +## Beware The Pipeline + +1. An iota or list of iotas come to be executed. This is the entrypoint to the VM, + `CastingVM#queueAndExecuteIotas`, and returns an [`ExecutionClientView`][] (might change later). +2. Those iotas are put into a [`ContinuationFrame`][] (specifically, [`FrameEvaluate`][]). +3. While there are still frames, control flows to the top one. +4. The frame (usually) returns control flow back to the VM by calling `CastingVM#executeInner` with an iota. +5. `executeInner` first does some pre-checks with intro/retro/consideration (like escaping embedded iotas), but usually + makes sure that the passed iota is a pattern and passes *that* on to `executePattern`. +6. `executePattern` is where the execution actually happens. The pattern is matched to an action or special handler. + and executed. +7. That execution doesn't mutate anything, but returns a [`CastResult`]. It contains: + - the rest of the current continuation (read as: the patterns to execute next); + - the updated state of the [`CastingImage`]; + - a list of `OperatorSideEffects`, like withdrawing media, casting spells or mishaping; + - misc display info like the color the pattern should be when drawn to the staff GUI and the sound. +8. Each of the side effects is applied to the world in turn. If any of them make the casting stop (like mishaping), + then it does! +9. If there's still iotas left in the current continuation, then control goes back to step 5, called on the next iota in + continuation. +10. Otherwise, control goes to the top frame of the stack (step 3). And if there are no stack frames, execution is + finished! What a ride. + +## The Hell's A Continuation And A Continuation Frame + +IDFK ask Alwinfy. + +But as I understand it, a [continuation frame][ContinuationFrame] tells the VM what to do *next*. + +While there are frames left on the stack (not the normal stack, a special execution stack), it is popped and +queried. It will then operate the VM into executing a [continuation][Continuation] in a certain way. + +Continuations are just a linked list of iotas to execute. The VM goes through each one and executes them. + +- For staffcasting, each pattern drawn spins up the VM with a `FrameEvaluate`, which will provide exactly one + continuation containing the pattern. +- For trinket casting, the VM gets a `FrameEvaluate` again, but the continuation list it provides has + all the patterns in the trinket. +- Hermes' Gambit pushes a new `FrameEvaluate` to the stack with the pattern list argument inside it. This doesn't clear + the continuation underneath, so once those patterns are through execution control goes right back to where it was + interrupted. +- Thoth's and Charon's do something with different kinds of stack frames, I don't really understand it. +- Iris' gambit is waaaay outside of my pay grade + +[CastingVM]: vm/CastingVM.kt + +[CastingImage]: vm/CastingImage.kt + +[CastingEnvironment]: CastingEnvironment.java + +[ExecutionClientView]: ExecutionClientView.kt + +[ContinuationFrame]: vm/ContinuationFrame.kt + +[Continuation]: vm/SpellContinuation.kt + +[FrameEvaluate]: vm/FrameEvaluate.kt + +[CastResult]: CastResult.kt + diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/sideeffects/OperatorSideEffect.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/sideeffects/OperatorSideEffect.kt index ab1e091d..a2cb880b 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/sideeffects/OperatorSideEffect.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/sideeffects/OperatorSideEffect.kt @@ -3,7 +3,7 @@ package at.petrak.hexcasting.api.casting.eval.sideeffects import at.petrak.hexcasting.api.advancements.HexAdvancementTriggers import at.petrak.hexcasting.api.casting.ParticleSpray import at.petrak.hexcasting.api.casting.RenderedSpell -import at.petrak.hexcasting.api.casting.eval.CastingHarness +import at.petrak.hexcasting.api.casting.eval.vm.CastingVM import at.petrak.hexcasting.api.casting.mishaps.Mishap import at.petrak.hexcasting.api.misc.FrozenColorizer import at.petrak.hexcasting.api.mod.HexStatistics @@ -18,10 +18,10 @@ import net.minecraft.world.item.ItemStack */ sealed class OperatorSideEffect { /** Return whether to cancel all further [OperatorSideEffect] */ - abstract fun performEffect(harness: CastingHarness): Boolean + abstract fun performEffect(harness: CastingVM): Boolean data class RequiredEnlightenment(val awardStat: Boolean) : OperatorSideEffect() { - override fun performEffect(harness: CastingHarness): Boolean { + override fun performEffect(harness: CastingVM): Boolean { harness.ctx.caster?.sendSystemMessage("hexcasting.message.cant_great_spell".asTranslatedComponent) if (awardStat) @@ -38,7 +38,7 @@ sealed class OperatorSideEffect { val awardStat: Boolean = true ) : OperatorSideEffect() { - override fun performEffect(harness: CastingHarness): Boolean { + override fun performEffect(harness: CastingVM): Boolean { this.spell.cast(harness.ctx) if (awardStat) harness.ctx.caster?.awardStat(HexStatistics.SPELLS_CAST) @@ -48,14 +48,14 @@ sealed class OperatorSideEffect { } data class ConsumeMedia(val amount: Int) : OperatorSideEffect() { - override fun performEffect(harness: CastingHarness): Boolean { + override fun performEffect(harness: CastingVM): Boolean { val leftoverMedia = harness.ctx.extractMedia(this.amount.toLong()) return leftoverMedia > 0 } } data class Particles(val spray: ParticleSpray) : OperatorSideEffect() { - override fun performEffect(harness: CastingHarness): Boolean { + override fun performEffect(harness: CastingVM): Boolean { harness.ctx.produceParticles(this.spray, harness.ctx.colorizer) this.spray.sprayParticles(harness.ctx.world, harness.getColorizer()) @@ -64,7 +64,7 @@ sealed class OperatorSideEffect { } data class DoMishap(val mishap: Mishap, val errorCtx: Mishap.Context) : OperatorSideEffect() { - override fun performEffect(harness: CastingHarness): Boolean { + override fun performEffect(harness: CastingVM): Boolean { val spray = mishap.particleSpray(harness.ctx) val color = mishap.accentColor(harness.ctx, errorCtx) spray.sprayParticles(harness.ctx.world, color) 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 new file mode 100644 index 00000000..46bc57a2 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingImage.kt @@ -0,0 +1,78 @@ +package at.petrak.hexcasting.api.casting.eval.vm + +import at.petrak.hexcasting.api.HexAPI +import at.petrak.hexcasting.api.casting.iota.Iota +import at.petrak.hexcasting.api.casting.iota.IotaType +import at.petrak.hexcasting.api.utils.* +import net.minecraft.nbt.CompoundTag +import net.minecraft.nbt.Tag +import net.minecraft.server.level.ServerLevel + +/** + * The state of a casting VM, containing the stack and all + */ +data class CastingImage private constructor( + val stack: List, + + val parenCount: Int, + val parenthesized: List, + val escapeNext: Boolean, + + val userData: CompoundTag +) { + public constructor() : this(listOf(), 0, listOf(), false, CompoundTag()) + + fun serializeToNbt() = NBTBuilder { + TAG_STACK %= stack.serializeToNBT() + + TAG_PAREN_COUNT %= parenCount + TAG_ESCAPE_NEXT %= escapeNext + TAG_PARENTHESIZED %= parenthesized.serializeToNBT() + + TAG_USERDATA %= userData + } + + companion object { + const val TAG_STACK = "stack" + const val TAG_PAREN_COUNT = "open_parens" + const val TAG_PARENTHESIZED = "parenthesized" + const val TAG_ESCAPE_NEXT = "escape_next" + const val TAG_USERDATA = "userdata" + + @JvmStatic + public fun loadFromNbt(tag: CompoundTag, world: ServerLevel): CastingImage { + return try { + val stack = mutableListOf() + val stackTag = tag.getList(TAG_STACK, Tag.TAG_COMPOUND) + for (subtag in stackTag) { + val datum = IotaType.deserialize(subtag.asCompound, world) + stack.add(datum) + } + + val userData = if (tag.contains(TAG_USERDATA)) { + tag.getCompound(TAG_USERDATA) + } else if (tag.contains("local")) { + NBTBuilder { + TAG_USERDATA %= tag.getCompound("local") + } + } else { + CompoundTag() + } + + val parenthesized = mutableListOf() + val parenTag = tag.getList(TAG_PARENTHESIZED, Tag.TAG_COMPOUND) + for (subtag in parenTag) { + parenthesized.add(IotaType.deserialize(subtag.downcast(CompoundTag.TYPE), world)) + } + + val parenCount = tag.getInt(CastingVM.TAG_PAREN_COUNT) + val escapeNext = tag.getBoolean(CastingVM.TAG_ESCAPE_NEXT) + + CastingImage(stack, parenCount, parenthesized, escapeNext, userData) + } catch (exn: Exception) { + HexAPI.LOGGER.warn("error while loading a CastingImage", exn) + CastingImage() + } + } + } +} \ No newline at end of file diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/CastingHarness.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingVM.kt similarity index 55% rename from Common/src/main/java/at/petrak/hexcasting/api/casting/eval/CastingHarness.kt rename to Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingVM.kt index 5515e575..9c48a034 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/CastingHarness.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingVM.kt @@ -1,116 +1,64 @@ -package at.petrak.hexcasting.api.casting.eval +package at.petrak.hexcasting.api.casting.eval.vm import at.petrak.hexcasting.api.HexAPI -import at.petrak.hexcasting.api.casting.ParticleSpray import at.petrak.hexcasting.api.casting.PatternShapeMatch import at.petrak.hexcasting.api.casting.PatternShapeMatch.* import at.petrak.hexcasting.api.casting.SpellList -import at.petrak.hexcasting.api.casting.castables.Action +import at.petrak.hexcasting.api.casting.eval.* import at.petrak.hexcasting.api.casting.eval.sideeffects.OperatorSideEffect -import at.petrak.hexcasting.api.casting.eval.vm.FrameEvaluate -import at.petrak.hexcasting.api.casting.eval.vm.FunctionalData -import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation import at.petrak.hexcasting.api.casting.iota.Iota +import at.petrak.hexcasting.api.casting.iota.IotaType import at.petrak.hexcasting.api.casting.iota.ListIota import at.petrak.hexcasting.api.casting.iota.PatternIota import at.petrak.hexcasting.api.casting.math.HexDir import at.petrak.hexcasting.api.casting.math.HexPattern import at.petrak.hexcasting.api.casting.mishaps.* -import at.petrak.hexcasting.api.misc.DiscoveryHandlers -import at.petrak.hexcasting.api.misc.FrozenColorizer import at.petrak.hexcasting.api.mod.HexTags import at.petrak.hexcasting.api.utils.* import at.petrak.hexcasting.common.casting.PatternRegistryManifest import at.petrak.hexcasting.common.lib.hex.HexEvalSounds -import at.petrak.hexcasting.common.lib.hex.HexIotaTypes import at.petrak.hexcasting.xplat.IXplatAbstractions -import com.mojang.datafixers.util.Either -import net.minecraft.nbt.CompoundTag -import net.minecraft.nbt.Tag import net.minecraft.network.chat.Component import net.minecraft.server.level.ServerLevel -import net.minecraft.sounds.SoundSource -import net.minecraft.world.level.gameevent.GameEvent -import net.minecraft.world.phys.Vec3 /** - * Keeps track of a player casting a spell on the server. - * It's stored as NBT on the player. - * - * TODO oh god this entire class needs a gigantic refactor. why are there like 6 different entrypoints for casting - * a pattern. oh god. + * The virtual machine! This is the glue that determines the next iteration of a [CastingImage], using a + * [CastingEnvironment] to affect the world. */ -class CastingHarness private constructor( - var stack: MutableList, - var ravenmind: Iota?, - var parenCount: Int, - var parenthesized: List, - var escapeNext: Boolean, - val ctx: CastingEnvironment, - val prepackagedColorizer: FrozenColorizer? // for trinkets with colorizers -) { - - @JvmOverloads - constructor( - ctx: CastingEnvironment, - prepackagedColorizer: FrozenColorizer? = null - ) : this(mutableListOf(), null, 0, mutableListOf(), false, ctx, prepackagedColorizer) +class CastingVM(val image: CastingImage, val env: CastingEnvironment) { /** * Execute a single iota. */ - fun executeIota(iota: Iota, world: ServerLevel): ControllerInfo = executeIotas(listOf(iota), world) - - private fun displayPatternDebug(escapeNext: Boolean, parenCount: Int, iotaRepresentation: Component) { - if (this.ctx.debugPatterns) { - val display = " ".repeat(parenCount).asTextComponent - if (escapeNext) - display.append("\\ ".asTextComponent.gold) - display.append(iotaRepresentation) - - this.ctx.caster.sendSystemMessage(display) - } - } + fun queueAndExecuteIota(iota: Iota, world: ServerLevel): ExecutionClientView = queueAndExecuteIotas(listOf(iota), world) /** - * Given a list of iotas, execute them in sequence. + * The main entrypoint to the VM. Given a list of iotas, execute them in sequence, and return whatever the client + * needs to see. */ - fun executeIotas(iotas: List, world: ServerLevel): ControllerInfo { + fun queueAndExecuteIotas(iotas: List, world: ServerLevel): ExecutionClientView { // Initialize the continuation stack to a single top-level eval for all iotas. var continuation = SpellContinuation.Done.pushFrame(FrameEvaluate(SpellList.LList(0, iotas), false)) // Begin aggregating info val info = TempControllerInfo(earlyExit = false) var lastResolutionType = ResolvedPatternType.UNRESOLVED - var sound = HexEvalSounds.NOTHING while (continuation is SpellContinuation.NotDone && !info.earlyExit) { // Take the top of the continuation stack... val next = continuation.frame // ...and execute it. // TODO there used to be error checking code here; I'm pretty sure any and all mishaps should already // get caught and folded into CastResult by evaluate. - val result = next.evaluate(continuation.next, world, this) + val image2 = next.evaluate(continuation.next, world, this) // Then write all pertinent data back to the harness for the next iteration. - if (result.newData != null) { - this.applyFunctionalData(result.newData) + if (image2.newData != null) { + this.applyFunctionalData(image2.newData) } - continuation = result.continuation - lastResolutionType = result.resolutionType - performSideEffects(info, result.sideEffects) - info.earlyExit = info.earlyExit || !lastResolutionType.success - sound = if (result.sound == HexEvalSounds.MISHAP) { - HexEvalSounds.MISHAP - } else { - sound.greaterOf(result.sound) - } - } + this.env.postExecution(image2) - sound.sound?.let { - this.ctx.world.playSound( - null, this.ctx.position.x, this.ctx.position.y, this.ctx.position.z, it, - SoundSource.PLAYERS, 1f, 1f - ) - // TODO: is it worth mixing in to the immut map and making our own game event with blackjack and hookers - this.ctx.world.gameEvent(this.ctx.caster, GameEvent.ITEM_INTERACT_FINISH, this.ctx.position) + continuation = image2.continuation + lastResolutionType = image2.resolutionType + performSideEffects(info, image2.sideEffects) + info.earlyExit = info.earlyExit || !lastResolutionType.success } if (continuation is SpellContinuation.NotDone) { @@ -118,29 +66,23 @@ class CastingHarness private constructor( if (lastResolutionType.success) ResolvedPatternType.EVALUATED else ResolvedPatternType.ERRORED } - val (stackDescs, parenDescs, ravenmind) = generateDescs() + val (stackDescs, ravenmind) = generateDescs() - return ControllerInfo( - this.stack.isEmpty() && this.parenCount == 0 && !this.escapeNext, - lastResolutionType, - stackDescs, - parenDescs, - ravenmind, - this.parenCount - ) + val isStackClear = image.stack.isEmpty() && image.parenCount == 0 && !image.escapeNext + return ExecutionClientView(isStackClear, lastResolutionType, stackDescs, ravenmind) } /** * this DOES NOT THROW THINGS */ @Throws() - fun getUpdate(iota: Iota, world: ServerLevel, continuation: SpellContinuation): CastResult { + fun executeInner(iota: Iota, world: ServerLevel, continuation: SpellContinuation): CastResult { 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@getUpdate CastResult(continuation, data, listOf(), resolutionType, HexEvalSounds.OPERATOR) + return@executeInner CastResult(continuation, data, listOf(), resolutionType, HexEvalSounds.OPERATOR) } } catch (e: MishapTooManyCloseParens) { // This is ridiculous and needs to be fixed @@ -162,7 +104,7 @@ class CastingHarness private constructor( } if (iota is PatternIota) { - return updateWithPattern(iota.pattern, world, continuation) + return executePattern(iota.pattern, world, continuation) } else { return CastResult( continuation, @@ -202,11 +144,13 @@ class CastingHarness private constructor( * When the server gets a packet from the client with a new pattern, * handle it functionally. */ - private fun updateWithPattern(newPat: HexPattern, world: ServerLevel, continuation: SpellContinuation): CastResult { + private fun executePattern(newPat: HexPattern, world: ServerLevel, continuation: SpellContinuation): CastResult { var castedName: Component? = null try { val lookup = PatternRegistryManifest.matchPattern(newPat, world, false) - val lookupResult: Either> = if (lookup is Normal || lookup is PerWorld) { + this.env.precheckAction(lookup) + + val action = if (lookup is Normal || lookup is PerWorld) { val key = when (lookup) { is Normal -> lookup.key is PerWorld -> lookup.key @@ -214,63 +158,33 @@ class CastingHarness private constructor( } val reqsEnlightenment = isOfTag(IXplatAbstractions.INSTANCE.actionRegistry, key, HexTags.Actions.REQUIRES_ENLIGHTENMENT) - val canEnlighten = isOfTag(IXplatAbstractions.INSTANCE.actionRegistry, key, HexTags.Actions.CAN_START_ENLIGHTEN) castedName = HexAPI.instance().getActionI18n(key, reqsEnlightenment) - if (!ctx.isCasterEnlightened && reqsEnlightenment) { - Either.right(listOf(OperatorSideEffect.RequiredEnlightenment(canEnlighten))) - } else { - val regiEntry = IXplatAbstractions.INSTANCE.actionRegistry.get(key)!! - Either.left(regiEntry.action) - } + IXplatAbstractions.INSTANCE.actionRegistry.get(key)!!.action } else if (lookup is Special) { castedName = lookup.handler.name - Either.left(lookup.handler.act()) + lookup.handler.act() } else if (lookup is PatternShapeMatch.Nothing) { throw MishapInvalidPattern() - } else { - throw IllegalStateException() - } - // TODO: the config denylist should be handled per VM type. - // I just removed it for now, should re-add it... + } else throw IllegalStateException() val sideEffects = mutableListOf() var stack2: List? = null var cont2 = continuation var ravenmind2: Iota? = null - if (lookupResult.left().isPresent) { - val action = lookupResult.left().get() - displayPatternDebug(false, 0, castedName) - val result = action.operate( - continuation, - this.stack.toMutableList(), - this.ravenmind, - this.ctx - ) - cont2 = result.newContinuation - stack2 = result.newStack - ravenmind2 = result.newRavenmind - // TODO parens also break prescience - sideEffects.addAll(result.sideEffects) - } else { - val problems = lookupResult.right().get() - sideEffects.addAll(problems) - } - - // Stick a poofy particle effect at the caster position - // TODO again this should be on the VM lalala - if (this.ctx.spellCircle == null) - sideEffects.add( - OperatorSideEffect.Particles( - ParticleSpray( - this.ctx.position, - Vec3(0.0, 1.0, 0.0), - 0.5, 1.0 - ) - ) - ) + val result = action.operate( + continuation, + this.stack.toMutableList(), + this.ravenmind, + this.ctx + ) + cont2 = result.newContinuation + stack2 = result.newStack + ravenmind2 = result.newRavenmind + // TODO parens also break prescience + sideEffects.addAll(result.sideEffects) val hereFd = this.getFunctionalData() val fd = if (stack2 != null) { @@ -282,25 +196,6 @@ class CastingHarness private constructor( hereFd } - // TODO again this should be per VM - var soundType = if (this.ctx.source == CastingEnvironment.CastSource.STAFF) { - HexEvalSounds.OPERATOR - } else { - HexEvalSounds.NOTHING - } - for (se in sideEffects) { - if (se is OperatorSideEffect.AttemptSpell) { - soundType = if (se.hasCastingSound) { - soundType.greaterOf(HexEvalSounds.SPELL) - } else { - // WITH CATLIKE TREAD - // UPON OUR PREY WE STEAL - HexEvalSounds.NOTHING - } - } else if (se is OperatorSideEffect.DoMishap) { - soundType = HexEvalSounds.MISHAP - } - } return CastResult( cont2, fd, @@ -333,11 +228,14 @@ class CastingHarness private constructor( } } - fun generateDescs() = Triple( - stack.map(HexIotaTypes::serialize), - parenthesized.map(HexIotaTypes::serialize), - ravenmind?.let(HexIotaTypes::serialize) - ) + fun generateDescs(): Pair, Component?> { + val stackDescs = this.image.stack.map { it.display() } + val ravenmind = if (this.image.userData.contains(HexAPI.RAVENMIND_USERDATA.toString())) { + val tag = this.image.userData.getCompound(HexAPI.RAVENMIND_USERDATA.toString()) + IotaType.getDisplay(tag) + } else null + return Pair(stackDescs, ravenmind) + } /** * Return the functional update represented by the current state (for use with `copy`) @@ -478,92 +376,6 @@ class CastingHarness private constructor( return out } - fun getColorizer(): FrozenColorizer { - if (this.prepackagedColorizer != null) - return this.prepackagedColorizer - - return IXplatAbstractions.INSTANCE.getColorizer(this.ctx.caster) - } - - - fun serializeToNBT() = NBTBuilder { - TAG_STACK %= stack.serializeToNBT() - - if (ravenmind != null) - TAG_LOCAL %= HexIotaTypes.serialize(ravenmind!!) - TAG_PAREN_COUNT %= parenCount - TAG_ESCAPE_NEXT %= escapeNext - - TAG_PARENTHESIZED %= parenthesized.serializeToNBT() - - if (prepackagedColorizer != null) - TAG_PREPACKAGED_COLORIZER %= prepackagedColorizer.serializeToNBT() - } - - - companion object { - const val TAG_STACK = "stack" - const val TAG_LOCAL = "local" - const val TAG_PAREN_COUNT = "open_parens" - const val TAG_PARENTHESIZED = "parenthesized" - const val TAG_ESCAPE_NEXT = "escape_next" - const val TAG_PREPACKAGED_COLORIZER = "prepackaged_colorizer" - - init { - DiscoveryHandlers.addMediaHolderDiscoverer { - it.ctx.caster.inventory.items - .filter(::isMediaItem) - .mapNotNull(IXplatAbstractions.INSTANCE::findMediaHolder) - } - DiscoveryHandlers.addMediaHolderDiscoverer { - it.ctx.caster.inventory.armor - .filter(::isMediaItem) - .mapNotNull(IXplatAbstractions.INSTANCE::findMediaHolder) - } - DiscoveryHandlers.addMediaHolderDiscoverer { - it.ctx.caster.inventory.offhand - .filter(::isMediaItem) - .mapNotNull(IXplatAbstractions.INSTANCE::findMediaHolder) - } - } - - @JvmStatic - fun fromNBT(nbt: CompoundTag, ctx: CastingEnvironment): CastingHarness { - return try { - val stack = mutableListOf() - val stackTag = nbt.getList(TAG_STACK, Tag.TAG_COMPOUND) - for (subtag in stackTag) { - val datum = HexIotaTypes.deserialize(subtag.asCompound, ctx.world) - stack.add(datum) - } - - val ravenmind = if (nbt.contains(TAG_LOCAL)) - HexIotaTypes.deserialize(nbt.getCompound(TAG_LOCAL), ctx.world) - else - null - - val parenthesized = mutableListOf() - val parenTag = nbt.getList(TAG_PARENTHESIZED, Tag.TAG_COMPOUND) - for (subtag in parenTag) { - parenthesized.add(HexIotaTypes.deserialize(subtag.downcast(CompoundTag.TYPE), ctx.world)) - } - - val parenCount = nbt.getInt(TAG_PAREN_COUNT) - val escapeNext = nbt.getBoolean(TAG_ESCAPE_NEXT) - - val colorizer = if (nbt.contains(TAG_PREPACKAGED_COLORIZER)) { - FrozenColorizer.fromNBT(nbt.getCompound(TAG_PREPACKAGED_COLORIZER)) - } else { - null - } - - CastingHarness(stack, ravenmind, parenCount, parenthesized, escapeNext, ctx, colorizer) - } catch (exn: Exception) { - CastingHarness(ctx) - } - } - } - data class TempControllerInfo( var earlyExit: Boolean, ) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/ContinuationFrame.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/ContinuationFrame.kt index 24235613..04460430 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/ContinuationFrame.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/ContinuationFrame.kt @@ -1,7 +1,6 @@ package at.petrak.hexcasting.api.casting.eval.vm import at.petrak.hexcasting.api.casting.SpellList -import at.petrak.hexcasting.api.casting.eval.CastingHarness import at.petrak.hexcasting.api.casting.eval.CastResult import at.petrak.hexcasting.api.casting.iota.Iota import at.petrak.hexcasting.api.utils.getList @@ -31,7 +30,7 @@ sealed interface ContinuationFrame { * For Evaluate, this consumes one pattern; for ForEach this queues the next iteration of the outer loop. * @return the result of this pattern step */ - fun evaluate(continuation: SpellContinuation, level: ServerLevel, harness: CastingHarness): CastResult + fun evaluate(continuation: SpellContinuation, level: ServerLevel, harness: CastingVM): CastResult /** * The OpHalt instruction wants us to "jump to" the END of the nearest meta-eval. 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 bec172a1..082b2c68 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 @@ -2,7 +2,6 @@ package at.petrak.hexcasting.api.casting.eval.vm import at.petrak.hexcasting.api.casting.SpellList import at.petrak.hexcasting.api.casting.eval.CastResult -import at.petrak.hexcasting.api.casting.eval.CastingHarness import at.petrak.hexcasting.api.casting.eval.ResolvedPatternType import at.petrak.hexcasting.api.casting.iota.Iota import at.petrak.hexcasting.api.utils.NBTBuilder @@ -23,7 +22,7 @@ data class FrameEvaluate(val list: SpellList, val isMetacasting: Boolean) : Cont override fun evaluate( continuation: SpellContinuation, level: ServerLevel, - harness: CastingHarness + harness: CastingVM ): CastResult { // If there are patterns left... return if (list.nonEmpty) { @@ -32,7 +31,7 @@ data class FrameEvaluate(val list: SpellList, val isMetacasting: Boolean) : Cont continuation.pushFrame(FrameEvaluate(list.cdr, this.isMetacasting)) } else continuation // ...before evaluating the first one in the list. - val update = harness.getUpdate(list.car, level, newCont) + val update = harness.executeInner(list.car, level, newCont) if (this.isMetacasting && update.sound != HexEvalSounds.MISHAP) { update.copy(sound = HexEvalSounds.HERMES) } else { diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameFinishEval.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameFinishEval.kt index 75c42654..cf20275e 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameFinishEval.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameFinishEval.kt @@ -1,7 +1,6 @@ package at.petrak.hexcasting.api.casting.eval.vm import at.petrak.hexcasting.api.casting.eval.CastResult -import at.petrak.hexcasting.api.casting.eval.CastingHarness import at.petrak.hexcasting.api.casting.eval.ResolvedPatternType import at.petrak.hexcasting.api.casting.iota.Iota import at.petrak.hexcasting.api.utils.NBTBuilder @@ -20,7 +19,7 @@ object FrameFinishEval : ContinuationFrame { override fun evaluate( continuation: SpellContinuation, level: ServerLevel, - harness: CastingHarness + harness: CastingVM ): CastResult { return CastResult( continuation, diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameForEach.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameForEach.kt index 25dc4bb0..6b629ba7 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameForEach.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameForEach.kt @@ -2,7 +2,6 @@ package at.petrak.hexcasting.api.casting.eval.vm import at.petrak.hexcasting.api.casting.SpellList import at.petrak.hexcasting.api.casting.eval.CastResult -import at.petrak.hexcasting.api.casting.eval.CastingHarness import at.petrak.hexcasting.api.casting.eval.ResolvedPatternType import at.petrak.hexcasting.api.casting.iota.Iota import at.petrak.hexcasting.api.casting.iota.ListIota @@ -39,7 +38,7 @@ data class FrameForEach( override fun evaluate( continuation: SpellContinuation, level: ServerLevel, - harness: CastingHarness + harness: CastingVM ): CastResult { // If this isn't the very first Thoth step (i.e. no Thoth computations run yet)... val stack = if (baseStack == null) { 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 85491970..5d60e74f 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 @@ -30,7 +30,7 @@ public abstract class Iota { /** * Serialize this under the {@code data} tag. *

- * You probably don't want to call this directly; use {@link HexIotaTypes#serialize}. + * You probably don't want to call this directly; use {@link IotaType#serialize}. */ abstract public @NotNull Tag serialize(); diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/IotaType.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/IotaType.java index 97cfc9c0..de76dc3e 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/IotaType.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/IotaType.java @@ -1,15 +1,27 @@ package at.petrak.hexcasting.api.casting.iota; +import at.petrak.hexcasting.api.HexAPI; +import at.petrak.hexcasting.api.utils.HexUtils; import at.petrak.hexcasting.common.lib.hex.HexIotaTypes; +import com.mojang.datafixers.util.Pair; +import net.minecraft.ChatFormatting; +import net.minecraft.client.gui.Font; +import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.Tag; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.TextColor; +import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.FormattedCharSequence; import javax.annotation.Nullable; +import java.util.ArrayDeque; +import java.util.Collections; +import java.util.Objects; // Take notes from ForgeRegistryEntry public abstract class IotaType { + /** * Spell datums are stored as such: {@code { "type": "modid:type", "datum": a_tag }}. *

@@ -41,4 +53,149 @@ public abstract class IotaType { return Component.translatable("hexcasting.iota." + key) .withStyle(style -> style.withColor(TextColor.fromRgb(color()))); } + + public static CompoundTag serialize(Iota iota) { + var type = iota.getType(); + var typeId = HexIotaTypes.REGISTRY.getKey(type); + if (typeId == null) { + throw new IllegalStateException( + "Tried to serialize an unregistered iota type. Iota: " + iota + + " ; Type" + type.getClass().getTypeName()); + } + + // We check if it's too big on serialization; if it is we just return a garbage. + if (iota instanceof ListIota listIota && isTooLargeToSerialize(listIota.getList())) { + // Garbage will never be too large so we just recurse + return serialize(new GarbageIota()); + } + var dataTag = iota.serialize(); + var out = new CompoundTag(); + out.putString(HexIotaTypes.KEY_TYPE, typeId.toString()); + out.put(HexIotaTypes.KEY_DATA, dataTag); + return out; + } + + public static boolean isTooLargeToSerialize(Iterable examinee) { + // We don't recurse here, just a work queue (or work stack, if we liked.) + // Each element is a found sub-iota, and how deep it is. + // + // TODO: is it worth trying to cache the depth and size statically on a SpellList. + var listsToExamine = new ArrayDeque<>(Collections.singleton(new Pair<>(examinee, 0))); + int totalEltsFound = 1; // count the first list + while (!listsToExamine.isEmpty()) { + var iotaPair = listsToExamine.removeFirst(); + var sublist = iotaPair.getFirst(); + int depth = iotaPair.getSecond(); + for (var iota : sublist) { + totalEltsFound++; + if (totalEltsFound >= HexIotaTypes.MAX_SERIALIZATION_TOTAL) { + return true; // too bad + } + if (iota instanceof ListIota subsublist) { + if (depth + 1 >= HexIotaTypes.MAX_SERIALIZATION_DEPTH) { + return true; + } + listsToExamine.addLast(new Pair<>(subsublist.getList(), depth + 1)); + } + } + } + // we made it! + return false; + } + + /** + * This method attempts to find the type from the {@code type} key. + * See {@link HexIotaTypes#serialize(Iota)} for the storage format. + * + * @return {@code null} if it cannot get the type. + */ + @org.jetbrains.annotations.Nullable + public static IotaType getTypeFromTag(CompoundTag tag) { + if (!tag.contains(HexIotaTypes.KEY_TYPE, Tag.TAG_STRING)) { + return null; + } + var typeKey = tag.getString(HexIotaTypes.KEY_TYPE); + if (!ResourceLocation.isValidResourceLocation(typeKey)) { + return null; + } + var typeLoc = new ResourceLocation(typeKey); + return HexIotaTypes.REGISTRY.get(typeLoc); + } + + /** + * Attempt to deserialize an iota from a tag. + *
+ * Iotas are saved as such: + * + * { + * "type": "hexcasting:atype", + * "data": {...} + * } + * + */ + public static Iota deserialize(CompoundTag tag, ServerLevel world) { + var type = getTypeFromTag(tag); + if (type == null) { + return new GarbageIota(); + } + var data = tag.get(HexIotaTypes.KEY_DATA); + if (data == null) { + return new GarbageIota(); + } + Iota deserialized; + try { + deserialized = Objects.requireNonNullElse(type.deserialize(data, world), new NullIota()); + } catch (IllegalArgumentException exn) { + HexAPI.LOGGER.warn("Caught an exception deserializing an iota", exn); + deserialized = new GarbageIota(); + } + return deserialized; + } + + private static Component brokenIota() { + return Component.translatable("hexcasting.spelldata.unknown") + .withStyle(ChatFormatting.GRAY, ChatFormatting.ITALIC); + } + + public static Component getDisplay(CompoundTag tag) { + var type = getTypeFromTag(tag); + if (type == null) { + return brokenIota(); + } + var data = tag.get(HexIotaTypes.KEY_DATA); + if (data == null) { + return brokenIota(); + } + return type.display(data); + } + + public static FormattedCharSequence getDisplayWithMaxWidth(CompoundTag tag, int maxWidth, Font font) { + var type = getTypeFromTag(tag); + if (type == null) { + return brokenIota().getVisualOrderText(); + } + var data = tag.get(HexIotaTypes.KEY_DATA); + if (data == null) { + return brokenIota().getVisualOrderText(); + } + var display = type.display(data); + var splitted = font.split(display, maxWidth - font.width("...")); + if (splitted.isEmpty()) + return FormattedCharSequence.EMPTY; + else if (splitted.size() == 1) + return splitted.get(0); + else { + var first = splitted.get(0); + return FormattedCharSequence.fromPair(first, + Component.literal("...").withStyle(ChatFormatting.GRAY).getVisualOrderText()); + } + } + + public static int getColor(CompoundTag tag) { + var type = getTypeFromTag(tag); + if (type == null) { + return HexUtils.ERROR_COLOR; + } + return type.color(); + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/ListIota.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/ListIota.java index ca4727c7..355c2643 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/ListIota.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/ListIota.java @@ -68,7 +68,7 @@ public class ListIota extends Iota { public @NotNull Tag serialize() { var out = new ListTag(); for (var subdatum : this.getList()) { - out.add(HexIotaTypes.serialize(subdatum)); + out.add(IotaType.serialize(subdatum)); } return out; } @@ -82,7 +82,7 @@ public class ListIota extends Iota { for (var sub : listTag) { var csub = HexUtils.downcast(sub, CompoundTag.TYPE); - var subiota = HexIotaTypes.deserialize(csub, world); + var subiota = IotaType.deserialize(csub, world); if (subiota == null) { return null; } @@ -100,7 +100,7 @@ public class ListIota extends Iota { Tag sub = list.get(i); var csub = HexUtils.downcast(sub, CompoundTag.TYPE); - out.append(HexIotaTypes.getDisplay(csub)); + out.append(IotaType.getDisplay(csub)); if (i < list.size() - 1) { out.append(", "); diff --git a/Common/src/main/java/at/petrak/hexcasting/api/item/IotaHolderItem.java b/Common/src/main/java/at/petrak/hexcasting/api/item/IotaHolderItem.java index d0ae62a8..46f4074f 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/item/IotaHolderItem.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/item/IotaHolderItem.java @@ -1,6 +1,7 @@ package at.petrak.hexcasting.api.item; import at.petrak.hexcasting.api.casting.iota.Iota; +import at.petrak.hexcasting.api.casting.iota.IotaType; import at.petrak.hexcasting.api.utils.HexUtils; import at.petrak.hexcasting.api.utils.NBTHelper; import at.petrak.hexcasting.client.ClientTickCounter; @@ -45,7 +46,7 @@ public interface IotaHolderItem { var tag = dh.readIotaTag(stack); if (tag != null) { - return HexIotaTypes.deserialize(tag, world); + return IotaType.deserialize(tag, world); } else { return null; } @@ -83,7 +84,7 @@ public interface IotaHolderItem { return HexUtils.ERROR_COLOR; } - return HexIotaTypes.getColor(tag); + return IotaType.getColor(tag); } /** @@ -100,7 +101,7 @@ public interface IotaHolderItem { TooltipFlag flag) { var datumTag = self.readIotaTag(stack); if (datumTag != null) { - var cmp = HexIotaTypes.getDisplay(datumTag); + var cmp = IotaType.getDisplay(datumTag); components.add(Component.translatable("hexcasting.spelldata.onitem", cmp)); if (flag.isAdvanced()) { diff --git a/Common/src/main/java/at/petrak/hexcasting/api/misc/DiscoveryHandlers.java b/Common/src/main/java/at/petrak/hexcasting/api/misc/DiscoveryHandlers.java index a4dc4400..f6299137 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/misc/DiscoveryHandlers.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/misc/DiscoveryHandlers.java @@ -2,7 +2,7 @@ package at.petrak.hexcasting.api.misc; import at.petrak.hexcasting.api.addldata.ADMediaHolder; import at.petrak.hexcasting.api.casting.eval.CastingEnvironment; -import at.petrak.hexcasting.api.casting.eval.CastingHarness; +import at.petrak.hexcasting.api.casting.eval.vm.CastingVM; import com.google.common.collect.Lists; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; @@ -15,10 +15,11 @@ import java.util.function.Predicate; public class DiscoveryHandlers { private static final List> HAS_LENS_PREDICATE = new ArrayList<>(); - private static final List>> MEDIA_HOLDER_DISCOVERY = new ArrayList<>(); + private static final List>> MEDIA_HOLDER_DISCOVERY = new ArrayList<>(); private static final List> GRID_SCALE_MODIFIERS = new ArrayList<>(); private static final List>> ITEM_SLOT_DISCOVERER = new ArrayList<>(); - private static final List>> OPERATIVE_SLOT_DISCOVERER = new ArrayList<>(); + private static final List>> OPERATIVE_SLOT_DISCOVERER = + new ArrayList<>(); private static final List> DEBUG_DISCOVERER = new ArrayList<>(); public static boolean hasLens(Player player) { @@ -30,7 +31,7 @@ public class DiscoveryHandlers { return false; } - public static List collectMediaHolders(CastingHarness harness) { + public static List collectMediaHolders(CastingVM harness) { List holders = Lists.newArrayList(); for (var discoverer : MEDIA_HOLDER_DISCOVERY) { holders.addAll(discoverer.apply(harness)); @@ -76,7 +77,7 @@ public class DiscoveryHandlers { HAS_LENS_PREDICATE.add(predicate); } - public static void addMediaHolderDiscoverer(Function> discoverer) { + public static void addMediaHolderDiscoverer(Function> discoverer) { MEDIA_HOLDER_DISCOVERY.add(discoverer); } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/utils/HexUtils.kt b/Common/src/main/java/at/petrak/hexcasting/api/utils/HexUtils.kt index c0c9e041..b8abdc0d 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/utils/HexUtils.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/utils/HexUtils.kt @@ -3,9 +3,9 @@ package at.petrak.hexcasting.api.utils import at.petrak.hexcasting.api.casting.iota.Iota +import at.petrak.hexcasting.api.casting.iota.IotaType import at.petrak.hexcasting.api.casting.iota.ListIota import at.petrak.hexcasting.api.casting.math.HexCoord -import at.petrak.hexcasting.common.lib.hex.HexIotaTypes import net.minecraft.ChatFormatting import net.minecraft.core.Registry import net.minecraft.nbt.* @@ -251,7 +251,7 @@ inline operator fun WeakValue.setValue(thisRef: Any?, property: KProperty * Returns an empty list if it's too complicated. */ fun Iterable.serializeToNBT() = - if (HexIotaTypes.isTooLargeToSerialize(this)) + if (IotaType.isTooLargeToSerialize(this)) ListTag() else ListIota(this.toList()).serialize() diff --git a/Common/src/main/java/at/petrak/hexcasting/client/RegisterClientStuff.java b/Common/src/main/java/at/petrak/hexcasting/client/RegisterClientStuff.java index 991d16b9..4846e857 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/RegisterClientStuff.java +++ b/Common/src/main/java/at/petrak/hexcasting/client/RegisterClientStuff.java @@ -2,6 +2,7 @@ package at.petrak.hexcasting.client; import at.petrak.hexcasting.api.block.circle.BlockAbstractImpetus; import at.petrak.hexcasting.api.block.circle.BlockEntityAbstractImpetus; +import at.petrak.hexcasting.api.casting.iota.IotaType; import at.petrak.hexcasting.api.client.ScryingLensOverlayRegistry; import at.petrak.hexcasting.api.item.IotaHolderItem; import at.petrak.hexcasting.api.item.MediaHolderItem; @@ -19,7 +20,6 @@ import at.petrak.hexcasting.common.items.magic.ItemPackagedHex; import at.petrak.hexcasting.common.lib.HexBlockEntities; import at.petrak.hexcasting.common.lib.HexBlocks; import at.petrak.hexcasting.common.lib.HexItems; -import at.petrak.hexcasting.common.lib.hex.HexIotaTypes; import at.petrak.hexcasting.xplat.IClientXplatAbstractions; import com.mojang.datafixers.util.Pair; import net.minecraft.ChatFormatting; @@ -138,7 +138,7 @@ public class RegisterClientStuff { if (iotaTag == null) { return 0xff_ffffff; } - return HexIotaTypes.getColor(iotaTag); + return IotaType.getColor(iotaTag); }, HexBlocks.AKASHIC_BOOKSHELF); } @@ -192,7 +192,7 @@ public class RegisterClientStuff { if (world.getBlockEntity(pos) instanceof BlockEntityAkashicBookshelf tile) { var iotaTag = tile.getIotaTag(); if (iotaTag != null) { - var display = HexIotaTypes.getDisplay(iotaTag); + var display = IotaType.getDisplay(iotaTag); lines.add(new Pair<>(new ItemStack(Items.BOOK), display)); } } 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 c4a934bd..fdc98611 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 @@ -1,21 +1,21 @@ package at.petrak.hexcasting.client.gui -import at.petrak.hexcasting.api.misc.DiscoveryHandlers -import at.petrak.hexcasting.api.mod.HexConfig -import at.petrak.hexcasting.api.mod.HexTags -import at.petrak.hexcasting.api.casting.eval.ControllerInfo +import at.petrak.hexcasting.api.casting.eval.ExecutionClientView import at.petrak.hexcasting.api.casting.eval.ResolvedPattern import at.petrak.hexcasting.api.casting.eval.ResolvedPatternType +import at.petrak.hexcasting.api.casting.iota.IotaType import at.petrak.hexcasting.api.casting.math.HexAngle import at.petrak.hexcasting.api.casting.math.HexCoord import at.petrak.hexcasting.api.casting.math.HexDir import at.petrak.hexcasting.api.casting.math.HexPattern +import at.petrak.hexcasting.api.misc.DiscoveryHandlers +import at.petrak.hexcasting.api.mod.HexConfig +import at.petrak.hexcasting.api.mod.HexTags import at.petrak.hexcasting.api.utils.asTranslatedComponent import at.petrak.hexcasting.client.* import at.petrak.hexcasting.client.ktxt.accumulatedScroll import at.petrak.hexcasting.client.sound.GridSoundInstance import at.petrak.hexcasting.common.lib.HexSounds -import at.petrak.hexcasting.common.lib.hex.HexIotaTypes import at.petrak.hexcasting.common.network.MsgNewSpellPatternSyn import at.petrak.hexcasting.xplat.IClientXplatAbstractions import com.mojang.blaze3d.systems.RenderSystem @@ -59,7 +59,7 @@ class GuiSpellcasting constructor( this.calculateIotaDisplays() } - fun recvServerUpdate(info: ControllerInfo, index: Int) { + fun recvServerUpdate(info: ExecutionClientView, index: Int) { this.patterns.getOrNull(index)?.let { it.type = info.resolutionType } @@ -75,7 +75,7 @@ class GuiSpellcasting constructor( val mc = Minecraft.getInstance() val width = (this.width * LHS_IOTAS_ALLOCATION).toInt() this.stackDescs = - this.cachedStack.map { HexIotaTypes.getDisplayWithMaxWidth(it, width, mc.font) } + this.cachedStack.map { IotaType.getDisplayWithMaxWidth(it, width, mc.font) } .asReversed() // this.parenDescs = if (this.cachedParens.isNotEmpty()) // this.cachedParens.flatMap { HexIotaTypes.getDisplayWithMaxWidth(it, width, mc.font) } @@ -86,7 +86,7 @@ class GuiSpellcasting constructor( this.parenDescs = emptyList() this.ravenmind = this.cachedRavenmind?.let { - HexIotaTypes.getDisplayWithMaxWidth( + IotaType.getDisplayWithMaxWidth( it, (this.width * RHS_IOTAS_ALLOCATION).toInt(), mc.font diff --git a/Common/src/main/java/at/petrak/hexcasting/common/blocks/akashic/BlockAkashicRecord.java b/Common/src/main/java/at/petrak/hexcasting/common/blocks/akashic/BlockAkashicRecord.java index 119ad21c..db3f96ac 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/blocks/akashic/BlockAkashicRecord.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/blocks/akashic/BlockAkashicRecord.java @@ -1,8 +1,8 @@ package at.petrak.hexcasting.common.blocks.akashic; import at.petrak.hexcasting.api.casting.iota.Iota; +import at.petrak.hexcasting.api.casting.iota.IotaType; import at.petrak.hexcasting.api.casting.math.HexPattern; -import at.petrak.hexcasting.common.lib.hex.HexIotaTypes; import net.minecraft.core.BlockPos; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.Level; @@ -57,7 +57,7 @@ public class BlockAkashicRecord extends Block { var tile = (BlockEntityAkashicBookshelf) slevel.getBlockEntity(foundPos); var tag = tile.getIotaTag(); - return tag == null ? null : HexIotaTypes.deserialize(tag, slevel); + return tag == null ? null : IotaType.deserialize(tag, slevel); } // TODO get comparators working again and also cache the number of iotas somehow? diff --git a/Common/src/main/java/at/petrak/hexcasting/common/blocks/akashic/BlockEntityAkashicBookshelf.java b/Common/src/main/java/at/petrak/hexcasting/common/blocks/akashic/BlockEntityAkashicBookshelf.java index d851731b..7fe9945c 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/blocks/akashic/BlockEntityAkashicBookshelf.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/blocks/akashic/BlockEntityAkashicBookshelf.java @@ -2,9 +2,9 @@ package at.petrak.hexcasting.common.blocks.akashic; import at.petrak.hexcasting.api.block.HexBlockEntity; import at.petrak.hexcasting.api.casting.iota.Iota; +import at.petrak.hexcasting.api.casting.iota.IotaType; import at.petrak.hexcasting.api.casting.math.HexPattern; import at.petrak.hexcasting.common.lib.HexBlockEntities; -import at.petrak.hexcasting.common.lib.hex.HexIotaTypes; import net.minecraft.core.BlockPos; import net.minecraft.nbt.CompoundTag; import net.minecraft.world.level.block.state.BlockState; @@ -38,7 +38,7 @@ public class BlockEntityAkashicBookshelf extends HexBlockEntity { public void setNewMapping(HexPattern pattern, Iota iota) { var previouslyEmpty = this.pattern == null; this.pattern = pattern; - this.iotaTag = HexIotaTypes.serialize(iota); + this.iotaTag = IotaType.serialize(iota); if (previouslyEmpty) { var oldBs = this.getBlockState(); diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/env/StaffCastEnv.java b/Common/src/main/java/at/petrak/hexcasting/common/casting/env/StaffCastEnv.java index 6066ceb1..bc225763 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/env/StaffCastEnv.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/env/StaffCastEnv.java @@ -2,7 +2,7 @@ package at.petrak.hexcasting.common.casting.env; import at.petrak.hexcasting.api.HexAPI; import at.petrak.hexcasting.api.casting.eval.CastResult; -import at.petrak.hexcasting.api.casting.eval.ControllerInfo; +import at.petrak.hexcasting.api.casting.eval.ExecutionClientView; import at.petrak.hexcasting.api.casting.eval.ResolvedPattern; import at.petrak.hexcasting.api.casting.eval.ResolvedPatternType; import at.petrak.hexcasting.api.casting.eval.sideeffects.OperatorSideEffect; @@ -74,13 +74,13 @@ public class StaffCastEnv extends PlayerBasedCastEnv { var harness = IXplatAbstractions.INSTANCE.getStaffHarness(sender, msg.handUsed()); - ControllerInfo clientInfo; + ExecutionClientView clientInfo; if (autoFail) { var descs = harness.generateDescs(); - clientInfo = new ControllerInfo(harness.getStack().isEmpty(), ResolvedPatternType.INVALID, + clientInfo = new ExecutionClientView(harness.getStack().isEmpty(), ResolvedPatternType.INVALID, descs.getFirst(), descs.getSecond(), descs.getThird(), harness.getParenCount()); } else { - clientInfo = harness.executeIota(new PatternIota(msg.pattern()), sender.getLevel()); + clientInfo = harness.queueAndExecuteIota(new PatternIota(msg.pattern()), sender.getLevel()); } if (clientInfo.isStackClear()) { diff --git a/Common/src/main/java/at/petrak/hexcasting/common/items/ItemAbacus.java b/Common/src/main/java/at/petrak/hexcasting/common/items/ItemAbacus.java index 8c933e68..1cdccbb1 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/items/ItemAbacus.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/items/ItemAbacus.java @@ -1,10 +1,10 @@ package at.petrak.hexcasting.common.items; -import at.petrak.hexcasting.api.item.IotaHolderItem; import at.petrak.hexcasting.api.casting.iota.DoubleIota; import at.petrak.hexcasting.api.casting.iota.Iota; +import at.petrak.hexcasting.api.casting.iota.IotaType; +import at.petrak.hexcasting.api.item.IotaHolderItem; import at.petrak.hexcasting.api.utils.NBTHelper; -import at.petrak.hexcasting.common.lib.hex.HexIotaTypes; import at.petrak.hexcasting.common.lib.HexSounds; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.chat.Component; @@ -30,7 +30,7 @@ public class ItemAbacus extends Item implements IotaHolderItem { public @Nullable CompoundTag readIotaTag(ItemStack stack) { var datum = new DoubleIota(NBTHelper.getDouble(stack, TAG_VALUE)); - return HexIotaTypes.serialize(datum); + return IotaType.serialize(datum); } @Override @@ -66,7 +66,7 @@ public class ItemAbacus extends Item implements IotaHolderItem { @Override public void appendHoverText(ItemStack pStack, @Nullable Level pLevel, List pTooltipComponents, - TooltipFlag pIsAdvanced) { + TooltipFlag pIsAdvanced) { IotaHolderItem.appendHoverText(this, pStack, pTooltipComponents, pIsAdvanced); } } diff --git a/Common/src/main/java/at/petrak/hexcasting/common/items/ItemFocus.java b/Common/src/main/java/at/petrak/hexcasting/common/items/ItemFocus.java index 48ea9b5e..92e66c73 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/items/ItemFocus.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/items/ItemFocus.java @@ -1,10 +1,10 @@ package at.petrak.hexcasting.common.items; import at.petrak.hexcasting.api.casting.iota.Iota; +import at.petrak.hexcasting.api.casting.iota.IotaType; import at.petrak.hexcasting.api.casting.iota.NullIota; import at.petrak.hexcasting.api.item.IotaHolderItem; import at.petrak.hexcasting.api.utils.NBTHelper; -import at.petrak.hexcasting.common.lib.hex.HexIotaTypes; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; @@ -57,7 +57,7 @@ public class ItemFocus extends Item implements IotaHolderItem { stack.removeTagKey(TAG_DATA); stack.removeTagKey(TAG_SEALED); } else if (!isSealed(stack)) { - NBTHelper.put(stack, TAG_DATA, HexIotaTypes.serialize(datum)); + NBTHelper.put(stack, TAG_DATA, IotaType.serialize(datum)); } } diff --git a/Common/src/main/java/at/petrak/hexcasting/common/items/ItemSpellbook.java b/Common/src/main/java/at/petrak/hexcasting/common/items/ItemSpellbook.java index f05bcc4b..3a91c4f6 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/items/ItemSpellbook.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/items/ItemSpellbook.java @@ -1,10 +1,10 @@ package at.petrak.hexcasting.common.items; -import at.petrak.hexcasting.api.item.IotaHolderItem; import at.petrak.hexcasting.api.casting.iota.Iota; +import at.petrak.hexcasting.api.casting.iota.IotaType; import at.petrak.hexcasting.api.casting.iota.NullIota; +import at.petrak.hexcasting.api.item.IotaHolderItem; import at.petrak.hexcasting.api.utils.NBTHelper; -import at.petrak.hexcasting.common.lib.hex.HexIotaTypes; import net.minecraft.ChatFormatting; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.Tag; @@ -42,7 +42,7 @@ public class ItemSpellbook extends Item implements IotaHolderItem { @Override public void appendHoverText(ItemStack stack, @Nullable Level level, List tooltip, - TooltipFlag isAdvanced) { + TooltipFlag isAdvanced) { boolean sealed = isSealed(stack); boolean empty = false; if (NBTHelper.hasNumber(stack, TAG_SELECTED_PAGE)) { @@ -148,14 +148,14 @@ public class ItemSpellbook extends Item implements IotaHolderItem { pages.remove(key); NBTHelper.remove(NBTHelper.getCompound(stack, TAG_SEALED), key); } else { - pages.put(key, HexIotaTypes.serialize(datum)); + pages.put(key, IotaType.serialize(datum)); } if (pages.isEmpty()) { NBTHelper.remove(stack, TAG_PAGES); } } else if (datum != null) { - NBTHelper.getOrCreateCompound(stack, TAG_PAGES).put(key, HexIotaTypes.serialize(datum)); + NBTHelper.getOrCreateCompound(stack, TAG_PAGES).put(key, IotaType.serialize(datum)); } else { NBTHelper.remove(NBTHelper.getCompound(stack, TAG_SEALED), key); } diff --git a/Common/src/main/java/at/petrak/hexcasting/common/items/magic/ItemPackagedHex.java b/Common/src/main/java/at/petrak/hexcasting/common/items/magic/ItemPackagedHex.java index 50d463c0..05f35557 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/items/magic/ItemPackagedHex.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/items/magic/ItemPackagedHex.java @@ -1,11 +1,11 @@ package at.petrak.hexcasting.common.items.magic; -import at.petrak.hexcasting.api.item.HexHolderItem; import at.petrak.hexcasting.api.casting.eval.CastingEnvironment; -import at.petrak.hexcasting.api.casting.eval.CastingHarness; +import at.petrak.hexcasting.api.casting.eval.vm.CastingVM; import at.petrak.hexcasting.api.casting.iota.Iota; +import at.petrak.hexcasting.api.casting.iota.IotaType; +import at.petrak.hexcasting.api.item.HexHolderItem; import at.petrak.hexcasting.api.utils.NBTHelper; -import at.petrak.hexcasting.common.lib.hex.HexIotaTypes; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.nbt.Tag; @@ -66,7 +66,7 @@ public abstract class ItemPackagedHex extends ItemMediaHolder implements HexHold var out = new ArrayList(); for (var patTag : patsTag) { CompoundTag tag = NBTHelper.getAsCompound(patTag); - out.add(HexIotaTypes.deserialize(tag, level)); + out.add(IotaType.deserialize(tag, level)); } return out; } @@ -75,7 +75,7 @@ public abstract class ItemPackagedHex extends ItemMediaHolder implements HexHold public void writeHex(ItemStack stack, List program, int media) { ListTag patsTag = new ListTag(); for (Iota pat : program) { - patsTag.add(HexIotaTypes.serialize(pat)); + patsTag.add(IotaType.serialize(pat)); } NBTHelper.putList(stack, TAG_PROGRAM, patsTag); @@ -107,8 +107,8 @@ public abstract class ItemPackagedHex extends ItemMediaHolder implements HexHold } var sPlayer = (ServerPlayer) player; var ctx = new CastingEnvironment(sPlayer, usedHand, CastingEnvironment.CastSource.PACKAGED_HEX); - var harness = new CastingHarness(ctx); - var info = harness.executeIotas(instrs, sPlayer.getLevel()); + var harness = new CastingVM(ctx); + var info = harness.queueAndExecuteIotas(instrs, sPlayer.getLevel()); boolean broken = breakAfterDepletion() && getMedia(stack) == 0; diff --git a/Common/src/main/java/at/petrak/hexcasting/common/lib/hex/HexIotaTypes.java b/Common/src/main/java/at/petrak/hexcasting/common/lib/hex/HexIotaTypes.java index 8d5def2d..96674128 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/lib/hex/HexIotaTypes.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/lib/hex/HexIotaTypes.java @@ -2,22 +2,13 @@ package at.petrak.hexcasting.common.lib.hex; import at.petrak.hexcasting.api.HexAPI; import at.petrak.hexcasting.api.casting.iota.*; -import at.petrak.hexcasting.api.utils.HexUtils; import at.petrak.hexcasting.xplat.IXplatAbstractions; -import com.mojang.datafixers.util.Pair; -import net.minecraft.ChatFormatting; -import net.minecraft.client.gui.Font; import net.minecraft.core.Registry; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.Tag; -import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.util.FormattedCharSequence; -import org.jetbrains.annotations.Nullable; import javax.annotation.ParametersAreNonnullByDefault; -import java.util.*; +import java.util.LinkedHashMap; +import java.util.Map; import java.util.function.BiConsumer; import static at.petrak.hexcasting.api.HexAPI.modLoc; @@ -34,151 +25,6 @@ public class HexIotaTypes { public static final int MAX_SERIALIZATION_DEPTH = 256; public static final int MAX_SERIALIZATION_TOTAL = 1024; - public static CompoundTag serialize(Iota iota) { - var type = iota.getType(); - var typeId = REGISTRY.getKey(type); - if (typeId == null) { - throw new IllegalStateException( - "Tried to serialize an unregistered iota type. Iota: " + iota - + " ; Type" + type.getClass().getTypeName()); - } - - // We check if it's too big on serialization; if it is we just return a garbage. - if (iota instanceof ListIota listIota && isTooLargeToSerialize(listIota.getList())) { - // Garbage will never be too large so we just recurse - return serialize(new GarbageIota()); - } - var dataTag = iota.serialize(); - var out = new CompoundTag(); - out.putString(KEY_TYPE, typeId.toString()); - out.put(KEY_DATA, dataTag); - return out; - } - - public static boolean isTooLargeToSerialize(Iterable examinee) { - // We don't recurse here, just a work queue (or work stack, if we liked.) - // Each element is a found sub-iota, and how deep it is. - // - // TODO: is it worth trying to cache the depth and size statically on a SpellList. - var listsToExamine = new ArrayDeque<>(Collections.singleton(new Pair<>(examinee, 0))); - int totalEltsFound = 1; // count the first list - while (!listsToExamine.isEmpty()) { - var iotaPair = listsToExamine.removeFirst(); - var sublist = iotaPair.getFirst(); - int depth = iotaPair.getSecond(); - for (var iota : sublist) { - totalEltsFound++; - if (totalEltsFound >= MAX_SERIALIZATION_TOTAL) { - return true; // too bad - } - if (iota instanceof ListIota subsublist) { - if (depth + 1 >= MAX_SERIALIZATION_DEPTH) { - return true; - } - listsToExamine.addLast(new Pair<>(subsublist.getList(), depth + 1)); - } - } - } - // we made it! - return false; - } - - /** - * This method attempts to find the type from the {@code type} key. - * See {@link HexIotaTypes#serialize(Iota)} for the storage format. - * - * @return {@code null} if it cannot get the type. - */ - @Nullable - public static IotaType getTypeFromTag(CompoundTag tag) { - if (!tag.contains(KEY_TYPE, Tag.TAG_STRING)) { - return null; - } - var typeKey = tag.getString(KEY_TYPE); - if (!ResourceLocation.isValidResourceLocation(typeKey)) { - return null; - } - var typeLoc = new ResourceLocation(typeKey); - return REGISTRY.get(typeLoc); - } - - /** - * Attempt to deserialize an iota from a tag. - *
- * Iotas are saved as such: - * - * { - * "type": "hexcasting:atype", - * "data": {...} - * } - * - */ - public static Iota deserialize(CompoundTag tag, ServerLevel world) { - var type = getTypeFromTag(tag); - if (type == null) { - return new GarbageIota(); - } - var data = tag.get(KEY_DATA); - if (data == null) { - return new GarbageIota(); - } - Iota deserialized; - try { - deserialized = Objects.requireNonNullElse(type.deserialize(data, world), new NullIota()); - } catch (IllegalArgumentException exn) { - HexAPI.LOGGER.warn("Caught an exception deserializing an iota", exn); - deserialized = new GarbageIota(); - } - return deserialized; - } - - private static Component brokenIota() { - return Component.translatable("hexcasting.spelldata.unknown") - .withStyle(ChatFormatting.GRAY, ChatFormatting.ITALIC); - } - - public static Component getDisplay(CompoundTag tag) { - var type = getTypeFromTag(tag); - if (type == null) { - return brokenIota(); - } - var data = tag.get(KEY_DATA); - if (data == null) { - return brokenIota(); - } - return type.display(data); - } - - public static FormattedCharSequence getDisplayWithMaxWidth(CompoundTag tag, int maxWidth, Font font) { - var type = getTypeFromTag(tag); - if (type == null) { - return brokenIota().getVisualOrderText(); - } - var data = tag.get(KEY_DATA); - if (data == null) { - return brokenIota().getVisualOrderText(); - } - var display = type.display(data); - var splitted = font.split(display, maxWidth - font.width("...")); - if (splitted.isEmpty()) - return FormattedCharSequence.EMPTY; - else if (splitted.size() == 1) - return splitted.get(0); - else { - var first = splitted.get(0); - return FormattedCharSequence.fromPair(first, - Component.literal("...").withStyle(ChatFormatting.GRAY).getVisualOrderText()); - } - } - - public static int getColor(CompoundTag tag) { - var type = getTypeFromTag(tag); - if (type == null) { - return HexUtils.ERROR_COLOR; - } - return type.color(); - } - public static void registerTypes(BiConsumer, ResourceLocation> r) { for (var e : TYPES.entrySet()) { r.accept(e.getValue(), e.getKey()); diff --git a/Common/src/main/java/at/petrak/hexcasting/common/network/MsgNewSpellPatternAck.java b/Common/src/main/java/at/petrak/hexcasting/common/network/MsgNewSpellPatternAck.java index 125bec2a..88a0218d 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/network/MsgNewSpellPatternAck.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/network/MsgNewSpellPatternAck.java @@ -1,6 +1,6 @@ package at.petrak.hexcasting.common.network; -import at.petrak.hexcasting.api.casting.eval.ControllerInfo; +import at.petrak.hexcasting.api.casting.eval.ExecutionClientView; import at.petrak.hexcasting.api.casting.eval.ResolvedPatternType; import at.petrak.hexcasting.client.gui.GuiSpellcasting; import at.petrak.hexcasting.common.lib.HexSounds; @@ -16,7 +16,7 @@ import static at.petrak.hexcasting.api.HexAPI.modLoc; /** * Sent server->client when the player finishes casting a spell. */ -public record MsgNewSpellPatternAck(ControllerInfo info, int index) implements IMessage { +public record MsgNewSpellPatternAck(ExecutionClientView info, int index) implements IMessage { public static final ResourceLocation ID = modLoc("pat_sc"); @Override @@ -38,7 +38,7 @@ public record MsgNewSpellPatternAck(ControllerInfo info, int index) implements I var parenCount = buf.readVarInt(); return new MsgNewSpellPatternAck( - new ControllerInfo(isStackEmpty, resolutionType, stack, parens, raven, parenCount), index + new ExecutionClientView(isStackEmpty, resolutionType, stack, parens, raven, parenCount), index ); } diff --git a/Common/src/main/java/at/petrak/hexcasting/common/network/MsgShiftScrollSyn.java b/Common/src/main/java/at/petrak/hexcasting/common/network/MsgShiftScrollSyn.java index 985491af..0dce6f86 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/network/MsgShiftScrollSyn.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/network/MsgShiftScrollSyn.java @@ -1,9 +1,9 @@ package at.petrak.hexcasting.common.network; +import at.petrak.hexcasting.api.casting.iota.IotaType; import at.petrak.hexcasting.api.utils.NBTHelper; import at.petrak.hexcasting.common.items.ItemAbacus; import at.petrak.hexcasting.common.items.ItemSpellbook; -import at.petrak.hexcasting.common.lib.hex.HexIotaTypes; import at.petrak.hexcasting.common.lib.HexItems; import at.petrak.hexcasting.common.lib.HexSounds; import io.netty.buffer.ByteBuf; @@ -144,7 +144,7 @@ public record MsgShiftScrollSyn(double mainHandDelta, double offHandDelta, boole var datumTag = HexItems.ABACUS.readIotaTag(stack); if (datumTag != null) { - var popup = HexIotaTypes.getDisplay(datumTag); + var popup = IotaType.getDisplay(datumTag); sender.displayClientMessage( Component.translatable("hexcasting.tooltip.abacus", popup).withStyle(ChatFormatting.GREEN), true); } diff --git a/Common/src/main/java/at/petrak/hexcasting/xplat/IXplatAbstractions.java b/Common/src/main/java/at/petrak/hexcasting/xplat/IXplatAbstractions.java index 98070e28..ec5bac82 100644 --- a/Common/src/main/java/at/petrak/hexcasting/xplat/IXplatAbstractions.java +++ b/Common/src/main/java/at/petrak/hexcasting/xplat/IXplatAbstractions.java @@ -6,9 +6,9 @@ import at.petrak.hexcasting.api.addldata.ADIotaHolder; import at.petrak.hexcasting.api.addldata.ADMediaHolder; import at.petrak.hexcasting.api.casting.ActionRegistryEntry; import at.petrak.hexcasting.api.casting.castables.SpecialHandler; -import at.petrak.hexcasting.api.casting.eval.CastingHarness; import at.petrak.hexcasting.api.casting.eval.ResolvedPattern; import at.petrak.hexcasting.api.casting.eval.sideeffects.EvalSound; +import at.petrak.hexcasting.api.casting.eval.vm.CastingVM; import at.petrak.hexcasting.api.casting.iota.IotaType; import at.petrak.hexcasting.api.misc.FrozenColorizer; import at.petrak.hexcasting.api.player.FlightAbility; @@ -79,7 +79,7 @@ public interface IXplatAbstractions { void setFlight(ServerPlayer target, FlightAbility flight); - void setHarness(ServerPlayer target, @Nullable CastingHarness harness); + void setHarness(ServerPlayer target, @Nullable CastingVM harness); void setPatterns(ServerPlayer target, List patterns); @@ -91,7 +91,7 @@ public interface IXplatAbstractions { Sentinel getSentinel(Player player); - CastingHarness getStaffHarness(ServerPlayer player, InteractionHand hand); + CastingVM getStaffHarness(ServerPlayer player, InteractionHand hand); List getPatternsSavedInUi(ServerPlayer player); diff --git a/Fabric/src/main/java/at/petrak/hexcasting/fabric/cc/CCHarness.java b/Fabric/src/main/java/at/petrak/hexcasting/fabric/cc/CCHarness.java index 90603d06..237a700a 100644 --- a/Fabric/src/main/java/at/petrak/hexcasting/fabric/cc/CCHarness.java +++ b/Fabric/src/main/java/at/petrak/hexcasting/fabric/cc/CCHarness.java @@ -1,7 +1,7 @@ package at.petrak.hexcasting.fabric.cc; import at.petrak.hexcasting.api.casting.eval.CastingEnvironment; -import at.petrak.hexcasting.api.casting.eval.CastingHarness; +import at.petrak.hexcasting.api.casting.eval.vm.CastingVM; import dev.onyxstudios.cca.api.v3.component.Component; import net.minecraft.nbt.CompoundTag; import net.minecraft.server.level.ServerPlayer; @@ -18,16 +18,16 @@ public class CCHarness implements Component { this.owner = owner; } - public CastingHarness getHarness(InteractionHand hand) { + public CastingVM getHarness(InteractionHand hand) { var ctx = new CastingEnvironment(this.owner, hand, CastingEnvironment.CastSource.STAFF); if (this.lazyLoadedTag.isEmpty()) { - return new CastingHarness(ctx); + return new CastingVM(ctx); } else { - return CastingHarness.fromNBT(this.lazyLoadedTag, ctx); + return CastingVM.fromNBT(this.lazyLoadedTag, ctx); } } - public void setHarness(@Nullable CastingHarness harness) { + public void setHarness(@Nullable CastingVM harness) { this.lazyLoadedTag = harness == null ? new CompoundTag() : harness.serializeToNBT(); } diff --git a/Fabric/src/main/java/at/petrak/hexcasting/fabric/cc/adimpl/CCItemIotaHolder.java b/Fabric/src/main/java/at/petrak/hexcasting/fabric/cc/adimpl/CCItemIotaHolder.java index 5f879164..c0f8993f 100644 --- a/Fabric/src/main/java/at/petrak/hexcasting/fabric/cc/adimpl/CCItemIotaHolder.java +++ b/Fabric/src/main/java/at/petrak/hexcasting/fabric/cc/adimpl/CCItemIotaHolder.java @@ -1,8 +1,8 @@ package at.petrak.hexcasting.fabric.cc.adimpl; -import at.petrak.hexcasting.api.item.IotaHolderItem; import at.petrak.hexcasting.api.casting.iota.Iota; -import at.petrak.hexcasting.common.lib.hex.HexIotaTypes; +import at.petrak.hexcasting.api.casting.iota.IotaType; +import at.petrak.hexcasting.api.item.IotaHolderItem; import at.petrak.hexcasting.fabric.cc.HexCardinalComponents; import dev.onyxstudios.cca.api.v3.item.ItemComponent; import net.minecraft.nbt.CompoundTag; @@ -56,7 +56,7 @@ public abstract class CCItemIotaHolder extends ItemComponent implements CCIotaHo @Override public @Nullable CompoundTag readIotaTag() { var iota = this.provider.apply(this.stack); - return iota == null ? null : HexIotaTypes.serialize(iota); + return iota == null ? null : IotaType.serialize(iota); } @Override diff --git a/Fabric/src/main/java/at/petrak/hexcasting/fabric/xplat/FabricXplatImpl.java b/Fabric/src/main/java/at/petrak/hexcasting/fabric/xplat/FabricXplatImpl.java index 560d153a..0499eae6 100644 --- a/Fabric/src/main/java/at/petrak/hexcasting/fabric/xplat/FabricXplatImpl.java +++ b/Fabric/src/main/java/at/petrak/hexcasting/fabric/xplat/FabricXplatImpl.java @@ -6,9 +6,9 @@ import at.petrak.hexcasting.api.addldata.ADIotaHolder; import at.petrak.hexcasting.api.addldata.ADMediaHolder; import at.petrak.hexcasting.api.casting.ActionRegistryEntry; import at.petrak.hexcasting.api.casting.castables.SpecialHandler; -import at.petrak.hexcasting.api.casting.eval.CastingHarness; import at.petrak.hexcasting.api.casting.eval.ResolvedPattern; import at.petrak.hexcasting.api.casting.eval.sideeffects.EvalSound; +import at.petrak.hexcasting.api.casting.eval.vm.CastingVM; import at.petrak.hexcasting.api.casting.iota.IotaType; import at.petrak.hexcasting.api.misc.FrozenColorizer; import at.petrak.hexcasting.api.mod.HexConfig; @@ -165,7 +165,7 @@ public class FabricXplatImpl implements IXplatAbstractions { } @Override - public void setHarness(ServerPlayer target, CastingHarness harness) { + public void setHarness(ServerPlayer target, CastingVM harness) { var cc = HexCardinalComponents.HARNESS.get(target); cc.setHarness(harness); } @@ -201,7 +201,7 @@ public class FabricXplatImpl implements IXplatAbstractions { } @Override - public CastingHarness getStaffHarness(ServerPlayer player, InteractionHand hand) { + public CastingVM getStaffHarness(ServerPlayer player, InteractionHand hand) { var cc = HexCardinalComponents.HARNESS.get(player); return cc.getHarness(hand); } diff --git a/Forge/src/main/java/at/petrak/hexcasting/forge/cap/adimpl/CapStaticIotaHolder.java b/Forge/src/main/java/at/petrak/hexcasting/forge/cap/adimpl/CapStaticIotaHolder.java index d55edcfb..1eb438a1 100644 --- a/Forge/src/main/java/at/petrak/hexcasting/forge/cap/adimpl/CapStaticIotaHolder.java +++ b/Forge/src/main/java/at/petrak/hexcasting/forge/cap/adimpl/CapStaticIotaHolder.java @@ -2,7 +2,7 @@ package at.petrak.hexcasting.forge.cap.adimpl; import at.petrak.hexcasting.api.addldata.ADIotaHolder; import at.petrak.hexcasting.api.casting.iota.Iota; -import at.petrak.hexcasting.common.lib.hex.HexIotaTypes; +import at.petrak.hexcasting.api.casting.iota.IotaType; import net.minecraft.nbt.CompoundTag; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.item.ItemStack; @@ -17,7 +17,7 @@ public record CapStaticIotaHolder(Function provider, public @Nullable CompoundTag readIotaTag() { var iota = provider.apply(stack); - return iota == null ? null : HexIotaTypes.serialize(iota); + return iota == null ? null : IotaType.serialize(iota); } @Override diff --git a/Forge/src/main/java/at/petrak/hexcasting/forge/xplat/ForgeXplatImpl.java b/Forge/src/main/java/at/petrak/hexcasting/forge/xplat/ForgeXplatImpl.java index 87571e60..cdd2ba5f 100644 --- a/Forge/src/main/java/at/petrak/hexcasting/forge/xplat/ForgeXplatImpl.java +++ b/Forge/src/main/java/at/petrak/hexcasting/forge/xplat/ForgeXplatImpl.java @@ -8,9 +8,9 @@ import at.petrak.hexcasting.api.addldata.ADMediaHolder; import at.petrak.hexcasting.api.casting.ActionRegistryEntry; import at.petrak.hexcasting.api.casting.castables.SpecialHandler; import at.petrak.hexcasting.api.casting.eval.CastingEnvironment; -import at.petrak.hexcasting.api.casting.eval.CastingHarness; import at.petrak.hexcasting.api.casting.eval.ResolvedPattern; import at.petrak.hexcasting.api.casting.eval.sideeffects.EvalSound; +import at.petrak.hexcasting.api.casting.eval.vm.CastingVM; import at.petrak.hexcasting.api.casting.iota.IotaType; import at.petrak.hexcasting.api.misc.FrozenColorizer; import at.petrak.hexcasting.api.mod.HexTags; @@ -183,7 +183,7 @@ public class ForgeXplatImpl implements IXplatAbstractions { } @Override - public void setHarness(ServerPlayer player, CastingHarness harness) { + public void setHarness(ServerPlayer player, CastingVM harness) { player.getPersistentData().put(TAG_HARNESS, harness == null ? new CompoundTag() : harness.serializeToNBT()); } @@ -237,10 +237,10 @@ public class ForgeXplatImpl implements IXplatAbstractions { } @Override - public CastingHarness getStaffHarness(ServerPlayer player, InteractionHand hand) { + public CastingVM getStaffHarness(ServerPlayer player, InteractionHand hand) { // This is always from a staff because we don't need to load the harness when casting from item var ctx = new CastingEnvironment(player, hand, CastingEnvironment.CastSource.STAFF); - return CastingHarness.fromNBT(player.getPersistentData().getCompound(TAG_HARNESS), ctx); + return CastingVM.fromNBT(player.getPersistentData().getCompound(TAG_HARNESS), ctx); } @Override