forked from mirrors/cobblemon
Added more molang expressions to a bunch of composed pool properties, fixed some bugs
This commit is contained in:
@@ -9,14 +9,18 @@
|
||||
package com.cobblemon.mod.common
|
||||
|
||||
import com.cobblemon.mod.common.api.data.JsonDataRegistry
|
||||
import com.cobblemon.mod.common.api.molang.ExpressionLike
|
||||
import com.cobblemon.mod.common.api.moves.MovesetBuilder
|
||||
import com.cobblemon.mod.common.api.npc.PartyPool
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
|
||||
import com.cobblemon.mod.common.api.reactive.SimpleObservable
|
||||
import com.cobblemon.mod.common.util.ScriptableIntRange
|
||||
import com.cobblemon.mod.common.util.adapters.ExpressionLikeAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.IntRangeAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.MovesetBuilderReferenceAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.PokemonPropertiesAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.ScriptableIntRangeAdapter
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
@@ -35,7 +39,9 @@ object CobblemonPartyPools : JsonDataRegistry<PartyPool> {
|
||||
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
|
||||
.registerTypeAdapter(PokemonProperties::class.java, PokemonPropertiesAdapter(saveLong = false))
|
||||
.registerTypeAdapter(IntRange::class.java, IntRangeAdapter)
|
||||
.registerTypeAdapter(ScriptableIntRange::class.java, ScriptableIntRangeAdapter)
|
||||
.registerTypeAdapter(MovesetBuilder::class.java, MovesetBuilderReferenceAdapter)
|
||||
.registerTypeAdapter(ExpressionLike::class.java, ExpressionLikeAdapter)
|
||||
.setPrettyPrinting()
|
||||
.create()
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import com.cobblemon.mod.common.api.npc.partyproviders.SimplePartyProvider
|
||||
import com.cobblemon.mod.common.api.storage.party.NPCPartyStore
|
||||
import com.cobblemon.mod.common.entity.npc.NPCEntity
|
||||
import com.google.gson.JsonElement
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
|
||||
/**
|
||||
@@ -26,15 +27,15 @@ import net.minecraft.server.level.ServerPlayer
|
||||
*/
|
||||
interface NPCPartyProvider {
|
||||
companion object {
|
||||
val types = mutableMapOf<String, (String) -> NPCPartyProvider>(
|
||||
SimplePartyProvider.TYPE to { SimplePartyProvider() },
|
||||
PoolPartyProvider.TYPE to { PoolPartyProvider() },
|
||||
ScriptPartyProvider.TYPE to { ScriptPartyProvider() },
|
||||
ComposedPoolPartyProvider.TYPE to { ComposedPoolPartyProvider() }
|
||||
val types = mutableMapOf(
|
||||
SimplePartyProvider.TYPE to ::SimplePartyProvider,
|
||||
PoolPartyProvider.TYPE to ::PoolPartyProvider,
|
||||
ScriptPartyProvider.TYPE to ::ScriptPartyProvider,
|
||||
ComposedPoolPartyProvider.TYPE to ::ComposedPoolPartyProvider
|
||||
)
|
||||
}
|
||||
|
||||
val type: String
|
||||
val type: ResourceLocation
|
||||
val isStatic: Boolean
|
||||
fun provide(npc: NPCEntity, level: Int, players: List<ServerPlayer> = emptyList()): NPCPartyStore
|
||||
// Why did I opt for manual JSON loading??? I must have had a reason but I can't remember. Maybe for S2C? Use a codec doofus
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
package com.cobblemon.mod.common.api.npc
|
||||
|
||||
import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import kotlin.random.Random
|
||||
@@ -29,12 +30,12 @@ class PartyComposition {
|
||||
val slot5 = listOf<String>()
|
||||
val slot6 = listOf<String>()
|
||||
|
||||
fun compose(pool: PartyPool, level: Int, aspects: Set<String>, desiredPokemonCount: Int, random: Random = Random.Default): List<Pokemon> {
|
||||
fun compose(pool: PartyPool, level: Int, aspects: Set<String>, desiredPokemonCount: Int, runtime: MoLangRuntime, random: Random = Random.Default): List<Pokemon> {
|
||||
val chosenEntries = mutableListOf<PartyPool.PoolEntry>()
|
||||
val availableEntries = pool.entries.filter { level in it.npcLevels && (it.npcAspects.isEmpty() || it.npcAspects.all(aspects::contains)) }
|
||||
val availableEntries = pool.entries.filter { level in it.npcLevels.resolve(runtime) && (it.npcAspects.isEmpty() || it.npcAspects.all(aspects::contains)) }
|
||||
|
||||
for (labelSet in listOf(slot1, slot2, slot3, slot4, slot5, slot6)) {
|
||||
pool.tryChoosingEntry(labelSet, random, availableEntries, chosenEntries)
|
||||
pool.tryChoosingEntry(labelSet, runtime, random, availableEntries, chosenEntries)
|
||||
if (chosenEntries.size >= desiredPokemonCount) {
|
||||
break
|
||||
}
|
||||
@@ -47,10 +48,11 @@ class PartyComposition {
|
||||
}
|
||||
|
||||
val pokemon = finalEntries.map { entry ->
|
||||
val lvlVariation = if (entry.levelVariation.first == entry.levelVariation.last) {
|
||||
entry.levelVariation.first
|
||||
val levelVariation = entry.levelVariation.resolve(runtime)
|
||||
val lvlVariation = if (levelVariation.first == levelVariation.last) {
|
||||
levelVariation.first
|
||||
} else {
|
||||
entry.levelVariation.random(random)
|
||||
levelVariation.random(random)
|
||||
}
|
||||
|
||||
val finalLevel = level + lvlVariation
|
||||
|
||||
@@ -8,9 +8,14 @@
|
||||
|
||||
package com.cobblemon.mod.common.api.npc
|
||||
|
||||
import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
import com.cobblemon.mod.common.api.molang.ExpressionLike
|
||||
import com.cobblemon.mod.common.api.moves.MovesetBuilder
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
|
||||
import com.cobblemon.mod.common.util.ScriptableIntRange
|
||||
import com.cobblemon.mod.common.util.asExpressionLike
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.toProperties
|
||||
import com.cobblemon.mod.common.util.weightedSelection
|
||||
import kotlin.random.Random
|
||||
|
||||
@@ -26,18 +31,18 @@ class PartyPool {
|
||||
val displayName = "cobblemon.party_pool.$id"
|
||||
val entries = mutableListOf<PoolEntry>()
|
||||
|
||||
class PoolEntry(
|
||||
val pokemon: PokemonProperties,
|
||||
val labels: List<String> = emptyList(),
|
||||
val npcAspects: List<String> = emptyList(),
|
||||
val required: List<String> = emptyList(),
|
||||
val excluded: List<String> = emptyList(),
|
||||
val maxSelectableTimes: Int = 6,
|
||||
val levelVariation: IntRange = 0..0,
|
||||
val npcLevels: IntRange = 1..100,
|
||||
val movesetBuilders: List<MovesetBuilder> = emptyList(),
|
||||
val weight: Int = 50
|
||||
)
|
||||
class PoolEntry {
|
||||
val pokemon: PokemonProperties = "pikachu".toProperties()
|
||||
val labels: List<String> = emptyList()
|
||||
val npcAspects: List<String> = emptyList()
|
||||
val required: List<String> = emptyList()
|
||||
val excluded: List<String> = emptyList()
|
||||
val maxSelectableTimes: ExpressionLike = "6".asExpressionLike()
|
||||
val levelVariation: ScriptableIntRange = ScriptableIntRange(0, 0)
|
||||
val npcLevels: ScriptableIntRange = ScriptableIntRange(1, 100)
|
||||
val movesetBuilders: List<MovesetBuilder> = emptyList()
|
||||
val weight: ExpressionLike = "50".asExpressionLike()
|
||||
}
|
||||
|
||||
/**
|
||||
* Goes through each of the labels in order, trying to find a valid choice. Once it finds one, it skips the later
|
||||
@@ -45,6 +50,7 @@ class PartyPool {
|
||||
*/
|
||||
fun tryChoosingEntry(
|
||||
labels: List<String>,
|
||||
runtime: MoLangRuntime,
|
||||
random: Random,
|
||||
availableEntries: List<PoolEntry>,
|
||||
chosenEntries: MutableList<PoolEntry>,
|
||||
@@ -58,11 +64,11 @@ class PartyPool {
|
||||
// Check excluded labels
|
||||
entry.excluded.none { it in labels } &&
|
||||
// Check max selectable times
|
||||
chosenEntries.count { it == entry } < entry.maxSelectableTimes
|
||||
chosenEntries.count { it == entry } < entry.maxSelectableTimes.resolveFloat(runtime)
|
||||
}
|
||||
|
||||
if (entries.isNotEmpty()) {
|
||||
val selectedEntry = entries.weightedSelection(random) { it.weight } ?: return
|
||||
val selectedEntry = entries.weightedSelection(random) { it.weight.resolveFloat(runtime) } ?: return
|
||||
chosenEntries.add(selectedEntry)
|
||||
return
|
||||
}
|
||||
|
||||
+4
-2
@@ -23,6 +23,7 @@ import com.cobblemon.mod.common.entity.npc.NPCEntity
|
||||
import com.cobblemon.mod.common.util.asArrayValue
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.resolveInt
|
||||
import com.cobblemon.mod.common.util.withQueryValue
|
||||
import com.google.gson.JsonElement
|
||||
@@ -41,10 +42,10 @@ import net.minecraft.server.level.ServerPlayer
|
||||
*/
|
||||
class ComposedPoolPartyProvider : NPCPartyProvider {
|
||||
companion object {
|
||||
const val TYPE = "composed_pool"
|
||||
val TYPE = cobblemonResource("composed_pool")
|
||||
}
|
||||
|
||||
override val type: String = TYPE
|
||||
override val type = TYPE
|
||||
override var isStatic: Boolean = true
|
||||
var useFixedRandom: Boolean = false
|
||||
var minPokemon: Expression = "1".asExpression()
|
||||
@@ -82,6 +83,7 @@ class ComposedPoolPartyProvider : NPCPartyProvider {
|
||||
level = level,
|
||||
aspects = npc.aspects,
|
||||
desiredPokemonCount = desiredPokemonCount,
|
||||
runtime = runtime,
|
||||
random = random
|
||||
)
|
||||
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ import kotlin.random.Random
|
||||
*/
|
||||
class PoolPartyProvider : NPCPartyProvider {
|
||||
companion object {
|
||||
const val TYPE = "pool"
|
||||
val TYPE = cobblemonResource("pool")
|
||||
}
|
||||
|
||||
@Transient
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ import net.minecraft.server.level.ServerPlayer
|
||||
|
||||
class ScriptPartyProvider : NPCPartyProvider {
|
||||
companion object {
|
||||
const val TYPE = "script"
|
||||
val TYPE = cobblemonResource("script")
|
||||
}
|
||||
|
||||
override val type = TYPE
|
||||
|
||||
+2
-1
@@ -16,6 +16,7 @@ import com.cobblemon.mod.common.api.pokemon.PokemonProperties
|
||||
import com.cobblemon.mod.common.api.storage.party.NPCPartyStore
|
||||
import com.cobblemon.mod.common.entity.npc.NPCEntity
|
||||
import com.cobblemon.mod.common.util.asArrayValue
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.getBooleanOrNull
|
||||
import com.cobblemon.mod.common.util.toProperties
|
||||
import com.google.gson.JsonElement
|
||||
@@ -29,7 +30,7 @@ import net.minecraft.server.level.ServerPlayer
|
||||
*/
|
||||
class SimplePartyProvider : NPCPartyProvider {
|
||||
companion object {
|
||||
const val TYPE = "simple"
|
||||
val TYPE = cobblemonResource("simple")
|
||||
}
|
||||
|
||||
@Transient
|
||||
|
||||
@@ -82,14 +82,14 @@ object CobblemonDataProvider : DataProvider {
|
||||
this.register(ActionEffects, reloadable = true)
|
||||
this.register(Moves, reloadable = false)
|
||||
this.register(CobblemonMovesetBuilders, reloadable = false)
|
||||
this.register(CobblemonPartyPools, reloadable = false)
|
||||
this.register(CobblemonPartyCompositions, reloadable = false)
|
||||
this.register(Abilities, reloadable = false)
|
||||
this.register(CobblemonBehaviours, reloadable = false)
|
||||
this.register(PokemonSpecies, reloadable = false)
|
||||
this.register(SpeciesAdditions, reloadable = false)
|
||||
this.register(PokeBalls, reloadable = false)
|
||||
this.register(PropertiesCompletionProvider, reloadable = false)
|
||||
this.register(CobblemonPartyPools, reloadable = false)
|
||||
this.register(CobblemonPartyCompositions, reloadable = false)
|
||||
this.register(SpawnDetailPresets, reloadable = true)
|
||||
this.register(CobblemonSpawnRules, reloadable = true)
|
||||
this.register(CobblemonMechanics, reloadable = true)
|
||||
|
||||
+1
-1
@@ -191,7 +191,7 @@ class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragMod
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setNoGravity(false)
|
||||
isNoGravity = false
|
||||
deltaMovement = Vec3.ZERO
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.util
|
||||
|
||||
import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.api.molang.ExpressionLike
|
||||
|
||||
/**
|
||||
* An IntRange where the minimum and maximum values are defined by MoLang expressions. When deserializing from
|
||||
* JSON the [com.cobblemon.mod.common.util.adapters.ScriptableIntRangeAdapter] will figure out if it's a single
|
||||
* value in which case it's not using any scripting stuff. Otherwise, if it's an object with "min" and "max" properties
|
||||
* it will respect them as expressions.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 15th, 2025
|
||||
*/
|
||||
class ScriptableIntRange(
|
||||
val min: ExpressionLike = "0".asExpressionLike(),
|
||||
val max: ExpressionLike = "0".asExpressionLike()
|
||||
) {
|
||||
constructor(min: Int, max: Int): this(
|
||||
min = min.toString().asExpressionLike(),
|
||||
max = max.toString().asExpressionLike()
|
||||
)
|
||||
|
||||
fun resolve(runtime: MoLangRuntime): IntRange {
|
||||
val minimum = min.resolveInt(runtime)
|
||||
val maximum = max.resolveInt(runtime)
|
||||
if (minimum > maximum) {
|
||||
Cobblemon.LOGGER.error("ScriptableIntRange(${min.getString()}, ${max.getString()}) resolved a minimum ($minimum) greater than maximum ($maximum). Returning empty range.")
|
||||
return IntRange.EMPTY
|
||||
}
|
||||
return minimum..maximum
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -9,6 +9,7 @@
|
||||
package com.cobblemon.mod.common.util.adapters
|
||||
|
||||
import com.cobblemon.mod.common.api.npc.NPCPartyProvider
|
||||
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
|
||||
import com.google.gson.JsonDeserializationContext
|
||||
import com.google.gson.JsonDeserializer
|
||||
import com.google.gson.JsonElement
|
||||
@@ -16,8 +17,8 @@ import java.lang.reflect.Type
|
||||
|
||||
object NPCPartyProviderAdapter : JsonDeserializer<NPCPartyProvider> {
|
||||
override fun deserialize(json: JsonElement, type: Type, ctx: JsonDeserializationContext): NPCPartyProvider {
|
||||
val typeName = if (json.isJsonPrimitive) json.asString else json.asJsonObject.get("type").asString
|
||||
return NPCPartyProvider.types[typeName]?.invoke(typeName)?.also { it.loadFromJSON(json) }
|
||||
val typeName = (if (json.isJsonPrimitive) json.asString else json.asJsonObject.get("type").asString).asIdentifierDefaultingNamespace()
|
||||
return NPCPartyProvider.types[typeName]?.invoke()?.also { it.loadFromJSON(json) }
|
||||
?: throw IllegalStateException("Unable to find party provider by type: $typeName")
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.util.adapters
|
||||
|
||||
import com.cobblemon.mod.common.util.ScriptableIntRange
|
||||
import com.cobblemon.mod.common.util.asExpressionLike
|
||||
import com.google.gson.JsonDeserializationContext
|
||||
import com.google.gson.JsonDeserializer
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonPrimitive
|
||||
import java.lang.reflect.Type
|
||||
|
||||
/**
|
||||
* Parses a [ScriptableIntRange] by two paths. One is if it's a primitive in which
|
||||
* case they're just doing a normal [IntRange] and it will parse it using the same
|
||||
* method as IntRange normally gets and then returns a ScriptableIntRange of literals.
|
||||
*
|
||||
* The other path is if they provide an object with "min" and "max" in which case
|
||||
* each property is deserialized as an [ExpressionLike].
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 15th, 2025
|
||||
*/
|
||||
object ScriptableIntRangeAdapter : JsonDeserializer<ScriptableIntRange> {
|
||||
override fun deserialize(
|
||||
json: JsonElement,
|
||||
typeOfT: Type,
|
||||
context: JsonDeserializationContext
|
||||
): ScriptableIntRange {
|
||||
if (json is JsonPrimitive) {
|
||||
IntRangeAdapter.deserialize(json, typeOfT, context).let {
|
||||
return ScriptableIntRange(
|
||||
it.first.toString().asExpressionLike(),
|
||||
it.last.toString().asExpressionLike()
|
||||
)
|
||||
}
|
||||
} else {
|
||||
val obj = json.asJsonObject
|
||||
val min = obj.get("min").asString
|
||||
val max = obj.get("max").asString
|
||||
return ScriptableIntRange(
|
||||
min.toString().asExpressionLike(),
|
||||
max.toString().asExpressionLike()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,75 +48,94 @@ For example, take `alpha.json`, used for alpha Pokémon:
|
||||
This moveset builder will try to fill slot 1 with the last suitable offensive move the Pokémon can learn,
|
||||
falling back to the last offensive move, and then the last level-up move if all else fails.
|
||||
|
||||
### Molang Expressions in Composed Parties
|
||||
For the Molang expression properties in the following sections, the following Molang queries will be available:
|
||||
|
||||
`q.players` - A list of players that are challenging the NPC.
|
||||
|
||||
`q.player` - The player challenging the NPC, specifically if being challenged only by one player. If there are multiple players, this will be 0.
|
||||
|
||||
`q.npc` - The NPC entity.
|
||||
|
||||
`q.level` - The seed level of the NPC.
|
||||
|
||||
|
||||
### Party Pools
|
||||
The `party_pools` datapack folder has all the pools of Pokémon that can be used to draw from when composing a party.
|
||||
Each entry of the pool has some number of labels used by the party composition.
|
||||
|
||||
| Property | Description |
|
||||
|----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `pokemon` | The guaranteed Pokémon information, such as `geodude shiny=true` |
|
||||
| `labels` | The identifying labels of the Pokémon. These can be anything you want. Definitely shouldn't be empty though. |
|
||||
| `npcLevels` | The seed levels that the NPC can be for which this entry is possible. 1-30 would mean a level 40 NPC cannot use that entry. |
|
||||
| `npcAspects` | The aspects that the NPC must have for this entry to be possible. If the list is empty, as is the default, then it won't check the NPC aspects. |
|
||||
| `weight` | The relative weight of this entry compared to others in the pool. Defaults to 50. |
|
||||
| `levelVariation` | The possible variation around the NPC's seed level that is possible. `-2-3` would mean the Pokémon's level could be anywhere from 2 levels below the NPC seed level to 3 levels above. |
|
||||
| `maxTimesSelectable` | The maximum number of times this entry can be selected for a party. Defaults to 6. |
|
||||
| `movesetBuilders` | A list of moveset builders from the `moveset_builders` folder that can be used to create this Pokémon's moveset. One will be selected at random if there are multiple. |
|
||||
| `requires` | A list of labels that must be present in the party for this entry to be selected. These are from the `labels` of entries that were already selected. |
|
||||
| `incompatible` | A list of labels that cannot be present in the party for this entry to be selected. These are from the `labels` of entries that were already selected. |
|
||||
| Property | Description |
|
||||
|----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `pokemon` | The guaranteed Pokémon information, such as `geodude shiny=true` |
|
||||
| `labels` | The identifying labels of the Pokémon. These can be anything you want. Definitely shouldn't be empty though. |
|
||||
| `npcLevels` | The seed levels that the NPC can be for which this entry is possible. 1-30 would mean a level 40 NPC cannot use that entry. You can also provide an object with `min` and `max` properties to use MoLang expressions. |
|
||||
| `npcAspects` | The aspects that the NPC must have for this entry to be possible. If the list is empty, as is the default, then it won't check the NPC aspects. |
|
||||
| `weight` | The relative weight of this entry compared to others in the pool. Defaults to 50. You can also use a Molang expression here. |
|
||||
| `levelVariation` | The possible variation around the NPC's seed level that is possible. `-2-3` would mean the Pokémon's level could be anywhere from 2 levels below the NPC seed level to 3 levels above. You can also provide an object with `min` and `max` properties to use MoLang expressions. |
|
||||
| `maxTimesSelectable` | The maximum number of times this entry can be selected for a party. Defaults to 6. You can also use a Molang expression here. |
|
||||
| `movesetBuilders` | A list of moveset builders from the `moveset_builders` folder that can be used to create this Pokémon's moveset. One will be selected at random if there are multiple. |
|
||||
| `requires` | A list of labels that must be present in the party for this entry to be selected. These are from the `labels` of entries that were already selected. |
|
||||
| `incompatible` | A list of labels that cannot be present in the party for this entry to be selected. These are from the `labels` of entries that were already selected. |
|
||||
|
||||
As an example, a trivially small pool might look like:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"pokemon": "geodude",
|
||||
"labels": [
|
||||
"basic",
|
||||
"rocky"
|
||||
],
|
||||
"npcLevels": "1-30",
|
||||
"weight": 20,
|
||||
"levelVariation": "-1-3",
|
||||
"maxTimesSelectable": 4,
|
||||
"movesetBuilders": ["cobblemon:npc_pokemon"],
|
||||
"requires": [
|
||||
"golem-ace"
|
||||
],
|
||||
"incompatible": [
|
||||
"steelix-ace"
|
||||
]
|
||||
},
|
||||
{
|
||||
"pokemon": "golem",
|
||||
"labels": [
|
||||
"ace",
|
||||
"strong"
|
||||
],
|
||||
"npcLevels": "1-30",
|
||||
"weight": 20,
|
||||
"levelVariation": "1-5",
|
||||
"maxTimesSelectable": 1,
|
||||
"movesetBuilders": ["cobblemon:offensive_npc_pokemon", "cobblemon:npc_pokemon"],
|
||||
"incompatible": [
|
||||
"steelix-ace",
|
||||
"ace"
|
||||
]
|
||||
},
|
||||
{
|
||||
"pokemon": "steelix shiny=true",
|
||||
"labels": [
|
||||
"ace",
|
||||
"strong"
|
||||
],
|
||||
"npcLevels": "15-30",
|
||||
"npcAspects": ["steel-boots"],
|
||||
"weight": 10,
|
||||
"levelVariation": "0-2",
|
||||
"maxTimesSelectable": 1,
|
||||
"movesetBuilders": ["cobblemon:offensive_npc_pokemon"],
|
||||
"requires": []
|
||||
}
|
||||
]
|
||||
{
|
||||
"displayName": "Some Name",
|
||||
"entries": [
|
||||
{
|
||||
"pokemon": "geodude",
|
||||
"labels": [
|
||||
"basic",
|
||||
"rocky"
|
||||
],
|
||||
"npcLevels": "1-30",
|
||||
"weight": 20,
|
||||
"levelVariation": {
|
||||
"min": "q.level < 10 ? -1 : -3",
|
||||
"max": "q.level < 10 ? 1 : 3"
|
||||
},
|
||||
"maxTimesSelectable": 4,
|
||||
"movesetBuilders": ["cobblemon:npc_pokemon"],
|
||||
"requires": [
|
||||
"golem-ace"
|
||||
],
|
||||
"incompatible": [
|
||||
"steelix-ace"
|
||||
]
|
||||
},
|
||||
{
|
||||
"pokemon": "golem",
|
||||
"labels": [
|
||||
"ace",
|
||||
"strong"
|
||||
],
|
||||
"npcLevels": "1-30",
|
||||
"weight": 20,
|
||||
"levelVariation": "1-5",
|
||||
"maxTimesSelectable": 1,
|
||||
"movesetBuilders": ["cobblemon:offensive_npc_pokemon", "cobblemon:npc_pokemon"],
|
||||
"incompatible": [
|
||||
"steelix-ace",
|
||||
"ace"
|
||||
]
|
||||
},
|
||||
{
|
||||
"pokemon": "steelix shiny=true",
|
||||
"labels": [
|
||||
"ace",
|
||||
"strong"
|
||||
],
|
||||
"npcLevels": "15-30",
|
||||
"npcAspects": ["steel-boots"],
|
||||
"weight": "q.level < 20 ? 10 : 30",
|
||||
"levelVariation": "0-2",
|
||||
"maxTimesSelectable": "q.level > 25 ? 2 : 1",
|
||||
"movesetBuilders": ["cobblemon:offensive_npc_pokemon"],
|
||||
"requires": [],
|
||||
"incompatible": []
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Party Compositions
|
||||
@@ -150,16 +169,6 @@ The `composed_pool` party provider type uses a party pool and a party compositio
|
||||
party. When generating the party, it first decides how many Pokémon to select, based on the `minPokemon` and `maxPokemon`
|
||||
expressions. It then uses the party composition and pool to select that many Pokémon and builds a party.
|
||||
|
||||
For the Molang expression properties, the following Molang queries will be available:
|
||||
|
||||
`q.players` - A list of players that are challenging the NPC.
|
||||
|
||||
`q.player` - The player challenging the NPC, specifically if being challenged only by one player. If there are multiple players, this will be 0.
|
||||
|
||||
`q.npc` - The NPC entity.
|
||||
|
||||
`q.level` - The seed level of the NPC.
|
||||
|
||||
|
||||
| Property | Description |
|
||||
|------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
@@ -174,7 +183,7 @@ An example `composed_pool` party provider might look like:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "cobblemon:composed_pool",
|
||||
"type": "composed_pool",
|
||||
"isStatic": false,
|
||||
"useFixedRandom": true,
|
||||
"minPokemon": "q.level > 30 ? 3 : 1",
|
||||
|
||||
Reference in New Issue
Block a user