push for rendering help

This commit is contained in:
gamma-delta 2021-12-26 17:09:56 -06:00
parent f52880e527
commit c5c38f7cd8
10 changed files with 388 additions and 374 deletions

View file

@ -2,6 +2,7 @@ package at.petrak.hex;
import at.petrak.hex.client.HexRenderOverlays;
import at.petrak.hex.items.HexItems;
import at.petrak.hex.network.HexMessages;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
@ -20,5 +21,6 @@ public class HexMod {
MinecraftForge.EVENT_BUS.register(this);
HexItems.ITEMS.register(FMLJavaModLoadingContext.get().getModEventBus());
MinecraftForge.EVENT_BUS.register(HexRenderOverlays.class);
HexMessages.register();
}
}

View file

@ -1,6 +1,7 @@
package at.petrak.hex
import net.minecraft.nbt.LongArrayTag
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
object HexUtils {
@ -16,5 +17,16 @@ object HexUtils {
Double.fromBits(tag[2])
)
@JvmStatic
fun Vec2.serializeToNBT(): LongArrayTag =
LongArrayTag(longArrayOf(this.x.toDouble().toRawBits(), this.y.toDouble().toRawBits()))
@JvmStatic
fun deserializeVec2FromNBT(tag: LongArray): Vec2 =
Vec2(
Double.fromBits(tag[0]).toFloat(),
Double.fromBits(tag[1]).toFloat(),
)
const val TAU = Math.PI * 2.0
}

View file

@ -1,109 +1,52 @@
package at.petrak.hex.casting
import at.petrak.hex.HexMod
import at.petrak.hex.HexUtils
import at.petrak.hex.HexUtils.TAU
import at.petrak.hex.HexUtils.deserializeVec3FromNBT
import at.petrak.hex.HexUtils.serializeToNBT
import at.petrak.hex.casting.operators.SpellOperator
import at.petrak.hex.hexes.HexAngle
import at.petrak.hex.hexes.HexCoord
import at.petrak.hex.hexes.HexDir
import at.petrak.hex.hexes.HexPattern
import com.mojang.math.Matrix3f
import com.mojang.math.Vector3f
import net.minecraft.nbt.*
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.phys.Vec2
import net.minecraft.world.phys.Vec3
import java.util.*
import kotlin.math.*
/**
* Keeps track of a player casting a spell
* Keeps track of a player casting a spell on the server.
* It's stored as NBT on the wand.
*/
class CastingHarness private constructor(
val stack: MutableList<SpellDatum<*>>,
var patternDrawState: PatternDrawState,
val worldPoints: MutableList<MutableList<Vec3>>,
val ctx: CastingContext,
) {
/**
* Internally update its own state based on the player's actions.
* When the server gets a packet from the client with a new pattern,
* handle it.
*/
fun update(): CastResult {
val caster = this.ctx.caster
return when (patternDrawState) {
is PatternDrawState.BetweenPatterns -> {
if (caster.isUsingItem) {
HexMod.LOGGER.info("Started drawing new pattern")
this.patternDrawState = PatternDrawState.JustStarted(caster.lookAngle)
worldPoints.add(mutableListOf(caster.lookAngle.add(caster.position())))
}
fun update(newPat: HexPattern): CastResult {
return try {
val operator = SpellOperator.fromPattern(newPat)
HexMod.LOGGER.info("Executing operator: $operator")
// now execute the operator
if (operator.argc > this.stack.size)
throw CastException(CastException.Reason.NOT_ENOUGH_ARGS, operator.argc, this.stack.size)
val args = this.stack.takeLast(operator.argc)
// there's gotta be a better way to do this
for (_idx in 0 until operator.argc)
this.stack.removeLast()
val newData = operator.execute(args, this.ctx)
this.stack.addAll(newData)
HexMod.LOGGER.info("Added new data to stack: ${this.stack}")
if (this.stack.isEmpty()) {
return CastResult.QuitCasting
}
val maybeSpell = this.stack[0]
if (this.stack.size == 1 && maybeSpell.payload is RenderedSpell) {
CastResult.Success(maybeSpell.payload)
} else {
CastResult.Nothing
}
is PatternDrawState.JustStarted -> {
val (anchor) = patternDrawState as PatternDrawState.JustStarted
if (caster.isUsingItem) {
val dir = anchor.hexDirBetween(caster.lookAngle)
if (dir.isPresent) {
val pat = HexPattern(dir.get())
HexMod.LOGGER.info("Started casting spell: $pat")
this.patternDrawState = PatternDrawState.Drawing(caster.lookAngle, pat)
worldPoints.last().add(caster.lookAngle.add(caster.position()))
}
CastResult.Nothing
} else {
// We never finished drawing the line
this.patternDrawState = PatternDrawState.BetweenPatterns
worldPoints.removeLastOrNull()
CastResult.Nothing
}
}
is PatternDrawState.Drawing -> {
val (anchor, pat) = patternDrawState as PatternDrawState.Drawing
if (caster.isUsingItem) {
val dir = anchor.hexDirBetween(caster.lookAngle)
if (dir.isPresent && pat.tryAppendDir(dir.get())) {
// nice! another line on the pattern
HexMod.LOGGER.info("Added dir to pattern: $pat")
(patternDrawState as PatternDrawState.Drawing).anchorLookPos = caster.lookAngle
worldPoints.last().add(caster.lookAngle.add(caster.position()))
}
CastResult.Nothing
} else {
// Finish the current pattern!
patternDrawState = PatternDrawState.BetweenPatterns
try {
val operator = SpellOperator.fromPattern(pat)
HexMod.LOGGER.info("Executing operator: $operator")
// now execute the operator
if (operator.argc > this.stack.size)
throw CastException(CastException.Reason.NOT_ENOUGH_ARGS, operator.argc, this.stack.size)
val args = this.stack.takeLast(operator.argc)
// there's gotta be a better way to do this
for (_idx in 0 until operator.argc)
this.stack.removeLast()
val newData = operator.execute(args, this.ctx)
this.stack.addAll(newData)
HexMod.LOGGER.info("Added new data to stack: ${this.stack}")
if (this.stack.isEmpty()) {
return CastResult.QuitCasting
}
val maybeSpell = this.stack[0]
if (this.stack.size == 1 && maybeSpell.payload is RenderedSpell) {
CastResult.Success(maybeSpell.payload)
} else {
CastResult.Nothing
}
} catch (e: CastException) {
CastResult.Error(e)
}
}
}
} catch (e: CastException) {
CastResult.Error(e)
}
}
@ -115,16 +58,7 @@ class CastingHarness private constructor(
stackTag.add(datum.serializeToNBT())
out.put(TAG_STACK, stackTag)
out.put(TAG_PDS, this.patternDrawState.serializeToNBT())
val pointsTag = ListTag()
for (patblob in this.worldPoints) {
val subtag = ListTag()
for (point in patblob) {
subtag.add(point.serializeToNBT())
}
pointsTag.add(subtag)
}
out.put(TAG_POINTS, pointsTag)
return out
@ -132,7 +66,6 @@ class CastingHarness private constructor(
companion object {
const val TAG_STACK = "stack"
const val TAG_PDS = "pds"
const val TAG_POINTS = "points"
@JvmStatic
@ -148,163 +81,10 @@ class CastingHarness private constructor(
stack.add(datum)
}
val pds = PatternDrawState.DeserializeFromNBT(nbt.getCompound(TAG_PDS))
val pointsTag = nbt.getList(TAG_POINTS, Tag.TAG_LIST.toInt())
val points = pointsTag.map { patgroup ->
(patgroup as ListTag).map { posTag ->
val pos = posTag as LongArrayTag
deserializeVec3FromNBT(pos.asLongArray)
}.toMutableList()
}.toMutableList()
CastingHarness(stack, pds, points, ctx)
CastingHarness(stack, ctx)
} catch (exn: Exception) {
HexMod.LOGGER.warn("Couldn't load harness from nbt tag, falling back to default: $nbt: $exn")
CastingHarness(mutableListOf(), PatternDrawState.BetweenPatterns, mutableListOf(), ctx)
}
}
// this is on a unit sphere, where 0 is straight ahead and 1 is straight up (or similar)
const val HEX_GRID_SPACING = 1.0 / 8.0
private fun screenToLookMat(look: Vec3): Matrix3f {
val towardsZenith = Vec3(0.0, 1.0, 0.0).subtract(look).normalize()
val towardsRight = look.cross(towardsZenith).normalize()
val ihatPrime = Vector3f(look.add(towardsRight))
val jhatPrime = Vector3f(look.add(towardsZenith))
val khatPrime = Vector3f(look)
val neo = Matrix3f()
neo.set(0, 0, ihatPrime.x())
neo.set(0, 1, ihatPrime.y())
neo.set(0, 2, ihatPrime.z())
neo.set(1, 0, jhatPrime.x())
neo.set(1, 1, jhatPrime.y())
neo.set(1, 2, jhatPrime.z())
neo.set(2, 0, khatPrime.x())
neo.set(2, 1, khatPrime.y())
neo.set(2, 2, khatPrime.z())
return neo
}
private fun Vec3.screenAngle(offset: Vec3): Double {
// https://gist.github.com/Alwinfy/d6f3e9b22e4432f4446a58ace8812a3c
// no idea how any of this works
fun pythag(x: Double, y: Double): Double = sqrt(x * x + y * y)
val yaw = atan2(this.x, this.z)
val pitch = atan2(this.y, pythag(this.x, this.z))
val zeroYaw = offset.yRot(-yaw.toFloat())
val zeroPitch = zeroYaw.xRot(-pitch.toFloat()).normalize()
return atan2(asin(zeroPitch.y), asin(-zeroPitch.x))
}
private fun Vec3.screenVec(offset: Vec3): Vec2 {
val angle = this.screenAngle(offset)
val dy = sin(angle)
val dx = cos(angle)
val dist = (this.normalize().subtract(offset.normalize())).length()
return Vec2((dx * dist).toFloat(), (dy * dist).toFloat())
}
/** Check if the two vectors are far enough apart to be more than one hex coord apart */
private fun Vec3.hexDirBetween(look: Vec3): Optional<HexDir> {
if (look.x.absoluteValue <= 1e-30 || look.z.absoluteValue <= 1e-30)
return Optional.empty()
val dist = (this.normalize().subtract(look.normalize())).length()
if (dist < HEX_GRID_SPACING)
return Optional.empty()
// val angle = this.screenAngle(look)
val neo = screenToLookMat(this)
neo.invert()
val lookScreen = Vector3f(look)
lookScreen.transform(neo)
HexMod.LOGGER.info(lookScreen)
val angle = atan2(lookScreen.y(), lookScreen.x())
// 0 is right, increases clockwise(?)
val snappedAngle = angle.div(TAU).mod(6.0).times(6).roundToInt()
return Optional.of(HexDir.values()[(-snappedAngle + 1).mod(6)])
}
/**
* Taking this as a look vector, get the look vector that is this vector offset
* by the given coordinate in draw-space.
*/
private fun Vec3.hexOffsetFrom(offset: HexCoord): Vec3 {
TODO()
}
}
sealed class PatternDrawState {
/** We're waiting on the player to right-click again */
object BetweenPatterns : PatternDrawState()
/** We just started drawing and haven't drawn the first line yet. */
data class JustStarted(val anchorLookPos: Vec3) : PatternDrawState()
/** We've started drawing a pattern for real. */
data class Drawing(var anchorLookPos: Vec3, val wipPattern: HexPattern) : PatternDrawState()
fun serializeToNBT(): CompoundTag {
val (key, value) = when (this) {
BetweenPatterns -> Pair(TAG_BETWEEN_PATTERNS, ListTag())
is JustStarted -> {
val (anchor) = this
Pair(TAG_JUST_STARTED, anchor.serializeToNBT())
}
is Drawing -> {
val (anchor, pat) = this
val subtag = CompoundTag()
subtag.put(TAG_ANCHOR, anchor.serializeToNBT())
subtag.put(TAG_START_DIR, ByteTag.valueOf(pat.startDir.ordinal.toByte()))
val anglesTag = ByteArrayTag(pat.angles.map { it.ordinal.toByte() })
subtag.put(TAG_ANGLES, anglesTag)
Pair(TAG_DRAWING, subtag)
}
}
val out = CompoundTag()
out.put(key, value)
return out
}
companion object {
const val TAG_BETWEEN_PATTERNS = "between_patterns"
const val TAG_JUST_STARTED = "just_started"
const val TAG_DRAWING = "drawing"
const val TAG_ANCHOR = "anchor"
const val TAG_START_DIR = "start_dir"
const val TAG_ANGLES = "angles"
fun DeserializeFromNBT(nbt: CompoundTag): PatternDrawState {
val keys = nbt.allKeys
if (keys.size != 1)
throw IllegalArgumentException("Expected exactly one kv pair: $nbt")
return when (val key = keys.iterator().next()) {
TAG_BETWEEN_PATTERNS -> BetweenPatterns
TAG_JUST_STARTED -> {
val anchor = HexUtils.deserializeVec3FromNBT(nbt.getLongArray(key))
JustStarted(anchor)
}
TAG_DRAWING -> {
val subtag = nbt.getCompound(key)
val anchor = HexUtils.deserializeVec3FromNBT(subtag.getLongArray(TAG_ANCHOR))
val startDir = HexDir.values()[subtag.getByte(TAG_START_DIR).toInt()]
val angles = subtag.getByteArray(TAG_ANGLES).map { HexAngle.values()[it.toInt()] }
Drawing(anchor, HexPattern(startDir, angles.toMutableList()))
}
else -> throw IllegalArgumentException("Unknown key $key: $nbt")
}
CastingHarness(mutableListOf(), ctx)
}
}
}

View file

@ -1,20 +1,5 @@
package at.petrak.hex.client;
import at.petrak.hex.HexUtils;
import at.petrak.hex.casting.CastingHarness;
import at.petrak.hex.items.HexItems;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import net.minecraft.client.Minecraft;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.LongArrayTag;
import net.minecraft.nbt.Tag;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.client.event.RenderGameOverlayEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
@ -22,67 +7,6 @@ import net.minecraftforge.eventbus.api.SubscribeEvent;
public class HexRenderOverlays {
@SubscribeEvent
public static void renderOverlay(RenderGameOverlayEvent e) {
LocalPlayer player = Minecraft.getInstance().player;
ItemStack held = player.getMainHandItem();
if (held.getItem() == HexItems.wand.get()) {
tryRenderCastOverlay(e, player, held);
}
}
private static void tryRenderCastOverlay(RenderGameOverlayEvent e, LocalPlayer player, ItemStack wand) {
if (wand.hasTag() && !wand.getTag().isEmpty()) {
CompoundTag tag = wand.getTag();
if (tag.contains(CastingHarness.TAG_POINTS)) {
PoseStack ps = e.getMatrixStack();
Minecraft mc = Minecraft.getInstance();
MultiBufferSource.BufferSource buffers = mc.renderBuffers().bufferSource();
Vec3 camPos = mc.gameRenderer.getMainCamera().getPosition();
Vec3 eyePos = player.getEyePosition(e.getPartialTicks());
ps.pushPose();
ps.translate(camPos.x, camPos.y, camPos.z);
// comment from when i tried to do this in VCC says I need this
// the chain of trust goes back to eutro
// *shudders*
ps.translate(-eyePos.x, -eyePos.y, -eyePos.z);
ListTag tagPointBlobs = tag.getList(CastingHarness.TAG_POINTS, Tag.TAG_LIST);
for (int patIdx = 0; patIdx < tagPointBlobs.size(); patIdx++) {
ListTag tagPoints = tagPointBlobs.getList(patIdx);
// Start new line
VertexConsumer buf = buffers.getBuffer(RenderType.LINES);
for (int idx = 0; idx < tagPoints.size(); idx++) {
// getLongArray is borken, who knew
Vec3 here = HexUtils.deserializeVec3FromNBT(
((LongArrayTag) tagPoints.get(idx)).getAsLongArray());
addVertex(ps, buf, here);
if (idx == tagPoints.size() - 1 &&
patIdx == tagPointBlobs.size() - 1 &&
tag.contains(CastingHarness.TAG_PDS) &&
!tag.getCompound(CastingHarness.TAG_PDS)
.contains(CastingHarness.PatternDrawState.TAG_BETWEEN_PATTERNS)) {
// Draw the final line to the player cursor
VertexConsumer buf1 = buffers.getBuffer(RenderType.LINES);
addVertex(ps, buf1, player.position().add(player.getLookAngle()));
}
}
}
ps.popPose();
buffers.endBatch();
}
}
}
private static void addVertex(PoseStack ps, VertexConsumer buf, Vec3 vert) {
buf.vertex(ps.last().pose(), (float) vert.x, (float) vert.y, (float) vert.z)
.color(128, 128, 255, 255)
.normal(0.0f, 1.0f, 0.0f)
.endVertex();
}
}

View file

@ -0,0 +1,169 @@
package at.petrak.hex.client.gui
import at.petrak.hex.HexMod
import at.petrak.hex.HexUtils.TAU
import at.petrak.hex.hexes.HexCoord
import at.petrak.hex.hexes.HexDir
import at.petrak.hex.hexes.HexPattern
import at.petrak.hex.network.HexMessages
import at.petrak.hex.network.MsgNewSpellPatternSyn
import com.mojang.blaze3d.systems.RenderSystem
import com.mojang.blaze3d.vertex.DefaultVertexFormat
import com.mojang.blaze3d.vertex.PoseStack
import com.mojang.blaze3d.vertex.Tesselator
import com.mojang.blaze3d.vertex.VertexFormat
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.screens.Screen
import net.minecraft.network.chat.TextComponent
import net.minecraft.world.phys.Vec2
import kotlin.math.*
class GuiSpellcasting : Screen(TextComponent("")) {
private var patterns: MutableList<Pair<HexPattern, Vec2>> = mutableListOf()
private var drawState: PatternDrawState = PatternDrawState.BetweenPatterns
override fun mouseClicked(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean {
if (super.mouseClicked(pMouseX, pMouseY, pButton)) {
return true
}
if (this.drawState is PatternDrawState.BetweenPatterns) {
this.drawState = PatternDrawState.JustStarted(Vec2(pMouseX.toFloat(), pMouseY.toFloat()))
}
return false
}
override fun mouseDragged(pMouseX: Double, pMouseY: Double, pButton: Int, pDragX: Double, pDragY: Double): Boolean {
if (super.mouseDragged(pMouseX, pMouseY, pButton, pDragX, pDragY)) {
return true
}
val anchor = when (this.drawState) {
PatternDrawState.BetweenPatterns -> null
is PatternDrawState.JustStarted -> (this.drawState as PatternDrawState.JustStarted).start
is PatternDrawState.Drawing -> (this.drawState as PatternDrawState.Drawing).current
}
if (anchor != null) {
val mouse = Vec2(pMouseX.toFloat(), pMouseY.toFloat())
if (anchor.distanceToSqr(mouse) >= this.hexSize() * this.hexSize()) {
val delta = mouse.add(anchor.negated())
val angle = atan2(delta.y, delta.x)
// 0 is right, increases clockwise(?)
val snappedAngle = angle.div(TAU.toFloat()).mod(6.0f)
val newdir = HexDir.values()[(-snappedAngle.times(6).roundToInt() + 1).mod(6)]
// The player might have a lousy aim, so set the new anchor point to the "ideal"
// location as if they had hit it exactly on the nose.
val idealNextLoc = anchor.add(Vec2(cos(snappedAngle), sin(snappedAngle)))
if (this.drawState is PatternDrawState.JustStarted) {
val pat = HexPattern(newdir)
this.drawState = PatternDrawState.Drawing(anchor, idealNextLoc, pat)
HexMod.LOGGER.info("Started drawing new pattern: $pat")
} else if (this.drawState is PatternDrawState.Drawing) {
// how anyone gets around without a borrowck is beyond me
val ds = (this.drawState as PatternDrawState.Drawing)
val success = ds.wipPattern.tryAppendDir(newdir)
if (success) {
ds.current = idealNextLoc
HexMod.LOGGER.info("Added to pattern: ${ds.wipPattern} ; New current pos: ${ds.current}")
}
}
}
}
return false
}
override fun mouseReleased(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean {
if (super.mouseReleased(pMouseX, pMouseY, pButton)) {
return true
}
when (this.drawState) {
PatternDrawState.BetweenPatterns -> {}
is PatternDrawState.JustStarted -> {
// Well, we never managed to get anything on the stack this go-around.
this.drawState = PatternDrawState.BetweenPatterns
if (this.patterns.isEmpty()) {
Minecraft.getInstance().setScreen(null)
}
}
is PatternDrawState.Drawing -> {
val (tstart, current, pat) = this.drawState as PatternDrawState.Drawing
this.drawState = PatternDrawState.BetweenPatterns
HexMessages.getNetwork().sendToServer(MsgNewSpellPatternSyn(0, pat))
}
}
return false
}
override fun render(poseStack: PoseStack, pMouseX: Int, pMouseY: Int, pPartialTick: Float) {
super.render(poseStack, pMouseX, pMouseY, pPartialTick)
// oh god
val tess = Tesselator.getInstance()
val buf = tess.builder
RenderSystem.disableCull()
RenderSystem.disableTexture()
RenderSystem.enableBlend()
for ((pat, origin) in this.patterns) {
buf.begin(VertexFormat.Mode.LINE_STRIP, DefaultVertexFormat.POSITION_COLOR)
for (coord in pat.positions()) {
val pix = this.coordToPx(coord, origin)
buf.vertex(pix.x.toDouble(), pix.y.toDouble(), 0.0).color(127, 127, 255, 255).endVertex()
}
buf.end()
}
// Now draw the currently WIP pattern
if (this.drawState !is PatternDrawState.BetweenPatterns) {
buf.begin(VertexFormat.Mode.LINE_STRIP, DefaultVertexFormat.POSITION_COLOR)
if (this.drawState is PatternDrawState.JustStarted) {
val ds = this.drawState as PatternDrawState.JustStarted
buf.vertex(ds.start.x.toDouble(), ds.start.y.toDouble(), 0.0).color(200, 200, 255, 255).endVertex()
} else if (this.drawState is PatternDrawState.Drawing) {
val ds = this.drawState as PatternDrawState.Drawing
for (pos in ds.wipPattern.positions()) {
val pix = this.coordToPx(pos, ds.start)
buf.vertex(pix.x.toDouble(), pix.y.toDouble(), 0.0).color(200, 200, 255, 255).endVertex()
}
}
buf.vertex(pMouseX.toDouble(), pMouseY.toDouble(), 0.0).color(240, 240, 255, 255).endVertex()
buf.end()
}
tess.end()
}
// why the hell is this default true
override fun isPauseScreen(): Boolean = false
/** Distance between adjacent hex centers */
fun hexSize(): Float =
this.width.toFloat() / 16.0f
fun coordToPx(coord: HexCoord, origin: Vec2) =
origin.add(
Vec2(
sqrt(3.0f) * coord.q.toFloat() + sqrt(3.0f) / 2.0f * coord.r.toFloat(),
1.5f * coord.r
).scale(this.hexSize())
)
private sealed class PatternDrawState {
/** We're waiting on the player to right-click again */
object BetweenPatterns : PatternDrawState()
/** We just started drawing and haven't drawn the first line yet. */
data class JustStarted(val start: Vec2) : PatternDrawState()
/** We've started drawing a pattern for real. */
data class Drawing(val start: Vec2, var current: Vec2, val wipPattern: HexPattern) : PatternDrawState()
}
}

View file

@ -1,5 +1,9 @@
package at.petrak.hex.hexes
import net.minecraft.nbt.ByteArrayTag
import net.minecraft.nbt.ByteTag
import net.minecraft.nbt.CompoundTag
/**
* Sequence of angles to define a pattern traced.
*/
@ -46,6 +50,14 @@ data class HexPattern(val startDir: HexDir, val angles: MutableList<HexAngle> =
return out
}
fun serializeToNBT(): CompoundTag {
val out = CompoundTag()
out.put(TAG_START_DIR, ByteTag.valueOf(this.startDir.ordinal.toByte()))
val anglesTag = ByteArrayTag(this.angles.map { it.ordinal.toByte() })
out.put(TAG_ANGLES, anglesTag)
return out
}
// Terrible shorthand method for easy matching
fun anglesSignature(): String {
return buildString {
@ -71,4 +83,16 @@ data class HexPattern(val startDir: HexDir, val angles: MutableList<HexAngle> =
append(this@HexPattern.anglesSignature())
append("]")
}
companion object {
const val TAG_START_DIR = "start_dir"
const val TAG_ANGLES = "angles"
@JvmStatic
fun DeserializeFromNBT(tag: CompoundTag): HexPattern {
val startDir = HexDir.values()[tag.getByte(TAG_START_DIR).toInt()]
val angles = tag.getByteArray(TAG_ANGLES).map { HexAngle.values()[it.toInt()] }
return HexPattern(startDir, angles.toMutableList())
}
}
}

View file

@ -1,18 +1,12 @@
package at.petrak.hex.items;
import at.petrak.hex.casting.CastingHarness;
import at.petrak.hex.casting.CastingHarness.CastResult;
import net.minecraft.Util;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.TextComponent;
import net.minecraft.server.level.ServerPlayer;
import at.petrak.hex.client.gui.GuiSpellcasting;
import net.minecraft.client.Minecraft;
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.item.ItemUtils;
import net.minecraft.world.level.Level;
public class ItemWand extends Item {
@ -21,43 +15,11 @@ public class ItemWand extends Item {
}
@Override
public void inventoryTick(ItemStack stack, Level world, Entity entity, int slotId, boolean isSelected) {
super.inventoryTick(stack, world, entity, slotId, isSelected);
if (world.isClientSide() || !isSelected) {
return;
public InteractionResultHolder<ItemStack> use(Level world, Player player, InteractionHand hand) {
if (world.isClientSide()) {
Minecraft.getInstance().setScreen(new GuiSpellcasting());
}
ServerPlayer player = (ServerPlayer) entity;
CompoundTag stackTag = stack.getOrCreateTag();
if (!stackTag.isEmpty() || player.isUsingItem()) {
CastingHarness harness = CastingHarness.DeserializeFromNBT(stack.getOrCreateTag(), player);
CastResult res = harness.update();
if (res instanceof CastResult.Nothing) {
// Save back the context
stack.setTag(harness.serializeToNBT());
} else {
if (res instanceof CastResult.Success) {
CastResult.Success success = (CastResult.Success) res;
success.getSpell().cast(harness.getCtx());
} else if (res instanceof CastResult.Error) {
CastResult.Error error = (CastResult.Error) res;
player.sendMessage(new TextComponent(error.toString()), Util.NIL_UUID);
}
// In any case clear the tag, we're through
stack.setTag(new CompoundTag());
}
}
}
@Override
public int getUseDuration(ItemStack pStack) {
return 9001;
}
@Override
public InteractionResultHolder<ItemStack> use(Level pLevel, Player pPlayer, InteractionHand pUsedHand) {
return ItemUtils.startUsingInstantly(pLevel, pPlayer, pUsedHand);
return InteractionResultHolder.success(player.getItemInHand(hand));
}
}

View file

@ -0,0 +1,29 @@
package at.petrak.hex.network;
import at.petrak.hex.HexMod;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.network.NetworkRegistry;
import net.minecraftforge.network.simple.SimpleChannel;
public class HexMessages {
private static final String PROTOCOL_VERSION = "1";
private static final SimpleChannel NETWORK = NetworkRegistry.newSimpleChannel(
new ResourceLocation(HexMod.MOD_ID, "main"),
() -> PROTOCOL_VERSION,
PROTOCOL_VERSION::equals,
PROTOCOL_VERSION::equals
);
public static SimpleChannel getNetwork() {
return NETWORK;
}
public static void register() {
int messageIdx = 0;
NETWORK.registerMessage(messageIdx++, MsgNewSpellPatternSyn.class, MsgNewSpellPatternSyn::serialize,
MsgNewSpellPatternSyn::deserialize, MsgNewSpellPatternSyn::handle);
NETWORK.registerMessage(messageIdx++, MsgNewSpellPatternAck.class, MsgNewSpellPatternAck::serialize,
MsgNewSpellPatternAck::deserialize, MsgNewSpellPatternAck::handle);
}
}

View file

@ -0,0 +1,45 @@
package at.petrak.hex.network;
import io.netty.buffer.ByteBuf;
import net.minecraft.client.Minecraft;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.fml.DistExecutor;
import net.minecraftforge.network.NetworkDirection;
import net.minecraftforge.network.NetworkEvent;
import java.util.function.Supplier;
/**
* Sent server->client when the player finishes casting a spell.
*/
public record MsgNewSpellPatternAck(int windowID, boolean quitCasting) {
public static MsgNewSpellPatternAck deserialize(ByteBuf buffer) {
var buf = new FriendlyByteBuf(buffer);
var windowID = buf.readInt();
var quitCasting = buf.readBoolean();
return new MsgNewSpellPatternAck(windowID, quitCasting);
}
public void serialize(ByteBuf buffer) {
var buf = new FriendlyByteBuf(buffer);
buf.writeInt(this.windowID);
buf.writeBoolean(this.quitCasting);
}
public void handle(Supplier<NetworkEvent.Context> ctx) {
ctx.get().enqueueWork(() ->
DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> {
if (ctx.get().getDirection() != NetworkDirection.PLAY_TO_CLIENT) {
return;
}
if (quitCasting) {
Minecraft.getInstance().setScreen(null);
}
})
);
ctx.get().setPacketHandled(true);
}
}

View file

@ -0,0 +1,67 @@
package at.petrak.hex.network;
import at.petrak.hex.casting.CastingContext;
import at.petrak.hex.casting.CastingHarness;
import at.petrak.hex.casting.CastingHarness.CastResult;
import at.petrak.hex.hexes.HexPattern;
import at.petrak.hex.items.ItemWand;
import io.netty.buffer.ByteBuf;
import net.minecraft.Util;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.chat.TextComponent;
import net.minecraft.server.level.ServerPlayer;
import net.minecraftforge.network.NetworkEvent;
import net.minecraftforge.network.PacketDistributor;
import java.util.function.Supplier;
/**
* Sent client->server when the player finishes drawing a pattern.
* Server will send back a MsgNewSpellPatternAck packet
*/
public record MsgNewSpellPatternSyn(int windowID, HexPattern pattern) {
// Not actually sure if you can check for window ID
// but i'll leave space for it just in case
public static MsgNewSpellPatternSyn deserialize(ByteBuf buffer) {
var buf = new FriendlyByteBuf(buffer);
var windowID = buf.readInt();
var pattern = HexPattern.DeserializeFromNBT(buf.readAnySizeNbt());
return new MsgNewSpellPatternSyn(windowID, pattern);
}
public void serialize(ByteBuf buffer) {
var buf = new FriendlyByteBuf(buffer);
buf.writeInt(this.windowID);
buf.writeNbt(this.pattern.serializeToNBT());
}
public void handle(Supplier<NetworkEvent.Context> ctx) {
ctx.get().enqueueWork(() -> {
ServerPlayer sender = ctx.get().getSender();
if (sender != null) {
var held = sender.getMainHandItem();
if (held.getItem() instanceof ItemWand) {
var tag = held.getOrCreateTag();
var harness = CastingHarness.DeserializeFromNBT(tag, sender);
var res = harness.update(this.pattern);
if (res instanceof CastResult.Success) {
CastResult.Success success = (CastResult.Success) res;
success.getSpell().cast(new CastingContext(sender));
} else if (res instanceof CastResult.Error) {
CastResult.Error error = (CastResult.Error) res;
sender.sendMessage(new TextComponent(error.getExn().toString()), Util.NIL_UUID);
}
var quit = !(res instanceof CastResult.Nothing);
HexMessages.getNetwork()
.send(PacketDistributor.PLAYER.with(() -> sender),
new MsgNewSpellPatternAck(this.windowID, quit));
}
}
});
ctx.get().setPacketHandled(true);
}
}