finish sound cleanup and also other things cleanup

This commit is contained in:
petrak@ 2022-11-21 19:48:25 -06:00
parent 7699a5e468
commit 76006d72dc
34 changed files with 397 additions and 304 deletions

View file

@ -2,7 +2,7 @@ package at.petrak.hexcasting.api.spell
import at.petrak.hexcasting.api.PatternRegistry
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.spell.casting.SpellContinuation
import at.petrak.hexcasting.api.spell.casting.eval.SpellContinuation
import at.petrak.hexcasting.api.spell.iota.Iota
import at.petrak.hexcasting.api.utils.asTranslatedComponent
import at.petrak.hexcasting.api.utils.lightPurple

View file

@ -1,7 +1,7 @@
package at.petrak.hexcasting.api.spell
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.spell.casting.SpellContinuation
import at.petrak.hexcasting.api.spell.casting.eval.SpellContinuation
import at.petrak.hexcasting.api.spell.casting.sideeffects.OperatorSideEffect
import at.petrak.hexcasting.api.spell.iota.Iota
import at.petrak.hexcasting.api.spell.mishaps.MishapNotEnoughArgs

View file

@ -1,6 +1,6 @@
package at.petrak.hexcasting.api.spell
import at.petrak.hexcasting.api.spell.casting.SpellContinuation
import at.petrak.hexcasting.api.spell.casting.eval.SpellContinuation
import at.petrak.hexcasting.api.spell.casting.sideeffects.OperatorSideEffect
import at.petrak.hexcasting.api.spell.iota.Iota

View file

@ -1,7 +1,7 @@
package at.petrak.hexcasting.api.spell
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.spell.casting.SpellContinuation
import at.petrak.hexcasting.api.spell.casting.eval.SpellContinuation
import at.petrak.hexcasting.api.spell.casting.sideeffects.OperatorSideEffect
import at.petrak.hexcasting.api.spell.iota.Iota
import at.petrak.hexcasting.api.spell.mishaps.MishapNotEnoughArgs

View file

@ -12,6 +12,11 @@ import at.petrak.hexcasting.api.mod.HexStatistics
import at.petrak.hexcasting.api.spell.Action
import at.petrak.hexcasting.api.spell.ParticleSpray
import at.petrak.hexcasting.api.spell.SpellList
import at.petrak.hexcasting.api.spell.casting.eval.ContinuationFrame
import at.petrak.hexcasting.api.spell.casting.eval.FrameEvaluate
import at.petrak.hexcasting.api.spell.casting.eval.FunctionalData
import at.petrak.hexcasting.api.spell.casting.eval.SpellContinuation
import at.petrak.hexcasting.api.spell.casting.sideeffects.EvalSound
import at.petrak.hexcasting.api.spell.casting.sideeffects.OperatorSideEffect
import at.petrak.hexcasting.api.spell.iota.Iota
import at.petrak.hexcasting.api.spell.iota.ListIota
@ -20,6 +25,7 @@ import at.petrak.hexcasting.api.spell.math.HexDir
import at.petrak.hexcasting.api.spell.math.HexPattern
import at.petrak.hexcasting.api.spell.mishaps.*
import at.petrak.hexcasting.api.utils.*
import at.petrak.hexcasting.common.lib.hex.HexEvalSounds
import at.petrak.hexcasting.common.lib.hex.HexIotaTypes
import at.petrak.hexcasting.xplat.IXplatAbstractions
import net.minecraft.ChatFormatting
@ -76,13 +82,13 @@ class CastingHarness private constructor(
}
private fun getPatternForFrame(frame: ContinuationFrame): HexPattern? {
if (frame !is ContinuationFrame.Evaluate) return null
if (frame !is FrameEvaluate) return null
return (frame.list.car as? PatternIota)?.pattern
}
private fun getOperatorForFrame(frame: ContinuationFrame, world: ServerLevel): Action? {
if (frame !is ContinuationFrame.Evaluate) return null
if (frame !is FrameEvaluate) return null
return getOperatorForPattern(frame.list.car, world)
}
@ -92,10 +98,11 @@ class CastingHarness private constructor(
*/
fun executeIotas(iotas: List<Iota>, world: ServerLevel): ControllerInfo {
// Initialize the continuation stack to a single top-level eval for all iotas.
var continuation = SpellContinuation.Done.pushFrame(ContinuationFrame.Evaluate(SpellList.LList(0, 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
@ -115,7 +122,7 @@ class CastingHarness private constructor(
Mishap.Context(pattern ?: HexPattern(HexDir.WEST), operator)
)
),
EvalSound.MISHAP,
HexEvalSounds.MISHAP,
)
}
// Then write all pertinent data back to the harness for the next iteration.
@ -124,8 +131,22 @@ class CastingHarness private constructor(
}
continuation = result.continuation
lastResolutionType = result.resolutionType
performSideEffects(info, result.sideEffects, result.sound)
performSideEffects(info, result.sideEffects)
info.earlyExit = info.earlyExit || !lastResolutionType.success
sound = if (result.sound == HexEvalSounds.MISHAP) {
HexEvalSounds.MISHAP
} else {
sound.greaterOf(result.sound)
}
}
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)
}
if (continuation is SpellContinuation.NotDone) {
@ -147,14 +168,15 @@ class CastingHarness private constructor(
fun getUpdate(iota: Iota, world: ServerLevel, continuation: SpellContinuation): CastResult {
try {
// TODO we can have a special intro/retro sound
this.handleParentheses(iota)?.let { (data, resolutionType) ->
return@getUpdate CastResult(continuation, data, resolutionType, listOf(), EvalSound.GENERIC)
return@getUpdate CastResult(continuation, data, resolutionType, listOf(), HexEvalSounds.OPERATOR)
}
return if (iota is PatternIota) {
updateWithPattern(iota.pattern, world, continuation)
if (iota is PatternIota) {
return updateWithPattern(iota.pattern, world, continuation)
} else {
CastResult(
return CastResult(
continuation,
null,
ResolvedPatternType.INVALID, // Should never matter
@ -164,7 +186,7 @@ class CastingHarness private constructor(
Mishap.Context(HexPattern(HexDir.WEST), null)
)
),
EvalSound.MISHAP
HexEvalSounds.MISHAP
)
}
} catch (mishap: Mishap) {
@ -181,7 +203,7 @@ class CastingHarness private constructor(
)
)
),
EvalSound.MISHAP
HexEvalSounds.MISHAP
)
} catch (exception: Exception) {
// This means something very bad has happened
@ -199,7 +221,7 @@ class CastingHarness private constructor(
)
)
),
EvalSound.MISHAP
HexEvalSounds.MISHAP
)
}
}
@ -273,20 +295,22 @@ class CastingHarness private constructor(
hereFd
}
var soundType =
if (this.ctx.source == CastingContext.CastSource.STAFF) EvalSound.GENERIC else EvalSound.NONE;
var soundType = if (this.ctx.source == CastingContext.CastSource.STAFF) {
HexEvalSounds.OPERATOR
} else {
HexEvalSounds.NOTHING
}
for (se in sideEffects) {
if (se is OperatorSideEffect.AttemptSpell) {
if (se.hasCastingSound) {
soundType = soundType.greaterOf(EvalSound.SPELL_BOINK)
soundType = if (se.hasCastingSound) {
soundType.greaterOf(HexEvalSounds.SPELL)
} else {
// WITH CATLIKE TREAD
// UPON OUR PREY WE STEAL
soundType = EvalSound.NONE
break
HexEvalSounds.NOTHING
}
} else if (se is OperatorSideEffect.DoMishap) {
soundType = EvalSound.MISHAP
soundType = HexEvalSounds.MISHAP
}
}
return CastResult(
@ -303,7 +327,7 @@ class CastingHarness private constructor(
null,
mishap.resolutionType(ctx),
listOf(OperatorSideEffect.DoMishap(mishap, Mishap.Context(newPat, actionIdPair?.first))),
EvalSound.MISHAP
HexEvalSounds.MISHAP
)
}
}
@ -311,7 +335,7 @@ class CastingHarness private constructor(
/**
* Execute the side effects of a pattern, updating our aggregated info.
*/
fun performSideEffects(info: TempControllerInfo, sideEffects: List<OperatorSideEffect>, sound: EvalSound) {
fun performSideEffects(info: TempControllerInfo, sideEffects: List<OperatorSideEffect>) {
for (haskellProgrammersShakingandCryingRN in sideEffects) {
val mustStop = haskellProgrammersShakingandCryingRN.performEffect(this)
if (mustStop) {
@ -319,14 +343,6 @@ class CastingHarness private constructor(
break
}
}
sound.soundEvent()?.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)
}
}
fun generateDescs() = Triple(

View file

@ -1,216 +0,0 @@
package at.petrak.hexcasting.api.spell.casting
import at.petrak.hexcasting.api.spell.SpellList
import at.petrak.hexcasting.api.spell.casting.CastingHarness.CastResult
import at.petrak.hexcasting.api.spell.iota.Iota
import at.petrak.hexcasting.api.spell.iota.ListIota
import at.petrak.hexcasting.api.utils.NBTBuilder
import at.petrak.hexcasting.api.utils.getList
import at.petrak.hexcasting.api.utils.hasList
import at.petrak.hexcasting.api.utils.serializeToNBT
import at.petrak.hexcasting.common.lib.hex.HexIotaTypes
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.Tag
import net.minecraft.server.level.ServerLevel
// TODO this should probably be a registry too
/**
* A single frame of evaluation during the execution of a spell.
*
* Specifically, an evaluation will keep a stack of these frames.
* An evaluation with no meta-eval will consist of a single [Evaluate(rest of the pats)] at all times.
* When an Eval is invoked, we push Evaluate(pats) to the top of the stack.
*
* Evaluation is performed by repeatedly popping the top-most (i.e. innermost) frame from the stack,
* then evaluating that frame (and possibly allowing it to push other frames (e.g. if it's a Hermes)).
*
* Once the stack of frames is empty, there are no more computations to run, so we're done.
*/
sealed interface ContinuationFrame {
/**
* Step the evaluation forward once.
* 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
/**
* The OpHalt instruction wants us to "jump to" the END of the nearest meta-eval.
* In other words, we should consume Evaluate frames until we hit a FinishEval or Thoth frame.
* @return whether the break should stop here, alongside the new stack state (e.g. for finalizing a Thoth)
*/
fun breakDownwards(stack: List<Iota>): Pair<Boolean, List<Iota>>
/**
* Serializes this frame. Used for things like delays, where we pause execution.
*/
fun serializeToNBT(): CompoundTag
/**
* A list of patterns to be evaluated in sequence.
* @property list the *remaining* list of patterns to be evaluated
*/
data class Evaluate(val list: SpellList) : ContinuationFrame {
// Discard this frame and keep discarding frames.
override fun breakDownwards(stack: List<Iota>) = false to stack
// Step the list of patterns, evaluating a single one.
override fun evaluate(
continuation: SpellContinuation,
level: ServerLevel,
harness: CastingHarness
): CastResult {
// If there are patterns left...
return if (list.nonEmpty) {
val newCont = if (list.cdr.nonEmpty) { // yay TCO
// ...enqueue the evaluation of the rest of the patterns...
continuation.pushFrame(Evaluate(list.cdr))
} else continuation
// ...before evaluating the first one in the list.
harness.getUpdate(list.car, level, newCont)
} else {
// If there are no patterns (e.g. empty Hermes), just return OK.
CastResult(continuation, null, ResolvedPatternType.EVALUATED, listOf(), EvalSound.NONE)
}
}
override fun serializeToNBT() = NBTBuilder {
"type" %= "evaluate"
"patterns" %= list.serializeToNBT()
}
}
/**
* A stack marker representing the end of a Hermes evaluation,
* so that we know when to stop removing frames during a Halt.
*/
object FinishEval : ContinuationFrame {
// Don't do anything else to the stack, just finish the halt statement.
override fun breakDownwards(stack: List<Iota>) = true to stack
// Evaluating it does nothing; it's only a boundary condition.
override fun evaluate(
continuation: SpellContinuation,
level: ServerLevel,
harness: CastingHarness
): CastResult {
return CastResult(
continuation,
FunctionalData(harness.stack.toList(), 0, listOf(), false, harness.ravenmind),
ResolvedPatternType.EVALUATED,
listOf(),
EvalSound.NONE,
)
}
override fun serializeToNBT() = NBTBuilder {
"type" %= "end"
}
}
/**
* A frame representing all the state for a Thoth evaluation.
* Pushed by an OpForEach.
* @property first whether the input stack state is the first one (since we don't want to save the base-stack before any changes are made)
* @property data list of *remaining* datums to ForEach over
* @property code code to run per datum
* @property baseStack the stack state at Thoth entry
* @property acc concatenated list of final stack states after Thoth exit
*/
data class ForEach(
val data: SpellList,
val code: SpellList,
val baseStack: List<Iota>?,
val acc: MutableList<Iota>
) : ContinuationFrame {
/** When halting, we add the stack state at halt to the stack accumulator, then return the original pre-Thoth stack, plus the accumulator. */
override fun breakDownwards(stack: List<Iota>): Pair<Boolean, List<Iota>> {
val newStack = baseStack?.toMutableList() ?: mutableListOf()
acc.addAll(stack)
newStack.add(ListIota(acc))
return true to newStack
}
/** Step the Thoth computation, enqueueing one code evaluation. */
override fun evaluate(
continuation: SpellContinuation,
level: ServerLevel,
harness: CastingHarness
): CastResult {
// If this isn't the very first Thoth step (i.e. no Thoth computations run yet)...
val stack = if (baseStack == null) {
// init stack to the harness stack...
harness.stack.toList()
} else {
// else save the stack to the accumulator and reuse the saved base stack.
acc.addAll(harness.stack)
baseStack
}
// If we still have data to process...
val (stackTop, newCont) = if (data.nonEmpty) {
// Increment the evaluation depth,
harness.ctx.incDepth()
// push the next datum to the top of the stack,
data.car to continuation
// put the next Thoth object back on the stack for the next Thoth cycle,
.pushFrame(ForEach(data.cdr, code, stack, acc))
// and prep the Thoth'd code block for evaluation.
.pushFrame(Evaluate(code))
} else {
// Else, dump our final list onto the stack.
ListIota(acc) to continuation
}
val tStack = stack.toMutableList()
tStack.add(stackTop)
// TODO: this means we could have Thoth casting do a different sound
return CastResult(
newCont,
FunctionalData(tStack, 0, listOf(), false, harness.ravenmind),
ResolvedPatternType.EVALUATED,
listOf(),
EvalSound.NONE,
)
}
override fun serializeToNBT() = NBTBuilder {
"type" %= "foreach"
"data" %= data.serializeToNBT()
"code" %= code.serializeToNBT()
if (baseStack != null)
"base" %= baseStack.serializeToNBT()
"accumulator" %= acc.serializeToNBT()
}
}
companion object {
@JvmStatic
fun fromNBT(tag: CompoundTag, world: ServerLevel): ContinuationFrame {
return when (tag.getString("type")) {
"eval" -> Evaluate(
HexIotaTypes.LIST.deserialize(
tag.getList("patterns", Tag.TAG_COMPOUND),
world
)!!.list
)
"end" -> FinishEval
"foreach" -> ForEach(
HexIotaTypes.LIST.deserialize(tag.getList("data", Tag.TAG_COMPOUND), world)!!.list,
HexIotaTypes.LIST.deserialize(tag.getList("code", Tag.TAG_COMPOUND), world)!!.list,
if (tag.hasList("base", Tag.TAG_COMPOUND))
HexIotaTypes.LIST.deserialize(tag.getList("base", Tag.TAG_COMPOUND), world)!!.list.toList()
else
null,
HexIotaTypes.LIST.deserialize(
tag.getList("accumulator", Tag.TAG_COMPOUND),
world
)!!.list.toMutableList()
)
else -> Evaluate(SpellList.LList(0, listOf()))
}
}
}
}

View file

@ -0,0 +1,78 @@
package at.petrak.hexcasting.api.spell.casting.eval
import at.petrak.hexcasting.api.spell.SpellList
import at.petrak.hexcasting.api.spell.casting.CastingHarness
import at.petrak.hexcasting.api.spell.casting.CastingHarness.CastResult
import at.petrak.hexcasting.api.spell.iota.Iota
import at.petrak.hexcasting.api.utils.getList
import at.petrak.hexcasting.api.utils.hasList
import at.petrak.hexcasting.common.lib.hex.HexIotaTypes
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.Tag
import net.minecraft.server.level.ServerLevel
// TODO this should probably be a registry too
/**
* A single frame of evaluation during the execution of a spell.
*
* Specifically, an evaluation will keep a stack of these frames.
* An evaluation with no meta-eval will consist of a single [Evaluate(rest of the pats)] at all times.
* When an Eval is invoked, we push Evaluate(pats) to the top of the stack.
*
* Evaluation is performed by repeatedly popping the top-most (i.e. innermost) frame from the stack,
* then evaluating that frame (and possibly allowing it to push other frames (e.g. if it's a Hermes)).
*
* Once the stack of frames is empty, there are no more computations to run, so we're done.
*
*/
sealed interface ContinuationFrame {
/**
* Step the evaluation forward once.
* 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
/**
* The OpHalt instruction wants us to "jump to" the END of the nearest meta-eval.
* In other words, we should consume Evaluate frames until we hit a FinishEval or Thoth frame.
* @return whether the break should stop here, alongside the new stack state (e.g. for finalizing a Thoth)
*/
fun breakDownwards(stack: List<Iota>): Pair<Boolean, List<Iota>>
/**
* Serializes this frame. Used for things like delays, where we pause execution.
*/
fun serializeToNBT(): CompoundTag
companion object {
@JvmStatic
fun fromNBT(tag: CompoundTag, world: ServerLevel): ContinuationFrame {
return when (tag.getString("type")) {
"eval" -> FrameEvaluate(
HexIotaTypes.LIST.deserialize(
tag.getList("patterns", Tag.TAG_COMPOUND),
world
)!!.list,
tag.getBoolean("isMetacasting")
)
"end" -> FrameFinishEval
"foreach" -> FrameForEach(
HexIotaTypes.LIST.deserialize(tag.getList("data", Tag.TAG_COMPOUND), world)!!.list,
HexIotaTypes.LIST.deserialize(tag.getList("code", Tag.TAG_COMPOUND), world)!!.list,
if (tag.hasList("base", Tag.TAG_COMPOUND))
HexIotaTypes.LIST.deserialize(tag.getList("base", Tag.TAG_COMPOUND), world)!!.list.toList()
else
null,
HexIotaTypes.LIST.deserialize(
tag.getList("accumulator", Tag.TAG_COMPOUND),
world
)!!.list.toMutableList()
)
else -> FrameEvaluate(SpellList.LList(0, listOf()), false)
}
}
}
}

View file

@ -0,0 +1,51 @@
package at.petrak.hexcasting.api.spell.casting.eval
import at.petrak.hexcasting.api.spell.SpellList
import at.petrak.hexcasting.api.spell.casting.CastingHarness
import at.petrak.hexcasting.api.spell.casting.ResolvedPatternType
import at.petrak.hexcasting.api.spell.iota.Iota
import at.petrak.hexcasting.api.utils.NBTBuilder
import at.petrak.hexcasting.api.utils.serializeToNBT
import at.petrak.hexcasting.common.lib.hex.HexEvalSounds
import net.minecraft.server.level.ServerLevel
/**
* A list of patterns to be evaluated in sequence.
* @property list the *remaining* list of patterns to be evaluated
* @property isMetacasting only for sound effects, if this is being cast from a hermes / iris
*/
data class FrameEvaluate(val list: SpellList, val isMetacasting: Boolean) : ContinuationFrame {
// Discard this frame and keep discarding frames.
override fun breakDownwards(stack: List<Iota>) = false to stack
// Step the list of patterns, evaluating a single one.
override fun evaluate(
continuation: SpellContinuation,
level: ServerLevel,
harness: CastingHarness
): CastingHarness.CastResult {
// If there are patterns left...
return if (list.nonEmpty) {
val newCont = if (list.cdr.nonEmpty) { // yay TCO
// ...enqueue the evaluation of the rest of the patterns...
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)
if (this.isMetacasting && update.sound != HexEvalSounds.MISHAP) {
update.copy(sound = HexEvalSounds.HERMES)
} else {
update
}
} else {
// If there are no patterns (e.g. empty Hermes), just return OK.
CastingHarness.CastResult(continuation, null, ResolvedPatternType.EVALUATED, listOf(), HexEvalSounds.HERMES)
}
}
override fun serializeToNBT() = NBTBuilder {
"type" %= "evaluate"
"patterns" %= list.serializeToNBT()
"isMetacasting" %= isMetacasting
}
}

View file

@ -0,0 +1,36 @@
package at.petrak.hexcasting.api.spell.casting.eval
import at.petrak.hexcasting.api.spell.casting.CastingHarness
import at.petrak.hexcasting.api.spell.casting.ResolvedPatternType
import at.petrak.hexcasting.api.spell.iota.Iota
import at.petrak.hexcasting.api.utils.NBTBuilder
import at.petrak.hexcasting.common.lib.hex.HexEvalSounds
import net.minecraft.server.level.ServerLevel
/**
* A stack marker representing the end of a Hermes evaluation,
* so that we know when to stop removing frames during a Halt.
*/
object FrameFinishEval : ContinuationFrame {
// Don't do anything else to the stack, just finish the halt statement.
override fun breakDownwards(stack: List<Iota>) = true to stack
// Evaluating it does nothing; it's only a boundary condition.
override fun evaluate(
continuation: SpellContinuation,
level: ServerLevel,
harness: CastingHarness
): CastingHarness.CastResult {
return CastingHarness.CastResult(
continuation,
FunctionalData(harness.stack.toList(), 0, listOf(), false, harness.ravenmind),
ResolvedPatternType.EVALUATED,
listOf(),
HexEvalSounds.NOTHING,
)
}
override fun serializeToNBT() = NBTBuilder {
"type" %= "end"
}
}

View file

@ -0,0 +1,87 @@
package at.petrak.hexcasting.api.spell.casting.eval
import at.petrak.hexcasting.api.spell.SpellList
import at.petrak.hexcasting.api.spell.casting.CastingHarness
import at.petrak.hexcasting.api.spell.casting.ResolvedPatternType
import at.petrak.hexcasting.api.spell.iota.Iota
import at.petrak.hexcasting.api.spell.iota.ListIota
import at.petrak.hexcasting.api.utils.NBTBuilder
import at.petrak.hexcasting.api.utils.serializeToNBT
import at.petrak.hexcasting.common.lib.hex.HexEvalSounds
import net.minecraft.server.level.ServerLevel
/**
* A frame representing all the state for a Thoth evaluation.
* Pushed by an OpForEach.
* @property first whether the input stack state is the first one (since we don't want to save the base-stack before any changes are made)
* @property data list of *remaining* datums to ForEach over
* @property code code to run per datum
* @property baseStack the stack state at Thoth entry
* @property acc concatenated list of final stack states after Thoth exit
*/
data class FrameForEach(
val data: SpellList,
val code: SpellList,
val baseStack: List<Iota>?,
val acc: MutableList<Iota>
) : ContinuationFrame {
/** When halting, we add the stack state at halt to the stack accumulator, then return the original pre-Thoth stack, plus the accumulator. */
override fun breakDownwards(stack: List<Iota>): Pair<Boolean, List<Iota>> {
val newStack = baseStack?.toMutableList() ?: mutableListOf()
acc.addAll(stack)
newStack.add(ListIota(acc))
return true to newStack
}
/** Step the Thoth computation, enqueueing one code evaluation. */
override fun evaluate(
continuation: SpellContinuation,
level: ServerLevel,
harness: CastingHarness
): CastingHarness.CastResult {
// If this isn't the very first Thoth step (i.e. no Thoth computations run yet)...
val stack = if (baseStack == null) {
// init stack to the harness stack...
harness.stack.toList()
} else {
// else save the stack to the accumulator and reuse the saved base stack.
acc.addAll(harness.stack)
baseStack
}
// If we still have data to process...
val (stackTop, newCont) = if (data.nonEmpty) {
// Increment the evaluation depth,
harness.ctx.incDepth()
// push the next datum to the top of the stack,
data.car to continuation
// put the next Thoth object back on the stack for the next Thoth cycle,
.pushFrame(FrameForEach(data.cdr, code, stack, acc))
// and prep the Thoth'd code block for evaluation.
.pushFrame(FrameEvaluate(code, true))
} else {
// Else, dump our final list onto the stack.
ListIota(acc) to continuation
}
val tStack = stack.toMutableList()
tStack.add(stackTop)
// TODO: this means we could have Thoth casting do a different sound
return CastingHarness.CastResult(
newCont,
FunctionalData(tStack, 0, listOf(), false, harness.ravenmind),
ResolvedPatternType.EVALUATED,
listOf(),
HexEvalSounds.THOTH,
)
}
override fun serializeToNBT() = NBTBuilder {
"type" %= "foreach"
"data" %= data.serializeToNBT()
"code" %= code.serializeToNBT()
if (baseStack != null)
"base" %= baseStack.serializeToNBT()
"accumulator" %= acc.serializeToNBT()
}
}

View file

@ -1,4 +1,4 @@
package at.petrak.hexcasting.api.spell.casting
package at.petrak.hexcasting.api.spell.casting.eval
import at.petrak.hexcasting.api.spell.iota.Iota

View file

@ -1,4 +1,4 @@
package at.petrak.hexcasting.api.spell.casting
package at.petrak.hexcasting.api.spell.casting.eval
/**
* A continuation during the execution of a spell.

View file

@ -12,4 +12,7 @@ import org.jetbrains.annotations.Nullable;
* shortcutMetacasting takes precedence over this.
*/
public record EvalSound(@Nullable SoundEvent sound, int priority) {
public EvalSound greaterOf(EvalSound that) {
return (this.priority > that.priority) ? this : that;
}
}

View file

@ -1,10 +0,0 @@
package at.petrak.hexcasting.api.spell.casting.sideeffects
/**
* Package for all the side effects that happen during one cast.
*
* This lives outside of nested evaluations so we don't get giant sound spam
*/
class SideEffectsTracker private constructor(private val ops: MutableList<OperatorSideEffect>, private var sound: EvalSound) {
public constructor() : this(mutableListOf(), EvalSound.NONE)
}

View file

@ -4,8 +4,9 @@ import at.petrak.hexcasting.api.spell.Action
import at.petrak.hexcasting.api.spell.OperationResult
import at.petrak.hexcasting.api.spell.SpellList
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.spell.casting.ContinuationFrame
import at.petrak.hexcasting.api.spell.casting.SpellContinuation
import at.petrak.hexcasting.api.spell.casting.eval.FrameEvaluate
import at.petrak.hexcasting.api.spell.casting.eval.FrameFinishEval
import at.petrak.hexcasting.api.spell.casting.eval.SpellContinuation
import at.petrak.hexcasting.api.spell.evaluatable
import at.petrak.hexcasting.api.spell.iota.Iota
import at.petrak.hexcasting.api.spell.iota.PatternIota
@ -27,14 +28,14 @@ object OpEval : Action {
// if not installed already...
// also, never make a break boundary when evaluating just one pattern
val newCont =
if (instrs.left().isPresent || (continuation is SpellContinuation.NotDone && continuation.frame is ContinuationFrame.FinishEval)) {
if (instrs.left().isPresent || (continuation is SpellContinuation.NotDone && continuation.frame is FrameFinishEval)) {
continuation
} else {
continuation.pushFrame(ContinuationFrame.FinishEval) // install a break-boundary after eval
continuation.pushFrame(FrameFinishEval) // install a break-boundary after eval
}
val instrsList = instrs.map({ SpellList.LList(0, listOf(PatternIota(it))) }, { it })
val frame = ContinuationFrame.Evaluate(instrsList)
val frame = FrameEvaluate(instrsList, true)
return OperationResult(newCont.pushFrame(frame), stack, ravenmind, listOf())
}
}

View file

@ -3,7 +3,7 @@ package at.petrak.hexcasting.common.casting.operators.eval
import at.petrak.hexcasting.api.spell.Action
import at.petrak.hexcasting.api.spell.OperationResult
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.spell.casting.SpellContinuation
import at.petrak.hexcasting.api.spell.casting.eval.SpellContinuation
import at.petrak.hexcasting.api.spell.iota.Iota
object OpEvalDelay : Action {

View file

@ -3,8 +3,8 @@ package at.petrak.hexcasting.common.casting.operators.eval
import at.petrak.hexcasting.api.spell.Action
import at.petrak.hexcasting.api.spell.OperationResult
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.spell.casting.ContinuationFrame
import at.petrak.hexcasting.api.spell.casting.SpellContinuation
import at.petrak.hexcasting.api.spell.casting.eval.FrameForEach
import at.petrak.hexcasting.api.spell.casting.eval.SpellContinuation
import at.petrak.hexcasting.api.spell.getList
import at.petrak.hexcasting.api.spell.iota.Iota
import at.petrak.hexcasting.api.spell.mishaps.MishapNotEnoughArgs
@ -24,7 +24,7 @@ object OpForEach : Action {
stack.removeLastOrNull()
stack.removeLastOrNull()
val frame = ContinuationFrame.ForEach(datums, instrs, null, mutableListOf())
val frame = FrameForEach(datums, instrs, null, mutableListOf())
return OperationResult(
continuation.pushFrame(frame),

View file

@ -3,7 +3,7 @@ package at.petrak.hexcasting.common.casting.operators.eval
import at.petrak.hexcasting.api.spell.Action
import at.petrak.hexcasting.api.spell.OperationResult
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.spell.casting.SpellContinuation
import at.petrak.hexcasting.api.spell.casting.eval.SpellContinuation
import at.petrak.hexcasting.api.spell.iota.Iota
object OpHalt : Action {

View file

@ -4,7 +4,7 @@ import at.petrak.hexcasting.api.spell.Action
import at.petrak.hexcasting.api.spell.OperationResult
import at.petrak.hexcasting.api.spell.asActionResult
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.spell.casting.SpellContinuation
import at.petrak.hexcasting.api.spell.casting.eval.SpellContinuation
import at.petrak.hexcasting.api.spell.getPositiveIntUnderInclusive
import at.petrak.hexcasting.api.spell.iota.Iota
import at.petrak.hexcasting.api.spell.mishaps.MishapNotEnoughArgs

View file

@ -3,7 +3,7 @@ package at.petrak.hexcasting.common.casting.operators.local
import at.petrak.hexcasting.api.spell.Action
import at.petrak.hexcasting.api.spell.OperationResult
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.spell.casting.SpellContinuation
import at.petrak.hexcasting.api.spell.casting.eval.SpellContinuation
import at.petrak.hexcasting.api.spell.iota.Iota
import at.petrak.hexcasting.api.spell.orNull

View file

@ -3,7 +3,7 @@ package at.petrak.hexcasting.common.casting.operators.local
import at.petrak.hexcasting.api.spell.Action
import at.petrak.hexcasting.api.spell.OperationResult
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.spell.casting.SpellContinuation
import at.petrak.hexcasting.api.spell.casting.eval.SpellContinuation
import at.petrak.hexcasting.api.spell.iota.Iota
import at.petrak.hexcasting.api.spell.mishaps.MishapNotEnoughArgs

View file

@ -4,7 +4,7 @@ import at.petrak.hexcasting.api.spell.Action
import at.petrak.hexcasting.api.spell.OperationResult
import at.petrak.hexcasting.api.spell.RenderedSpell
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.spell.casting.SpellContinuation
import at.petrak.hexcasting.api.spell.casting.eval.SpellContinuation
import at.petrak.hexcasting.api.spell.casting.sideeffects.OperatorSideEffect
import at.petrak.hexcasting.api.spell.iota.Iota
import at.petrak.hexcasting.api.spell.mishaps.MishapNotEnoughArgs

View file

@ -3,7 +3,7 @@ package at.petrak.hexcasting.common.casting.operators.stack
import at.petrak.hexcasting.api.spell.Action
import at.petrak.hexcasting.api.spell.OperationResult
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.spell.casting.SpellContinuation
import at.petrak.hexcasting.api.spell.casting.eval.SpellContinuation
import at.petrak.hexcasting.api.spell.getPositiveInt
import at.petrak.hexcasting.api.spell.iota.Iota
import at.petrak.hexcasting.api.spell.mishaps.MishapNotEnoughArgs

View file

@ -3,7 +3,7 @@ package at.petrak.hexcasting.common.casting.operators.stack
import at.petrak.hexcasting.api.spell.Action
import at.petrak.hexcasting.api.spell.OperationResult
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.spell.casting.SpellContinuation
import at.petrak.hexcasting.api.spell.casting.eval.SpellContinuation
import at.petrak.hexcasting.api.spell.getPositiveInt
import at.petrak.hexcasting.api.spell.iota.Iota
import at.petrak.hexcasting.api.spell.mishaps.MishapNotEnoughArgs

View file

@ -3,7 +3,7 @@ package at.petrak.hexcasting.common.casting.operators.stack
import at.petrak.hexcasting.api.spell.Action
import at.petrak.hexcasting.api.spell.OperationResult
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.spell.casting.SpellContinuation
import at.petrak.hexcasting.api.spell.casting.eval.SpellContinuation
import at.petrak.hexcasting.api.spell.getPositiveIntUnderInclusive
import at.petrak.hexcasting.api.spell.iota.Iota
import at.petrak.hexcasting.api.spell.mishaps.MishapNotEnoughArgs

View file

@ -3,7 +3,7 @@ package at.petrak.hexcasting.common.casting.operators.stack
import at.petrak.hexcasting.api.spell.Action
import at.petrak.hexcasting.api.spell.OperationResult
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.spell.casting.SpellContinuation
import at.petrak.hexcasting.api.spell.casting.eval.SpellContinuation
import at.petrak.hexcasting.api.spell.getPositiveIntUnderInclusive
import at.petrak.hexcasting.api.spell.iota.Iota
import at.petrak.hexcasting.api.spell.mishaps.MishapNotEnoughArgs

View file

@ -3,7 +3,7 @@ package at.petrak.hexcasting.common.casting.operators.stack
import at.petrak.hexcasting.api.spell.Action
import at.petrak.hexcasting.api.spell.OperationResult
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.spell.casting.SpellContinuation
import at.petrak.hexcasting.api.spell.casting.eval.SpellContinuation
import at.petrak.hexcasting.api.spell.iota.DoubleIota
import at.petrak.hexcasting.api.spell.iota.Iota

View file

@ -24,6 +24,8 @@ public class HexSounds {
public static final SoundEvent FAIL_PATTERN = sound("casting.fail_pattern");
public static final SoundEvent CASTING_AMBIANCE = sound("casting.ambiance");
public static final SoundEvent ACTUALLY_CAST = sound("casting.cast");
public static final SoundEvent CAST_HERMES = sound("casting.hermes");
public static final SoundEvent CAST_THOTH = sound("casting.thoth");
public static final SoundEvent ABACUS = sound("abacus");
public static final SoundEvent ABACUS_SHAKE = sound("abacus.shake");

View file

@ -8,13 +8,14 @@ import net.minecraft.resources.ResourceLocation;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.BiConsumer;
import static at.petrak.hexcasting.api.HexAPI.modLoc;
public class HexEvalSounds {
public static final Registry<EvalSound> REGISTRY = IXplatAbstractions.INSTANCE.getEvalSoundRegistry();
private static final Map<ResourceLocation, EvalSound> TYPES = new LinkedHashMap<>();
private static final Map<ResourceLocation, EvalSound> SOUNDS = new LinkedHashMap<>();
public static final EvalSound NOTHING = make("nothing",
new EvalSound(null, Integer.MIN_VALUE));
@ -24,12 +25,23 @@ public class HexEvalSounds {
new EvalSound(HexSounds.ACTUALLY_CAST, 1000));
public static final EvalSound MISHAP = make("mishap",
new EvalSound(HexSounds.FAIL_PATTERN, Integer.MAX_VALUE));
public static final EvalSound HERMES = make("hermes",
new EvalSound(HexSounds.CAST_HERMES, Integer.MAX_VALUE));
public static final EvalSound THOTH = make("thoth",
new EvalSound(HexSounds.CAST_THOTH, Integer.MAX_VALUE));
private static EvalSound make(String name, EvalSound sound) {
var old = TYPES.put(modLoc(name), sound);
var old = SOUNDS.put(modLoc(name), sound);
if (old != null) {
throw new IllegalArgumentException("Typo? Duplicate id " + name);
}
return sound;
}
public static void registerTypes() {
BiConsumer<EvalSound, ResourceLocation> r = (type, id) -> Registry.register(REGISTRY, id, type);
for (var e : SOUNDS.entrySet()) {
r.accept(e.getValue(), e.getKey());
}
}
}

View file

@ -43,7 +43,18 @@
"hexcasting:cast_hex"
]
},
"casting.hermes": {
"subtitle": "hexcasting.subtitles.cast",
"sounds": [
"hexcasting:hermes"
]
},
"casting.thoth": {
"subtitle": "hexcasting.subtitles.cast",
"sounds": [
"hexcasting:thoth"
]
},
"abacus": {
"subtitle": "hexcasting.subtitles.abacus",
"sounds": [
@ -58,7 +69,6 @@
"hexcasting:abacus_shake"
]
},
"spellcircle.find_block": {
"sounds": [
{
@ -83,7 +93,6 @@
],
"subtitle": "hexcasting.subtitles.spellcircle.cast"
},
"scroll.dust": {
"sounds": [
"minecraft:dig/sand1",
@ -101,7 +110,6 @@
],
"subtitle": "hexcasting.subtitles.scroll.scribble"
},
"impetus.fletcher.tick": {
"sounds": [
"minecraft:note/hat"
@ -114,7 +122,6 @@
],
"subtitle": "hexcasting.subtitles.impetus.cleric.register"
},
"lore_fragment.read": {
"sounds": [
"block/enchantment_table/enchant1",

View file

@ -11,6 +11,7 @@ import at.petrak.hexcasting.api.player.FlightAbility;
import at.petrak.hexcasting.api.player.Sentinel;
import at.petrak.hexcasting.api.spell.casting.CastingHarness;
import at.petrak.hexcasting.api.spell.casting.ResolvedPattern;
import at.petrak.hexcasting.api.spell.casting.sideeffects.EvalSound;
import at.petrak.hexcasting.api.spell.iota.IotaType;
import at.petrak.hexcasting.common.lib.HexItems;
import at.petrak.hexcasting.common.network.IMessage;
@ -24,6 +25,7 @@ import at.petrak.hexcasting.mixin.accessor.AccessorVillager;
import at.petrak.hexcasting.xplat.IXplatAbstractions;
import at.petrak.hexcasting.xplat.IXplatTags;
import at.petrak.hexcasting.xplat.Platform;
import com.google.common.base.Suppliers;
import com.jamieswhiteshirt.reachentityattributes.ReachEntityAttributes;
import com.mojang.serialization.Lifecycle;
import net.fabricmc.api.EnvType;
@ -79,6 +81,7 @@ import virtuoel.pehkui.api.ScaleTypes;
import java.util.List;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Supplier;
import java.util.stream.Stream;
import static at.petrak.hexcasting.api.HexAPI.modLoc;
@ -258,7 +261,7 @@ public class FabricXplatImpl implements IXplatAbstractions {
@Override
public <T extends BlockEntity> BlockEntityType<T> createBlockEntityType(BiFunction<BlockPos, BlockState, T> func,
Block... blocks) {
Block... blocks) {
return FabricBlockEntityTypeBuilder.create(func::apply, blocks).build();
}
@ -409,17 +412,27 @@ public class FabricXplatImpl implements IXplatAbstractions {
return namespace;
}
private static Registry<IotaType<?>> IOTA_TYPE_REGISTRY = null;
private static final Supplier<Registry<IotaType<?>>> IOTA_TYPE_REGISTRY = Suppliers.memoize(() ->
FabricRegistryBuilder.from(new DefaultedRegistry<IotaType<?>>(
HexAPI.MOD_ID + ":null", ResourceKey.createRegistryKey(modLoc("iota_type")),
Lifecycle.stable(), null))
.buildAndRegister()
);
private static final Supplier<Registry<EvalSound>> EVAL_SOUNDS_REGISTRY = Suppliers.memoize(() ->
FabricRegistryBuilder.from(new DefaultedRegistry<EvalSound>(
HexAPI.MOD_ID + ":nothing", ResourceKey.createRegistryKey(modLoc("eval_sound")),
Lifecycle.stable(), null))
.buildAndRegister()
);
@Override
public Registry<IotaType<?>> getIotaTypeRegistry() {
if (IOTA_TYPE_REGISTRY == null) {
IOTA_TYPE_REGISTRY = FabricRegistryBuilder.from(new DefaultedRegistry<IotaType<?>>(
HexAPI.MOD_ID + ":null", ResourceKey.createRegistryKey(modLoc("iota_type")),
Lifecycle.stable(), null))
.buildAndRegister();
}
return IOTA_TYPE_REGISTRY;
return IOTA_TYPE_REGISTRY.get();
}
@Override
public Registry<EvalSound> getEvalSoundRegistry() {
return EVAL_SOUNDS_REGISTRY.get();
}
@Override

View file

@ -12,10 +12,12 @@ import at.petrak.hexcasting.api.player.Sentinel;
import at.petrak.hexcasting.api.spell.casting.CastingContext;
import at.petrak.hexcasting.api.spell.casting.CastingHarness;
import at.petrak.hexcasting.api.spell.casting.ResolvedPattern;
import at.petrak.hexcasting.api.spell.casting.sideeffects.EvalSound;
import at.petrak.hexcasting.api.spell.iota.IotaType;
import at.petrak.hexcasting.api.utils.HexUtils;
import at.petrak.hexcasting.common.lib.hex.HexIotaTypes;
import at.petrak.hexcasting.common.lib.HexItems;
import at.petrak.hexcasting.common.lib.hex.HexEvalSounds;
import at.petrak.hexcasting.common.lib.hex.HexIotaTypes;
import at.petrak.hexcasting.common.network.IMessage;
import at.petrak.hexcasting.forge.cap.CapSyncers;
import at.petrak.hexcasting.forge.cap.HexCapabilities;
@ -30,6 +32,7 @@ import at.petrak.hexcasting.mixin.accessor.AccessorVillager;
import at.petrak.hexcasting.xplat.IXplatAbstractions;
import at.petrak.hexcasting.xplat.IXplatTags;
import at.petrak.hexcasting.xplat.Platform;
import com.google.common.base.Suppliers;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.NonNullList;
@ -83,6 +86,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Supplier;
import static at.petrak.hexcasting.api.HexAPI.modLoc;
import static net.minecraftforge.fluids.capability.IFluidHandler.FluidAction.EXECUTE;
@ -414,16 +418,25 @@ public class ForgeXplatImpl implements IXplatAbstractions {
return namespace;
}
private static Registry<IotaType<?>> IOTA_TYPE_REGISTRY = null;
private static final Supplier<Registry<IotaType<?>>> IOTA_TYPE_REGISTRY = Suppliers.memoize(() ->
ForgeAccessorRegistry.hex$registerDefaulted(
ResourceKey.createRegistryKey(modLoc("iota_type")),
HexAPI.MOD_ID + ":null", registry -> HexIotaTypes.NULL)
);
private static final Supplier<Registry<EvalSound>> EVAL_SOUND_REGISTRY = Suppliers.memoize(() ->
ForgeAccessorRegistry.hex$registerDefaulted(
ResourceKey.createRegistryKey(modLoc("eval_sound")),
HexAPI.MOD_ID + ":nothing", registry -> HexEvalSounds.NOTHING)
);
@Override
public Registry<IotaType<?>> getIotaTypeRegistry() {
if (IOTA_TYPE_REGISTRY == null) {
IOTA_TYPE_REGISTRY = ForgeAccessorRegistry.hex$registerDefaulted(
ResourceKey.createRegistryKey(modLoc("iota_type")),
HexAPI.MOD_ID + ":null", registry -> HexIotaTypes.NULL);
}
return IOTA_TYPE_REGISTRY;
return IOTA_TYPE_REGISTRY.get();
}
@Override
public Registry<EvalSound> getEvalSoundRegistry() {
return EVAL_SOUND_REGISTRY.get();
}
@Override