can now save and cast spells from spellbooks!

This commit is contained in:
gamma-delta 2021-12-28 01:49:28 -06:00
parent 57b3b2854c
commit fd5f1087fd
13 changed files with 165 additions and 18 deletions

View file

@ -1,6 +1,7 @@
package at.petrak.hex package at.petrak.hex
import net.minecraft.nbt.LongArrayTag import net.minecraft.nbt.LongArrayTag
import net.minecraft.world.InteractionHand
import net.minecraft.world.phys.Vec2 import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3 import net.minecraft.world.phys.Vec3
@ -10,7 +11,7 @@ object HexUtils {
LongArrayTag(longArrayOf(this.x.toRawBits(), this.y.toRawBits(), this.z.toRawBits())) LongArrayTag(longArrayOf(this.x.toRawBits(), this.y.toRawBits(), this.z.toRawBits()))
@JvmStatic @JvmStatic
fun deserializeVec3FromNBT(tag: LongArray): Vec3 = fun DeserializeVec3FromNBT(tag: LongArray): Vec3 =
Vec3( Vec3(
Double.fromBits(tag[0]), Double.fromBits(tag[0]),
Double.fromBits(tag[1]), Double.fromBits(tag[1]),
@ -22,11 +23,15 @@ object HexUtils {
LongArrayTag(longArrayOf(this.x.toDouble().toRawBits(), this.y.toDouble().toRawBits())) LongArrayTag(longArrayOf(this.x.toDouble().toRawBits(), this.y.toDouble().toRawBits()))
@JvmStatic @JvmStatic
fun deserializeVec2FromNBT(tag: LongArray): Vec2 = fun DeserializeVec2FromNBT(tag: LongArray): Vec2 =
Vec2( Vec2(
Double.fromBits(tag[0]).toFloat(), Double.fromBits(tag[0]).toFloat(),
Double.fromBits(tag[1]).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 const val TAU = Math.PI * 2.0
} }

View file

@ -17,7 +17,6 @@ import net.minecraftforge.fml.common.Mod;
public class ShiftScrollListener { public class ShiftScrollListener {
@SubscribeEvent @SubscribeEvent
public static void onScroll(InputEvent.MouseScrollEvent evt) { public static void onScroll(InputEvent.MouseScrollEvent evt) {
HexMod.LOGGER.info("scrolling {}", evt.getScrollDelta());
LocalPlayer player = Minecraft.getInstance().player; LocalPlayer player = Minecraft.getInstance().player;
if (player.isCrouching()) { if (player.isCrouching()) {
InteractionHand hand = null; InteractionHand hand = null;

View file

@ -1,7 +1,11 @@
package at.petrak.hex.client.gui package at.petrak.hex.client.gui
import at.petrak.hex.HexMod import at.petrak.hex.HexMod
import at.petrak.hex.HexUtils
import at.petrak.hex.HexUtils.TAU 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.HexAngle
import at.petrak.hex.hexmath.HexCoord import at.petrak.hex.hexmath.HexCoord
import at.petrak.hex.hexmath.HexDir import at.petrak.hex.hexmath.HexDir
@ -101,7 +105,7 @@ class GuiSpellcasting(private val handOpenedWith: InteractionHand) : Screen(Text
this.drawState = PatternDrawState.BetweenPatterns this.drawState = PatternDrawState.BetweenPatterns
this.patterns.add(Pair(pat, start)) this.patterns.add(Pair(pat, start))
at.petrak.hex.common.network.HexMessages.getNetwork().sendToServer( HexMessages.getNetwork().sendToServer(
at.petrak.hex.common.network.MsgNewSpellPatternSyn( at.petrak.hex.common.network.MsgNewSpellPatternSyn(
this.handOpenedWith, this.handOpenedWith,
pat pat
@ -113,6 +117,16 @@ class GuiSpellcasting(private val handOpenedWith: InteractionHand) : Screen(Text
return false 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() { override fun onClose() {
at.petrak.hex.common.network.HexMessages.getNetwork() at.petrak.hex.common.network.HexMessages.getNetwork()
.sendToServer(at.petrak.hex.common.network.MsgQuitSpellcasting()) .sendToServer(at.petrak.hex.common.network.MsgQuitSpellcasting())

View file

@ -50,6 +50,13 @@ class CastException(val reason: Reason, vararg val data: Any) : Exception() {
* `vec: Vec3` * `vec: Vec3`
*/ */
TOO_FAR, TOO_FAR,
/**
* An operator needed a spellbook in the offhand.
*
* `no args`
*/
REQUIRES_SPELLBOOK,
} }
override val message: String 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.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_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.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"
} }
} }

View file

@ -1,7 +1,11 @@
package at.petrak.hex.common.casting 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.ServerLevel
import net.minecraft.server.level.ServerPlayer 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. * Info about the moment the spell started being cast.
@ -9,6 +13,17 @@ import net.minecraft.server.level.ServerPlayer
@JvmRecord @JvmRecord
data class CastingContext( data class CastingContext(
val caster: ServerPlayer, val caster: ServerPlayer,
val wandHand: InteractionHand,
) { ) {
val world: ServerLevel get() = caster.getLevel() 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)
}
}
} }

View file

@ -6,6 +6,7 @@ import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.ListTag import net.minecraft.nbt.ListTag
import net.minecraft.nbt.Tag import net.minecraft.nbt.Tag
import net.minecraft.server.level.ServerPlayer import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.InteractionHand
/** /**
* Keeps track of a player casting a spell on the server. * 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" const val TAG_ESCAPE_NEXT = "escape_next"
@JvmStatic @JvmStatic
fun DeserializeFromNBT(nbt: Tag?, caster: ServerPlayer): CastingHarness { fun DeserializeFromNBT(nbt: Tag?, caster: ServerPlayer, wandHand: InteractionHand): CastingHarness {
val ctx = CastingContext(caster) val ctx = CastingContext(caster, wandHand)
return try { return try {
val nbt = nbt as CompoundTag val nbt = nbt as CompoundTag

View file

@ -114,7 +114,7 @@ class SpellDatum<T : Any> private constructor(val payload: T) {
SpellDatum(if (entity == null || !entity.isAlive) SpellWidget.NULL else entity) SpellDatum(if (entity == null || !entity.isAlive) SpellWidget.NULL else entity)
} }
TAG_DOUBLE -> SpellDatum(nbt.getDouble(key)) TAG_DOUBLE -> SpellDatum(nbt.getDouble(key))
TAG_VEC3 -> SpellDatum(HexUtils.deserializeVec3FromNBT(nbt.getLongArray(key))) TAG_VEC3 -> SpellDatum(HexUtils.DeserializeVec3FromNBT(nbt.getLongArray(key)))
TAG_LIST -> { TAG_LIST -> {
val arr = nbt.getList(key, Tag.TAG_COMPOUND.toInt()) val arr = nbt.getList(key, Tag.TAG_COMPOUND.toInt())
val out = ArrayList<SpellDatum<*>>(arr.size) val out = ArrayList<SpellDatum<*>>(arr.size)

View file

@ -65,10 +65,12 @@ interface SpellOperator {
// == Meta stuff == // == Meta stuff ==
"qqq" to SpellWidget.OPEN_PAREN, "qqq" to SpellWidget.OPEN_PAREN,
"eee" to SpellWidget.CLOSE_PAREN, "eee" to SpellWidget.CLOSE_PAREN,
"aaaqw" to SpellWidget.ESCAPE, "qqqaw" to SpellWidget.ESCAPE,
// http://www.toroidalsnark.net/mkss3-pix/CalderheadJMM2014.pdf // http://www.toroidalsnark.net/mkss3-pix/CalderheadJMM2014.pdf
// eval being a space filling curve feels apt doesn't it // eval being a space filling curve feels apt doesn't it
"deaqq" to OpEval "deaqq" to OpEval,
"aqqqqq" to OpReadFromSpellbook,
"deeeee" to OpWriteToSpellbook,
) )
/** /**

View file

@ -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<SpellDatum<*>>, ctx: CastingContext): List<SpellDatum<*>> {
val spellbook = ctx.getSpellbook()
val datum = ItemSpellbook.ReadDatum(spellbook.orCreateTag, ctx)
return listOf(datum ?: SpellDatum.make(SpellWidget.NULL))
}
}

View file

@ -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<SpellDatum<*>>, ctx: CastingContext): List<SpellDatum<*>> {
val spellbook = ctx.getSpellbook()
ItemSpellbook.WriteDatum(spellbook.orCreateTag, args[0])
return spellListOf()
}
}

View file

@ -1,9 +1,12 @@
package at.petrak.hex.common.items; 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.nbt.CompoundTag;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.TextComponent; import net.minecraft.network.chat.TextComponent;
import net.minecraft.network.chat.TranslatableComponent; import net.minecraft.network.chat.TranslatableComponent;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.item.Item; import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag; import net.minecraft.world.item.TooltipFlag;
@ -15,7 +18,8 @@ import java.util.stream.Stream;
public class ItemSpellbook extends Item { public class ItemSpellbook extends Item {
public static String TAG_SELECTED_PAGE = "page_idx"; 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 static String TAG_PAGES = "pages";
public ItemSpellbook(Properties properties) { 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) { public static int HighestPage(CompoundTag tag) {
var highestKey = tag.getAllKeys().stream().flatMap(s -> { var highestKey = tag.getAllKeys().stream().flatMap(s -> {
try { try {
@ -53,12 +119,14 @@ public class ItemSpellbook extends Item {
public static void RotatePageIdx(CompoundTag tag, boolean increase) { public static void RotatePageIdx(CompoundTag tag, boolean increase) {
int newIdx; int newIdx;
if (tag.contains(ItemSpellbook.TAG_SELECTED_PAGE)) { if (ArePagesEmpty(tag)) {
var delta = increase ? 1 : -1;
newIdx = Math.max(0, tag.getInt(ItemSpellbook.TAG_SELECTED_PAGE) + delta);
} else {
newIdx = 0; 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);
} }
} }

View file

@ -42,11 +42,11 @@ public record MsgNewSpellPatternSyn(InteractionHand handUsed, HexPattern pattern
var held = sender.getItemInHand(this.handUsed); var held = sender.getItemInHand(this.handUsed);
if (held.getItem() instanceof ItemWand) { if (held.getItem() instanceof ItemWand) {
var tag = held.getOrCreateTag(); var tag = held.getOrCreateTag();
var harness = CastingHarness.DeserializeFromNBT(tag, sender); var harness = CastingHarness.DeserializeFromNBT(tag, sender, this.handUsed);
var res = harness.update(this.pattern); var res = harness.update(this.pattern);
if (res instanceof CastResult.Success success) { 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) { } else if (res instanceof CastResult.Error error) {
sender.sendMessage(new TextComponent(error.getExn().getMessage()), Util.NIL_UUID); sender.sendMessage(new TextComponent(error.getExn().getMessage()), Util.NIL_UUID);
} }

View file

@ -12,7 +12,8 @@ import net.minecraftforge.network.NetworkEvent;
import java.util.function.Supplier; 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 record MsgShiftScrollSyn(InteractionHand hand, double scrollDelta) {
public static MsgShiftScrollSyn deserialize(ByteBuf buffer) { public static MsgShiftScrollSyn deserialize(ByteBuf buffer) {