maybe 1/3 done with reworking the backend

This commit is contained in:
gamma-delta 2022-01-28 20:02:36 -06:00
parent ecd2e65fad
commit 9cf873a40c
16 changed files with 173 additions and 91 deletions

View file

@ -2,6 +2,7 @@ package at.petrak.hexcasting.api
import at.petrak.hexcasting.common.casting.CastException
import at.petrak.hexcasting.common.casting.CastingContext
import at.petrak.hexcasting.common.casting.OperatorSideEffect
/**
* A SimpleOperator that always costs the same amount of mana.
@ -13,15 +14,15 @@ interface ConstManaOperator : Operator {
fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): List<SpellDatum<*>>
override fun modifyStack(stack: MutableList<SpellDatum<*>>, ctx: CastingContext): OperationResult {
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.takeLast(this.argc)
// there's gotta be a better way to do this
for (_idx in 0 until this.argc)
stack.removeLast()
val args = stack.dropLast(this.argc)
val newData = this.execute(args, ctx)
stack.addAll(newData)
return OperationResult(this.manaCost, emptyList())
val sideEffects = mutableListOf<OperatorSideEffect>(OperatorSideEffect.ConsumeMana(this.manaCost))
return OperationResult(stack, sideEffects)
}
}

View file

@ -1,8 +1,8 @@
package at.petrak.hexcasting.api
import at.petrak.hexcasting.common.casting.OperatorSideEffect
/**
* What happens when an operator is through?
*
* This has the mana cost and some spells that might be cast.
*/
data class OperationResult(val manaCost: Int, val spells: List<RenderedSpell>)
data class OperationResult(val newStack: List<SpellDatum<*>>, val sideEffects: List<OperatorSideEffect>)

View file

@ -14,12 +14,18 @@ import net.minecraft.world.phys.Vec3
*/
interface Operator {
/**
* Operate on the stack and return the mana cost.
* Operate on the stack. Return the new stack and any side effects of the cast.
*
* Although this is passed a [MutableList], this is only for the convenience of implementors.
* It is a clone of the stack and modifying it does nothing. You must return the new stack
* with the [OperationResult].
*
* A particle effect at the cast site and various messages and advancements are done automagically.
*/
fun modifyStack(stack: MutableList<SpellDatum<*>>, ctx: CastingContext): OperationResult
fun operate(stack: MutableList<SpellDatum<*>>, ctx: CastingContext): OperationResult
/**
* Do you need to be enlightened to cast this spell?
* Do you need to be enlightened to use this operator?
*/
val isGreat: Boolean get() = false

View file

@ -1,25 +0,0 @@
package at.petrak.hexcasting.api
import at.petrak.hexcasting.common.casting.CastException
import at.petrak.hexcasting.common.casting.CastingContext
/**
* An operator that acts in the expected method of popping some arguments
* and pushing some more arguments, not returning any spells.
*/
interface SimpleOperator : Operator {
val argc: Int
fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): Pair<List<SpellDatum<*>>, Int>
override fun modifyStack(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.takeLast(this.argc)
// there's gotta be a better way to do this
for (_idx in 0 until this.argc)
stack.removeLast()
val (newData, mana) = this.execute(args, ctx)
stack.addAll(newData)
return OperationResult(mana, emptyList())
}
}

View file

@ -2,19 +2,28 @@ package at.petrak.hexcasting.api
import at.petrak.hexcasting.common.casting.CastException
import at.petrak.hexcasting.common.casting.CastingContext
import at.petrak.hexcasting.common.casting.OperatorSideEffect
import net.minecraft.world.phys.Vec3
interface SpellOperator : Operator {
val argc: Int
fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): Pair<RenderedSpell, Int>
fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): Triple<RenderedSpell, Int, List<Vec3>>
override fun modifyStack(stack: MutableList<SpellDatum<*>>, ctx: CastingContext): OperationResult {
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.takeLast(this.argc)
for (_idx in 0 until this.argc)
stack.removeLast()
val (spell, mana) = this.execute(args, ctx)
return OperationResult(mana, listOf(spell))
val args = stack.dropLast(this.argc)
val (spell, mana, particlePoses) = this.execute(args, ctx)
val sideEffects = mutableListOf(
OperatorSideEffect.ConsumeMana(mana),
OperatorSideEffect.AttemptSpell(spell, this.isGreat)
)
for (pos in particlePoses) {
sideEffects.add(OperatorSideEffect.Particles(pos))
}
return OperationResult(stack, sideEffects)
}
}

View file

@ -78,7 +78,8 @@ public class HexAdditionalRenderers {
Consumer<float[]> v = (point) -> {
var color = -1;
if (finalCap != null) {
color = finalCap.getColor(owner, time, new Vec3(point[0], point[1], point[2]));
color = CapPreferredColorizer.getColor(finalCap.colorizer, owner, time,
new Vec3(point[0], point[1], point[2]));
}
buf.vertex(neo, point[0], point[1], point[2])
.color(color)

View file

@ -24,10 +24,12 @@ data class CastingContext(
val castingHand: InteractionHand,
) {
private var depth: Int = 0
val world: ServerLevel get() = caster.getLevel()
val otherHand: InteractionHand get() = HexUtils.OtherHand(this.castingHand)
val position: Vec3 get() = caster.position()
fun getSpellbook(): ItemStack {
val handItem =
caster.getItemInHand(this.otherHand)

View file

@ -2,22 +2,22 @@ package at.petrak.hexcasting.common.casting
import at.petrak.hexcasting.HexMod
import at.petrak.hexcasting.api.PatternRegistry
import at.petrak.hexcasting.api.RenderedSpell
import at.petrak.hexcasting.api.SpellDatum
import at.petrak.hexcasting.common.items.HexItems
import at.petrak.hexcasting.common.items.ItemWand
import at.petrak.hexcasting.common.items.magic.ItemPackagedSpell
import at.petrak.hexcasting.common.lib.HexCapabilities
import at.petrak.hexcasting.common.lib.HexDamageSources
import at.petrak.hexcasting.common.lib.HexStatistics
import at.petrak.hexcasting.datagen.Advancements
import at.petrak.hexcasting.hexmath.HexPattern
import net.minecraft.Util
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.ListTag
import net.minecraft.nbt.Tag
import net.minecraft.network.chat.TranslatableComponent
import net.minecraft.server.level.ServerLevel
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.InteractionHand
import net.minecraft.world.item.ItemStack
import java.util.*
import kotlin.math.min
import kotlin.random.Random
@ -33,8 +33,11 @@ class CastingHarness private constructor(
var parenthesized: MutableList<HexPattern>,
var escapeNext: Boolean,
val ctx: CastingContext,
val prepackagedColorizer: ItemStack?
) {
constructor(ctx: CastingContext) : this(mutableListOf(), 0, mutableListOf(), false, ctx)
constructor(ctx: CastingContext) : this(mutableListOf(), 0, mutableListOf(), false, ctx, null)
/**
* When the server gets a packet from the client with a new pattern,
@ -42,7 +45,6 @@ class CastingHarness private constructor(
*/
fun update(newPat: HexPattern, world: ServerLevel): CastResult {
return try {
var spellsToCast = emptyList<RenderedSpell>()
var exn: CastException? = null
val operator = try {
PatternRegistry.matchPattern(newPat, world)
@ -89,34 +91,9 @@ class CastingHarness private constructor(
throw CastException(CastException.Reason.TOO_MANY_CLOSE_PARENS)
} else {
// we know the operator is ok here
val (manaCost, spells) = operator!!.modifyStack(this.stack, this.ctx)
val leftover = this.withdrawMana(manaCost, ctx.canOvercast)
if (ctx.caster.isDeadOrDying)
return CastResult.Died
else if (leftover > 0) {
if (!ctx.canOvercast) {
ctx.caster.sendMessage(
TranslatableComponent("hexcasting.message.cant_overcast"),
Util.NIL_UUID
)
}
return CastResult.QuitCasting
}
// is great IMPLIES caster is enlightened
if (!operator.isGreat || ctx.isCasterEnlightened) {
spellsToCast = spells
} else if (operator.isGreat) {
ctx.caster.sendMessage(
TranslatableComponent("hexcasting.message.cant_great_spell"),
Util.NIL_UUID
)
Advancements.FAIL_GREAT_SPELL_TRIGGER.trigger(ctx.caster)
}
val (stackPrime, sideEffects) = operator!!.operate(this.stack, this.ctx)
}
if (spellsToCast.isNotEmpty()) {
@ -201,6 +178,18 @@ class CastingHarness private constructor(
return costLeft
}
fun getColorizer(): ItemStack {
if (this.prepackagedColorizer != null)
return this.prepackagedColorizer
val maybeCap = this.ctx.caster.getCapability(HexCapabilities.PREFERRED_COLORIZER).resolve()
if (maybeCap.isEmpty) {
// uh oh
return ItemStack(HexItems.DYE_COLORIZERS[0].get())
}
return maybeCap.get().colorizer
}
fun serializeToNBT(): CompoundTag {
val out = CompoundTag()
@ -218,6 +207,10 @@ class CastingHarness private constructor(
parensTag.add(pat.serializeToNBT())
out.put(TAG_PARENTHESIZED, parensTag)
if (this.prepackagedColorizer != null) {
out.put(TAG_PREPACKAGED_COLORIZER, this.prepackagedColorizer.serializeNBT())
}
return out
}
@ -227,6 +220,7 @@ class CastingHarness private constructor(
const val TAG_PAREN_COUNT = "open_parens"
const val TAG_PARENTHESIZED = "parenthesized"
const val TAG_ESCAPE_NEXT = "escape_next"
const val TAG_PREPACKAGED_COLORIZER = "prepackaged_colorizer"
@JvmStatic
fun DeserializeFromNBT(nbt: Tag?, caster: ServerPlayer, wandHand: InteractionHand): CastingHarness {
@ -250,7 +244,13 @@ class CastingHarness private constructor(
val parenCount = nbt.getInt(TAG_PAREN_COUNT)
val escapeNext = nbt.getBoolean(TAG_ESCAPE_NEXT)
CastingHarness(stack, parenCount, parenthesized, escapeNext, ctx)
val colorizer = if (nbt.contains(TAG_PREPACKAGED_COLORIZER)) {
ItemStack.of(nbt.getCompound(TAG_PREPACKAGED_COLORIZER))
} else {
null
}
CastingHarness(stack, parenCount, parenthesized, escapeNext, ctx, colorizer)
} catch (exn: Exception) {
HexMod.LOGGER.warn("Couldn't load harness from nbt tag, falling back to default: $nbt: $exn")
CastingHarness(ctx)
@ -266,7 +266,7 @@ class CastingHarness private constructor(
object QuitCasting : CastResult()
/** Finished casting */
data class Cast(val spells: List<RenderedSpell>, val quit: Boolean) : CastResult()
data class Cast(val sideEffects: List<OperatorSideEffect>, val quit: Boolean) : CastResult()
/** uh-oh */
data class Error(val exn: CastException) : CastResult()

View file

@ -0,0 +1,87 @@
package at.petrak.hexcasting.common.casting
import at.petrak.hexcasting.api.RenderedSpell
import at.petrak.hexcasting.api.SpellDatum
import at.petrak.hexcasting.common.casting.colors.CapPreferredColorizer
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.TranslatableComponent
import net.minecraft.util.FastColor
import net.minecraft.world.phys.Vec3
import kotlin.random.Random
import kotlin.random.nextInt
/**
* Things that happen after a spell is cast.
*/
sealed class OperatorSideEffect {
/** Return whether to cancel all further [OperatorSideEffect] */
abstract fun performEffect(harness: CastingHarness): Boolean
/** Try to cast a spell */
data class AttemptSpell(val spell: RenderedSpell, val isGreat: Boolean) : OperatorSideEffect() {
override fun performEffect(harness: CastingHarness): Boolean {
return if (this.isGreat && !harness.ctx.isCasterEnlightened) {
harness.ctx.caster.sendMessage(
TranslatableComponent("hexcasting.message.cant_great_spell"),
Util.NIL_UUID
)
Advancements.FAIL_GREAT_SPELL_TRIGGER.trigger(harness.ctx.caster)
true
} else {
this.spell.cast(harness.ctx)
false
}
}
}
data class ConsumeMana(val amount: Int) : OperatorSideEffect() {
override fun performEffect(harness: CastingHarness): Boolean {
val overcastOk = harness.ctx.canOvercast
val leftoverMana = harness.withdrawMana(this.amount, overcastOk)
if (leftoverMana > 0 && overcastOk) {
harness.ctx.caster.sendMessage(
TranslatableComponent("hexcasting.message.cant_overcast"),
Util.NIL_UUID
)
}
return leftoverMana > 0
}
}
data class Particles(val position: Vec3) : OperatorSideEffect() {
override fun performEffect(harness: CastingHarness): Boolean {
val colorizer = harness.getColorizer()
for (i in 0 until 6) {
// 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(
DustParticleOptions(Vector3f(r.toFloat(), g.toFloat(), b.toFloat()), 1f),
position.x,
position.y,
position.z,
0.1,
0.1,
0.1
)
}
return false
}
}
object AddGarbage : OperatorSideEffect() {
override fun performEffect(harness: CastingHarness): Boolean {
val idx = Random.nextInt(0..harness.stack.size)
harness.stack.add(idx, SpellDatum.make(Widget.GARBAGE))
return false
}
}
}

View file

@ -5,11 +5,11 @@ import at.petrak.hexcasting.common.items.HexItems;
import at.petrak.hexcasting.common.items.colorizer.ItemDyeColorizer;
import at.petrak.hexcasting.common.items.colorizer.ItemPoliticalColorizer;
import at.petrak.hexcasting.common.lib.HexCapabilities;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.util.FastColor;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.DyeColor;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
@ -44,8 +44,8 @@ public class CapPreferredColorizer implements ICapabilitySerializable<CompoundTa
* @param position a position for the icosahedron, a randomish number for particles.
* @return an AARRGGBB color.
*/
public int getColor(LocalPlayer asker, float time, Vec3 position) {
var proto = this.colorizer.getItem();
public static int getColor(ItemStack colorizer, Player asker, float time, Vec3 position) {
var proto = colorizer.getItem();
if (proto instanceof ItemDyeColorizer dye) {
return DyeColor.values()[dye.getDyeIdx()].getTextColor() | 0xff_000000;
} else if (proto instanceof ItemPoliticalColorizer politics) {
@ -68,7 +68,7 @@ public class CapPreferredColorizer implements ICapabilitySerializable<CompoundTa
return 0xff_ff00dc; // missing color
}
private int morphBetweenColors(int[] colors, Vec3 gradientDir, float time, Vec3 position) {
private static int morphBetweenColors(int[] colors, Vec3 gradientDir, float time, Vec3 position) {
float fIdx = ((time + (float) gradientDir.dot(position)) % 1f) * colors.length;
int baseIdx = Mth.floor(fIdx);

View file

@ -9,7 +9,7 @@ import at.petrak.hexcasting.common.casting.CastingContext
import at.petrak.hexcasting.common.casting.CastingHarness
object OpEval : Operator {
override fun modifyStack(stack: MutableList<SpellDatum<*>>, ctx: CastingContext): OperationResult {
override fun operate(stack: MutableList<SpellDatum<*>>, ctx: CastingContext): OperationResult {
val instrs: List<SpellDatum<*>> = stack.getChecked(stack.lastIndex)
stack.removeLastOrNull()

View file

@ -12,7 +12,7 @@ import kotlin.math.max
import kotlin.math.roundToInt
object OpEvalDelay : Operator {
override fun modifyStack(stack: MutableList<SpellDatum<*>>, ctx: CastingContext): OperationResult {
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()

View file

@ -11,7 +11,7 @@ import at.petrak.hexcasting.common.casting.CastingHarness
import at.petrak.hexcasting.hexmath.HexPattern
object OpForEach : Operator {
override fun modifyStack(stack: MutableList<SpellDatum<*>>, ctx: CastingContext): OperationResult {
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)

View file

@ -16,12 +16,13 @@ object OpAddMotion : 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 target = args.getChecked<Entity>(0)
val motion = args.getChecked<Vec3>(1)
return Pair(
return Triple(
Spell(target, motion),
(motion.lengthSqr() * 10_000f).toInt()
(motion.lengthSqr() * 10_000f).toInt(),
listOf(),
)
}

View file

@ -31,7 +31,7 @@ object OpColorize : SpellOperator {
val otherHandItem = ctx.caster.getItemInHand(ctx.otherHand)
if (CapPreferredColorizer.isColorizer(otherHandItem.item)) {
val copied = ItemStack(otherHandItem.item, 1)
otherHandItem.shrink(1)
ctx.withdrawItem(otherHandItem.item, 1, true)
cap.colorizer = copied
HexMessages.getNetwork().send(PacketDistributor.PLAYER.with { ctx.caster }, MsgColorizerUpdateAck(cap))

View file

@ -9,7 +9,7 @@ import net.minecraft.Util
import net.minecraft.network.chat.TextComponent
object OpPrint : Operator {
override fun modifyStack(stack: MutableList<SpellDatum<*>>, ctx: CastingContext): OperationResult {
override fun operate(stack: MutableList<SpellDatum<*>>, ctx: CastingContext): OperationResult {
val datum = stack[stack.lastIndex]
return OperationResult(0, listOf(Spell(datum)))
}