[WIP] start 1.15 port

reorganize packages to match Fabric version
use util classes from Fabric version
This commit is contained in:
octarine-noise
2021-05-01 13:52:21 +02:00
parent 9566ae8341
commit 09ccb83e8b
81 changed files with 1220 additions and 1011 deletions

View File

@@ -0,0 +1,60 @@
package mods.betterfoliage.util
import java.lang.ref.WeakReference
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
interface Invalidator {
fun invalidate() {
val iterator = callbacks.iterator()
while(iterator.hasNext()) iterator.next().let { callback ->
callback.get()?.invoke() ?: iterator.remove()
}
}
val callbacks: MutableList<WeakReference<()->Unit>>
fun onInvalidate(callback: ()->Unit) {
callbacks.add(WeakReference(callback))
}
}
class LazyInvalidatable<V>(invalidator: Invalidator, val valueFactory: ()->V): ReadOnlyProperty<Any, V> {
init { invalidator.onInvalidate { value = null } }
var value: V? = null
override fun getValue(thisRef: Any, property: KProperty<*>): V {
value?.let { return it }
return synchronized(this) {
value?.let { return it }
valueFactory().apply { value = this }
}
}
}
class LazyMap<K, V>(val invalidator: Invalidator, val valueFactory: (K)->V) {
init { invalidator.onInvalidate { values.clear() } }
val values = mutableMapOf<K, V>()
operator fun get(key: K): V {
values[key]?.let { return it }
return synchronized(values) {
values[key]?.let { return it }
valueFactory(key).apply { values[key] = this }
}
}
operator fun set(key: K, value: V) { values[key] = value }
fun delegate(key: K) = Delegate(key)
inner class Delegate(val key: K) : ReadOnlyProperty<Any, V> {
init { invalidator.onInvalidate { cached = null } }
private var cached: V? = null
override fun getValue(thisRef: Any, property: KProperty<*>): V {
cached?.let { return it }
get(key).let { cached = it; return it }
}
}
}

View File

@@ -0,0 +1,63 @@
package mods.betterfoliage.util
import java.util.*
/**
* Starting with the second element of this [Iterable] until the last, call the supplied lambda with
* the parameters (index, element, previous element).
*/
inline fun <reified T> Iterable<T>.forEachPairIndexed(func: (Int, T, T)->Unit) {
var previous: T? = null
forEachIndexed { idx, current ->
if (previous != null) func(idx, current, previous!!)
previous = current
}
}
inline fun <T, C: Comparable<C>> Pair<T, T>.minBy(func: (T)->C) =
if (func(first) < func(second)) first else second
inline fun <T, C: Comparable<C>> Pair<T, T>.maxBy(func: (T)->C) =
if (func(first) > func(second)) first else second
inline fun <T, C: Comparable<C>> Triple<T, T, T>.maxValueBy(func: (T)->C): C {
var result = func(first)
func(second).let { if (it > result) result = it }
func(third).let { if (it > result) result = it }
return result
}
@Suppress("UNCHECKED_CAST")
inline fun <K, V> Map<K, V?>.filterValuesNotNull() = filterValues { it != null } as Map<K, V>
inline fun <reified T, R> Iterable<T>.findFirst(func: (T)->R?): R? {
forEach { func(it)?.let { return it } }
return null
}
inline fun <A1, reified A2, B> List<Pair<A1, B>>.filterIsInstanceFirst(cls: Class<A2>) = filter { it.first is A2 } as List<Pair<A2, B>>
/** Cross product of this [Iterable] with the parameter. */
fun <A, B> Iterable<A>.cross(other: Iterable<B>) = flatMap { a -> other.map { b -> a to b } }
inline fun <C, R, T> Iterable<T>.mapAs(transform: (C) -> R) = map { transform(it as C) }
inline fun <T1, T2> forEachNested(list1: Iterable<T1>, list2: Iterable<T2>, func: (T1, T2)-> Unit) =
list1.forEach { e1 ->
list2.forEach { e2 ->
func(e1, e2)
}
}
/** Mutating version of _map_. Replace each element of the list with the result of the given transformation. */
inline fun <reified T> MutableList<T>.replace(transform: (T) -> T) = forEachIndexed { idx, value -> this[idx] = transform(value) }
/** Exchange the two elements of the list with the given indices */
inline fun <T> MutableList<T>.exchange(idx1: Int, idx2: Int) {
val e = this[idx1]
this[idx1] = this[idx2]
this[idx2] = e
}
/** Return a random element from the array using the provided random generator */
inline operator fun <T> Array<T>.get(random: Random) = get(random.nextInt(Int.MAX_VALUE) % size)

View File

@@ -0,0 +1,15 @@
package mods.betterfoliage.util
import net.minecraft.client.Minecraft
import java.util.concurrent.CompletableFuture
import java.util.concurrent.CompletionStage
import java.util.function.Consumer
import java.util.function.Function
fun completedVoid() = CompletableFuture.completedFuture<Void>(null)!!
fun <T, U> CompletionStage<T>.map(func: (T)->U) = thenApply(Function(func)).toCompletableFuture()!!
fun <T, U> CompletionStage<T>.mapAsync(func: (T)->U) = thenApplyAsync(Function(func), Minecraft.getInstance()).toCompletableFuture()!!
fun <T> CompletionStage<T>.sink(func: (T)->Unit) = thenAccept(Consumer(func)).toCompletableFuture()!!
fun <T> CompletionStage<T>.sinkAsync(func: (T)->Unit) = thenAcceptAsync(Consumer(func), Minecraft.getInstance()).toCompletableFuture()!!

View File

@@ -0,0 +1,221 @@
package mods.betterfoliage.util
import net.minecraft.util.Direction
import net.minecraft.util.Direction.*
import net.minecraft.util.Direction.Axis.*
import net.minecraft.util.Direction.AxisDirection.NEGATIVE
import net.minecraft.util.Direction.AxisDirection.POSITIVE
import net.minecraft.util.math.BlockPos
// ================================
// Axes and directions
// ================================
val axes = listOf(X, Y, Z)
val axisDirs = listOf(POSITIVE, NEGATIVE)
val Direction.dir: AxisDirection get() = axisDirection
val AxisDirection.sign: String get() = when(this) { POSITIVE -> "+"; NEGATIVE -> "-" }
val allDirections = Direction.values()
val horizontalDirections = listOf(NORTH, SOUTH, EAST, WEST)
val allDirOffsets = allDirections.map { Int3(it) }
val Pair<Axis, AxisDirection>.face: Direction get() = when(this) {
X to POSITIVE -> EAST; X to NEGATIVE -> WEST;
Y to POSITIVE -> UP; Y to NEGATIVE -> DOWN;
Z to POSITIVE -> SOUTH; else -> NORTH;
}
val Direction.perpendiculars: List<Direction> get() =
axes.filter { it != this.axis }.cross(axisDirs).map { it.face }
val Direction.offset: Int3 get() = allDirOffsets[ordinal]
/** Old ForgeDirection rotation matrix yanked from 1.7.10 */
val ROTATION_MATRIX: Array<IntArray> get() = arrayOf(
intArrayOf(0, 1, 4, 5, 3, 2, 6),
intArrayOf(0, 1, 5, 4, 2, 3, 6),
intArrayOf(5, 4, 2, 3, 0, 1, 6),
intArrayOf(4, 5, 2, 3, 1, 0, 6),
intArrayOf(2, 3, 1, 0, 4, 5, 6),
intArrayOf(3, 2, 0, 1, 4, 5, 6)
)
// ================================
// Vectors
// ================================
operator fun Direction.times(scale: Double) =
Double3(directionVec.x.toDouble() * scale, directionVec.y.toDouble() * scale, directionVec.z.toDouble() * scale)
val Direction.vec: Double3 get() = Double3(directionVec.x.toDouble(), directionVec.y.toDouble(), directionVec.z.toDouble())
operator fun BlockPos.plus(other: Int3) = BlockPos(x + other.x, y + other.y, z + other.z)
/** 3D vector of [Double]s. Offers both mutable operations, and immutable operations in operator notation. */
data class Double3(var x: Double, var y: Double, var z: Double) {
constructor(x: Float, y: Float, z: Float) : this(x.toDouble(), y.toDouble(), z.toDouble())
constructor(dir: Direction) : this(dir.directionVec.x.toDouble(), dir.directionVec.y.toDouble(), dir.directionVec.z.toDouble())
companion object {
val zero: Double3 get() = Double3(0.0, 0.0, 0.0)
fun weight(v1: Double3, weight1: Double, v2: Double3, weight2: Double) =
Double3(v1.x * weight1 + v2.x * weight2, v1.y * weight1 + v2.y * weight2, v1.z * weight1 + v2.z * weight2)
}
// immutable operations
operator fun plus(other: Double3) = Double3(x + other.x, y + other.y, z + other.z)
operator fun unaryMinus() = Double3(-x, -y, -z)
operator fun minus(other: Double3) = Double3(x - other.x, y - other.y, z - other.z)
operator fun times(scale: Double) = Double3(x * scale, y * scale, z * scale)
operator fun times(other: Double3) = Double3(x * other.x, y * other.y, z * other.z)
/** Rotate this vector, and return coordinates in the unrotated frame */
fun rotate(rot: Rotation) = Double3(
rot.rotatedComponent(EAST, x, y, z),
rot.rotatedComponent(UP, x, y, z),
rot.rotatedComponent(SOUTH, x, y, z)
)
// mutable operations
fun setTo(other: Double3): Double3 { x = other.x; y = other.y; z = other.z; return this }
fun setTo(x: Double, y: Double, z: Double): Double3 { this.x = x; this.y = y; this.z = z; return this }
fun setTo(x: Float, y: Float, z: Float) = setTo(x.toDouble(), y.toDouble(), z.toDouble())
fun add(other: Double3): Double3 { x += other.x; y += other.y; z += other.z; return this }
fun add(x: Double, y: Double, z: Double): Double3 { this.x += x; this.y += y; this.z += z; return this }
fun sub(other: Double3): Double3 { x -= other.x; y -= other.y; z -= other.z; return this }
fun sub(x: Double, y: Double, z: Double): Double3 { this.x -= x; this.y -= y; this.z -= z; return this }
fun invert(): Double3 { x = -x; y = -y; z = -z; return this }
fun mul(scale: Double): Double3 { x *= scale; y *= scale; z *= scale; return this }
fun mul(other: Double3): Double3 { x *= other.x; y *= other.y; z *= other.z; return this }
fun rotateMut(rot: Rotation): Double3 {
val rotX = rot.rotatedComponent(EAST, x, y, z)
val rotY = rot.rotatedComponent(UP, x, y, z)
val rotZ = rot.rotatedComponent(SOUTH, x, y, z)
return setTo(rotX, rotY, rotZ)
}
// misc operations
infix fun dot(other: Double3) = x * other.x + y * other.y + z * other.z
infix fun cross(o: Double3) = Double3(y * o.z - z * o.y, z * o.x - x * o.z, x * o.y - y * o.x)
val length: Double get() = Math.sqrt(x * x + y * y + z * z)
val normalize: Double3 get() = (1.0 / length).let { Double3(x * it, y * it, z * it) }
val nearestCardinal: Direction get() = nearestAngle(this, allDirections.asIterable()) { it.vec }.first
}
/** 3D vector of [Int]s. Offers both mutable operations, and immutable operations in operator notation. */
data class Int3(var x: Int, var y: Int, var z: Int) {
constructor(dir: Direction) : this(dir.directionVec.x, dir.directionVec.y, dir.directionVec.z)
constructor(offset: Pair<Int, Direction>) : this(
offset.first * offset.second.directionVec.x,
offset.first * offset.second.directionVec.y,
offset.first * offset.second.directionVec.z
)
companion object {
val zero = Int3(0, 0, 0)
}
// immutable operations
operator fun plus(other: Int3) = Int3(x + other.x, y + other.y, z + other.z)
operator fun plus(other: Pair<Int, Direction>) = Int3(
x + other.first * other.second.directionVec.x,
y + other.first * other.second.directionVec.y,
z + other.first * other.second.directionVec.z
)
operator fun unaryMinus() = Int3(-x, -y, -z)
operator fun minus(other: Int3) = Int3(x - other.x, y - other.y, z - other.z)
operator fun times(scale: Int) = Int3(x * scale, y * scale, z * scale)
operator fun times(other: Int3) = Int3(x * other.x, y * other.y, z * other.z)
/** Rotate this vector, and return coordinates in the unrotated frame */
fun rotate(rot: Rotation) = Int3(
rot.rotatedComponent(EAST, x, y, z),
rot.rotatedComponent(UP, x, y, z),
rot.rotatedComponent(SOUTH, x, y, z)
)
// mutable operations
fun setTo(other: Int3): Int3 { x = other.x; y = other.y; z = other.z; return this }
fun setTo(x: Int, y: Int, z: Int): Int3 { this.x = x; this.y = y; this.z = z; return this }
fun add(other: Int3): Int3 { x += other.x; y += other.y; z += other.z; return this }
fun sub(other: Int3): Int3 { x -= other.x; y -= other.y; z -= other.z; return this }
fun invert(): Int3 { x = -x; y = -y; z = -z; return this }
fun mul(scale: Int): Int3 { x *= scale; y *= scale; z *= scale; return this }
fun mul(other: Int3): Int3 { x *= other.x; y *= other.y; z *= other.z; return this }
fun rotateMut(rot: Rotation): Int3 {
val rotX = rot.rotatedComponent(EAST, x, y, z)
val rotY = rot.rotatedComponent(UP, x, y, z)
val rotZ = rot.rotatedComponent(SOUTH, x, y, z)
return setTo(rotX, rotY, rotZ)
}
}
// ================================
// Rotation
// ================================
val Direction.rotations: Array<Direction> get() =
Array(6) { idx -> Direction.values()[ROTATION_MATRIX[ordinal][idx]] }
fun Direction.rotate(rot: Rotation) = rot.forward[ordinal]
fun rot(axis: Direction) = Rotation.rot90[axis.ordinal]
/**
* Class representing an arbitrary rotation (or combination of rotations) around cardinal axes by 90 degrees.
* In effect, a permutation of [ForgeDirection]s.
*/
@Suppress("NOTHING_TO_INLINE")
class Rotation(val forward: Array<Direction>, val reverse: Array<Direction>) {
operator fun plus(other: Rotation) = Rotation(
Array(6) { idx -> forward[other.forward[idx].ordinal] },
Array(6) { idx -> other.reverse[reverse[idx].ordinal] }
)
operator fun unaryMinus() = Rotation(reverse, forward)
operator fun times(num: Int) = when(num % 4) { 1 -> this; 2 -> this + this; 3 -> -this; else -> identity }
inline fun rotatedComponent(dir: Direction, x: Int, y: Int, z: Int) =
when(reverse[dir.ordinal]) { EAST -> x; WEST -> -x; UP -> y; DOWN -> -y; SOUTH -> z; NORTH -> -z; else -> 0 }
inline fun rotatedComponent(dir: Direction, x: Double, y: Double, z: Double) =
when(reverse[dir.ordinal]) { EAST -> x; WEST -> -x; UP -> y; DOWN -> -y; SOUTH -> z; NORTH -> -z; else -> 0.0 }
companion object {
// Forge rotation matrix is left-hand
val rot90 = Array(6) { idx -> Rotation(allDirections[idx].opposite.rotations, allDirections[idx].rotations) }
val identity = Rotation(allDirections, allDirections)
}
}
// ================================
// Miscellaneous
// ================================
inline operator fun <reified T> Array<T>.get(face: Direction): T = get(face.ordinal)
data class BoxFace(val top: Direction, val left: Direction) {
val allCorners = listOf(top to left, top to left.opposite, top.opposite to left, top.opposite to left.opposite)
}
val boxFaces = allDirections.map { when(it) {
DOWN -> BoxFace(SOUTH, WEST)
UP -> BoxFace(SOUTH, EAST)
NORTH -> BoxFace(WEST, UP)
SOUTH -> BoxFace(UP, WEST)
WEST -> BoxFace(SOUTH, UP)
EAST -> BoxFace(SOUTH, DOWN)
}}.toTypedArray()
/** List of all 12 box edges, represented as a [Pair] of [ForgeDirection]s */
val boxEdges = allDirections.flatMap { face1 -> allDirections.filter { it.axis > face1.axis }.map { face1 to it } }
/**
* Get the closest object to the specified point from a list of objects.
*
* @param[vertex] the reference point
* @param[objs] list of geomertric objects
* @param[objPos] lambda to calculate the position of an object
* @return [Pair] of (object, distance)
*/
fun <T> nearestPosition(vertex: Double3, objs: Iterable<T>, objPos: (T)-> Double3): Pair<T, Double> =
objs.map { it to (objPos(it) - vertex).length }.minBy { it.second }!!
/**
* Get the object closest in orientation to the specified vector from a list of objects.
*
* @param[vector] the reference vector (direction)
* @param[objs] list of geomertric objects
* @param[objAngle] lambda to calculate the orientation of an object
* @return [Pair] of (object, normalized dot product)
*/
fun <T> nearestAngle(vector: Double3, objs: Iterable<T>, objAngle: (T)-> Double3): Pair<T, Double> =
objs.map { it to objAngle(it).dot(vector) }.maxBy { it.second }!!

View File

@@ -0,0 +1,22 @@
@file:JvmName("Utils")
package mods.betterfoliage.util
import net.minecraft.util.text.StringTextComponent
import net.minecraft.util.text.Style
import net.minecraft.util.text.TextFormatting
import net.minecraft.util.text.TextFormatting.AQUA
import net.minecraft.util.text.TextFormatting.GRAY
fun stripTooltipDefaultText(tooltip: MutableList<String>) {
var defaultRows = false
val iter = tooltip.iterator()
while (iter.hasNext()) {
if (iter.next().startsWith(AQUA.toString())) defaultRows = true
if (defaultRows) iter.remove()
}
}
fun textComponent(msg: String, color: TextFormatting = GRAY): StringTextComponent {
val style = Style().apply { this.color = color }
return StringTextComponent(msg).apply { this.style = style }
}

View File

@@ -0,0 +1,75 @@
@file:Suppress("NOTHING_TO_INLINE")
package mods.betterfoliage.util
import net.minecraft.util.ResourceLocation
import net.minecraft.util.math.BlockPos
import net.minecraft.world.World
import org.apache.logging.log4j.Level
import org.apache.logging.log4j.Logger
import java.lang.Math.*
import kotlin.reflect.KProperty
const val PI2 = 2.0 * PI
/** Strip the given prefix off the start of the string, if present */
inline fun String.stripStart(str: String, ignoreCase: Boolean = true) = if (startsWith(str, ignoreCase)) substring(str.length) else this
inline fun String.stripEnd(str: String, ignoreCase: Boolean = true) = if (endsWith(str, ignoreCase)) substring(0, length - str.length) else this
/** Strip the given prefix off the start of the resource path, if present */
inline fun ResourceLocation.stripStart(str: String) = ResourceLocation(namespace, path.stripStart(str))
inline fun ResourceLocation.stripEnd(str: String) = ResourceLocation(namespace, path.stripEnd(str))
/**
* Property-level delegate backed by a [ThreadLocal].
*
* @param[init] Lambda to get initial value
*/
class ThreadLocalDelegate<T>(init: () -> T) {
var tlVal = ThreadLocal.withInitial(init)
operator fun getValue(thisRef: Any?, property: KProperty<*>): T = tlVal.get()
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { tlVal.set(value) }
}
/** Call the supplied lambda and return its result, or the given default value if an exception is thrown. */
fun <T> tryDefault(default: T, work: ()->T) = try { work() } catch (e: Throwable) { default }
/**
* Return this [Double] value if it lies between the two limits. If outside, return the
* minimum/maximum value correspondingly.
*/
fun Double.minmax(minVal: Double, maxVal: Double) = min(max(this, minVal), maxVal)
/**
* Return this [Int] value if it lies between the two limits. If outside, return the
* minimum/maximum value correspondingly.
*/
fun Int.minmax(minVal: Int, maxVal: Int) = min(max(this, minVal), maxVal)
fun nextPowerOf2(x: Int): Int {
return 1 shl (if (x == 0) 0 else 32 - Integer.numberOfLeadingZeros(x - 1))
}
/**
* Check if the Chunk containing the given [BlockPos] is loaded.
* Works for both [World] and [ChunkCache] (vanilla and OptiFine) instances.
*/
//fun IWorldReader.isBlockLoaded(pos: BlockPos) = when {
// this is World -> isBlockLoaded(pos, false)
// this is RenderChunkCache -> isworld.isBlockLoaded(pos, false)
// Refs.OptifineChunkCache.isInstance(this) -> (Refs.CCOFChunkCache.get(this) as ChunkCache).world.isBlockLoaded(pos, false)
// else -> false
//}
interface HasLogger {
val logger: Logger
val logName: String get() = this::class.simpleName!!
fun log(msg: String) = log(Level.INFO, msg)
fun log(level: Level, msg: String) = logger.log(level, "[$logName] $msg")
fun log(msg: String, e: Throwable) = log(Level.WARN, msg, e)
fun log(level: Level, msg: String, e: Throwable) = logger.log(level, "[$logName] $msg", e)
}
//fun textComponent(msg: String, color: Formatting = Formatting.GRAY): LiteralText {
// val style = Style().apply { this.color = color }
// return LiteralText(msg).apply { this.style = style }
//}

View File

@@ -0,0 +1,18 @@
package mods.betterfoliage.util
import net.minecraft.util.math.BlockPos
import java.util.Random
val random = Random(System.nanoTime())
fun randomB() = random.nextBoolean()
fun randomI(min: Int = 0, max: Int = Int.MAX_VALUE) = min + random.nextInt(max - min)
fun randomD(min: Double = 0.0, max: Double = 1.0) = random.nextDouble() * (max - min) + min
fun semiRandom(x: Int, y: Int, z: Int, seed: Int): Int {
var value = (x * x + y * y + z * z + x * y + y * z + z * x + (seed * seed))
value = (3 * x * value + 5 * y * value + 7 * z * value + (11 * seed))
return value shr 4
}
fun BlockPos.semiRandom(seed: Int) = semiRandom(x, y, z, seed)

View File

@@ -0,0 +1,179 @@
@file:JvmName("Reflection")
package mods.betterfoliage.util
import java.lang.reflect.Field
import java.lang.reflect.Method
import mods.betterfoliage.util.Namespace.*
/** Get a Java class with the given name. */
fun getJavaClass(name: String) = tryDefault(null) { Class.forName(name) }
/** Get the field with the given name and type using reflection. */
inline fun <reified T> Any.reflectField(field: String): T? =
tryDefault(null) { this.javaClass.getDeclaredField(field) }?.let {
it.isAccessible = true
it.get(this) as T
}
/** Get the static field with the given name and type using reflection. */
inline fun <reified T> Class<*>.reflectStaticField(field: String): T? =
tryDefault(null) { this.getDeclaredField(field) }?.let {
it.isAccessible = true
it.get(null) as T
}
/**
* Get all nested _object_s of this _object_ with reflection.
*
* @return [Pair]s of (name, instance)
*/
val Any.reflectNestedObjects: List<Pair<String, Any>> get() = this.javaClass.declaredClasses.map {
tryDefault(null) { it.name.split("$")[1] to it.getField("INSTANCE").get(null) }
}.filterNotNull()
/**
* Get all fields of this instance that match (or subclass) any of the given classes.
*
* @param[types] classes to look for
* @return [Pair]s of (field name, instance)
*/
fun Any.reflectFieldsOfType(vararg types: Class<*>) = this.javaClass.declaredFields
.filter { field -> types.any { it.isAssignableFrom(field.type) } }
.map { field -> field.name to field.let { it.isAccessible = true; it.get(this) } }
.filterNotNull()
fun <T> Any.reflectFields(type: Class<T>) = this.javaClass.declaredFields
.filter { field -> type.isAssignableFrom(field.type) }
.map { field -> field.name to field.let { it.isAccessible = true; it.get(this) as T } }
fun <T> Any.reflectDelegates(type: Class<T>) = this.javaClass.declaredFields
.filter { field -> type.isAssignableFrom(field.type) && field.name.contains("$") }
.map { field -> field.name.split("$")[0] to field.let { it.isAccessible = true; it.get(this) } as T }
enum class Namespace { MCP, SRG }
abstract class Resolvable<T> {
abstract fun resolve(): T?
val element: T? by lazy { resolve() }
}
/** Return true if all given elements are found. */
fun allAvailable(vararg codeElement: Resolvable<*>) = codeElement.all { it.element != null }
/**
* Reference to a class.
*
* @param[name] MCP name of the class
*/
open class ClassRef<T: Any?>(val name: String) : Resolvable<Class<T>>() {
companion object {
val int = ClassRefPrimitive("I", Int::class.java)
val long = ClassRefPrimitive("J", Long::class.java)
val float = ClassRefPrimitive("F", Float::class.java)
val boolean = ClassRefPrimitive("Z", Boolean::class.java)
val void = ClassRefPrimitive("V", Void::class.java)
}
open fun asmDescriptor(namespace: Namespace) = "L${name.replace(".", "/")};"
override fun resolve() = getJavaClass(name) as Class<T>?
fun isInstance(obj: Any) = element?.isInstance(obj) ?: false
}
/**
* Reference to a primitive type.
*
* @param[name] ASM descriptor of this primitive type
* @param[clazz] class of this primitive type
*/
class ClassRefPrimitive<T>(name: String, val clazz: Class<T>?) : ClassRef<T>(name) {
override fun asmDescriptor(namespace: Namespace) = name
override fun resolve() = clazz
}
/**
* Reference to a method.
*
* @param[parentClass] reference to the class containing the method
* @param[mcpName] MCP name of the method
* @param[srgName] SRG name of the method
* @param[returnType] reference to the return type
* @param[returnType] references to the argument types
*/
class MethodRef<T: Any?>(val parentClass: ClassRef<*>,
val mcpName: String,
val srgName: String,
val returnType: ClassRef<T>,
vararg val argTypes: ClassRef<*>
) : Resolvable<Method>() {
constructor(parentClass: ClassRef<*>, mcpName: String, returnType: ClassRef<T>, vararg argTypes: ClassRef<*>) :
this(parentClass, mcpName, mcpName, returnType, *argTypes)
fun name(namespace: Namespace) = when(namespace) { SRG -> srgName; MCP -> mcpName }
fun asmDescriptor(namespace: Namespace) = "(${argTypes.map { it.asmDescriptor(namespace) }.fold(""){ s1, s2 -> s1 + s2 } })${returnType.asmDescriptor(namespace)}"
override fun resolve(): Method? =
if (parentClass.element == null || argTypes.any { it.element == null }) null
else {
val args = argTypes.map { it.element!! }.toTypedArray()
listOf(srgName, mcpName).map { tryDefault(null) {
parentClass.element!!.getDeclaredMethod(it, *args)
}}.filterNotNull().firstOrNull()
?.apply { isAccessible = true }
}
/** Invoke this method using reflection. */
operator fun invoke(receiver: Any, vararg args: Any?) = element?.invoke(receiver, *args) as T
/** Invoke this static method using reflection. */
fun invokeStatic(vararg args: Any) = element?.invoke(null, *args)
}
/**
* Reference to a field.
*
* @param[parentClass] reference to the class containing the field
* @param[mcpName] MCP name of the field
* @param[srgName] SRG name of the field
* @param[type] reference to the field type
*/
class FieldRef<T>(val parentClass: ClassRef<*>,
val mcpName: String,
val srgName: String,
val type: ClassRef<T>
) : Resolvable<Field>() {
constructor(parentClass: ClassRef<*>, mcpName: String, type: ClassRef<T>) : this(parentClass, mcpName, mcpName, type)
fun name(namespace: Namespace) = when(namespace) { SRG -> srgName; MCP -> mcpName }
fun asmDescriptor(namespace: Namespace) = type.asmDescriptor(namespace)
override fun resolve(): Field? =
if (parentClass.element == null) null
else {
listOf(srgName, mcpName).mapNotNull { tryDefault(null) {
parentClass.element!!.getDeclaredField(it)
} }.firstOrNull()
?.apply{ isAccessible = true }
}
/** Get this field using reflection. */
operator fun get(receiver: Any?) = element?.get(receiver) as T
/** Get this static field using reflection. */
fun getStatic() = get(null)
fun set(receiver: Any?, obj: Any?) { element?.set(receiver, obj) }
}
fun Any.isInstance(cls: ClassRef<*>) = cls.isInstance(this)
interface ReflectionCallable<T> {
operator fun invoke(vararg args: Any): T
}
inline operator fun <reified T> Any.get(field: FieldRef<T>) = field.get(this)
inline operator fun <reified T> Any.set(field: FieldRef<T>, value: T) = field.set(this, value)
inline operator fun <T> Any.get(methodRef: MethodRef<T>) = object : ReflectionCallable<T> {
override fun invoke(vararg args: Any) = methodRef.invoke(this@get, args)
}

View File

@@ -0,0 +1,26 @@
package mods.betterfoliage.util
import net.minecraft.client.Minecraft
import net.minecraft.resources.IReloadableResourceManager
import net.minecraft.resources.IResource
import net.minecraft.resources.IResourceManager
import net.minecraft.util.ResourceLocation
/** Concise getter for the Minecraft resource manager. */
val resourceManager: IReloadableResourceManager
get() = Minecraft.getInstance().resourceManager as IReloadableResourceManager
/** Append a string to the [ResourceLocation]'s path. */
operator fun ResourceLocation.plus(str: String) = ResourceLocation(namespace, path + str)
/** Index operator to get a resource. */
operator fun IResourceManager.get(domain: String, path: String): IResource? = get(ResourceLocation(domain, path))
/** Index operator to get a resource. */
operator fun IResourceManager.get(location: ResourceLocation): IResource? = tryDefault(null) { getResource(location) }
/** Get the lines of a text resource. */
fun IResource.getLines(): List<String> {
val result = arrayListOf<String>()
inputStream.bufferedReader().useLines { it.forEach { result.add(it) } }
return result
}

View File

@@ -0,0 +1,86 @@
package mods.betterfoliage.util
import mods.betterfoliage.render.lighting.HSB
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.texture.AtlasTexture
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.resources.IResource
import net.minecraft.resources.IResourceManager
import net.minecraft.util.ResourceLocation
import java.awt.image.BufferedImage
import java.io.ByteArrayOutputStream
import java.io.IOException
import javax.imageio.ImageIO
import kotlin.math.atan2
import kotlin.math.cos
import kotlin.math.sin
enum class Atlas(val basePath: String, val resourceId: ResourceLocation) {
BLOCKS("textures", AtlasTexture.LOCATION_BLOCKS_TEXTURE),
PARTICLES("textures", AtlasTexture.LOCATION_PARTICLES_TEXTURE);
/** Get the fully-qualified resource name for sprites belonging to this atlas*/
fun wrap(resource: ResourceLocation) = ResourceLocation(resource.namespace, "$basePath/${resource.path}.png")
/** Get the short resource name for sprites belonging to this atlas*/
fun unwrap(resource: ResourceLocation) = resource.stripStart("$basePath/").stripEnd(".png")
/** Reference to the atlas itself */
val atlas: AtlasTexture get() = Minecraft.getInstance().textureManager.getTexture(resourceId) as AtlasTexture
}
inline operator fun AtlasTexture.get(res: ResourceLocation): TextureAtlasSprite? = this.getSprite(res)
inline operator fun AtlasTexture.get(name: String): TextureAtlasSprite? = get(ResourceLocation(name))
fun IResourceManager.loadSprite(id: ResourceLocation) = this.get(id)?.loadImage() ?: throw IOException("Cannot load resource $id")
fun IResource.loadImage(): BufferedImage? = ImageIO.read(this.inputStream)
/** Index operator to get the RGB value of a pixel. */
operator fun BufferedImage.get(x: Int, y: Int) = this.getRGB(x, y)
/** Index operator to set the RGB value of a pixel. */
operator fun BufferedImage.set(x: Int, y: Int, value: Int) = this.setRGB(x, y, value)
val BufferedImage.bytes: ByteArray get() =
ByteArrayOutputStream().let { ImageIO.write(this, "PNG", it); it.toByteArray() }
/**
* Calculate the average color of a texture.
*
* Only non-transparent pixels are considered. Averages are taken in the HSB color space (note: Hue is a circular average),
* and the result transformed back to the RGB color space.
*/
val TextureAtlasSprite.averageColor: Int get() {
var numOpaque = 0
var sumHueX = 0.0
var sumHueY = 0.0
var sumSaturation = 0.0f
var sumBrightness = 0.0f
for (x in 0 until width)
for (y in 0 until height) {
val pixel = getPixelRGBA(0, x, y)
val alpha = (pixel shr 24) and 255
val hsb = HSB.fromColor(pixel)
if (alpha == 255) {
numOpaque++
sumHueX += cos((hsb.hue.toDouble() - 0.5) * PI2)
sumHueY += sin((hsb.hue.toDouble() - 0.5) * PI2)
sumSaturation += hsb.saturation
sumBrightness += hsb.brightness
}
}
// circular average - transform sum vector to polar angle
val avgHue = (atan2(sumHueY, sumHueX) / PI2 + 0.5).toFloat()
return HSB(avgHue, sumSaturation / numOpaque.toFloat(), sumBrightness / numOpaque.toFloat()).asColor
}
/** Weighted blend of 2 packed RGB colors */
fun blendRGB(rgb1: Int, rgb2: Int, weight1: Int, weight2: Int): Int {
val r = (((rgb1 shr 16) and 255) * weight1 + ((rgb2 shr 16) and 255) * weight2) / (weight1 + weight2)
val g = (((rgb1 shr 8) and 255) * weight1 + ((rgb2 shr 8) and 255) * weight2) / (weight1 + weight2)
val b = ((rgb1 and 255) * weight1 + (rgb2 and 255) * weight2) / (weight1 + weight2)
val a = (rgb1 shr 24) and 255
val result = ((a shl 24) or (r shl 16) or (g shl 8) or b)
return result
}