Add pet matcher texture pack support
Closes https://github.com/nea89o/Firmament/issues/29
This commit is contained in:
@@ -114,6 +114,30 @@ Sub object match:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Pet Data
|
||||||
|
|
||||||
|
Filter by pet information. While you can already filter by the skyblock id for pet type and tier, this allows you to
|
||||||
|
further filter by level and some other pet info.
|
||||||
|
|
||||||
|
```json5
|
||||||
|
"firmament:pet" {
|
||||||
|
"id": "WOLF",
|
||||||
|
"exp": ">=25353230",
|
||||||
|
"tier": "[RARE,LEGENDARY]",
|
||||||
|
"level": "[50,)",
|
||||||
|
"candyUsed": 0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Name | Type | Description |
|
||||||
|
|-------------|------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| `id` | [String](#string-matcher) | The id of the pet |
|
||||||
|
| `exp` | [Number](#number-matcher) | The total experience of the pet |
|
||||||
|
| `tier` | Rarity (like [Number](#number-matcher), but with rarity names instead) | The total experience of the pet |
|
||||||
|
| `level` | [Number](#number-matcher) | The current level of the pet |
|
||||||
|
| `candyUsed` | [Number](#number-matcher) | The number of pet candies used on the pet. This is present even if they are not shown in game (such as on a level 100 legendary pet) |
|
||||||
|
|
||||||
|
Every part of this matcher is optional.
|
||||||
|
|
||||||
|
|
||||||
#### Logic Operators
|
#### Logic Operators
|
||||||
@@ -181,6 +205,58 @@ specify one of these other matchers and one color preserving property.
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Number Matchers
|
||||||
|
|
||||||
|
This matches a number against either a range or a specific number.
|
||||||
|
|
||||||
|
#### Direct number
|
||||||
|
|
||||||
|
You can directly specify a number using that value directly:
|
||||||
|
```json5
|
||||||
|
"firmament:pet": {
|
||||||
|
"level": 100
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This is best for whole numbers, since decimal numbers can be really close together but still be different.
|
||||||
|
|
||||||
|
#### Intervals
|
||||||
|
|
||||||
|
For ranges you can instead use an interval. This uses the standard mathematical notation for those as a string:
|
||||||
|
|
||||||
|
|
||||||
|
```json5
|
||||||
|
"firmament:pet": {
|
||||||
|
"level": "(50,100]"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This is in the format of `(min,max)` or `[min,max]`. Either min or max can be omitted, which results in that boundary
|
||||||
|
being ignored (so `[50,)` would be 50 until infinity). You can also vary the parenthesis on either side independently.
|
||||||
|
|
||||||
|
Specifying round parenthesis `()` means the number is exclusive, so not including this number. For example `(50,100)`
|
||||||
|
would not match just the number `50` or `100`, but would match `51`.
|
||||||
|
|
||||||
|
Specifying square brackets `[]` means the number is inclusive. For example `[50,100]` would match both `50` and `100`.
|
||||||
|
|
||||||
|
You can mix and match parenthesis and brackets, they only ever affect the number next to it.
|
||||||
|
|
||||||
|
For more information in intervals check out [Wikipedia](https://en.wikipedia.org/wiki/Interval_(mathematics)).
|
||||||
|
|
||||||
|
#### Operators
|
||||||
|
|
||||||
|
If instead of specifying a range you just need to specify one boundary you can also use the standard operators to
|
||||||
|
compare your number:
|
||||||
|
|
||||||
|
```json5
|
||||||
|
"firmament:pet": {
|
||||||
|
"level": "<50"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This example would match if the level is less than fifty. The available operators are `<`, `>`, `<=` and `>=`. The
|
||||||
|
operator needs to be specified on the left. The versions of the operator with `=` also allow the number to be equal.
|
||||||
|
|
||||||
### Nbt Matcher
|
### Nbt Matcher
|
||||||
|
|
||||||
This matches a single nbt element.
|
This matches a single nbt element.
|
||||||
@@ -223,6 +299,11 @@ You can override that like so:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> This syntax for numbers is *just* for **NBT values**. This is also why specifying the type of the number is necessary.
|
||||||
|
> For other number matchers, use [the number matchers](#number-matchers)
|
||||||
|
|
||||||
## Armor textures
|
## Armor textures
|
||||||
|
|
||||||
You can re-*texture* armors, but not re-*model* them with firmament.
|
You can re-*texture* armors, but not re-*model* them with firmament.
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ object CustomModelOverrideParser {
|
|||||||
registerPredicateParser("not", NotPredicate.Parser)
|
registerPredicateParser("not", NotPredicate.Parser)
|
||||||
registerPredicateParser("item", ItemPredicate.Parser)
|
registerPredicateParser("item", ItemPredicate.Parser)
|
||||||
registerPredicateParser("extra_attributes", ExtraAttributesPredicate.Parser)
|
registerPredicateParser("extra_attributes", ExtraAttributesPredicate.Parser)
|
||||||
|
registerPredicateParser("pet", PetPredicate.Parser)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val neverPredicate = listOf(
|
private val neverPredicate = listOf(
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user