aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinnea Gräf <nea@nea.moe>2024-07-06 01:06:53 +0200
committerLinnea Gräf <nea@nea.moe>2024-07-06 01:06:53 +0200
commitc3d32559e4132f163509143777ce8c6477a92c70 (patch)
tree1cc5f49a1b11a14ffdbf9f2e56668748677143b2
parentdd2c455b19519c93e254f3f567d8779e6ccbb4e5 (diff)
downloadFirmament-c3d32559e4132f163509143777ce8c6477a92c70.tar.gz
Firmament-c3d32559e4132f163509143777ce8c6477a92c70.tar.bz2
Firmament-c3d32559e4132f163509143777ce8c6477a92c70.zip
Add pet matcher texture pack support
Closes https://github.com/nea89o/Firmament/issues/29
-rw-r--r--docs/Texture Pack Format.md81
-rw-r--r--src/main/kotlin/moe/nea/firmament/features/texturepack/CustomModelOverrideParser.kt1
-rw-r--r--src/main/kotlin/moe/nea/firmament/features/texturepack/NumberMatcher.kt130
-rw-r--r--src/main/kotlin/moe/nea/firmament/features/texturepack/PetPredicate.kt71
-rw-r--r--src/main/kotlin/moe/nea/firmament/features/texturepack/RarityMatcher.kt74
5 files changed, 357 insertions, 0 deletions
diff --git a/docs/Texture Pack Format.md b/docs/Texture Pack Format.md
index 54a5d5e..ea54c9d 100644
--- a/docs/Texture Pack Format.md
+++ b/docs/Texture Pack Format.md
@@ -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
@@ -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
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
You can re-*texture* armors, but not re-*model* them with firmament.
diff --git a/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomModelOverrideParser.kt b/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomModelOverrideParser.kt
index 4ae73e8..5c16150 100644
--- a/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomModelOverrideParser.kt
+++ b/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomModelOverrideParser.kt
@@ -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(
diff --git a/src/main/kotlin/moe/nea/firmament/features/texturepack/NumberMatcher.kt b/src/main/kotlin/moe/nea/firmament/features/texturepack/NumberMatcher.kt
new file mode 100644
index 0000000..b39330e
--- /dev/null
+++ b/src/main/kotlin/moe/nea/firmament/features/texturepack/NumberMatcher.kt
@@ -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
+ }
+ }
+ }
+
+}
diff --git a/src/main/kotlin/moe/nea/firmament/features/texturepack/PetPredicate.kt b/src/main/kotlin/moe/nea/firmament/features/texturepack/PetPredicate.kt
new file mode 100644
index 0000000..b252832
--- /dev/null
+++ b/src/main/kotlin/moe/nea/firmament/features/texturepack/PetPredicate.kt
@@ -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()
+ }
+}
diff --git a/src/main/kotlin/moe/nea/firmament/features/texturepack/RarityMatcher.kt b/src/main/kotlin/moe/nea/firmament/features/texturepack/RarityMatcher.kt
new file mode 100644
index 0000000..1a0daac
--- /dev/null
+++ b/src/main/kotlin/moe/nea/firmament/features/texturepack/RarityMatcher.kt
@@ -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
+ }
+ }
+
+}