Compare commits

..
1 Commits
Author SHA1 Message Date
Paul dcfed61069 Push for non-snapshot 1.1.0 2022-11-28 10:03:14 +10:00
805 changed files with 4262 additions and 6570 deletions

No files matched your search

+1 -4
View File
@@ -28,7 +28,4 @@ forge*changelog.txt
/logs/
.vscode/launch.json
.vscode
common/src/main/resources/assets/cobblemon/showdown
# Generated files
*/generated/resources/*
common/src/main/resources/assets/cobblemon/showdown
+1 -22
View File
@@ -1,27 +1,6 @@
# Changelog
## 1.1.2
### Additions
- Reworked customisation of models, textures, and animations to make fakemon very easy to create.
- Added 'nature' and 'pokeball' options to commands such as /spawnpokemon and /givepokemon.
- Added support for datapack spawn files and spawn file presets.
### Changes
- Changed the healer advancements to make the healing machine's mechanics more obvious.
### Fixes
- Fixed an incompatibility with [Porting Lib](https://github.com/Fabricators-of-Create/Porting-Lib) used by Fabric ports of mods such as Create or Twilight Forest.
- Fixed HP and max HP values in the battle GUI not being correct.
- Fixed some animations on snake-type Pokémon being mega slow.
- Fixed a typo in the English name for Calcium. Calcuim.
- Fixed Pokémon gradually becoming rarer around you if you move long distances.
## 1.1.1 (November 27th, 2022)
### Fixes
- Fixed a critical issue with servers where Pokémon data didn't properly synchronize and so you couldn't see any.
## 1.1.0 - The Swim and Sleep Update (November 27th, 2022)
## 1.1.0 - The Swim and Sleep Update
#### "Ideally not at the same time."
-3
View File
@@ -9,7 +9,6 @@ architectury {
repositories {
maven(url = "https://dl.cloudsmith.io/public/geckolib3/geckolib/maven/")
maven(url = "https://maven.impactdev.net/repository/development/")
}
dependencies {
@@ -39,8 +38,6 @@ dependencies {
// modCompileOnly 'com.caoccao.javet:javet:1.0.6' // Linux or Windows
// modCompileOnly 'com.caoccao.javet:javet-macos:1.0.6' // Mac OS (x86_64 Only)
// modCompileOnly group: 'commons-io', name: 'commons-io', version: '2.6'
implementation("net.impactdev:json:1.0.0")
}
publishing {
@@ -35,10 +35,9 @@ import com.cobblemon.mod.common.api.pokemon.effect.ShoulderEffectRegistry
import com.cobblemon.mod.common.api.pokemon.experience.ExperienceCalculator
import com.cobblemon.mod.common.api.pokemon.experience.ExperienceGroups
import com.cobblemon.mod.common.api.pokemon.experience.StandardExperienceCalculator
import com.cobblemon.mod.common.api.pokemon.feature.ChoiceSpeciesFeatureProvider
import com.cobblemon.mod.common.api.pokemon.feature.FlagSpeciesFeatureProvider
import com.cobblemon.mod.common.api.pokemon.feature.GlobalSpeciesFeatures
import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeatures
import com.cobblemon.mod.common.api.pokemon.feature.EnumSpeciesFeature
import com.cobblemon.mod.common.api.pokemon.feature.FlagSpeciesFeature
import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeature
import com.cobblemon.mod.common.api.pokemon.stats.EvCalculator
import com.cobblemon.mod.common.api.pokemon.stats.Generation8EvCalculator
import com.cobblemon.mod.common.api.pokemon.stats.StatProvider
@@ -80,12 +79,21 @@ import com.cobblemon.mod.common.net.messages.client.settings.ServerSettingsPacke
import com.cobblemon.mod.common.net.serverhandling.ServerPacketRegistrar
import com.cobblemon.mod.common.permission.LaxPermissionValidator
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.aspects.FishStripesAspect
import com.cobblemon.mod.common.pokemon.aspects.GENDER_ASPECT
import com.cobblemon.mod.common.pokemon.aspects.SHINY_ASPECT
import com.cobblemon.mod.common.pokemon.aspects.SeasonAspect
import com.cobblemon.mod.common.pokemon.aspects.SnakePatternAspect
import com.cobblemon.mod.common.pokemon.evolution.variants.BlockClickEvolution
import com.cobblemon.mod.common.pokemon.feature.BattleCriticalHitsFeature
import com.cobblemon.mod.common.pokemon.feature.DamageTakenFeature
import com.cobblemon.mod.common.pokemon.feature.TagSeasonResolver
import com.cobblemon.mod.common.pokemon.feature.DamageTakenFeature
import com.cobblemon.mod.common.pokemon.feature.FISH_STRIPES
import com.cobblemon.mod.common.pokemon.feature.FishStripesFeature
import com.cobblemon.mod.common.pokemon.feature.SEASON
import com.cobblemon.mod.common.pokemon.feature.SNAKE_PATTERN
import com.cobblemon.mod.common.pokemon.feature.SeasonFeature
import com.cobblemon.mod.common.pokemon.feature.SnakePatternFeature
import com.cobblemon.mod.common.pokemon.properties.HiddenAbilityPropertyType
import com.cobblemon.mod.common.pokemon.properties.UncatchableProperty
import com.cobblemon.mod.common.pokemon.properties.UntradeableProperty
@@ -93,6 +101,7 @@ import com.cobblemon.mod.common.pokemon.properties.tags.PokemonFlagProperty
import com.cobblemon.mod.common.pokemon.stat.CobblemonStatProvider
import com.cobblemon.mod.common.registry.CompletableRegistry
import com.cobblemon.mod.common.starter.CobblemonStarterHandler
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.getServer
import com.cobblemon.mod.common.util.ifDedicatedServer
@@ -123,7 +132,7 @@ import org.apache.logging.log4j.LogManager
object Cobblemon {
const val MODID = "cobblemon"
const val VERSION = "1.2.0"
const val VERSION = "1.1.0"
const val CONFIG_PATH = "config/$MODID/main.json"
val LOGGER = LogManager.getLogger()
@@ -153,6 +162,7 @@ object Cobblemon {
DropEntry.register("item", ItemDropEntry::class.java, isDefault = true)
ExperienceGroups.registerDefaults()
PokemonSpecies.observable.subscribe { CobblemonSpawnPools.load() }
this.loadConfig()
this.implementation = implementation
@@ -215,12 +225,21 @@ object Cobblemon {
SHINY_ASPECT.register()
GENDER_ASPECT.register()
SnakePatternAspect.register()
FishStripesAspect.register()
SeasonAspect.register()
SpeciesFeatures.types["choice"] = ChoiceSpeciesFeatureProvider::class.java
SpeciesFeatures.types["flag"] = FlagSpeciesFeatureProvider::class.java
GlobalSpeciesFeatures.register(DamageTakenFeature.ID) { DamageTakenFeature() }
GlobalSpeciesFeatures.register(BattleCriticalHitsFeature.ID) { BattleCriticalHitsFeature() }
config.flagSpeciesFeatures.forEach(FlagSpeciesFeature::registerWithPropertyAndAspect)
config.globalFlagSpeciesFeatures.forEach(FlagSpeciesFeature::registerWithPropertyAndAspect)
FlagSpeciesFeature.registerWithPropertyAndAspect(DataKeys.ALOLAN)
FlagSpeciesFeature.registerWithPropertyAndAspect(DataKeys.GALARIAN)
FlagSpeciesFeature.registerWithPropertyAndAspect(DataKeys.HISUIAN)
FlagSpeciesFeature.registerWithPropertyAndAspect(DataKeys.VALENCIAN)
SpeciesFeature.registerGlobalFeature(DamageTakenFeature.ID) { DamageTakenFeature() }
SpeciesFeature.registerGlobalFeature(BattleCriticalHitsFeature.ID) { BattleCriticalHitsFeature() }
EnumSpeciesFeature.registerWithProperty(SNAKE_PATTERN, SnakePatternFeature::class.java)
EnumSpeciesFeature.registerWithProperty(SEASON, SeasonFeature::class.java)
EnumSpeciesFeature.registerWithProperty(FISH_STRIPES, FishStripesFeature::class.java)
CustomPokemonProperty.register(UntradeableProperty)
CustomPokemonProperty.register(UncatchableProperty)
@@ -32,8 +32,15 @@ object CobblemonEntities : CompletableRegistry<EntityType<*>>(Registry.ENTITY_TY
EntityAttributeRegistry.register(
{ POKEMON.get() },
{
LivingEntity.createLivingAttributes()
DefaultAttributeContainer
.builder()
.add(EntityAttributes.GENERIC_FOLLOW_RANGE)
.add(EntityAttributes.GENERIC_MAX_HEALTH)
.add(EntityAttributes.GENERIC_MOVEMENT_SPEED)
.add(EntityAttributes.HORSE_JUMP_STRENGTH)
.add(EntityAttributes.GENERIC_KNOCKBACK_RESISTANCE)
.add(EntityAttributes.GENERIC_ARMOR)
.add(EntityAttributes.GENERIC_ARMOR_TOUGHNESS)
.also { CobblemonEvents.ENTITY_ATTRIBUTE.post(EntityAttributeEvent(POKEMON.get(), it)) }
}
)
@@ -25,7 +25,7 @@ interface DataProvider {
*
* @param registry The [DataRegistry] being registered.
*/
fun <T : DataRegistry> register(registry: T): T
fun register(registry: DataRegistry)
/**
* Attempts to find a [DataRegistry] with the given [Identifier].
@@ -174,7 +174,4 @@ object Natures {
fun getRandomNature(): Nature {
return allNatures.random()
}
fun all(): Collection<Nature> = this.allNatures.toList()
}
@@ -1,111 +0,0 @@
package com.cobblemon.mod.common.api.pokemon
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.abilities.AbilityPool
import com.cobblemon.mod.common.api.abilities.AbilityTemplate
import com.cobblemon.mod.common.api.ai.SleepDepth
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.data.DataRegistry
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.drop.DropEntry
import com.cobblemon.mod.common.api.drop.ItemDropMethod
import com.cobblemon.mod.common.api.entity.EntityDimensionsAdapter
import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.moves.adapters.MoveTemplateAdapter
import com.cobblemon.mod.common.api.pokemon.effect.ShoulderEffect
import com.cobblemon.mod.common.api.pokemon.effect.adapter.ShoulderEffectAdapter
import com.cobblemon.mod.common.api.pokemon.egg.EggGroup
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.api.pokemon.evolution.PreEvolution
import com.cobblemon.mod.common.api.pokemon.evolution.requirement.EvolutionRequirement
import com.cobblemon.mod.common.api.pokemon.experience.ExperienceGroup
import com.cobblemon.mod.common.api.pokemon.experience.ExperienceGroupAdapter
import com.cobblemon.mod.common.api.pokemon.moves.Learnset
import com.cobblemon.mod.common.api.pokemon.stats.Stat
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.spawning.condition.TimeRange
import com.cobblemon.mod.common.api.types.ElementalType
import com.cobblemon.mod.common.api.types.adapters.ElementalTypeAdapter
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.pokemon.evolution.adapters.CobblemonEvolutionAdapter
import com.cobblemon.mod.common.pokemon.evolution.adapters.CobblemonPreEvolutionAdapter
import com.cobblemon.mod.common.pokemon.evolution.adapters.CobblemonRequirementAdapter
import com.cobblemon.mod.common.util.adapters.AbilityPoolAdapter
import com.cobblemon.mod.common.util.adapters.AbilityTemplateAdapter
import com.cobblemon.mod.common.util.adapters.BiomeLikeConditionAdapter
import com.cobblemon.mod.common.util.adapters.BlockLikeConditionAdapter
import com.cobblemon.mod.common.util.adapters.BoxAdapter
import com.cobblemon.mod.common.util.adapters.DropEntryAdapter
import com.cobblemon.mod.common.util.adapters.EggGroupAdapter
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.adapters.IntRangeAdapter
import com.cobblemon.mod.common.util.adapters.ItemLikeConditionAdapter
import com.cobblemon.mod.common.util.adapters.LazySetAdapter
import com.cobblemon.mod.common.util.adapters.LearnsetAdapter
import com.cobblemon.mod.common.util.adapters.NbtCompoundAdapter
import com.cobblemon.mod.common.util.adapters.TimeRangeAdapter
import com.cobblemon.mod.common.util.adapters.pokemonPropertiesShortAdapter
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import net.minecraft.block.Block
import net.minecraft.entity.EntityDimensions
import net.minecraft.item.Item
import net.minecraft.nbt.NbtCompound
import net.minecraft.resource.ResourceType
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.Identifier
import net.minecraft.util.math.Box
import net.minecraft.world.biome.Biome
object PokemonForms : JsonDataRegistry<FormData> {
override val id = cobblemonResource("forms")
override val type = ResourceType.SERVER_DATA
override val gson: Gson = GsonBuilder()
.registerTypeAdapter(Stat::class.java, Cobblemon.statProvider.typeAdapter)
.registerTypeAdapter(ElementalType::class.java, ElementalTypeAdapter)
.registerTypeAdapter(AbilityTemplate::class.java, AbilityTemplateAdapter)
.registerTypeAdapter(ShoulderEffect::class.java, ShoulderEffectAdapter)
.registerTypeAdapter(MoveTemplate::class.java, MoveTemplateAdapter)
.registerTypeAdapter(ExperienceGroup::class.java, ExperienceGroupAdapter)
.registerTypeAdapter(EntityDimensions::class.java, EntityDimensionsAdapter)
.registerTypeAdapter(Learnset::class.java, LearnsetAdapter)
.registerTypeAdapter(Evolution::class.java, CobblemonEvolutionAdapter)
.registerTypeAdapter(Box::class.java, BoxAdapter)
.registerTypeAdapter(AbilityPool::class.java, AbilityPoolAdapter)
.registerTypeAdapter(EvolutionRequirement::class.java, CobblemonRequirementAdapter)
.registerTypeAdapter(PreEvolution::class.java, CobblemonPreEvolutionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(Set::class.java, Evolution::class.java).type, LazySetAdapter(Evolution::class))
.registerTypeAdapter(IntRange::class.java, IntRangeAdapter)
.registerTypeAdapter(PokemonProperties::class.java, pokemonPropertiesShortAdapter)
.registerTypeAdapter(Identifier::class.java, IdentifierAdapter)
.registerTypeAdapter(TimeRange::class.java, TimeRangeAdapter)
.registerTypeAdapter(ItemDropMethod::class.java, ItemDropMethod.adapter)
.registerTypeAdapter(SleepDepth::class.java, SleepDepth.adapter)
.registerTypeAdapter(DropEntry::class.java, DropEntryAdapter)
.registerTypeAdapter(NbtCompound::class.java, NbtCompoundAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Biome::class.java).type, BiomeLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Block::class.java).type, BlockLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Item::class.java).type, ItemLikeConditionAdapter)
.registerTypeAdapter(EggGroup::class.java, EggGroupAdapter)
.disableHtmlEscaping()
.enableComplexMapKeySerialization()
.create()
override val typeToken: TypeToken<FormData> = TypeToken.get(FormData::class.java)
override val resourcePath: String = "forms"
override val observable = SimpleObservable<PokemonForms>()
override fun reload(data: Map<Identifier, FormData>) {
TODO("Not yet implemented")
}
override fun sync(player: ServerPlayerEntity) {
TODO("Not yet implemented")
}
}
@@ -9,7 +9,6 @@
package com.cobblemon.mod.common.api.pokemon
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.pokeball.PokeBalls
import com.cobblemon.mod.common.api.pokemon.aspect.AspectProvider
import com.cobblemon.mod.common.api.properties.CustomPokemonProperty
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
@@ -26,7 +25,6 @@ import net.minecraft.nbt.NbtCompound
import net.minecraft.nbt.NbtElement
import net.minecraft.nbt.NbtList
import net.minecraft.nbt.NbtString
import net.minecraft.util.Identifier
import net.minecraft.util.InvalidIdentifierException
import net.minecraft.world.World
@@ -76,8 +74,6 @@ open class PokemonProperties {
props.species = parseSpeciesIdentifier(keyPairs)
props.form = parseForm(keyPairs)
props.friendship = parseIntProperty(keyPairs, listOf("friendship"))?.coerceIn(0, Cobblemon.config.maxPokemonFriendship)
props.pokeball = parseIdentifierOfRegistry(keyPairs, listOf("pokeball")) { identifier -> PokeBalls.getPokeBall(identifier)?.name?.toString() }
props.nature = parseIdentifierOfRegistry(keyPairs, listOf("nature")) { identifier -> Natures.getNature(identifier)?.name?.toString() }
props.updateAspects()
return props
}
@@ -96,17 +92,6 @@ open class PokemonProperties {
}
}
private fun parseIdentifierOfRegistry(keyPairs: MutableList<Pair<String, String?>>, validKeys: List<String>, valueFetcher: (Identifier) -> String?): String? {
val matched = getMatchedKeyPair(keyPairs, validKeys) ?: return null
val value = matched.second?.lowercase() ?: return null
return try {
val identifier = value.asIdentifierDefaultingNamespace()
valueFetcher(identifier)
} catch (_: InvalidIdentifierException) {
null
}
}
private fun parseSpeciesIdentifier(keyPairs: MutableList<Pair<String, String?>>): String? {
val matched = getMatchedKeyPair(keyPairs, listOf("species"))
if (matched != null) {
@@ -201,8 +186,6 @@ open class PokemonProperties {
var gender: Gender? = null
var level: Int? = null
var friendship: Int? = null
var pokeball: String? = null
var nature: String? = null
var aspects: Set<String> = emptySet()
var customProperties = mutableListOf<CustomPokemonProperty>()
@@ -235,8 +218,6 @@ open class PokemonProperties {
gender?.let { pokemon.gender = it }
level?.let { pokemon.level = it }
friendship?.let { pokemon.setFriendship(it) }
pokeball?.let { PokeBalls.getPokeBall(it.asIdentifierDefaultingNamespace())?.let { pokeball -> pokemon.caughtBall = pokeball } }
nature?.let { Natures.getNature(it.asIdentifierDefaultingNamespace())?.let { nature -> pokemon.nature = nature } }
customProperties.forEach { it.apply(pokemon) }
}
@@ -257,8 +238,6 @@ open class PokemonProperties {
shiny?.let { pokemonEntity.pokemon.shiny = it }
gender?.let { pokemonEntity.pokemon.gender = it }
friendship?.let { pokemonEntity.pokemon.setFriendship(it) }
pokeball?.let { PokeBalls.getPokeBall(it.asIdentifierDefaultingNamespace())?.let { pokeball -> pokemonEntity.pokemon.caughtBall = pokeball } }
nature?.let { Natures.getNature(it.asIdentifierDefaultingNamespace())?.let { nature -> pokemonEntity.pokemon.nature = nature } }
customProperties.forEach { it.apply(pokemonEntity) }
}
@@ -282,8 +261,6 @@ open class PokemonProperties {
}
form?.takeIf { !it.equals(pokemon.form.name, true) }?.let { return false }
friendship?.takeIf { it != pokemon.friendship }?.let { return false }
pokeball?.takeIf { it != pokemon.caughtBall.name.toString() }?.let { return false }
nature?.takeIf { it != pokemon.nature.name.toString() }?.let { return false }
return customProperties.none { !it.matches(pokemon) }
}
@@ -305,8 +282,6 @@ open class PokemonProperties {
}
form?.takeIf { !it.equals(pokemonEntity.pokemon.form.name, true) }?.let { return false }
friendship?.takeIf { it != pokemonEntity.pokemon.friendship }?.let { return false }
pokeball?.takeIf { it != pokemonEntity.pokemon.caughtBall.name.toString() }?.let { return false }
nature?.takeIf { it != pokemonEntity.pokemon.nature.name.toString() }?.let { return false }
return customProperties.none { !it.matches(pokemonEntity) }
}
@@ -327,8 +302,6 @@ open class PokemonProperties {
species?.let { nbt.putString(DataKeys.POKEMON_SPECIES_TEXT, it) }
form?.let { nbt.putString(DataKeys.POKEMON_FORM_ID, it) }
friendship?.let { nbt.putInt(DataKeys.POKEMON_FRIENDSHIP, it) }
pokeball?.let { nbt.putString(DataKeys.POKEMON_CAUGHT_BALL, it) }
nature?.let { nbt.putString(DataKeys.POKEMON_NATURE, it) }
val custom = NbtList()
customProperties.map { NbtString.of(it.asString()) }.forEach { custom.add(it) }
nbt.put(DataKeys.POKEMON_PROPERTIES_CUSTOM, custom)
@@ -343,8 +316,6 @@ open class PokemonProperties {
species = if (tag.contains(DataKeys.POKEMON_SPECIES_TEXT)) tag.getString(DataKeys.POKEMON_SPECIES_TEXT) else null
form = if (tag.contains(DataKeys.POKEMON_FORM_ID)) tag.getString(DataKeys.POKEMON_FORM_ID) else null
friendship = if (tag.contains(DataKeys.POKEMON_FRIENDSHIP)) tag.getInt(DataKeys.POKEMON_FRIENDSHIP) else null
pokeball = if (tag.contains(DataKeys.POKEMON_CAUGHT_BALL)) tag.getString(DataKeys.POKEMON_CAUGHT_BALL) else null
nature = if (tag.contains(DataKeys.POKEMON_NATURE)) tag.getString(DataKeys.POKEMON_NATURE) else null
val custom = tag.getList(DataKeys.POKEMON_PROPERTIES_CUSTOM, NbtElement.STRING_TYPE.toInt())
// This is kinda gross
custom.forEach { customProperties.addAll(parse(it.asString()).customProperties) }
@@ -361,8 +332,6 @@ open class PokemonProperties {
species?.let { json.addProperty(DataKeys.POKEMON_SPECIES_TEXT, it) }
form?.let { json.addProperty(DataKeys.POKEMON_FORM_ID, it) }
friendship?.let { json.addProperty(DataKeys.POKEMON_FRIENDSHIP, it) }
pokeball?.let { json.addProperty(DataKeys.POKEMON_CAUGHT_BALL, it) }
nature?.let { json.addProperty(DataKeys.POKEMON_NATURE, it) }
val custom = JsonArray()
customProperties.map { it.asString() }.forEach { custom.add(it) }
json.add(DataKeys.POKEMON_PROPERTIES_CUSTOM, custom)
@@ -377,8 +346,6 @@ open class PokemonProperties {
species = json.get(DataKeys.POKEMON_SPECIES_TEXT)?.asString
form = json.get(DataKeys.POKEMON_FORM_ID)?.asString
friendship = json.get(DataKeys.POKEMON_FRIENDSHIP)?.asInt
pokeball = json.get(DataKeys.POKEMON_CAUGHT_BALL)?.asString
nature = json.get(DataKeys.POKEMON_NATURE)?.asString
val custom = json.get(DataKeys.POKEMON_PROPERTIES_CUSTOM)?.asJsonArray
// This is still kinda gross
custom?.forEach { customProperties.addAll(parse(it.asString).customProperties) }
@@ -394,8 +361,6 @@ open class PokemonProperties {
shiny?.let { pieces.add("shiny=$it") }
gender?.let { pieces.add("gender=$it")}
friendship?.let { pieces.add("friendship=$it") }
pokeball?.let { pieces.add("pokeball=$it") }
nature?.let { pieces.add("nature=$it") }
customProperties.forEach { pieces.add(it.asString()) }
return pieces.joinToString(separator)
}
@@ -1,87 +0,0 @@
/*
* Copyright (C) 2022 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.api.pokemon.feature
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.aspect.AspectProvider
import com.cobblemon.mod.common.api.properties.CustomPokemonPropertyType
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.substitute
/**
* A [SpeciesFeatureProvider] which is a string value selected from a fixed list of choices. Parameters exist
* to change default behaviour, aspects, and the available choices. Choices must be lowercase.
*
* @author Hiroku
* @since November 30th, 2022
*/
open class ChoiceSpeciesFeatureProvider(
override val keys: List<String>,
var default: String? = null,
var choices: List<String> = listOf(),
var isAspect: Boolean = true,
var aspectFormat: String = "{{choice}}"
) : SpeciesFeatureProvider<StringSpeciesFeature>, CustomPokemonPropertyType<StringSpeciesFeature>, AspectProvider {
override val needsKey = true
fun getAspect(feature: StringSpeciesFeature) = aspectFormat.substitute("choice", feature.value)
override fun examples() = choices
internal constructor(): this(emptyList())
fun get(pokemon: Pokemon) = pokemon.getFeature<StringSpeciesFeature>(keys.first())
override fun invoke(pokemon: Pokemon): StringSpeciesFeature? {
val existing = pokemon.getFeature<StringSpeciesFeature>(keys.first())
return if (existing != null && existing.value in choices) {
existing
} else {
val value = if (default in choices) {
default!!
} else if (default == "random") {
// If it's mandatory, but they provided no value and no default, give it a random value.
choices.randomOrNull() ?: throw IllegalStateException("The 'choices' list is empty for species feature provider: ${keys.joinToString()}")
} else {
return null
}
fromString(value)
}
}
override fun fromString(value: String?): StringSpeciesFeature? {
val lower = value?.lowercase()
if (lower == null || lower !in choices) {
return null
}
return StringSpeciesFeature(keys.first(), lower)
}
override fun provide(pokemon: Pokemon): Set<String> {
return if (isAspect) {
pokemon.getFeature<StringSpeciesFeature>(keys.first())?.let { setOf(getAspect(it)) } ?: emptySet()
} else {
emptySet()
}
}
override fun provide(properties: PokemonProperties): Set<String> {
return if (isAspect) {
val feature = properties.customProperties.filterIsInstance<StringSpeciesFeature>().find { it.name == keys.first() }
if (feature != null) {
setOf(getAspect(feature))
} else {
emptySet()
}
} else {
emptySet()
}
}
}
@@ -0,0 +1,66 @@
/*
* Copyright (C) 2022 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.api.pokemon.feature
import com.cobblemon.mod.common.api.properties.CustomPokemonProperty
import com.cobblemon.mod.common.pokemon.Pokemon
import com.google.gson.JsonObject
import java.util.EnumSet
import net.minecraft.nbt.NbtCompound
/**
* A [SpeciesFeature] which has a single value from an enum. Extensions of this class
* just need to provide the set of all entries of that enum.
*
* @author Hiroku
* @since October 13th, 2022
*/
abstract class EnumSpeciesFeature<T : Enum<T>> : SpeciesFeature, CustomPokemonProperty {
companion object {
fun <E : Enum<E>, T : EnumSpeciesFeature<E>> registerWithProperty(name: String, clazz: Class<T>) {
SpeciesFeature.register(name, clazz)
CustomPokemonProperty.properties.add(EnumSpeciesFeatureCustomPropertyType<E>(name))
}
}
abstract override val name: String
abstract fun getValues(): EnumSet<T>
open var enumValue = getValues().random()
override fun saveToNBT(pokemonNBT: NbtCompound): NbtCompound {
pokemonNBT.putString(name, enumValue.name)
return pokemonNBT
}
override fun loadFromNBT(pokemonNBT: NbtCompound): SpeciesFeature {
val valueName = pokemonNBT.getString(name)?.takeIf { it.isNotBlank() } ?: return this
enumValue = getValues().first { it.name == valueName }
return this
}
override fun saveToJSON(pokemonJSON: JsonObject): JsonObject {
pokemonJSON.addProperty(name, enumValue.name)
return pokemonJSON
}
override fun loadFromJSON(pokemonJSON: JsonObject): SpeciesFeature {
val valueName = pokemonJSON.get(name)?.asString ?: return this
enumValue = getValues().first { it.name == valueName }
return this
}
override fun asString() = "$name=${enumValue.name}"
override fun apply(pokemon: Pokemon) {
pokemon.getFeature<EnumSpeciesFeature<T>>(name)?.enumValue = enumValue
pokemon.updateAspects()
}
override fun matches(pokemon: Pokemon) = pokemon.getFeature<EnumSpeciesFeature<T>>(name)?.enumValue == enumValue
}
@@ -0,0 +1,36 @@
/*
* Copyright (C) 2022 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.api.pokemon.feature
import com.cobblemon.mod.common.api.properties.CustomPokemonPropertyType
/**
* An implementation of [CustomPokemonPropertyType] that interprets values for some [EnumSpeciesFeature]. The key of the
* property is the name of the enum species feature.
*
* @author Hiroku
* @since October 13th, 2022
*/
open class EnumSpeciesFeatureCustomPropertyType<T : Enum<T>>(val name: String) : CustomPokemonPropertyType<EnumSpeciesFeature<T>> {
override val keys = setOf(name)
override val needsKey = true
override fun fromString(value: String?): EnumSpeciesFeature<T>? {
value ?: return null
val feature = SpeciesFeature.get(name)?.invoke() as? EnumSpeciesFeature<T> ?: return null
val enumValue = feature.getValues().find { it.name.equals(value, ignoreCase = true) } ?: return null
feature.enumValue = enumValue
return feature
}
override fun examples(): Collection<String> {
val feature = SpeciesFeature.get(name)?.invoke() as? EnumSpeciesFeature<T> ?: return emptySet()
return feature.getValues().map { it.name.lowercase() }
}
}
@@ -8,19 +8,18 @@
package com.cobblemon.mod.common.api.pokemon.feature
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.aspect.AspectProvider
import com.cobblemon.mod.common.api.pokemon.aspect.SingleConditionalAspectProvider
import com.cobblemon.mod.common.api.properties.CustomPokemonProperty
import com.cobblemon.mod.common.api.properties.CustomPokemonPropertyType
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.substitute
import com.google.gson.JsonObject
import kotlin.random.Random
import net.minecraft.nbt.NbtCompound
/**
* A simple [SpeciesFeature] that is a true/false flag value. It implements [CustomPokemonProperty] for use in
* [PokemonProperties]. [FlagSpeciesFeatureProvider]s must be registered within [SpeciesFeatures].
* A simple [SpeciesFeature] that is a true/false flag value. It implements [CustomPokemonProperty]
* to provide a convenient means of registering it with a [CustomPokemonPropertyType]. That can be done
* smoothly using [FlagSpeciesFeature.registerWithProperty].
*
* Implementations of this class don't need to implement anything.
*
@@ -28,11 +27,21 @@ import net.minecraft.nbt.NbtCompound
* @since May 13th, 2022
*/
open class FlagSpeciesFeature(override val name: String) : SpeciesFeature, CustomPokemonProperty {
constructor(name: String, enabled: Boolean): this(name) {
this.enabled = enabled
companion object {
fun <T : FlagSpeciesFeature> registerWithProperty(name: String, clazz: Class<T>) {
SpeciesFeature.register(name, clazz)
CustomPokemonProperty.properties.add(FlagSpeciesFeatureCustomPropertyType(name))
}
fun registerWithPropertyAndAspect(name: String) {
SpeciesFeature.register(name) { FlagSpeciesFeature(name) }
CustomPokemonProperty.properties.add(FlagSpeciesFeatureCustomPropertyType(name))
AspectProvider.register(SingleConditionalAspectProvider.getForFeature(name))
}
}
var enabled = false
open var enabled = false
override fun saveToNBT(pokemonNBT: NbtCompound): NbtCompound {
pokemonNBT.putBoolean(name, enabled)
return pokemonNBT
@@ -57,75 +66,9 @@ open class FlagSpeciesFeature(override val name: String) : SpeciesFeature, Custo
override fun asString() = "$name=$enabled"
override fun apply(pokemon: Pokemon) {
val featureProvider = SpeciesFeatures.getFeature(name) ?: return
if (featureProvider in SpeciesFeatures.getFeaturesFor(pokemon.species)) {
val existingFeature = pokemon.getFeature<FlagSpeciesFeature>(name)
if (existingFeature != null) {
existingFeature.enabled = enabled
} else {
pokemon.features.add(FlagSpeciesFeature(name, enabled))
}
pokemon.updateAspects()
}
pokemon.getFeature<FlagSpeciesFeature>(name)?.enabled = enabled
pokemon.updateAspects()
}
override fun matches(pokemon: Pokemon) = pokemon.getFeature<FlagSpeciesFeature>(name)?.enabled == enabled
}
class FlagSpeciesFeatureProvider : SpeciesFeatureProvider<FlagSpeciesFeature>, CustomPokemonPropertyType<FlagSpeciesFeature>, AspectProvider {
override val keys: List<String>
override val needsKey get() = true
val default: String? = null
val isAspect = true
override fun examples() = setOf("true", "false")
internal constructor() {
this.keys = emptyList()
}
constructor(keys: List<String>) {
this.keys = keys
}
constructor(vararg keys: String) : this(keys.toList())
override fun invoke(pokemon: Pokemon): FlagSpeciesFeature? {
return pokemon.getFeature(keys.first())
?: when (default) {
"random" -> FlagSpeciesFeature(keys.first(), Random.Default.nextBoolean())
in setOf("true", "false") -> FlagSpeciesFeature(keys.first(), default.toBoolean())
else -> null
}
}
override fun fromString(value: String?): FlagSpeciesFeature? {
val isWeirdValue = value != null && value !in examples()
if (isWeirdValue) {
return null
}
return if (value == null) {
FlagSpeciesFeature(keys.first(), true)
} else {
FlagSpeciesFeature(keys.first(), value.toBoolean())
}
}
override fun provide(pokemon: Pokemon): Set<String> {
return if (isAspect && pokemon.getFeature<FlagSpeciesFeature>(keys.first())?.enabled == true) {
setOf(keys.first())
} else {
emptySet()
}
}
override fun provide(properties: PokemonProperties): Set<String> {
return if (isAspect && properties.customProperties.filterIsInstance<FlagSpeciesFeature>().find { it.name == keys.first() }?.enabled == true) {
setOf(keys.first())
} else {
emptySet()
}
}
}
@@ -0,0 +1,37 @@
/*
* Copyright (C) 2022 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.api.pokemon.feature
import com.cobblemon.mod.common.api.properties.CustomPokemonPropertyType
/**
* An implementation of [CustomPokemonPropertyType] that is a simple true/false value for a specific
* [FlagSpeciesFeature]. The key of the property is the name of the flag.
*
* @author Hiroku
* @since May 13th, 2022
*/
open class FlagSpeciesFeatureCustomPropertyType(val name: String) : CustomPokemonPropertyType<FlagSpeciesFeature> {
override val keys = setOf(name)
override val needsKey = true
override fun fromString(value: String?): FlagSpeciesFeature? {
var enabled = true
if (value != null) {
try {
enabled = value.toBoolean()
} catch (_: Exception) {}
}
val feature = SpeciesFeature.get(name)?.invoke() as? FlagSpeciesFeature ?: return null
feature.enabled = enabled
return feature
}
override fun examples() = setOf("true", "false")
}
@@ -1,85 +0,0 @@
/*
* Copyright (C) 2022 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.api.pokemon.feature
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.pokemon.aspect.AspectProvider
import com.cobblemon.mod.common.api.properties.CustomPokemonProperty
import com.cobblemon.mod.common.api.properties.CustomPokemonPropertyType
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.util.adapters.SpeciesFeatureProviderAdapter
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import net.minecraft.resource.ResourceType
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.Identifier
/**
* A registry very similar to [SpeciesFeatures] but the [SpeciesFeatureProvider] contained within it
* will attempt to be applied to all Pokémon, even those that have not elected to have the feature.
*
* @author Hiroku
* @since November 30th, 2022
*/
object GlobalSpeciesFeatures : JsonDataRegistry<SpeciesFeatureProvider<*>> {
override val id = cobblemonResource("global_species_features")
override val type = ResourceType.SERVER_DATA
override val observable = SimpleObservable<SpeciesFeatures>()
private val codeFeatures = mutableMapOf<String, SpeciesFeatureProvider<*>>()
private val resourceFeatures = mutableMapOf<String, SpeciesFeatureProvider<*>>()
override val gson: Gson = GsonBuilder()
.setPrettyPrinting()
.registerTypeAdapter(SpeciesFeatureProvider::class.java, SpeciesFeatureProviderAdapter)
.create()
override val typeToken: TypeToken<SpeciesFeatureProvider<*>> = TypeToken.get(SpeciesFeatureProvider::class.java)
override val resourcePath: String = "global_species_features"
override fun sync(player: ServerPlayerEntity) {}
override fun reload(data: Map<Identifier, SpeciesFeatureProvider<*>>) {
resourceFeatures.keys.toList().forEach(this::unregister)
data.forEach(this::registerFromAssets)
}
fun getCodeFeature(name: String) = resourceFeatures[name]
fun getResourceFeature(name: String) = codeFeatures[name]
fun getFeature(name: String) = getCodeFeature(name) ?: getResourceFeature(name)
fun getFeatures() = (resourceFeatures.keys + codeFeatures.keys).mapNotNull(this::getFeature)
private fun register(name: String, provider: SpeciesFeatureProvider<*>, isCoded: Boolean) {
val mapping = if (isCoded) codeFeatures else resourceFeatures
if (provider is AspectProvider) {
AspectProvider.register(provider)
}
if (provider is CustomPokemonPropertyType<*>) {
CustomPokemonProperty.register(provider)
}
mapping[name] = provider
}
fun register(name: String, provider: SpeciesFeatureProvider<*>) = register(name, provider, isCoded = true)
private fun registerFromAssets(identifier: Identifier, provider: SpeciesFeatureProvider<*>) = register(identifier.path, provider, isCoded = false)
fun unregister(name: String) {
var coded = true
val value = getResourceFeature(name)?.also { coded = false } ?: getCodeFeature(name) ?: return
if (value is AspectProvider) {
AspectProvider.unregister(value)
}
if (value is CustomPokemonPropertyType<*>) {
CustomPokemonProperty.unregister(value)
}
val mapping = if (coded) codeFeatures else resourceFeatures
mapping.remove(name)
}
}
@@ -16,9 +16,8 @@ import net.minecraft.nbt.NbtCompound
/**
* A piece of state that can be added to some species of Pokémon. Registering an implementing class
* using [SpeciesFeatures.register] adds it as a usable value in the [SpeciesFeatures] list. All Pokémon
* are given the opportunity to be assigned features depending on whether it's a global feature or they
* have opted into it in their species JSON.
* using [SpeciesFeature.register] adds it as a usable value in the [Species.features] list. All Pokémon
* are given a default-constructed instance of all SpeciesFeatures mentioned in its species definition.
*
* The role of this is to allow species-specific data to be attached to individual Pokémon, such as an alolan
* flag or a Vivillon pattern variety. This is powerful when combined with [AspectProvider]s.
@@ -28,6 +27,36 @@ import net.minecraft.nbt.NbtCompound
*/
interface SpeciesFeature {
val name: String
companion object {
private val speciesFeatures = HashBiMap.create<String, () -> SpeciesFeature>()
private val globalSpeciesFeatures = HashBiMap.create<String, () -> SpeciesFeature>()
fun <T : SpeciesFeature> register(name: String, clazz: Class<T>, global: Boolean = false) {
try {
clazz.getDeclaredConstructor().newInstance()
} catch (e: Exception) {
e.printStackTrace()
throw IllegalArgumentException("The given species feature class for $name could not be constructed with a default constructor call. You need a default constructor to use species features.", e)
}
val instanceJob = { clazz.getDeclaredConstructor().newInstance() }
speciesFeatures[name] = instanceJob
if (global) {
globalSpeciesFeatures[name] = instanceJob
}
}
fun <T : SpeciesFeature> register(name: String, instantiator: () -> T) {
speciesFeatures[name] = instantiator
}
fun <T : SpeciesFeature> registerGlobalFeature(name: String, instantiator: () -> T) {
this.register(name, instantiator)
globalSpeciesFeatures[name] = instantiator
}
fun unregister(name: String) {
speciesFeatures.remove(name)
}
fun get(name: String) = speciesFeatures[name]
fun globalFeatures() = this.globalSpeciesFeatures.toMap()
}
fun saveToNBT(pokemonNBT: NbtCompound): NbtCompound
fun loadFromNBT(pokemonNBT: NbtCompound): SpeciesFeature
fun saveToJSON(pokemonJSON: JsonObject): JsonObject
@@ -1,25 +0,0 @@
/*
* Copyright (C) 2022 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.api.pokemon.feature
import com.cobblemon.mod.common.pokemon.Species
/**
* An assignment of some number of [Species] to some number of [SpeciesFeatureProvider]s. In most cases
* this will be a single Pokémon and a single feature, but it can be used to attach many to many.
*
* These assignments are registered in [SpeciesFeatureAssignments].
*
* @author Hiroku
* @since December 1st, 2022
*/
class SpeciesFeatureAssignment {
val pokemon: List<String> = emptyList()
val features: List<String> = emptyList()
}
@@ -1,53 +0,0 @@
/*
* Copyright (C) 2022 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.api.pokemon.feature
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.pokemon.Species
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import net.minecraft.resource.ResourceType
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.Identifier
/**
* A registry of assignments combining [SpeciesFeatures] and [PokemonSpecies]. This is a way around the issue
* of when multiple data packs want to add their own [SpeciesFeature]s to the same species. The correct way,
* with this registry, is to add a new JSON that joins together a list of species with a list of species feature
* keys.
*
* @author Hiroku
* @since December 1st, 2022
*/
object SpeciesFeatureAssignments : JsonDataRegistry<SpeciesFeatureAssignment> {
override val id: Identifier = cobblemonResource("species_feature_assignments")
override val type: ResourceType = ResourceType.SERVER_DATA
override val observable = SimpleObservable<SpeciesFeatureAssignments>()
override val gson: Gson = GsonBuilder().setPrettyPrinting().create()
override val typeToken = TypeToken.get(SpeciesFeatureAssignment::class.java)
override val resourcePath = "species_feature_assignments"
private val assignments = mutableMapOf<String, MutableSet<String>>()
override fun sync(player: ServerPlayerEntity) {}
override fun reload(data: Map<Identifier, SpeciesFeatureAssignment>) {
data.values.forEach {
it.pokemon.forEach { pokemon ->
assignments.getOrPut(pokemon) { mutableSetOf() }.addAll(it.features)
}
}
}
fun getFeatures(species: Species) = assignments[species.name] ?: emptySet()
}
@@ -1,30 +0,0 @@
/*
* Copyright (C) 2022 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.api.pokemon.feature
import com.cobblemon.mod.common.pokemon.Pokemon
/**
* A provider of [SpeciesFeature]s. These must be registered via [SpeciesFeatures] either programmatically or
* using the datapack structure. Note that for datapacks to be able to use an implementation of this interface,
* it must be registered in [SpeciesFeatures.types] and then the JSON must include a 'type' field that references
* the key used for that mapping.
*
* @author Hiroku
* @since November 29th, 2022
*/
fun interface SpeciesFeatureProvider<T : SpeciesFeature> {
/**
* Attempts to create a species feature for the given [Pokemon]. It is appropriate to check for an existing feature
* that is compatible with this type and transfer / retain that feature instead of constructing a new one. For example
* a feature for an Ekan's snake pattern should be retained when evolving into Arbok, and species feature invocation
* occurs whenever a species changes.
*/
operator fun invoke(pokemon: Pokemon): T?
}
@@ -1,101 +0,0 @@
/*
* Copyright (C) 2022 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.api.pokemon.feature
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.pokemon.aspect.AspectProvider
import com.cobblemon.mod.common.api.properties.CustomPokemonProperty
import com.cobblemon.mod.common.api.properties.CustomPokemonPropertyType
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.pokemon.Species
import com.cobblemon.mod.common.util.adapters.SpeciesFeatureProviderAdapter
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import net.minecraft.resource.ResourceType
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.Identifier
/**
* A registry for [SpeciesFeatureProvider]s. This is the backbone of species-specific data such as
* variation information and counters used for some evolution types.
*
* Providers are loaded from JSON but can also be added programmatically. Species feature providers
* that are registered via code will replace same-name features that were drawn from assets.
*
* If a species feature provider implements [CustomPokemonPropertyType] then it will be automatically
* registered appropriately, as it will with aspects if it implements [AspectProvider].
*
* @author Hiroku
* @since November 30th, 2022
*/
object SpeciesFeatures : JsonDataRegistry<SpeciesFeatureProvider<*>> {
override val id = cobblemonResource("species_features")
override val type = ResourceType.SERVER_DATA
override val observable = SimpleObservable<SpeciesFeatures>()
val types = mutableMapOf<String, Class<out SpeciesFeatureProvider<*>>>()
private val codeFeatures = mutableMapOf<String, SpeciesFeatureProvider<*>>()
private val resourceFeatures = mutableMapOf<String, SpeciesFeatureProvider<*>>()
override val gson: Gson = GsonBuilder()
.setPrettyPrinting()
.registerTypeAdapter(SpeciesFeatureProvider::class.java, SpeciesFeatureProviderAdapter)
.create()
override val typeToken: TypeToken<SpeciesFeatureProvider<*>> = TypeToken.get(SpeciesFeatureProvider::class.java)
override val resourcePath: String = "species_features"
override fun sync(player: ServerPlayerEntity) {}
override fun reload(data: Map<Identifier, SpeciesFeatureProvider<*>>) {
resourceFeatures.keys.toList().forEach(this::unregister)
data.forEach(this::registerFromAssets)
}
fun getCodeFeature(name: String) = resourceFeatures[name]
fun getResourceFeature(name: String) = codeFeatures[name]
fun getFeature(name: String) = getCodeFeature(name) ?: getResourceFeature(name)
fun getFeatures() = (resourceFeatures.keys + codeFeatures.keys).mapNotNull(this::getFeature)
fun getFeaturesFor(species: Species): List<SpeciesFeatureProvider<*>> {
val mentionedFeatures = species.features.mapNotNull(this::getFeature)
val globalFeatures = GlobalSpeciesFeatures.getFeatures()
val assignedFeatures = SpeciesFeatureAssignments.getFeatures(species).mapNotNull(this::getFeature)
return (mentionedFeatures + globalFeatures + assignedFeatures).distinct()
}
private fun register(name: String, provider: SpeciesFeatureProvider<*>, isCoded: Boolean) {
val mapping = if (isCoded) codeFeatures else resourceFeatures
if (provider is AspectProvider) {
AspectProvider.register(provider)
}
if (provider is CustomPokemonPropertyType<*>) {
CustomPokemonProperty.register(provider)
}
mapping[name] = provider
}
fun register(name: String, provider: SpeciesFeatureProvider<*>) = register(name, provider, isCoded = true)
private fun registerFromAssets(identifier: Identifier, provider: SpeciesFeatureProvider<*>) = register(identifier.path, provider, isCoded = false)
fun unregister(name: String) {
var coded = true
val value = getResourceFeature(name)?.also { coded = false } ?: getCodeFeature(name) ?: return
if (value is AspectProvider) {
AspectProvider.unregister(value)
}
if (value is CustomPokemonPropertyType<*>) {
CustomPokemonProperty.unregister(value)
}
val mapping = if (coded) codeFeatures else resourceFeatures
mapping.remove(name)
}
}
@@ -1,62 +0,0 @@
/*
* Copyright (C) 2022 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.api.pokemon.feature
import com.cobblemon.mod.common.api.properties.CustomPokemonProperty
import com.cobblemon.mod.common.pokemon.Pokemon
import com.google.gson.JsonObject
import net.minecraft.nbt.NbtCompound
/**
* A species feature value that is a string value.
*
* @author Hiroku
* @since December 30th, 2022
*/
class StringSpeciesFeature(
override val name: String,
var value: String
) : SpeciesFeature, CustomPokemonProperty {
override fun saveToNBT(pokemonNBT: NbtCompound): NbtCompound {
pokemonNBT.putString(name, value)
return pokemonNBT
}
override fun loadFromNBT(pokemonNBT: NbtCompound): SpeciesFeature {
value = pokemonNBT.getString(name)?.takeIf { it.isNotBlank() }?.lowercase() ?: return this
return this
}
override fun saveToJSON(pokemonJSON: JsonObject): JsonObject {
pokemonJSON.addProperty(name, value)
return pokemonJSON
}
override fun loadFromJSON(pokemonJSON: JsonObject): SpeciesFeature {
value = pokemonJSON.get(name)?.asString?.lowercase() ?: return this
return this
}
override fun asString() = "$name=$value"
override fun apply(pokemon: Pokemon) {
val featureProvider = SpeciesFeatures.getFeature(name) ?: return
if (featureProvider in SpeciesFeatures.getFeaturesFor(pokemon.species)) {
val existingFeature = pokemon.getFeature<StringSpeciesFeature>(name)
if (existingFeature != null) {
existingFeature.value = value
} else {
pokemon.features.add(StringSpeciesFeature(name, value))
}
pokemon.updateAspects()
}
}
override fun matches(pokemon: Pokemon) = pokemon.getFeature<StringSpeciesFeature>(name)?.value == value
}
@@ -48,10 +48,6 @@ interface CustomPokemonProperty {
this.triggerSyncAttempt()
}
fun unregister(property: CustomPokemonPropertyType<*>) {
properties.remove(property)
}
// We do this every time a new property is registered if the server is running in order to synchronize all players with the new property for tab completion purposes
private fun triggerSyncAttempt() {
val server = getServer() ?: return
@@ -44,7 +44,6 @@ import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import com.cobblemon.mod.common.api.spawning.spawner.TickingSpawner
import com.cobblemon.mod.common.entity.pokemon.CobblemonAgingDespawner
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.util.Identifier
/**
* A grouping of all the overarching behaviours of the Best Spawner system. This is a convenient accessor to
@@ -80,6 +79,7 @@ import net.minecraft.util.Identifier
*/
object BestSpawner {
var config = BestSpawnerConfig()
var presets = mutableMapOf<String, SpawnDetailPreset>()
val spawnerManagers = mutableListOf<SpawnerManager>(CobblemonWorldSpawnerManager)
var defaultPokemonDespawner: Despawner<PokemonEntity> = CobblemonAgingDespawner(getAgeTicks = { it.ticksLived })
@@ -108,8 +108,10 @@ object BestSpawner {
config = BestSpawnerConfig.load()
SpawnDetailPresets.registerPresetType(BasicSpawnDetailPreset.NAME, BasicSpawnDetailPreset::class.java)
SpawnDetailPresets.registerPresetType(PokemonSpawnDetailPreset.NAME, PokemonSpawnDetailPreset::class.java)
SpawnDetailPreset.registerPresetType(BasicSpawnDetailPreset.NAME, BasicSpawnDetailPreset::class.java)
SpawnDetailPreset.registerPresetType(PokemonSpawnDetailPreset.NAME, PokemonSpawnDetailPreset::class.java)
presets = SpawnDetailPreset.load()
LOGGER.info("Loaded ${presets.size} spawn detail presets.")
}
fun onServerStarted() {
@@ -11,7 +11,6 @@ package com.cobblemon.mod.common.api.spawning
import com.cobblemon.mod.common.api.spawning.condition.BucketPrecalculation
import com.cobblemon.mod.common.api.spawning.condition.ContextPrecalculation
import com.cobblemon.mod.common.api.spawning.detail.SpawnPool
import com.cobblemon.mod.common.data.CobblemonDataProvider
/**
* A collection of all of Cobblemon's general-purpose [SpawnPool]s. These
@@ -26,6 +25,6 @@ object CobblemonSpawnPools {
lateinit var WORLD_SPAWN_POOL: SpawnPool
fun load() {
WORLD_SPAWN_POOL = CobblemonDataProvider.register(SpawnPool("world").addPrecalculators(ContextPrecalculation, BucketPrecalculation))
WORLD_SPAWN_POOL = SpawnLoader.load("world").addPrecalculators(ContextPrecalculation, BucketPrecalculation)
}
}
@@ -1,83 +0,0 @@
/*
* Copyright (C) 2022 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.api.spawning
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.data.DataRegistry
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.spawning.condition.SpawningCondition
import com.cobblemon.mod.common.api.spawning.condition.TimeRange
import com.cobblemon.mod.common.api.spawning.context.RegisteredSpawningContext
import com.cobblemon.mod.common.api.spawning.preset.SpawnDetailPreset
import com.cobblemon.mod.common.util.adapters.BiomeLikeConditionAdapter
import com.cobblemon.mod.common.util.adapters.BlockLikeConditionAdapter
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.adapters.RegisteredSpawningContextAdapter
import com.cobblemon.mod.common.util.adapters.SpawnBucketAdapter
import com.cobblemon.mod.common.util.adapters.SpawnDetailPresetAdapter
import com.cobblemon.mod.common.util.adapters.SpawningConditionAdapter
import com.cobblemon.mod.common.util.adapters.TimeRangeAdapter
import com.cobblemon.mod.common.util.adapters.pokemonPropertiesShortAdapter
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import net.minecraft.block.Block
import net.minecraft.resource.ResourceType
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.Identifier
import net.minecraft.world.biome.Biome
/**
* Data registry for [SpawnDetailPreset]s. These help the maintainability of spawn files by allowing common presets
* to be defined separately to the spawns that obey it. You can register custom presets either programmatically or
* by adding preset JSONs to the spawn_detail_presets data folder.
*
* @author Hiroku
* @since December 9th, 2022
*/
object SpawnDetailPresets : JsonDataRegistry<SpawnDetailPreset> {
val GSON = GsonBuilder()
.setPrettyPrinting()
.setLenient()
.disableHtmlEscaping()
.registerTypeAdapter(SpawnBucket::class.java, SpawnBucketAdapter)
.registerTypeAdapter(RegisteredSpawningContext::class.java, RegisteredSpawningContextAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Biome::class.java).type, BiomeLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Block::class.java).type, BlockLikeConditionAdapter)
.registerTypeAdapter(SpawnDetailPreset::class.java, SpawnDetailPresetAdapter)
.registerTypeAdapter(Identifier::class.java, IdentifierAdapter)
.registerTypeAdapter(SpawningCondition::class.java, SpawningConditionAdapter)
.registerTypeAdapter(TimeRange::class.java, TimeRangeAdapter)
.registerTypeAdapter(PokemonProperties::class.java, pokemonPropertiesShortAdapter)
.create()
val presetTypes = mutableMapOf<String, Class<out SpawnDetailPreset>>()
fun <T : SpawnDetailPreset> registerPresetType(name: String, detailClass: Class<T>) {
presetTypes[name] = detailClass
}
override val gson: Gson = GSON
override val typeToken = TypeToken.get(SpawnDetailPreset::class.java)
override val resourcePath = "spawn_detail_presets"
override val id = cobblemonResource(resourcePath)
override val type = ResourceType.SERVER_DATA
override val observable = SimpleObservable<SpawnDetailPresets>()
var presets = mutableMapOf<Identifier, SpawnDetailPreset>()
override fun sync(player: ServerPlayerEntity) {}
override fun reload(data: Map<Identifier, SpawnDetailPreset>) {
this.presets = data.toMutableMap()
Cobblemon.LOGGER.info("Loaded ${presets.size} spawn detail presets.")
}
}
@@ -81,145 +81,145 @@ object SpawnLoader {
var deserializingConditionClass: Class<out SpawningCondition<*>>? = null
// fun load(folder: String): SpawnPool {
// val pool = SpawnPool()
//
// val external = getExternalSets(folder)
// val internal = loadInternalFolder(folder)
//
// external.forEach { set ->
// val matchingInternal = internal.find { it.id == set.id }
// if (set.preventOverwrite) {
// if (matchingInternal != null) {
// internal.remove(matchingInternal)
// }
// if (set.isEnabled()) {
// pool.details.addAll(set.filter { it.isModDependencySatisfied() })
// }
// } else {
// if (matchingInternal != null && matchingInternal.version.isHigherVersion(set.version)) {
// if (matchingInternal.isEnabled()) {
// pool.details.addAll(matchingInternal.filter { it.isModDependencySatisfied() })
// }
// internal.remove(matchingInternal)
// exportSet(folder, matchingInternal)
// }
// }
// }
//
// internal.forEach { set ->
// if (set.isEnabled()) {
// pool.details.addAll(set.filter { it.isModDependencySatisfied() })
// }
// if (config.exportSpawnsToConfig) {
// exportSet(folder, set)
// }
// }
//
// pool.precalculate()
//
// return pool
// }
//
// fun exportSet(folder: String, set: SpawnSet) {
// val file = File("./config/cobblemon/spawning/spawns/$folder/${set.id.lowercase()}.set.json")
// file.parentFile.mkdirs()
// file.createNewFile()
//
// val internalStream = Cobblemon::class.java.getResourceAsStream(set.path.toAbsolutePath().toString())!!
// val bytes = internalStream.readAllBytes()
// internalStream.close()
//
// val externalStream = FileOutputStream(file)
// externalStream.write(bytes)
// externalStream.close()
// }
//
// fun getExternalSets(folder: String): MutableList<SpawnSet> {
// val sets = mutableListOf<SpawnSet>()
// val files = mutableListOf<File>()
// AssetLoading.searchFor(
// dir = "config/cobblemon/spawning/spawns/$folder",
// suffix = ".json",
// list = files
// )
// val paths = files.map { it.toPath() }
//
// sets.addAll(
// paths.mapNotNull {
// try {
// val file = it.toFile()
// val reader = FileReader(file)
// val set = gson.fromJson<SpawnSet>(reader)
// reader.close()
// return@mapNotNull set
// } catch (exception : Exception) {
// LOGGER.error("Unable to load spawn set from ${it.pathString}")
// exception.printStackTrace()
// null
// }
// }
// )
//
// return sets
// }
//
// fun loadSetFromPath(path: Path): SpawnSet {
// val pathString = if (path.toString().startsWith("/")) path.toString() else "/$path"
// val inputStream = Cobblemon.javaClass.getResourceAsStream(pathString)
// ?: throw IllegalArgumentException("Unable to open resource stream for: $pathString")
// val reader = InputStreamReader(inputStream)
// val set = gson.fromJson<SpawnSet>(reader)
// reader.close()
// return set
// }
//
// fun loadInternalFolder(folder: String): MutableList<SpawnSet> {
// val internalFolder = cobblemonResource("spawning/spawns/$folder")
// val path = internalFolder.toPath() ?: throw IllegalArgumentException("There is no valid, internal path for $internalFolder")
// val internalPaths = AssetLoading.fileSearch(path, { it.toString().endsWith(".json") }, recursive = true)
// val sets = mutableListOf<SpawnSet>()
// sets.addAll(internalPaths.flatMap { filePath -> loadInternalFile(filePath) })
// return sets
// }
//
// fun loadInternalFile(path: Path): MutableList<SpawnSet> {
// if (path.toString().endsWith(".packed.json")) {
// return loadPackedSets(path).onEach { it.path = path }
// } else if (path.toString().endsWith(".json")) {
// return mutableListOf(loadSetFromPath(path)).onEach { it.path = path }
// }
//
// return mutableListOf()
// }
//
// fun loadPackedSets(path: Path): MutableList<SpawnSet> {
// val fileStream = Cobblemon::class.java.getResourceAsStream(path.toAbsolutePath().toString())
// ?: throw IllegalArgumentException("Unable to create input stream for $path")
//
// return try {
// loadPackedSetsFromStream(fileStream)
// } catch (e: Exception) {
// LOGGER.error(e.message)
// return mutableListOf()
// } finally {
// fileStream.close()
// }
// }
//
//
// fun loadPackedSetsFromStream(inputStream: InputStream): MutableList<SpawnSet> {
// val sets = mutableListOf<SpawnSet>()
// val stream = InputStreamReader(inputStream)
// val jsonArray = gson.fromJson(stream, JsonArray::class.java)
// stream.close()
// jsonArray.forEach { jsonElement ->
// try {
// sets.add(gson.fromJson<SpawnSet>(jsonElement))
// } catch (exception: Exception) {
// exception.printStackTrace()
// }
// }
// return sets
// }
fun load(folder: String): SpawnPool {
val pool = SpawnPool()
val external = getExternalSets(folder)
val internal = loadInternalFolder(folder)
external.forEach { set ->
val matchingInternal = internal.find { it.id == set.id }
if (set.preventOverwrite) {
if (matchingInternal != null) {
internal.remove(matchingInternal)
}
if (set.isEnabled()) {
pool.details.addAll(set.filter { it.isModDependencySatisfied() })
}
} else {
if (matchingInternal != null && matchingInternal.version.isHigherVersion(set.version)) {
if (matchingInternal.isEnabled()) {
pool.details.addAll(matchingInternal.filter { it.isModDependencySatisfied() })
}
internal.remove(matchingInternal)
exportSet(folder, matchingInternal)
}
}
}
internal.forEach { set ->
if (set.isEnabled()) {
pool.details.addAll(set.filter { it.isModDependencySatisfied() })
}
if (config.exportSpawnsToConfig) {
exportSet(folder, set)
}
}
pool.precalculate()
return pool
}
fun exportSet(folder: String, set: SpawnSet) {
val file = File("./config/cobblemon/spawning/spawns/$folder/${set.id.lowercase()}.set.json")
file.parentFile.mkdirs()
file.createNewFile()
val internalStream = Cobblemon::class.java.getResourceAsStream(set.path.toAbsolutePath().toString())!!
val bytes = internalStream.readAllBytes()
internalStream.close()
val externalStream = FileOutputStream(file)
externalStream.write(bytes)
externalStream.close()
}
fun getExternalSets(folder: String): MutableList<SpawnSet> {
val sets = mutableListOf<SpawnSet>()
val files = mutableListOf<File>()
AssetLoading.searchFor(
dir = "config/cobblemon/spawning/spawns/$folder",
suffix = ".json",
list = files
)
val paths = files.map { it.toPath() }
sets.addAll(
paths.mapNotNull {
try {
val file = it.toFile()
val reader = FileReader(file)
val set = gson.fromJson<SpawnSet>(reader)
reader.close()
return@mapNotNull set
} catch (exception : Exception) {
LOGGER.error("Unable to load spawn set from ${it.pathString}")
exception.printStackTrace()
null
}
}
)
return sets
}
fun loadSetFromPath(path: Path): SpawnSet {
val pathString = if (path.toString().startsWith("/")) path.toString() else "/$path"
val inputStream = Cobblemon.javaClass.getResourceAsStream(pathString)
?: throw IllegalArgumentException("Unable to open resource stream for: $pathString")
val reader = InputStreamReader(inputStream)
val set = gson.fromJson<SpawnSet>(reader)
reader.close()
return set
}
fun loadInternalFolder(folder: String): MutableList<SpawnSet> {
val internalFolder = cobblemonResource("spawning/spawns/$folder")
val path = internalFolder.toPath() ?: throw IllegalArgumentException("There is no valid, internal path for $internalFolder")
val internalPaths = AssetLoading.fileSearch(path, { it.toString().endsWith(".json") }, recursive = true)
val sets = mutableListOf<SpawnSet>()
sets.addAll(internalPaths.flatMap { filePath -> loadInternalFile(filePath) })
return sets
}
fun loadInternalFile(path: Path): MutableList<SpawnSet> {
if (path.toString().endsWith(".packed.json")) {
return loadPackedSets(path).onEach { it.path = path }
} else if (path.toString().endsWith(".json")) {
return mutableListOf(loadSetFromPath(path)).onEach { it.path = path }
}
return mutableListOf()
}
fun loadPackedSets(path: Path): MutableList<SpawnSet> {
val fileStream = Cobblemon::class.java.getResourceAsStream(path.toAbsolutePath().toString())
?: throw IllegalArgumentException("Unable to create input stream for $path")
return try {
loadPackedSetsFromStream(fileStream)
} catch (e: Exception) {
LOGGER.error(e.message)
return mutableListOf()
} finally {
fileStream.close()
}
}
fun loadPackedSetsFromStream(inputStream: InputStream): MutableList<SpawnSet> {
val sets = mutableListOf<SpawnSet>()
val stream = InputStreamReader(inputStream)
val jsonArray = gson.fromJson(stream, JsonArray::class.java)
stream.close()
jsonArray.forEach { jsonElement ->
try {
sets.add(gson.fromJson<SpawnSet>(jsonElement))
} catch (exception: Exception) {
exception.printStackTrace()
}
}
return sets
}
}
@@ -20,11 +20,14 @@ import java.nio.file.Path
* @since January 27th, 2022
*/
class SpawnSet : Iterable<SpawnDetail>, ModDependant {
var id = ""
var enabled = true
var version = "1"
var preventOverwrite = false
override var neededInstalledMods = listOf<String>()
override var neededUninstalledMods = listOf<String>()
var spawns = mutableListOf<SpawnDetail>()
@Transient
lateinit var path: Path
fun isEnabled(): Boolean = enabled && isModDependencySatisfied()
@@ -12,7 +12,8 @@ import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.feature.SeasonFeatureHandler
import com.cobblemon.mod.common.pokemon.feature.SEASON
import com.cobblemon.mod.common.pokemon.feature.SeasonFeature
import kotlin.random.Random
/**
@@ -36,7 +37,7 @@ class PokemonSpawnAction(
props.shiny = Cobblemon.config.shinyRate.takeIf { it >= 1 }?.let { Random.Default.nextFloat() < 1 / it }
}
val entity = props.createEntity(ctx.world)
SeasonFeatureHandler.updateSeason(entity.pokemon, ctx.world, ctx.position)
entity.pokemon.getFeature<SeasonFeature>(SEASON)?.update(entity.pokemon, ctx.world, ctx.position)
entity.drops = detail.drops
return entity
}
@@ -8,23 +8,11 @@
package com.cobblemon.mod.common.api.spawning.detail
import com.cobblemon.mod.common.api.data.DataRegistry
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.spawning.SpawnLoader
import com.cobblemon.mod.common.api.spawning.SpawnSet
import com.cobblemon.mod.common.api.spawning.condition.FinalPrecalculationResult
import com.cobblemon.mod.common.api.spawning.condition.PrecalculationResult
import com.cobblemon.mod.common.api.spawning.condition.RootPrecalculation
import com.cobblemon.mod.common.api.spawning.condition.SpawningPrecalculation
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import net.minecraft.resource.ResourceType
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.Identifier
/**
* A collection of [SpawnDetail]s with precalculation logic for optimization of searches.
@@ -37,22 +25,7 @@ import net.minecraft.util.Identifier
* @author Hiroku
* @since February 9th, 2022
*/
class SpawnPool(val name: String) : JsonDataRegistry<SpawnSet>, Iterable<SpawnDetail> {
override val id = cobblemonResource("spawn_pool_$name")
override val type = ResourceType.SERVER_DATA
override val observable = SimpleObservable<SpawnPool>()
override val gson: Gson = SpawnLoader.gson
override val typeToken = TypeToken.get(SpawnSet::class.java)
override val resourcePath = id.path
override fun sync(player: ServerPlayerEntity) {}
override fun reload(data: Map<Identifier, SpawnSet>) {
details.clear()
for (set in data.values) {
details.addAll(set.filter { it.isModDependencySatisfied() })
}
precalculate()
}
class SpawnPool : Iterable<SpawnDetail> {
val details = mutableListOf<SpawnDetail>()
var precalculation: PrecalculationResult<*> = RootPrecalculation.generate(details, emptyList())
val precalculators = mutableListOf<SpawningPrecalculation<*>>()
@@ -102,8 +75,8 @@ class SpawnPool(val name: String) : JsonDataRegistry<SpawnSet>, Iterable<SpawnDe
* Creates a de-referenced copy of the pool which can be modified safely without
* this pool being changed.
*/
fun copy(newName: String): SpawnPool {
val copy = SpawnPool(newName)
fun copy(): SpawnPool {
val copy = SpawnPool()
copy.details.addAll(details)
copy.precalculators.addAll(precalculators)
copy.precalculation = precalculation
@@ -54,7 +54,7 @@ class BestSpawnerConfig {
fun load(): BestSpawnerConfig {
val internal = loadInternal()
if (Cobblemon.config.exportSpawnConfig) {
if (Cobblemon.config.exportSpawnConfigToConfig) {
val external = loadExternal()
return if (external == null) {
saveExternal()
@@ -8,16 +8,39 @@
package com.cobblemon.mod.common.api.spawning.preset
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.api.asset.JsonManifestWalker
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.SpawnDetailPresets
import com.cobblemon.mod.common.api.spawning.SpawnLoader
import com.cobblemon.mod.common.api.spawning.condition.SpawningCondition
import com.cobblemon.mod.common.api.spawning.condition.TimeRange
import com.cobblemon.mod.common.api.spawning.context.RegisteredSpawningContext
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.multiplier.WeightMultiplier
import com.cobblemon.mod.common.util.AssetLoading
import com.cobblemon.mod.common.util.MergeMode
import com.cobblemon.mod.common.util.adapters.BiomeLikeConditionAdapter
import com.cobblemon.mod.common.util.adapters.BlockLikeConditionAdapter
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.adapters.RegisteredSpawningContextAdapter
import com.cobblemon.mod.common.util.adapters.SpawnBucketAdapter
import com.cobblemon.mod.common.util.adapters.SpawnDetailAdapter
import com.cobblemon.mod.common.util.adapters.SpawnDetailPresetAdapter
import com.cobblemon.mod.common.util.adapters.SpawningConditionAdapter
import com.cobblemon.mod.common.util.adapters.TimeRangeAdapter
import com.cobblemon.mod.common.util.adapters.pokemonPropertiesShortAdapter
import com.cobblemon.mod.common.util.fromJson
import com.google.gson.GsonBuilder
import com.google.gson.JsonObject
import com.google.gson.reflect.TypeToken
import java.io.File
import java.io.FileReader
import net.minecraft.block.Block
import net.minecraft.util.Identifier
import net.minecraft.world.biome.Biome
/**
* Base class for spawn detail presets. Presets are a spawn loading mechanism that allows various properties to be
@@ -25,7 +48,7 @@ import com.google.gson.JsonObject
* to shortcut the process of commonly used conditions and other [SpawnDetail] properties as well as make those
* commonly used properties very easy to maintain.
*
* A subclass of this base must be registered using [SpawnDetailPresets.registerPresetType].
* A subclass of this base must be registered using [SpawnDetailPreset.registerPresetType].
*
* Preset loading occurs during initialization and first will load the internal presets. Then the external
* config/cobblemon/spawning/presets directory and its child directories will be searched for presets.
@@ -40,6 +63,75 @@ import com.google.gson.JsonObject
* @since July 8th, 2022
*/
abstract class SpawnDetailPreset {
companion object {
val GSON = GsonBuilder()
.setPrettyPrinting()
.setLenient()
.disableHtmlEscaping()
.registerTypeAdapter(SpawnBucket::class.java, SpawnBucketAdapter)
.registerTypeAdapter(RegisteredSpawningContext::class.java, RegisteredSpawningContextAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Biome::class.java).type, BiomeLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Block::class.java).type, BlockLikeConditionAdapter)
.registerTypeAdapter(SpawnDetailPreset::class.java, SpawnDetailPresetAdapter)
.registerTypeAdapter(Identifier::class.java, IdentifierAdapter)
.registerTypeAdapter(SpawningCondition::class.java, SpawningConditionAdapter)
.registerTypeAdapter(TimeRange::class.java, TimeRangeAdapter)
.registerTypeAdapter(PokemonProperties::class.java, pokemonPropertiesShortAdapter)
.create()
val presetTypes = mutableMapOf<String, Class<out SpawnDetailPreset>>()
fun <T : SpawnDetailPreset> registerPresetType(name: String, detailClass: Class<T>) {
presetTypes[name] = detailClass
}
fun load(): MutableMap<String, SpawnDetailPreset> {
val map = mutableMapOf<String, SpawnDetailPreset>()
map.putAll(loadInternal())
map.putAll(loadExternal())
return map
}
fun loadInternal(): MutableMap<String, SpawnDetailPreset> {
val map = mutableMapOf<String, SpawnDetailPreset>()
try {
val presets = JsonManifestWalker.load(
SpawnDetailPreset::class.java,
"spawning/presets",
GSON
)
for (template in presets) {
map[template.name] = template
}
return map
} catch (e: Exception) {
LOGGER.error("Error loading internal spawn detail presets", e)
}
return mutableMapOf()
}
fun loadExternal(): MutableMap<String, SpawnDetailPreset> {
val files = mutableListOf<File>()
val map = mutableMapOf<String, SpawnDetailPreset>()
AssetLoading.searchFor(
dir = "config/${Cobblemon.MODID}/spawning/presets",
suffix = ".json",
list = files
)
files.forEach {
try {
val reader = FileReader(it)
val preset = GSON.fromJson<SpawnDetailPreset>(reader)
reader.close()
map[preset.name] = preset
} catch (e: Exception) {
LOGGER.error("Unable to load preset from file: ${it.name}", e)
}
}
return map
}
}
var name = ""
var bucket: SpawnBucket? = null
var spawnDetailType: String? = null
var context: RegisteredSpawningContext<*>? = null
@@ -46,6 +46,7 @@ abstract class TickingSpawner(
var active = true
val spawnedEntities = mutableListOf<Entity>()
var maximumSpawned = Cobblemon.config.maximumSpawnedPokemonPerPlayer
var lastSpawnTime = 0L
var ticksUntilNextSpawn = 100F
@@ -65,7 +66,7 @@ abstract class TickingSpawner(
removalCheckTicks = 0
}
if (!active) {
if (!active || spawnedEntities.size >= maximumSpawned) {
return
}
@@ -172,7 +172,7 @@ class BattleOverlay : InGameHud(MinecraftClient.getInstance(), MinecraftClient.g
colour = Triple(r, g, b),
opacity = opacity.toFloat(),
ballState = activeBattlePokemon.ballCapturing,
trueHealth = truePokemon?.let { (it.hp * battlePokemon.hpRatio).roundToInt() to it.hp }
trueHealth = truePokemon?.let { (it.currentHealth * battlePokemon.hpRatio).roundToInt() to (it.hp * battlePokemon.hpRatio).roundToInt() }
)
}
@@ -8,10 +8,10 @@
package com.cobblemon.mod.common.client.render.models.blockbench
import com.cobblemon.mod.common.Cobblemon
import com.google.gson.GsonBuilder
import com.google.gson.annotations.SerializedName
import net.minecraft.client.model.*
class TexturedModel {
@SerializedName("format_version")
val formatVersion: String = "0"
@@ -142,11 +142,11 @@ class TexturedModel {
.setLenient()
.create()
fun from(json: String) : TexturedModel {
fun from(path: String) : TexturedModel {
try {
return GSON.fromJson(json, TexturedModel::class.java)
return GSON.fromJson(Cobblemon::class.java.getResourceAsStream("/assets/cobblemon/$path")!!.reader(), TexturedModel::class.java)
} catch (exception: Exception) {
throw IllegalStateException("Issue loading pokemon geo: $json", exception)
throw IllegalStateException("Issue loading pokemon geo: $path", exception)
}
}
}
@@ -56,7 +56,7 @@ class WaveAnimation<T : Entity>(
override fun setAngles(entity: T?, model: PoseableEntityModel<T>, state: PoseableEntityState<T>?, limbSwing: Float, limbSwingAmount: Float, ageInTicks: Float, headYaw: Float, headPitch: Float) {
val t = if (basedOnLimbSwing) {
limbSwing
limbSwing / 20F
} else {
entity?.let { model.getState(it).animationSeconds } ?: 0F
}
@@ -9,7 +9,6 @@
package com.cobblemon.mod.common.client.render.models.blockbench.repository
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.client.render.models.blockbench.TexturedModel
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.JsonPokemonPoseableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPoseableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.gen1.AbraModel
@@ -245,14 +244,13 @@ import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.gen8.Kle
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.gen8.SizzlipedeModel
import com.cobblemon.mod.common.client.render.pokemon.ModelLayer
import com.cobblemon.mod.common.client.render.pokemon.RegisteredSpeciesRendering
import com.cobblemon.mod.common.client.render.pokemon.SpeciesVariationSet
import com.cobblemon.mod.common.client.render.pokemon.SpeciesAssetResolver
import com.cobblemon.mod.common.client.util.exists
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.Species
import com.cobblemon.mod.common.pokemon.aspects.SHINY_ASPECT
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.endsWith
import com.cobblemon.mod.common.util.fromJson
import java.io.File
import java.nio.charset.StandardCharsets
import net.minecraft.client.model.ModelPart
@@ -509,8 +507,6 @@ object PokemonModelRepository : ModelRepository<PokemonEntity>() {
posers[cobblemonResource(name)] = model
}
fun registerJsonPosers(resourceManager: ResourceManager) {
resourceManager.findResources("bedrock/posers") { path -> path.endsWith(".json") }.forEach { identifier, resource ->
resource.inputStream.use { stream ->
@@ -525,47 +521,32 @@ object PokemonModelRepository : ModelRepository<PokemonEntity>() {
}
fun registerSpeciesAssetResolvers(resourceManager: ResourceManager) {
val speciesToSpeciesVariationSets = mutableMapOf<Identifier, MutableList<SpeciesVariationSet>>()
resourceManager.findResources("bedrock/species") { path -> path.endsWith(".json") }.forEach { identifier, resource ->
resource.inputStream.use { stream ->
val json = String(stream.readAllBytes(), StandardCharsets.UTF_8)
val speciesVariationSet = RegisteredSpeciesRendering.GSON.fromJson<SpeciesVariationSet>(json)
speciesToSpeciesVariationSets.getOrPut(speciesVariationSet.species) { mutableListOf() }.add(speciesVariationSet)
val resolvedIdentifier = Identifier(identifier.namespace, File(identifier.path).nameWithoutExtension)
renders[resolvedIdentifier] = RegisteredSpeciesRendering(
resolvedIdentifier,
SpeciesAssetResolver.GSON.fromJson(json, SpeciesAssetResolver::class.java)
)
}
}
for ((species, speciesVariationSets) in speciesToSpeciesVariationSets) {
val variations = speciesVariationSets.sortedBy { it.order }.flatMap { it.variations }.toMutableList()
renders[species] = RegisteredSpeciesRendering(species, variations)
}
renders.values.forEach(RegisteredSpeciesRendering::initialize)
}
override fun registerAll() {
}
val texturedModels = mutableMapOf<Identifier, TexturedModel>()
fun registerModels(resourceManager: ResourceManager) {
resourceManager.findResources("bedrock/models") { path -> path.endsWith(".geo.json") }.forEach { identifier, resource ->
resource.inputStream.use { stream ->
val json = String(stream.readAllBytes(), StandardCharsets.UTF_8)
val resolvedIdentifier = Identifier(identifier.namespace, File(identifier.path).nameWithoutExtension)
texturedModels[resolvedIdentifier] = TexturedModel.from(json)
}
}
}
override fun reload(resourceManager: ResourceManager) {
Cobblemon.LOGGER.info("Initializing Pokémon models")
this.renders.clear()
this.posers.clear()
registerPosers(resourceManager)
registerModels(resourceManager)
registerSpeciesAssetResolvers(resourceManager)
initializeModelLayers()
}
var brokenOnes = mutableListOf<Species>()
fun getPoser(species: Species, aspects: Set<String>): PokemonPoseableModel {
try {
val poser = this.renders[species.resourceIdentifier]?.getPoser(aspects)
@@ -8,13 +8,14 @@
package com.cobblemon.mod.common.client.render.pokemon
import com.cobblemon.mod.common.api.pokemon.aspect.AspectProvider
import com.cobblemon.mod.common.client.render.models.blockbench.TexturedModel
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPoseableModel
import com.cobblemon.mod.common.client.render.models.blockbench.repository.PokemonModelRepository
import com.cobblemon.mod.common.pokemon.Species
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.adapters.Vec3fAdapter
import com.cobblemon.mod.common.util.adapters.Vector4fAdapter
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.GsonBuilder
import net.minecraft.client.model.ModelPart
import net.minecraft.util.Identifier
@@ -29,30 +30,74 @@ import net.minecraft.util.math.Vector4f
*/
class RegisteredSpeciesRendering(
val species: Identifier,
val variations: MutableList<ModelAssetVariation>
private val assetResolver: SpeciesAssetResolver
) {
fun getResolvedPoser(aspects: Set<String>): Identifier {
return getVariationValue(aspects) { poser }
?: throw IllegalStateException("Unable to find a poser for $species with aspects ${aspects.joinToString()}. This shouldn't be possible if you've defined the fallback variation.")
val posers = mutableMapOf<Pair<Identifier, Identifier>, PokemonPoseableModel>()
val models = mutableMapOf<Identifier, ModelPart>()
init {
posers.clear()
assetResolver.getAllModels().forEach { identifier ->
models[identifier] = TexturedModel.from(identifier.path).create().createModel()
}
}
fun getResolvedModel(aspects: Set<String>): Identifier {
return getVariationValue(aspects) { model }
?: throw IllegalStateException("Unable to find a model for $species with aspects ${aspects.joinToString()}. This shouldn't be possible if you've defined the fallback variation.")
fun getPoser(aspects: Set<String>): PokemonPoseableModel {
val poserName = assetResolver.getPoser(aspects)
val poserSupplier = PokemonModelRepository.posers[poserName] ?: throw IllegalStateException("No poser found for name: $poserName")
val modelName = assetResolver.getModel(aspects)
val existingEntityModel = posers[poserName to modelName]
return if (existingEntityModel != null) {
existingEntityModel
} else {
val entityModel = poserSupplier(models[modelName]!!)
entityModel.registerPoses()
posers[poserName to modelName] = entityModel
entityModel
}
}
fun getResolvedTexture(aspects: Set<String>): Identifier {
return getVariationValue(aspects) { texture }
?: throw IllegalStateException("Unable to find a texture for $species with aspects ${aspects.joinToString()}. This shouldn't be possible if you've defined the fallback variation.")
fun getTexture(aspects: Set<String>): Identifier {
PokemonModelRepository.posers[assetResolver.getPoser(aspects)] ?: throw IllegalStateException("No poser for $species")
return assetResolver.getTexture(aspects)
}
private fun <T> getVariationValue(aspects: Set<String>, selector: ModelAssetVariation.() -> T?): T? {
return variations.lastOrNull { it.aspects.all { it in aspects } && selector(it) != null }?.let(selector)
fun getLayers(aspects: Set<String>): List<ModelLayer> {
PokemonModelRepository.posers[assetResolver.getPoser(aspects)] ?: throw IllegalStateException("No poser for $species")
return assetResolver.getLayers(aspects)
}
}
/**
* A resolver of species assets. This takes a set of assets supplied by the registered [AspectProvider]s and
* is used to produce the most appropriate animator class, model, and texture by searching for the last matching
* [ModelAssetVariation]. It will fall back to the top-level properties if none match the aspect conditions.
*
* @author Hiroku
* @since May 14th, 2022
*/
class SpeciesAssetResolver {
val poser = Identifier("")
val model = Identifier("")
val texture = Identifier("")
val layers: List<ModelLayer>? = null
val variations = mutableListOf<ModelAssetVariation>()
fun getPoser(aspects: Set<String>): Identifier {
return variations.lastOrNull { it.aspects.all { it in aspects } && it.poser != null }?.poser ?: poser
}
fun getResolvedLayers(aspects: Set<String>): List<ModelLayer> {
fun getModel(aspects: Set<String>): Identifier {
return variations.lastOrNull { it.aspects.all { it in aspects } && it.model != null }?.model ?: model
}
fun getTexture(aspects: Set<String>): Identifier {
return variations.lastOrNull { it.aspects.all { it in aspects } && it.texture != null }?.texture ?: texture
}
fun getLayers(aspects: Set<String>): List<ModelLayer> {
val allLayers = mutableListOf<ModelLayer>()
variations[0].layers?.let(allLayers::addAll)
layers?.let(allLayers::addAll)
val variationLayers = variations.lastOrNull { it.aspects.all { it in aspects } && it.layers != null }?.layers ?: emptyList()
variationLayers.forEach { layer ->
@@ -67,6 +112,7 @@ class RegisteredSpeciesRendering(
fun getAllModels(): Set<Identifier> {
val models = mutableSetOf<Identifier>()
models.add(model)
for (variation in variations) {
if (variation.model != null) {
models.add(variation.model)
@@ -85,59 +131,10 @@ class RegisteredSpeciesRendering(
.setLenient()
.create()
}
val posers = mutableMapOf<Pair<Identifier, Identifier>, PokemonPoseableModel>()
val models = mutableMapOf<Identifier, ModelPart>()
fun initialize() {
posers.clear()
getAllModels().forEach { identifier ->
models[identifier] = PokemonModelRepository.texturedModels[identifier]!!.create().createModel()
}
}
fun getPoser(aspects: Set<String>): PokemonPoseableModel {
val poserName = getResolvedPoser(aspects)
val poserSupplier = PokemonModelRepository.posers[poserName] ?: throw IllegalStateException("No poser found for name: $poserName")
val modelName = getResolvedModel(aspects)
val existingEntityModel = posers[poserName to modelName]
return if (existingEntityModel != null) {
existingEntityModel
} else {
val entityModel = poserSupplier(models[modelName]!!)
entityModel.registerPoses()
posers[poserName to modelName] = entityModel
entityModel
}
}
fun getTexture(aspects: Set<String>): Identifier {
PokemonModelRepository.posers[getResolvedPoser(aspects)] ?: throw IllegalStateException("No poser for $species")
return getResolvedTexture(aspects)
}
fun getLayers(aspects: Set<String>): List<ModelLayer> {
PokemonModelRepository.posers[getResolvedPoser(aspects)] ?: throw IllegalStateException("No poser for $species")
return getResolvedLayers(aspects)
}
}
/**
* A set of species variations. This is essentially a prioritized list of [ModelAssetVariation]s for a species, with
* an [order] property to control the priority of this set compared to other sets.
*
* @author Hiroku
* @since December 4th, 2022
*/
class SpeciesVariationSet(
val species: Identifier = cobblemonResource("pokemon"),
val order: Int = 0,
val variations: MutableList<ModelAssetVariation> = mutableListOf()
)
/**
* A variation to the base species, which can overwrite the poser, model, texture, or any combination of the above.
* A variation to the base species, which can overwrite the animator, model, texture, or any combination.
* It contains a set of aspects that must ALL be present on a renderable for this variation to be considered.
* If a later variation also matches, but provides different properties, both this and the other variation will
* be used for their respective non-null properties.
@@ -145,14 +142,13 @@ class SpeciesVariationSet(
* @author Hiroku
* @since May 14th, 2022
*/
class ModelAssetVariation(
val aspects: MutableSet<String> = mutableSetOf(),
val poser: Identifier? = null,
val model: Identifier? = null,
val texture: Identifier? = null,
class ModelAssetVariation {
val aspects = mutableSetOf<String>()
val poser: Identifier? = null
val model: Identifier? = null
val texture: Identifier? = null
val layers: List<ModelLayer>? = null
)
}
class ModelLayer {
val name: String = ""
val scale: Vec3f = Vec3f(1F, 1F, 1F)
@@ -39,18 +39,14 @@ object SpawnPokemon {
private fun execute(context: CommandContext<ServerCommandSource>) : Int {
val entity = context.source.entity
if (entity is ServerPlayerEntity && !entity.world.isClient) {
try {
val pkm = PokemonPropertiesArgumentType.getPokemonProperties(context, PROPERTIES)
if (pkm.species == null) {
entity.sendMessage(commandLang("${NAME}.nospecies").red())
return Command.SINGLE_SUCCESS
}
val pokemonEntity = pkm.createEntity(entity.world)
entity.world.spawnEntity(pokemonEntity)
pokemonEntity.setPosition(entity.pos)
} catch (e: Exception) {
e.printStackTrace()
val pkm = PokemonPropertiesArgumentType.getPokemonProperties(context, PROPERTIES)
if (pkm.species == null) {
entity.sendMessage(commandLang("${NAME}.nospecies").red())
return Command.SINGLE_SUCCESS
}
val pokemonEntity = pkm.createEntity(entity.world)
entity.world.spawnEntity(pokemonEntity)
pokemonEntity.setPosition(entity.pos)
}
return Command.SINGLE_SUCCESS
}
@@ -90,7 +90,13 @@ class CobblemonConfig {
var maximumSliceDistanceFromPlayer = 16 * 4F
@NodeCategory(Category.Spawning)
var exportSpawnConfig = false
var maximumSpawnedPokemonPerPlayer = 30
@NodeCategory(Category.Spawning)
var exportSpawnsToConfig = false
@NodeCategory(Category.Spawning)
var exportSpawnConfigToConfig = false
@NodeCategory(Category.Battles)
var autoUpdateShowdown = true
@@ -138,6 +144,12 @@ class CobblemonConfig {
@NodeCategory(Category.Spawning)
var baseApricornTreeGenerationChance = 0.1F
@NodeCategory(Category.Pokemon)
var globalFlagSpeciesFeatures = mutableListOf<String>()
@NodeCategory(Category.Pokemon)
var flagSpeciesFeatures = mutableListOf("sunglasses")
@NodeCategory(Category.Pokemon)
var ninjaskCreatesShedinja = true
@@ -16,11 +16,6 @@ import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.moves.Moves
import com.cobblemon.mod.common.api.pokeball.PokeBalls
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.pokemon.feature.GlobalSpeciesFeatures
import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeatureAssignments
import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeatures
import com.cobblemon.mod.common.api.spawning.CobblemonSpawnPools
import com.cobblemon.mod.common.api.spawning.SpawnDetailPresets
import com.cobblemon.mod.common.pokemon.properties.PropertiesCompletionProvider
import dev.architectury.registry.ReloadListenerRegistry
import java.util.UUID
@@ -29,7 +24,7 @@ import net.minecraft.resource.SynchronousResourceReloader
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.Identifier
object CobblemonDataProvider : DataProvider {
internal object CobblemonDataProvider : DataProvider {
// Both Forge n Fabric keep insertion order so if a registry depends on another simply register it after
var canReload = true
@@ -43,18 +38,12 @@ object CobblemonDataProvider : DataProvider {
this.register(Abilities)
this.register(PokemonSpecies)
this.register(PokeBalls)
this.register(SpeciesFeatures)
this.register(GlobalSpeciesFeatures)
this.register(PropertiesCompletionProvider)
this.register(SpeciesFeatureAssignments)
this.register(SpawnDetailPresets)
CobblemonSpawnPools.load()
CobblemonEvents.PLAYER_QUIT.subscribe { synchronizedPlayerIds.remove(it.uuid) }
}
override fun <T : DataRegistry> register(registry: T): T {
override fun register(registry: DataRegistry) {
// Only send message once
if (this.registries.isEmpty()) {
LOGGER.info("Note: Cobblemon data registries are only loaded once per server instance as Pokémon species are not safe to reload.")
@@ -63,7 +52,6 @@ object CobblemonDataProvider : DataProvider {
this.registries.add(registry)
LOGGER.info("Registered the {} registry", registry.id.toString())
LOGGER.debug("Registered the {} registry of class {}", registry.id.toString(), registry::class.qualifiedName)
return registry
}
override fun fromIdentifier(registryIdentifier: Identifier): DataRegistry? = this.registries.find { it.id == registryIdentifier }
@@ -84,8 +84,6 @@ class FormData(
private var _dynamaxBlocked: Boolean? = null,
@SerializedName("eggGroups")
private val _eggGroups: Set<EggGroup>? = null,
@SerializedName("eggCycles")
private val _eggCycles: Int? = null,
@SerializedName("height")
private var _height: Float? = null,
@SerializedName("weight")
@@ -158,9 +156,6 @@ class FormData(
val eggGroups: Set<EggGroup>
get() = _eggGroups ?: species.eggGroups
val eggCycles: Int
get() = _eggCycles ?: species.eggCycles
/**
* The height in decimeters
*/
@@ -41,7 +41,6 @@ import com.cobblemon.mod.common.api.pokemon.evolution.PreEvolution
import com.cobblemon.mod.common.api.pokemon.experience.ExperienceGroup
import com.cobblemon.mod.common.api.pokemon.experience.ExperienceSource
import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeature
import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeatures
import com.cobblemon.mod.common.api.pokemon.friendship.FriendshipMutationCalculator
import com.cobblemon.mod.common.api.pokemon.labels.CobblemonPokemonLabels
import com.cobblemon.mod.common.api.pokemon.moves.LearnsetQuery
@@ -82,7 +81,8 @@ import com.cobblemon.mod.common.pokemon.activestate.PokemonState
import com.cobblemon.mod.common.pokemon.activestate.SentOutState
import com.cobblemon.mod.common.pokemon.evolution.CobblemonEvolutionProxy
import com.cobblemon.mod.common.pokemon.feature.DamageTakenFeature
import com.cobblemon.mod.common.pokemon.feature.SeasonFeatureHandler
import com.cobblemon.mod.common.pokemon.feature.SEASON
import com.cobblemon.mod.common.pokemon.feature.SeasonFeature
import com.cobblemon.mod.common.pokemon.status.PersistentStatus
import com.cobblemon.mod.common.pokemon.status.PersistentStatusContainer
import com.cobblemon.mod.common.util.DataKeys
@@ -125,10 +125,13 @@ open class Pokemon {
throw IllegalArgumentException("Cannot set a species that isn't registered")
}
val quotient = clamp(currentHealth / hp.toFloat(), 0F, 1F)
val previousFeatureKeys = features.map { it.name }.toSet()
field = value
val newFeatures = SpeciesFeatures.getFeaturesFor(species).mapNotNull { it.invoke(this) }
features.clear()
features.addAll(newFeatures)
val newFeatureKeys = species.features + Cobblemon.config.globalFlagSpeciesFeatures + SpeciesFeature.globalFeatures().keys
val addedFeatures = newFeatureKeys - previousFeatureKeys
val removedFeatures = previousFeatureKeys - newFeatureKeys
features.addAll(addedFeatures.mapNotNull { SpeciesFeature.get(it)?.invoke() })
features.removeAll { it.name in removedFeatures }
this.evolutionProxy.current().clear()
updateAspects()
updateForm()
@@ -361,7 +364,7 @@ open class Pokemon {
open fun getStat(stat: Stat) = Cobblemon.statProvider.getStatForPokemon(this, stat)
fun sendOut(level: ServerWorld, position: Vec3d, mutation: (PokemonEntity) -> Unit = {}): PokemonEntity {
SeasonFeatureHandler.updateSeason(this, level, position.toBlockPos())
getFeature<SeasonFeature>(SEASON)?.update(this, level, position.toBlockPos())
val entity = PokemonEntity(level, this)
entity.setPositionSafely(position)
mutation(entity)
@@ -137,7 +137,6 @@ class Species : ClientDataSynchronizer<Species> {
override fun encode(buffer: PacketByteBuf) {
buffer.writeIdentifier(this.resourceIdentifier)
buffer.writeBoolean(this.implemented)
buffer.writeString(this.name)
buffer.writeInt(this.nationalPokedexNumber)
buffer.writeMap(this.baseStats,
@@ -160,7 +159,6 @@ class Species : ClientDataSynchronizer<Species> {
}
override fun decode(buffer: PacketByteBuf) {
this.implemented = buffer.readBoolean()
// identifier is decoded in the sync packet for easier debug log
this.name = buffer.readString()
this.nationalPokedexNumber = buffer.readInt()
@@ -0,0 +1,27 @@
/*
* Copyright (C) 2022 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.pokemon.aspects
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.aspect.AspectProvider
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.feature.FISH_STRIPES
import com.cobblemon.mod.common.pokemon.feature.FishStripesFeature
object FishStripesAspect : AspectProvider {
fun provideForFeature(feature: FishStripesFeature) = setOf("${feature.enumValue.name.lowercase()}striped")
override fun provide(pokemon: Pokemon): Set<String> {
return provideForFeature(pokemon.getFeature(FISH_STRIPES) ?: return emptySet())
}
override fun provide(properties: PokemonProperties): Set<String> {
return provideForFeature(properties.customProperties.filterIsInstance<FishStripesFeature>().firstOrNull() ?: return emptySet())
}
}
@@ -0,0 +1,29 @@
/*
* Copyright (C) 2022 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.pokemon.aspects
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.aspect.AspectProvider
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.feature.SEASON
import com.cobblemon.mod.common.pokemon.feature.SNAKE_PATTERN
import com.cobblemon.mod.common.pokemon.feature.SeasonFeature
import com.cobblemon.mod.common.pokemon.feature.SnakePatternFeature
object SeasonAspect : AspectProvider {
fun provideForFeature(feature: SeasonFeature) = setOf(feature.enumValue.name.lowercase())
override fun provide(pokemon: Pokemon): Set<String> {
return provideForFeature(pokemon.getFeature(SEASON) ?: return emptySet())
}
override fun provide(properties: PokemonProperties): Set<String> {
return provideForFeature(properties.customProperties.filterIsInstance<SeasonFeature>().firstOrNull() ?: return emptySet())
}
}
@@ -0,0 +1,27 @@
/*
* Copyright (C) 2022 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.pokemon.aspects
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.aspect.AspectProvider
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.feature.SNAKE_PATTERN
import com.cobblemon.mod.common.pokemon.feature.SnakePatternFeature
object SnakePatternAspect : AspectProvider {
fun provideForFeature(feature: SnakePatternFeature) = setOf("$SNAKE_PATTERN-${feature.enumValue.name.lowercase()}")
override fun provide(pokemon: Pokemon): Set<String> {
return provideForFeature(pokemon.getFeature(SNAKE_PATTERN) ?: return emptySet())
}
override fun provide(properties: PokemonProperties): Set<String> {
return provideForFeature(properties.customProperties.filterIsInstance<SnakePatternFeature>().firstOrNull() ?: return emptySet())
}
}
@@ -62,10 +62,10 @@ class SlowFallEffect : ShoulderEffect {
override fun update(entity: LivingEntity, overwriteCallback: Runnable?): Boolean {
entity as ServerPlayerEntity
duration = if (isShoulderedPokemon(entity.shoulderEntityLeft) || isShoulderedPokemon(entity.shoulderEntityRight)) {
10
if (isShoulderedPokemon(entity.shoulderEntityLeft) || isShoulderedPokemon(entity.shoulderEntityRight)) {
duration = 10
} else {
0
duration = 0
}
return super.update(entity, overwriteCallback)
}
@@ -0,0 +1,31 @@
/*
* Copyright (C) 2022 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.pokemon.feature
import com.cobblemon.mod.common.pokemon.feature.FishStripes.Companion.ALL_VALUES
import com.cobblemon.mod.common.api.pokemon.feature.EnumSpeciesFeature
import java.util.EnumSet
/**
* Fish stripes, Basculin.
*/
enum class FishStripes {
BLUE,
RED;
companion object {
val ALL_VALUES = EnumSet.allOf(FishStripes::class.java)
}
}
const val FISH_STRIPES = "striped"
class FishStripesFeature : EnumSpeciesFeature<FishStripes>() {
override val name: String = FISH_STRIPES
override fun getValues() = ALL_VALUES
}
@@ -10,7 +10,7 @@ package com.cobblemon.mod.common.pokemon.feature
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.SeasonResolver
import com.cobblemon.mod.common.api.pokemon.feature.StringSpeciesFeature
import com.cobblemon.mod.common.api.pokemon.feature.EnumSpeciesFeature
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.tags.CobblemonBiomeTags
import java.util.EnumSet
@@ -18,7 +18,7 @@ import net.minecraft.util.math.BlockPos
import net.minecraft.world.WorldAccess
/**
* A season. You know the ones.
* A season variation of a Pokémon.
*
* @author Hiroku
* @since November 25th, 2022
@@ -35,16 +35,17 @@ enum class CobblemonSeason {
}
const val SEASON = "season"
class SeasonFeature : EnumSpeciesFeature<CobblemonSeason>() {
override val name: String = SEASON
override fun getValues() = CobblemonSeason.ALL_VALUES
object SeasonFeatureHandler {
fun updateSeason(pokemon: Pokemon, world: WorldAccess, pos: BlockPos) {
val feature = pokemon.getFeature<StringSpeciesFeature>(SEASON) ?: return
val currentSeason = feature.value
val newSeason = Cobblemon.seasonResolver(world, pos)?.name?.lowercase()
fun update(pokemon: Pokemon, world: WorldAccess, pos: BlockPos) {
val currentSeason = enumValue
val newSeason = Cobblemon.seasonResolver(world, pos)
if (currentSeason != newSeason && newSeason != null) {
feature.value = newSeason
enumValue = newSeason
pokemon.updateAspects()
pokemon.markFeatureDirty(feature)
pokemon.markFeatureDirty(this)
}
}
}
@@ -0,0 +1,36 @@
/*
* Copyright (C) 2022 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.pokemon.feature
import com.cobblemon.mod.common.api.pokemon.feature.EnumSpeciesFeature
import java.util.EnumSet
/**
* A pattern on a snake Pokémon, typically Arbok.
*/
enum class SnakePattern {
CLASSIC,
LEGACY,
ATTACK,
DARK,
ELUSIVE,
HEART,
SPEED,
SOUND;
companion object {
val ALL_VALUES = EnumSet.allOf(SnakePattern::class.java)
}
}
const val SNAKE_PATTERN = "snake-pattern"
class SnakePatternFeature : EnumSpeciesFeature<SnakePattern>() {
override val name: String = SNAKE_PATTERN
override fun getValues() = SnakePattern.ALL_VALUES
}
@@ -10,8 +10,6 @@ package com.cobblemon.mod.common.pokemon.properties
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.data.DataRegistry
import com.cobblemon.mod.common.api.pokeball.PokeBalls
import com.cobblemon.mod.common.api.pokemon.Natures
import com.cobblemon.mod.common.api.properties.CustomPokemonProperty
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.net.messages.client.data.PropertiesCompletionRegistrySyncPacket
@@ -127,8 +125,6 @@ internal object PropertiesCompletionProvider : DataRegistry {
this.inject(setOf("shiny", "s"), setOf("yes", "no"))
this.inject(setOf("gender"), Gender.values().map { it.name.lowercase() })
this.inject(setOf("friendship"), setOf("0", Cobblemon.config.maxPokemonLevel.toString()))
this.inject(setOf("pokeball"), PokeBalls.all().map { if (it.name.namespace == Cobblemon.MODID) it.name.path else it.name.toString() })
this.inject(setOf("nature"), Natures.all().map { if (it.name.namespace == Cobblemon.MODID) it.name.path else it.name.toString() })
}
private fun addCustom() {
@@ -10,13 +10,11 @@ package com.cobblemon.mod.common.util.adapters
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.api.spawning.SpawnDetailPresets
import com.cobblemon.mod.common.api.spawning.SpawnLoader
import com.cobblemon.mod.common.api.spawning.condition.SpawningCondition
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.preset.SpawnDetailPreset
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.singularToPluralList
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
@@ -38,7 +36,7 @@ object SpawnDetailAdapter : JsonDeserializer<SpawnDetail> {
element.singularToPluralList("preset")
val presetNames = element.get("presets")?.asJsonArray?.map { it.asString }?.toMutableSet() ?: mutableSetOf()
val presets = presetNames.mapNotNull {
val preset = SpawnDetailPresets.presets[it.asIdentifierDefaultingNamespace()]
val preset = Cobblemon.bestSpawner.presets[it]
if (preset == null) {
LOGGER.error("Unknown preset name: $it.")
}
@@ -8,7 +8,6 @@
package com.cobblemon.mod.common.util.adapters
import com.cobblemon.mod.common.api.spawning.SpawnDetailPresets
import com.cobblemon.mod.common.api.spawning.preset.BasicSpawnDetailPreset
import com.cobblemon.mod.common.api.spawning.preset.SpawnDetailPreset
import com.google.gson.JsonDeserializationContext
@@ -27,7 +26,7 @@ object SpawnDetailPresetAdapter : JsonDeserializer<SpawnDetailPreset> {
override fun deserialize(json: JsonElement, type: Type, ctx: JsonDeserializationContext): SpawnDetailPreset {
json as JsonObject
val type = json.get("type")?.asString ?: BasicSpawnDetailPreset.NAME
val clazz = SpawnDetailPresets.presetTypes[type] ?: throw IllegalStateException("Unrecognized preset type: $type")
val clazz = SpawnDetailPreset.presetTypes[type] ?: throw IllegalStateException("Unrecognized preset type: $type")
return ctx.deserialize(json, clazz)
}
}
@@ -1,31 +0,0 @@
/*
* Copyright (C) 2022 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.api.pokemon.feature.SpeciesFeatureProvider
import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeatures
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import java.lang.reflect.Type
/**
* Adapter for [SpeciesFeatureProvider]s. This will rely on a 'type' property, which will be used as a lookup in
* [SpeciesFeatures.types].
*
* @author Hiroku
* @since November 30th, 2022
*/
object SpeciesFeatureProviderAdapter : JsonDeserializer<SpeciesFeatureProvider<*>> {
override fun deserialize(json: JsonElement, type: Type, ctx: JsonDeserializationContext): SpeciesFeatureProvider<*> {
val typeName = json.asJsonObject.get("type").asString
val clazz = SpeciesFeatures.types[typeName] ?: throw IllegalArgumentException("No type registered in SpeciesFeatures for name: $typeName")
return ctx.deserialize(json, clazz)
}
}
@@ -12,23 +12,12 @@ import com.google.gson.JsonArray
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.JsonSerializationContext
import com.google.gson.JsonSerializer
import java.lang.reflect.Type
import net.minecraft.util.math.Vec3f
object Vec3fAdapter : JsonDeserializer<Vec3f>, JsonSerializer<Vec3f> {
object Vec3fAdapter : JsonDeserializer<Vec3f> {
override fun deserialize(json: JsonElement, type: Type, ctx: JsonDeserializationContext): Vec3f {
json as JsonArray
return Vec3f(json[0].asFloat, json[1].asFloat, json[2].asFloat)
}
override fun serialize(src: Vec3f, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement {
return JsonArray()
.also {
it.add(src.x)
it.add(src.y)
it.add(src.z)
}
}
}
@@ -12,24 +12,12 @@ import com.google.gson.JsonArray
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.JsonSerializationContext
import com.google.gson.JsonSerializer
import java.lang.reflect.Type
import net.minecraft.util.math.Vector4f
object Vector4fAdapter : JsonDeserializer<Vector4f>, JsonSerializer<Vector4f> {
object Vector4fAdapter : JsonDeserializer<Vector4f> {
override fun deserialize(json: JsonElement, type: Type, ctx: JsonDeserializationContext): Vector4f {
json as JsonArray
return Vector4f(json[0].asFloat, json[1].asFloat, json[2].asFloat, json[3].asFloat)
}
override fun serialize(src: Vector4f, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement {
return JsonArray()
.also {
it.add(src.x)
it.add(src.y)
it.add(src.z)
it.add(src.w)
}
}
}
@@ -1,19 +0,0 @@
{
"species": "cobblemon:substitute",
"order": 0,
"variations": [
{
"aspects": [],
"poser": "cobblemon:substitute",
"model": "cobblemon:substitute.geo",
"texture": "cobblemon:textures/pokemon/substitute.png",
"layers": []
},
{
"aspects": [
"shiny"
],
"texture": "cobblemon:textures/pokemon/substitute_shiny.png"
}
]
}
@@ -1,19 +0,0 @@
{
"species": "cobblemon:bulbasaur",
"order": 0,
"variations": [
{
"aspects": [],
"poser": "cobblemon:bulbasaur",
"model": "cobblemon:bulbasaur.geo",
"texture": "cobblemon:textures/pokemon/0001_bulbasaur/bulbasaur.png",
"layers": []
},
{
"aspects": [
"shiny"
],
"texture": "cobblemon:textures/pokemon/0001_bulbasaur/bulbasaur_shiny.png"
}
]
}
@@ -1,19 +0,0 @@
{
"species": "cobblemon:ivysaur",
"order": 0,
"variations": [
{
"aspects": [],
"poser": "cobblemon:ivysaur",
"model": "cobblemon:ivysaur.geo",
"texture": "cobblemon:textures/pokemon/0002_ivysaur/ivysaur.png",
"layers": []
},
{
"aspects": [
"shiny"
],
"texture": "cobblemon:textures/pokemon/0002_ivysaur/ivysaur_shiny.png"
}
]
}
@@ -1,19 +0,0 @@
{
"species": "cobblemon:venusaur",
"order": 0,
"variations": [
{
"aspects": [],
"poser": "cobblemon:venusaur",
"model": "cobblemon:venusaur_male.geo",
"texture": "cobblemon:textures/pokemon/0003_venusaur/venusaur.png",
"layers": []
},
{
"aspects": [
"shiny"
],
"texture": "cobblemon:textures/pokemon/0003_venusaur/venusaur_shiny.png"
}
]
}
@@ -1,12 +0,0 @@
{
"species": "cobblemon:venusaur",
"order": 1,
"variations": [
{
"aspects": [
"female"
],
"model": "cobblemon:venusaur_female.geo"
}
]
}
@@ -1,19 +0,0 @@
{
"species": "cobblemon:charmander",
"order": 0,
"variations": [
{
"aspects": [],
"poser": "cobblemon:charmander",
"model": "cobblemon:charmander.geo",
"texture": "cobblemon:textures/pokemon/0004_charmander/charmander.png",
"layers": []
},
{
"aspects": [
"shiny"
],
"texture": "cobblemon:textures/pokemon/0004_charmander/charmander_shiny.png"
}
]
}
@@ -1,19 +0,0 @@
{
"species": "cobblemon:charmeleon",
"order": 0,
"variations": [
{
"aspects": [],
"poser": "cobblemon:charmeleon",
"model": "cobblemon:charmeleon.geo",
"texture": "cobblemon:textures/pokemon/0005_charmeleon/charmeleon.png",
"layers": []
},
{
"aspects": [
"shiny"
],
"texture": "cobblemon:textures/pokemon/0005_charmeleon/charmeleon_shiny.png"
}
]
}
@@ -1,19 +0,0 @@
{
"species": "cobblemon:charizard",
"order": 0,
"variations": [
{
"aspects": [],
"poser": "cobblemon:charizard",
"model": "cobblemon:charizard.geo",
"texture": "cobblemon:textures/pokemon/0006_charizard/charizard.png",
"layers": []
},
{
"aspects": [
"shiny"
],
"texture": "cobblemon:textures/pokemon/0006_charizard/charizard_shiny.png"
}
]
}
@@ -1,19 +0,0 @@
{
"species": "cobblemon:squirtle",
"order": 0,
"variations": [
{
"aspects": [],
"poser": "cobblemon:squirtle",
"model": "cobblemon:squirtle.geo",
"texture": "cobblemon:textures/pokemon/0007_squirtle/squirtle.png",
"layers": []
},
{
"aspects": [
"shiny"
],
"texture": "cobblemon:textures/pokemon/0007_squirtle/squirtle_shiny.png"
}
]
}
@@ -1,19 +0,0 @@
{
"species": "cobblemon:wartortle",
"order": 0,
"variations": [
{
"aspects": [],
"poser": "cobblemon:wartortle",
"model": "cobblemon:wartortle.geo",
"texture": "cobblemon:textures/pokemon/0008_wartortle/wartortle.png",
"layers": []
},
{
"aspects": [
"shiny"
],
"texture": "cobblemon:textures/pokemon/0008_wartortle/wartortle_shiny.png"
}
]
}
@@ -1,19 +0,0 @@
{
"species": "cobblemon:blastoise",
"order": 0,
"variations": [
{
"aspects": [],
"poser": "cobblemon:blastoise",
"model": "cobblemon:blastoise.geo",
"texture": "cobblemon:textures/pokemon/0009_blastoise/blastoise.png",
"layers": []
},
{
"aspects": [
"shiny"
],
"texture": "cobblemon:textures/pokemon/0009_blastoise/blastoise_shiny.png"
}
]
}
@@ -1,19 +0,0 @@
{
"species": "cobblemon:caterpie",
"order": 0,
"variations": [
{
"aspects": [],
"poser": "cobblemon:caterpie",
"model": "cobblemon:caterpie.geo",
"texture": "cobblemon:textures/pokemon/0010_caterpie/caterpie.png",
"layers": []
},
{
"aspects": [
"shiny"
],
"texture": "cobblemon:textures/pokemon/0010_caterpie/caterpie_shiny.png"
}
]
}
@@ -1,19 +0,0 @@
{
"species": "cobblemon:metapod",
"order": 0,
"variations": [
{
"aspects": [],
"poser": "cobblemon:metapod",
"model": "cobblemon:metapod.geo",
"texture": "cobblemon:textures/pokemon/0011_metapod/metapod.png",
"layers": []
},
{
"aspects": [
"shiny"
],
"texture": "cobblemon:textures/pokemon/0011_metapod/metapod_shiny.png"
}
]
}
@@ -1,19 +0,0 @@
{
"species": "cobblemon:butterfree",
"order": 0,
"variations": [
{
"aspects": [],
"poser": "cobblemon:butterfree",
"model": "cobblemon:butterfree_male.geo",
"texture": "cobblemon:textures/pokemon/0012_butterfree/butterfree.png",
"layers": []
},
{
"aspects": [
"shiny"
],
"texture": "cobblemon:textures/pokemon/0012_butterfree/butterfree_shiny.png"
}
]
}
@@ -1,12 +0,0 @@
{
"species": "cobblemon:butterfree",
"order": 1,
"variations": [
{
"aspects": [
"female"
],
"model": "cobblemon:butterfree_female.geo"
}
]
}
@@ -1,19 +0,0 @@
{
"species": "cobblemon:butterfree",
"order": 2,
"variations": [
{
"aspects": [
"valencian"
],
"texture": "cobblemon:textures/pokemon/0012_butterfree/butterfree_valencian.png"
},
{
"aspects": [
"valencian",
"shiny"
],
"texture": "cobblemon:textures/pokemon/0012_butterfree/butterfree_valencian_shiny.png"
}
]
}
@@ -1,19 +0,0 @@
{
"species": "cobblemon:weedle",
"order": 0,
"variations": [
{
"aspects": [],
"poser": "cobblemon:weedle",
"model": "cobblemon:weedle.geo",
"texture": "cobblemon:textures/pokemon/0013_weedle/weedle.png",
"layers": []
},
{
"aspects": [
"shiny"
],
"texture": "cobblemon:textures/pokemon/0013_weedle/weedle_shiny.png"
}
]
}
@@ -1,19 +0,0 @@
{
"species": "cobblemon:kakuna",
"order": 0,
"variations": [
{
"aspects": [],
"poser": "cobblemon:kakuna",
"model": "cobblemon:kakuna.geo",
"texture": "cobblemon:textures/pokemon/0014_kakuna/kakuna.png",
"layers": []
},
{
"aspects": [
"shiny"
],
"texture": "cobblemon:textures/pokemon/0014_kakuna/kakuna_shiny.png"
}
]
}
@@ -1,19 +0,0 @@
{
"species": "cobblemon:beedrill",
"order": 0,
"variations": [
{
"aspects": [],
"poser": "cobblemon:beedrill",
"model": "cobblemon:beedrill.geo",
"texture": "cobblemon:textures/pokemon/0015_beedrill/beedrill.png",
"layers": []
},
{
"aspects": [
"shiny"
],
"texture": "cobblemon:textures/pokemon/0015_beedrill/beedrill_shiny.png"
}
]
}
@@ -1,19 +0,0 @@
{
"species": "cobblemon:pidgey",
"order": 0,
"variations": [
{
"aspects": [],
"poser": "cobblemon:pidgey",
"model": "cobblemon:pidgey.geo",
"texture": "cobblemon:textures/pokemon/0016_pidgey/pidgey.png",
"layers": []
},
{
"aspects": [
"shiny"
],
"texture": "cobblemon:textures/pokemon/0016_pidgey/pidgey_shiny.png"
}
]
}
@@ -1,19 +0,0 @@
{
"species": "cobblemon:pidgeotto",
"order": 0,
"variations": [
{
"aspects": [],
"poser": "cobblemon:pidgeotto",
"model": "cobblemon:pidgeotto.geo",
"texture": "cobblemon:textures/pokemon/0017_pidgeotto/pidgeotto.png",
"layers": []
},
{
"aspects": [
"shiny"
],
"texture": "cobblemon:textures/pokemon/0017_pidgeotto/pidgeotto_shiny.png"
}
]
}
@@ -1,19 +0,0 @@
{
"species": "cobblemon:pidgeot",
"order": 0,
"variations": [
{
"aspects": [],
"poser": "cobblemon:pidgeot",
"model": "cobblemon:pidgeot.geo",
"texture": "cobblemon:textures/pokemon/0018_pidgeot/pidgeot.png",
"layers": []
},
{
"aspects": [
"shiny"
],
"texture": "cobblemon:textures/pokemon/0018_pidgeot/pidgeot_shiny.png"
}
]
}
@@ -1,19 +0,0 @@
{
"species": "cobblemon:rattata",
"order": 0,
"variations": [
{
"aspects": [],
"poser": "cobblemon:rattata",
"model": "cobblemon:rattata_male.geo",
"texture": "cobblemon:textures/pokemon/0019_rattata/rattata.png",
"layers": []
},
{
"aspects": [
"shiny"
],
"texture": "cobblemon:textures/pokemon/0019_rattata/rattata_shiny.png"
}
]
}
@@ -1,12 +0,0 @@
{
"species": "cobblemon:rattata",
"order": 1,
"variations": [
{
"aspects": [
"female"
],
"model": "cobblemon:rattata_female.geo"
}
]
}
@@ -1,20 +0,0 @@
{
"species": "cobblemon:rattata",
"order": 2,
"variations": [
{
"aspects": [
"alolan"
],
"model": "cobblemon:rattata_alolan.geo",
"texture": "cobblemon:textures/pokemon/0019_rattata/rattata_alolan.png"
},
{
"aspects": [
"alolan",
"shiny"
],
"texture": "cobblemon:textures/pokemon/0019_rattata/rattata_alolan_shiny.png"
}
]
}
@@ -1,19 +0,0 @@
{
"species": "cobblemon:raticate",
"order": 0,
"variations": [
{
"aspects": [],
"poser": "cobblemon:raticate",
"model": "cobblemon:raticate_male.geo",
"texture": "cobblemon:textures/pokemon/0020_raticate/raticate.png",
"layers": []
},
{
"aspects": [
"shiny"
],
"texture": "cobblemon:textures/pokemon/0020_raticate/raticate_shiny.png"
}
]
}
@@ -1,12 +0,0 @@
{
"species": "cobblemon:raticate",
"order": 1,
"variations": [
{
"aspects": [
"female"
],
"model": "cobblemon:raticate_female.geo"
}
]
}
@@ -1,20 +0,0 @@
{
"species": "cobblemon:raticate",
"order": 2,
"variations": [
{
"aspects": [
"alolan"
],
"model": "cobblemon:raticate_alolan.geo",
"texture": "cobblemon:textures/pokemon/0020_raticate/raticate_alolan.png"
},
{
"aspects": [
"alolan",
"shiny"
],
"texture": "cobblemon:textures/pokemon/0020_raticate/raticate_alolan_shiny.png"
}
]
}
@@ -1,19 +0,0 @@
{
"species": "cobblemon:spearow",
"order": 0,
"variations": [
{
"aspects": [],
"poser": "cobblemon:spearow",
"model": "cobblemon:spearow.geo",
"texture": "cobblemon:textures/pokemon/0021_spearow/spearow.png",
"layers": []
},
{
"aspects": [
"shiny"
],
"texture": "cobblemon:textures/pokemon/0021_spearow/spearow_shiny.png"
}
]
}
@@ -1,19 +0,0 @@
{
"species": "cobblemon:fearow",
"order": 0,
"variations": [
{
"aspects": [],
"poser": "cobblemon:fearow",
"model": "cobblemon:fearow.geo",
"texture": "cobblemon:textures/pokemon/0022_fearow/fearow.png",
"layers": []
},
{
"aspects": [
"shiny"
],
"texture": "cobblemon:textures/pokemon/0022_fearow/fearow_shiny.png"
}
]
}
@@ -1,19 +0,0 @@
{
"species": "cobblemon:ekans",
"order": 0,
"variations": [
{
"aspects": [],
"poser": "cobblemon:ekans",
"model": "cobblemon:ekans.geo",
"texture": "cobblemon:textures/pokemon/0023_ekans/ekans.png",
"layers": []
},
{
"aspects": [
"shiny"
],
"texture": "cobblemon:textures/pokemon/0023_ekans/ekans_shiny.png"
}
]
}
@@ -1,52 +0,0 @@
{
"species": "cobblemon:arbok",
"order": 0,
"variations": [
{
"aspects": [],
"poser": "cobblemon:arbok",
"model": "cobblemon:arbok.geo",
"texture": "cobblemon:textures/pokemon/0024_arbok/arbok.png",
"layers": [
{
"name": "pattern",
"scale": [
1.0,
1.0,
1.0
],
"tint": [
1.0,
1.0,
1.0,
1.0
],
"texture": "cobblemon:textures/pokemon/0024_arbok/arbok_pattern_classic.png"
}
]
},
{
"aspects": [
"shiny"
],
"texture": "cobblemon:textures/pokemon/0024_arbok/arbok_shiny.png",
"layers": [
{
"name": "pattern",
"scale": [
1.0,
1.0,
1.0
],
"tint": [
1.0,
1.0,
1.0,
1.0
],
"texture": "cobblemon:textures/pokemon/0024_arbok/arbok_pattern_classic_shiny.png"
}
]
}
]
}
@@ -1,19 +0,0 @@
{
"species": "cobblemon:pikachu",
"order": 0,
"variations": [
{
"aspects": [],
"poser": "cobblemon:pikachu",
"model": "cobblemon:pikachu_male.geo",
"texture": "cobblemon:textures/pokemon/0025_pikachu/pikachu.png",
"layers": []
},
{
"aspects": [
"shiny"
],
"texture": "cobblemon:textures/pokemon/0025_pikachu/pikachu_shiny.png"
}
]
}
@@ -1,12 +0,0 @@
{
"species": "cobblemon:pikachu",
"order": 1,
"variations": [
{
"aspects": [
"female"
],
"model": "cobblemon:pikachu_female.geo"
}
]
}
@@ -1,19 +0,0 @@
{
"species": "cobblemon:raichu",
"order": 0,
"variations": [
{
"aspects": [],
"poser": "cobblemon:raichu",
"model": "cobblemon:raichu_male.geo",
"texture": "cobblemon:textures/pokemon/0026_raichu/raichu.png",
"layers": []
},
{
"aspects": [
"shiny"
],
"texture": "cobblemon:textures/pokemon/0026_raichu/raichu_shiny.png"
}
]
}
@@ -1,12 +0,0 @@
{
"species": "cobblemon:raichu",
"order": 1,
"variations": [
{
"aspects": [
"female"
],
"model": "cobblemon:raichu_female.geo"
}
]
}
@@ -1,19 +0,0 @@
{
"species": "cobblemon:sandshrew",
"order": 0,
"variations": [
{
"aspects": [],
"poser": "cobblemon:sandshrew",
"model": "cobblemon:sandshrew.geo",
"texture": "cobblemon:textures/pokemon/0027_sandshrew/sandshrew.png",
"layers": []
},
{
"aspects": [
"shiny"
],
"texture": "cobblemon:textures/pokemon/0027_sandshrew/sandshrew_shiny.png"
}
]
}
@@ -1,19 +0,0 @@
{
"species": "cobblemon:sandslash",
"order": 0,
"variations": [
{
"aspects": [],
"poser": "cobblemon:sandslash",
"model": "cobblemon:sandslash.geo",
"texture": "cobblemon:textures/pokemon/0028_sandslash/sandslash.png",
"layers": []
},
{
"aspects": [
"shiny"
],
"texture": "cobblemon:textures/pokemon/0028_sandslash/sandslash_shiny.png"
}
]
}
@@ -1,19 +0,0 @@
{
"species": "cobblemon:nidoranf",
"order": 0,
"variations": [
{
"aspects": [],
"poser": "cobblemon:nidoranf",
"model": "cobblemon:nidoranf.geo",
"texture": "cobblemon:textures/pokemon/0029_nidoranf/nidoranf.png",
"layers": []
},
{
"aspects": [
"shiny"
],
"texture": "cobblemon:textures/pokemon/0029_nidoranf/nidoranf_shiny.png"
}
]
}
@@ -1,19 +0,0 @@
{
"species": "cobblemon:nidorina",
"order": 0,
"variations": [
{
"aspects": [],
"poser": "cobblemon:nidorina",
"model": "cobblemon:nidorina.geo",
"texture": "cobblemon:textures/pokemon/0030_nidorina/nidorina.png",
"layers": []
},
{
"aspects": [
"shiny"
],
"texture": "cobblemon:textures/pokemon/0030_nidorina/nidorina_shiny.png"
}
]
}
@@ -1,19 +0,0 @@
{
"species": "cobblemon:nidoqueen",
"order": 0,
"variations": [
{
"aspects": [],
"poser": "cobblemon:nidoqueen",
"model": "cobblemon:nidoqueen.geo",
"texture": "cobblemon:textures/pokemon/0031_nidoqueen/nidoqueen.png",
"layers": []
},
{
"aspects": [
"shiny"
],
"texture": "cobblemon:textures/pokemon/0031_nidoqueen/nidoqueen_shiny.png"
}
]
}
@@ -1,19 +0,0 @@
{
"species": "cobblemon:nidoranm",
"order": 0,
"variations": [
{
"aspects": [],
"poser": "cobblemon:nidoranm",
"model": "cobblemon:nidoranm.geo",
"texture": "cobblemon:textures/pokemon/0032_nidoranm/nidoranm.png",
"layers": []
},
{
"aspects": [
"shiny"
],
"texture": "cobblemon:textures/pokemon/0032_nidoranm/nidoranm_shiny.png"
}
]
}
@@ -1,19 +0,0 @@
{
"species": "cobblemon:nidorino",
"order": 0,
"variations": [
{
"aspects": [],
"poser": "cobblemon:nidorino",
"model": "cobblemon:nidorino.geo",
"texture": "cobblemon:textures/pokemon/0033_nidorino/nidorino.png",
"layers": []
},
{
"aspects": [
"shiny"
],
"texture": "cobblemon:textures/pokemon/0033_nidorino/nidorino_shiny.png"
}
]
}
@@ -1,19 +0,0 @@
{
"species": "cobblemon:nidoking",
"order": 0,
"variations": [
{
"aspects": [],
"poser": "cobblemon:nidoking",
"model": "cobblemon:nidoking.geo",
"texture": "cobblemon:textures/pokemon/0034_nidoking/nidoking.png",
"layers": []
},
{
"aspects": [
"shiny"
],
"texture": "cobblemon:textures/pokemon/0034_nidoking/nidoking_shiny.png"
}
]
}
Loaded 100 of 805 files, more files were not shown because too many files have changed in this diff. Show more