Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 0 additions & 11 deletions Common/src/main/java/at/petrak/hexcasting/api/HexAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -213,17 +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
*/
String OP_COUNT_USERDATA = modLoc("op_count").toString();

String MARKED_MOVED_USERDATA = modLoc("impulsed").toString();

static HexAPI instance() {
return INSTANCE.get();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ interface SpellAction : Action {

@Throws(Mishap::class)

fun executeWithUserdata(
args: List<Iota>, env: CastingEnvironment, userData: CompoundTag
fun executeWithImage(
args: List<Iota>, env: CastingEnvironment, image: CastingImage
): Result {
return this.execute(args, env)
}
Expand All @@ -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<OperatorSideEffect>()

Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,12 @@ public abstract class CastingEnvironment {
/**
* Stores all listeners that should be notified whenever a CastingEnvironment is initialised.
*/
private static final List<BiConsumer<CastingEnvironment, CompoundTag>> createEventListeners = new ArrayList<>();
private static final List<BiConsumer<CastingEnvironment, CastingImage>> createEventListeners = new ArrayList<>();

/**
* Add a listener that will be called whenever a new CastingEnvironment is created.
*/
public static void addCreateEventListener(BiConsumer<CastingEnvironment, CompoundTag> listener) {
public static void addCreateEventListener(BiConsumer<CastingEnvironment, CastingImage> listener) {
createEventListeners.add(listener);
}

Expand All @@ -71,10 +71,10 @@ public static void addCreateEventListener(Consumer<CastingEnvironment> 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;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@ 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
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

Expand All @@ -24,9 +28,9 @@ data class CastingImage private constructor(
val escapeNext: Boolean,
val opsConsumed: Long,

val userData: CompoundTag
val components: Map<ComponentType<*>, 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 {
Expand Down Expand Up @@ -72,6 +76,12 @@ data class CastingImage private constructor(
*/
fun withResetEscape() = this.copy(parenCount = 0, parenthesized = listOf(), escapeNext = false)

@Suppress("UNCHECKED_CAST")
fun <T : CastingImageComponent> getComponent(type: ComponentType<T>): T? = this.components[type] as? T
fun <T : CastingImageComponent> withComponent(type: ComponentType<T>, value: T): CastingImage = copy(components = this.components + (type to value))
fun <T : CastingImageComponent> withoutComponent(type: ComponentType<T>): CastingImage = copy(components = this.components - type)
fun removeTransientComponents(): CastingImage = copy(components = this.components.filterKeys { !it.transient })

fun serializeToNbt() = NBTBuilder {
TAG_STACK %= stack.serializeToNBT()

Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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<ComponentType<*>, 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<ParenthesizedIota>()
Expand All @@ -120,23 +139,11 @@ 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()
}
}

@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
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -24,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)
}

/**
Expand Down Expand Up @@ -86,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
Expand Down Expand Up @@ -160,8 +163,9 @@ class CastingVM(var image: CastingImage, val env: CastingEnvironment) {

fun generateDescs(): Pair<List<CompoundTag>, 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)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package at.petrak.hexcasting.api.casting.eval.vm.components

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<T : CastingImageComponent>(val id: ResourceLocation) {
open val transient: Boolean = false
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()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package at.petrak.hexcasting.api.casting.eval.vm.components

import at.petrak.hexcasting.api.HexAPI.modLoc
import net.minecraft.resources.ResourceLocation

object CastingImageComponents {
val RAVENMIND: ComponentType<GenericIotaComponent> = GenericIotaComponentType(modLoc("ravenmind"))
val IMPULSE_SCALING: ComponentType<ImpulseScalingComponent> = ImpulseScalingComponentType

private val registry = HashMap<ResourceLocation, ComponentType<*>>()

@JvmStatic
fun registerComponents() {
register(RAVENMIND)
register(IMPULSE_SCALING)
}

fun <T : CastingImageComponent> register(type: ComponentType<T>): ComponentType<T> {
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]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package at.petrak.hexcasting.api.casting.eval.vm.components

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<GenericIotaComponent>(id) {
override fun serialize(value: GenericIotaComponent): CompoundTag = IotaType.serialize(value.iota)
override fun deserialize(tag: CompoundTag, world: ServerLevel): GenericIotaComponent = GenericIotaComponent(IotaType.deserialize(tag, world))
}
Original file line number Diff line number Diff line change
@@ -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<UUID>) : CastingImageComponent

object ImpulseScalingComponentType : ComponentType<ImpulseScalingComponent>(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<UUID>()
tag.getList("set", Tag.TAG_STRING.toInt()).forEach { uuid -> set.add(UUID.fromString(uuid.asString)) }
return ImpulseScalingComponent(set)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,20 @@
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

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)
}
}
Loading
Loading