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
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
}

View file

@ -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;

View file

@ -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())

View file

@ -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"
}
}

View file

@ -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)
}
}
}

View file

@ -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

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)
}
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<SpellDatum<*>>(arr.size)

View file

@ -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,
)
/**

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;
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);
}
}

View file

@ -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);
}

View file

@ -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) {