From f5ea639e30ae1e2af6c6830dfd11b4d3e471f0c0 Mon Sep 17 00:00:00 2001 From: miyucomics Date: Sat, 7 Mar 2026 17:52:36 -0400 Subject: [PATCH 1/9] Basic component framework --- .../eval/vm/userdata/CastingImageComponent.kt | 35 +++++++++++++++++++ .../vm/userdata/CastingImageComponents.kt | 24 +++++++++++++ .../components/GenericIotaComponent.kt | 16 +++++++++ .../components/ImpulseScalingComponent.kt | 26 ++++++++++++++ 4 files changed, 101 insertions(+) create mode 100644 Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/userdata/CastingImageComponent.kt create mode 100644 Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/userdata/CastingImageComponents.kt create mode 100644 Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/userdata/components/GenericIotaComponent.kt create mode 100644 Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/userdata/components/ImpulseScalingComponent.kt diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/userdata/CastingImageComponent.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/userdata/CastingImageComponent.kt new file mode 100644 index 0000000000..571e571b43 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/userdata/CastingImageComponent.kt @@ -0,0 +1,35 @@ +package at.petrak.hexcasting.api.casting.eval.vm.userdata + +import net.minecraft.nbt.CompoundTag +import net.minecraft.server.level.ServerLevel +import at.petrak.hexcasting.api.casting.eval.vm.CastingImage +import net.minecraft.resources.ResourceLocation + +/** + * A single instance of component data attached to a [CastingImage]. + * + * Components are the replacement for storing arbitrary data in [CastingImage]s. + * Instead of reaching into an untyped [CompoundTag] and constantly deserializing / reserializing, you declare a [ComponentType], + * register it, and patterns simply call [CastingImage.getComponent] or [CastingImage.withComponent]. + */ +interface CastingImageComponent + +/** + * Describes a type of component: how to serialize, deserialize, and whether components of this type are transient. + * There may only be one component of a given type per [CastingImage]. + * + * @param T The [CastingImageComponent] type this describes. + * @param id The identifier for the type, e.g. `"hexcasting:ravenmind"`. + * @param transient If `true`, components of this type are stripped by [CastingImage.removeTransientComponents]. + * Use this for per-cast state that must not bleed across spell-circle slate jumps or separate staff patterns. + * Currently used only for impulse cost accumulator. + */ +abstract class ComponentType(val id: ResourceLocation) { + open val transient: Boolean = false + abstract fun serialize(value: T): CompoundTag + abstract fun deserialize(tag: CompoundTag, world: ServerLevel): T + + fun safeDeserialize(tag: CompoundTag, world: ServerLevel): T? = runCatching { deserialize(tag, world) }.getOrNull() + override fun equals(other: Any?) = other is ComponentType<*> && other.id == id + override fun hashCode() = id.hashCode() +} \ No newline at end of file diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/userdata/CastingImageComponents.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/userdata/CastingImageComponents.kt new file mode 100644 index 0000000000..165ce51971 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/userdata/CastingImageComponents.kt @@ -0,0 +1,24 @@ +package at.petrak.hexcasting.api.casting.eval.vm.userdata + +import at.petrak.hexcasting.api.HexAPI.modLoc +import at.petrak.hexcasting.api.casting.eval.vm.userdata.components.GenericIotaComponent +import at.petrak.hexcasting.api.casting.eval.vm.userdata.components.GenericIotaComponentType +import at.petrak.hexcasting.api.casting.eval.vm.userdata.components.ImpulseScalingComponent +import at.petrak.hexcasting.api.casting.eval.vm.userdata.components.ImpulseScalingComponentType +import net.minecraft.resources.ResourceLocation + +object CastingImageComponents { + val RAVENMIND: ComponentType = register(GenericIotaComponentType(modLoc("ravenmind"))) + val IMPULSE_SCALING: ComponentType = register(ImpulseScalingComponentType) + + private val registry = HashMap>() + + fun register(type: ComponentType): ComponentType { + val existing = registry.putIfAbsent(type.id, type) + if (existing != null && existing != type) + throw AssertionError("A component type of id '${type.id}' is already registered by a different object.") + return type + } + + fun getById(id: ResourceLocation): ComponentType<*>? = registry[id] +} \ No newline at end of file diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/userdata/components/GenericIotaComponent.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/userdata/components/GenericIotaComponent.kt new file mode 100644 index 0000000000..a5f18c6689 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/userdata/components/GenericIotaComponent.kt @@ -0,0 +1,16 @@ +package at.petrak.hexcasting.api.casting.eval.vm.userdata.components + +import at.petrak.hexcasting.api.casting.eval.vm.userdata.CastingImageComponent +import at.petrak.hexcasting.api.casting.eval.vm.userdata.ComponentType +import at.petrak.hexcasting.api.casting.iota.Iota +import at.petrak.hexcasting.api.casting.iota.IotaType +import net.minecraft.nbt.CompoundTag +import net.minecraft.resources.ResourceLocation +import net.minecraft.server.level.ServerLevel + +data class GenericIotaComponent(val iota: Iota) : CastingImageComponent + +class GenericIotaComponentType(id: ResourceLocation) : ComponentType(id) { + override fun serialize(value: GenericIotaComponent): CompoundTag = IotaType.serialize(value.iota) + override fun deserialize(tag: CompoundTag, world: ServerLevel): GenericIotaComponent = GenericIotaComponent(IotaType.deserialize(tag, world)) +} \ No newline at end of file diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/userdata/components/ImpulseScalingComponent.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/userdata/components/ImpulseScalingComponent.kt new file mode 100644 index 0000000000..7a576a396a --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/userdata/components/ImpulseScalingComponent.kt @@ -0,0 +1,26 @@ +package at.petrak.hexcasting.api.casting.eval.vm.userdata.components + +import at.petrak.hexcasting.api.HexAPI.modLoc +import at.petrak.hexcasting.api.casting.eval.vm.userdata.CastingImageComponent +import at.petrak.hexcasting.api.casting.eval.vm.userdata.ComponentType +import net.minecraft.nbt.CompoundTag +import net.minecraft.server.level.ServerLevel +import java.util.UUID + +data class ImpulseScalingComponent(val map: HashMap) : CastingImageComponent + +object ImpulseScalingComponentType : ComponentType(modLoc("impulse_scaling")) { + override val transient: Boolean = true + + override fun serialize(value: ImpulseScalingComponent): CompoundTag { + val tag = CompoundTag() + value.map.forEach { (uuid, tax) -> tag.putInt(uuid.toString(), tax) } + return tag + } + + override fun deserialize(tag: CompoundTag, world: ServerLevel): ImpulseScalingComponent { + val map = HashMap() + tag.allKeys.forEach { uuid -> map[UUID.fromString(uuid)] = tag.getInt(uuid) } + return ImpulseScalingComponent(map) + } +} \ No newline at end of file From 7e42bdfb046b22f7bf0ba280670fa1c921a43de2 Mon Sep 17 00:00:00 2001 From: miyucomics Date: Sat, 7 Mar 2026 17:59:02 -0400 Subject: [PATCH 2/9] Fixed impulse component --- .../vm/components/ImpulseScalingComponent.kt | 31 +++++++++++++++++++ .../components/ImpulseScalingComponent.kt | 26 ---------------- 2 files changed, 31 insertions(+), 26 deletions(-) create mode 100644 Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/components/ImpulseScalingComponent.kt delete mode 100644 Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/userdata/components/ImpulseScalingComponent.kt diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/components/ImpulseScalingComponent.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/components/ImpulseScalingComponent.kt new file mode 100644 index 0000000000..d48569f248 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/components/ImpulseScalingComponent.kt @@ -0,0 +1,31 @@ +package at.petrak.hexcasting.api.casting.eval.vm.components + +import at.petrak.hexcasting.api.HexAPI.modLoc +import at.petrak.hexcasting.api.utils.putList +import net.minecraft.nbt.CompoundTag +import net.minecraft.nbt.ListTag +import net.minecraft.nbt.StringTag +import net.minecraft.nbt.Tag +import net.minecraft.server.level.ServerLevel +import java.util.UUID + +data class ImpulseScalingComponent(val impulsedEntities: HashSet) : CastingImageComponent + +object ImpulseScalingComponentType : ComponentType(modLoc("impulse_scaling")) { + override val transient: Boolean = true + + override fun serialize(value: ImpulseScalingComponent): CompoundTag { + val set = ListTag().apply { + value.impulsedEntities.forEach { uuid -> add(StringTag.valueOf(uuid.toString())) } + } + val tag = CompoundTag() + tag.putList("set", set) + return tag + } + + override fun deserialize(tag: CompoundTag, world: ServerLevel): ImpulseScalingComponent { + val set = HashSet() + tag.getList("set", Tag.TAG_INT.toInt()).forEach { tag -> set.add(UUID.fromString(tag.asString)) } + return ImpulseScalingComponent(set) + } +} \ No newline at end of file diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/userdata/components/ImpulseScalingComponent.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/userdata/components/ImpulseScalingComponent.kt deleted file mode 100644 index 7a576a396a..0000000000 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/userdata/components/ImpulseScalingComponent.kt +++ /dev/null @@ -1,26 +0,0 @@ -package at.petrak.hexcasting.api.casting.eval.vm.userdata.components - -import at.petrak.hexcasting.api.HexAPI.modLoc -import at.petrak.hexcasting.api.casting.eval.vm.userdata.CastingImageComponent -import at.petrak.hexcasting.api.casting.eval.vm.userdata.ComponentType -import net.minecraft.nbt.CompoundTag -import net.minecraft.server.level.ServerLevel -import java.util.UUID - -data class ImpulseScalingComponent(val map: HashMap) : CastingImageComponent - -object ImpulseScalingComponentType : ComponentType(modLoc("impulse_scaling")) { - override val transient: Boolean = true - - override fun serialize(value: ImpulseScalingComponent): CompoundTag { - val tag = CompoundTag() - value.map.forEach { (uuid, tax) -> tag.putInt(uuid.toString(), tax) } - return tag - } - - override fun deserialize(tag: CompoundTag, world: ServerLevel): ImpulseScalingComponent { - val map = HashMap() - tag.allKeys.forEach { uuid -> map[UUID.fromString(uuid)] = tag.getInt(uuid) } - return ImpulseScalingComponent(map) - } -} \ No newline at end of file From 80f46e864f9ebf0785ab46315d6de3d95ff32238 Mon Sep 17 00:00:00 2001 From: miyucomics Date: Sun, 8 Mar 2026 12:26:33 -0400 Subject: [PATCH 3/9] Integrated into CastingImage --- .../api/casting/eval/vm/CastingImage.kt | 37 ++++++++++++++----- .../api/casting/eval/vm/CastingVM.kt | 7 +++- .../CastingImageComponent.kt | 4 +- .../CastingImageComponents.kt | 6 +-- .../components/GenericIotaComponent.kt | 4 +- 5 files changed, 38 insertions(+), 20 deletions(-) rename Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/{userdata => components}/CastingImageComponent.kt (91%) rename Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/{userdata => components}/CastingImageComponents.kt (64%) rename Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/{userdata => }/components/GenericIotaComponent.kt (74%) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingImage.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingImage.kt index cf887a117b..e5bd3f3b69 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingImage.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingImage.kt @@ -3,6 +3,9 @@ package at.petrak.hexcasting.api.casting.eval.vm import at.petrak.hexcasting.api.HexAPI import at.petrak.hexcasting.api.casting.eval.vm.CastingImage.ParenthesizedIota.Companion.TAG_ESCAPED import at.petrak.hexcasting.api.casting.eval.vm.CastingImage.ParenthesizedIota.Companion.TAG_IOTAS +import at.petrak.hexcasting.api.casting.eval.vm.components.CastingImageComponent +import at.petrak.hexcasting.api.casting.eval.vm.components.CastingImageComponents +import at.petrak.hexcasting.api.casting.eval.vm.components.ComponentType import at.petrak.hexcasting.api.casting.iota.Iota import at.petrak.hexcasting.api.casting.iota.IotaType import at.petrak.hexcasting.api.casting.iota.ListIota @@ -10,6 +13,7 @@ import at.petrak.hexcasting.api.utils.* import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.ListTag import net.minecraft.nbt.Tag +import net.minecraft.resources.ResourceLocation import net.minecraft.server.level.ServerLevel import net.minecraft.world.entity.Entity @@ -24,9 +28,9 @@ data class CastingImage private constructor( val escapeNext: Boolean, val opsConsumed: Long, - val userData: CompoundTag + val components: Map, CastingImageComponent> ) { - constructor() : this(listOf(), 0, listOf(), false, 0, CompoundTag()) + constructor() : this(listOf(), 0, listOf(), false, 0, emptyMap()) data class ParenthesizedIota(val iota: Iota, val escaped: Boolean) { companion object { @@ -72,6 +76,12 @@ data class CastingImage private constructor( */ fun withResetEscape() = this.copy(parenCount = 0, parenthesized = listOf(), escapeNext = false) + @Suppress("UNCHECKED_CAST") + fun getComponent(type: ComponentType): T? = this.components[type] as? T + fun withComponent(type: ComponentType, value: T): CastingImage = copy(components = this.components + (type to value)) + fun withoutComponent(type: ComponentType): CastingImage = copy(components = this.components - type) + fun removeTransientComponents(): CastingImage = copy(components = this.components.filterKeys { !it.transient }) + fun serializeToNbt() = NBTBuilder { TAG_STACK %= stack.serializeToNBT() @@ -80,7 +90,12 @@ data class CastingImage private constructor( TAG_PARENTHESIZED %= parenthesized.serializeToNBT() TAG_OPS_CONSUMED %= opsConsumed - TAG_USERDATA %= userData + val componentsTag = CompoundTag() + for ((type, component) in components) { + val serialized = type.uncheckedSerialize(component) + componentsTag.put(type.id.toString(), serialized) + } + TAG_COMPONENTS %= componentsTag } companion object { @@ -89,7 +104,7 @@ data class CastingImage private constructor( const val TAG_PARENTHESIZED = "parenthesized" const val TAG_ESCAPE_NEXT = "escape_next" const val TAG_OPS_CONSUMED = "ops_consumed" - const val TAG_USERDATA = "userdata" + const val TAG_COMPONENTS = "components" @JvmStatic fun loadFromNbt(tag: CompoundTag, world: ServerLevel): CastingImage { @@ -101,10 +116,14 @@ data class CastingImage private constructor( stack.add(datum) } - val userData = if (tag.contains(TAG_USERDATA)) { - tag.getCompound(TAG_USERDATA) - } else { - CompoundTag() + val components = mutableMapOf, CastingImageComponent>() + if (tag.contains(TAG_COMPONENTS, Tag.TAG_COMPOUND.toInt())) { + val componentsTag = tag.getCompound(TAG_COMPONENTS) + for (id in componentsTag.allKeys) { + val type = CastingImageComponents.getById(ResourceLocation(id)) ?: continue + val value = type.safeDeserialize(componentsTag.getCompound(id), world) ?: continue + components[type] = value + } } val parenthesized = mutableListOf() @@ -120,7 +139,7 @@ data class CastingImage private constructor( val escapeNext = tag.getBoolean(TAG_ESCAPE_NEXT) val opsUsed = tag.getLong(TAG_OPS_CONSUMED) - CastingImage(stack, parenCount, parenthesized, escapeNext, opsUsed, userData) + CastingImage(stack, parenCount, parenthesized, escapeNext, opsUsed, components) } catch (exn: Exception) { HexAPI.LOGGER.warn("error while loading a CastingImage", exn) CastingImage() diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingVM.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingVM.kt index 11503b0b6f..ecfdf396b8 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingVM.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingVM.kt @@ -6,6 +6,8 @@ import at.petrak.hexcasting.api.casting.SpellList import at.petrak.hexcasting.api.casting.eval.* import at.petrak.hexcasting.api.casting.eval.sideeffects.OperatorSideEffect import at.petrak.hexcasting.api.casting.eval.vm.CastingImage.ParenthesizedIota +import at.petrak.hexcasting.api.casting.eval.vm.components.CastingImageComponents +import at.petrak.hexcasting.api.casting.eval.vm.components.ComponentType import at.petrak.hexcasting.api.casting.iota.Iota import at.petrak.hexcasting.api.casting.iota.IotaType import at.petrak.hexcasting.api.casting.iota.ListIota @@ -160,8 +162,9 @@ class CastingVM(var image: CastingImage, val env: CastingEnvironment) { fun generateDescs(): Pair, CompoundTag?> { val stackDescs = this.image.stack.map { IotaType.serialize(it) } - val ravenmind = if (this.image.userData.contains(HexAPI.RAVENMIND_USERDATA)) { - this.image.userData.getCompound(HexAPI.RAVENMIND_USERDATA) + val ravenmindComponent = this.image.getComponent(CastingImageComponents.RAVENMIND) + val ravenmind = if (ravenmindComponent != null) { + IotaType.serialize(ravenmindComponent.iota) } else null return Pair(stackDescs, ravenmind) } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/userdata/CastingImageComponent.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/components/CastingImageComponent.kt similarity index 91% rename from Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/userdata/CastingImageComponent.kt rename to Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/components/CastingImageComponent.kt index 571e571b43..311543a7ad 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/userdata/CastingImageComponent.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/components/CastingImageComponent.kt @@ -1,4 +1,4 @@ -package at.petrak.hexcasting.api.casting.eval.vm.userdata +package at.petrak.hexcasting.api.casting.eval.vm.components import net.minecraft.nbt.CompoundTag import net.minecraft.server.level.ServerLevel @@ -29,6 +29,8 @@ abstract class ComponentType(val id: ResourceLocation abstract fun serialize(value: T): CompoundTag abstract fun deserialize(tag: CompoundTag, world: ServerLevel): T + @Suppress("UNCHECKED_CAST") + fun uncheckedSerialize(value: Any): CompoundTag = serialize(value as T) fun safeDeserialize(tag: CompoundTag, world: ServerLevel): T? = runCatching { deserialize(tag, world) }.getOrNull() override fun equals(other: Any?) = other is ComponentType<*> && other.id == id override fun hashCode() = id.hashCode() diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/userdata/CastingImageComponents.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/components/CastingImageComponents.kt similarity index 64% rename from Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/userdata/CastingImageComponents.kt rename to Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/components/CastingImageComponents.kt index 165ce51971..1378334f34 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/userdata/CastingImageComponents.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/components/CastingImageComponents.kt @@ -1,10 +1,6 @@ -package at.petrak.hexcasting.api.casting.eval.vm.userdata +package at.petrak.hexcasting.api.casting.eval.vm.components import at.petrak.hexcasting.api.HexAPI.modLoc -import at.petrak.hexcasting.api.casting.eval.vm.userdata.components.GenericIotaComponent -import at.petrak.hexcasting.api.casting.eval.vm.userdata.components.GenericIotaComponentType -import at.petrak.hexcasting.api.casting.eval.vm.userdata.components.ImpulseScalingComponent -import at.petrak.hexcasting.api.casting.eval.vm.userdata.components.ImpulseScalingComponentType import net.minecraft.resources.ResourceLocation object CastingImageComponents { diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/userdata/components/GenericIotaComponent.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/components/GenericIotaComponent.kt similarity index 74% rename from Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/userdata/components/GenericIotaComponent.kt rename to Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/components/GenericIotaComponent.kt index a5f18c6689..8f0eec1247 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/userdata/components/GenericIotaComponent.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/components/GenericIotaComponent.kt @@ -1,7 +1,5 @@ -package at.petrak.hexcasting.api.casting.eval.vm.userdata.components +package at.petrak.hexcasting.api.casting.eval.vm.components -import at.petrak.hexcasting.api.casting.eval.vm.userdata.CastingImageComponent -import at.petrak.hexcasting.api.casting.eval.vm.userdata.ComponentType import at.petrak.hexcasting.api.casting.iota.Iota import at.petrak.hexcasting.api.casting.iota.IotaType import net.minecraft.nbt.CompoundTag From fe59e0be4539b8a6086730f5f3756b4a87c9218b Mon Sep 17 00:00:00 2001 From: miyucomics Date: Sun, 8 Mar 2026 12:38:45 -0400 Subject: [PATCH 4/9] Replaced old Ravenmind logic --- .../java/at/petrak/hexcasting/api/HexAPI.java | 4 ---- .../common/casting/actions/local/OpPeekLocal.kt | 16 ++++------------ .../common/casting/actions/local/OpPushLocal.kt | 11 ++++++----- 3 files changed, 10 insertions(+), 21 deletions(-) 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 8ddde5669b..ac9402594f 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/HexAPI.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/HexAPI.java @@ -213,10 +213,6 @@ default ArmorMaterial robesMaterial() { return DUMMY_ARMOR_MATERIAL; } - /** - * Location in the userdata of the ravenmind - */ - String RAVENMIND_USERDATA = modLoc("ravenmind").toString(); /** * Location in the userdata of the number of ops executed */ diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/local/OpPeekLocal.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/local/OpPeekLocal.kt index 375b5bbcc0..26de4ff6bb 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/local/OpPeekLocal.kt +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/local/OpPeekLocal.kt @@ -1,11 +1,11 @@ package at.petrak.hexcasting.common.casting.actions.local -import at.petrak.hexcasting.api.HexAPI 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.eval.vm.components.CastingImageComponents import at.petrak.hexcasting.api.casting.iota.IotaType import at.petrak.hexcasting.api.casting.iota.NullIota import at.petrak.hexcasting.common.lib.hex.HexEvalSounds @@ -13,16 +13,8 @@ import at.petrak.hexcasting.common.lib.hex.HexEvalSounds object OpPeekLocal : Action { override fun operate(env: CastingEnvironment, image: CastingImage, continuation: SpellContinuation): OperationResult { val stack = image.stack.toMutableList() - - val rm = if (image.userData.contains(HexAPI.RAVENMIND_USERDATA)) { - IotaType.deserialize(image.userData.getCompound(HexAPI.RAVENMIND_USERDATA), env.world) - } else { - NullIota() - } - stack.add(rm) - - // does not mutate userdata - val image2 = image.withUsedOp().copy(stack = stack) - return OperationResult(image2, listOf(), continuation, HexEvalSounds.NORMAL_EXECUTE) + val ravenmindComponent = image.getComponent(CastingImageComponents.RAVENMIND) + stack.add(ravenmindComponent?.iota ?: NullIota()) + return OperationResult(image.withUsedOp().copy(stack = stack), listOf(), continuation, HexEvalSounds.NORMAL_EXECUTE) } } diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/local/OpPushLocal.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/local/OpPushLocal.kt index 59c8a7e078..9e3ac104a9 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/local/OpPushLocal.kt +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/local/OpPushLocal.kt @@ -6,6 +6,8 @@ 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.eval.vm.components.CastingImageComponents +import at.petrak.hexcasting.api.casting.eval.vm.components.GenericIotaComponent import at.petrak.hexcasting.api.casting.iota.IotaType import at.petrak.hexcasting.api.casting.mishaps.MishapNotEnoughArgs import at.petrak.hexcasting.common.lib.hex.HexEvalSounds @@ -19,12 +21,11 @@ object OpPushLocal : Action { throw MishapNotEnoughArgs(1, 0) val newLocal = stack.removeLast() - if (newLocal.type == HexIotaTypes.NULL) - image.userData.remove(HexAPI.RAVENMIND_USERDATA) + val newImage = if (newLocal.type == HexIotaTypes.NULL) + image.withoutComponent(CastingImageComponents.RAVENMIND) else - image.userData.put(HexAPI.RAVENMIND_USERDATA, IotaType.serialize(newLocal)) + image.withComponent(CastingImageComponents.RAVENMIND, GenericIotaComponent(newLocal)) - val image2 = image.withUsedOp().copy(stack = stack) - return OperationResult(image2, listOf(), continuation, HexEvalSounds.NORMAL_EXECUTE) + return OperationResult(newImage.withUsedOp().copy(stack = stack), listOf(), continuation, HexEvalSounds.NORMAL_EXECUTE) } } From 5e59fba1acd33af6c5e4e702c41b5deab5332471 Mon Sep 17 00:00:00 2001 From: miyucomics Date: Sun, 8 Mar 2026 12:58:16 -0400 Subject: [PATCH 5/9] Replaced impulse's use of userData to use components --- .../java/at/petrak/hexcasting/api/HexAPI.java | 7 ------ .../api/casting/castables/Action.kt | 3 --- .../api/casting/castables/SpellAction.kt | 9 ++++--- .../api/casting/eval/CastingEnvironment.java | 8 +++---- .../api/casting/eval/vm/CastingImage.kt | 12 ---------- .../api/casting/eval/vm/CastingVM.kt | 2 +- .../directrix/BlockBooleanDirectrix.java | 2 +- .../casting/actions/spells/OpAddMotion.kt | 24 ++++++++++--------- 8 files changed, 23 insertions(+), 44 deletions(-) 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 ac9402594f..d99462fe29 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/HexAPI.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/HexAPI.java @@ -213,13 +213,6 @@ default ArmorMaterial robesMaterial() { return DUMMY_ARMOR_MATERIAL; } - /** - * Location in the userdata of the number of ops executed - */ - String OP_COUNT_USERDATA = modLoc("op_count").toString(); - - String MARKED_MOVED_USERDATA = modLoc("impulsed").toString(); - static HexAPI instance() { return INSTANCE.get(); } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/castables/Action.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/castables/Action.kt index a3963d7640..c26c2023be 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/castables/Action.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/castables/Action.kt @@ -30,9 +30,6 @@ interface Action { * remember to increment the op count, sil vous plait. * * A particle effect at the cast site and various messages and advancements are done automagically. - * - * The userdata tag is copied for you, so you don't need to worry about mutation messing up things - * behind the scenes. */ @Throws(Mishap::class) fun operate( diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/castables/SpellAction.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/castables/SpellAction.kt index 3405830d62..8f80526e8a 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/castables/SpellAction.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/castables/SpellAction.kt @@ -29,8 +29,8 @@ interface SpellAction : Action { @Throws(Mishap::class) - fun executeWithUserdata( - args: List, env: CastingEnvironment, userData: CompoundTag + fun executeWithImage( + args: List, env: CastingEnvironment, image: CastingImage ): Result { return this.execute(args, env) } @@ -44,8 +44,7 @@ interface SpellAction : Action { for (_i in 0 until this.argc) stack.removeLast() // execute! - val userDataMut = image.userData.copy() - val result = this.executeWithUserdata(args, env, userDataMut) + val result = this.executeWithImage(args, env, image.copy(stack = stack)) val sideEffects = mutableListOf() @@ -65,7 +64,7 @@ interface SpellAction : Action { for (spray in result.particles) sideEffects.add(OperatorSideEffect.Particles(spray)) - val image2 = image.copy(stack = stack, opsConsumed = image.opsConsumed + result.opCount, userData = userDataMut) + val image2 = image.copy(stack = stack, opsConsumed = image.opsConsumed + result.opCount) val sound = if (this.hasCastingSound(env)) HexEvalSounds.SPELL else HexEvalSounds.MUTE return OperationResult(image2, sideEffects, continuation, sound) 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 54e033b013..5093978f27 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 @@ -48,12 +48,12 @@ public abstract class CastingEnvironment { /** * Stores all listeners that should be notified whenever a CastingEnvironment is initialised. */ - private static final List> createEventListeners = new ArrayList<>(); + private static final List> createEventListeners = new ArrayList<>(); /** * Add a listener that will be called whenever a new CastingEnvironment is created. */ - public static void addCreateEventListener(BiConsumer listener) { + public static void addCreateEventListener(BiConsumer listener) { createEventListeners.add(listener); } @@ -71,10 +71,10 @@ public static void addCreateEventListener(Consumer listener) private boolean createEventTriggered = false; - public final void triggerCreateEvent(CompoundTag userData) { + public final void triggerCreateEvent(CastingImage image) { if (!createEventTriggered) { for (var listener : createEventListeners) - listener.accept(this, userData); + listener.accept(this, image); createEventTriggered = true; } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingImage.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingImage.kt index e5bd3f3b69..4ea7c638b6 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingImage.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingImage.kt @@ -145,17 +145,5 @@ data class CastingImage private constructor( CastingImage() } } - - @JvmStatic - fun checkAndMarkGivenMotion(userData: CompoundTag, entity: Entity): Boolean { - val marked = userData.getOrCreateCompound(HexAPI.MARKED_MOVED_USERDATA) - return if (marked.contains(entity.stringUUID)) { - true - } else { - marked.putBoolean(entity.stringUUID, true) - userData.putCompound(HexAPI.MARKED_MOVED_USERDATA, marked) - false - } - } } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingVM.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingVM.kt index ecfdf396b8..0136e96947 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingVM.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingVM.kt @@ -26,7 +26,7 @@ import net.minecraft.server.level.ServerLevel */ class CastingVM(var image: CastingImage, val env: CastingEnvironment) { init { - env.triggerCreateEvent(image.userData) + env.triggerCreateEvent(image) } /** diff --git a/Common/src/main/java/at/petrak/hexcasting/common/blocks/circles/directrix/BlockBooleanDirectrix.java b/Common/src/main/java/at/petrak/hexcasting/common/blocks/circles/directrix/BlockBooleanDirectrix.java index 6d99767bce..940cb130ec 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/blocks/circles/directrix/BlockBooleanDirectrix.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/blocks/circles/directrix/BlockBooleanDirectrix.java @@ -63,7 +63,7 @@ public ControlFlow acceptControlFlow(CastingImage imageIn, CircleCastEnv env, Di ? bs.getValue(FACING).getOpposite() : bs.getValue(FACING); var imageOut = imageIn.copy(stack, imageIn.getParenCount(), imageIn.getParenthesized(), - imageIn.getEscapeNext(), imageIn.getOpsConsumed(), imageIn.getUserData()); + imageIn.getEscapeNext(), imageIn.getOpsConsumed(), imageIn.getComponents()); return new ControlFlow.Continue(imageOut, List.of(this.exitPositionFromDirection(pos, outputDir))); } diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/OpAddMotion.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/OpAddMotion.kt index 7f45168f85..6f592e41a4 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/OpAddMotion.kt +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/OpAddMotion.kt @@ -5,11 +5,12 @@ import at.petrak.hexcasting.api.casting.RenderedSpell import at.petrak.hexcasting.api.casting.castables.SpellAction import at.petrak.hexcasting.api.casting.eval.CastingEnvironment import at.petrak.hexcasting.api.casting.eval.vm.CastingImage +import at.petrak.hexcasting.api.casting.eval.vm.components.CastingImageComponents +import at.petrak.hexcasting.api.casting.eval.vm.components.ImpulseScalingComponent import at.petrak.hexcasting.api.casting.getEntity import at.petrak.hexcasting.api.casting.getVec3 import at.petrak.hexcasting.api.casting.iota.Iota import at.petrak.hexcasting.api.misc.MediaConstants -import net.minecraft.nbt.CompoundTag import net.minecraft.world.entity.Entity import net.minecraft.world.phys.Vec3 @@ -18,27 +19,26 @@ object OpAddMotion : SpellAction { get() = 2 // for bug #387 - val MAX_MOTION: Double = 8192.0 + const val MAX_MOTION: Double = 8192.0 - override fun executeWithUserdata( - args: List, - env: CastingEnvironment, - userData: CompoundTag - ): SpellAction.Result { + override fun executeWithImage(args: List, env: CastingEnvironment, image: CastingImage): SpellAction.Result { val target = args.getEntity(0, argc) val motion = args.getVec3(1, argc) env.assertEntityInRange(target) var motionForCost = motion.lengthSqr() - if (CastingImage.checkAndMarkGivenMotion(userData, target)) + val updatedImpulseComponent = image.getComponent(CastingImageComponents.IMPULSE_SCALING) ?: ImpulseScalingComponent(HashSet()) + if (updatedImpulseComponent.impulsedEntities.contains(target.uuid)) motionForCost++ + else + updatedImpulseComponent.impulsedEntities.add(target.uuid) val shrunkMotion = if (motion.lengthSqr() > MAX_MOTION * MAX_MOTION) motion.normalize().scale(MAX_MOTION) else motion return SpellAction.Result( - Spell(target, shrunkMotion), + Spell(target, shrunkMotion, updatedImpulseComponent), (motionForCost * MediaConstants.DUST_UNIT).toLong(), listOf( ParticleSpray( @@ -55,10 +55,12 @@ object OpAddMotion : SpellAction { throw IllegalStateException() } - private data class Spell(val target: Entity, val motion: Vec3) : RenderedSpell { - override fun cast(env: CastingEnvironment) { + private data class Spell(val target: Entity, val motion: Vec3, val newImpulseComponent: ImpulseScalingComponent) : RenderedSpell { + override fun cast(env: CastingEnvironment) {} + override fun cast(env: CastingEnvironment, image: CastingImage): CastingImage { target.push(motion.x, motion.y, motion.z) target.hurtMarked = true // Whyyyyy + return image.withComponent(CastingImageComponents.IMPULSE_SCALING, newImpulseComponent) } } } From 856e98108d64dcfd2c0fe1d810d718b4500827f6 Mon Sep 17 00:00:00 2001 From: miyucomics Date: Sun, 8 Mar 2026 13:15:26 -0400 Subject: [PATCH 6/9] Registered components properly --- .../eval/vm/components/CastingImageComponents.kt | 10 ++++++++-- .../at/petrak/hexcasting/common/misc/RegisterMisc.java | 3 +++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/components/CastingImageComponents.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/components/CastingImageComponents.kt index 1378334f34..558bc3a281 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/components/CastingImageComponents.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/components/CastingImageComponents.kt @@ -4,11 +4,17 @@ import at.petrak.hexcasting.api.HexAPI.modLoc import net.minecraft.resources.ResourceLocation object CastingImageComponents { - val RAVENMIND: ComponentType = register(GenericIotaComponentType(modLoc("ravenmind"))) - val IMPULSE_SCALING: ComponentType = register(ImpulseScalingComponentType) + val RAVENMIND: ComponentType = GenericIotaComponentType(modLoc("ravenmind")) + val IMPULSE_SCALING: ComponentType = ImpulseScalingComponentType private val registry = HashMap>() + @JvmStatic + fun registerComponents() { + register(RAVENMIND) + register(IMPULSE_SCALING) + } + fun register(type: ComponentType): ComponentType { val existing = registry.putIfAbsent(type.id, type) if (existing != null && existing != type) diff --git a/Common/src/main/java/at/petrak/hexcasting/common/misc/RegisterMisc.java b/Common/src/main/java/at/petrak/hexcasting/common/misc/RegisterMisc.java index 2559748264..888c799026 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/misc/RegisterMisc.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/misc/RegisterMisc.java @@ -1,6 +1,7 @@ package at.petrak.hexcasting.common.misc; import at.petrak.hexcasting.api.HexAPI; +import at.petrak.hexcasting.api.casting.eval.vm.components.CastingImageComponents; import at.petrak.hexcasting.mixin.accessor.AccessorAbstractArrow; import at.petrak.hexcasting.mixin.accessor.AccessorVillager; import net.minecraft.server.level.ServerPlayer; @@ -33,6 +34,8 @@ public static void register() { allay.getBrain().eraseMemory(MemoryModuleType.LIKED_PLAYER); HexAPI.instance().defaultBrainsweepingBehavior().accept(allay); }); + + CastingImageComponents.registerComponents(); } private static Vec3 arrowVelocitizer(AbstractArrow arrow) { From cfcd0a4b22b31f27d2044d03c76194354edee688 Mon Sep 17 00:00:00 2001 From: miyucomics Date: Sun, 8 Mar 2026 21:55:16 -0400 Subject: [PATCH 7/9] Fixed serialization --- .../api/casting/eval/vm/components/ImpulseScalingComponent.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/components/ImpulseScalingComponent.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/components/ImpulseScalingComponent.kt index d48569f248..58f4a06377 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/components/ImpulseScalingComponent.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/components/ImpulseScalingComponent.kt @@ -25,7 +25,7 @@ object ImpulseScalingComponentType : ComponentType(modL override fun deserialize(tag: CompoundTag, world: ServerLevel): ImpulseScalingComponent { val set = HashSet() - tag.getList("set", Tag.TAG_INT.toInt()).forEach { tag -> set.add(UUID.fromString(tag.asString)) } + tag.getList("set", Tag.TAG_STRING.toInt()).forEach { tag -> set.add(UUID.fromString(tag.asString)) } return ImpulseScalingComponent(set) } } \ No newline at end of file From d8044933ce7ee41e5d55e69957ec182279f09b20 Mon Sep 17 00:00:00 2001 From: miyucomics Date: Sun, 8 Mar 2026 22:03:23 -0400 Subject: [PATCH 8/9] Remove transient components at end of `queueExecuteAndWrapIotas` --- .../java/at/petrak/hexcasting/api/casting/eval/vm/CastingVM.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingVM.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingVM.kt index 0136e96947..93078d323c 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingVM.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingVM.kt @@ -88,6 +88,7 @@ class CastingVM(var image: CastingImage, val env: CastingEnvironment) { if (lastResolutionType.success) ResolvedPatternType.EVALUATED else ResolvedPatternType.ERRORED } + this.image = this.image.removeTransientComponents() val (stackDescs, ravenmind) = generateDescs() val isStackClear = image.stack.isEmpty() && image.parenCount == 0 && !image.escapeNext && ravenmind == null From c1c6ee2d2b6657f618a8c3610f43ea823fb50877 Mon Sep 17 00:00:00 2001 From: miyucomics Date: Sun, 8 Mar 2026 22:21:13 -0400 Subject: [PATCH 9/9] Removed accidental shadowing --- .../api/casting/eval/vm/components/ImpulseScalingComponent.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/components/ImpulseScalingComponent.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/components/ImpulseScalingComponent.kt index 58f4a06377..bbcbfaadf4 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/components/ImpulseScalingComponent.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/components/ImpulseScalingComponent.kt @@ -25,7 +25,7 @@ object ImpulseScalingComponentType : ComponentType(modL override fun deserialize(tag: CompoundTag, world: ServerLevel): ImpulseScalingComponent { val set = HashSet() - tag.getList("set", Tag.TAG_STRING.toInt()).forEach { tag -> set.add(UUID.fromString(tag.asString)) } + tag.getList("set", Tag.TAG_STRING.toInt()).forEach { uuid -> set.add(UUID.fromString(uuid.asString)) } return ImpulseScalingComponent(set) } -} \ No newline at end of file +}