can now save and cast spells from spellbooks!
This commit is contained in:
parent
57b3b2854c
commit
fd5f1087fd
|
@ -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
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
||||
/**
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue