finally banged the evaluator into shape, minus a couple rough edges (eval)

This commit is contained in:
gamma-delta 2022-01-29 18:14:33 -06:00
parent 9cf873a40c
commit 72f2c662ef
39 changed files with 418 additions and 357 deletions

View file

@ -17,3 +17,8 @@ colorizer = [12161791]
[Alwinfy]
#colorizer = [0x3327e3]
colorizer = [3352547]
[Dev]
# 0xff0000 0x00ff00 0x0000ff
# For testing purposes
colorizer = [16711680, 65280, 255]

View file

@ -17,7 +17,8 @@ interface ConstManaOperator : Operator {
override fun operate(stack: MutableList<SpellDatum<*>>, ctx: CastingContext): OperationResult {
if (this.argc > stack.size)
throw CastException(CastException.Reason.NOT_ENOUGH_ARGS, this.argc, stack.size)
val args = stack.dropLast(this.argc)
val args = stack.takeLast(this.argc)
for (_i in 0 until this.argc) stack.removeLast()
val newData = this.execute(args, ctx)
stack.addAll(newData)

View file

@ -7,6 +7,8 @@ import at.petrak.hexcasting.common.casting.CastingContext
import at.petrak.hexcasting.common.casting.Widget
import at.petrak.hexcasting.hexmath.HexPattern
import net.minecraft.nbt.*
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.TextComponent
import net.minecraft.world.entity.Entity
import net.minecraft.world.phys.Vec3
@ -71,12 +73,12 @@ class SpellDatum<T : Any> private constructor(val payload: T) {
append(']')
}
fun display(): String = buildString {
fun display(): Component = TextComponent(buildString {
when (val pl = this@SpellDatum.payload) {
is List<*> -> {
append("[")
for ((i, v) in pl.withIndex()) {
append((v as SpellDatum<*>).display())
append((v as SpellDatum<*>).display().contents)
if (i != pl.lastIndex) {
append(", ")
}
@ -93,7 +95,7 @@ class SpellDatum<T : Any> private constructor(val payload: T) {
append("HexPattern(")
append(pl.startDir)
append(" ")
append(pl.angles)
append(pl.anglesSignature())
append(")")
}
is Entity -> {
@ -104,7 +106,7 @@ class SpellDatum<T : Any> private constructor(val payload: T) {
}
else -> append(pl.toString())
}
}
})
companion object {
@JvmStatic

View file

@ -13,7 +13,8 @@ interface SpellOperator : Operator {
override fun operate(stack: MutableList<SpellDatum<*>>, ctx: CastingContext): OperationResult {
if (this.argc > stack.size)
throw CastException(CastException.Reason.NOT_ENOUGH_ARGS, this.argc, stack.size)
val args = stack.dropLast(this.argc)
val args = stack.takeLast(this.argc)
for (_i in 0 until this.argc) stack.removeLast()
val (spell, mana, particlePoses) = this.execute(args, ctx)
val sideEffects = mutableListOf(

View file

@ -41,9 +41,9 @@ public class HexAdditionalRenderers {
var mc = Minecraft.getInstance();
var playerPos = mc.gameRenderer.getMainCamera().getPosition();
ps.translate(
sentinel.position.x - playerPos.x + 0.5,
sentinel.position.y - playerPos.y + 0.5,
sentinel.position.z - playerPos.z + 0.5);
sentinel.position.x - playerPos.x,
sentinel.position.y - playerPos.y,
sentinel.position.z - playerPos.z);
var time = mc.level.getLevelData().getGameTime() + partialTicks;
var bobSpeed = 1f / 20;

View file

@ -3,7 +3,7 @@ package at.petrak.hexcasting.client.gui
import at.petrak.hexcasting.HexUtils
import at.petrak.hexcasting.HexUtils.TAU
import at.petrak.hexcasting.client.RenderLib
import at.petrak.hexcasting.common.items.HexItems
import at.petrak.hexcasting.common.casting.ControllerInfo
import at.petrak.hexcasting.common.items.ItemSpellbook
import at.petrak.hexcasting.common.lib.HexSounds
import at.petrak.hexcasting.common.network.HexMessages
@ -21,6 +21,7 @@ import net.minecraft.client.renderer.GameRenderer
import net.minecraft.client.resources.sounds.AbstractSoundInstance
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.client.resources.sounds.SoundInstance
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.TextComponent
import net.minecraft.sounds.SoundSource
import net.minecraft.util.Mth
@ -38,13 +39,19 @@ class GuiSpellcasting(private val handOpenedWith: InteractionHand) : Screen(Text
private var ambianceSoundInstance: AbstractSoundInstance? = null
private var stackDescs: List<String> = emptyList()
private var stackDescs: List<Component> = emptyList()
fun recvServerUpdate(stackDescs: List<String>, prevPatternBad: Boolean) {
fun recvServerUpdate(info: ControllerInfo, stackDescs: List<Component>) {
this.stackDescs = stackDescs
this.patterns.lastOrNull()?.let { it.valid = if (prevPatternBad) PatternValidity.ERROR else PatternValidity.OK }
this.patterns.lastOrNull()?.let {
it.valid = if (info.status == ControllerInfo.Status.PREV_PATTERN_INVALID)
PatternValidity.ERROR
else
PatternValidity.OK
}
val sound = if (prevPatternBad) HexSounds.FAIL_PATTERN.get() else HexSounds.ADD_PATTERN.get()
val sound =
if (info.status == ControllerInfo.Status.PREV_PATTERN_INVALID) HexSounds.FAIL_PATTERN.get() else HexSounds.ADD_PATTERN.get()
Minecraft.getInstance().soundManager.play(
SimpleSoundInstance.forUI(
sound,
@ -288,14 +295,14 @@ class GuiSpellcasting(private val handOpenedWith: InteractionHand) : Screen(Text
RenderSystem.enableDepthTest()
val mc = Minecraft.getInstance()
if (mc.player?.getItemInHand(HexUtils.OtherHand(handOpenedWith))?.`is`(HexItems.SCRYING_LENS.get()) == true) {
// if (mc.player?.getItemInHand(HexUtils.OtherHand(handOpenedWith))?.`is`(HexItems.SCRYING_LENS.get()) == true) {
val font = mc.font
for ((i, s) in this.stackDescs.withIndex()) {
val offsetIdx = this.stackDescs.size - i - 1
font.draw(poseStack, s, 10f, 10f + 9f * offsetIdx, -1)
}
val font = mc.font
for ((i, s) in this.stackDescs.withIndex()) {
val offsetIdx = this.stackDescs.size - i - 1
font.draw(poseStack, s, 10f, 10f + 9f * offsetIdx, -1)
}
// }
}
// why the hell is this default true

View file

@ -20,17 +20,15 @@ import net.minecraft.world.InteractionHand
import net.minecraft.world.item.ItemStack
import java.util.*
import kotlin.math.min
import kotlin.random.Random
import kotlin.random.nextInt
/**
* Keeps track of a player casting a spell on the server.
* It's stored as NBT on the wand.
*/
class CastingHarness private constructor(
val stack: MutableList<SpellDatum<*>>,
var stack: MutableList<SpellDatum<*>>,
var parenCount: Int,
var parenthesized: MutableList<HexPattern>,
var parenthesized: List<HexPattern>,
var escapeNext: Boolean,
val ctx: CastingContext,
val prepackagedColorizer: ItemStack?
@ -38,84 +36,179 @@ class CastingHarness private constructor(
constructor(ctx: CastingContext) : this(mutableListOf(), 0, mutableListOf(), false, ctx, null)
/**
* Given a pattern, do all the updating/side effects/etc required.
*/
fun executeNewPattern(newPat: HexPattern, world: ServerLevel): ControllerInfo {
val result = this.getUpdate(newPat, world)
this.applyFunctionalData(result.newData)
return this.performSideEffects(result.sideEffects)
}
/**
* When the server gets a packet from the client with a new pattern,
* handle it.
* handle it functionally.
*/
fun update(newPat: HexPattern, world: ServerLevel): CastResult {
return try {
var exn: CastException? = null
val operator = try {
PatternRegistry.matchPattern(newPat, world)
} catch (e: CastException) {
exn = e
null
}
if (this.parenCount > 0) {
if (this.escapeNext) {
this.escapeNext = false
this.parenthesized.add(newPat)
} else if (operator == Widget.ESCAPE) {
this.escapeNext = true
} else if (operator == Widget.OPEN_PAREN) {
// we have escaped the parens onto the stack; we just also record our count.
this.parenthesized.add(newPat)
this.parenCount++
} else if (operator == Widget.CLOSE_PAREN) {
this.parenCount--
if (this.parenCount == 0) {
this.stack.add(SpellDatum.make(this.parenthesized.map { SpellDatum.make(it) }))
this.parenthesized.clear()
} else if (this.parenCount < 0) {
throw CastException(CastException.Reason.TOO_MANY_CLOSE_PARENS)
} else {
// we have this situation: "(()"
// we need to add the close paren
this.parenthesized.add(newPat)
}
} else {
this.parenthesized.add(newPat)
}
} else if (this.escapeNext) {
this.escapeNext = false
this.stack.add(SpellDatum.make(newPat))
} else if (operator == Widget.ESCAPE) {
this.escapeNext = true
} else if (exn != null) {
// there was a problem finding the pattern and it was NOT due to numbers
throw exn
} else if (operator == Widget.OPEN_PAREN) {
this.parenCount++
} else if (operator == Widget.CLOSE_PAREN) {
throw CastException(CastException.Reason.TOO_MANY_CLOSE_PARENS)
} else {
// we know the operator is ok here
val (stackPrime, sideEffects) = operator!!.operate(this.stack, this.ctx)
fun getUpdate(newPat: HexPattern, world: ServerLevel): CastResult {
try {
// wouldn't it be nice to be able to go paren'
// i guess i'll call it paren2
val paren2 = this.handleParentheses(newPat)
if (paren2 != null) {
return CastResult(
paren2,
listOf(),
)
}
if (spellsToCast.isNotEmpty()) {
CastResult.Cast(spellsToCast, this.stack.isEmpty())
} else if (this.stack.isEmpty() && !this.escapeNext) {
if (this.parenCount == 0) {
CastResult.QuitCasting
} else {
CastResult.Continue(false)
}
} else {
CastResult.Continue(false)
}
// Don't catch this one
val operator = PatternRegistry.matchPattern(newPat, world)
val (stack2, sideEffectsUnmut) = operator.operate(this.stack.toMutableList(), this.ctx)
// Stick a poofy particle effect at the caster position
val sideEffects = sideEffectsUnmut.toMutableList()
sideEffects.add(OperatorSideEffect.Particles(this.ctx.position))
val fd = this.getFunctionalData().copy(
stack = stack2,
)
return CastResult(
fd,
sideEffects,
)
} catch (exn: CastException) {
return CastResult(
this.getFunctionalData(),
listOf(OperatorSideEffect.Mishap(exn)),
)
}
}
/**
* Execute the side effects of a cast, and then tell the client what to think about it.
*/
fun performSideEffects(sideEffects: List<OperatorSideEffect>): ControllerInfo {
var status = ControllerInfo.Status.NONE
for (haskellProgrammersShakingandCryingRN in sideEffects) {
if (haskellProgrammersShakingandCryingRN is OperatorSideEffect.Mishap)
status = ControllerInfo.Status.PREV_PATTERN_INVALID
val mustStop = haskellProgrammersShakingandCryingRN.performEffect(this)
if (mustStop)
break
if (haskellProgrammersShakingandCryingRN is OperatorSideEffect.AttemptSpell)
status = ControllerInfo.Status.SPELL_CAST
}
if (status == ControllerInfo.Status.SPELL_CAST && this.stack.isEmpty())
status = ControllerInfo.Status.SPELL_CAST_AND_DONE
return ControllerInfo(
status,
)
}
/**
* Return the functional update represented by the current state (for use with `copy`)
*/
private fun getFunctionalData() = FunctionalData(
this.stack.toList(),
this.parenCount,
this.parenthesized.toList(),
this.escapeNext,
)
/**
* Apply the functional update.
*/
private fun applyFunctionalData(data: FunctionalData) {
this.stack.clear()
this.stack.addAll(data.stack)
this.parenCount = data.parenCount
this.parenthesized = data.parenthesized
this.escapeNext = data.escapeNext
}
/**
* Return a non-null value if we handled this in some sort of parenthesey way,
* either escaping it onto the stack or changing the parenthese-handling state.
*/
private fun handleParentheses(newPat: HexPattern): FunctionalData? {
val operator = try {
PatternRegistry.matchPattern(newPat, this.ctx.world)
} catch (e: CastException) {
if (e.reason == CastException.Reason.INVALID_PATTERN) {
val idx = Random.nextInt(0..this.stack.size)
this.stack.add(idx, SpellDatum.make(Widget.GARBAGE))
CastResult.Continue(true)
} else {
CastResult.Error(e)
}
null
}
return if (this.parenCount > 0) {
if (this.escapeNext) {
val newParens = this.parenthesized.toMutableList()
newParens.add(newPat)
this.getFunctionalData().copy(
escapeNext = false,
parenthesized = newParens
)
} else if (operator == Widget.ESCAPE) {
this.getFunctionalData().copy(
escapeNext = true,
)
} else if (operator == Widget.OPEN_PAREN) {
// we have escaped the parens onto the stack; we just also record our count.
val newParens = this.parenthesized.toMutableList()
newParens.add(newPat)
this.getFunctionalData().copy(
parenthesized = newParens,
parenCount = this.parenCount + 1
)
} else if (operator == Widget.CLOSE_PAREN) {
val newParenCount = this.parenCount - 1
if (newParenCount == 0) {
val newStack = this.stack.toMutableList()
newStack.add(SpellDatum.make(this.parenthesized.map { SpellDatum.make(it) }))
this.getFunctionalData().copy(
stack = newStack,
parenCount = newParenCount,
parenthesized = listOf()
)
} else if (newParenCount < 0) {
throw CastException(CastException.Reason.TOO_MANY_CLOSE_PARENS)
} else {
// we have this situation: "(()"
// we need to add the close paren
val newParens = this.parenthesized.toMutableList()
newParens.add(newPat)
this.getFunctionalData().copy(
parenCount = newParenCount,
parenthesized = newParens
)
}
} else {
val newParens = this.parenthesized.toMutableList()
newParens.add(newPat)
this.getFunctionalData().copy(
parenthesized = newParens
)
}
} else if (this.escapeNext) {
val newStack = this.stack.toMutableList()
newStack.add(SpellDatum.make(newPat))
this.getFunctionalData().copy(
stack = newStack,
escapeNext = false,
)
} else if (operator == Widget.ESCAPE) {
this.getFunctionalData().copy(
escapeNext = true
)
} else if (operator == Widget.OPEN_PAREN) {
this.getFunctionalData().copy(
parenCount = this.parenCount + 1
)
} else if (operator == Widget.CLOSE_PAREN) {
throw CastException(CastException.Reason.TOO_MANY_CLOSE_PARENS)
} else {
null
}
}
@ -258,35 +351,8 @@ class CastingHarness private constructor(
}
}
sealed class CastResult {
/** Casting still in progress */
data class Continue(val prevPatternBad: Boolean) : CastResult()
/** Non-catastrophic quit */
object QuitCasting : CastResult()
/** Finished casting */
data class Cast(val sideEffects: List<OperatorSideEffect>, val quit: Boolean) : CastResult()
/** uh-oh */
data class Error(val exn: CastException) : CastResult()
/** YOU DIED due to casting too hard from hit points. */
object Died : CastResult()
fun quitStatus(): QuitStatus =
when (this) {
QuitCasting, Died, is Error -> QuitStatus.QUIT
is Cast -> if (this.quit) QuitStatus.QUIT else QuitStatus.OK
is Continue -> if (this.prevPatternBad) QuitStatus.LAST_PATTERN_INVALID else QuitStatus.OK
}
}
enum class QuitStatus {
OK,
/** Keep going, but the pattern you just drew was sussy */
LAST_PATTERN_INVALID,
QUIT
}
data class CastResult(
val newData: FunctionalData,
val sideEffects: List<OperatorSideEffect>,
)
}

View file

@ -0,0 +1,16 @@
package at.petrak.hexcasting.common.casting
/**
* Information for the sake of the GUI.
*/
data class ControllerInfo(val status: Status) {
enum class Status {
NONE,
SPELL_CAST,
SPELL_CAST_AND_DONE,
PREV_PATTERN_INVALID,
}
fun shouldQuit(): Boolean = this.status == Status.SPELL_CAST_AND_DONE
fun wasSpellCast(): Boolean = this.status == Status.SPELL_CAST || this.status == Status.SPELL_CAST_AND_DONE
}

View file

@ -0,0 +1,24 @@
package at.petrak.hexcasting.common.casting
import at.petrak.hexcasting.api.SpellDatum
import at.petrak.hexcasting.hexmath.HexPattern
/**
* A change to the data in a CastHarness after a pattern is drawn.
*
* [wasThisPatternInvalid] is for the benefit of the controller.
*/
data class FunctionalData(
val stack: List<SpellDatum<*>>,
val parenCount: Int,
val parenthesized: List<HexPattern>,
val escapeNext: Boolean,
) {
/**
* Whether this by itself is enough to get the client to quit casting.
*
* Note the client may want to quit for other reasons.
*/
fun shouldQuit(): Boolean =
this.stack.isEmpty() && this.parenCount == 0 && !this.escapeNext
}

View file

@ -7,6 +7,7 @@ import at.petrak.hexcasting.datagen.Advancements
import com.mojang.math.Vector3f
import net.minecraft.Util
import net.minecraft.core.particles.DustParticleOptions
import net.minecraft.network.chat.TextComponent
import net.minecraft.network.chat.TranslatableComponent
import net.minecraft.util.FastColor
import net.minecraft.world.phys.Vec3
@ -55,21 +56,23 @@ sealed class OperatorSideEffect {
override fun performEffect(harness: CastingHarness): Boolean {
val colorizer = harness.getColorizer()
for (i in 0 until 6) {
for (i in 0 until 20) {
// For the colors, pick any random time to get a mix of colors
val color =
CapPreferredColorizer.getColor(colorizer, harness.ctx.caster, Random.nextFloat() * 256f, Vec3.ZERO)
val r = FastColor.ARGB32.red(color)
val g = FastColor.ARGB32.green(color)
val b = FastColor.ARGB32.blue(color)
harness.ctx.world.addParticle(
harness.ctx.world.sendParticles(
DustParticleOptions(Vector3f(r.toFloat(), g.toFloat(), b.toFloat()), 1f),
position.x,
position.y,
position.z,
1,
0.1,
0.1,
0.1,
0.1,
0.1
)
}
@ -77,10 +80,21 @@ sealed class OperatorSideEffect {
}
}
object AddGarbage : OperatorSideEffect() {
data class Mishap(val exn: CastException) : OperatorSideEffect() {
override fun performEffect(harness: CastingHarness): Boolean {
val idx = Random.nextInt(0..harness.stack.size)
harness.stack.add(idx, SpellDatum.make(Widget.GARBAGE))
harness.ctx.caster.sendMessage(
TextComponent(exn.toString()),
Util.NIL_UUID
)
when (exn.reason) {
CastException.Reason.INVALID_PATTERN -> {
val idx = Random.nextInt(0..harness.stack.size)
harness.stack.add(idx, SpellDatum.make(Widget.GARBAGE))
}
else -> {}
}
return false
}
}

View file

@ -7,7 +7,6 @@ import at.petrak.hexcasting.api.SpellDatum;
import at.petrak.hexcasting.common.casting.operators.*;
import at.petrak.hexcasting.common.casting.operators.lists.OpAppend;
import at.petrak.hexcasting.common.casting.operators.lists.OpConcat;
import at.petrak.hexcasting.common.casting.operators.lists.OpForEach;
import at.petrak.hexcasting.common.casting.operators.lists.OpIndex;
import at.petrak.hexcasting.common.casting.operators.math.*;
import at.petrak.hexcasting.common.casting.operators.selectors.OpGetCaster;

View file

@ -33,9 +33,9 @@ object OpBlockRaycast : ConstManaOperator {
// this is weird (for example, casting OpBreakBlock at this position will not break the block we're looking at)
// so we return the block pos instead
Vec3(
blockHitResult.blockPos.x.toDouble(),
blockHitResult.blockPos.y.toDouble(),
blockHitResult.blockPos.z.toDouble()
blockHitResult.blockPos.x.toDouble() + 0.5,
blockHitResult.blockPos.y.toDouble() + 0.5,
blockHitResult.blockPos.z.toDouble() + 0.5
)
} else {
Widget.NULL

View file

@ -3,10 +3,10 @@ package at.petrak.hexcasting.common.casting.operators
import at.petrak.hexcasting.api.OperationResult
import at.petrak.hexcasting.api.Operator
import at.petrak.hexcasting.api.Operator.Companion.getChecked
import at.petrak.hexcasting.api.RenderedSpell
import at.petrak.hexcasting.api.SpellDatum
import at.petrak.hexcasting.common.casting.CastingContext
import at.petrak.hexcasting.common.casting.CastingHarness
import at.petrak.hexcasting.common.casting.OperatorSideEffect
object OpEval : Operator {
override fun operate(stack: MutableList<SpellDatum<*>>, ctx: CastingContext): OperationResult {
@ -16,20 +16,16 @@ object OpEval : Operator {
ctx.incDepth()
val harness = CastingHarness(ctx)
harness.stack.addAll(stack)
stack.clear()
val spellsToCast = mutableListOf<RenderedSpell>()
val sideEffects = mutableListOf<OperatorSideEffect>()
/*
for (pat in instrs) {
val res = harness.update(pat.tryGet(), ctx.world)
when (res) {
is CastingHarness.CastResult.Error -> throw res.exn
is CastingHarness.CastResult.Cast -> spellsToCast.addAll(res.spells)
else -> {}
}
if (res.quitStatus() == CastingHarness.QuitStatus.QUIT) break
val res = harness.getUpdate(pat.tryGet(), ctx.world)
sideEffects.addAll(res.sideEffects)
}
stack.addAll(harness.stack)
return OperationResult(50_000, spellsToCast)
*/
return OperationResult(harness.stack, sideEffects)
}
}

View file

@ -2,47 +2,11 @@ package at.petrak.hexcasting.common.casting.operators
import at.petrak.hexcasting.api.OperationResult
import at.petrak.hexcasting.api.Operator
import at.petrak.hexcasting.api.Operator.Companion.getChecked
import at.petrak.hexcasting.api.RenderedSpell
import at.petrak.hexcasting.api.SpellDatum
import at.petrak.hexcasting.common.casting.CastingContext
import at.petrak.hexcasting.common.casting.CastingHarness
import at.petrak.hexcasting.server.TickScheduler
import kotlin.math.max
import kotlin.math.roundToInt
object OpEvalDelay : Operator {
override fun operate(stack: MutableList<SpellDatum<*>>, ctx: CastingContext): OperationResult {
val instrs: List<SpellDatum<*>> = stack.getChecked(stack.lastIndex - 1)
val delay: Double = stack.getChecked(stack.lastIndex)
stack.removeLastOrNull()
stack.removeLastOrNull()
val ticks = max((delay * 20.0).roundToInt(), 1)
val harness = CastingHarness(ctx)
harness.stack.addAll(stack)
stack.clear()
TickScheduler.schedule(ticks) {
val spellsToCast = mutableListOf<RenderedSpell>()
for (pat in instrs) {
val res = harness.update(pat.tryGet(), ctx.world)
when (res) {
is CastingHarness.CastResult.Error -> throw res.exn
is CastingHarness.CastResult.Cast -> spellsToCast.addAll(res.spells)
else -> {}
}
if (res.quitStatus() == CastingHarness.QuitStatus.QUIT) break
}
// god i hate how this would just work too
// stack.addAll(harness.stack)
// it is so weird to think about garbage collection
for (spell in spellsToCast) {
spell.cast(ctx)
}
}
return OperationResult(80_000, emptyList())
return OperationResult(stack, listOf())
}
}

View file

@ -0,0 +1,12 @@
package at.petrak.hexcasting.common.casting.operators
import at.petrak.hexcasting.api.OperationResult
import at.petrak.hexcasting.api.Operator
import at.petrak.hexcasting.api.SpellDatum
import at.petrak.hexcasting.common.casting.CastingContext
object OpForEach : Operator {
override fun operate(stack: MutableList<SpellDatum<*>>, ctx: CastingContext): OperationResult {
return OperationResult(stack, listOf())
}
}

View file

@ -1,50 +0,0 @@
package at.petrak.hexcasting.common.casting.operators.lists
import at.petrak.hexcasting.api.OperationResult
import at.petrak.hexcasting.api.Operator
import at.petrak.hexcasting.api.Operator.Companion.getChecked
import at.petrak.hexcasting.api.RenderedSpell
import at.petrak.hexcasting.api.SpellDatum
import at.petrak.hexcasting.common.casting.CastException
import at.petrak.hexcasting.common.casting.CastingContext
import at.petrak.hexcasting.common.casting.CastingHarness
import at.petrak.hexcasting.hexmath.HexPattern
object OpForEach : Operator {
override fun operate(stack: MutableList<SpellDatum<*>>, ctx: CastingContext): OperationResult {
val last = stack.lastIndex
val maybeProgram = stack[last - 1]
val vals = stack.getChecked<List<SpellDatum<*>>>(last)
stack.removeLastOrNull()
stack.removeLastOrNull()
val program = when (maybeProgram.payload) {
is HexPattern -> listOf(maybeProgram.payload)
is List<*> -> maybeProgram.payload.map { (it as SpellDatum<*>).tryGet() }
else -> throw CastException(CastException.Reason.OP_WRONG_TYPE, List::class.java, maybeProgram.payload)
}
val outvalues = mutableListOf<SpellDatum<*>>()
val spellsToCast = mutableListOf<RenderedSpell>()
for (v in vals) {
ctx.incDepth()
val harness = CastingHarness(ctx)
// Put the entire current stack on there, then the next value
harness.stack.addAll(stack)
harness.stack.add(v)
for (pat in program) {
val res = harness.update(pat, ctx.world)
when (res) {
is CastingHarness.CastResult.Error -> throw res.exn
is CastingHarness.CastResult.Cast -> spellsToCast.addAll(res.spells)
else -> {}
}
if (res.quitStatus() == CastingHarness.QuitStatus.QUIT) break
}
outvalues.addAll(harness.stack)
}
stack.addAll(outvalues)
return OperationResult(10_000, spellsToCast)
}
}

View file

@ -22,7 +22,7 @@ object OpAddMotion : SpellOperator {
return Triple(
Spell(target, motion),
(motion.lengthSqr() * 10_000f).toInt(),
listOf(),
listOf(target.position()),
)
}

View file

@ -16,38 +16,43 @@ import kotlin.math.roundToInt
object OpBlink : SpellOperator {
override val argc = 2
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): Pair<RenderedSpell, Int> {
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): Triple<RenderedSpell, Int, List<Vec3>> {
val target = args.getChecked<Entity>(0)
val delta = args.getChecked<Double>(1)
ctx.assertVecInRange(target.position())
ctx.assertVecInRange(target.position().add(target.lookAngle.scale(delta)))
val dvec = targetDelta(ctx, target, delta)
return Pair(
ctx.assertVecInRange(target.position())
ctx.assertVecInRange(target.position().add(dvec))
return Triple(
Spell(target, delta),
20_000 * delta.roundToInt(),
50_000 * delta.roundToInt(),
listOf(target.position().add(dvec))
)
}
private data class Spell(val target: Entity, val delta: Double) : RenderedSpell {
override fun cast(ctx: CastingContext) {
val look = target.lookAngle
// https://github.com/VazkiiMods/Psi/blob/master/src/main/java/vazkii/psi/common/spell/trick/entity/PieceTrickBlink.java#L74
// IIRC this is to prevent you from teleporting into blocks because people tend to look a little bit down
// ... but isn't the condition backwards?
val dx = look.x * delta
val dy = if (target != ctx.caster) {
look.y * delta
} else {
max(0.0, look.y * delta)
}
val dz = look.z * delta
val dvec = Vec3(dx, dy, dz)
val dvec = targetDelta(ctx, target, delta)
target.setPos(target.position().add(dvec))
if (target is ServerPlayer) {
HexMessages.getNetwork().send(PacketDistributor.PLAYER.with { target }, MsgBlinkAck(dvec))
}
}
}
private fun targetDelta(ctx: CastingContext, target: Entity, delta: Double): Vec3 {
val look = target.lookAngle
// https://github.com/VazkiiMods/Psi/blob/master/src/main/java/vazkii/psi/common/spell/trick/entity/PieceTrickBlink.java#L74
val dx = look.x * delta
val dy = if (target != ctx.caster) {
look.y * delta
} else {
max(0.0, look.y * delta)
}
val dz = look.z * delta
return Vec3(dx, dy, dz)
}
}

View file

@ -14,12 +14,13 @@ object OpBreakBlock : SpellOperator {
override val argc: Int
get() = 1
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): Pair<RenderedSpell, Int> {
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): Triple<RenderedSpell, Int, List<Vec3>> {
val pos = args.getChecked<Vec3>(0)
ctx.assertVecInRange(pos)
return Pair(
return Triple(
Spell(pos),
20_000
20_000,
listOf(pos)
)
}
@ -38,7 +39,7 @@ object OpBreakBlock : SpellOperator {
|| TierSortingRegistry.isCorrectTierForDrops(tier, blockstate))
) {
ctx.world.destroyBlock(pos, true, ctx.caster)
} // TODO: else some kind of failureific particle effect?
}
}
}
}

View file

@ -9,15 +9,17 @@ import at.petrak.hexcasting.common.lib.HexCapabilities
import at.petrak.hexcasting.common.network.HexMessages
import at.petrak.hexcasting.common.network.MsgColorizerUpdateAck
import net.minecraft.world.item.ItemStack
import net.minecraft.world.phys.Vec3
import net.minecraftforge.network.PacketDistributor
object OpColorize : SpellOperator {
override val argc = 0
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): Pair<RenderedSpell, Int> {
return Pair(
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): Triple<RenderedSpell, Int, List<Vec3>> {
return Triple(
Spell,
10_000
10_000,
listOf()
)
}

View file

@ -13,13 +13,14 @@ import net.minecraft.world.phys.Vec3
object OpCreateWater : SpellOperator {
override val argc = 1
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): Pair<RenderedSpell, Int> {
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): Triple<RenderedSpell, Int, List<Vec3>> {
val target = args.getChecked<Vec3>(0)
ctx.assertVecInRange(target)
return Pair(
return Triple(
Spell(target),
10_000
10_000,
listOf(target)
)
}

View file

@ -22,13 +22,14 @@ import net.minecraft.world.phys.Vec3
object OpDestroyWater : SpellOperator {
override val argc = 1
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): Pair<RenderedSpell, Int> {
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): Triple<RenderedSpell, Int, List<Vec3>> {
val target = args.getChecked<Vec3>(0)
ctx.assertVecInRange(target)
return Pair(
return Triple(
Spell(target),
200_000
200_000,
listOf(target)
)
}

View file

@ -13,13 +13,14 @@ class OpExplode(val fire: Boolean) : SpellOperator {
override val argc: Int
get() = 2
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): Pair<RenderedSpell, Int> {
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): Triple<RenderedSpell, Int, List<Vec3>> {
val pos = args.getChecked<Vec3>(0)
val strength = args.getChecked<Double>(1)
ctx.assertVecInRange(pos)
return Pair(
return Triple(
Spell(pos, strength, this.fire),
((1 + strength + if (this.fire) 2 else 0) * 50_000.0).toInt(),
listOf(pos)
)
}

View file

@ -10,24 +10,21 @@ import at.petrak.hexcasting.common.casting.ManaHelper
import at.petrak.hexcasting.common.items.magic.ItemPackagedSpell
import at.petrak.hexcasting.hexmath.HexPattern
import net.minecraft.nbt.ListTag
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.item.ItemEntity
import net.minecraft.world.phys.Vec3
class OpMakePackagedSpell<T : ItemPackagedSpell>(val type: Class<T>, val cost: Int) : SpellOperator {
override val argc = 2
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): Pair<RenderedSpell, Int> {
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): Triple<RenderedSpell, Int, List<Vec3>> {
val otherHandItem = ctx.caster.getItemInHand(ctx.otherHand)
if (!type.isAssignableFrom(otherHandItem.item.javaClass)) {
throw CastException(CastException.Reason.BAD_OFFHAND_ITEM, type, otherHandItem)
}
val entity = args.getChecked<Entity>(0)
val entity = args.getChecked<ItemEntity>(0)
val patterns = args.getChecked<List<SpellDatum<*>>>(1).map { it.tryGet<HexPattern>() }
if (entity !is ItemEntity)
throw CastException(CastException.Reason.OP_WRONG_TYPE, ItemEntity::class.java, entity)
return Pair(Spell(entity, patterns), cost)
return Triple(Spell(entity, patterns), cost, listOf(entity.position()))
}
private data class Spell(val itemEntity: ItemEntity, val patterns: List<HexPattern>) : RenderedSpell {

View file

@ -22,12 +22,13 @@ object OpPlaceBlock : SpellOperator {
override val argc: Int
get() = 1
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): Pair<RenderedSpell, Int> {
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): Triple<RenderedSpell, Int, List<Vec3>> {
val pos = args.getChecked<Vec3>(0)
ctx.assertVecInRange(pos)
return Pair(
return Triple(
Spell(pos),
10_000
10_000,
listOf(pos)
)
}

View file

@ -8,13 +8,14 @@ import at.petrak.hexcasting.common.casting.CastingContext
import net.minecraft.world.effect.MobEffect
import net.minecraft.world.effect.MobEffectInstance
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.phys.Vec3
import kotlin.math.max
class OpPotionEffect(val effect: MobEffect, val baseCost: Int, val potency: Boolean) : SpellOperator {
override val argc: Int
get() = if (this.potency) 3 else 2
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): Pair<RenderedSpell, Int> {
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): Triple<RenderedSpell, Int, List<Vec3>> {
val target = args.getChecked<LivingEntity>(0)
val duration = max(args.getChecked(1), 0.0)
val potency = if (this.potency)
@ -22,9 +23,10 @@ class OpPotionEffect(val effect: MobEffect, val baseCost: Int, val potency: Bool
else 1.0
val cost = this.baseCost * duration * potency
return Pair(
return Triple(
Spell(effect, target, duration, potency),
cost.toInt()
cost.toInt(),
listOf(target.position())
)
}

View file

@ -5,19 +5,23 @@ import at.petrak.hexcasting.api.Operator
import at.petrak.hexcasting.api.RenderedSpell
import at.petrak.hexcasting.api.SpellDatum
import at.petrak.hexcasting.common.casting.CastingContext
import at.petrak.hexcasting.common.casting.OperatorSideEffect
import net.minecraft.Util
import net.minecraft.network.chat.TextComponent
object OpPrint : Operator {
override fun operate(stack: MutableList<SpellDatum<*>>, ctx: CastingContext): OperationResult {
val datum = stack[stack.lastIndex]
return OperationResult(0, listOf(Spell(datum)))
return OperationResult(
stack, listOf(
OperatorSideEffect.AttemptSpell(Spell(datum), false)
)
)
}
private data class Spell(val datum: SpellDatum<*>) : RenderedSpell {
override fun cast(ctx: CastingContext) {
ctx.caster.sendMessage(
TextComponent(datum.display()),
datum.display(),
Util.NIL_UUID
)
}

View file

@ -10,10 +10,11 @@ import at.petrak.hexcasting.common.casting.ManaHelper
import at.petrak.hexcasting.common.items.magic.ItemPackagedSpell
import net.minecraft.util.Mth
import net.minecraft.world.entity.item.ItemEntity
import net.minecraft.world.phys.Vec3
object OpRecharge : SpellOperator {
override val argc = 1
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): Pair<RenderedSpell, Int> {
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): Triple<RenderedSpell, Int, List<Vec3>> {
val otherHandItem = ctx.caster.getItemInHand(ctx.otherHand)
if (otherHandItem.item !is ItemPackagedSpell) {
throw CastException(CastException.Reason.BAD_OFFHAND_ITEM, ItemPackagedSpell::class.java, otherHandItem)
@ -21,7 +22,7 @@ object OpRecharge : SpellOperator {
val entity = args.getChecked<ItemEntity>(0)
return Pair(Spell(entity), 100_000)
return Triple(Spell(entity), 100_000, listOf(entity.position()))
}
private data class Spell(val itemEntity: ItemEntity) : RenderedSpell {

View file

@ -17,13 +17,14 @@ import net.minecraft.world.phys.Vec3
object OpTheOnlyReasonAnyoneDownloadedPsi : SpellOperator {
override val argc = 1
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): Pair<RenderedSpell, Int> {
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): Triple<RenderedSpell, Int, List<Vec3>> {
val target = args.getChecked<Vec3>(0)
ctx.assertVecInRange(target)
return Pair(
return Triple(
Spell(target),
10_000
10_000,
listOf(target)
)
}

View file

@ -14,13 +14,14 @@ import net.minecraft.world.phys.Vec3
object OpCreateLava : SpellOperator {
override val argc = 1
override val isGreat = true
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): Pair<RenderedSpell, Int> {
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): Triple<RenderedSpell, Int, List<Vec3>> {
val target = args.getChecked<Vec3>(0)
ctx.assertVecInRange(target)
return Pair(
return Triple(
Spell(target),
100_000
100_000,
listOf(target),
)
}

View file

@ -26,16 +26,17 @@ import kotlin.math.roundToInt
object OpFlight : SpellOperator {
override val argc = 3
override val isGreat = true
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): Pair<RenderedSpell, Int> {
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): Triple<RenderedSpell, Int, List<Vec3>> {
val target = args.getChecked<ServerPlayer>(0)
val timeRaw = max(args.getChecked(1), 0.0)
val radiusRaw = max(args.getChecked(2), 0.0)
// Convert to ticks
val time = (timeRaw * 20.0).roundToInt()
return Pair(
return Triple(
Spell(target, time, radiusRaw, ctx.position),
10_000 * (timeRaw * radiusRaw + 1.0).roundToInt()
10_000 * (timeRaw * radiusRaw + 1.0).roundToInt(),
listOf()
)
}

View file

@ -13,12 +13,13 @@ object OpLightning : SpellOperator {
override val argc = 1
override val isGreat = true
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): Pair<RenderedSpell, Int> {
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): Triple<RenderedSpell, Int, List<Vec3>> {
val target = args.getChecked<Vec3>(0)
ctx.assertVecInRange(target)
return Pair(
return Triple(
Spell(target),
150_000
150_000,
listOf(target)
)
}

View file

@ -15,14 +15,15 @@ import net.minecraftforge.network.PacketDistributor
object OpTeleport : SpellOperator {
override val argc = 2
override val isGreat = true
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): Pair<RenderedSpell, Int> {
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): Triple<RenderedSpell, Int, List<Vec3>> {
val teleportee = args.getChecked<Entity>(0)
val delta = args.getChecked<Vec3>(1)
ctx.assertVecInRange(teleportee.position())
return Pair(
return Triple(
Spell(teleportee, delta),
1_000_000
1_000_000,
listOf(teleportee.position().add(delta))
)
}

View file

@ -13,13 +13,14 @@ import net.minecraftforge.network.PacketDistributor
class OpCreateSentinel(val extendsRange: Boolean) : SpellOperator {
override val argc = 1
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): Pair<RenderedSpell, Int> {
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): Triple<RenderedSpell, Int, List<Vec3>> {
val target = args.getChecked<Vec3>(0)
ctx.assertVecInRange(target)
return Pair(
return Triple(
Spell(target, this.extendsRange),
10_000
10_000,
listOf(target)
)
}

View file

@ -7,14 +7,16 @@ import at.petrak.hexcasting.common.casting.CastingContext
import at.petrak.hexcasting.common.lib.HexCapabilities
import at.petrak.hexcasting.common.network.HexMessages
import at.petrak.hexcasting.common.network.MsgSentinelStatusUpdateAck
import net.minecraft.world.phys.Vec3
import net.minecraftforge.network.PacketDistributor
object OpDestroySentinel : SpellOperator {
override val argc = 0
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): Pair<RenderedSpell, Int> {
return Pair(
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): Triple<RenderedSpell, Int, List<Vec3>> {
return Triple(
Spell,
1_000
1_000,
listOf()
)
}

View file

@ -6,10 +6,8 @@ import at.petrak.hexcasting.common.casting.CastingHarness;
import at.petrak.hexcasting.common.casting.ManaHelper;
import at.petrak.hexcasting.common.lib.HexSounds;
import at.petrak.hexcasting.hexmath.HexPattern;
import net.minecraft.Util;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
import net.minecraft.network.chat.TextComponent;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundSource;
@ -59,15 +57,8 @@ public abstract class ItemPackagedSpell extends Item {
var harness = new CastingHarness(ctx);
List<HexPattern> patterns = getPatterns(tag);
for (var pattern : patterns) {
var res = harness.update(pattern, sPlayer.getLevel());
if (res instanceof CastingHarness.CastResult.Error error) {
sPlayer.sendMessage(new TextComponent(error.getExn().getMessage()), Util.NIL_UUID);
} else if (res instanceof CastingHarness.CastResult.Cast cast) {
for (var spell : cast.getSpells()) {
spell.cast(ctx);
}
}
if (res.quitStatus() == CastingHarness.QuitStatus.QUIT) {
var info = harness.executeNewPattern(pattern, sPlayer.getLevel());
if (info.shouldQuit()) {
break;
}
}

View file

@ -1,15 +1,12 @@
package at.petrak.hexcasting.common.network;
import at.petrak.hexcasting.client.gui.GuiSpellcasting;
import at.petrak.hexcasting.common.casting.CastingHarness;
import at.petrak.hexcasting.common.casting.ControllerInfo;
import at.petrak.hexcasting.common.lib.HexSounds;
import io.netty.buffer.ByteBuf;
import net.minecraft.client.Minecraft;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.StringTag;
import net.minecraft.nbt.Tag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.fml.DistExecutor;
import net.minecraftforge.network.NetworkEvent;
@ -21,49 +18,49 @@ import java.util.function.Supplier;
/**
* Sent server->client when the player finishes casting a spell.
*/
public record MsgNewSpellPatternAck(CastingHarness.QuitStatus state, List<String> stackDesc) {
public record MsgNewSpellPatternAck(ControllerInfo info, List<Component> stackDesc) {
private static final String TAG_DESC = "desc";
public static MsgNewSpellPatternAck deserialize(ByteBuf buffer) {
var buf = new FriendlyByteBuf(buffer);
var state = CastingHarness.QuitStatus.values()[buf.readInt()];
var descsTag = buf.readNbt().getList(TAG_DESC, Tag.TAG_STRING);
var descs = new ArrayList<String>(descsTag.size());
for (int i = 0; i < descsTag.size(); i++) {
descs.add(descsTag.getString(i));
var status = ControllerInfo.Status.values()[buf.readInt()];
var descsLen = buf.readInt();
var desc = new ArrayList<Component>(descsLen);
for (int i = 0; i < descsLen; i++) {
desc.add(buf.readComponent());
}
return new MsgNewSpellPatternAck(state, descs);
return new MsgNewSpellPatternAck(
new ControllerInfo(status),
desc
);
}
public void serialize(ByteBuf buffer) {
var buf = new FriendlyByteBuf(buffer);
buf.writeInt(this.state.ordinal());
var descsCTag = new CompoundTag();
var descsTag = new ListTag();
for (String s : this.stackDesc) {
descsTag.add(StringTag.valueOf(s));
buf.writeInt(this.info.getStatus().ordinal());
buf.writeInt(this.stackDesc.size());
for (var desc : this.stackDesc) {
buf.writeComponent(desc);
}
descsCTag.put(TAG_DESC, descsTag);
buf.writeNbt(descsCTag);
}
public void handle(Supplier<NetworkEvent.Context> ctx) {
ctx.get().enqueueWork(() ->
DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> {
var mc = Minecraft.getInstance();
if (this.state == CastingHarness.QuitStatus.QUIT) {
if (this.info.shouldQuit()) {
// don't pay attention to the screen, so it also stops when we die
mc.getSoundManager().stop(HexSounds.CASTING_AMBIANCE.getId(), null);
}
var screen = Minecraft.getInstance().screen;
if (screen instanceof GuiSpellcasting spellGui) {
if (this.state == CastingHarness.QuitStatus.QUIT) {
if (this.info.shouldQuit()) {
mc.setScreen(null);
} else {
spellGui.recvServerUpdate(this.stackDesc,
this.state == CastingHarness.QuitStatus.LAST_PATTERN_INVALID);
spellGui.recvServerUpdate(this.info, this.stackDesc);
}
}
})

View file

@ -1,16 +1,13 @@
package at.petrak.hexcasting.common.network;
import at.petrak.hexcasting.common.casting.CastingContext;
import at.petrak.hexcasting.common.casting.CastingHarness;
import at.petrak.hexcasting.common.casting.CastingHarness.CastResult;
import at.petrak.hexcasting.common.items.ItemWand;
import at.petrak.hexcasting.common.lib.HexSounds;
import at.petrak.hexcasting.hexmath.HexPattern;
import io.netty.buffer.ByteBuf;
import net.minecraft.Util;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.chat.TextComponent;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.InteractionHand;
@ -48,21 +45,16 @@ public record MsgNewSpellPatternSyn(InteractionHand handUsed, HexPattern pattern
var harness = CastingHarness.DeserializeFromNBT(tag.getCompound(ItemWand.TAG_HARNESS), sender,
this.handUsed);
var res = harness.update(this.pattern, sender.getLevel());
if (res instanceof CastResult.Cast success) {
var castCtx = new CastingContext(sender, this.handUsed);
for (var spell : success.getSpells()) {
spell.cast(castCtx);
}
var clientInfo = harness.executeNewPattern(this.pattern, sender.getLevel());
if (clientInfo.wasSpellCast()) {
sender.level.playSound(null, sender.getX(), sender.getY(), sender.getZ(),
HexSounds.ACTUALLY_CAST.get(), SoundSource.PLAYERS, 1f,
1f + ((float) Math.random() - 0.5f) * 0.2f);
} else if (res instanceof CastResult.Error error) {
sender.sendMessage(new TextComponent(error.getExn().getMessage()), Util.NIL_UUID);
}
CompoundTag nextHarnessTag;
if (res.quitStatus() == CastingHarness.QuitStatus.QUIT) {
if (clientInfo.shouldQuit()) {
// discard the changes
nextHarnessTag = new CompoundTag();
} else {
@ -71,14 +63,14 @@ public record MsgNewSpellPatternSyn(InteractionHand handUsed, HexPattern pattern
}
tag.put(ItemWand.TAG_HARNESS, nextHarnessTag);
var descs = new ArrayList<String>(harness.getStack().size());
var descs = new ArrayList<Component>(harness.getStack().size());
for (var datum : harness.getStack()) {
descs.add(datum.display());
}
HexMessages.getNetwork()
.send(PacketDistributor.PLAYER.with(() -> sender),
new MsgNewSpellPatternAck(res.quitStatus(), descs));
new MsgNewSpellPatternAck(clientInfo, descs));
}
}
});

View file

@ -44,7 +44,7 @@
"anchor": "hexcasting:blink",
"input": "entity, number",
"output": "",
"text": "Remove an entity and length from the stack, then teleport the given entity along its look vector by the given length.$(br)Costs about 2 $(item)Amethyst Dust/$s times the square of the distance teleported-- thus, teleporting 5 blocks would cost five $(item)Charged Amethyst Crystals$(0) or equivalent."
"text": "Remove an entity and length from the stack, then teleport the given entity along its look vector by the given length.$(br)Costs about 1 $(item)Amethyst Shard/$s times the the distance teleported."
}
]
}