begone, helper objects. kotlin has no need of thou

This commit is contained in:
yrsegal@gmail.com 2022-05-25 00:10:09 -04:00
parent 5945d6ea70
commit 0da0b40256
14 changed files with 347 additions and 362 deletions

View file

@ -86,14 +86,14 @@ object PatternRegistry {
// when we try to look
for (handler in specialHandlers) {
val op = handler.handler.handlePattern(pat)
if (op != null) return Pair(op, handler.id)
if (op != null) return op to handler.id
}
// Is it global?
val sig = pat.anglesSignature()
this.regularPatternLookup[sig]?.let {
val op = this.operatorLookup[it.opId] ?: throw MishapInvalidPattern()
return Pair(op, it.opId)
return op to it.opId
}
// Look it up in the world?
@ -102,7 +102,7 @@ object PatternRegistry {
ds.computeIfAbsent(Save.Companion::load, { Save.create(overworld.seed) }, TAG_SAVED_DATA)
perWorldPatterns.lookup[sig]?.let {
val op = this.operatorLookup[it.first]!!
return Pair(op, it.first)
return op to it.first
}
throw MishapInvalidPattern()
@ -188,7 +188,7 @@ object PatternRegistry {
val entry = tag.getCompound(sig)
val opId = ResourceLocation.tryParse(entry.getString(TAG_OP_ID))!!
val startDir = HexDir.values()[entry.getInt(TAG_START_DIR)]
map[sig] = Pair(opId, startDir)
map[sig] = opId to startDir
}
return Save(map)
}
@ -199,7 +199,7 @@ object PatternRegistry {
for ((opId, entry) in PatternRegistry.perWorldPatternLookup) {
// waugh why doesn't kotlin recursively destructure things
val scrungled = EulerPathFinder.findAltDrawing(entry.prototype, seed)
map[scrungled.anglesSignature()] = Pair(opId, scrungled.startDir)
map[scrungled.anglesSignature()] = opId to scrungled.startDir
}
val save = Save(map)
save.setDirty()

View file

@ -385,10 +385,10 @@ class CastingHarness private constructor(
}
if (casterStack.`is`(HexItemTags.WANDS) || ipsCanDrawFromInv) {
val manableItems = this.ctx.caster.inventory.items
.filter(ManaHelper::isManaItem)
.sortedWith(Comparator(ManaHelper::compare).reversed())
.filter(::isManaItem)
.sortedWith(Comparator(::compareManaItem).reversed())
for (stack in manableItems) {
costLeft -= ManaHelper.extractMana(stack, costLeft)
costLeft -= extractMana(stack, costLeft)
if (costLeft <= 0)
break
}

View file

@ -49,7 +49,7 @@ sealed interface ContinuationFrame {
*/
data class Evaluate(val list: SpellList) : ContinuationFrame {
// Discard this frame and keep discarding frames.
override fun breakDownwards(stack: List<SpellDatum<*>>) = Pair(false, stack)
override fun breakDownwards(stack: List<SpellDatum<*>>) = false to stack
// Step the list of patterns, evaluating a single one.
override fun evaluate(
@ -83,7 +83,7 @@ sealed interface ContinuationFrame {
*/
object FinishEval : ContinuationFrame {
// Don't do anything else to the stack, just finish the halt statement.
override fun breakDownwards(stack: List<SpellDatum<*>>) = Pair(true, stack)
override fun breakDownwards(stack: List<SpellDatum<*>>) = true to stack
// Evaluating it does nothing; it's only a boundary condition.
override fun evaluate(
@ -122,10 +122,10 @@ sealed interface ContinuationFrame {
/** When halting, we add the stack state at halt to the stack accumulator, then return the original pre-Thoth stack, plus the accumulator. */
override fun breakDownwards(stack: List<SpellDatum<*>>): Pair<Boolean, List<SpellDatum<*>>> {
val newStack = baseStack!!.toMutableList()
val newStack = baseStack?.toMutableList() ?: mutableListOf()
acc.addAll(stack)
newStack.add(SpellDatum.make(acc))
return Pair(true, newStack)
return true to newStack
}
/** Step the Thoth computation, enqueueing one code evaluation. */
@ -146,17 +146,15 @@ sealed interface ContinuationFrame {
// If we still have data to process...
val (stackTop, newCont) = if (data.nonEmpty) {
Pair(
data.car, // Push the next datum to the top of the stack,
continuation
// put the next Thoth object back on the stack for the next Thoth cycle,
.pushFrame(ForEach(data.cdr, code, stack, acc))
// and prep the Thoth'd code block for evaluation.
.pushFrame(Evaluate(code))
)
// Push the next datum to the top of the stack,
data.car to continuation
// put the next Thoth object back on the stack for the next Thoth cycle,
.pushFrame(ForEach(data.cdr, code, stack, acc))
// and prep the Thoth'd code block for evaluation.
.pushFrame(Evaluate(code))
} else {
// Else, dump our final list onto the stack.
Pair(SpellDatum.make(acc), continuation)
SpellDatum.make(acc) to continuation
}
val tStack = stack.toMutableList()
tStack.add(stackTop)

View file

@ -4,8 +4,6 @@ import at.petrak.hexcasting.api.spell.SpellDatum
/**
* A change to the data in a CastHarness after a pattern is drawn.
*
* [wasThisPatternInvalid] is for the benefit of the controller.
*/
data class FunctionalData(
val stack: List<SpellDatum<*>>,

View file

@ -23,15 +23,15 @@ data class HexPattern(public val startDir: HexDir, public val angles: MutableLis
var compass = this.startDir
var cursor = HexCoord.Origin
for (a in this.angles) {
linesSeen.add(Pair(cursor, compass))
linesSeen.add(cursor to compass)
// Line from here to there also blocks there to here
linesSeen.add(Pair(cursor + compass, compass.rotatedBy(HexAngle.BACK)))
linesSeen.add(cursor + compass to compass.rotatedBy(HexAngle.BACK))
cursor += compass
compass *= a
}
cursor += compass
val potentialNewLine = Pair(cursor, newDir)
val potentialNewLine = cursor to newDir
if (potentialNewLine in linesSeen) return false
val nextAngle = newDir - compass
if (nextAngle == HexAngle.BACK) return false

View file

@ -1,3 +1,4 @@
@file:JvmName("ManaHelper")
package at.petrak.hexcasting.api.utils
import at.petrak.hexcasting.xplat.IXplatAbstractions
@ -5,76 +6,70 @@ import net.minecraft.util.Mth
import net.minecraft.world.item.ItemStack
import kotlin.math.roundToInt
object ManaHelper {
@JvmStatic
fun isManaItem(stack: ItemStack): Boolean {
val manaHolder = IXplatAbstractions.INSTANCE.findManaHolder(stack) ?: return false
if (!manaHolder.canProvide())
return false
return manaHolder.withdrawMana(-1, true) > 0
}
fun isManaItem(stack: ItemStack): Boolean {
val manaHolder = IXplatAbstractions.INSTANCE.findManaHolder(stack) ?: return false
if (!manaHolder.canProvide())
return false
return manaHolder.withdrawMana(-1, true) > 0
}
/**
* Extract [cost] mana from [stack]. If [cost] is less than zero, extract all mana instead.
* This may mutate [stack] (and may consume it) unless [simulate] is set.
*
* If [drainForBatteries] is false, this will only consider forms of mana that can be used to make new batteries.
*
* Return the amount of mana extracted. This may be over [cost] if mana is wasted.
*/
@JvmStatic
@JvmOverloads
fun extractMana(
stack: ItemStack,
cost: Int = -1,
drainForBatteries: Boolean = false,
simulate: Boolean = false
): Int {
val manaHolder = IXplatAbstractions.INSTANCE.findManaHolder(stack) ?: return 0
/**
* Extract [cost] mana from [stack]. If [cost] is less than zero, extract all mana instead.
* This may mutate [stack] (and may consume it) unless [simulate] is set.
*
* If [drainForBatteries] is false, this will only consider forms of mana that can be used to make new batteries.
*
* Return the amount of mana extracted. This may be over [cost] if mana is wasted.
*/
@JvmOverloads
fun extractMana(
stack: ItemStack,
cost: Int = -1,
drainForBatteries: Boolean = false,
simulate: Boolean = false
): Int {
val manaHolder = IXplatAbstractions.INSTANCE.findManaHolder(stack) ?: return 0
if (drainForBatteries && !manaHolder.canConstructBattery())
return 0
if (drainForBatteries && !manaHolder.canConstructBattery())
return 0
return manaHolder.withdrawMana(cost, simulate)
}
return manaHolder.withdrawMana(cost, simulate)
}
/**
* Sorted from least important to most important
*/
fun compare(astack: ItemStack, bstack: ItemStack): Int {
val aMana = IXplatAbstractions.INSTANCE.findManaHolder(astack)
val bMana = IXplatAbstractions.INSTANCE.findManaHolder(bstack)
/**
* Sorted from least important to most important
*/
fun compareManaItem(astack: ItemStack, bstack: ItemStack): Int {
val aMana = IXplatAbstractions.INSTANCE.findManaHolder(astack)
val bMana = IXplatAbstractions.INSTANCE.findManaHolder(bstack)
return if (astack.item != bstack.item) {
(aMana?.consumptionPriority ?: 0) - (bMana?.consumptionPriority ?: 0)
} else if (aMana != null && bMana != null) {
aMana.mana - bMana.mana
} else {
astack.count - bstack.count
}
}
@JvmStatic
fun barColor(mana: Int, maxMana: Int): Int {
val amt = if (maxMana == 0) {
0f
} else {
mana.toFloat() / maxMana.toFloat()
}
val r = Mth.lerp(amt, 84f, 254f)
val g = Mth.lerp(amt, 57f, 203f)
val b = Mth.lerp(amt, 138f, 230f)
return Mth.color(r / 255f, g / 255f, b / 255f)
}
@JvmStatic
fun barWidth(mana: Int, maxMana: Int): Int {
val amt = if (maxMana == 0) {
0f
} else {
mana.toFloat() / maxMana.toFloat()
}
return (13f * amt).roundToInt()
return if (astack.item != bstack.item) {
(aMana?.consumptionPriority ?: 0) - (bMana?.consumptionPriority ?: 0)
} else if (aMana != null && bMana != null) {
aMana.mana - bMana.mana
} else {
astack.count - bstack.count
}
}
fun manaBarColor(mana: Int, maxMana: Int): Int {
val amt = if (maxMana == 0) {
0f
} else {
mana.toFloat() / maxMana.toFloat()
}
val r = Mth.lerp(amt, 84f, 254f)
val g = Mth.lerp(amt, 57f, 203f)
val b = Mth.lerp(amt, 138f, 230f)
return Mth.color(r / 255f, g / 255f, b / 255f)
}
fun manaBarWidth(mana: Int, maxMana: Int): Int {
val amt = if (maxMana == 0) {
0f
} else {
mana.toFloat() / maxMana.toFloat()
}
return (13f * amt).roundToInt()
}

View file

@ -134,15 +134,15 @@ value class NbtListBuilder(val tag: ListTag) {
}
/**
* Add the given Tag<* tags to this list
* Add the given tags to this list
*/
inline operator fun Collection<Tag>.unaryPlus() {
tag.addAll(this)
}
/**
* Add the given Tag<* tag to this list. This is explicitly defined for [ListTag] because otherwise there is overload
* ambiguity between the [Tag<*] and [Collection]<[Tag<*]> methods.
* Add the given tag to this list. This is explicitly defined for [ListTag] because otherwise there is overload
* ambiguity between the [Tag] and [Collection]<Tag> methods.
*/
inline operator fun ListTag.unaryPlus() {
tag.add(this)

View file

@ -1,8 +1,10 @@
@file:JvmName("RenderLib")
package at.petrak.hexcasting.client
import at.petrak.hexcasting.api.mod.HexConfig
import at.petrak.hexcasting.api.spell.math.HexPattern
import at.petrak.hexcasting.api.utils.TAU
import at.petrak.hexcasting.client.gui.GuiSpellcasting
import com.mojang.blaze3d.systems.RenderSystem
import com.mojang.blaze3d.vertex.DefaultVertexFormat
import com.mojang.blaze3d.vertex.PoseStack
@ -12,6 +14,7 @@ import com.mojang.math.Matrix4f
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.screens.Screen
import net.minecraft.core.BlockPos
import net.minecraft.util.FastColor
import net.minecraft.util.Mth
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.levelgen.XoroshiroRandomSource
@ -19,272 +22,259 @@ import net.minecraft.world.level.levelgen.synth.PerlinNoise
import net.minecraft.world.phys.Vec2
import kotlin.math.floor
import kotlin.math.min
import net.minecraft.util.FastColor.ARGB32 as FC
/**
* Common draw code
* Source of perlin noise
*/
object RenderLib {
/**
* Source of perlin noise
*/
val NOISE = PerlinNoise.create(XoroshiroRandomSource(9001L), listOf(0, 1, 2, 3, 4))
val NOISE: PerlinNoise = PerlinNoise.create(XoroshiroRandomSource(9001L), listOf(0, 1, 2, 3, 4))
/**
* Draw a sequence of linePoints spanning the given points.
*
* Please make sure to enable the right asinine shaders; see [GuiSpellcasting][at.petrak.hexcasting.client.gui.GuiSpellcasting]
*/
@JvmStatic
@JvmOverloads
fun drawLineSeq(
mat: Matrix4f,
points: List<Vec2>,
width: Float,
z: Float,
tail: Int,
head: Int,
animTime: Float? = null,
) {
if (points.size <= 1) return
/**
* Draw a sequence of linePoints spanning the given points.
*
* Please make sure to enable the right asinine shaders; see [GuiSpellcasting]
*/
@JvmOverloads
fun drawLineSeq(
mat: Matrix4f,
points: List<Vec2>,
width: Float,
z: Float,
tail: Int,
head: Int,
animTime: Float? = null,
) {
if (points.size <= 1) return
val r1 = FC.red(tail).toFloat()
val g1 = FC.green(tail).toFloat()
val b1 = FC.blue(tail).toFloat()
val a = FC.alpha(tail)
val headSource = if (Screen.hasControlDown() != HexConfig.client().ctrlTogglesOffStrokeOrder())
head
else
tail
val r2 = FC.red(headSource).toFloat()
val g2 = FC.green(headSource).toFloat()
val b2 = FC.blue(headSource).toFloat()
val r1 = FastColor.ARGB32.red(tail).toFloat()
val g1 = FastColor.ARGB32.green(tail).toFloat()
val b1 = FastColor.ARGB32.blue(tail).toFloat()
val a = FastColor.ARGB32.alpha(tail)
val headSource = if (Screen.hasControlDown() != HexConfig.client().ctrlTogglesOffStrokeOrder())
head
else
tail
val r2 = FastColor.ARGB32.red(headSource).toFloat()
val g2 = FastColor.ARGB32.green(headSource).toFloat()
val b2 = FastColor.ARGB32.blue(headSource).toFloat()
// they spell it wrong at mojang lmao
val tess = Tesselator.getInstance()
val buf = tess.builder
// they spell it wrong at mojang lmao
val tess = Tesselator.getInstance()
val buf = tess.builder
// We use one single TRIANGLE_STRIP
// in order to connect adjacent segments together and not get the weird hinge effect.
// There's still some artifacting but this is passable, at least.
buf.begin(VertexFormat.Mode.TRIANGLE_STRIP, DefaultVertexFormat.POSITION_COLOR)
// We use one single TRIANGLE_STRIP
// in order to connect adjacent segments together and not get the weird hinge effect.
// There's still some artifacting but this is passable, at least.
buf.begin(VertexFormat.Mode.TRIANGLE_STRIP, DefaultVertexFormat.POSITION_COLOR)
val n = points.size
for ((i, pair) in points.zipWithNext().withIndex()) {
val (p1, p2) = pair
// https://github.com/not-fl3/macroquad/blob/master/src/shapes.rs#L163
// GuiComponent::innerFill line 52
// fedor have useful variable names challenge (99% can't beat)
val dx = p2.x - p1.x
val dy = p2.y - p1.y
// normal x and y, presumably?
val nx = -dy
val ny = dx
// thickness?
val tlen = Mth.sqrt(nx * nx + ny * ny) / (width * 0.5f)
val tx = nx / tlen
val ty = ny / tlen
val n = points.size
for ((i, pair) in points.zipWithNext().withIndex()) {
val (p1, p2) = pair
// https://github.com/not-fl3/macroquad/blob/master/src/shapes.rs#L163
// GuiComponent::innerFill line 52
// fedor have useful variable names challenge (99% can't beat)
val dx = p2.x - p1.x
val dy = p2.y - p1.y
// normal x and y, presumably?
val nx = -dy
val ny = dx
// thickness?
val tlen = Mth.sqrt(nx * nx + ny * ny) / (width * 0.5f)
val tx = nx / tlen
val ty = ny / tlen
fun color(time: Float): BlockPos =
BlockPos(Mth.lerp(time, r1, r2).toInt(), Mth.lerp(time, g1, g2).toInt(), Mth.lerp(time, b1, b2).toInt())
fun color(time: Float): BlockPos =
BlockPos(Mth.lerp(time, r1, r2).toInt(), Mth.lerp(time, g1, g2).toInt(), Mth.lerp(time, b1, b2).toInt())
val color1 = color(i.toFloat() / n)
val color2 = color((i + 1f) / n)
buf.vertex(mat, p1.x + tx, p1.y + ty, z).color(color1.x, color1.y, color1.z, a).endVertex()
buf.vertex(mat, p1.x - tx, p1.y - ty, z).color(color1.x, color1.y, color1.z, a).endVertex()
buf.vertex(mat, p2.x + tx, p2.y + ty, z).color(color2.x, color2.y, color2.z, a).endVertex()
buf.vertex(mat, p2.x - tx, p2.y - ty, z).color(color2.x, color2.y, color2.z, a).endVertex()
}
tess.end()
if (animTime != null) {
val pointCircuit =
(animTime * 30f * HexConfig.client().patternPointSpeedMultiplier().toFloat()) % (points.size + 10)
// subtract 1 to avoid the point appearing between the end and start for 1 frame
if (pointCircuit < points.size - 1) {
val pointMacro = floor(pointCircuit).toInt()
val pointMicro = pointCircuit - pointMacro
val p1 = points[pointMacro]
val p2 = points[(pointMacro + 1) % points.size]
val drawPos = Vec2(
p1.x + (p2.x - p1.x) * pointMicro,
p1.y + (p2.y - p1.y) * pointMicro,
)
drawSpot(
mat,
drawPos,
2f,
(r1 + 255) / 2f / 255f,
(g1 + 255) / 2f / 255f,
(b1 + 255) / 2f / 255f,
a / 1.2f / 255f
)
}
}
val color1 = color(i.toFloat() / n)
val color2 = color((i + 1f) / n)
buf.vertex(mat, p1.x + tx, p1.y + ty, z).color(color1.x, color1.y, color1.z, a).endVertex()
buf.vertex(mat, p1.x - tx, p1.y - ty, z).color(color1.x, color1.y, color1.z, a).endVertex()
buf.vertex(mat, p2.x + tx, p2.y + ty, z).color(color2.x, color2.y, color2.z, a).endVertex()
buf.vertex(mat, p2.x - tx, p2.y - ty, z).color(color2.x, color2.y, color2.z, a).endVertex()
}
tess.end()
/**
* Draw a hex pattern from the given list of non-zappy points (as in, do the *style* of drawing it,
* you have to do the conversion yourself.)
*/
@JvmStatic
fun drawPatternFromPoints(
mat: Matrix4f,
points: List<Vec2>,
drawLast: Boolean,
tail: Int,
head: Int,
animTime: Float? = null
) {
val zappyPts = makeZappy(points, 10f, 2.5f, 0.1f)
val nodes = if (drawLast) {
points
} else {
points.dropLast(1)
}
drawLineSeq(mat, zappyPts, 5f, 0f, tail, head, null)
drawLineSeq(mat, zappyPts, 2f, 1f, screenCol(tail), screenCol(head), animTime)
for (node in nodes) {
if (animTime != null) {
val pointCircuit =
(animTime * 30f * HexConfig.client().patternPointSpeedMultiplier().toFloat()) % (points.size + 10)
// subtract 1 to avoid the point appearing between the end and start for 1 frame
if (pointCircuit < points.size - 1) {
val pointMacro = floor(pointCircuit).toInt()
val pointMicro = pointCircuit - pointMacro
val p1 = points[pointMacro]
val p2 = points[(pointMacro + 1) % points.size]
val drawPos = Vec2(
p1.x + (p2.x - p1.x) * pointMicro,
p1.y + (p2.y - p1.y) * pointMicro,
)
drawSpot(
mat,
node,
drawPos,
2f,
dodge(FC.red(head)) / 255f,
dodge(FC.green(head)) / 255f,
dodge(FC.blue(head)) / 255f,
FC.alpha(head) / 255f
);
(r1 + 255) / 2f / 255f,
(g1 + 255) / 2f / 255f,
(b1 + 255) / 2f / 255f,
a / 1.2f / 255f
)
}
}
/**
* Split up a sequence of linePoints with a lightning effect
* @param hops: rough number of points to subdivide each segment into
* @param speed: rate at which the lightning effect should move/shake/etc
*/
@JvmStatic
fun makeZappy(points: List<Vec2>, hops: Float, variance: Float, speed: Float): List<Vec2> {
// Nothing in, nothing out
if (points.isEmpty()) {
return emptyList()
}
val zSeed = (ClientTickCounter.total.toDouble()) * speed
// Create our output list of zap points
val zappyPts = mutableListOf(points[0])
// For each segment in the original...
for ((i, pair) in points.zipWithNext().withIndex()) {
val (src, target) = pair
// Compute distance-squared to the destination, then scale it down by # of hops
// to know how long each individual hop should be (squared)
val hopDistSqr = src.distanceToSqr(target) / (hops * hops)
// Then take the square root to find the actual hop distance
val hopDist = Mth.sqrt(hopDistSqr)
// Compute how big the radius of variance should be
val maxVariance = hopDist * variance
var position = src
var j = 0
while (position.distanceToSqr(target) > hopDistSqr) {
// Add the next hop...
val hop = target.add(position.negated()).normalized().scale(hopDist)
// as well as some random variance...
// (We use i, j (segment #, subsegment #) as seeds for the Perlin noise,
// and zSeed (i.e. time elapsed) to perturb the shape gradually over time)
val theta = (3 * NOISE.getValue(i.toDouble(), j.toDouble(), zSeed) * TAU).toFloat()
val r = NOISE.getValue(i.inv().toDouble(), j.toDouble(), zSeed).toFloat() * maxVariance
val randomHop = Vec2(r * Mth.cos(theta), r * Mth.sin(theta))
position = position.add(hop).add(randomHop)
// Then record the new location.
zappyPts.add(position)
j += 1
}
// Finally, we hit the destination, add that too
zappyPts.add(target)
}
return zappyPts
}
/**
* Draw a little circle, because Minecraft rendering code is a nightmare and doesn't
* include primitive drawing code...
*/
@JvmStatic
fun drawSpot(mat: Matrix4f, point: Vec2, radius: Float, r: Float, g: Float, b: Float, a: Float) {
val tess = Tesselator.getInstance()
val buf = tess.builder
// https://stackoverflow.com/questions/20394727/gl-triangle-strip-vs-gl-triangle-fan
// Starting point is the center
buf.begin(VertexFormat.Mode.TRIANGLE_FAN, DefaultVertexFormat.POSITION_COLOR)
buf.vertex(mat, point.x, point.y, 1f).color(r, g, b, a).endVertex()
// https://github.com/not-fl3/macroquad/blob/master/src/shapes.rs#L98
// yes they are gonna be little hexagons fite me
val fracOfCircle = 6
// run 0 AND last; this way the circle closes
for (i in 0..fracOfCircle) {
val theta = i.toFloat() / fracOfCircle * TAU.toFloat()
val rx = Mth.cos(theta) * radius + point.x
val ry = Mth.sin(theta) * radius + point.y
buf.vertex(mat, rx, ry, 1f).color(r, g, b, a).endVertex()
}
tess.end()
}
fun dodge(n: Int): Float = n * 0.9f
fun screen(n: Int): Int = (n + 255) / 2
@JvmStatic
fun screenCol(n: Int): Int {
return FC.color(
FC.alpha(n),
screen(FC.red(n)),
screen(FC.green(n)),
screen(FC.blue(n)),
)
}
/**
* Return the scale and dots formed by the pattern when centered.
*/
@JvmStatic
fun getCenteredPattern(pattern: HexPattern, width: Float, height: Float, minSize: Float): Pair<Float, List<Vec2>> {
// Do two passes: one with a random size to find a good COM and one with the real calculation
val com1: Vec2 = pattern.getCenter(1f)
val lines1: List<Vec2> = pattern.toLines(1f, Vec2.ZERO)
var maxDx = -1f
var maxDy = -1f
for (dot in lines1) {
val dx = Mth.abs(dot.x - com1.x)
if (dx > maxDx) {
maxDx = dx
}
val dy = Mth.abs(dot.y - com1.y)
if (dy > maxDy) {
maxDy = dy
}
}
val scale =
min(minSize, min(width / 3f / maxDx, height / 3f / maxDy))
val com2: Vec2 = pattern.getCenter(scale)
val lines2: List<Vec2> = pattern.toLines(scale, com2.negated())
return Pair(scale, lines2)
}
@JvmStatic
fun renderItemStackInGui(ms: PoseStack, stack: ItemStack, x: Int, y: Int) {
transferMsToGl(ms) { Minecraft.getInstance().itemRenderer.renderAndDecorateItem(stack, x, y) }
}
@JvmStatic
fun transferMsToGl(ms: PoseStack, toRun: Runnable) {
val mvs = RenderSystem.getModelViewStack()
mvs.pushPose()
mvs.mulPoseMatrix(ms.last().pose())
RenderSystem.applyModelViewMatrix()
toRun.run()
mvs.popPose()
RenderSystem.applyModelViewMatrix()
}
}
/**
* Draw a hex pattern from the given list of non-zappy points (as in, do the *style* of drawing it,
* you have to do the conversion yourself.)
*/
@JvmOverloads
fun drawPatternFromPoints(
mat: Matrix4f,
points: List<Vec2>,
drawLast: Boolean,
tail: Int,
head: Int,
animTime: Float? = null
) {
val zappyPts = makeZappy(points, 10f, 2.5f, 0.1f)
val nodes = if (drawLast) {
points
} else {
points.dropLast(1)
}
drawLineSeq(mat, zappyPts, 5f, 0f, tail, head, null)
drawLineSeq(mat, zappyPts, 2f, 1f, screenCol(tail), screenCol(head), animTime)
for (node in nodes) {
drawSpot(
mat,
node,
2f,
dodge(FastColor.ARGB32.red(head)) / 255f,
dodge(FastColor.ARGB32.green(head)) / 255f,
dodge(FastColor.ARGB32.blue(head)) / 255f,
FastColor.ARGB32.alpha(head) / 255f
);
}
}
/**
* Split up a sequence of linePoints with a lightning effect
* @param hops: rough number of points to subdivide each segment into
* @param speed: rate at which the lightning effect should move/shake/etc
*/
fun makeZappy(points: List<Vec2>, hops: Float, variance: Float, speed: Float): List<Vec2> {
// Nothing in, nothing out
if (points.isEmpty()) {
return emptyList()
}
val zSeed = (ClientTickCounter.total.toDouble()) * speed
// Create our output list of zap points
val zappyPts = mutableListOf(points[0])
// For each segment in the original...
for ((i, pair) in points.zipWithNext().withIndex()) {
val (src, target) = pair
// Compute distance-squared to the destination, then scale it down by # of hops
// to know how long each individual hop should be (squared)
val hopDistSqr = src.distanceToSqr(target) / (hops * hops)
// Then take the square root to find the actual hop distance
val hopDist = Mth.sqrt(hopDistSqr)
// Compute how big the radius of variance should be
val maxVariance = hopDist * variance
var position = src
var j = 0
while (position.distanceToSqr(target) > hopDistSqr) {
// Add the next hop...
val hop = target.add(position.negated()).normalized().scale(hopDist)
// as well as some random variance...
// (We use i, j (segment #, subsegment #) as seeds for the Perlin noise,
// and zSeed (i.e. time elapsed) to perturb the shape gradually over time)
val theta = (3 * NOISE.getValue(i.toDouble(), j.toDouble(), zSeed) * TAU).toFloat()
val r = NOISE.getValue(i.inv().toDouble(), j.toDouble(), zSeed).toFloat() * maxVariance
val randomHop = Vec2(r * Mth.cos(theta), r * Mth.sin(theta))
position = position.add(hop).add(randomHop)
// Then record the new location.
zappyPts.add(position)
j += 1
}
// Finally, we hit the destination, add that too
zappyPts.add(target)
}
return zappyPts
}
/**
* Draw a little circle, because Minecraft rendering code is a nightmare and doesn't
* include primitive drawing code...
*/
fun drawSpot(mat: Matrix4f, point: Vec2, radius: Float, r: Float, g: Float, b: Float, a: Float) {
val tess = Tesselator.getInstance()
val buf = tess.builder
// https://stackoverflow.com/questions/20394727/gl-triangle-strip-vs-gl-triangle-fan
// Starting point is the center
buf.begin(VertexFormat.Mode.TRIANGLE_FAN, DefaultVertexFormat.POSITION_COLOR)
buf.vertex(mat, point.x, point.y, 1f).color(r, g, b, a).endVertex()
// https://github.com/not-fl3/macroquad/blob/master/src/shapes.rs#L98
// yes they are gonna be little hexagons fite me
val fracOfCircle = 6
// run 0 AND last; this way the circle closes
for (i in 0..fracOfCircle) {
val theta = i.toFloat() / fracOfCircle * TAU.toFloat()
val rx = Mth.cos(theta) * radius + point.x
val ry = Mth.sin(theta) * radius + point.y
buf.vertex(mat, rx, ry, 1f).color(r, g, b, a).endVertex()
}
tess.end()
}
fun screenCol(n: Int): Int {
return FastColor.ARGB32.color(
FastColor.ARGB32.alpha(n),
screen(FastColor.ARGB32.red(n)),
screen(FastColor.ARGB32.green(n)),
screen(FastColor.ARGB32.blue(n)),
)
}
fun screen(n: Int) = (n + 255) / 2
fun dodge(n: Int) = n * 0.9f
/**
* Return the scale and dots formed by the pattern when centered.
*/
fun getCenteredPattern(pattern: HexPattern, width: Float, height: Float, minSize: Float): Pair<Float, List<Vec2>> {
// Do two passes: one with a random size to find a good COM and one with the real calculation
val com1: Vec2 = pattern.getCenter(1f)
val lines1: List<Vec2> = pattern.toLines(1f, Vec2.ZERO)
var maxDx = -1f
var maxDy = -1f
for (dot in lines1) {
val dx = Mth.abs(dot.x - com1.x)
if (dx > maxDx) {
maxDx = dx
}
val dy = Mth.abs(dot.y - com1.y)
if (dy > maxDy) {
maxDy = dy
}
}
val scale =
min(minSize, min(width / 3f / maxDx, height / 3f / maxDy))
val com2: Vec2 = pattern.getCenter(scale)
val lines2: List<Vec2> = pattern.toLines(scale, com2.negated())
return scale to lines2
}
fun renderItemStackInGui(ms: PoseStack, stack: ItemStack, x: Int, y: Int) {
transferMsToGl(ms) { Minecraft.getInstance().itemRenderer.renderAndDecorateItem(stack, x, y) }
}
fun transferMsToGl(ms: PoseStack, toRun: Runnable) {
val mvs = RenderSystem.getModelViewStack()
mvs.pushPose()
mvs.mulPoseMatrix(ms.last().pose())
RenderSystem.applyModelViewMatrix()
toRun.run()
mvs.popPose()
RenderSystem.applyModelViewMatrix()
}

View file

@ -9,7 +9,8 @@ import at.petrak.hexcasting.api.spell.math.HexCoord
import at.petrak.hexcasting.api.spell.math.HexDir
import at.petrak.hexcasting.api.spell.math.HexPattern
import at.petrak.hexcasting.api.utils.otherHand
import at.petrak.hexcasting.client.RenderLib
import at.petrak.hexcasting.client.drawPatternFromPoints
import at.petrak.hexcasting.client.drawSpot
import at.petrak.hexcasting.client.sound.GridSoundInstance
import at.petrak.hexcasting.common.items.ItemSpellbook
import at.petrak.hexcasting.common.lib.HexItems
@ -279,7 +280,7 @@ class GuiSpellcasting(
0f,
1f
)
RenderLib.drawSpot(
drawSpot(
mat,
dotPx,
scaledDist * 2f,
@ -293,7 +294,7 @@ class GuiSpellcasting(
RenderSystem.defaultBlendFunc()
for ((pat, origin, valid) in this.patterns) {
RenderLib.drawPatternFromPoints(mat, pat.toLines(
drawPatternFromPoints(mat, pat.toLines(
this.hexSize(),
this.coordToPx(origin)
), true, valid.color or (0xC8 shl 24), valid.fadeColor or (0xC8 shl 24))
@ -315,7 +316,7 @@ class GuiSpellcasting(
}
points.add(mousePos)
RenderLib.drawPatternFromPoints(mat, points, false, 0xff_64c8ff_u.toInt(), 0xff_fecbe6_u.toInt())
drawPatternFromPoints(mat, points, false, 0xff_64c8ff_u.toInt(), 0xff_fecbe6_u.toInt())
}
RenderSystem.setShader { prevShader }

View file

@ -1,8 +1,8 @@
package at.petrak.hexcasting.client.gui;
import at.petrak.hexcasting.client.ClientTickCounter;
import at.petrak.hexcasting.client.RenderLib;
import at.petrak.hexcasting.api.spell.math.HexPattern;
import at.petrak.hexcasting.client.RenderLib;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.*;

View file

@ -10,7 +10,8 @@ import at.petrak.hexcasting.api.spell.SpellOperator
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.spell.mishaps.MishapBadItem
import at.petrak.hexcasting.api.spell.mishaps.MishapBadOffhandItem
import at.petrak.hexcasting.api.utils.ManaHelper
import at.petrak.hexcasting.api.utils.extractMana
import at.petrak.hexcasting.api.utils.isManaItem
import at.petrak.hexcasting.common.items.magic.ItemManaHolder
import at.petrak.hexcasting.common.lib.HexItems
import net.minecraft.world.entity.item.ItemEntity
@ -46,7 +47,7 @@ object OpMakeBattery : SpellOperator {
ctx.assertEntityInRange(entity)
if (!ManaHelper.isManaItem(entity.item) || ManaHelper.extractMana(
if (!isManaItem(entity.item) || extractMana(
entity.item,
drainForBatteries = true,
simulate = true
@ -67,7 +68,7 @@ object OpMakeBattery : SpellOperator {
val (handStack, hand) = ctx.getHeldItemToOperateOn { it.`is`(HexItemTags.PHIAL_BASE) }
if (handStack.`is`(HexItemTags.PHIAL_BASE) && itemEntity.isAlive) {
val entityStack = itemEntity.item.copy()
val manaAmt = ManaHelper.extractMana(entityStack, drainForBatteries = true)
val manaAmt = extractMana(entityStack, drainForBatteries = true)
if (manaAmt > 0) {
ctx.caster.setItemInHand(
hand,

View file

@ -10,7 +10,8 @@ import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.spell.mishaps.MishapBadItem
import at.petrak.hexcasting.api.spell.mishaps.MishapBadOffhandItem
import at.petrak.hexcasting.api.spell.mishaps.MishapOthersName
import at.petrak.hexcasting.api.utils.ManaHelper
import at.petrak.hexcasting.api.utils.extractMana
import at.petrak.hexcasting.api.utils.isManaItem
import at.petrak.hexcasting.common.items.magic.ItemPackagedHex
import at.petrak.hexcasting.xplat.IXplatAbstractions
import net.minecraft.world.entity.item.ItemEntity
@ -36,7 +37,7 @@ class OpMakePackagedSpell<T : ItemPackagedHex>(val itemType: T, val cost: Int) :
}
ctx.assertEntityInRange(entity)
if (!ManaHelper.isManaItem(entity.item) || ManaHelper.extractMana(
if (!isManaItem(entity.item) || extractMana(
entity.item,
drainForBatteries = true,
simulate = true
@ -64,7 +65,7 @@ class OpMakePackagedSpell<T : ItemPackagedHex>(val itemType: T, val cost: Int) :
&& itemEntity.isAlive
) {
val entityStack = itemEntity.item.copy()
val manaAmt = ManaHelper.extractMana(entityStack, drainForBatteries = true)
val manaAmt = extractMana(entityStack, drainForBatteries = true)
if (manaAmt > 0) {
hexHolder.writeHex(patterns, manaAmt)
}

View file

@ -10,7 +10,8 @@ import at.petrak.hexcasting.api.spell.SpellOperator
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.spell.mishaps.MishapBadItem
import at.petrak.hexcasting.api.spell.mishaps.MishapBadOffhandItem
import at.petrak.hexcasting.api.utils.ManaHelper
import at.petrak.hexcasting.api.utils.extractMana
import at.petrak.hexcasting.api.utils.isManaItem
import at.petrak.hexcasting.xplat.IXplatAbstractions
import net.minecraft.world.entity.item.ItemEntity
@ -37,7 +38,7 @@ object OpRecharge : SpellOperator {
val entity = args.getChecked<ItemEntity>(0, argc)
ctx.assertEntityInRange(entity)
if (!ManaHelper.isManaItem(entity.item)) {
if (!isManaItem(entity.item)) {
throw MishapBadItem.of(
entity,
"mana"
@ -65,7 +66,7 @@ object OpRecharge : SpellOperator {
val maxMana = mana.maxMana
val existingMana = mana.mana
val manaAmt = ManaHelper.extractMana(entityStack, maxMana - existingMana)
val manaAmt = extractMana(entityStack, maxMana - existingMana)
mana.mana = manaAmt + existingMana

View file

@ -57,14 +57,14 @@ public abstract class ItemManaHolder extends Item implements ManaHolderItem {
public int getBarColor(ItemStack pStack) {
var mana = getMana(pStack);
var maxMana = getMaxMana(pStack);
return ManaHelper.barColor(mana, maxMana);
return ManaHelper.manaBarColor(mana, maxMana);
}
@Override
public int getBarWidth(ItemStack pStack) {
var mana = getMana(pStack);
var maxMana = getMaxMana(pStack);
return ManaHelper.barWidth(mana, maxMana);
return ManaHelper.manaBarWidth(mana, maxMana);
}
@Override