From 11042f3008b8c28fa9d65ed120b6f2caf4c5e4c7 Mon Sep 17 00:00:00 2001 From: gamma-delta <29877714+gamma-delta@users.noreply.github.com> Date: Thu, 30 Dec 2021 16:00:20 -0600 Subject: [PATCH] 3 new spells and fix a bunch of bugs, closes #1 --- original_ideas_doc.txt | 21 +++ src/main/java/at/petrak/hex/HexConfig.java | 42 +++++- src/main/java/at/petrak/hex/HexMod.java | 35 ++--- .../at/petrak/hex/api/ConstManaOperator.kt | 2 +- .../java/at/petrak/hex/api/SimpleOperator.kt | 7 +- .../java/at/petrak/hex/api/SpellOperator.kt | 23 +-- .../hex/client/RegisterClientStuff.java | 2 +- .../hex/common/casting/CastException.kt | 16 ++ .../hex/common/casting/CastingContext.kt | 141 +++++++++++++++++- .../hex/common/casting/CastingHarness.kt | 32 ++-- .../petrak/hex/common/casting/SpellWidget.kt | 4 +- .../casting/operators/OpBlockAxisRaycast.kt | 5 +- .../casting/operators/OpBlockRaycast.kt | 5 +- .../common/casting/operators/OpDuplicate.kt | 4 +- .../common/casting/operators/OpEntityLook.kt | 4 +- .../common/casting/operators/OpEntityPos.kt | 4 +- .../casting/operators/OpEntityRaycast.kt | 5 +- .../hex/common/casting/operators/OpEval.kt | 22 +-- .../common/casting/operators/OpGetCaster.kt | 4 +- .../hex/common/casting/operators/OpRead.kt | 14 +- .../hex/common/casting/operators/OpSwap.kt | 4 +- .../hex/common/casting/operators/OpUndo.kt | 4 +- .../hex/common/casting/operators/OpWrite.kt | 14 +- .../common/casting/operators/math/OpAbsLen.kt | 4 +- .../common/casting/operators/math/OpAdd.kt | 4 +- .../casting/operators/math/OpDivCross.kt | 4 +- .../common/casting/operators/math/OpMulDot.kt | 4 +- .../casting/operators/math/OpPowProj.kt | 4 +- .../common/casting/operators/math/OpSub.kt | 4 +- .../casting/operators/spells/OpAddMotion.kt | 7 +- .../casting/operators/spells/OpBreakBlock.kt | 43 ++++++ .../casting/operators/spells/OpExplode.kt | 25 +++- .../casting/operators/spells/OpLightning.kt | 34 +++++ .../casting/operators/spells/OpPlaceBlock.kt | 71 +++++++++ .../casting/operators/spells/OpPrint.kt | 4 +- .../at/petrak/hex/common/items/HexItems.java | 27 +++- .../hex/common/items/ItemDataHolder.java | 18 ++- .../at/petrak/hex/common/items/ItemFocus.java | 22 ++- .../hex/common/items/ItemSpellbook.java | 48 +++--- .../at/petrak/hex/common/items/ItemWand.java | 64 ++++++++ .../hex/common/lib/LibDamageSources.java | 7 +- .../common/network/MsgNewSpellPatternAck.java | 3 +- .../common/network/MsgNewSpellPatternSyn.java | 9 +- .../common/network/MsgQuitSpellcasting.java | 2 +- .../at/petrak/hex/server/TickScheduler.kt | 28 ++++ src/main/resources/assets/hex/lang/en_US.json | 4 +- 46 files changed, 698 insertions(+), 156 deletions(-) create mode 100644 original_ideas_doc.txt create mode 100644 src/main/java/at/petrak/hex/common/casting/operators/spells/OpBreakBlock.kt create mode 100644 src/main/java/at/petrak/hex/common/casting/operators/spells/OpLightning.kt create mode 100644 src/main/java/at/petrak/hex/common/casting/operators/spells/OpPlaceBlock.kt diff --git a/original_ideas_doc.txt b/original_ideas_doc.txt new file mode 100644 index 00000000..d12f56dd --- /dev/null +++ b/original_ideas_doc.txt @@ -0,0 +1,21 @@ +PieceTrickBlaze.java +PieceTrickBreakLoop.java +PieceTrickChangeSlot.java +PieceTrickDebug.java +PieceTrickDebugSpamless.java +PieceTrickDelay.java +PieceTrickDetonate.java +PieceTrickDie.java +PieceTrickEidosAnchor.java +PieceTrickEidosReversal.java +PieceTrickEvaluate.java +PieceTrickExplode.java +PieceTrickOvergrow.java +PieceTrickParticleTrail.java +PieceTrickPlaySound.java +PieceTrickRussianRoulette.java +PieceTrickSaveVector.java +PieceTrickSmite.java +PieceTrickSpinChamber.java +PieceTrickSwitchTargetSlot.java +PieceTrickTorrent.java \ No newline at end of file diff --git a/src/main/java/at/petrak/hex/HexConfig.java b/src/main/java/at/petrak/hex/HexConfig.java index cca0245a..2bb94c68 100644 --- a/src/main/java/at/petrak/hex/HexConfig.java +++ b/src/main/java/at/petrak/hex/HexConfig.java @@ -1,2 +1,42 @@ -package at.petrak.hex;public class HexConfig { +package at.petrak.hex; + +import net.minecraft.world.item.Tier; +import net.minecraft.world.item.Tiers; +import net.minecraftforge.common.ForgeConfigSpec; + +public class HexConfig { + public final ForgeConfigSpec.IntValue maxRecurseDepth; + public final ForgeConfigSpec.IntValue wandRechargeRate; + public final ForgeConfigSpec.DoubleValue healthToManaRate; + public final ForgeConfigSpec.IntValue opBreakHarvestLevel; + + public HexConfig(ForgeConfigSpec.Builder builder) { + maxRecurseDepth = builder.comment("How many times an Eval can recursively cast itself") + .defineInRange("maxRecurseDepth", 15, 0, Integer.MAX_VALUE); + wandRechargeRate = builder.comment("How many mana points a wand recharges per tick") + .defineInRange("wandRechargeRate", 2, 0, Integer.MAX_VALUE); + healthToManaRate = builder.comment("How many points of mana a half-heart is worth when casting from HP") + .defineInRange("healthToManaRate", 10.0, 0.0, 1_000_000.0); + + builder.push("spells"); + opBreakHarvestLevel = builder.comment( + "The harvest level of the Break Block spell.", + "0 = wood, 1 = stone, 2 = iron, 3 = diamond, 4 = netherite." + ).defineInRange("opBreakHarvestLevel", 3, 0, 4); + builder.pop(); + } + + /** + * i'm not kidding look upon net.minecraftforge.common.TierSortingRegistry and weep + */ + public Tier getOpBreakHarvestLevelBecauseForgeThoughtItWasAGoodIdeaToImplementHarvestTiersUsingAnHonestToGodTopoSort() { + return switch (this.opBreakHarvestLevel.get()) { + case 0 -> Tiers.WOOD; + case 1 -> Tiers.STONE; + case 2 -> Tiers.IRON; + case 3 -> Tiers.DIAMOND; + case 4 -> Tiers.NETHERITE; + default -> throw new RuntimeException("unreachable"); + }; + } } diff --git a/src/main/java/at/petrak/hex/HexMod.java b/src/main/java/at/petrak/hex/HexMod.java index a97fa756..5243de28 100644 --- a/src/main/java/at/petrak/hex/HexMod.java +++ b/src/main/java/at/petrak/hex/HexMod.java @@ -6,12 +6,12 @@ import at.petrak.hex.common.casting.SpellDatum; import at.petrak.hex.common.casting.SpellWidget; import at.petrak.hex.common.casting.operators.*; import at.petrak.hex.common.casting.operators.math.*; -import at.petrak.hex.common.casting.operators.spells.OpAddMotion; -import at.petrak.hex.common.casting.operators.spells.OpExplode; -import at.petrak.hex.common.casting.operators.spells.OpPrint; +import at.petrak.hex.common.casting.operators.spells.*; import at.petrak.hex.common.items.HexItems; import at.petrak.hex.common.network.HexMessages; +import at.petrak.hex.server.TickScheduler; import com.mojang.datafixers.util.Pair; +import net.minecraftforge.common.ForgeConfigSpec; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; @@ -24,6 +24,14 @@ import org.apache.logging.log4j.Logger; public class HexMod { // hmm today I will use a popular logging framework :clueless: public static final Logger LOGGER = LogManager.getLogger(); + public static final HexConfig CONFIG; + public static final ForgeConfigSpec CONFIG_SPEC; + + static { + final var specPair = new ForgeConfigSpec.Builder().configure(HexConfig::new); + CONFIG = specPair.getLeft(); + CONFIG_SPEC = specPair.getRight(); + } public static final String MOD_ID = "hex"; @@ -32,8 +40,10 @@ public class HexMod { var evbus = FMLJavaModLoadingContext.get().getModEventBus(); MinecraftForge.EVENT_BUS.register(this); evbus.register(HexMod.class); + HexItems.ITEMS.register(evbus); HexMessages.register(); + MinecraftForge.EVENT_BUS.register(TickScheduler.INSTANCE); } // I guess this means the client will have a big empty map for patterns @@ -44,32 +54,22 @@ public class HexMod { for (Pair p : new Pair[]{ // == Getters == - // diamond shape to get the caster new Pair<>("qaq", OpGetCaster.INSTANCE), new Pair<>("ede", OpGetCaster.INSTANCE), - // small triangle to get the entity pos new Pair<>("aa", OpEntityPos.INSTANCE), new Pair<>("dd", OpEntityPos.INSTANCE), - // Arrow to get the look vector new Pair<>("wa", OpEntityLook.INSTANCE), new Pair<>("wd", OpEntityLook.INSTANCE), - // CCW battleaxe for block raycast new Pair<>("wqaawdd", OpBlockRaycast.INSTANCE), - // and CW for axis raycast new Pair<>("weddwaa", OpBlockAxisRaycast.INSTANCE), - // CCW diamond mace thing for entity raycast new Pair<>("weaqa", OpEntityRaycast.INSTANCE), // == Modify Stack == - // CCW hook for undo new Pair<>("a", OpUndo.INSTANCE), - // and CW for null new Pair<>("d", SpellWidget.NULL), - // Two triangles holding hands to duplicate new Pair<>("aadaa", OpDuplicate.INSTANCE), - // Two opposing triangles to swap new Pair<>("aawdd", OpSwap.INSTANCE), // == Math == @@ -82,12 +82,13 @@ public class HexMod { // == Spells == - // hook for debug new Pair<>("de", OpPrint.INSTANCE), new Pair<>("aq", OpPrint.INSTANCE), - // nuclear sign for explosion new Pair<>("aawaawaa", OpExplode.INSTANCE), new Pair<>("weeewdq", OpAddMotion.INSTANCE), + new Pair<>("qaqqqqq", OpPlaceBlock.INSTANCE), + new Pair<>("eeeeede", OpBreakBlock.INSTANCE), + new Pair<>("waadwawdaaweewq", OpLightning.INSTANCE), // == Meta stuff == new Pair<>("qqq", SpellWidget.OPEN_PAREN), @@ -96,8 +97,8 @@ public class HexMod { // http://www.toroidalsnark.net/mkss3-pix/CalderheadJMM2014.pdf // eval being a space filling curve feels apt doesn't it new Pair<>("deaqq", OpEval.INSTANCE), - new Pair<>("aqqqqq", OpReadFromSpellbook.INSTANCE), - new Pair<>("deeeee", OpWriteToSpellbook.INSTANCE), + new Pair<>("aqqqqq", OpRead.INSTANCE), + new Pair<>("deeeee", OpWrite.INSTANCE), }) { PatternRegistry.addRegularPattern(p.getFirst(), p.getSecond()); count++; diff --git a/src/main/java/at/petrak/hex/api/ConstManaOperator.kt b/src/main/java/at/petrak/hex/api/ConstManaOperator.kt index 6cd20c1c..588c1081 100644 --- a/src/main/java/at/petrak/hex/api/ConstManaOperator.kt +++ b/src/main/java/at/petrak/hex/api/ConstManaOperator.kt @@ -7,7 +7,7 @@ import at.petrak.hex.common.casting.SpellDatum /** * A SimpleOperator that always costs the same amount of mana. */ -interface SimpleFreeOperator : SpellOperator { +interface ConstManaOperator : SpellOperator { val argc: Int val manaCost: Int get() = 0 diff --git a/src/main/java/at/petrak/hex/api/SimpleOperator.kt b/src/main/java/at/petrak/hex/api/SimpleOperator.kt index b3e60dfe..3b90724f 100644 --- a/src/main/java/at/petrak/hex/api/SimpleOperator.kt +++ b/src/main/java/at/petrak/hex/api/SimpleOperator.kt @@ -10,16 +10,17 @@ import at.petrak.hex.common.casting.SpellDatum */ interface SimpleOperator : SpellOperator { val argc: Int - fun execute(args: List>, ctx: CastingContext): List> + fun execute(args: List>, ctx: CastingContext): Pair>, Int> - override fun modifyStack(stack: MutableList>, ctx: CastingContext) { + override fun modifyStack(stack: MutableList>, ctx: CastingContext): Int { 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 = this.execute(args, ctx) + val (newData, mana) = this.execute(args, ctx) stack.addAll(newData) + return mana } } \ No newline at end of file diff --git a/src/main/java/at/petrak/hex/api/SpellOperator.kt b/src/main/java/at/petrak/hex/api/SpellOperator.kt index 1fe26858..cae67f9c 100644 --- a/src/main/java/at/petrak/hex/api/SpellOperator.kt +++ b/src/main/java/at/petrak/hex/api/SpellOperator.kt @@ -1,6 +1,5 @@ package at.petrak.hex.api -import at.petrak.hex.common.casting.CastException import at.petrak.hex.common.casting.CastingContext import at.petrak.hex.common.casting.SpellDatum import net.minecraft.world.phys.Vec3 @@ -14,10 +13,10 @@ import net.minecraft.world.phys.Vec3 * Implementors MUST NOT mutate the context. */ interface SpellOperator { - val manaCost: Int - get() = 0 - - fun modifyStack(stack: MutableList>, ctx: CastingContext) + /** + * Operate on the stack and return the mana cost. + */ + fun modifyStack(stack: MutableList>, ctx: CastingContext): Int companion object { // I see why vzakii did this: you can't raycast out to infinity! @@ -42,15 +41,6 @@ interface SpellOperator { this.getChecked(idx) } - /** - * Make sure the vector is in range of the player. - */ - @JvmStatic - fun assertVecInRange(vec: Vec3, ctx: CastingContext) { - if (vec.distanceToSqr(ctx.caster.position()) > MAX_DISTANCE * MAX_DISTANCE) - throw CastException(CastException.Reason.TOO_FAR, vec) - } - @JvmStatic fun spellListOf(vararg vs: Any): List> { val out = ArrayList>(vs.size) @@ -61,11 +51,12 @@ interface SpellOperator { } @JvmStatic - fun makeConstantOp(x: SpellDatum<*>): SpellOperator = object : SimpleOperator { + fun makeConstantOp(x: SpellDatum<*>): SpellOperator = object : ConstManaOperator { override val argc: Int get() = 0 - override fun execute(args: List>, ctx: CastingContext): List> = listOf(x) + override fun execute(args: List>, ctx: CastingContext): List> = + listOf(x) } } } \ No newline at end of file diff --git a/src/main/java/at/petrak/hex/client/RegisterClientStuff.java b/src/main/java/at/petrak/hex/client/RegisterClientStuff.java index 27274a6e..21dab313 100644 --- a/src/main/java/at/petrak/hex/client/RegisterClientStuff.java +++ b/src/main/java/at/petrak/hex/client/RegisterClientStuff.java @@ -17,7 +17,7 @@ public class RegisterClientStuff { evt.enqueueWork(() -> ItemProperties.register(HexItems.FOCUS.get(), ItemFocus.PREDICATE, (stack, level, holder, holderID) -> { if (stack.hasTag()) { - var tagname = stack.getTag().getAllKeys().iterator().next(); + var tagname = stack.getTag().getCompound(ItemFocus.TAG_DATA).getAllKeys().iterator().next(); return switch (tagname) { case SpellDatum.TAG_ENTITY -> 1f; case SpellDatum.TAG_DOUBLE -> 2f; diff --git a/src/main/java/at/petrak/hex/common/casting/CastException.kt b/src/main/java/at/petrak/hex/common/casting/CastException.kt index 760cddd1..bb8fa592 100644 --- a/src/main/java/at/petrak/hex/common/casting/CastException.kt +++ b/src/main/java/at/petrak/hex/common/casting/CastException.kt @@ -57,6 +57,20 @@ class CastException(val reason: Reason, vararg val data: Any) : Exception() { * `no args` */ REQUIRES_SPELLBOOK, + + /** + * An operator needed a data holder in the offhand. + * + * `no args` + */ + REQUIRES_DATA_HOLDER, + + /** + * We went too deep! + * + * `maxDepth: Int, gotDepth: Int` + */ + TOO_MANY_RECURSIVE_EVALS, } override val message: String @@ -68,5 +82,7 @@ class CastException(val reason: Reason, vararg val data: Any) : Exception() { Reason.TOO_MANY_CLOSE_PARENS -> "too many close parentheses" Reason.TOO_FAR -> "tried to interact with something too far away at ${this.data[0] as Vec3}" Reason.REQUIRES_SPELLBOOK -> "required a spellbook in the other hand" + Reason.REQUIRES_DATA_HOLDER -> "required a data holder in the other hand" + Reason.TOO_MANY_RECURSIVE_EVALS -> "can only recursively call OpEval ${this.data[0] as Int} times but called it ${this.data[1] as Int} times" } } \ No newline at end of file diff --git a/src/main/java/at/petrak/hex/common/casting/CastingContext.kt b/src/main/java/at/petrak/hex/common/casting/CastingContext.kt index 73d753c7..3925e21c 100644 --- a/src/main/java/at/petrak/hex/common/casting/CastingContext.kt +++ b/src/main/java/at/petrak/hex/common/casting/CastingContext.kt @@ -1,29 +1,166 @@ package at.petrak.hex.common.casting +import at.petrak.hex.HexMod import at.petrak.hex.HexUtils +import at.petrak.hex.api.SpellOperator +import at.petrak.hex.common.items.ItemDataHolder import at.petrak.hex.common.items.ItemSpellbook +import at.petrak.hex.common.items.ItemWand +import at.petrak.hex.common.lib.LibDamageSources import net.minecraft.server.level.ServerLevel import net.minecraft.server.level.ServerPlayer import net.minecraft.world.InteractionHand +import net.minecraft.world.item.Item import net.minecraft.world.item.ItemStack +import net.minecraft.world.phys.Vec3 +import java.util.function.Predicate +import kotlin.math.max +import kotlin.math.min /** * Info about the moment the spell started being cast. */ -@JvmRecord data class CastingContext( val caster: ServerPlayer, val wandHand: InteractionHand, ) { + private var depth: Int = 0 val world: ServerLevel get() = caster.getLevel() + val otherHand: InteractionHand get() = HexUtils.OtherHand(this.wandHand) fun getSpellbook(): ItemStack { val handItem = - caster.getItemInHand(HexUtils.OtherHand(wandHand)) + caster.getItemInHand(this.otherHand) return if (handItem.item is ItemSpellbook) { handItem } else { throw CastException(CastException.Reason.REQUIRES_SPELLBOOK) } } + + fun getDataHolder(): ItemStack { + val handItem = + caster.getItemInHand(this.otherHand) + return if (handItem.item is ItemDataHolder) { + handItem + } else { + throw CastException(CastException.Reason.REQUIRES_DATA_HOLDER) + } + } + + /** + * Throws if we get too deep + */ + fun withIncDepth(): CastingContext { + val next = this.copy() + next.depth++ + + val maxAllowedDepth = HexMod.CONFIG.maxRecurseDepth.get() + if (next.depth > maxAllowedDepth) { + throw CastException(CastException.Reason.TOO_MANY_RECURSIVE_EVALS, maxAllowedDepth, next.depth) + } else { + return next + } + } + + /** + * Might cast from hitpoints. + * Returns the mana cost still remaining after we deplete everything. It will be <= 0 if we could pay for it. */ + fun withdrawMana(manaCost: Int, allowOvercast: Boolean): Int { + var costLeft = manaCost + + val held = caster.getItemInHand(this.wandHand) + if (held.item is ItemWand) { + val tag = held.orCreateTag + val manaHere = tag.getInt(ItemWand.TAG_MANA) + val manaLeft = manaHere - costLeft + tag.putInt(ItemWand.TAG_MANA, max(0, manaLeft)) + costLeft = max(0, costLeft - manaHere) + } + if (allowOvercast && costLeft > 0) { + // Cast from HP! + val healthToMana = HexMod.CONFIG.healthToManaRate.get() + val healthtoRemove = healthToMana * costLeft.toDouble() + val manaAbleToCastFromHP = + if (caster.isInvulnerable) Double.POSITIVE_INFINITY else caster.health / healthToMana + caster.hurt(LibDamageSources.OVERCAST, healthtoRemove.toFloat()) + costLeft = (costLeft.toDouble() - manaAbleToCastFromHP).toInt() + } + return costLeft + } + + fun assertVecInRange(vec: Vec3) { + if (vec.distanceToSqr(this.caster.position()) > SpellOperator.MAX_DISTANCE * SpellOperator.MAX_DISTANCE) + throw CastException(CastException.Reason.TOO_FAR, vec) + } + + /** + * Return the slot from which to take blocks and items. + */ + // https://wiki.vg/Inventory is WRONG + // slots 0-8 are the hotbar + // for what purpose i cannot imagine + // http://redditpublic.com/images/b/b2/Items_slot_number.png looks right + // and offhand is 150 Inventory.java:464 + fun getOperativeSlot(stackOK: Predicate): Int? { + val otherHandStack = this.caster.getItemInHand(this.otherHand) + if (stackOK.test(otherHandStack)) { + return when (this.otherHand) { + InteractionHand.MAIN_HAND -> this.caster.inventory.selected + InteractionHand.OFF_HAND -> 150 + } + } + val anchorSlot = when (this.wandHand) { + // slot to the right of the wand + InteractionHand.MAIN_HAND -> (this.caster.inventory.selected + 1) % 9 + // first hotbar slot + InteractionHand.OFF_HAND -> 0 + } + for (delta in 0 until 9) { + val slot = (anchorSlot + delta) % 9 + val stack = this.caster.inventory.getItem(slot) + if (stackOK.test(stack)) { + return slot + } + } + return null + } + + /** + * Remove the given gound of the specified item from somewhere in the inventory, favoring slots not in the hotbar. + * Return whether the withdrawal was successful. + */ + // https://github.com/VazkiiMods/Psi/blob/master/src/main/java/vazkii/psi/common/spell/trick/block/PieceTrickPlaceBlock.java#L143 + fun withdrawItem(item: Item, count: Int, actuallyRemove: Boolean): Boolean { + if (this.caster.isCreative) return true + + val inv = this.caster.inventory + // TODO: withdraw from ender chest given a specific ender charm? + val stacksToExamine = inv.items.asReversed() + + fun matches(stack: ItemStack): Boolean = + !stack.isEmpty && stack.`is`(item) + + val presentCount = stacksToExamine.fold(0) { acc, stack -> + acc + if (matches(stack)) stack.count else 0 + } + if (presentCount < count) return false + + // now that we know we have enough items, if we don't need to remove anything we're through. + if (!actuallyRemove) return true + + var remaining = count + for (stack in stacksToExamine) { + if (matches(stack)) { + val toWithdraw = min(stack.count, remaining) + stack.shrink(toWithdraw) + + remaining -= toWithdraw + if (remaining == 0) { + return true + } + } + } + throw RuntimeException("unreachable") + } } diff --git a/src/main/java/at/petrak/hex/common/casting/CastingHarness.kt b/src/main/java/at/petrak/hex/common/casting/CastingHarness.kt index e6b58a24..43c0724b 100644 --- a/src/main/java/at/petrak/hex/common/casting/CastingHarness.kt +++ b/src/main/java/at/petrak/hex/common/casting/CastingHarness.kt @@ -8,6 +8,7 @@ import net.minecraft.nbt.ListTag import net.minecraft.nbt.Tag import net.minecraft.server.level.ServerPlayer import net.minecraft.world.InteractionHand +import kotlin.math.max /** * Keeps track of a player casting a spell on the server. @@ -63,19 +64,23 @@ class CastingHarness private constructor( this.escapeNext = false HexMod.LOGGER.info("Escaping onto stack") this.stack.add(SpellDatum.make(newPat)) + } else if (operator == SpellWidget.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 == SpellWidget.OPEN_PAREN) { + this.parenCount++ + } else if (operator == SpellWidget.CLOSE_PAREN) { + throw CastException(CastException.Reason.TOO_MANY_CLOSE_PARENS) } else { - // Plain ol operator - if (exn != null) { - // there was a problem finding the pattern and it was NOT due to numbers - throw exn - } else if (operator == SpellWidget.OPEN_PAREN) { - this.parenCount++ - } else if (operator == SpellWidget.CLOSE_PAREN) { - throw CastException(CastException.Reason.TOO_MANY_CLOSE_PARENS) - } else { - // we know the operator is ok here - operator!!.modifyStack(this.stack, this.ctx) - } + // we know the operator is ok here + val manaCost = operator!!.modifyStack(this.stack, this.ctx) + + // prevent poor impls from gaining you mana + ctx.withdrawMana(max(0, manaCost), true) + if (ctx.caster.isDeadOrDying) + return CastResult.Died } if (this.parenCount > 0) { @@ -172,5 +177,8 @@ class CastingHarness private constructor( /** uh-oh */ data class Error(val exn: CastException) : CastResult() + + /** YOU DIED due to casting too hard from hit points. */ + object Died : CastResult() } } \ No newline at end of file diff --git a/src/main/java/at/petrak/hex/common/casting/SpellWidget.kt b/src/main/java/at/petrak/hex/common/casting/SpellWidget.kt index 6f203563..9c2ff614 100644 --- a/src/main/java/at/petrak/hex/common/casting/SpellWidget.kt +++ b/src/main/java/at/petrak/hex/common/casting/SpellWidget.kt @@ -1,6 +1,6 @@ package at.petrak.hex.common.casting -import at.petrak.hex.api.SimpleOperator +import at.petrak.hex.api.ConstManaOperator import at.petrak.hex.api.SpellOperator.Companion.spellListOf /** @@ -8,7 +8,7 @@ import at.petrak.hex.api.SpellOperator.Companion.spellListOf * * They act as operators that push themselves. */ -enum class SpellWidget : SimpleOperator { +enum class SpellWidget : ConstManaOperator { NULL, OPEN_PAREN, CLOSE_PAREN, ESCAPE; diff --git a/src/main/java/at/petrak/hex/common/casting/operators/OpBlockAxisRaycast.kt b/src/main/java/at/petrak/hex/common/casting/operators/OpBlockAxisRaycast.kt index b0ca48a9..4fb7de35 100644 --- a/src/main/java/at/petrak/hex/common/casting/operators/OpBlockAxisRaycast.kt +++ b/src/main/java/at/petrak/hex/common/casting/operators/OpBlockAxisRaycast.kt @@ -1,6 +1,6 @@ package at.petrak.hex.common.casting.operators -import at.petrak.hex.api.SimpleOperator +import at.petrak.hex.api.ConstManaOperator import at.petrak.hex.api.SpellOperator import at.petrak.hex.api.SpellOperator.Companion.getChecked import at.petrak.hex.common.casting.CastingContext @@ -10,8 +10,9 @@ import net.minecraft.world.level.ClipContext import net.minecraft.world.phys.HitResult import net.minecraft.world.phys.Vec3 -object OpBlockAxisRaycast : SimpleOperator { +object OpBlockAxisRaycast : ConstManaOperator { override val argc = 2 + override val manaCost = 10 override fun execute(args: List>, ctx: CastingContext): List> { val origin: Vec3 = args.getChecked(0) val look: Vec3 = args.getChecked(1) diff --git a/src/main/java/at/petrak/hex/common/casting/operators/OpBlockRaycast.kt b/src/main/java/at/petrak/hex/common/casting/operators/OpBlockRaycast.kt index 61f13a0b..69eeaa99 100644 --- a/src/main/java/at/petrak/hex/common/casting/operators/OpBlockRaycast.kt +++ b/src/main/java/at/petrak/hex/common/casting/operators/OpBlockRaycast.kt @@ -1,6 +1,6 @@ package at.petrak.hex.common.casting.operators -import at.petrak.hex.api.SimpleOperator +import at.petrak.hex.api.ConstManaOperator import at.petrak.hex.api.SpellOperator import at.petrak.hex.api.SpellOperator.Companion.getChecked import at.petrak.hex.common.casting.CastingContext @@ -10,8 +10,9 @@ import net.minecraft.world.level.ClipContext import net.minecraft.world.phys.HitResult import net.minecraft.world.phys.Vec3 -object OpBlockRaycast : SimpleOperator { +object OpBlockRaycast : ConstManaOperator { override val argc = 2 + override val manaCost = 10 override fun execute(args: List>, ctx: CastingContext): List> { val origin: Vec3 = args.getChecked(0) val look: Vec3 = args.getChecked(1) diff --git a/src/main/java/at/petrak/hex/common/casting/operators/OpDuplicate.kt b/src/main/java/at/petrak/hex/common/casting/operators/OpDuplicate.kt index 586bfb9a..1f226ecf 100644 --- a/src/main/java/at/petrak/hex/common/casting/operators/OpDuplicate.kt +++ b/src/main/java/at/petrak/hex/common/casting/operators/OpDuplicate.kt @@ -1,12 +1,12 @@ package at.petrak.hex.common.casting.operators -import at.petrak.hex.api.SimpleOperator +import at.petrak.hex.api.ConstManaOperator import at.petrak.hex.api.SpellOperator.Companion.getChecked import at.petrak.hex.api.SpellOperator.Companion.spellListOf import at.petrak.hex.common.casting.CastingContext import at.petrak.hex.common.casting.SpellDatum -object OpDuplicate : SimpleOperator { +object OpDuplicate : ConstManaOperator { override val argc: Int get() = 1 diff --git a/src/main/java/at/petrak/hex/common/casting/operators/OpEntityLook.kt b/src/main/java/at/petrak/hex/common/casting/operators/OpEntityLook.kt index 5ca68a21..74c51700 100644 --- a/src/main/java/at/petrak/hex/common/casting/operators/OpEntityLook.kt +++ b/src/main/java/at/petrak/hex/common/casting/operators/OpEntityLook.kt @@ -1,13 +1,13 @@ package at.petrak.hex.common.casting.operators -import at.petrak.hex.api.SimpleOperator +import at.petrak.hex.api.ConstManaOperator import at.petrak.hex.api.SpellOperator.Companion.getChecked import at.petrak.hex.api.SpellOperator.Companion.spellListOf import at.petrak.hex.common.casting.CastingContext import at.petrak.hex.common.casting.SpellDatum import net.minecraft.world.entity.Entity -object OpEntityLook : SimpleOperator { +object OpEntityLook : ConstManaOperator { override val argc = 1 override fun execute(args: List>, ctx: CastingContext): List> { diff --git a/src/main/java/at/petrak/hex/common/casting/operators/OpEntityPos.kt b/src/main/java/at/petrak/hex/common/casting/operators/OpEntityPos.kt index a744ba7b..7babd7cc 100644 --- a/src/main/java/at/petrak/hex/common/casting/operators/OpEntityPos.kt +++ b/src/main/java/at/petrak/hex/common/casting/operators/OpEntityPos.kt @@ -1,6 +1,6 @@ package at.petrak.hex.common.casting.operators -import at.petrak.hex.api.SimpleOperator +import at.petrak.hex.api.ConstManaOperator import at.petrak.hex.api.SpellOperator.Companion.getChecked import at.petrak.hex.api.SpellOperator.Companion.spellListOf import at.petrak.hex.common.casting.CastingContext @@ -8,7 +8,7 @@ import at.petrak.hex.common.casting.SpellDatum import net.minecraft.world.entity.Entity import net.minecraft.world.entity.player.Player -object OpEntityPos : SimpleOperator { +object OpEntityPos : ConstManaOperator { override val argc = 1 override fun execute(args: List>, ctx: CastingContext): List> { diff --git a/src/main/java/at/petrak/hex/common/casting/operators/OpEntityRaycast.kt b/src/main/java/at/petrak/hex/common/casting/operators/OpEntityRaycast.kt index a0d02407..34293cab 100644 --- a/src/main/java/at/petrak/hex/common/casting/operators/OpEntityRaycast.kt +++ b/src/main/java/at/petrak/hex/common/casting/operators/OpEntityRaycast.kt @@ -1,6 +1,6 @@ package at.petrak.hex.common.casting.operators -import at.petrak.hex.api.SimpleOperator +import at.petrak.hex.api.ConstManaOperator import at.petrak.hex.api.SpellOperator import at.petrak.hex.api.SpellOperator.Companion.getChecked import at.petrak.hex.common.casting.CastingContext @@ -10,8 +10,9 @@ import net.minecraft.world.entity.projectile.ProjectileUtil import net.minecraft.world.phys.AABB import net.minecraft.world.phys.Vec3 -object OpEntityRaycast : SimpleOperator { +object OpEntityRaycast : ConstManaOperator { override val argc = 2 + override val manaCost = 10 override fun execute(args: List>, ctx: CastingContext): List> { val origin: Vec3 = args.getChecked(0) val look: Vec3 = args.getChecked(1) diff --git a/src/main/java/at/petrak/hex/common/casting/operators/OpEval.kt b/src/main/java/at/petrak/hex/common/casting/operators/OpEval.kt index e0f8520a..ae9784e1 100644 --- a/src/main/java/at/petrak/hex/common/casting/operators/OpEval.kt +++ b/src/main/java/at/petrak/hex/common/casting/operators/OpEval.kt @@ -1,19 +1,19 @@ package at.petrak.hex.common.casting.operators -import at.petrak.hex.api.SimpleOperator +import at.petrak.hex.api.SpellOperator import at.petrak.hex.api.SpellOperator.Companion.getChecked import at.petrak.hex.common.casting.CastingContext import at.petrak.hex.common.casting.CastingHarness import at.petrak.hex.common.casting.SpellDatum -object OpEval : SimpleOperator { - override val argc: Int - get() = 1 - - override fun execute(args: List>, ctx: CastingContext): List> { - val instrs: List> = args.getChecked(0) - - val harness = CastingHarness.Default(ctx) +object OpEval : SpellOperator { + override fun modifyStack(stack: MutableList>, ctx: CastingContext): Int { + val instrs: List> = stack.getChecked(stack.lastIndex) + stack.removeLastOrNull() + val ctxDeeper = ctx.withIncDepth() + val harness = CastingHarness.Default(ctxDeeper) + harness.stack.addAll(stack) + stack.clear() for (pat in instrs) { val res = harness.update(pat.tryGet()) if (res is CastingHarness.CastResult.Error) { @@ -22,6 +22,8 @@ object OpEval : SimpleOperator { // in ANY OTHER CASE JUST KEEP GOING // including if there's RenderedSpells on the stack or the stack becomes clear } - return harness.stack + stack.addAll(harness.stack) + + return 50 } } \ No newline at end of file diff --git a/src/main/java/at/petrak/hex/common/casting/operators/OpGetCaster.kt b/src/main/java/at/petrak/hex/common/casting/operators/OpGetCaster.kt index cf3f672d..d50db9d6 100644 --- a/src/main/java/at/petrak/hex/common/casting/operators/OpGetCaster.kt +++ b/src/main/java/at/petrak/hex/common/casting/operators/OpGetCaster.kt @@ -1,12 +1,12 @@ package at.petrak.hex.common.casting.operators -import at.petrak.hex.api.SimpleOperator +import at.petrak.hex.api.ConstManaOperator import at.petrak.hex.api.SpellOperator import at.petrak.hex.common.casting.CastingContext import at.petrak.hex.common.casting.SpellDatum import net.minecraft.world.entity.Entity -object OpGetCaster : SimpleOperator { +object OpGetCaster : ConstManaOperator { override val argc = 0 override fun execute(args: List>, ctx: CastingContext): List> = diff --git a/src/main/java/at/petrak/hex/common/casting/operators/OpRead.kt b/src/main/java/at/petrak/hex/common/casting/operators/OpRead.kt index 7420a63e..0b378d6f 100644 --- a/src/main/java/at/petrak/hex/common/casting/operators/OpRead.kt +++ b/src/main/java/at/petrak/hex/common/casting/operators/OpRead.kt @@ -1,18 +1,18 @@ package at.petrak.hex.common.casting.operators -import at.petrak.hex.api.SimpleOperator +import at.petrak.hex.api.ConstManaOperator import at.petrak.hex.common.casting.CastingContext import at.petrak.hex.common.casting.SpellDatum import at.petrak.hex.common.casting.SpellWidget -import at.petrak.hex.common.items.ItemSpellbook +import at.petrak.hex.common.items.ItemDataHolder -object OpReadFromSpellbook : SimpleOperator { - override val argc: Int - get() = 0 +object OpRead : ConstManaOperator { + override val argc = 0 + override val manaCost = 10 override fun execute(args: List>, ctx: CastingContext): List> { - val spellbook = ctx.getSpellbook() - val datum = ItemSpellbook.ReadDatum(spellbook.orCreateTag, ctx) + val dataer = ctx.getDataHolder() + val datum = (dataer.item as ItemDataHolder).readDatum(dataer.orCreateTag, ctx) return listOf(datum ?: SpellDatum.make(SpellWidget.NULL)) } } \ No newline at end of file diff --git a/src/main/java/at/petrak/hex/common/casting/operators/OpSwap.kt b/src/main/java/at/petrak/hex/common/casting/operators/OpSwap.kt index 846cacfe..362d54c1 100644 --- a/src/main/java/at/petrak/hex/common/casting/operators/OpSwap.kt +++ b/src/main/java/at/petrak/hex/common/casting/operators/OpSwap.kt @@ -1,12 +1,12 @@ package at.petrak.hex.common.casting.operators -import at.petrak.hex.api.SimpleOperator +import at.petrak.hex.api.ConstManaOperator import at.petrak.hex.api.SpellOperator.Companion.getChecked import at.petrak.hex.api.SpellOperator.Companion.spellListOf import at.petrak.hex.common.casting.CastingContext import at.petrak.hex.common.casting.SpellDatum -object OpSwap : SimpleOperator { +object OpSwap : ConstManaOperator { override val argc: Int get() = 2 diff --git a/src/main/java/at/petrak/hex/common/casting/operators/OpUndo.kt b/src/main/java/at/petrak/hex/common/casting/operators/OpUndo.kt index 04df1549..44c045dc 100644 --- a/src/main/java/at/petrak/hex/common/casting/operators/OpUndo.kt +++ b/src/main/java/at/petrak/hex/common/casting/operators/OpUndo.kt @@ -1,10 +1,10 @@ package at.petrak.hex.common.casting.operators -import at.petrak.hex.api.SimpleOperator +import at.petrak.hex.api.ConstManaOperator import at.petrak.hex.common.casting.CastingContext import at.petrak.hex.common.casting.SpellDatum -object OpUndo : SimpleOperator { +object OpUndo : ConstManaOperator { override val argc = 1 // Do literally nothing! diff --git a/src/main/java/at/petrak/hex/common/casting/operators/OpWrite.kt b/src/main/java/at/petrak/hex/common/casting/operators/OpWrite.kt index f1901b9e..9a712d17 100644 --- a/src/main/java/at/petrak/hex/common/casting/operators/OpWrite.kt +++ b/src/main/java/at/petrak/hex/common/casting/operators/OpWrite.kt @@ -1,18 +1,18 @@ package at.petrak.hex.common.casting.operators -import at.petrak.hex.api.SimpleOperator +import at.petrak.hex.api.ConstManaOperator import at.petrak.hex.api.SpellOperator.Companion.spellListOf import at.petrak.hex.common.casting.CastingContext import at.petrak.hex.common.casting.SpellDatum -import at.petrak.hex.common.items.ItemSpellbook +import at.petrak.hex.common.items.ItemDataHolder -object OpWriteToSpellbook : SimpleOperator { - override val argc: Int - get() = 1 +object OpWrite : ConstManaOperator { + override val argc = 1 + override val manaCost = 10 override fun execute(args: List>, ctx: CastingContext): List> { - val spellbook = ctx.getSpellbook() - ItemSpellbook.WriteDatum(spellbook.orCreateTag, args[0]) + val dataer = ctx.getDataHolder() + (dataer.item as ItemDataHolder).writeDatum(dataer.orCreateTag, args[0]) return spellListOf() } } \ No newline at end of file diff --git a/src/main/java/at/petrak/hex/common/casting/operators/math/OpAbsLen.kt b/src/main/java/at/petrak/hex/common/casting/operators/math/OpAbsLen.kt index 17a6511c..9160e65c 100644 --- a/src/main/java/at/petrak/hex/common/casting/operators/math/OpAbsLen.kt +++ b/src/main/java/at/petrak/hex/common/casting/operators/math/OpAbsLen.kt @@ -1,12 +1,12 @@ package at.petrak.hex.common.casting.operators.math -import at.petrak.hex.api.SimpleOperator +import at.petrak.hex.api.ConstManaOperator import at.petrak.hex.api.SpellOperator.Companion.spellListOf import at.petrak.hex.common.casting.CastingContext import at.petrak.hex.common.casting.SpellDatum import kotlin.math.absoluteValue -object OpAbsLen : SimpleOperator { +object OpAbsLen : ConstManaOperator { override val argc: Int get() = 1 diff --git a/src/main/java/at/petrak/hex/common/casting/operators/math/OpAdd.kt b/src/main/java/at/petrak/hex/common/casting/operators/math/OpAdd.kt index aa0e0f84..025a7335 100644 --- a/src/main/java/at/petrak/hex/common/casting/operators/math/OpAdd.kt +++ b/src/main/java/at/petrak/hex/common/casting/operators/math/OpAdd.kt @@ -1,11 +1,11 @@ package at.petrak.hex.common.casting.operators.math -import at.petrak.hex.api.SimpleOperator +import at.petrak.hex.api.ConstManaOperator import at.petrak.hex.api.SpellOperator.Companion.spellListOf import at.petrak.hex.common.casting.CastingContext import at.petrak.hex.common.casting.SpellDatum -object OpAdd : SimpleOperator { +object OpAdd : ConstManaOperator { override val argc: Int get() = 2 diff --git a/src/main/java/at/petrak/hex/common/casting/operators/math/OpDivCross.kt b/src/main/java/at/petrak/hex/common/casting/operators/math/OpDivCross.kt index 203b7f53..e8f3b25b 100644 --- a/src/main/java/at/petrak/hex/common/casting/operators/math/OpDivCross.kt +++ b/src/main/java/at/petrak/hex/common/casting/operators/math/OpDivCross.kt @@ -1,12 +1,12 @@ package at.petrak.hex.common.casting.operators.math -import at.petrak.hex.api.SimpleOperator +import at.petrak.hex.api.ConstManaOperator import at.petrak.hex.api.SpellOperator.Companion.spellListOf import at.petrak.hex.common.casting.CastingContext import at.petrak.hex.common.casting.SpellDatum import net.minecraft.world.phys.Vec3 -object OpDivCross : SimpleOperator { +object OpDivCross : ConstManaOperator { override val argc: Int get() = 2 diff --git a/src/main/java/at/petrak/hex/common/casting/operators/math/OpMulDot.kt b/src/main/java/at/petrak/hex/common/casting/operators/math/OpMulDot.kt index 34f9dc77..695cc083 100644 --- a/src/main/java/at/petrak/hex/common/casting/operators/math/OpMulDot.kt +++ b/src/main/java/at/petrak/hex/common/casting/operators/math/OpMulDot.kt @@ -1,11 +1,11 @@ package at.petrak.hex.common.casting.operators.math -import at.petrak.hex.api.SimpleOperator +import at.petrak.hex.api.ConstManaOperator import at.petrak.hex.api.SpellOperator.Companion.spellListOf import at.petrak.hex.common.casting.CastingContext import at.petrak.hex.common.casting.SpellDatum -object OpMulDot : SimpleOperator { +object OpMulDot : ConstManaOperator { override val argc: Int get() = 2 diff --git a/src/main/java/at/petrak/hex/common/casting/operators/math/OpPowProj.kt b/src/main/java/at/petrak/hex/common/casting/operators/math/OpPowProj.kt index 50453944..7ad8339d 100644 --- a/src/main/java/at/petrak/hex/common/casting/operators/math/OpPowProj.kt +++ b/src/main/java/at/petrak/hex/common/casting/operators/math/OpPowProj.kt @@ -1,13 +1,13 @@ package at.petrak.hex.common.casting.operators.math -import at.petrak.hex.api.SimpleOperator +import at.petrak.hex.api.ConstManaOperator import at.petrak.hex.api.SpellOperator.Companion.spellListOf import at.petrak.hex.common.casting.CastingContext import at.petrak.hex.common.casting.SpellDatum import net.minecraft.world.phys.Vec3 import kotlin.math.pow -object OpPowProj : SimpleOperator { +object OpPowProj : ConstManaOperator { override val argc: Int get() = 2 diff --git a/src/main/java/at/petrak/hex/common/casting/operators/math/OpSub.kt b/src/main/java/at/petrak/hex/common/casting/operators/math/OpSub.kt index 48ca46dd..71c6b3d0 100644 --- a/src/main/java/at/petrak/hex/common/casting/operators/math/OpSub.kt +++ b/src/main/java/at/petrak/hex/common/casting/operators/math/OpSub.kt @@ -1,12 +1,12 @@ package at.petrak.hex.common.casting.operators.math -import at.petrak.hex.api.SimpleOperator +import at.petrak.hex.api.ConstManaOperator import at.petrak.hex.api.SpellOperator.Companion.spellListOf import at.petrak.hex.common.casting.CastingContext import at.petrak.hex.common.casting.SpellDatum import net.minecraft.world.phys.Vec3 -object OpSub : SimpleOperator { +object OpSub : ConstManaOperator { override val argc: Int get() = 2 diff --git a/src/main/java/at/petrak/hex/common/casting/operators/spells/OpAddMotion.kt b/src/main/java/at/petrak/hex/common/casting/operators/spells/OpAddMotion.kt index ffc335de..b58b138b 100644 --- a/src/main/java/at/petrak/hex/common/casting/operators/spells/OpAddMotion.kt +++ b/src/main/java/at/petrak/hex/common/casting/operators/spells/OpAddMotion.kt @@ -18,10 +18,13 @@ object OpAddMotion : SimpleOperator, RenderedSpellImpl { override val argc: Int get() = 2 - override fun execute(args: List>, ctx: CastingContext): List> { + override fun execute(args: List>, ctx: CastingContext): Pair>, Int> { val target = args.getChecked(0) val motion = args.getChecked(1) - return spellListOf(RenderedSpell(OpAddMotion, spellListOf(target, motion))) + return Pair( + spellListOf(RenderedSpell(OpAddMotion, spellListOf(target, motion))), + motion.lengthSqr().toInt() * 100 + ) } override fun cast(args: List>, ctx: CastingContext) { diff --git a/src/main/java/at/petrak/hex/common/casting/operators/spells/OpBreakBlock.kt b/src/main/java/at/petrak/hex/common/casting/operators/spells/OpBreakBlock.kt new file mode 100644 index 00000000..3986aade --- /dev/null +++ b/src/main/java/at/petrak/hex/common/casting/operators/spells/OpBreakBlock.kt @@ -0,0 +1,43 @@ +package at.petrak.hex.common.casting.operators.spells + +import at.petrak.hex.HexMod +import at.petrak.hex.api.SimpleOperator +import at.petrak.hex.api.SpellOperator.Companion.getChecked +import at.petrak.hex.api.SpellOperator.Companion.spellListOf +import at.petrak.hex.common.casting.CastingContext +import at.petrak.hex.common.casting.RenderedSpell +import at.petrak.hex.common.casting.RenderedSpellImpl +import at.petrak.hex.common.casting.SpellDatum +import net.minecraft.core.BlockPos +import net.minecraft.world.phys.Vec3 +import net.minecraftforge.common.TierSortingRegistry + +object OpBreakBlock : SimpleOperator, RenderedSpellImpl { + override val argc: Int + get() = 1 + + override fun execute(args: List>, ctx: CastingContext): Pair>, Int> { + val pos = args.getChecked(0) + ctx.assertVecInRange(pos) + return Pair( + spellListOf(RenderedSpell(OpBreakBlock, spellListOf(pos))), + 100 + ) + } + + override fun cast(args: List>, ctx: CastingContext) { + val pos = BlockPos(args.getChecked(0)) + + val blockstate = ctx.world.getBlockState(pos) + val tier = + HexMod.CONFIG.opBreakHarvestLevelBecauseForgeThoughtItWasAGoodIdeaToImplementHarvestTiersUsingAnHonestToGodTopoSort + + if (!blockstate.isAir && (!blockstate.requiresCorrectToolForDrops() || TierSortingRegistry.isCorrectTierForDrops( + tier, + blockstate + )) + ) { + ctx.world.destroyBlock(pos, true, ctx.caster) + } // TODO: else some kind of failureific particle effect? + } +} \ No newline at end of file diff --git a/src/main/java/at/petrak/hex/common/casting/operators/spells/OpExplode.kt b/src/main/java/at/petrak/hex/common/casting/operators/spells/OpExplode.kt index ff1f4575..3ce50ecf 100644 --- a/src/main/java/at/petrak/hex/common/casting/operators/spells/OpExplode.kt +++ b/src/main/java/at/petrak/hex/common/casting/operators/spells/OpExplode.kt @@ -1,30 +1,41 @@ package at.petrak.hex.common.casting.operators.spells import at.petrak.hex.api.SimpleOperator -import at.petrak.hex.api.SpellOperator.Companion.assertVecInRange import at.petrak.hex.api.SpellOperator.Companion.getChecked import at.petrak.hex.api.SpellOperator.Companion.spellListOf import at.petrak.hex.common.casting.CastingContext import at.petrak.hex.common.casting.RenderedSpell import at.petrak.hex.common.casting.RenderedSpellImpl import at.petrak.hex.common.casting.SpellDatum +import net.minecraft.util.Mth import net.minecraft.world.level.Explosion import net.minecraft.world.phys.Vec3 object OpExplode : SimpleOperator, RenderedSpellImpl { override val argc: Int - get() = 1 + get() = 2 - override fun execute(args: List>, ctx: CastingContext): List> { + override fun execute(args: List>, ctx: CastingContext): Pair>, Int> { val pos = args.getChecked(0) - assertVecInRange(pos, ctx) - return spellListOf(RenderedSpell(OpExplode, spellListOf(pos))) + val strength = args.getChecked(1) + ctx.assertVecInRange(pos) + return Pair( + spellListOf(RenderedSpell(OpExplode, spellListOf(pos, strength))), + (strength * 100.0).toInt(), + ) } override fun cast(args: List>, ctx: CastingContext) { val pos = args.getChecked(0) + val strength = args.getChecked(1) - // 4.0 is the strength of TNT, i guess - ctx.world.explode(ctx.caster, pos.x, pos.y, pos.z, 4.0f, Explosion.BlockInteraction.BREAK) + ctx.world.explode( + ctx.caster, + pos.x, + pos.y, + pos.z, + Mth.clamp(strength.toFloat(), 0f, 10f), + Explosion.BlockInteraction.BREAK + ) } } \ No newline at end of file diff --git a/src/main/java/at/petrak/hex/common/casting/operators/spells/OpLightning.kt b/src/main/java/at/petrak/hex/common/casting/operators/spells/OpLightning.kt new file mode 100644 index 00000000..3a35789e --- /dev/null +++ b/src/main/java/at/petrak/hex/common/casting/operators/spells/OpLightning.kt @@ -0,0 +1,34 @@ +package at.petrak.hex.common.casting.operators.spells + +import at.petrak.hex.api.SimpleOperator +import at.petrak.hex.api.SpellOperator.Companion.getChecked +import at.petrak.hex.api.SpellOperator.Companion.spellListOf +import at.petrak.hex.common.casting.CastingContext +import at.petrak.hex.common.casting.RenderedSpell +import at.petrak.hex.common.casting.RenderedSpellImpl +import at.petrak.hex.common.casting.SpellDatum +import net.minecraft.world.entity.EntityType +import net.minecraft.world.entity.LightningBolt +import net.minecraft.world.phys.Vec3 + +object OpLightning : SimpleOperator, RenderedSpellImpl { + override val argc: Int + get() = 1 + + override fun execute(args: List>, ctx: CastingContext): Pair>, Int> { + val target = args.getChecked(0) + ctx.assertVecInRange(target) + return Pair( + spellListOf(RenderedSpell(OpLightning, spellListOf(target))), + 1500 + ) + } + + override fun cast(args: List>, ctx: CastingContext) { + val target = args.getChecked(0) + + val lightning = LightningBolt(EntityType.LIGHTNING_BOLT, ctx.world) + lightning.setPosRaw(target.x, target.y, target.z) + ctx.world.addWithUUID(lightning) // why the hell is it called this it doesnt even involve a uuid + } +} \ No newline at end of file diff --git a/src/main/java/at/petrak/hex/common/casting/operators/spells/OpPlaceBlock.kt b/src/main/java/at/petrak/hex/common/casting/operators/spells/OpPlaceBlock.kt new file mode 100644 index 00000000..4af56111 --- /dev/null +++ b/src/main/java/at/petrak/hex/common/casting/operators/spells/OpPlaceBlock.kt @@ -0,0 +1,71 @@ +package at.petrak.hex.common.casting.operators.spells + +import at.petrak.hex.api.SimpleOperator +import at.petrak.hex.api.SpellOperator.Companion.getChecked +import at.petrak.hex.api.SpellOperator.Companion.spellListOf +import at.petrak.hex.common.casting.CastingContext +import at.petrak.hex.common.casting.RenderedSpell +import at.petrak.hex.common.casting.RenderedSpellImpl +import at.petrak.hex.common.casting.SpellDatum +import net.minecraft.core.BlockPos +import net.minecraft.world.InteractionResult +import net.minecraft.world.item.BlockItem +import net.minecraft.world.item.context.UseOnContext +import net.minecraft.world.phys.BlockHitResult +import net.minecraft.world.phys.Vec3 +import net.minecraftforge.common.MinecraftForge +import net.minecraftforge.common.util.BlockSnapshot +import net.minecraftforge.event.world.BlockEvent + +object OpPlaceBlock : SimpleOperator, RenderedSpellImpl { + override val argc: Int + get() = 1 + + override fun execute(args: List>, ctx: CastingContext): Pair>, Int> { + val pos = args.getChecked(0) + ctx.assertVecInRange(pos) + return Pair( + spellListOf(RenderedSpell(OpPlaceBlock, spellListOf(pos))), + 30 + ) + } + + override fun cast(args: List>, ctx: CastingContext) { + val vec = args.getChecked(0) + val pos = BlockPos(vec) + val bstate = ctx.world.getBlockState(pos) + if (bstate.isAir || bstate.material.isReplaceable) { + val placeeSlot = ctx.getOperativeSlot { it.item is BlockItem } + if (placeeSlot != null) { + val placeeStack = ctx.caster.inventory.getItem(placeeSlot) + val placee = placeeStack.item as BlockItem + if (ctx.withdrawItem(placee, 1, false)) { + // https://github.com/VazkiiMods/Psi/blob/master/src/main/java/vazkii/psi/common/spell/trick/block/PieceTrickPlaceBlock.java#L143 + val evt = BlockEvent.EntityPlaceEvent( + BlockSnapshot.create(ctx.world.dimension(), ctx.world, pos), + ctx.world.getBlockState(pos.above()), + ctx.caster + ) + MinecraftForge.EVENT_BUS.post(evt) + + // we temporarily give the player the stack, place it using mc code, then give them the old stack back. + val oldStack = ctx.caster.getItemInHand(ctx.wandHand) + val spoofedStack = placeeStack.copy() + spoofedStack.count = 1 + ctx.caster.setItemInHand(ctx.wandHand, spoofedStack) + + val blockHit = BlockHitResult( + Vec3.ZERO, ctx.caster.direction, pos, false + ) + val itemUseCtx = UseOnContext(ctx.caster, ctx.wandHand, blockHit) + val res = spoofedStack.useOn(itemUseCtx) + + ctx.caster.setItemInHand(ctx.wandHand, oldStack) + if (res != InteractionResult.FAIL) { + ctx.withdrawItem(placee, 1, true) + } + } + } + } + } +} \ No newline at end of file diff --git a/src/main/java/at/petrak/hex/common/casting/operators/spells/OpPrint.kt b/src/main/java/at/petrak/hex/common/casting/operators/spells/OpPrint.kt index bd5b57c7..5c7b797e 100644 --- a/src/main/java/at/petrak/hex/common/casting/operators/spells/OpPrint.kt +++ b/src/main/java/at/petrak/hex/common/casting/operators/spells/OpPrint.kt @@ -1,6 +1,6 @@ package at.petrak.hex.common.casting.operators.spells -import at.petrak.hex.api.SimpleOperator +import at.petrak.hex.api.ConstManaOperator import at.petrak.hex.api.SpellOperator.Companion.getChecked import at.petrak.hex.api.SpellOperator.Companion.spellListOf import at.petrak.hex.common.casting.CastingContext @@ -10,7 +10,7 @@ import at.petrak.hex.common.casting.SpellDatum import net.minecraft.Util import net.minecraft.network.chat.TextComponent -object OpPrint : SimpleOperator, RenderedSpellImpl { +object OpPrint : ConstManaOperator, RenderedSpellImpl { override val argc = 1 override fun execute(args: List>, ctx: CastingContext): List> { val datum = args.getChecked(0) diff --git a/src/main/java/at/petrak/hex/common/items/HexItems.java b/src/main/java/at/petrak/hex/common/items/HexItems.java index a5105ccc..e6f5293f 100644 --- a/src/main/java/at/petrak/hex/common/items/HexItems.java +++ b/src/main/java/at/petrak/hex/common/items/HexItems.java @@ -2,23 +2,46 @@ package at.petrak.hex.common.items; import at.petrak.hex.HexMod; import at.petrak.hex.common.lib.LibItemNames; +import net.minecraft.core.NonNullList; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.item.CreativeModeTab; import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; import net.minecraftforge.registries.DeferredRegister; import net.minecraftforge.registries.ForgeRegistries; import net.minecraftforge.registries.RegistryObject; public class HexItems { public static final DeferredRegister ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, HexMod.MOD_ID); + public static final CreativeModeTab TAB = new CreativeModeTab("hex") { + @Override + public ItemStack makeIcon() { + return new ItemStack(SPELLBOOK::get); + } + + @Override + public void fillItemList(NonNullList items) { + // Make the wand spawn with some sensible NBT + var tag = new CompoundTag(); + tag.putInt(ItemWand.TAG_MANA, 1000); + tag.putInt(ItemWand.TAG_MAX_MANA, 1000); + var stack = new ItemStack(WAND::get); + stack.setTag(tag); + items.add(stack); + + super.fillItemList(items); + } + }; public static final RegistryObject WAND = ITEMS.register(LibItemNames.WAND, - () -> new ItemWand(unstackable())); + () -> new ItemWand(new Item.Properties().stacksTo(1))); public static final RegistryObject FOCUS = ITEMS.register(LibItemNames.FOCUS, () -> new ItemFocus(props())); public static final RegistryObject SPELLBOOK = ITEMS.register(LibItemNames.SPELLBOOK, () -> new ItemSpellbook(unstackable())); public static Item.Properties props() { - return new Item.Properties(); + return new Item.Properties().tab(TAB); } public static Item.Properties unstackable() { diff --git a/src/main/java/at/petrak/hex/common/items/ItemDataHolder.java b/src/main/java/at/petrak/hex/common/items/ItemDataHolder.java index 0285f95a..aa2a0955 100644 --- a/src/main/java/at/petrak/hex/common/items/ItemDataHolder.java +++ b/src/main/java/at/petrak/hex/common/items/ItemDataHolder.java @@ -1,2 +1,18 @@ -package at.petrak.hex.common.items;public class ItemDataDolder { +package at.petrak.hex.common.items; + +import at.petrak.hex.common.casting.CastingContext; +import at.petrak.hex.common.casting.SpellDatum; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.item.Item; +import org.jetbrains.annotations.Nullable; + +abstract public class ItemDataHolder extends Item { + public ItemDataHolder(Properties pProperties) { + super(pProperties); + } + + @Nullable + public abstract SpellDatum readDatum(CompoundTag tag, CastingContext ctx); + + public abstract void writeDatum(CompoundTag tag, SpellDatum datum); } diff --git a/src/main/java/at/petrak/hex/common/items/ItemFocus.java b/src/main/java/at/petrak/hex/common/items/ItemFocus.java index 90f52bc1..204b021c 100644 --- a/src/main/java/at/petrak/hex/common/items/ItemFocus.java +++ b/src/main/java/at/petrak/hex/common/items/ItemFocus.java @@ -1,13 +1,31 @@ package at.petrak.hex.common.items; import at.petrak.hex.HexMod; +import at.petrak.hex.common.casting.CastingContext; +import at.petrak.hex.common.casting.SpellDatum; +import net.minecraft.nbt.CompoundTag; import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.Item; +import org.jetbrains.annotations.Nullable; -public class ItemFocus extends Item { +public class ItemFocus extends ItemDataHolder { public static final ResourceLocation PREDICATE = new ResourceLocation(HexMod.MOD_ID, "datatype"); + public static final String TAG_DATA = "data"; public ItemFocus(Properties pProperties) { super(pProperties); } + + @Override + public @Nullable SpellDatum readDatum(CompoundTag tag, CastingContext ctx) { + try { + return SpellDatum.DeserializeFromNBT(tag.getCompound(TAG_DATA), ctx); + } catch (Exception e) { + return null; + } + } + + @Override + public void writeDatum(CompoundTag tag, SpellDatum datum) { + tag.put(TAG_DATA, datum.serializeToNBT()); + } } diff --git a/src/main/java/at/petrak/hex/common/items/ItemSpellbook.java b/src/main/java/at/petrak/hex/common/items/ItemSpellbook.java index a54c39c9..3c0b0743 100644 --- a/src/main/java/at/petrak/hex/common/items/ItemSpellbook.java +++ b/src/main/java/at/petrak/hex/common/items/ItemSpellbook.java @@ -7,7 +7,6 @@ import net.minecraft.network.chat.Component; import net.minecraft.network.chat.TextComponent; import net.minecraft.network.chat.TranslatableComponent; import net.minecraft.world.entity.Entity; -import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.TooltipFlag; import net.minecraft.world.level.Level; @@ -16,7 +15,7 @@ import org.jetbrains.annotations.Nullable; import java.util.List; import java.util.stream.Stream; -public class ItemSpellbook extends Item { +public class ItemSpellbook extends ItemDataHolder { public static String TAG_SELECTED_PAGE = "page_idx"; // this is a CompoundTag of string numerical keys to SpellData\ // it is 1-indexed, so that 0/0 can be the special case of "it is empty" @@ -64,7 +63,29 @@ public class ItemSpellbook extends Item { tag.getCompound(ItemSpellbook.TAG_PAGES).isEmpty(); } - public static void WriteDatum(CompoundTag tag, SpellDatum datum) { + + @Nullable + public SpellDatum readDatum(CompoundTag tag, CastingContext ctx) { + int idx; + if (tag.contains(TAG_SELECTED_PAGE)) { + idx = tag.getInt(TAG_SELECTED_PAGE); + } else { + idx = 0; + } + var key = String.valueOf(idx); + if (tag.contains(TAG_PAGES)) { + var pagesTag = tag.getCompound(TAG_PAGES); + if (pagesTag.contains(key)) { + return SpellDatum.DeserializeFromNBT(pagesTag.getCompound(key), ctx); + } else { + return null; + } + } else { + return null; + } + } + + public void writeDatum(CompoundTag tag, SpellDatum datum) { int idx; if (tag.contains(TAG_SELECTED_PAGE)) { idx = tag.getInt(TAG_SELECTED_PAGE); @@ -85,27 +106,6 @@ public class ItemSpellbook extends Item { } } - @Nullable - public static SpellDatum ReadDatum(CompoundTag tag, CastingContext ctx) { - int idx; - if (tag.contains(TAG_SELECTED_PAGE)) { - idx = tag.getInt(TAG_SELECTED_PAGE); - } else { - idx = 0; - } - var key = String.valueOf(idx); - if (tag.contains(TAG_PAGES)) { - var pagesTag = tag.getCompound(TAG_PAGES); - if (pagesTag.contains(key)) { - return SpellDatum.DeserializeFromNBT(pagesTag.getCompound(key), ctx); - } else { - return null; - } - } else { - return null; - } - } - public static int HighestPage(CompoundTag tag) { var highestKey = tag.getAllKeys().stream().flatMap(s -> { try { diff --git a/src/main/java/at/petrak/hex/common/items/ItemWand.java b/src/main/java/at/petrak/hex/common/items/ItemWand.java index 93472434..9a30bf05 100644 --- a/src/main/java/at/petrak/hex/common/items/ItemWand.java +++ b/src/main/java/at/petrak/hex/common/items/ItemWand.java @@ -1,15 +1,22 @@ package at.petrak.hex.common.items; +import at.petrak.hex.HexMod; import at.petrak.hex.client.gui.GuiSpellcasting; import net.minecraft.client.Minecraft; +import net.minecraft.util.Mth; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResultHolder; +import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; public class ItemWand extends Item { + public static final String TAG_MANA = "mana"; + public static final String TAG_MAX_MANA = "maxMana"; + public static final String TAG_HARNESS = "harness"; + public ItemWand(Properties pProperties) { super(pProperties); } @@ -22,4 +29,61 @@ public class ItemWand extends Item { return InteractionResultHolder.success(player.getItemInHand(hand)); } + + @Override + public void inventoryTick(ItemStack pStack, Level pLevel, Entity pEntity, int pSlotId, boolean pIsSelected) { + var tag = pStack.getOrCreateTag(); + var mana = tag.getInt(TAG_MANA); + var maxMana = tag.getInt(TAG_MAX_MANA); + if (mana < maxMana) { + tag.putInt(TAG_MANA, Math.min(maxMana, mana + HexMod.CONFIG.wandRechargeRate.get())); + } + } + + @Override + public boolean isDamageable(ItemStack stack) { + return false; + } + + @Override + public boolean canBeDepleted() { + return false; + } + + @Override + public boolean isBarVisible(ItemStack pStack) { + return true; + } + + @Override + public int getBarColor(ItemStack pStack) { + var tag = pStack.getOrCreateTag(); + var mana = tag.getInt(TAG_MANA); + var maxMana = tag.getInt(TAG_MAX_MANA); + float amt; + if (maxMana == 0) { + amt = 0f; + } else { + amt = ((float) mana) / ((float) maxMana); + } + + var r = Mth.lerp(amt, 149f, 112f); + var g = Mth.lerp(amt, 196f, 219f); + var b = Mth.lerp(amt, 174f, 212f); + return Mth.color(r / 255f, g / 255f, b / 255f); + } + + @Override + public int getBarWidth(ItemStack pStack) { + var tag = pStack.getOrCreateTag(); + var mana = tag.getInt(TAG_MANA); + var maxMana = tag.getInt(TAG_MAX_MANA); + float amt; + if (maxMana == 0) { + amt = 0f; + } else { + amt = ((float) mana) / ((float) maxMana); + } + return Math.round(13f * amt); + } } diff --git a/src/main/java/at/petrak/hex/common/lib/LibDamageSources.java b/src/main/java/at/petrak/hex/common/lib/LibDamageSources.java index 127f8dfd..00d95724 100644 --- a/src/main/java/at/petrak/hex/common/lib/LibDamageSources.java +++ b/src/main/java/at/petrak/hex/common/lib/LibDamageSources.java @@ -1,2 +1,7 @@ -package at.petrak.hex.common.lib;public class LibDamage { +package at.petrak.hex.common.lib; + +import net.minecraft.world.damagesource.DamageSource; + +public class LibDamageSources { + public static final DamageSource OVERCAST = new DamageSource("hex.overcast").bypassArmor().bypassMagic().setMagic(); } diff --git a/src/main/java/at/petrak/hex/common/network/MsgNewSpellPatternAck.java b/src/main/java/at/petrak/hex/common/network/MsgNewSpellPatternAck.java index 3634b2d2..df65d0cf 100644 --- a/src/main/java/at/petrak/hex/common/network/MsgNewSpellPatternAck.java +++ b/src/main/java/at/petrak/hex/common/network/MsgNewSpellPatternAck.java @@ -1,5 +1,6 @@ package at.petrak.hex.common.network; +import at.petrak.hex.client.gui.GuiSpellcasting; import io.netty.buffer.ByteBuf; import net.minecraft.client.Minecraft; import net.minecraft.network.FriendlyByteBuf; @@ -27,7 +28,7 @@ public record MsgNewSpellPatternAck(boolean quitCasting) { public void handle(Supplier ctx) { ctx.get().enqueueWork(() -> DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> { - if (quitCasting) { + if (quitCasting && Minecraft.getInstance().screen instanceof GuiSpellcasting) { Minecraft.getInstance().setScreen(null); } }) diff --git a/src/main/java/at/petrak/hex/common/network/MsgNewSpellPatternSyn.java b/src/main/java/at/petrak/hex/common/network/MsgNewSpellPatternSyn.java index fa4ff19b..a2e65c8d 100644 --- a/src/main/java/at/petrak/hex/common/network/MsgNewSpellPatternSyn.java +++ b/src/main/java/at/petrak/hex/common/network/MsgNewSpellPatternSyn.java @@ -42,7 +42,8 @@ public record MsgNewSpellPatternSyn(InteractionHand handUsed, HexPattern pattern var held = sender.getItemInHand(this.handUsed); if (held.getItem() instanceof ItemWand) { var tag = held.getOrCreateTag(); - var harness = CastingHarness.DeserializeFromNBT(tag, sender, this.handUsed); + var harness = CastingHarness.DeserializeFromNBT(tag.getCompound(ItemWand.TAG_HARNESS), sender, + this.handUsed); var res = harness.update(this.pattern); if (res instanceof CastResult.Success success) { @@ -52,15 +53,17 @@ public record MsgNewSpellPatternSyn(InteractionHand handUsed, HexPattern pattern } boolean quit; + CompoundTag nextHarnessTag; if (res instanceof CastResult.Nothing) { // save the changes - held.setTag(harness.serializeToNBT()); + nextHarnessTag = harness.serializeToNBT(); quit = false; } else { // Else we requested to quit in some way or another - held.setTag(new CompoundTag()); + nextHarnessTag = new CompoundTag(); quit = true; } + tag.put(ItemWand.TAG_HARNESS, nextHarnessTag); HexMessages.getNetwork() .send(PacketDistributor.PLAYER.with(() -> sender), new MsgNewSpellPatternAck(quit)); diff --git a/src/main/java/at/petrak/hex/common/network/MsgQuitSpellcasting.java b/src/main/java/at/petrak/hex/common/network/MsgQuitSpellcasting.java index 6a5dcc2d..b9b7845d 100644 --- a/src/main/java/at/petrak/hex/common/network/MsgQuitSpellcasting.java +++ b/src/main/java/at/petrak/hex/common/network/MsgQuitSpellcasting.java @@ -26,7 +26,7 @@ public record MsgQuitSpellcasting() { var held = sender.getMainHandItem(); if (held.getItem() instanceof ItemWand) { // Todo: appropriate consequences for quitting a spell like this - held.setTag(new CompoundTag()); + held.getOrCreateTag().put(ItemWand.TAG_HARNESS, new CompoundTag()); } } }); diff --git a/src/main/java/at/petrak/hex/server/TickScheduler.kt b/src/main/java/at/petrak/hex/server/TickScheduler.kt index 70327e15..118e6f62 100644 --- a/src/main/java/at/petrak/hex/server/TickScheduler.kt +++ b/src/main/java/at/petrak/hex/server/TickScheduler.kt @@ -1,4 +1,32 @@ package at.petrak.hex.server +import net.minecraftforge.event.TickEvent +import net.minecraftforge.eventbus.api.SubscribeEvent +import java.util.* + object TickScheduler { + val tasks: MutableList = LinkedList() + + fun schedule(task: Task) { + this.tasks.add(task) + } + + fun schedule(ticks: Int, task: Runnable) { + this.tasks.add(Task(ticks, task)) + } + + @SubscribeEvent + fun onTick(evt: TickEvent.ServerTickEvent) { + this.tasks.removeIf { + it.ticks-- + if (it.ticks <= 0) { + it.task.run() + true + } else { + false + } + } + } + + data class Task(var ticks: Int, val task: Runnable) } \ No newline at end of file diff --git a/src/main/resources/assets/hex/lang/en_US.json b/src/main/resources/assets/hex/lang/en_US.json index 61b00932..1c507e2b 100644 --- a/src/main/resources/assets/hex/lang/en_US.json +++ b/src/main/resources/assets/hex/lang/en_US.json @@ -3,5 +3,7 @@ "item.hex.focus": "Focus", "item.hex.spellbook": "Spellbook", "hex.spellbook.tooltip.page": "Selected Page %d/%d", - "hex.spelldata.desc.entity": "Entity %s" + "hex.spelldata.desc.entity": "Entity %s", + + "itemGroup.hex": "Hex" }