diff --git a/src/main/java/at/petrak/hex/HexUtils.kt b/src/main/java/at/petrak/hex/HexUtils.kt index 352b8720..86420aef 100644 --- a/src/main/java/at/petrak/hex/HexUtils.kt +++ b/src/main/java/at/petrak/hex/HexUtils.kt @@ -1,6 +1,7 @@ package at.petrak.hex import net.minecraft.nbt.LongArrayTag +import net.minecraft.world.InteractionHand import net.minecraft.world.phys.Vec2 import net.minecraft.world.phys.Vec3 @@ -10,7 +11,7 @@ object HexUtils { LongArrayTag(longArrayOf(this.x.toRawBits(), this.y.toRawBits(), this.z.toRawBits())) @JvmStatic - fun deserializeVec3FromNBT(tag: LongArray): Vec3 = + fun DeserializeVec3FromNBT(tag: LongArray): Vec3 = Vec3( Double.fromBits(tag[0]), Double.fromBits(tag[1]), @@ -22,11 +23,15 @@ object HexUtils { LongArrayTag(longArrayOf(this.x.toDouble().toRawBits(), this.y.toDouble().toRawBits())) @JvmStatic - fun deserializeVec2FromNBT(tag: LongArray): Vec2 = + fun DeserializeVec2FromNBT(tag: LongArray): Vec2 = Vec2( Double.fromBits(tag[0]).toFloat(), Double.fromBits(tag[1]).toFloat(), ) + @JvmStatic + fun OtherHand(hand: InteractionHand) = + if (hand == InteractionHand.MAIN_HAND) InteractionHand.OFF_HAND else InteractionHand.MAIN_HAND + const val TAU = Math.PI * 2.0 } \ No newline at end of file diff --git a/src/main/java/at/petrak/hex/client/ShiftScrollListener.java b/src/main/java/at/petrak/hex/client/ShiftScrollListener.java index eb161346..f2f61f40 100644 --- a/src/main/java/at/petrak/hex/client/ShiftScrollListener.java +++ b/src/main/java/at/petrak/hex/client/ShiftScrollListener.java @@ -17,7 +17,6 @@ import net.minecraftforge.fml.common.Mod; public class ShiftScrollListener { @SubscribeEvent public static void onScroll(InputEvent.MouseScrollEvent evt) { - HexMod.LOGGER.info("scrolling {}", evt.getScrollDelta()); LocalPlayer player = Minecraft.getInstance().player; if (player.isCrouching()) { InteractionHand hand = null; diff --git a/src/main/java/at/petrak/hex/client/gui/GuiSpellcasting.kt b/src/main/java/at/petrak/hex/client/gui/GuiSpellcasting.kt index c5fc1284..f3343817 100644 --- a/src/main/java/at/petrak/hex/client/gui/GuiSpellcasting.kt +++ b/src/main/java/at/petrak/hex/client/gui/GuiSpellcasting.kt @@ -1,7 +1,11 @@ package at.petrak.hex.client.gui import at.petrak.hex.HexMod +import at.petrak.hex.HexUtils import at.petrak.hex.HexUtils.TAU +import at.petrak.hex.common.items.ItemSpellbook +import at.petrak.hex.common.network.HexMessages +import at.petrak.hex.common.network.MsgShiftScrollSyn import at.petrak.hex.hexmath.HexAngle import at.petrak.hex.hexmath.HexCoord import at.petrak.hex.hexmath.HexDir @@ -101,7 +105,7 @@ class GuiSpellcasting(private val handOpenedWith: InteractionHand) : Screen(Text this.drawState = PatternDrawState.BetweenPatterns this.patterns.add(Pair(pat, start)) - at.petrak.hex.common.network.HexMessages.getNetwork().sendToServer( + HexMessages.getNetwork().sendToServer( at.petrak.hex.common.network.MsgNewSpellPatternSyn( this.handOpenedWith, pat @@ -113,6 +117,16 @@ class GuiSpellcasting(private val handOpenedWith: InteractionHand) : Screen(Text return false } + override fun mouseScrolled(pMouseX: Double, pMouseY: Double, pDelta: Double): Boolean { + super.mouseScrolled(pMouseX, pMouseY, pDelta) + + val otherHand = HexUtils.OtherHand(this.handOpenedWith) + if (Minecraft.getInstance().player!!.getItemInHand(otherHand).item is ItemSpellbook) + HexMessages.getNetwork().sendToServer(MsgShiftScrollSyn(otherHand, pDelta)) + + return true + } + override fun onClose() { at.petrak.hex.common.network.HexMessages.getNetwork() .sendToServer(at.petrak.hex.common.network.MsgQuitSpellcasting()) 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 edfaf274..760cddd1 100644 --- a/src/main/java/at/petrak/hex/common/casting/CastException.kt +++ b/src/main/java/at/petrak/hex/common/casting/CastException.kt @@ -50,6 +50,13 @@ class CastException(val reason: Reason, vararg val data: Any) : Exception() { * `vec: Vec3` */ TOO_FAR, + + /** + * An operator needed a spellbook in the offhand. + * + * `no args` + */ + REQUIRES_SPELLBOOK, } override val message: String @@ -60,5 +67,6 @@ class CastException(val reason: Reason, vararg val data: Any) : Exception() { Reason.NOT_ENOUGH_ARGS -> "required at least ${this.data[0] as Int} args on the stack but only had ${this.data[1] as Int}" 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" } } \ 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 c9670a12..73d753c7 100644 --- a/src/main/java/at/petrak/hex/common/casting/CastingContext.kt +++ b/src/main/java/at/petrak/hex/common/casting/CastingContext.kt @@ -1,7 +1,11 @@ package at.petrak.hex.common.casting +import at.petrak.hex.HexUtils +import at.petrak.hex.common.items.ItemSpellbook import net.minecraft.server.level.ServerLevel import net.minecraft.server.level.ServerPlayer +import net.minecraft.world.InteractionHand +import net.minecraft.world.item.ItemStack /** * Info about the moment the spell started being cast. @@ -9,6 +13,17 @@ import net.minecraft.server.level.ServerPlayer @JvmRecord data class CastingContext( val caster: ServerPlayer, + val wandHand: InteractionHand, ) { val world: ServerLevel get() = caster.getLevel() + + fun getSpellbook(): ItemStack { + val handItem = + caster.getItemInHand(HexUtils.OtherHand(wandHand)) + return if (handItem.item is ItemSpellbook) { + handItem + } else { + throw CastException(CastException.Reason.REQUIRES_SPELLBOOK) + } + } } 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 04624588..6dd42891 100644 --- a/src/main/java/at/petrak/hex/common/casting/CastingHarness.kt +++ b/src/main/java/at/petrak/hex/common/casting/CastingHarness.kt @@ -6,6 +6,7 @@ import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.ListTag import net.minecraft.nbt.Tag import net.minecraft.server.level.ServerPlayer +import net.minecraft.world.InteractionHand /** * Keeps track of a player casting a spell on the server. @@ -143,8 +144,8 @@ class CastingHarness private constructor( const val TAG_ESCAPE_NEXT = "escape_next" @JvmStatic - fun DeserializeFromNBT(nbt: Tag?, caster: ServerPlayer): CastingHarness { - val ctx = CastingContext(caster) + fun DeserializeFromNBT(nbt: Tag?, caster: ServerPlayer, wandHand: InteractionHand): CastingHarness { + val ctx = CastingContext(caster, wandHand) return try { val nbt = nbt as CompoundTag diff --git a/src/main/java/at/petrak/hex/common/casting/SpellDatum.kt b/src/main/java/at/petrak/hex/common/casting/SpellDatum.kt index 19c4f139..e3e7e062 100644 --- a/src/main/java/at/petrak/hex/common/casting/SpellDatum.kt +++ b/src/main/java/at/petrak/hex/common/casting/SpellDatum.kt @@ -114,7 +114,7 @@ class SpellDatum private constructor(val payload: T) { SpellDatum(if (entity == null || !entity.isAlive) SpellWidget.NULL else entity) } TAG_DOUBLE -> SpellDatum(nbt.getDouble(key)) - TAG_VEC3 -> SpellDatum(HexUtils.deserializeVec3FromNBT(nbt.getLongArray(key))) + TAG_VEC3 -> SpellDatum(HexUtils.DeserializeVec3FromNBT(nbt.getLongArray(key))) TAG_LIST -> { val arr = nbt.getList(key, Tag.TAG_COMPOUND.toInt()) val out = ArrayList>(arr.size) diff --git a/src/main/java/at/petrak/hex/common/casting/SpellOperator.kt b/src/main/java/at/petrak/hex/common/casting/SpellOperator.kt index fa952714..44ede6b7 100644 --- a/src/main/java/at/petrak/hex/common/casting/SpellOperator.kt +++ b/src/main/java/at/petrak/hex/common/casting/SpellOperator.kt @@ -65,10 +65,12 @@ interface SpellOperator { // == Meta stuff == "qqq" to SpellWidget.OPEN_PAREN, "eee" to SpellWidget.CLOSE_PAREN, - "aaaqw" to SpellWidget.ESCAPE, + "qqqaw" to SpellWidget.ESCAPE, // http://www.toroidalsnark.net/mkss3-pix/CalderheadJMM2014.pdf // eval being a space filling curve feels apt doesn't it - "deaqq" to OpEval + "deaqq" to OpEval, + "aqqqqq" to OpReadFromSpellbook, + "deeeee" to OpWriteToSpellbook, ) /** diff --git a/src/main/java/at/petrak/hex/common/casting/operators/OpReadFromSpellbook.kt b/src/main/java/at/petrak/hex/common/casting/operators/OpReadFromSpellbook.kt new file mode 100644 index 00000000..3d45fcd6 --- /dev/null +++ b/src/main/java/at/petrak/hex/common/casting/operators/OpReadFromSpellbook.kt @@ -0,0 +1,17 @@ +package at.petrak.hex.common.casting.operators + +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 + +object OpReadFromSpellbook : SimpleOperator { + override val argc: Int + get() = 0 + + override fun execute(args: List>, ctx: CastingContext): List> { + val spellbook = ctx.getSpellbook() + val datum = ItemSpellbook.ReadDatum(spellbook.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/OpWriteToSpellbook.kt b/src/main/java/at/petrak/hex/common/casting/operators/OpWriteToSpellbook.kt new file mode 100644 index 00000000..8b9d06f4 --- /dev/null +++ b/src/main/java/at/petrak/hex/common/casting/operators/OpWriteToSpellbook.kt @@ -0,0 +1,17 @@ +package at.petrak.hex.common.casting.operators + +import at.petrak.hex.common.casting.CastingContext +import at.petrak.hex.common.casting.SpellDatum +import at.petrak.hex.common.casting.SpellOperator.Companion.spellListOf +import at.petrak.hex.common.items.ItemSpellbook + +object OpWriteToSpellbook : SimpleOperator { + override val argc: Int + get() = 1 + + override fun execute(args: List>, ctx: CastingContext): List> { + val spellbook = ctx.getSpellbook() + ItemSpellbook.WriteDatum(spellbook.orCreateTag, args[0]) + return spellListOf() + } +} \ No newline at end of file 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 eef9d79d..a54c39c9 100644 --- a/src/main/java/at/petrak/hex/common/items/ItemSpellbook.java +++ b/src/main/java/at/petrak/hex/common/items/ItemSpellbook.java @@ -1,9 +1,12 @@ 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.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; @@ -15,7 +18,8 @@ import java.util.stream.Stream; public class ItemSpellbook extends Item { public static String TAG_SELECTED_PAGE = "page_idx"; - // this is a CompoundTag of string numerical keys to SpellData + // 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" public static String TAG_PAGES = "pages"; public ItemSpellbook(Properties properties) { @@ -40,6 +44,68 @@ public class ItemSpellbook extends Item { } } + @Override + public void inventoryTick(ItemStack stack, Level pLevel, Entity pEntity, int pSlotId, boolean pIsSelected) { + var tag = stack.getOrCreateTag(); + if (ArePagesEmpty(tag)) { + tag.putInt(TAG_SELECTED_PAGE, 0); + } else if (!tag.contains(TAG_SELECTED_PAGE)) { + tag.putInt(TAG_SELECTED_PAGE, 1); + } else { + var pageIdx = tag.getInt(TAG_SELECTED_PAGE); + if (pageIdx == 0) { + tag.putInt(TAG_SELECTED_PAGE, 1); + } + } + } + + public static boolean ArePagesEmpty(CompoundTag tag) { + return !tag.contains(ItemSpellbook.TAG_PAGES) || + tag.getCompound(ItemSpellbook.TAG_PAGES).isEmpty(); + } + + public static void WriteDatum(CompoundTag tag, SpellDatum datum) { + int idx; + if (tag.contains(TAG_SELECTED_PAGE)) { + idx = tag.getInt(TAG_SELECTED_PAGE); + // But we want to write to page *1* to start if this is our first page + if (idx == 0 && ArePagesEmpty(tag)) { + idx = 1; + } + } else { + idx = 1; + } + var key = String.valueOf(idx); + if (tag.contains(TAG_PAGES)) { + tag.getCompound(TAG_PAGES).put(key, datum.serializeToNBT()); + } else { + var pagesTag = new CompoundTag(); + pagesTag.put(key, datum.serializeToNBT()); + tag.put(TAG_PAGES, pagesTag); + } + } + + @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 { @@ -53,12 +119,14 @@ public class ItemSpellbook extends Item { public static void RotatePageIdx(CompoundTag tag, boolean increase) { int newIdx; - if (tag.contains(ItemSpellbook.TAG_SELECTED_PAGE)) { - var delta = increase ? 1 : -1; - newIdx = Math.max(0, tag.getInt(ItemSpellbook.TAG_SELECTED_PAGE) + delta); - } else { + if (ArePagesEmpty(tag)) { newIdx = 0; + } else if (tag.contains(TAG_SELECTED_PAGE)) { + var delta = increase ? 1 : -1; + newIdx = Math.max(1, tag.getInt(TAG_SELECTED_PAGE) + delta); + } else { + newIdx = 1; } - tag.putInt(ItemSpellbook.TAG_SELECTED_PAGE, newIdx); + tag.putInt(TAG_SELECTED_PAGE, newIdx); } } 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 010991fb..fa4ff19b 100644 --- a/src/main/java/at/petrak/hex/common/network/MsgNewSpellPatternSyn.java +++ b/src/main/java/at/petrak/hex/common/network/MsgNewSpellPatternSyn.java @@ -42,11 +42,11 @@ 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); + var harness = CastingHarness.DeserializeFromNBT(tag, sender, this.handUsed); var res = harness.update(this.pattern); if (res instanceof CastResult.Success success) { - success.getSpell().cast(new CastingContext(sender)); + success.getSpell().cast(new CastingContext(sender, this.handUsed)); } else if (res instanceof CastResult.Error error) { sender.sendMessage(new TextComponent(error.getExn().getMessage()), Util.NIL_UUID); } diff --git a/src/main/java/at/petrak/hex/common/network/MsgShiftScrollSyn.java b/src/main/java/at/petrak/hex/common/network/MsgShiftScrollSyn.java index e6937027..1b5fe6e0 100644 --- a/src/main/java/at/petrak/hex/common/network/MsgShiftScrollSyn.java +++ b/src/main/java/at/petrak/hex/common/network/MsgShiftScrollSyn.java @@ -12,7 +12,8 @@ import net.minecraftforge.network.NetworkEvent; import java.util.function.Supplier; /** - * Sent client->server when the client shift+scrolls with a shift-scrollable item. + * Sent client->server when the client shift+scrolls with a shift-scrollable item + * or scrolls in the spellcasting UI. */ public record MsgShiftScrollSyn(InteractionHand hand, double scrollDelta) { public static MsgShiftScrollSyn deserialize(ByteBuf buffer) {