Add pet matcher texture pack support

Closes https://github.com/nea89o/Firmament/issues/29
This commit is contained in:
Linnea Gräf
2024-07-06 01:06:53 +02:00
parent dd2c455b19
commit c3d32559e4
5 changed files with 357 additions and 0 deletions

View File

@@ -45,6 +45,7 @@ object CustomModelOverrideParser {
registerPredicateParser("not", NotPredicate.Parser)
registerPredicateParser("item", ItemPredicate.Parser)
registerPredicateParser("extra_attributes", ExtraAttributesPredicate.Parser)
registerPredicateParser("pet", PetPredicate.Parser)
}
private val neverPredicate = listOf(

View File

@@ -0,0 +1,130 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package moe.nea.firmament.features.texturepack
import com.google.gson.JsonElement
import com.google.gson.JsonPrimitive
import moe.nea.firmament.util.useMatch
abstract class NumberMatcher {
abstract fun test(number: Number): Boolean
companion object {
fun parse(jsonElement: JsonElement): NumberMatcher? {
if (jsonElement is JsonPrimitive) {
if (jsonElement.isString) {
val string = jsonElement.asString
return parseRange(string) ?: parseOperator(string)
}
if (jsonElement.isNumber) {
val number = jsonElement.asNumber
val hasDecimals = (number.toString().contains("."))
return MatchNumberExact(if (hasDecimals) number.toLong() else number.toDouble())
}
}
return null
}
private val intervalSpec =
"(?<beginningOpen>[\\[\\(])(?<beginning>[0-9.]+)?,(?<ending>[0-9.]+)?(?<endingOpen>[\\]\\)])"
.toPattern()
fun parseRange(string: String): RangeMatcher? {
intervalSpec.useMatch<Nothing>(string) {
// Open in the set-theory sense, meaning does not include its end.
val beginningOpen = group("beginningOpen") == "("
val endingOpen = group("endingOpen") == ")"
val beginning = group("beginning")?.toDouble()
val ending = group("ending")?.toDouble()
return RangeMatcher(beginning, !beginningOpen, ending, !endingOpen)
}
return null
}
enum class Operator(val operator: String) {
LESS("<") {
override fun matches(comparisonResult: Int): Boolean {
return comparisonResult < 0
}
},
LESS_EQUALS("<=") {
override fun matches(comparisonResult: Int): Boolean {
return comparisonResult <= 0
}
},
GREATER(">") {
override fun matches(comparisonResult: Int): Boolean {
return comparisonResult > 0
}
},
GREATER_EQUALS(">=") {
override fun matches(comparisonResult: Int): Boolean {
return comparisonResult >= 0
}
},
;
abstract fun matches(comparisonResult: Int): Boolean
}
private val operatorPattern = "(?<operator>${Operator.entries.joinToString("|") {it.operator}})(?<value>[0-9.]+)".toPattern()
fun parseOperator(string: String): OperatorMatcher? {
operatorPattern.useMatch<Nothing>(string) {
val operatorName = group("operator")
val operator = Operator.entries.find { it.operator == operatorName }!!
val value = group("value").toDouble()
return OperatorMatcher(operator, value)
}
return null
}
data class OperatorMatcher(val operator: Operator, val value: Double) : NumberMatcher() {
override fun test(number: Number): Boolean {
return operator.matches(number.toDouble().compareTo(value))
}
}
data class MatchNumberExact(val number: Number) : NumberMatcher() {
override fun test(number: Number): Boolean {
return when (this.number) {
is Double -> number.toDouble() == this.number.toDouble()
else -> number.toLong() == this.number.toLong()
}
}
}
data class RangeMatcher(
val beginning: Double?,
val beginningInclusive: Boolean,
val ending: Double?,
val endingInclusive: Boolean,
) : NumberMatcher() {
override fun test(number: Number): Boolean {
val value = number.toDouble()
if (beginning != null) {
if (beginningInclusive) {
if (value < beginning) return false
} else {
if (value <= beginning) return false
}
}
if (ending != null) {
if (endingInclusive) {
if (value > ending) return false
} else {
if (value >= ending) return false
}
}
return true
}
}
}
}

View File

@@ -0,0 +1,71 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package moe.nea.firmament.features.texturepack
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import net.minecraft.item.ItemStack
import moe.nea.firmament.repo.ExpLadders
import moe.nea.firmament.util.petData
class PetPredicate(
val petId: StringMatcher?,
val tier: RarityMatcher?,
val exp: NumberMatcher?,
val candyUsed: NumberMatcher?,
val level: NumberMatcher?,
) : FirmamentModelPredicate {
override fun test(stack: ItemStack): Boolean {
val petData = stack.petData ?: return false
if (petId != null) {
if (!petId.matches(petData.type)) return false
}
if (exp != null) {
if (!exp.test(petData.exp)) return false
}
if (candyUsed != null) {
if (!candyUsed.test(petData.candyUsed)) return false
}
if (tier != null) {
if (!tier.match(petData.tier)) return false
}
val levelData by lazy(LazyThreadSafetyMode.NONE) {
ExpLadders.getExpLadder(petData.type, petData.tier)
.getPetLevel(petData.exp)
}
if (level != null) {
if (!level.test(levelData.currentLevel)) return false
}
return true
}
object Parser : FirmamentModelPredicateParser {
override fun parse(jsonElement: JsonElement): FirmamentModelPredicate? {
if (jsonElement.isJsonPrimitive) {
return PetPredicate(StringMatcher.Equals(jsonElement.asString, false), null, null, null, null)
}
if (jsonElement !is JsonObject) return null
val idMatcher = jsonElement["id"]?.let(StringMatcher::parse)
val expMatcher = jsonElement["exp"]?.let(NumberMatcher::parse)
val levelMatcher = jsonElement["level"]?.let(NumberMatcher::parse)
val candyMatcher = jsonElement["candyUsed"]?.let(NumberMatcher::parse)
val tierMatcher = jsonElement["tier"]?.let(RarityMatcher::parse)
return PetPredicate(
idMatcher,
tierMatcher,
expMatcher,
candyMatcher,
levelMatcher,
)
}
}
override fun toString(): String {
return super.toString()
}
}

View File

@@ -0,0 +1,74 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package moe.nea.firmament.features.texturepack
import com.google.gson.JsonElement
import io.github.moulberry.repo.data.Rarity
import moe.nea.firmament.util.useMatch
abstract class RarityMatcher {
abstract fun match(rarity: Rarity): Boolean
companion object {
fun parse(jsonElement: JsonElement): RarityMatcher {
val string = jsonElement.asString
val range = parseRange(string)
if (range != null) return range
return Exact(Rarity.valueOf(string))
}
private val allRarities = Rarity.entries.joinToString("|", "(?:", ")")
private val intervalSpec =
"(?<beginningOpen>[\\[\\(])(?<beginning>$allRarities)?,(?<ending>$allRarities)?(?<endingOpen>[\\]\\)])"
.toPattern()
fun parseRange(string: String): RangeMatcher? {
intervalSpec.useMatch<Nothing>(string) {
// Open in the set-theory sense, meaning does not include its end.
val beginningOpen = group("beginningOpen") == "("
val endingOpen = group("endingOpen") == ")"
val beginning = group("beginning")?.let(Rarity::valueOf)
val ending = group("ending")?.let(Rarity::valueOf)
return RangeMatcher(beginning, !beginningOpen, ending, !endingOpen)
}
return null
}
}
data class Exact(val expected: Rarity) : RarityMatcher() {
override fun match(rarity: Rarity): Boolean {
return rarity == expected
}
}
data class RangeMatcher(
val beginning: Rarity?,
val beginningInclusive: Boolean,
val ending: Rarity?,
val endingInclusive: Boolean,
) : RarityMatcher() {
override fun match(rarity: Rarity): Boolean {
if (beginning != null) {
if (beginningInclusive) {
if (rarity < beginning) return false
} else {
if (rarity <= beginning) return false
}
}
if (ending != null) {
if (endingInclusive) {
if (rarity > ending) return false
} else {
if (rarity >= ending) return false
}
}
return true
}
}
}