push for rendering help
This commit is contained in:
parent
f52880e527
commit
c5c38f7cd8
10 changed files with 388 additions and 374 deletions
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
169
src/main/java/at/petrak/hex/client/gui/GuiSpellcasting.kt
Normal file
169
src/main/java/at/petrak/hex/client/gui/GuiSpellcasting.kt
Normal 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()
|
||||
}
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
29
src/main/java/at/petrak/hex/network/HexMessages.java
Normal file
29
src/main/java/at/petrak/hex/network/HexMessages.java
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue