Merge branch 'complex-spawn-buckets' into 'main'

Rebuilt spawn buckets to be controlled by the spawner, allowing new buckets in specific contexts, and flattened habitat configuration classes

See merge request cable-mc/cobblemon!2267
This commit is contained in:
Polymeta
2026-05-17 23:54:13 +00:00
63 changed files with 438 additions and 811 deletions
+2
View File
@@ -340,6 +340,8 @@
- Tweaked the spawning selector interface to take a bucket function rather than a bucket directly.
- Added a new `cobblemon:height_range` processor type to allow developers to restrict structure-spawning ranges.
- Separated all the functions of MoLangFunctions.kt into their own files.
- Spawn buckets and their base weights are now provided by the spawner, with different base weights for world, fishing, snack, and habitat spawn buckets.
- Removed "SpawnBucket" class in favour of just storing maps of bucket names to weights.
### Molang & Datapacks
- Added `moveset_builders` datapack folder for different styles of movesets.
@@ -8,7 +8,6 @@
package com.cobblemon.mod.common.api.events.entity
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.SpawnCause
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
@@ -21,7 +20,7 @@ class SpawnBucketChosenEvent(
/** The cause of the spawn. */
val spawnCause: SpawnCause,
/** The bucket that was chosen. You can alter this. */
var bucket: SpawnBucket,
var bucket: String,
/** The final weights that were used to make the choice. */
val bucketWeights: Map<SpawnBucket, Float>
val bucketWeights: Map<String, Float>
)
@@ -9,7 +9,6 @@
package com.cobblemon.mod.common.api.events.fishing
import com.cobblemon.mod.common.api.events.Cancelable
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.entity.fishing.PokeRodFishingBobberEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.world.item.ItemStack
@@ -27,7 +26,7 @@ interface BaitSpawnPokemonEvent {
*/
data class Pre(
val bobber: PokeRodFishingBobberEntity, // todo replace this with the bait entity
val chosenBucket: SpawnBucket,
val chosenBucket: String,
val rod: ItemStack
) : Cancelable(), BobberSpawnPokemonEvent
@@ -38,7 +37,7 @@ interface BaitSpawnPokemonEvent {
* @param pokemon The Pokemon that is modified.
*/
data class Modify(
val chosenBucket: SpawnBucket,
val chosenBucket: String,
val rod: ItemStack,
val pokemon: PokemonEntity
) : BobberSpawnPokemonEvent
@@ -52,7 +51,7 @@ interface BaitSpawnPokemonEvent {
*/
data class Post(
val bobber: PokeRodFishingBobberEntity,
val chosenBucket: SpawnBucket,
val chosenBucket: String,
val bait: ItemStack,
val pokemon: PokemonEntity
) : BobberSpawnPokemonEvent
@@ -8,12 +8,10 @@
package com.cobblemon.mod.common.api.events.fishing
import com.cobblemon.mod.common.api.spawning.SpawnBucket
/**
* Event that is fired when a bucket is chosen by the bobber.
* @param chosenBucket The bucket that is chosen.
* @param buckets The list of buckets that the bobber can choose from.
* @param luckOfTheSeaLevel The level of the Luck of the Sea enchantment on the rod.
*/
class BobberBucketChosenEvent(var chosenBucket: SpawnBucket, val buckets: MutableList<SpawnBucket>, val luckOfTheSeaLevel: Int)
class BobberBucketChosenEvent(var chosenBucket: String, val buckets: MutableList<String>, val luckOfTheSeaLevel: Int)
@@ -12,10 +12,7 @@ import com.bedrockk.molang.runtime.value.MoValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.events.Cancelable
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.moLangFunctionMap
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.detail.SpawnAction
import com.cobblemon.mod.common.entity.fishing.PokeRodFishingBobberEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
@@ -73,8 +70,8 @@ interface BobberSpawnPokemonEvent {
val bait: ItemStack,
val pokemon: PokemonEntity
) : BobberSpawnPokemonEvent {
val context = mutableMapOf<String, MoValue>(
"chosen_bucket" to ObjectValue(spawnAction.bucket, { it.name }),
val context = mutableMapOf(
"chosen_bucket" to StringValue(spawnAction.bucket),
"bait" to pokemon.level().itemRegistry.wrapAsHolder(bait.item).asMoLangValue(Registries.ITEM),
"pokemon_entity" to pokemon.asMoLangValue()
)
@@ -1,37 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.habitats
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
/**
* A [HabitatPool] of [ActivatedHabitatSpawn]s. Mainly just to help with deserialization and ensuring
* a single type for everything that's applied to a habitat block.
*
* @author Hiroku
* @since February 13th, 2026
*/
class ActivatedHabitatPool : HabitatPool<ActivatedHabitatSpawn>() {
override val type: ResourceLocation = ActivatedHabitatSpawn.TYPE
override var spawns: List<ActivatedHabitatSpawn> = listOf()
companion object {
fun default() = ActivatedHabitatPool().apply {
id = ResourceLocation("cobblemon", "custom__default_activated_pool")
name = "Custom"
}
}
override fun decode(buffer: RegistryFriendlyByteBuf, buckets: List<SpawnBucket>) {
super.decode(buffer, buckets)
spawns = buffer.readList { ActivatedHabitatSpawn().apply { decode(buffer, buckets) } }
}
}
@@ -1,23 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.habitats
import com.cobblemon.mod.common.util.cobblemonResource
/**
* A basic [HabitatSpawn] that will be triggered via activated spawning in a habitat. Nothing to it, really.
*
* @author Hiroku
* @since February 13th, 2026
*/
class ActivatedHabitatSpawn : HabitatSpawn() {
companion object {
val TYPE = cobblemonResource("activated")
}
}
@@ -8,39 +8,81 @@
package com.cobblemon.mod.common.api.habitats
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.spawning.detail.PokemonSpawnDetail
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.block.habitat.HabitatBlockEntity
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.ListTag
import net.minecraft.nbt.Tag
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
/**
* A pool of [HabitatSpawn]s. This is either filled with [NaturalHabitatSpawn]s or [ActivatedHabitatSpawn]s depending
* on whether the pool is a [NaturalHabitatPool] or an [ActivatedHabitatPool]. The pool is responsible for converting
* A pool of [HabitatSpawn]s. This is filled with [HabitatSpawn]s. The pool is responsible for converting
* its spawns into [PokemonSpawnDetail]s.
*
* @author Hiroku
* @since February 13th, 2026
*/
abstract class HabitatPool<T : HabitatSpawn> {
class HabitatPool {
companion object {
val types = mutableMapOf(
ActivatedHabitatSpawn.TYPE to ActivatedHabitatPool::class.java,
NaturalHabitatSpawn.TYPE to NaturalHabitatPool::class.java
)
fun default() = HabitatPool().apply {
id = ResourceLocation("cobblemon", "custom__default_pool")
name = "Custom"
}
fun writePoolToNBT(pool: HabitatPool, nbt: CompoundTag) {
nbt.putString(DataKeys.HABITAT_POOL_ID, pool.id.toString())
if (pool.id.path.startsWith("custom__")) {
nbt.putString(DataKeys.HABITAT_POOL_NAME, pool.name)
val spawnsListNBT = ListTag()
for (spawn in pool.spawns) {
val spawnTag = CompoundTag()
spawn.writeToNBT(spawnTag)
spawnsListNBT.add(spawnTag)
}
nbt.put(DataKeys.HABITAT_POOL_SPAWNS, spawnsListNBT)
}
}
fun readPoolFromNBT(nbt: CompoundTag): HabitatPool {
val poolId = nbt.getString(DataKeys.HABITAT_POOL_ID).asIdentifierDefaultingNamespace()
if (nbt.contains(DataKeys.HABITAT_POOL_NAME)) {
// It's one that was stored on the block
val pool = HabitatPool()
pool.id = poolId
pool.name = nbt.getString(DataKeys.HABITAT_POOL_NAME)
val spawnsListNBT = nbt.getList(DataKeys.HABITAT_POOL_SPAWNS, Tag.TAG_COMPOUND.toInt())
val spawns = mutableListOf<HabitatSpawn>()
spawnsListNBT.forEach { spawnTag ->
try {
val spawn = HabitatSpawn().apply { readFromNBT(spawnTag as CompoundTag) }
spawns.add(spawn)
} catch (_: Exception) {
// If there's an error reading a spawn, skip it and log the error.
Cobblemon.LOGGER.error("Error reading activated habitat spawn from NBT for habitat block, skipping.")
}
}
pool.spawns = spawns
return pool
} else {
return HabitatPools.habitatPoolsById[poolId] ?: HabitatPool.default()
}
}
}
lateinit var id: ResourceLocation
var name: String = ""
abstract val type: ResourceLocation
abstract var spawns: List<T>
var spawns: List<HabitatSpawn> = listOf()
/** Whether a pool is editable is just down to whether it's not from a datapack. */
@Transient
var editable: Boolean = false
open fun validate() {
fun validate() {
for (spawn in spawns) {
if (SpawnablePosition.getByName(spawn.spawnablePositionType) == null) {
throw IllegalArgumentException("$id mentions non-existent spawnable position type ${spawn.spawnablePositionType} on spawn for ${spawn.species}")
@@ -58,8 +100,9 @@ abstract class HabitatPool<T : HabitatSpawn> {
buffer.writeCollection(spawns) { _, it -> it.encode(buffer) }
}
open fun decode(buffer: RegistryFriendlyByteBuf, buckets: List<SpawnBucket>) {
fun decode(buffer: RegistryFriendlyByteBuf) {
id = buffer.readResourceLocation()
name = buffer.readUtf()
spawns = buffer.readList { HabitatSpawn().apply { decode(buffer) } }
}
}
@@ -12,16 +12,13 @@ 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.IntRanges
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.TimeRange
import com.cobblemon.mod.common.api.spawning.position.SpawnablePositionType
import com.cobblemon.mod.common.pokemon.Species
import com.cobblemon.mod.common.util.adapters.HabitatPoolAdapter
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.adapters.IntRangeAdapter
import com.cobblemon.mod.common.util.adapters.IntRangesAdapter
import com.cobblemon.mod.common.util.adapters.RegisteredSpawnablePositionAdapter
import com.cobblemon.mod.common.util.adapters.SpawnBucketAdapter
import com.cobblemon.mod.common.util.adapters.SpeciesAdapter
import com.cobblemon.mod.common.util.adapters.pokemonPropertiesShortAdapter
import com.cobblemon.mod.common.util.cobblemonResource
@@ -36,18 +33,16 @@ import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
import net.minecraft.world.level.storage.LevelResource
object HabitatPools : JsonDataRegistry<HabitatPool<*>> {
object HabitatPools : JsonDataRegistry<HabitatPool> {
override val id = cobblemonResource("habitat_pools")
override val type = PackType.SERVER_DATA
override val observable = SimpleObservable<HabitatPools>()
override val typeToken: TypeToken<HabitatPool<*>> = TypeToken.get(HabitatPool::class.java)
override val typeToken: TypeToken<HabitatPool> = TypeToken.get(HabitatPool::class.java)
override val resourcePath = "habitat_pools"
override val gson: Gson = GsonBuilder()
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
.registerTypeAdapter(HabitatPool::class.java, HabitatPoolAdapter)
.registerTypeAdapter(SpawnablePositionType::class.java, RegisteredSpawnablePositionAdapter)
.registerTypeAdapter(SpawnBucket::class.java, SpawnBucketAdapter)
.registerTypeAdapter(Species::class.java, SpeciesAdapter)
.registerTypeAdapter(IntRange::class.java, IntRangeAdapter)
.registerTypeAdapter(TimeRange::class.java, TimeRange.adapter)
@@ -56,10 +51,10 @@ object HabitatPools : JsonDataRegistry<HabitatPool<*>> {
.setPrettyPrinting()
.create()
private val datapacked = mutableMapOf<ResourceLocation, HabitatPool<*>>()
private val worldSaved = mutableMapOf<ResourceLocation, HabitatPool<*>>()
private val datapacked = mutableMapOf<ResourceLocation, HabitatPool>()
private val worldSaved = mutableMapOf<ResourceLocation, HabitatPool>()
val habitatPoolsById = mutableMapOf<ResourceLocation, HabitatPool<*>>()
val habitatPoolsById = mutableMapOf<ResourceLocation, HabitatPool>()
private lateinit var worldSavedPoolsFolder: File
@@ -73,7 +68,7 @@ object HabitatPools : JsonDataRegistry<HabitatPool<*>> {
worldSavedPoolsFolder.listFiles { it.endsWith(".json") }.forEach {
val identifier = ResourceLocation.parse(it.nameWithoutExtension)
val habitat = gson.fromJson<HabitatPool<*>>(it.reader())
val habitat = gson.fromJson<HabitatPool>(it.reader())
habitat.id = identifier
habitat.validate()
habitat.editable = true
@@ -82,7 +77,7 @@ object HabitatPools : JsonDataRegistry<HabitatPool<*>> {
setupHabitatPoolsById()
}
override fun reload(data: Map<ResourceLocation, HabitatPool<*>>) {
override fun reload(data: Map<ResourceLocation, HabitatPool>) {
datapacked.clear()
datapacked.putAll(data)
data.entries.forEach { (identifier, habitat) ->
@@ -10,9 +10,7 @@ package com.cobblemon.mod.common.api.habitats
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.spawning.BestSpawner
import com.cobblemon.mod.common.api.spawning.IntRanges
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.TimeRange
import com.cobblemon.mod.common.api.spawning.condition.AppendageCondition
import com.cobblemon.mod.common.api.spawning.condition.SpawningCondition
@@ -30,9 +28,7 @@ import net.minecraft.nbt.CompoundTag
import net.minecraft.network.RegistryFriendlyByteBuf
/**
* A kind of simplified spawn definition for the purposes of spawning from [HabitatBlockEntity] instances. This is a
* very shallow class tree, with [ActivatedHabitatSpawn] being a trivial subclass but the other subclass being
* [NaturalHabitatSpawn] which introduces the need to supply a spawn bucket.
* A kind of simplified spawn definition for the purposes of spawning from [HabitatBlockEntity] instances.
*
* These have only a small subset of the properties that a regular [PokemonSpawnDetail] would have to align with the
* fact that these are configured from a simple GUI. There are limits to how many properties and conditions could be
@@ -46,11 +42,13 @@ import net.minecraft.network.RegistryFriendlyByteBuf
* @author Hiroku
* @since February 13th, 2026
*/
sealed class HabitatSpawn {
class HabitatSpawn {
/** The species that will be spawned. */
lateinit var species: Species
/** The kind of position the Pokémon will spawn in. Things like "grounded", "seafloor", "fishing". */
lateinit var spawnablePositionType: String
/** The bucket to spawn from. */
var bucket: String = "common"
/** Selection weight. */
var weight: Float = 1F
@@ -77,6 +75,7 @@ sealed class HabitatSpawn {
timeRange?.let { nbt.putString(DataKeys.HABITAT_POOL_SPAWN_TIMES, TimeRange.adapter.serialize(it)) }
minLight?.let { nbt.putInt(DataKeys.HABITAT_POOL_SPAWN_MIN_LIGHT, it) }
maxLight?.let { nbt.putInt(DataKeys.HABITAT_POOL_SPAWN_MAX_LIGHT, it) }
nbt.putString(DataKeys.HABITAT_POOL_SPAWN_BUCKET, bucket)
}
open fun readFromNBT(nbt: CompoundTag) {
@@ -89,6 +88,7 @@ sealed class HabitatSpawn {
timeRange = if (nbt.contains(DataKeys.HABITAT_POOL_SPAWN_TIMES)) TimeRange.adapter.deserialize(nbt.getString(DataKeys.HABITAT_POOL_SPAWN_TIMES)) else null
minLight = if (nbt.contains(DataKeys.HABITAT_POOL_SPAWN_MIN_LIGHT)) nbt.getInt(DataKeys.HABITAT_POOL_SPAWN_MIN_LIGHT) else null
maxLight = if (nbt.contains(DataKeys.HABITAT_POOL_SPAWN_MAX_LIGHT)) nbt.getInt(DataKeys.HABITAT_POOL_SPAWN_MAX_LIGHT) else null
bucket = if (nbt.contains(DataKeys.HABITAT_POOL_SPAWN_BUCKET)) nbt.getString(DataKeys.HABITAT_POOL_SPAWN_BUCKET) else "common"
}
open fun encode(buffer: RegistryFriendlyByteBuf) {
@@ -101,12 +101,10 @@ sealed class HabitatSpawn {
buffer.writeNullable(timeRange) { _, value -> buffer.writeUtf(TimeRange.adapter.serialize(value)) }
buffer.writeNullable(minLight) { _, value -> buffer.writeInt(value) }
buffer.writeNullable(maxLight) { _, value -> buffer.writeInt(value) }
buffer.writeUtf(bucket)
}
open fun decode(
buffer: RegistryFriendlyByteBuf,
buckets: List<SpawnBucket>
) {
open fun decode(buffer: RegistryFriendlyByteBuf) {
species = PokemonSpecies.getByIdentifier(buffer.readResourceLocation())!!
spawnablePositionType = buffer.readUtf()
weight = buffer.readFloat()
@@ -116,6 +114,7 @@ sealed class HabitatSpawn {
timeRange = buffer.readNullable { _ -> TimeRange.adapter.deserialize(buffer.readUtf()) }
minLight = buffer.readNullable { _ -> buffer.readInt() }
maxLight = buffer.readNullable { _ -> buffer.readInt() }
bucket = buffer.readUtf()
}
/** Creates the [PokemonSpawnDetail] that will be used in the spawner. This is done per habitat block. */
@@ -138,7 +137,7 @@ sealed class HabitatSpawn {
spawnDetail.apply {
this.id = "habitat_${species.resourceIdentifier}"
this.pokemon = properties
this.bucket = BestSpawner.config.buckets.first { it.weight > 0 }
this.bucket = this@HabitatSpawn.bucket
this.weight = this@HabitatSpawn.weight
this.levelRange = levelRange
this.conditions = mutableListOf(condition)
@@ -1,37 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.habitats
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
/**
* A [HabitatPool] of [NaturalHabitatSpawn]s. Mainly just to help with deserialization and ensuring
* a single type for everything that's applied to a habitat block.
*
* @author Hiroku
* @since February 13th, 2026
*/
class NaturalHabitatPool : HabitatPool<NaturalHabitatSpawn>() {
override val type: ResourceLocation = NaturalHabitatSpawn.TYPE
override var spawns: List<NaturalHabitatSpawn> = listOf()
companion object {
fun default() = NaturalHabitatPool().apply {
id = ResourceLocation("cobblemon", "custom__default_natural_pool")
name = "Custom"
}
}
override fun decode(buffer: RegistryFriendlyByteBuf, buckets: List<SpawnBucket>) {
super.decode(buffer, buckets)
spawns = buffer.readList { NaturalHabitatSpawn().apply { decode(buffer, buckets) } }
}
}
@@ -1,63 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.habitats
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.detail.PokemonSpawnDetail
import com.cobblemon.mod.common.block.habitat.HabitatBlockEntity
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.RegistryFriendlyByteBuf
/**
* A type of [HabitatSpawn] that spawns Pokémon as part of natural world spawning, and therefore
* needs to indicate the [SpawnBucket] it will be part of.
*
* @author Hiroku
* @since February 13th, 2026
*/
class NaturalHabitatSpawn : HabitatSpawn() {
companion object {
val TYPE = cobblemonResource("natural")
}
lateinit var bucket: SpawnBucket
override fun createSpawnDetail(habitatBlockEntity: HabitatBlockEntity): PokemonSpawnDetail? {
val spawnDetail = super.createSpawnDetail(habitatBlockEntity)
spawnDetail?.bucket = bucket
return spawnDetail
}
override fun writeToNBT(nbt: CompoundTag) {
super.writeToNBT(nbt)
nbt.putString(DataKeys.HABITAT_POOL_SPAWN_BUCKET, bucket.name)
}
override fun readFromNBT(nbt: CompoundTag) {
super.readFromNBT(nbt)
bucket = Cobblemon.bestSpawner.config.buckets.first { it.name == nbt.getString(DataKeys.HABITAT_POOL_SPAWN_BUCKET) }
}
override fun encode(buffer: RegistryFriendlyByteBuf) {
super.encode(buffer)
buffer.writeUtf(bucket.name)
}
override fun decode(
buffer: RegistryFriendlyByteBuf,
buckets: List<SpawnBucket>
) {
super.decode(buffer, buckets)
val bucketName = buffer.readUtf()
bucket = buckets.first { it.name == bucketName }
}
}
@@ -8,9 +8,7 @@
package com.cobblemon.mod.common.api.habitats.dto
import com.cobblemon.mod.common.api.habitats.ActivatedHabitatPool
import com.cobblemon.mod.common.api.habitats.spawningstyle.ActivatedHabitatSpawning
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import net.minecraft.network.RegistryFriendlyByteBuf
class ActivatedHabitatSettingsDTO() {
@@ -20,7 +18,6 @@ class ActivatedHabitatSettingsDTO() {
var spawnRange = 16
var maxSpawns = -1
var maxSpawnsPerActivation = 1
var habitatPool: HabitatPoolSettingsDTO = HabitatPoolSettingsDTO(ActivatedHabitatPool.default())
constructor(activatedHabitatSpawning: ActivatedHabitatSpawning): this() {
this.chance = activatedHabitatSpawning.chance
@@ -29,7 +26,6 @@ class ActivatedHabitatSettingsDTO() {
this.spawnRange = activatedHabitatSpawning.spawnRange
this.maxSpawns = activatedHabitatSpawning.maxSpawns
this.maxSpawnsPerActivation = activatedHabitatSpawning.maxSpawnsPerActivation
this.habitatPool = HabitatPoolSettingsDTO(activatedHabitatSpawning.pool)
}
fun encode(buffer: RegistryFriendlyByteBuf) {
@@ -39,20 +35,14 @@ class ActivatedHabitatSettingsDTO() {
buffer.writeInt(spawnRange)
buffer.writeInt(maxSpawns)
buffer.writeInt(maxSpawnsPerActivation)
habitatPool.encode(buffer)
}
fun decode(
buffer: RegistryFriendlyByteBuf,
buckets: List<SpawnBucket>
) {
fun decode(buffer: RegistryFriendlyByteBuf) {
this.chance = buffer.readFloat()
this.trigger = buffer.readEnum(ActivatedHabitatSpawning.Trigger::class.java)
this.cancelledNaturalSpawningRange = buffer.readInt()
this.spawnRange = buffer.readInt()
this.maxSpawns = buffer.readInt()
this.maxSpawnsPerActivation = buffer.readInt()
this.habitatPool = HabitatPoolSettingsDTO()
habitatPool.decode(buffer, buckets)
}
}
@@ -9,30 +9,23 @@
package com.cobblemon.mod.common.api.habitats.dto
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.habitats.ActivatedHabitatPool
import com.cobblemon.mod.common.api.habitats.ActivatedHabitatSpawn
import com.cobblemon.mod.common.api.habitats.HabitatPool
import com.cobblemon.mod.common.api.habitats.HabitatPools
import com.cobblemon.mod.common.api.habitats.HabitatSpawn
import com.cobblemon.mod.common.api.habitats.NaturalHabitatPool
import com.cobblemon.mod.common.api.habitats.NaturalHabitatSpawn
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
class HabitatPoolSettingsDTO() {
lateinit var id: ResourceLocation
var isReference = false
var isActivated = false
// Below properties are only there when it's not a reference, otherwise the handler will have to look them up from the id
var name: String? = null
var spawns: MutableList<HabitatSpawn>? = null
constructor(habitatPool: HabitatPool<*>): this() {
constructor(habitatPool: HabitatPool): this() {
this.id = habitatPool.id
this.isReference = !id.path.startsWith("custom__")
this.isActivated = habitatPool is ActivatedHabitatPool
if (!this.isReference) {
this.name = habitatPool.name
this.spawns = habitatPool.spawns.toMutableList()
@@ -42,7 +35,6 @@ class HabitatPoolSettingsDTO() {
fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(id)
buffer.writeBoolean(isReference)
buffer.writeBoolean(isActivated)
if (!isReference) {
buffer.writeUtf(name!!)
buffer.writeCollection(spawns!!) { _, spawn ->
@@ -52,50 +44,32 @@ class HabitatPoolSettingsDTO() {
}
fun decode(
buffer: RegistryFriendlyByteBuf,
buckets: List<SpawnBucket>
buffer: RegistryFriendlyByteBuf
) {
this.id = buffer.readResourceLocation()
this.isReference = buffer.readBoolean()
this.isActivated = buffer.readBoolean()
if (!isReference) {
this.name = buffer.readUtf()
this.spawns = buffer.readList {
val spawn = if (isActivated) {
ActivatedHabitatSpawn()
} else {
NaturalHabitatSpawn()
}
spawn.decode(buffer, buckets)
val spawn = HabitatSpawn()
spawn.decode(buffer)
spawn
}.toMutableList()
}
}
fun toHabitatPool(): HabitatPool<*>? {
fun toHabitatPool(): HabitatPool? {
if (isReference) {
val pool = HabitatPools.habitatPoolsById[id]
if (pool == null) {
Cobblemon.LOGGER.error("Could not find habitat pool with id $id")
} else if (pool is ActivatedHabitatPool && !isActivated) {
Cobblemon.LOGGER.error("Pool with id $id is an activated habitat pool but was deserialized as a natural pool")
} else if (pool !is ActivatedHabitatPool && isActivated) {
Cobblemon.LOGGER.error("Pool with id $id is a natural habitat pool but was deserialized as an activated pool")
}
return pool
}
return if (isActivated) {
val pool = ActivatedHabitatPool()
pool.id = id
pool.name = name!!
pool.spawns = spawns!!.map { it as ActivatedHabitatSpawn }
pool
} else {
val pool = NaturalHabitatPool()
pool.id = id
pool.name = name!!
pool.spawns = spawns!!.map { it as NaturalHabitatSpawn }
pool
}
val pool = HabitatPool()
pool.id = id
pool.name = name!!
pool.spawns = spawns!!
return pool
}
}
@@ -9,9 +9,9 @@
package com.cobblemon.mod.common.api.habitats.dto
import com.cobblemon.mod.common.api.habitats.HabitatPhaseOrder
import com.cobblemon.mod.common.api.habitats.HabitatPool
import com.cobblemon.mod.common.api.habitats.spawningstyle.ActivatedHabitatSpawning
import com.cobblemon.mod.common.api.habitats.spawningstyle.NaturalHabitatSpawning
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.block.habitat.HabitatBlockEntity
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
@@ -23,6 +23,7 @@ class HabitatSettingsDTO() {
var modifiers: String = ""
lateinit var mimicId: ResourceLocation
var isActivatedSpawning = false
var pool: HabitatPoolSettingsDTO = HabitatPoolSettingsDTO(HabitatPool.default())
var activatedSettings: ActivatedHabitatSettingsDTO? = null
var naturalSettings: NaturalHabitatSettingsDTO? = null
@@ -32,6 +33,7 @@ class HabitatSettingsDTO() {
this.levelRange = habitatBlockEntity.levelRange
this.modifiers = habitatBlockEntity.modifiers.asString()
this.mimicId = habitatBlockEntity.mimicId
this.pool = HabitatPoolSettingsDTO(habitatBlockEntity.pool)
val style = habitatBlockEntity.spawningStyle
if (style is ActivatedHabitatSpawning) {
this.isActivatedSpawning = true
@@ -48,6 +50,7 @@ class HabitatSettingsDTO() {
buffer.writeInt(levelRange.last)
buffer.writeUtf(modifiers)
buffer.writeResourceLocation(mimicId)
pool.encode(buffer)
buffer.writeBoolean(isActivatedSpawning)
if (isActivatedSpawning) {
activatedSettings?.encode(buffer)
@@ -56,10 +59,7 @@ class HabitatSettingsDTO() {
}
}
fun decode(
buffer: RegistryFriendlyByteBuf,
buckets: List<SpawnBucket>
) {
fun decode(buffer: RegistryFriendlyByteBuf) {
this.numberOfPhases = buffer.readInt()
this.phaseOrder = buffer.readEnum(HabitatPhaseOrder::class.java)
val levelRangeStart = buffer.readInt()
@@ -67,16 +67,18 @@ class HabitatSettingsDTO() {
this.levelRange = levelRangeStart..levelRangeEnd
this.modifiers = buffer.readUtf()
this.mimicId = buffer.readResourceLocation()
this.pool = HabitatPoolSettingsDTO()
this.pool.decode(buffer)
if (buffer.readBoolean()) {
this.isActivatedSpawning = true
this.naturalSettings = null
val activatedSettings = ActivatedHabitatSettingsDTO()
activatedSettings.decode(buffer, buckets)
activatedSettings.decode(buffer)
this.activatedSettings = activatedSettings
} else {
this.activatedSettings = null
val naturalSettings = NaturalHabitatSettingsDTO()
naturalSettings.decode(buffer, buckets)
naturalSettings.decode(buffer)
this.naturalSettings = naturalSettings
}
}
@@ -8,35 +8,25 @@
package com.cobblemon.mod.common.api.habitats.dto
import com.cobblemon.mod.common.api.habitats.NaturalHabitatPool
import com.cobblemon.mod.common.api.habitats.spawningstyle.NaturalHabitatSpawning
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import net.minecraft.network.RegistryFriendlyByteBuf
class NaturalHabitatSettingsDTO() {
var replaceSpawns = false
var rangeOfInfluence = 16
var habitatPool: HabitatPoolSettingsDTO = HabitatPoolSettingsDTO(NaturalHabitatPool.default())
constructor(naturalHabitatSpawning: NaturalHabitatSpawning): this() {
this.replaceSpawns = naturalHabitatSpawning.replaceSpawns
this.rangeOfInfluence = naturalHabitatSpawning.rangeOfInfluence
this.habitatPool = HabitatPoolSettingsDTO(naturalHabitatSpawning.pool)
}
fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeBoolean(replaceSpawns)
buffer.writeInt(rangeOfInfluence)
habitatPool.encode(buffer)
}
fun decode(
buffer: RegistryFriendlyByteBuf,
buckets: List<SpawnBucket>
) {
fun decode(buffer: RegistryFriendlyByteBuf, ) {
this.replaceSpawns = buffer.readBoolean()
this.rangeOfInfluence = buffer.readInt()
this.habitatPool = HabitatPoolSettingsDTO()
habitatPool.decode(buffer, buckets)
}
}
@@ -8,17 +8,16 @@
package com.cobblemon.mod.common.api.habitats.spawningstyle
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.habitats.HabitatSpawnActivatedEvent
import com.cobblemon.mod.common.api.habitats.ActivatedHabitatPool
import com.cobblemon.mod.common.api.habitats.ActivatedHabitatSpawn
import com.cobblemon.mod.common.api.habitats.ActivatedHabitatSpawningInfluence
import com.cobblemon.mod.common.api.spawning.SpawnCause
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.detail.SpawnPool
import com.cobblemon.mod.common.api.spawning.spawner.FixedAreaSpawner
import com.cobblemon.mod.common.block.habitat.HabitatBlockEntity
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.core.BlockPos
import net.minecraft.nbt.CompoundTag
import net.minecraft.server.level.ServerLevel
@@ -27,7 +26,7 @@ import net.minecraft.server.level.ServerLevel
* A habitat spawning style defined by being triggered by something. The activation trigger, [trigger], is either
* a redstone pulse, every tick, or every random tick.
*
* The spawn details are generated from the [pool] every time there is some change to the habitat block's settings,
* The spawn details are generated from the pool every time there is some change to the habitat block's settings,
* then those spawn details are used for spawning Pokémon when the habitat block is triggered.
*
* The [chance] property is applied after the trigger logic so that not every activation necessarily results in a spawn
@@ -37,6 +36,10 @@ import net.minecraft.server.level.ServerLevel
* @since February 14th, 2026
*/
class ActivatedHabitatSpawning(val habitatBlockEntity: HabitatBlockEntity) : HabitatSpawningStyle {
companion object {
val TYPE = cobblemonResource("activated")
}
enum class Trigger {
/** Requires a redstone pulse. */
REDSTONE,
@@ -46,7 +49,8 @@ class ActivatedHabitatSpawning(val habitatBlockEntity: HabitatBlockEntity) : Hab
RANDOM_TICK
}
override val type = ActivatedHabitatSpawn.TYPE
@Transient
override val type = TYPE
/** The chance that an activation by the [trigger] will result in a spawn attempt. The spawn attempt may still fail. */
var chance = 1F
@@ -60,11 +64,7 @@ class ActivatedHabitatSpawning(val habitatBlockEntity: HabitatBlockEntity) : Hab
var maxSpawns = -1
/** The maximum number of Pokémon that can spawn in a single activation. This will still be capped by [maxSpawns]. */
var maxSpawnsPerActivation = 1
/** The pool of spawns to choose from. */
var pool: ActivatedHabitatPool = ActivatedHabitatPool.default()
// This is cached here so that it doesn't have to be regenerated every time the block is triggered, updated when settings change.
var spawnDetails: List<SpawnDetail> = listOf()
lateinit var spawner: FixedAreaSpawner
override fun writeToNBT(nbt: CompoundTag) {
@@ -75,10 +75,6 @@ class ActivatedHabitatSpawning(val habitatBlockEntity: HabitatBlockEntity) : Hab
nbt.putInt(DataKeys.HABITAT_ACTIVATED_SPAWN_RANGE, spawnRange)
nbt.putInt(DataKeys.HABITAT_ACTIVATED_MAX_SPAWNS, maxSpawns)
nbt.putInt(DataKeys.HABITAT_ACTIVATED_MAX_SPAWNS_PER_ACTIVATION, maxSpawnsPerActivation)
val poolNBT = CompoundTag()
writePoolToNBT(pool, poolNBT)
nbt.put(DataKeys.HABITAT_POOL, poolNBT)
}
override fun readFromNBT(nbt: CompoundTag) {
@@ -88,25 +84,18 @@ class ActivatedHabitatSpawning(val habitatBlockEntity: HabitatBlockEntity) : Hab
spawnRange = nbt.getInt(DataKeys.HABITAT_ACTIVATED_SPAWN_RANGE)
maxSpawns = nbt.getInt(DataKeys.HABITAT_ACTIVATED_MAX_SPAWNS)
maxSpawnsPerActivation = nbt.getInt(DataKeys.HABITAT_ACTIVATED_MAX_SPAWNS_PER_ACTIVATION)
pool = HabitatSpawningStyle.readPoolFromNBT(
nbt = nbt.getCompound(DataKeys.HABITAT_POOL),
poolInitializer = ::ActivatedHabitatPool,
spawnInitializer = ::ActivatedHabitatSpawn,
defaultPool = ActivatedHabitatPool::default
)
}
fun generateSpawnDetails(world: ServerLevel, pos: BlockPos) {
spawnDetails = pool.createSpawnDetails(habitatBlockEntity)
spawner = FixedAreaSpawner(
name = "habitat_spawner_$pos",
world = world,
position = pos,
spawnPool = SpawnPool("habitat_block").also {
it.details.addAll(spawnDetails)
it.details.addAll(habitatBlockEntity.spawnDetails)
it.precalculate()
},
buckets = Cobblemon.bestSpawner.config.activatedHabitatBuckets,
maxPokemonPerChunk = 16F,
verticalRadius = spawnRange,
horizontalRadius = spawnRange
@@ -118,7 +107,7 @@ class ActivatedHabitatSpawning(val habitatBlockEntity: HabitatBlockEntity) : Hab
// Check if the spawn attempt should even happen based on chance.
if (chance < 1F && world.random.nextFloat() >= chance) {
return
} else if (spawnDetails.isEmpty()) {
} else if (habitatBlockEntity.spawnDetails.isEmpty()) {
// If there are no spawn details, then there's no point in trying to spawn anything.
return
}
@@ -8,15 +8,7 @@
package com.cobblemon.mod.common.api.habitats.spawningstyle
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.habitats.HabitatPool
import com.cobblemon.mod.common.api.habitats.HabitatPools
import com.cobblemon.mod.common.api.habitats.HabitatSpawn
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.ListTag
import net.minecraft.nbt.Tag
import net.minecraft.resources.ResourceLocation
/**
@@ -33,47 +25,4 @@ sealed interface HabitatSpawningStyle {
fun writeToNBT(nbt: CompoundTag)
fun readFromNBT(nbt: CompoundTag)
fun writePoolToNBT(pool: HabitatPool<*>, nbt: CompoundTag) {
nbt.putString(DataKeys.HABITAT_POOL_ID, pool.id.toString())
if (pool.id.path.startsWith("custom__")) {
nbt.putString(DataKeys.HABITAT_POOL_NAME, pool.name)
val spawnsListNBT = ListTag()
for (spawn in pool.spawns) {
val spawnTag = CompoundTag()
spawn.writeToNBT(spawnTag)
spawnsListNBT.add(spawnTag)
}
nbt.put(DataKeys.HABITAT_POOL_SPAWNS, spawnsListNBT)
}
}
companion object {
fun <T : HabitatSpawn, P : HabitatPool<T>> readPoolFromNBT(nbt: CompoundTag, poolInitializer: () -> P, spawnInitializer: () -> T, defaultPool: () -> P): P {
var pool: P
val poolId = nbt.getString(DataKeys.HABITAT_POOL_ID).asIdentifierDefaultingNamespace()
if (nbt.contains(DataKeys.HABITAT_POOL_NAME)) {
// It's one that was stored on the block
pool = poolInitializer()
pool.id = poolId
pool.name = nbt.getString(DataKeys.HABITAT_POOL_NAME)
val spawnsListNBT = nbt.getList(DataKeys.HABITAT_POOL_SPAWNS, Tag.TAG_COMPOUND.toInt())
val spawns = mutableListOf<T>()
spawnsListNBT.forEach { spawnTag ->
try {
val spawn = spawnInitializer().apply { readFromNBT(spawnTag as CompoundTag) }
spawns.add(spawn)
} catch (_: Exception) {
// If there's an error reading a spawn, skip it and log the error.
Cobblemon.LOGGER.error("Error reading activated habitat spawn from NBT for habitat block, skipping.")
}
}
pool.spawns = spawns
} else {
pool = HabitatPools.habitatPoolsById[poolId] as? P ?: defaultPool()
}
return pool
}
}
}
@@ -8,21 +8,18 @@
package com.cobblemon.mod.common.api.habitats.spawningstyle
import com.cobblemon.mod.common.api.habitats.NaturalHabitatPool
import com.cobblemon.mod.common.api.habitats.NaturalHabitatSpawn
import com.cobblemon.mod.common.api.habitats.spawningstyle.HabitatSpawningStyle.Companion.readPoolFromNBT
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.block.habitat.HabitatBlockEntity
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.nbt.CompoundTag
import net.minecraft.resources.ResourceLocation
/**
* A habitat spawning style defined by tapping into regular spawning methods. This works by adding to the spawn list
* for spawning that's occurring nearby. When [replaceSpawns] is set to true, the original spawns will be
* removed completely in favour of the spawns provided in this habitat's [pool].
* removed completely in favour of the spawns provided in this habitat's pool.
*
* The spawn details are generated from the [pool] every time there is some change to the habitat block's settings,
* The spawn details are generated from the pool every time there is some change to the habitat block's settings,
* then those spawn details are used for spawning Pokémon when world spawning is occurring nearby.
*
* Whether or not this interactions with overworld or fishing spawns is determined by whether the habitat pool has
@@ -32,10 +29,13 @@ import net.minecraft.resources.ResourceLocation
* @since February 22nd, 2026
*/
class NaturalHabitatSpawning(val habitatBlockEntity: HabitatBlockEntity) : HabitatSpawningStyle {
companion object {
val TYPE = cobblemonResource("natural")
}
@Transient
override val type: ResourceLocation = NaturalHabitatSpawn.TYPE
override val type: ResourceLocation = TYPE
var pool: NaturalHabitatPool = NaturalHabitatPool.default()
var replaceSpawns = false
var rangeOfInfluence = 16
@@ -43,33 +43,19 @@ class NaturalHabitatSpawning(val habitatBlockEntity: HabitatBlockEntity) : Habit
var affectsFishing = false
@Transient
var affectsOverworld = false
@Transient
var spawnDetails: List<SpawnDetail> = listOf()
fun generateSpawnDetails() {
spawnDetails = pool.createSpawnDetails(habitatBlockEntity = habitatBlockEntity)
affectsFishing = spawnDetails.any { it.spawnablePositionType.name == "fishing" }
affectsOverworld = spawnDetails.any { it.spawnablePositionType.name != "fishing" }
affectsFishing = habitatBlockEntity.spawnDetails.any { it.spawnablePositionType.name == "fishing" }
affectsOverworld = habitatBlockEntity.spawnDetails.any { it.spawnablePositionType.name != "fishing" }
}
override fun writeToNBT(nbt: CompoundTag) {
nbt.putBoolean(DataKeys.HABITAT_NATURAL_REPLACE_SPAWNS, replaceSpawns)
nbt.putInt(DataKeys.HABITAT_NATURAL_RANGE_OF_INFLUENCE, rangeOfInfluence)
val poolNBT = CompoundTag()
writePoolToNBT(pool, poolNBT)
nbt.put(DataKeys.HABITAT_POOL, poolNBT)
}
override fun readFromNBT(nbt: CompoundTag) {
replaceSpawns = nbt.getBoolean(DataKeys.HABITAT_NATURAL_REPLACE_SPAWNS)
rangeOfInfluence = nbt.getInt(DataKeys.HABITAT_NATURAL_RANGE_OF_INFLUENCE)
pool = readPoolFromNBT(
nbt = nbt.getCompound(DataKeys.HABITAT_POOL),
poolInitializer = ::NaturalHabitatPool,
spawnInitializer = ::NaturalHabitatSpawn,
defaultPool = NaturalHabitatPool::default
)
}
}
@@ -18,12 +18,12 @@ import com.cobblemon.mod.common.api.spawning.condition.SeafloorSpawningCondition
import com.cobblemon.mod.common.api.spawning.condition.SpawningCondition
import com.cobblemon.mod.common.api.spawning.condition.SubmergedSpawningCondition
import com.cobblemon.mod.common.api.spawning.condition.SurfaceSpawningCondition
import com.cobblemon.mod.common.api.spawning.detail.EmptySpawnDetail
import com.cobblemon.mod.common.api.spawning.detail.NPCSpawnDetail
import com.cobblemon.mod.common.api.spawning.detail.PokemonHerdSpawnDetail
import com.cobblemon.mod.common.api.spawning.detail.PokemonSpawnDetail
import com.cobblemon.mod.common.api.spawning.detail.SpawnAction
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.influence.BucketMultiplyingInfluence
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
import com.cobblemon.mod.common.api.spawning.position.AreaSpawnablePositionResolver
import com.cobblemon.mod.common.api.spawning.position.FishingSpawnablePosition
@@ -121,6 +121,7 @@ object BestSpawner {
SpawnDetail.registerSpawnType(name = PokemonSpawnDetail.TYPE, PokemonSpawnDetail::class.java)
SpawnDetail.registerSpawnType(name = NPCSpawnDetail.TYPE, NPCSpawnDetail::class.java)
SpawnDetail.registerSpawnType(name = PokemonHerdSpawnDetail.TYPE, PokemonHerdSpawnDetail::class.java)
SpawnDetail.registerSpawnType(name = EmptySpawnDetail.TYPE, EmptySpawnDetail::class.java)
LOGGER.info("Loaded ${SpawnDetail.spawnDetailTypes.size} spawn detail types.")
loadConfig()
@@ -1,33 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning
/**
* A spawn bucket is a simple referenceable object by spawn files. The bucket is used at the beginning of
* the selection process to decide which set of spawns will be used for spawning on that attempt. Weights are used to
* make entire buckets more or less likely, and then weighted selections will occur within whichever bucket
* was chosen.
*
* Spawn buckets are configured in the main config.
*
* @author Hiroku
* @since June 20th, 2022
*/
class SpawnBucket() {
lateinit var name: String
var weight = 0F
constructor(name: String, weight: Float): this() {
this.name = name
this.weight = weight
}
override fun hashCode() = name.hashCode()
override fun equals(other: Any?) = other is SpawnBucket && other.name == name
}
@@ -49,7 +49,6 @@ object SpawnDetailPresets : JsonDataRegistry<SpawnDetailPreset> {
.setPrettyPrinting()
.setLenient()
.disableHtmlEscaping()
.registerTypeAdapter(SpawnBucket::class.java, SpawnBucketAdapter)
.registerTypeAdapter(SpawnablePositionType::class.java, RegisteredSpawnablePositionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Biome::class.java).type, BiomeLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Block::class.java).type, BlockLikeConditionAdapter)
@@ -67,7 +67,6 @@ object SpawnLoader {
.registerTypeAdapter(MoonPhaseRange::class.java, IntRangesAdapter(MoonPhaseRange.moonPhaseRanges) { MoonPhaseRange(*it) })
.registerTypeAdapter(ItemDropMethod::class.java, ItemDropMethod.adapter)
.registerTypeAdapter(PokemonProperties::class.java, pokemonPropertiesShortAdapter)
.registerTypeAdapter(SpawnBucket::class.java, SpawnBucketAdapter)
.registerTypeAdapter(CompoundTag::class.java, NbtCompoundAdapter)
.registerTypeAdapter(IntRange::class.java, IntRangeAdapter)
.registerTypeAdapter(PossibleHeldItem::class.java, PossibleHeldItemAdapter)
@@ -8,10 +8,9 @@
package com.cobblemon.mod.common.api.spawning.condition
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import com.cobblemon.mod.common.api.spawning.detail.SpawnPool
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import net.minecraft.resources.ResourceLocation
/**
@@ -22,8 +21,8 @@ import net.minecraft.resources.ResourceLocation
* time as sharing that value. The requirement of implementations is only to get that
* key from both a [SpawnDetail] and a [SpawnablePosition].
*
* When a precalculation has been registered with a [Spawner] by adding to [Spawner.precalculators],
* the precalculation result will need to be updated by running [Spawner.precalculate]. That
* When a precalculation has been registered with a [SpawnPool] by adding to [SpawnPool.precalculators],
* the precalculation result will need to be updated by running [SpawnPool.precalculate]. That
* might take it a hot minute, but the result will be that there is an extra layer of
* shortcutting to the matching algorithm.
*
@@ -34,7 +33,7 @@ interface SpawningPrecalculation<T : Any> {
/** Retrieves the precalculation keys for the given spawn detail. */
fun select(detail: SpawnDetail): Collection<T>
/** Retrieves the precalculation key for the given spawnable position, if that key exists for this position. */
fun select(bucket: SpawnBucket, spawnablePosition: SpawnablePosition): T?
fun select(bucket: String, spawnablePosition: SpawnablePosition): T?
/**
* Generates a precalculation result for the list of [SpawnDetail]s and the subsequent precalculations.
@@ -74,7 +73,7 @@ interface SpawningPrecalculation<T : Any> {
object RootPrecalculation : SpawningPrecalculation<Any> {
val list = listOf(Unit)
override fun select(detail: SpawnDetail): List<Any> = list
override fun select(bucket: SpawnBucket, spawnablePosition: SpawnablePosition): Any = Unit
override fun select(bucket: String, spawnablePosition: SpawnablePosition): Any = Unit
}
/**
@@ -85,12 +84,12 @@ object RootPrecalculation : SpawningPrecalculation<Any> {
*/
object SpawnablePositionTypePrecalculation : SpawningPrecalculation<Class<out SpawnablePosition>> {
override fun select(detail: SpawnDetail) = listOf(detail.spawnablePositionType.clazz)
override fun select(bucket: SpawnBucket, spawnablePosition: SpawnablePosition) = spawnablePosition::class.java
override fun select(bucket: String, spawnablePosition: SpawnablePosition) = spawnablePosition::class.java
}
object BucketPrecalculation : SpawningPrecalculation<SpawnBucket> {
object BucketPrecalculation : SpawningPrecalculation<String> {
override fun select(detail: SpawnDetail) = listOf(detail.bucket)
override fun select(bucket: SpawnBucket, spawnablePosition: SpawnablePosition) = bucket
override fun select(bucket: String, spawnablePosition: SpawnablePosition) = bucket
}
/**
@@ -99,7 +98,7 @@ object BucketPrecalculation : SpawningPrecalculation<SpawnBucket> {
*/
object BiomePrecalculation : SpawningPrecalculation<ResourceLocation> {
override fun select(detail: SpawnDetail) = detail.validBiomes
override fun select(bucket: SpawnBucket, spawnablePosition: SpawnablePosition) = spawnablePosition.biomeHolder.unwrapKey().map { it.location() }.orElse(null)
override fun select(bucket: String, spawnablePosition: SpawnablePosition) = spawnablePosition.biomeHolder.unwrapKey().map { it.location() }.orElse(null)
}
/**
@@ -114,7 +113,7 @@ sealed class PrecalculationResult<T : Any>(
val calculation: SpawningPrecalculation<*>
) {
/** Gets the list of spawn details that fit this spawnable position. */
abstract fun retrieve(bucket: SpawnBucket, spawnablePosition: SpawnablePosition): List<SpawnDetail>
abstract fun retrieve(bucket: String, spawnablePosition: SpawnablePosition): List<SpawnDetail>
}
/**
@@ -127,7 +126,7 @@ class NestedPrecalculationResult<T : Any>(
calculation: SpawningPrecalculation<T>,
val mapping: Map<T, PrecalculationResult<*>> = mutableMapOf()
) : PrecalculationResult<T>(calculation) {
override fun retrieve(bucket: SpawnBucket, spawnablePosition: SpawnablePosition) = mapping[calculation.select(bucket, spawnablePosition)]?.retrieve(bucket, spawnablePosition) ?: emptyList()
override fun retrieve(bucket: String, spawnablePosition: SpawnablePosition) = mapping[calculation.select(bucket, spawnablePosition)]?.retrieve(bucket, spawnablePosition) ?: emptyList()
}
/**
@@ -141,5 +140,5 @@ class FinalPrecalculationResult<T : Any>(
calculation: SpawningPrecalculation<*>,
val mapping: Map<T, List<SpawnDetail>>
) : PrecalculationResult<T>(calculation) {
override fun retrieve(bucket: SpawnBucket, spawnablePosition: SpawnablePosition) = mapping[calculation.select(bucket, spawnablePosition)] ?: emptyList()
override fun retrieve(bucket: String, spawnablePosition: SpawnablePosition) = mapping[calculation.select(bucket, spawnablePosition)] ?: emptyList()
}
@@ -0,0 +1,25 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning.detail
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
/**
* Does a whole lot of nothing. Spawns nothing.
*
* @author Hiroku
* @since May 16th, 2026
*/
class EmptySpawnAction(
spawnablePosition: SpawnablePosition,
bucket: String,
detail: EmptySpawnDetail
) : SpawnAction<Unit>(spawnablePosition, bucket, detail) {
override fun run() {}
}
@@ -0,0 +1,48 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning.detail
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.spawning.selection.SpawnSelectionData
/**
* A spawn detail for nothing. It is used to take a slot, like to weigh down a spawn attempt,
* with something that ultimately doesn't spawn anything.
*
* The use case for this is very niche. With 1.8, the buckets that can be selected by a spawner
* during a spawn attempt will be narrowed down to the buckets that are used by the spawn
* pool. That means that if a bucket is never used by the pool, it will never be selected. That
* way, a bucket won't be selected when it never had any chance of being selected. This is what
* you would want in almost every case.
*
* However, in some cases you may want spawning to not spawn anything or you may want to preserve
* the relative rarity of the other buckets, and in that case you'd put something like this in a
* bucket so that it technically exists in the spawn pool without actually resulting in a spawned
* entity.
*
* @author Hiroku
* @since May 16th, 2026
*/
class EmptySpawnDetail : SpawnDetail() {
companion object {
val TYPE = "empty"
}
override val type: String = TYPE
override fun createSpawnAction(
spawnablePosition: SpawnablePosition,
bucket: String,
selectionData: SpawnSelectionData
) = EmptySpawnAction(
spawnablePosition = spawnablePosition,
bucket = bucket,
detail = this
)
}
@@ -9,7 +9,6 @@
package com.cobblemon.mod.common.api.spawning.detail
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.util.resolveInt
@@ -20,7 +19,7 @@ import com.cobblemon.mod.common.util.resolveInt
* @author Hiroku
* @since October 8th, 2023
*/
class NPCSpawnAction(spawnablePosition: SpawnablePosition, bucket: SpawnBucket, override val detail: NPCSpawnDetail) : SingleEntitySpawnAction<NPCEntity>(spawnablePosition, bucket, detail) {
class NPCSpawnAction(spawnablePosition: SpawnablePosition, bucket: String, override val detail: NPCSpawnDetail) : SingleEntitySpawnAction<NPCEntity>(spawnablePosition, bucket, detail) {
override fun createEntity(): NPCEntity {
val npc = NPCEntity(spawnablePosition.world)
npc.npc = detail.npcClass
@@ -11,13 +11,11 @@ package com.cobblemon.mod.common.api.spawning.detail
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.npc.NPCClass
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.spawning.selection.SpawnSelectionData
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.util.asExpression
import com.google.gson.annotations.SerializedName
import kotlin.math.ceil
/**
* A [SpawnDetail] describing an [NPCEntity] spawn.
@@ -51,7 +49,7 @@ class NPCSpawnDetail : SpawnDetail() {
override fun createSpawnAction(
spawnablePosition: SpawnablePosition,
bucket: SpawnBucket,
bucket: String,
selectionData: SpawnSelectionData
) = NPCSpawnAction(spawnablePosition, bucket, this)
}
@@ -11,7 +11,6 @@ package com.cobblemon.mod.common.api.spawning.detail
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.api.drop.DropTable
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.spawning.selection.SpawnSelectionData
import com.cobblemon.mod.common.util.weightedSelection
@@ -100,7 +99,7 @@ class PokemonHerdSpawnDetail : SpawnDetail() {
override fun createSpawnAction(
spawnablePosition: SpawnablePosition,
bucket: SpawnBucket,
bucket: String,
selectionData: SpawnSelectionData
): PokemonSpawnAction {
val validHerdMembers = getValidHerdMembers(selectionData)
@@ -11,7 +11,6 @@ package com.cobblemon.mod.common.api.spawning.detail
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.drop.DropTable
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.feature.SeasonFeatureHandler
@@ -26,7 +25,7 @@ import net.minecraft.world.item.ItemStack
*/
class PokemonSpawnAction(
spawnablePosition: SpawnablePosition,
bucket: SpawnBucket,
bucket: String,
override val detail: SpawnDetail,
/**
* The [PokemonProperties] that are about to be used. When using it, a copy will be taken.
@@ -14,7 +14,6 @@ import com.cobblemon.mod.common.api.drop.DropTable
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.spawning.selection.SpawnSelectionData
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
@@ -104,7 +103,8 @@ class PokemonSpawnDetail : SpawnDetail() {
}
override fun createSpawnAction(
spawnablePosition: SpawnablePosition, bucket: SpawnBucket,
spawnablePosition: SpawnablePosition,
bucket: String,
selectionData: SpawnSelectionData
): SingleEntitySpawnAction<PokemonEntity> {
val levelRange = pokemon.deriveLevelRange(levelRange)
@@ -11,7 +11,6 @@ package com.cobblemon.mod.common.api.spawning.detail
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.entity.SpawnEvent
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.util.toVec3d
import net.minecraft.world.entity.Entity
@@ -26,7 +25,7 @@ import net.minecraft.world.entity.MobSpawnType
*/
abstract class SingleEntitySpawnAction<T : Entity>(
spawnablePosition: SpawnablePosition,
bucket: SpawnBucket,
bucket: String,
detail: SpawnDetail
) : SpawnAction<EntitySpawnResult>(spawnablePosition, bucket, detail) {
abstract fun createEntity(): T?
@@ -8,7 +8,6 @@
package com.cobblemon.mod.common.api.spawning.detail
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import java.util.concurrent.CompletableFuture
@@ -21,7 +20,7 @@ import java.util.concurrent.CompletableFuture
*/
abstract class SpawnAction<R>(
val spawnablePosition: SpawnablePosition,
val bucket: SpawnBucket,
val bucket: String,
open val detail: SpawnDetail
) {
val labels = mutableListOf<String>()
@@ -15,7 +15,6 @@ import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.api.ModDependant
import com.cobblemon.mod.common.api.molang.MoLangFunctions.queryStructOf
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.condition.CompositeSpawningCondition
import com.cobblemon.mod.common.api.spawning.condition.SpawningCondition
import com.cobblemon.mod.common.api.spawning.multiplier.WeightMultiplier
@@ -54,7 +53,7 @@ abstract class SpawnDetail : ModDependant {
var displayName: String? = null
@SerializedName("spawnablePositionType", alternate = ["context"])
lateinit var spawnablePositionType: SpawnablePositionType<*>
var bucket = SpawnBucket("", 0F)
var bucket = ""
var conditions = mutableListOf<SpawningCondition<*>>()
var anticonditions = mutableListOf<SpawningCondition<*>>()
var compositeCondition: CompositeSpawningCondition? = null
@@ -79,7 +78,7 @@ abstract class SpawnDetail : ModDependant {
"weight" to { DoubleValue(weight) },
"percentage" to { DoubleValue(percentage) },
"id" to { StringValue(id) },
"bucket" to { StringValue(bucket.name) },
"bucket" to { StringValue(bucket) },
"width" to { DoubleValue(width.toDouble()) },
"height" to { DoubleValue(height.toDouble()) },
"spawnable_position_type" to { StringValue(spawnablePositionType.name) },
@@ -136,6 +135,10 @@ abstract class SpawnDetail : ModDependant {
containsNullValues = true
LOGGER.error("Spawn Detail with id $id is invalid as it contains invalid values in its anticonditions (commonly caused by trailing comma in biomes or other arrays)")
}
if (bucket.isBlank()) {
containsNullValues = true
LOGGER.error("Spawn Detail with id $id is invalid as it does not have a bucket assigned")
}
return super.isModDependencySatisfied() && !containsNullValues
}
@@ -163,7 +166,7 @@ abstract class SpawnDetail : ModDependant {
*/
fun choose(
spawnablePosition: SpawnablePosition,
bucket: SpawnBucket,
bucket: String,
selectionData: SpawnSelectionData
): SpawnAction<*> {
val action = createSpawnAction(spawnablePosition, bucket, selectionData)
@@ -175,10 +178,10 @@ abstract class SpawnDetail : ModDependant {
return action
}
/** Instantiates an action at the given [SpawnablePosition] for the given [SpawnBucket]. */
/** Instantiates an action at the given [SpawnablePosition] for the given spawn bucket. */
protected abstract fun createSpawnAction(
spawnablePosition: SpawnablePosition,
bucket: SpawnBucket,
bucket: String,
selectionData: SpawnSelectionData
): SpawnAction<*>
}
@@ -11,7 +11,6 @@ package com.cobblemon.mod.common.api.spawning.detail
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.spawning.CobblemonSpawnPools
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.SpawnLoader
import com.cobblemon.mod.common.api.spawning.SpawnSet
import com.cobblemon.mod.common.api.spawning.condition.PrecalculationResult
@@ -61,12 +60,13 @@ class SpawnPool(val name: String) : JsonDataRegistry<SpawnSet>, Iterable<SpawnDe
}
val details = mutableListOf<SpawnDetail>()
/** Tracks which buckets are in use by the pool, to prevent spawners for rolling buckets that don't exist. */
val buckets = mutableListOf<String>()
var precalculation: PrecalculationResult<*> = RootPrecalculation.generate(details, emptyList())
val precalculators = mutableListOf<SpawningPrecalculation<*>>()
override fun iterator() = details.iterator()
fun addPrecalculators(vararg precalculators: SpawningPrecalculation<*>): SpawnPool {
this.precalculators.addAll(precalculators)
precalculate()
@@ -88,6 +88,9 @@ class SpawnPool(val name: String) : JsonDataRegistry<SpawnSet>, Iterable<SpawnDe
* be slow, especially if there are many precalculators and spawns.
*/
fun precalculate() {
buckets.clear()
buckets.addAll(details.asSequence().map(SpawnDetail::bucket).distinct())
if (precalculators.isEmpty()) {
precalculation = RootPrecalculation.generate(details, emptyList())
} else {
@@ -103,7 +106,7 @@ class SpawnPool(val name: String) : JsonDataRegistry<SpawnSet>, Iterable<SpawnDe
* to leverage the precalculation to get a smaller list of spawns as quickly
* as possible.
*/
fun retrieve(bucket: SpawnBucket, spawnablePosition: SpawnablePosition): List<SpawnDetail> {
fun retrieve(bucket: String, spawnablePosition: SpawnablePosition): List<SpawnDetail> {
return precalculation.retrieve(bucket, spawnablePosition)
}
@@ -8,19 +8,19 @@
package com.cobblemon.mod.common.api.spawning.influence
import com.cobblemon.mod.common.api.spawning.SpawnBucket
/**
* A [SpawningInfluence] that multiplies bucket weights by the provided values
* and then normalizes the resulting weights so that their total adds up to 100.
*
* No longer used by the mod, but kept for external suers.
*/
class BucketMultiplyingInfluence(
val multipliers: Map<String, Float>,
) : SpawningInfluence {
override fun affectBucketWeights(bucketWeights: MutableMap<SpawnBucket, Float>) {
val updates = mutableMapOf<SpawnBucket, Float>()
override fun affectBucketWeights(bucketWeights: MutableMap<String, Float>) {
val updates = mutableMapOf<String, Float>()
for ((bucket, weight) in bucketWeights) {
val multiplier = multipliers[bucket.name]
val multiplier = multipliers[bucket]
if (multiplier != null) {
updates[bucket] = weight * multiplier
}
@@ -8,7 +8,6 @@
package com.cobblemon.mod.common.api.spawning.influence
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import kotlin.math.pow
/**
@@ -33,7 +32,7 @@ open class BucketNormalizingInfluence(
val gradient: Float = 0.2F,
val firstTier: Float = 1.29F
) : SpawningInfluence {
override fun affectBucketWeights(bucketWeights: MutableMap<SpawnBucket, Float>) {
override fun affectBucketWeights(bucketWeights: MutableMap<String, Float>) {
if (tier == 0) {
return
}
@@ -9,7 +9,6 @@
package com.cobblemon.mod.common.api.spawning.influence
import com.cobblemon.mod.common.api.spawning.BestSpawner
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.detail.SpawnAction
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
@@ -39,9 +38,9 @@ interface SpawningInfluence {
/** Applies some influence over the entity that's been spawned. */
fun affectSpawn(action: SpawnAction<*>, entity: Entity) {}
/** Applies some influence over the weight of spawn buckets. */
fun affectBucketWeights(bucketWeights: MutableMap<SpawnBucket, Float>) {}
fun affectBucketWeights(bucketWeights: MutableMap<String, Float>) {}
/** Normalizes all bucket weights to add up to 100 */
fun normalizeBucketWeights(bucketWeights: MutableMap<SpawnBucket, Float>) {
fun normalizeBucketWeights(bucketWeights: MutableMap<String, Float>) {
val sum = bucketWeights.values.sum()
val to100 = 100F / sum
bucketWeights.keys.toList().forEach {
@@ -56,5 +55,5 @@ interface SpawningInfluence {
*
* You should make sure the spawn position satisfies any spawn details that you inject.
*/
fun injectSpawns(bucket: SpawnBucket, spawnablePosition: SpawnablePosition): List<SpawnDetail>? = null
fun injectSpawns(bucket: String, spawnablePosition: SpawnablePosition): List<SpawnDetail>? = null
}
@@ -11,7 +11,6 @@ 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.spawning.BestSpawner
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.position.SpawnablePositionType
import com.cobblemon.mod.common.util.adapters.RegisteredSpawnablePositionAdapter
import com.google.gson.GsonBuilder
@@ -39,11 +38,34 @@ class BestSpawnerConfig {
"surface" to 0.01F,
"seafloor" to 1F
)
val buckets = mutableListOf(
SpawnBucket("common", 94.3F),
SpawnBucket("uncommon", 5F),
SpawnBucket("rare", 0.5F),
SpawnBucket("ultra-rare", 0.2F)
val worldBuckets = mutableMapOf(
"common" to 94F,
"uncommon" to 5F,
"rare" to 0.5F,
"ultra-rare" to 0.2F,
"boss" to 0.3F,
)
val fishingBuckets = mutableMapOf(
"common" to 83.25F,
"uncommon" to 11.25F,
"rare" to 4.125F,
"ultra-rare" to 1.375F
)
val activatedHabitatBuckets: MutableMap<String, Float> = mutableMapOf(
"common" to 83.25F,
"uncommon" to 11.25F,
"rare" to 4.125F,
"ultra-rare" to 1.375F
)
val pokeSnackBuckets = mutableMapOf(
"common" to 83.25F,
"uncommon" to 11.25F,
"rare" to 4.125F,
"ultra-rare" to 1.375F
)
companion object {
@@ -8,13 +8,12 @@
package com.cobblemon.mod.common.api.spawning.preset
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.position.SpawnablePositionType
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.multiplier.WeightMultiplier
import com.cobblemon.mod.common.api.spawning.position.SpawnablePositionType
import com.cobblemon.mod.common.util.MergeMode
import com.cobblemon.mod.common.util.adapters.SpawnDetailAdapter
import com.google.gson.JsonObject
@@ -41,7 +40,7 @@ import com.google.gson.annotations.SerializedName
* @since July 8th, 2022
*/
abstract class SpawnDetailPreset {
var bucket: SpawnBucket? = null
var bucket: String? = null
var spawnDetailType: String? = null
@SerializedName("spawnablePositionType", alternate = ["context"])
var spawnablePositionType: SpawnablePositionType<*>? = null
@@ -9,7 +9,6 @@
package com.cobblemon.mod.common.api.spawning.selection
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.detail.SpawnAction
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
@@ -88,12 +87,12 @@ open class FlatSelector : SpawningSelector<FlatSelector.SpawnablePositionSelecti
class SpawnablePositionSelectionData(
val spawner: Spawner,
val spawnablePositions: List<SpawnablePosition>,
val selectingSpawnInformation: MutableMap<SpawnBucket, SpawnablePositionBucketSelectionData>,
val selectingSpawnInformation: MutableMap<String, SpawnablePositionBucketSelectionData>,
): SpawnSelectionData {
override val spawnActions = mutableListOf<SpawnAction<*>>()
override val context = mutableMapOf<String, Any>()
fun getDataForBucket(bucket: SpawnBucket): SpawnablePositionBucketSelectionData {
fun getDataForBucket(bucket: String): SpawnablePositionBucketSelectionData {
val existing = selectingSpawnInformation[bucket]
if (existing != null) {
return existing
@@ -130,14 +129,14 @@ open class FlatSelector : SpawningSelector<FlatSelector.SpawnablePositionSelecti
spawner: Spawner,
spawnablePositions: List<SpawnablePosition>
): SpawnablePositionSelectionData {
val selectingSpawnInformation: MutableMap<SpawnBucket, SpawnablePositionBucketSelectionData> = mutableMapOf()
val selectingSpawnInformation: MutableMap<String, SpawnablePositionBucketSelectionData> = mutableMapOf()
return SpawnablePositionSelectionData(spawner, spawnablePositions, selectingSpawnInformation)
}
override fun selectSpawnAction(
spawner: Spawner,
bucket: SpawnBucket,
bucket: String,
selectionData: SpawnablePositionSelectionData,
): SpawnAction<*>? {
val selectingSpawnInformation = selectionData.getDataForBucket(bucket) ?: return null
@@ -149,7 +148,7 @@ open class FlatSelector : SpawningSelector<FlatSelector.SpawnablePositionSelecti
if (percentSum > 100) {
Cobblemon.LOGGER.warn(
"""
A spawn list for ${spawner.name} exceeded 100% on percentage sums in bucket ${bucket.name}...
A spawn list for ${spawner.name} exceeded 100% on percentage sums in bucket $bucket...
This means you don't understand how this option works.
""".trimIndent()
)
@@ -186,7 +185,7 @@ open class FlatSelector : SpawningSelector<FlatSelector.SpawnablePositionSelecti
override fun getTotalWeights(
spawner: Spawner,
bucket: SpawnBucket,
bucket: String,
spawnablePositions: List<SpawnablePosition>
): Map<SpawnDetail, Float> {
val selectionData = getSelectionData(spawner, spawnablePositions).getDataForBucket(bucket)
@@ -9,7 +9,6 @@
package com.cobblemon.mod.common.api.spawning.selection
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.detail.SpawnAction
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
@@ -105,7 +104,7 @@ open class FlatSpawnablePositionWeightedSelector : SpawningSelector<FlatSpawnabl
override val spawnActions = mutableListOf<SpawnAction<*>>()
override val context = mutableMapOf<String, Any>()
val bucketData = mutableMapOf<SpawnBucket, MutableMap<SpawnablePositionType<*>, SpawnablePositionSelectionData>>()
val bucketData = mutableMapOf<String, MutableMap<SpawnablePositionType<*>, SpawnablePositionSelectionData>>()
override fun removeSpawnDetails(shouldRemove: (SpawnDetail) -> Boolean) {
bucketData.values.forEach {
@@ -121,7 +120,7 @@ open class FlatSpawnablePositionWeightedSelector : SpawningSelector<FlatSpawnabl
}
}
fun getDataForBucket(bucket: SpawnBucket): MutableMap<SpawnablePositionType<*>, SpawnablePositionSelectionData> {
fun getDataForBucket(bucket: String): MutableMap<SpawnablePositionType<*>, SpawnablePositionSelectionData> {
bucketData[bucket]?.let { return it }
val spawnablePositionTypeToSpawns = mutableMapOf<SpawnablePositionType<*>, SpawnablePositionSelectionData>()
@@ -149,7 +148,7 @@ open class FlatSpawnablePositionWeightedSelector : SpawningSelector<FlatSpawnabl
override fun selectSpawnAction(
spawner: Spawner,
bucket: SpawnBucket,
bucket: String,
selectionData: SeparatedSelectionData
): SpawnAction<*>? {
val bucketData = selectionData.getDataForBucket(bucket)
@@ -172,7 +171,7 @@ open class FlatSpawnablePositionWeightedSelector : SpawningSelector<FlatSpawnabl
if (percentSum > 100) {
LOGGER.warn(
"""
A spawn list for ${spawner.name} exceeded 100% on percentage sums in bucket ${bucket.name}...
A spawn list for ${spawner.name} exceeded 100% on percentage sums in bucket $bucket...
This means you don't understand how this option works.
""".trimIndent()
)
@@ -244,7 +243,7 @@ open class FlatSpawnablePositionWeightedSelector : SpawningSelector<FlatSpawnabl
override fun getTotalWeights(
spawner: Spawner,
bucket: SpawnBucket,
bucket: String,
spawnablePositions: List<SpawnablePosition>
): Map<SpawnDetail, Float> {
val selectionData = getSelectionData(spawner, spawnablePositions).getDataForBucket(bucket)
@@ -8,7 +8,6 @@
package com.cobblemon.mod.common.api.spawning.selection
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.detail.SpawnAction
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
@@ -33,13 +32,13 @@ interface SpawningSelector<T : SpawnSelectionData> {
fun selectSpawnAction(
spawner: Spawner,
bucket: SpawnBucket,
bucket: String,
selectionData: T
): SpawnAction<*>?
fun select(
spawner: Spawner,
bucketSelector: () -> SpawnBucket,
bucketSelector: () -> String,
spawnablePositions: List<SpawnablePosition>,
maxSpawns: Int
): List<SpawnAction<*>> {
@@ -65,7 +64,7 @@ interface SpawningSelector<T : SpawnSelectionData> {
return spawnActions
}
fun getProbabilities(spawner: Spawner, bucket: SpawnBucket, spawnablePositions: List<SpawnablePosition>): Map<SpawnDetail, Float> {
fun getProbabilities(spawner: Spawner, bucket: String, spawnablePositions: List<SpawnablePosition>): Map<SpawnDetail, Float> {
val weights = getTotalWeights(spawner, bucket, spawnablePositions)
val totalWeight = weights.values.sum()
val percentages = mutableMapOf<SpawnDetail, Float>()
@@ -73,5 +72,5 @@ interface SpawningSelector<T : SpawnSelectionData> {
return percentages
}
fun getTotalWeights(spawner: Spawner, bucket: SpawnBucket, spawnablePositions: List<SpawnablePosition>): Map<SpawnDetail, Float>
fun getTotalWeights(spawner: Spawner, bucket: String, spawnablePositions: List<SpawnablePosition>): Map<SpawnDetail, Float>
}
@@ -25,7 +25,8 @@ import com.cobblemon.mod.common.api.spawning.selection.SpawningSelector
open class BasicSpawner(
override val name: String,
override var spawnPool: SpawnPool,
override var maxPokemonPerChunk: Float = Cobblemon.config.pokemonPerChunk
override var maxPokemonPerChunk: Float = Cobblemon.config.pokemonPerChunk,
override val buckets: Map<String, Float>
) : Spawner {
override var selector: SpawningSelector<*> = SpawningSelector.DEFAULT
override var generator: SpawningZoneGenerator = Cobblemon.spawningZoneGenerator
@@ -8,11 +8,11 @@
package com.cobblemon.mod.common.api.spawning.spawner
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonPoiTypes
import com.cobblemon.mod.common.api.spawning.BestSpawner
import com.cobblemon.mod.common.api.spawning.CobblemonSpawnPools
import com.cobblemon.mod.common.api.spawning.detail.SpawnPool
import com.cobblemon.mod.common.api.spawning.influence.BucketMultiplyingInfluence
import com.cobblemon.mod.common.api.spawning.influence.BucketNormalizingInfluence
import com.cobblemon.mod.common.api.spawning.influence.PlayerLevelRangeInfluence
import com.cobblemon.mod.common.api.spawning.influence.PlayerLevelRangeInfluence.Companion.TYPICAL_VARIATION
@@ -72,16 +72,9 @@ object FishingSpawnerFactory {
fun createSharedSpawner(): BasicSpawner {
return BasicSpawner(
name = "fishing",
spawnPool = spawnPool
).also { spawner ->
spawner.influences += BucketMultiplyingInfluence(
multipliers = mapOf(
"uncommon" to 2.25f,
"rare" to 5.5f,
"ultra-rare" to 5.5f,
)
)
}
spawnPool = spawnPool,
buckets = Cobblemon.bestSpawner.config.fishingBuckets,
)
}
fun findHabitatInfluences(world: ServerLevel, position: BlockPos, spawner: Spawner): Array<HabitatBlockEntity> {
@@ -11,8 +11,8 @@ package com.cobblemon.mod.common.api.spawning.spawner
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.spawning.SpawnCause
import com.cobblemon.mod.common.api.spawning.detail.SpawnPool
import net.minecraft.server.level.ServerLevel
import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerLevel
/**
* A spawner that works within a fixed area. Instances provide the center point and the radii and then the spawner
@@ -24,12 +24,13 @@ import net.minecraft.core.BlockPos
open class FixedAreaSpawner(
name: String,
spawnPool: SpawnPool,
buckets: Map<String, Float>,
val world: ServerLevel,
val position: BlockPos,
val horizontalRadius: Int,
val verticalRadius: Int,
maxPokemonPerChunk: Float = Cobblemon.config.pokemonPerChunk
) : BasicSpawner(name, spawnPool, maxPokemonPerChunk) {
) : BasicSpawner(name, spawnPool, maxPokemonPerChunk, buckets) {
fun run(cause: SpawnCause, maxSpawns: Int? = null): List<Any> {
return runForArea(
zoneInput = getZoneInput(cause),
@@ -8,20 +8,21 @@
package com.cobblemon.mod.common.api.spawning.spawner
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Cobblemon.config
import com.cobblemon.mod.common.api.spawning.SpawnCause
import com.cobblemon.mod.common.api.spawning.detail.SpawnPool
import com.cobblemon.mod.common.util.getPlayer
import com.cobblemon.mod.common.util.nextBetween
import net.minecraft.server.level.ServerLevel
import net.minecraft.server.level.ServerPlayer
import net.minecraft.util.Mth.PI
import net.minecraft.util.Mth.ceil
import java.util.*
import java.util.UUID
import kotlin.math.atan
import kotlin.math.cos
import kotlin.math.sin
import kotlin.random.Random
import net.minecraft.server.level.ServerLevel
import net.minecraft.server.level.ServerPlayer
import net.minecraft.util.Mth.PI
import net.minecraft.util.Mth.ceil
/**
* A spawner that works around a single player. It will do basic tracking of a player's speed
@@ -35,7 +36,8 @@ open class PlayerSpawner(
spawnPool: SpawnPool
) : BasicSpawner(
name = player.name.string,
spawnPool = spawnPool
spawnPool = spawnPool,
buckets = Cobblemon.bestSpawner.config.worldBuckets
) {
val uuid: UUID = player.uuid
@@ -8,11 +8,11 @@
package com.cobblemon.mod.common.api.spawning.spawner
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.fishing.SpawnBait
import com.cobblemon.mod.common.api.spawning.CobblemonSpawnPools
import com.cobblemon.mod.common.api.spawning.CobblemonSpawnRules
import com.cobblemon.mod.common.api.spawning.detail.SpawnPool
import com.cobblemon.mod.common.api.spawning.influence.BucketMultiplyingInfluence
import com.cobblemon.mod.common.api.spawning.influence.BucketNormalizingInfluence
import com.cobblemon.mod.common.api.spawning.influence.SpawnBaitInfluence
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
@@ -61,15 +61,6 @@ object PokeSnackSpawnerFactory {
firstTier = 1.2F
)
},
{ _ ->
BucketMultiplyingInfluence(
mapOf(
"uncommon" to 2.25f,
"rare" to 5.5f,
"ultra-rare" to 5.5f,
)
)
},
{ ctx ->
SpawnBaitInfluence(effects = ctx.baitEffects)
},
@@ -81,6 +72,7 @@ object PokeSnackSpawnerFactory {
return FixedAreaSpawner(
name = ctx.name,
spawnPool = spawnPool,
buckets = Cobblemon.bestSpawner.config.pokeSnackBuckets,
world = ctx.world,
position = ctx.pos,
horizontalRadius = ctx.horizontalRadius,
@@ -12,7 +12,6 @@ import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.entity.SpawnBucketChosenEvent
import com.cobblemon.mod.common.api.spawning.BestSpawner
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.SpawnCause
import com.cobblemon.mod.common.api.spawning.SpawningZoneGenerator
import com.cobblemon.mod.common.api.spawning.detail.EntitySpawnResult
@@ -26,6 +25,7 @@ import com.cobblemon.mod.common.api.spawning.position.calculators.SpawnablePosit
import com.cobblemon.mod.common.api.spawning.selection.SpawningSelector
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.isBoxLoaded
import com.cobblemon.mod.common.util.removeIf
import com.cobblemon.mod.common.util.squeezeWithinBounds
import com.cobblemon.mod.common.util.toVec3f
import com.cobblemon.mod.common.util.weightedSelection
@@ -52,6 +52,7 @@ import net.minecraft.world.phys.Vec3
interface Spawner {
val name: String
val influences: MutableList<SpawningInfluence>
val buckets: Map<String, Float>
var spawnPool: SpawnPool
var selector: SpawningSelector<*>
var generator: SpawningZoneGenerator
@@ -65,7 +66,7 @@ interface Spawner {
const val ENTITY_LIMIT_CHUNK_RANGE = 3
}
fun getMatchingSpawns(bucket: SpawnBucket, spawnablePosition: SpawnablePosition): List<SpawnDetail> {
fun getMatchingSpawns(bucket: String, spawnablePosition: SpawnablePosition): List<SpawnDetail> {
val spawns = mutableListOf<SpawnDetail>()
spawns.addAll(spawnPool.retrieve(bucket, spawnablePosition).filter { it.isSatisfiedBy(spawnablePosition) })
spawnablePosition.influences.forEach { influence ->
@@ -172,11 +173,12 @@ interface Spawner {
fun copyInfluences() = influences.filter { !it.isExpired() }.toMutableList()
fun chooseBucket(cause: SpawnCause, influences: List<SpawningInfluence>): SpawnBucket {
val buckets = Cobblemon.bestSpawner.config.buckets
val bucketWeights = buckets.associateWith { it.weight }.toMutableMap()
fun chooseBucket(cause: SpawnCause, influences: List<SpawningInfluence>): String {
val bucketWeights = buckets.toMutableMap()
influences.forEach { it.affectBucketWeights(bucketWeights) }
val bucket = bucketWeights.entries.weightedSelection { it.value }?.key ?: buckets.first()
val usedBuckets = spawnPool.buckets.intersect(bucketWeights.keys)
bucketWeights.removeIf { it.key !in usedBuckets }
val bucket = bucketWeights.entries.weightedSelection { it.value }?.key ?: buckets.keys.first()
val event = SpawnBucketChosenEvent(
spawner = this,
spawnCause = cause,
@@ -11,16 +11,13 @@ package com.cobblemon.mod.common.block.habitat
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.api.habitats.ActivatedHabitatPool
import com.cobblemon.mod.common.api.habitats.ActivatedHabitatSpawn
import com.cobblemon.mod.common.api.habitats.HabitatPhaseOrder
import com.cobblemon.mod.common.api.habitats.NaturalHabitatPool
import com.cobblemon.mod.common.api.habitats.HabitatPool
import com.cobblemon.mod.common.api.habitats.dto.HabitatSettingsDTO
import com.cobblemon.mod.common.api.habitats.spawningstyle.ActivatedHabitatSpawning
import com.cobblemon.mod.common.api.habitats.spawningstyle.HabitatSpawningStyle
import com.cobblemon.mod.common.api.habitats.spawningstyle.NaturalHabitatSpawning
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
@@ -69,12 +66,15 @@ class HabitatBlockEntity(pos: BlockPos, state: BlockState) :
var levelRange: IntRange = 1..Cobblemon.config.maxPokemonLevel
var modifiers: PokemonProperties = PokemonProperties()
var pool: HabitatPool = HabitatPool.default()
var mimicId: ResourceLocation = BuiltInRegistries.BLOCK.getKey(Blocks.STONE)
val mimickedState: BlockState
get() = BuiltInRegistries.BLOCK.get(mimicId).defaultBlockState() ?: Blocks.STONE.defaultBlockState()
var displaySpeciesIds: List<ResourceLocation> = emptyList()
// This is cached here so that it doesn't have to be regenerated every time the block is triggered, updated when settings change.
var spawnDetails: List<SpawnDetail> = listOf()
fun onUse(player: Player, hit: BlockHitResult): InteractionResult {
if (player.level().isClientSide) {
return InteractionResult.SUCCESS
@@ -97,6 +97,7 @@ class HabitatBlockEntity(pos: BlockPos, state: BlockState) :
val spawningStyle = spawningStyle
var activatedStyle = false
var cancelsRegularSpawns = false
spawnDetails = pool.createSpawnDetails(this)
if (spawningStyle is ActivatedHabitatSpawning) {
spawningStyle.generateSpawnDetails(level as ServerLevel, blockPos)
activatedStyle = true
@@ -136,21 +137,14 @@ class HabitatBlockEntity(pos: BlockPos, state: BlockState) :
fun applySettings(habitatSettingsDTO: HabitatSettingsDTO) {
val activatedSettings = habitatSettingsDTO.activatedSettings
val activatedPool = habitatSettingsDTO.activatedSettings?.habitatPool?.toHabitatPool() as? ActivatedHabitatPool
val isActivated = habitatSettingsDTO.isActivatedSpawning
if (isActivated && activatedPool == null) {
return // Pool was bad, check console logs
}
val naturalSettings = habitatSettingsDTO.naturalSettings
val naturalPool = habitatSettingsDTO.naturalSettings?.habitatPool?.toHabitatPool() as? NaturalHabitatPool
if (!isActivated && naturalPool == null) {
return // Pool was bad, check console logs
}
val isActivated = habitatSettingsDTO.isActivatedSpawning
this.numberOfPhases = habitatSettingsDTO.numberOfPhases
this.phaseOrder = habitatSettingsDTO.phaseOrder
this.levelRange = habitatSettingsDTO.levelRange
this.modifiers = PokemonProperties.parse(habitatSettingsDTO.modifiers)
this.mimicId = habitatSettingsDTO.mimicId
this.pool = habitatSettingsDTO.pool.toHabitatPool() ?: HabitatPool.default()
if (isActivated && activatedSettings != null) {
val activatedHabitatSpawning = ActivatedHabitatSpawning(this)
activatedHabitatSpawning.trigger = activatedSettings.trigger
@@ -159,13 +153,11 @@ class HabitatBlockEntity(pos: BlockPos, state: BlockState) :
activatedHabitatSpawning.spawnRange = activatedSettings.spawnRange
activatedHabitatSpawning.maxSpawns = activatedSettings.maxSpawns
activatedHabitatSpawning.maxSpawnsPerActivation = activatedSettings.maxSpawnsPerActivation
activatedHabitatSpawning.pool = activatedPool!!
this.spawningStyle = activatedHabitatSpawning
} else if (naturalSettings != null && naturalPool != null) {
} else if (naturalSettings != null) {
val naturalHabitatSpawning = NaturalHabitatSpawning(this)
naturalHabitatSpawning.replaceSpawns = naturalSettings.replaceSpawns
naturalHabitatSpawning.rangeOfInfluence = naturalSettings.rangeOfInfluence
naturalHabitatSpawning.pool = naturalPool
this.spawningStyle = naturalHabitatSpawning
}
@@ -186,6 +178,7 @@ class HabitatBlockEntity(pos: BlockPos, state: BlockState) :
tag.putString(DataKeys.HABITAT_MODIFIERS, modifiers.asString())
tag.putString(DataKeys.HABITAT_SPAWNING_STYLE, spawningStyle.type.toString())
spawningStyle.writeToNBT(tag)
HabitatPool.writePoolToNBT(pool = pool, nbt = tag)
val speciesListTag = ListTag()
displaySpeciesIds.forEach { speciesListTag.add(StringTag.valueOf(it.toString())) }
tag.put(DISPLAY_SPECIES_KEY, speciesListTag)
@@ -213,12 +206,13 @@ class HabitatBlockEntity(pos: BlockPos, state: BlockState) :
levelRange = tag.getString(DataKeys.HABITAT_LEVEL_RANGE).takeIf { it.isNotBlank() }?.let(IntRangeAdapter::deserialize) ?: 1..Cobblemon.config.maxPokemonLevel
modifiers = tag.getString(DataKeys.HABITAT_MODIFIERS).takeIf { it.isNotBlank() }?.let(PokemonProperties::parse) ?: PokemonProperties()
val spawningStyleType = tag.getString(DataKeys.HABITAT_SPAWNING_STYLE).asIdentifierDefaultingNamespace()
spawningStyle = if (spawningStyleType == ActivatedHabitatSpawn.TYPE) {
spawningStyle = if (spawningStyleType == ActivatedHabitatSpawning.TYPE) {
ActivatedHabitatSpawning(this)
} else {
NaturalHabitatSpawning(this)
}
spawningStyle.readFromNBT(tag)
pool = HabitatPool.readPoolFromNBT(tag)
refreshDisplaySpeciesIds()
}
@@ -237,10 +231,7 @@ class HabitatBlockEntity(pos: BlockPos, state: BlockState) :
}
private fun refreshDisplaySpeciesIds() {
displaySpeciesIds = when (val style = spawningStyle) {
is ActivatedHabitatSpawning -> style.pool.spawns.map { it.species.resourceIdentifier }.distinct()
is NaturalHabitatSpawning -> style.pool.spawns.map { it.species.resourceIdentifier }.distinct()
}
displaySpeciesIds = pool.spawns.map { it.species.resourceIdentifier }.distinct()
}
fun getInfluentialRange(spawner: Spawner): Int {
@@ -273,7 +264,7 @@ class HabitatBlockEntity(pos: BlockPos, state: BlockState) :
return if (spawningStyle is NaturalHabitatSpawning) {
if (spawningStyle.replaceSpawns) {
// Replace mode! Permit spawning only if it's from this block
detail in spawningStyle.spawnDetails
detail in spawnDetails
} else {
true // We don't mess with other types
}
@@ -285,10 +276,10 @@ class HabitatBlockEntity(pos: BlockPos, state: BlockState) :
}
}
override fun injectSpawns(bucket: SpawnBucket, spawnablePosition: SpawnablePosition): List<SpawnDetail>? {
override fun injectSpawns(bucket: String, spawnablePosition: SpawnablePosition): List<SpawnDetail>? {
val spawningStyle = spawningStyle
return if (spawningStyle is NaturalHabitatSpawning) {
spawningStyle.spawnDetails.filter { it.spawnablePositionType.clazz == spawnablePosition::class.java && it.isSatisfiedBy(spawnablePosition) }
spawnDetails.filter { it.spawnablePositionType.clazz == spawnablePosition::class.java && it.isSatisfiedBy(spawnablePosition) }
} else {
null
}
@@ -8,15 +8,13 @@
package com.cobblemon.mod.common.client.gui.habitat
import com.cobblemon.mod.common.api.habitats.ActivatedHabitatPool
import com.cobblemon.mod.common.api.habitats.HabitatPhaseOrder
import com.cobblemon.mod.common.api.habitats.NaturalHabitatPool
import com.cobblemon.mod.common.api.habitats.HabitatPool
import com.cobblemon.mod.common.api.habitats.dto.ActivatedHabitatSettingsDTO
import com.cobblemon.mod.common.api.habitats.dto.HabitatPoolSettingsDTO
import com.cobblemon.mod.common.api.habitats.dto.HabitatSettingsDTO
import com.cobblemon.mod.common.api.habitats.dto.NaturalHabitatSettingsDTO
import com.cobblemon.mod.common.api.habitats.spawningstyle.ActivatedHabitatSpawning
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.tags.CobblemonBlockTags
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
@@ -47,12 +45,12 @@ class HabitatEditGUI(
val maxPokemonLevel: Int,
/** Use this as reference data for what spawnable position types exist. */
val spawnablePositionTypes: List<String>,
/** Use this as reference data for what buckets can be chosen and how rare they are */
val buckets: List<SpawnBucket>,
/** Use this as reference data for pre-made activated habitat pools */
val activatedHabitatPools: Map<ResourceLocation, ActivatedHabitatPool>,
/** Use this as reference data for pre-made natural habitat pools */
val naturalHabitatPools: Map<ResourceLocation, NaturalHabitatPool>,
/** Use this as reference data for what buckets can be chosen for natural spawns */
val worldBuckets: List<String>,
/** Use this as reference data for what buckets can be chosen for activated spawns */
val activatedHabitatBuckets: List<String>,
/** Use this as reference data for pre-made habitat pools */
val pools: Map<ResourceLocation, HabitatPool>,
/** The current settings - edit the properties in here then send it back to the server. */
val habitatSettingsDTO: HabitatSettingsDTO
) : Screen(lang("ui.edit.habitat")), CobblemonRenderable {
@@ -124,7 +122,7 @@ class HabitatEditGUI(
val phases = value.toIntOrNull()
val valid = phases != null && phases > 0
if (valid) {
habitatSettingsDTO.numberOfPhases = phases!!
habitatSettingsDTO.numberOfPhases = phases
}
phasesInput.setTextColor(if (valid) validTextColor else invalidTextColor)
setFieldValidity("phases", valid, "ui.edit.habitat.validation.phases")
@@ -289,17 +287,12 @@ class HabitatEditGUI(
// Pool Selection
poolSelectorButton = addOptionWidget(
Button.builder(getPoolText()) {
val currentPool = if (habitatSettingsDTO.isActivatedSpawning) {
habitatSettingsDTO.activatedSettings?.habitatPool
} else {
habitatSettingsDTO.naturalSettings?.habitatPool
}
val currentPool = habitatSettingsDTO.pool
val selectedPoolId = if (currentPool?.isReference == true) currentPool.id else null
Minecraft.getInstance().setScreen(
HabitatPoolSelectionGUI(
isActivated = habitatSettingsDTO.isActivatedSpawning,
activatedPools = activatedHabitatPools,
naturalPools = naturalHabitatPools,
pools = pools,
selectedPoolId = selectedPoolId,
parentScreen = this
) { poolId ->
@@ -312,11 +305,8 @@ class HabitatEditGUI(
viewPoolButton = addOptionWidget(
Button.builder(getViewPoolText()) {
val pool = if (habitatSettingsDTO.isActivatedSpawning) {
habitatSettingsDTO.activatedSettings?.habitatPool
} else {
habitatSettingsDTO.naturalSettings?.habitatPool
} ?: return@builder
val pool = habitatSettingsDTO.pool
val buckets = if (habitatSettingsDTO.isActivatedSpawning) activatedHabitatBuckets else worldBuckets
if (!pool.isReference) {
Minecraft.getInstance().setScreen(
@@ -325,16 +315,11 @@ class HabitatEditGUI(
return@builder
}
val sourcePool = if (habitatSettingsDTO.isActivatedSpawning) {
activatedHabitatPools[pool.id]
} else {
naturalHabitatPools[pool.id]
} ?: return@builder
val sourcePool = pools[pool.id] ?: return@builder
val viewPool = HabitatPoolSettingsDTO(sourcePool).apply {
isReference = true
id = sourcePool.id
isActivated = habitatSettingsDTO.isActivatedSpawning
name = sourcePool.name
spawns = sourcePool.spawns.toMutableList()
}
@@ -424,29 +409,17 @@ class HabitatEditGUI(
private fun getReplaceSpawnsText() = lang("ui.edit.habitat.replace_spawns", habitatSettingsDTO.naturalSettings?.replaceSpawns ?: false)
private fun getPoolText(): net.minecraft.network.chat.Component {
val pool = if (habitatSettingsDTO.isActivatedSpawning) {
habitatSettingsDTO.activatedSettings?.habitatPool
} else {
habitatSettingsDTO.naturalSettings?.habitatPool
}
val pool = habitatSettingsDTO.pool
if (pool == null || !pool.isReference) {
return lang("ui.edit.habitat.pool", lang("ui.edit.habitat.pool.custom"))
}
val poolName = if (habitatSettingsDTO.isActivatedSpawning) {
activatedHabitatPools[pool.id]?.name?.asTranslated()
} else {
naturalHabitatPools[pool.id]?.name?.asTranslated()
}
val poolName = pools[pool.id]?.name?.asTranslated()
return lang("ui.edit.habitat.pool", poolName ?: pool.id)
}
private fun getViewPoolText(): net.minecraft.network.chat.Component {
val pool = if (habitatSettingsDTO.isActivatedSpawning) {
habitatSettingsDTO.activatedSettings?.habitatPool
} else {
habitatSettingsDTO.naturalSettings?.habitatPool
}
return if (pool?.isReference == true) {
val pool = habitatSettingsDTO.pool
return if (pool.isReference) {
lang("ui.edit.habitat.view_pool")
} else {
lang("ui.edit.habitat.edit_pool")
@@ -454,47 +427,29 @@ class HabitatEditGUI(
}
private fun applyPoolSelection(poolId: ResourceLocation?) {
val currentPool = if (habitatSettingsDTO.isActivatedSpawning) {
habitatSettingsDTO.activatedSettings?.habitatPool
} else {
habitatSettingsDTO.naturalSettings?.habitatPool
}
val currentPool = habitatSettingsDTO.pool
if (poolId == null) {
if (currentPool == null || currentPool.isReference) {
if (currentPool.isReference) {
val newCustomPool = HabitatPoolSettingsDTO().apply {
id = cobblemonResource("custom__${System.currentTimeMillis()}")
isReference = false
isActivated = habitatSettingsDTO.isActivatedSpawning
name = "Custom"
spawns = mutableListOf()
}
if (habitatSettingsDTO.isActivatedSpawning) {
habitatSettingsDTO.activatedSettings?.habitatPool = newCustomPool
} else {
habitatSettingsDTO.naturalSettings?.habitatPool = newCustomPool
}
habitatSettingsDTO.pool = newCustomPool
}
return
}
val sourcePool = if (habitatSettingsDTO.isActivatedSpawning) {
activatedHabitatPools[poolId]
} else {
naturalHabitatPools[poolId]
} ?: return
val sourcePool = pools[poolId] ?: return
val selectedPool = HabitatPoolSettingsDTO(sourcePool).apply {
isReference = true
id = poolId
isActivated = habitatSettingsDTO.isActivatedSpawning
name = sourcePool.name
}
if (habitatSettingsDTO.isActivatedSpawning) {
habitatSettingsDTO.activatedSettings?.habitatPool = selectedPool
} else {
habitatSettingsDTO.naturalSettings?.habitatPool = selectedPool
}
habitatSettingsDTO.pool = selectedPool
}
private fun validateLevelRangeInputs() {
@@ -8,11 +8,9 @@
package com.cobblemon.mod.common.client.gui.habitat
import com.cobblemon.mod.common.api.habitats.ActivatedHabitatSpawn
import com.cobblemon.mod.common.api.habitats.NaturalHabitatSpawn
import com.cobblemon.mod.common.api.habitats.HabitatSpawn
import com.cobblemon.mod.common.api.habitats.dto.HabitatPoolSettingsDTO
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.util.adapters.IntRangesAdapter
@@ -28,7 +26,7 @@ import net.minecraft.network.chat.MutableComponent
class HabitatPoolEditorGUI(
val pool: HabitatPoolSettingsDTO,
val isActivated: Boolean,
val buckets: List<SpawnBucket>,
val buckets: List<String>,
val spawnablePositionTypes: List<String>,
val maxPokemonLevel: Int,
val parentScreen: Screen,
@@ -73,7 +71,7 @@ class HabitatPoolEditorGUI(
}
// Headers
val colWidth = if (isActivated) buttonWidth / 5 else buttonWidth / 6
val colWidth = buttonWidth / 6
var x = 10
headers.add(lang("ui.edit.habitat.pool.species") to x)
x += colWidth
@@ -84,16 +82,12 @@ class HabitatPoolEditorGUI(
headers.add(lang("ui.edit.habitat.pool.position") to x)
x += colWidth
headers.add(lang("ui.edit.habitat.pool.weight") to x)
if (!isActivated) {
x += colWidth - 10
headers.add(lang("ui.edit.habitat.pool.bucket") to x)
}
x += colWidth - 10
headers.add(lang("ui.edit.habitat.pool.bucket") to x)
y += 20
var spawns = pool.spawns!!
if (!isActivated) {
spawns = spawns.sortedBy { (it as? NaturalHabitatSpawn)?.bucket?.name?.first() }.toMutableList()
}
spawns = spawns.sortedBy { it.bucket }.toMutableList()
spawns.forEachIndexed { index, spawn ->
val rowY = y
@@ -109,14 +103,14 @@ class HabitatPoolEditorGUI(
phases = spawn.phases?.let { IntRangesAdapter.basic.serialize(it) } ?: "All",
position = spawn.spawnablePositionType,
weight = spawn.weight.toString(),
bucket = if (!isActivated && spawn is NaturalHabitatSpawn) spawn.bucket.name.first().uppercaseChar().toString() else null,
bucket = spawn.bucket.first().uppercaseChar().toString(),
y = rowY
))
if (editable) {
addOptionWidget(
Button.builder("E".text()) {
Minecraft.getInstance().setScreen(HabitatSpawnEditorGUI(spawn, isActivated, maxPokemonLevel, buckets, spawnablePositionTypes, this))
Minecraft.getInstance().setScreen(HabitatSpawnEditorGUI(spawn, maxPokemonLevel, buckets, spawnablePositionTypes, this))
}.bounds(getScaledWidth() - 55, rowY, 20, 20).build()
)
@@ -134,12 +128,10 @@ class HabitatPoolEditorGUI(
if (editable) {
addOptionWidget(
Button.builder(lang("ui.edit.habitat.pool.add_spawn")) {
val newSpawn = if (isActivated) ActivatedHabitatSpawn() else NaturalHabitatSpawn()
val newSpawn = HabitatSpawn()
newSpawn.species = PokemonSpecies.implemented.random()
newSpawn.spawnablePositionType = spawnablePositionTypes.firstOrNull() ?: "grounded"
if (newSpawn is NaturalHabitatSpawn) {
newSpawn.bucket = buckets.firstOrNull() ?: return@builder
}
newSpawn.bucket = buckets.firstOrNull() ?: return@builder
pool.spawns!!.add(newSpawn)
rebuildWidgets()
}.bounds(10, y, buttonWidth, 20).build()
@@ -179,7 +171,7 @@ class HabitatPoolEditorGUI(
guiGraphics.drawCenteredString(font, title, width / 2, 8, 0xFFFFFF)
val buttonWidth = getScaledWidth() - 20
val colWidth = if (isActivated) buttonWidth / 5 else buttonWidth / 6
val colWidth = buttonWidth / 6
val optionsTop = getOptionsTop()
val optionsBottom = getOptionsBottom()
@@ -8,8 +8,7 @@
package com.cobblemon.mod.common.client.gui.habitat
import com.cobblemon.mod.common.api.habitats.ActivatedHabitatPool
import com.cobblemon.mod.common.api.habitats.NaturalHabitatPool
import com.cobblemon.mod.common.api.habitats.HabitatPool
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.util.asTranslated
@@ -22,13 +21,11 @@ import net.minecraft.client.gui.components.ObjectSelectionList
import net.minecraft.client.gui.screens.Screen
import net.minecraft.client.resources.language.I18n
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.resources.ResourceLocation
class HabitatPoolSelectionGUI(
private val isActivated: Boolean,
private val activatedPools: Map<ResourceLocation, ActivatedHabitatPool>,
private val naturalPools: Map<ResourceLocation, NaturalHabitatPool>,
private val pools: Map<ResourceLocation, HabitatPool>,
private val selectedPoolId: ResourceLocation?,
private val parentScreen: Screen,
private val onSelect: (ResourceLocation?) -> Unit
@@ -85,11 +82,7 @@ class HabitatPoolSelectionGUI(
private fun getOptions(): List<Pair<ResourceLocation?, String>> {
val options = mutableListOf<Pair<ResourceLocation?, String>>()
options.add(null to I18n.get("cobblemon.ui.edit.habitat.pool.custom"))
val pools = if (isActivated) {
activatedPools.entries.map { it.key to I18n.get(it.value.name) }
} else {
naturalPools.entries.map { it.key to I18n.get(it.value.name) }
}.sortedBy { it.second.lowercase() }
val pools = pools.entries.map { it.key to I18n.get(it.value.name) }.sortedBy { it.second.lowercase() }
options.addAll(pools)
return options
}
@@ -9,11 +9,9 @@
package com.cobblemon.mod.common.client.gui.habitat
import com.cobblemon.mod.common.api.habitats.HabitatSpawn
import com.cobblemon.mod.common.api.habitats.NaturalHabitatSpawn
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.spawning.IntRanges
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.TimeRange
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
@@ -32,9 +30,8 @@ import net.minecraft.network.chat.MutableComponent
class HabitatSpawnEditorGUI(
val spawn: HabitatSpawn,
val isActivated: Boolean,
val maxPokemonLevel: Int,
val buckets: List<SpawnBucket>,
val buckets: List<String>,
val spawnablePositionTypes: List<String>,
val parentScreen: Screen
) : Screen(lang("ui.edit.habitat.spawn")), CobblemonRenderable {
@@ -110,16 +107,14 @@ class HabitatSpawnEditorGUI(
)
y += spacing
if (spawn is NaturalHabitatSpawn) {
bucketButton = addOptionWidget(
Button.builder(lang("ui.edit.habitat.spawn.bucket", spawn.bucket.name)) {
val currentIndex = buckets.indexOf(spawn.bucket)
spawn.bucket = buckets[(currentIndex + 1) % buckets.size]
refresh()
}.bounds(10, y, buttonWidth, 20).build()
)
y += spacing
}
bucketButton = addOptionWidget(
Button.builder(lang("ui.edit.habitat.spawn.bucket", spawn.bucket)) {
val currentIndex = buckets.indexOf(spawn.bucket)
spawn.bucket = buckets[(currentIndex + 1) % buckets.size]
refresh()
}.bounds(10, y, buttonWidth, 20).build()
)
y += spacing
labels.add(lang("ui.edit.habitat.spawn.weight") to (10 to y))
y += 12
@@ -309,9 +304,7 @@ class HabitatSpawnEditorGUI(
fun refresh() {
positionTypeButton.message = lang("ui.edit.habitat.spawn.position_type", spawn.spawnablePositionType)
if (spawn is NaturalHabitatSpawn) {
bucketButton.message = lang("ui.edit.habitat.spawn.bucket", spawn.bucket.name)
}
bucketButton.message = lang("ui.edit.habitat.spawn.bucket", spawn.bucket)
}
// data validation for error handling
@@ -22,9 +22,9 @@ object OpenHabitatBlockEditorHandler : ClientNetworkPacketHandler<OpenHabitatBlo
currentPhase = packet.currentPhase,
maxPokemonLevel = packet.maxPokemonLevel,
spawnablePositionTypes = packet.spawnablePositionTypes,
buckets = packet.buckets,
activatedHabitatPools = packet.activatedHabitatPools,
naturalHabitatPools = packet.naturalHabitatPools,
worldBuckets = packet.worldBuckets,
activatedHabitatBuckets = packet.activatedHabitatBuckets,
pools = packet.habitatPools,
habitatSettingsDTO = packet.habitatSettingsDTO
)
)
@@ -9,7 +9,6 @@
package com.cobblemon.mod.common.command.argument
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.util.lang
import com.mojang.brigadier.StringReader
import com.mojang.brigadier.arguments.ArgumentType
@@ -17,25 +16,25 @@ import com.mojang.brigadier.context.CommandContext
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType
import com.mojang.brigadier.suggestion.Suggestions
import com.mojang.brigadier.suggestion.SuggestionsBuilder
import net.minecraft.commands.SharedSuggestionProvider
import java.util.concurrent.CompletableFuture
import net.minecraft.commands.SharedSuggestionProvider
class SpawnBucketArgumentType: ArgumentType<SpawnBucket> {
class SpawnBucketArgumentType: ArgumentType<String> {
companion object {
val EXAMPLES: List<String> = listOf(Cobblemon.bestSpawner.config.buckets.first().name)
val EXAMPLES: List<String> = listOf(Cobblemon.bestSpawner.config.worldBuckets.keys.first())
val INVALID_BUCKET = lang("command.checkspawns.invalid-bucket")
fun spawnBucket() = SpawnBucketArgumentType()
fun <S> getSpawnBucket(context: CommandContext<S>, name: String): SpawnBucket {
return context.getArgument(name, SpawnBucket::class.java)
fun <S> getSpawnBucket(context: CommandContext<S>, name: String): String {
return context.getArgument(name, String::class.java)
}
}
override fun parse(reader: StringReader): SpawnBucket {
override fun parse(reader: StringReader): String {
val name = reader.readString()
return Cobblemon.bestSpawner.config.buckets.find { it.name.equals(name, ignoreCase = true) }
return Cobblemon.bestSpawner.config.worldBuckets.entries.find { it.key.equals(name, ignoreCase = true) }?.key
?: throw SimpleCommandExceptionType(INVALID_BUCKET).createWithContext(reader)
}
@@ -43,7 +42,7 @@ class SpawnBucketArgumentType: ArgumentType<SpawnBucket> {
context: CommandContext<S>,
builder: SuggestionsBuilder
): CompletableFuture<Suggestions> {
return SharedSuggestionProvider.suggest(Cobblemon.bestSpawner.config.buckets.map { it.name }, builder)
return SharedSuggestionProvider.suggest(Cobblemon.bestSpawner.config.worldBuckets.keys, builder)
}
override fun getExamples() = EXAMPLES
@@ -39,7 +39,6 @@ import net.minecraft.advancements.CriteriaTriggers
import net.minecraft.client.resources.sounds.EntityBoundSoundInstance
import net.minecraft.core.BlockPos
import net.minecraft.core.particles.ParticleTypes
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.core.registries.Registries
import net.minecraft.network.protocol.game.ClientboundAddEntityPacket
import net.minecraft.network.syncher.EntityDataAccessor
@@ -248,13 +247,6 @@ class PokeRodFishingBobberEntity(type: EntityType<out PokeRodFishingBobberEntity
INVALID
}
private fun setPlayerFishHook(fishingBobber: FishingHook?) {
val playerEntity = this.playerOwner
if (playerEntity != null) {
playerEntity.fishing = fishingBobber
}
}
enum class State {
FLYING,
HOOKED_IN_ENTITY,
@@ -375,7 +367,7 @@ class PokeRodFishingBobberEntity(type: EntityType<out PokeRodFishingBobberEntity
.sumOf { it.value }
.toInt()
planSpawn(player, rodStack, stackedLureTier)
val selectedWeight = plannedSpawnAction?.bucket?.weight ?: 50F
val selectedWeight = plannedSpawnAction?.bucket?.let { BestSpawner.fishingSpawner.buckets[it] } ?: 50F
val reactionMinMax = calculateMinMaxCountdown(selectedWeight)
// set the hook reaction time to be based off the rarity of the bucket chosen
@@ -9,12 +9,10 @@
package com.cobblemon.mod.common.net.messages.client.habitat
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.habitats.ActivatedHabitatPool
import com.cobblemon.mod.common.api.habitats.HabitatPool
import com.cobblemon.mod.common.api.habitats.HabitatPools
import com.cobblemon.mod.common.api.habitats.NaturalHabitatPool
import com.cobblemon.mod.common.api.habitats.dto.HabitatSettingsDTO
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.block.habitat.HabitatBlockEntity
import com.cobblemon.mod.common.net.IntSize
@@ -38,9 +36,9 @@ class OpenHabitatBlockEditorPacket(
val currentPhase: Int,
val maxPokemonLevel: Int,
val spawnablePositionTypes: List<String>,
val buckets: List<SpawnBucket>,
val activatedHabitatPools: Map<ResourceLocation, ActivatedHabitatPool>,
val naturalHabitatPools: Map<ResourceLocation, NaturalHabitatPool>,
val worldBuckets: List<String>,
val activatedHabitatBuckets: List<String>,
val habitatPools: Map<ResourceLocation, HabitatPool>,
val habitatSettingsDTO: HabitatSettingsDTO
) : NetworkPacket<OpenHabitatBlockEditorPacket> {
companion object {
@@ -51,40 +49,28 @@ class OpenHabitatBlockEditorPacket(
val currentPhase = buffer.readInt()
val maxPokemonLevel = buffer.readInt()
val spawnablePositionTypes = buffer.readList { buffer.readUtf() }
val buckets = buffer.readList {
val name = buffer.readUtf()
val weight = buffer.readFloat()
SpawnBucket(name = name, weight = weight)
}
val activatedHabitatPools = buffer.readMapK(
val worldBuckets = buffer.readList { buffer.readUtf() }
val activatedHabitatBuckets = buffer.readList { buffer.readUtf() }
val habitatPools = buffer.readMapK(
size = IntSize.U_SHORT,
mutableMapOf(),
entryReader = {
val pool = ActivatedHabitatPool()
pool.decode(buffer, buckets)
pool.id to pool
}
)
val naturalHabitatPools = buffer.readMapK(
size = IntSize.U_SHORT,
mutableMapOf(),
entryReader = {
val pool = NaturalHabitatPool()
pool.decode(buffer, buckets)
val pool = HabitatPool()
pool.decode(buffer)
pool.id to pool
}
)
val habitatSettingsDTO = HabitatSettingsDTO()
habitatSettingsDTO.decode(buffer, buckets)
habitatSettingsDTO.decode(buffer)
return OpenHabitatBlockEditorPacket(
blockPos = blockPos,
currentlySpawned = currentlySpawned,
currentPhase = currentPhase,
maxPokemonLevel = maxPokemonLevel,
spawnablePositionTypes = spawnablePositionTypes,
buckets = buckets,
activatedHabitatPools = activatedHabitatPools,
naturalHabitatPools = naturalHabitatPools,
worldBuckets = worldBuckets,
activatedHabitatBuckets = activatedHabitatBuckets,
habitatPools = habitatPools,
habitatSettingsDTO = habitatSettingsDTO
)
}
@@ -96,10 +82,10 @@ class OpenHabitatBlockEditorPacket(
currentPhase = habitatBlockEntity.currentPhase,
maxPokemonLevel = Cobblemon.config.maxPokemonLevel,
spawnablePositionTypes = SpawnablePosition.spawnablePositionTypes.map { it.name },
buckets = Cobblemon.bestSpawner.config.buckets,
worldBuckets = Cobblemon.bestSpawner.config.worldBuckets.keys.toList(),
activatedHabitatBuckets = Cobblemon.bestSpawner.config.activatedHabitatBuckets.keys.toList(),
habitatSettingsDTO = HabitatSettingsDTO(habitatBlockEntity),
activatedHabitatPools = HabitatPools.habitatPoolsById.entries.filter { it.value is ActivatedHabitatPool }.associate { it.key to it.value as ActivatedHabitatPool },
naturalHabitatPools = HabitatPools.habitatPoolsById.entries.filter { it.value is NaturalHabitatPool }.associate { it.key to it.value as NaturalHabitatPool }
habitatPools = HabitatPools.habitatPoolsById.toMap(),
)
override val id = ID
@@ -110,18 +96,11 @@ class OpenHabitatBlockEditorPacket(
buffer.writeInt(currentPhase)
buffer.writeInt(maxPokemonLevel)
buffer.writeCollection(spawnablePositionTypes) { _, it -> buffer.writeUtf(it) }
buffer.writeCollection(buckets) { _, bucket ->
buffer.writeUtf(bucket.name)
buffer.writeFloat(bucket.weight)
}
buffer.writeCollection(worldBuckets) { _, bucket -> buffer.writeUtf(bucket) }
buffer.writeCollection(activatedHabitatBuckets) { _, bucket -> buffer.writeUtf(bucket) }
buffer.writeMapK(
size = IntSize.U_SHORT,
map = activatedHabitatPools,
entryWriter = { (_, value) -> value.encode(buffer) },
)
buffer.writeMapK(
size = IntSize.U_SHORT,
map = naturalHabitatPools,
map = habitatPools,
entryWriter = { (_, value) -> value.encode(buffer) },
)
habitatSettingsDTO.encode(buffer)
@@ -8,7 +8,6 @@
package com.cobblemon.mod.common.net.messages.server.habitat
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.habitats.dto.HabitatSettingsDTO
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.util.cobblemonResource
@@ -30,10 +29,7 @@ class SaveHabitatBlockSettingsPacket(
fun decode(buffer: RegistryFriendlyByteBuf): SaveHabitatBlockSettingsPacket {
val blockPos = buffer.readBlockPos()
val settings = HabitatSettingsDTO()
settings.decode(
buffer = buffer,
buckets = Cobblemon.bestSpawner.config.buckets
)
settings.decode(buffer)
return SaveHabitatBlockSettingsPacket(blockPos, settings)
}
}
@@ -1,32 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.util.adapters
import com.cobblemon.mod.common.api.habitats.HabitatPool
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import java.lang.reflect.Type
/**
* Simple map adapter for which type of HabitatPool is being deserialized.
*
* @author Hiroku
* @since February 13th, 2026
*/
object HabitatPoolAdapter : JsonDeserializer<HabitatPool<*>> {
override fun deserialize(json: JsonElement, t: Type, ctx: JsonDeserializationContext): HabitatPool<*>? {
json as JsonObject
val type = json.get("type").asString.asIdentifierDefaultingNamespace()
val clazz = HabitatPool.types[type] ?: throw IllegalArgumentException("Unknown habitat pool type: $type")
return ctx.deserialize(json, clazz)
}
}
@@ -1,34 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.util.adapters
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.JsonPrimitive
import com.google.gson.JsonSerializationContext
import com.google.gson.JsonSerializer
import java.lang.reflect.Type
/**
* Spawn bucket adapter that searches the config for a matching named element to deserialize. The serialized form
* is simply the name of the bucket.
*
* @author Hiroku
* @since June 20th, 2022
*/
object SpawnBucketAdapter : JsonDeserializer<SpawnBucket>, JsonSerializer<SpawnBucket> {
override fun serialize(bucket: SpawnBucket, type: Type, ctx: JsonSerializationContext) = JsonPrimitive(bucket.name)
override fun deserialize(json: JsonElement, type: Type, ctx: JsonDeserializationContext): SpawnBucket {
return Cobblemon.bestSpawner.config.buckets.find { it.name == json.asString }
?: throw IllegalStateException("Spawn referred to invalid spawn bucket: ${json.asString}. Is it missing from the config?")
}
}
@@ -12,8 +12,8 @@ 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.position.SpawnablePosition
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.spawning.preset.SpawnDetailPreset
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.singularToPluralList
@@ -76,7 +76,7 @@ object SpawnDetailAdapter : JsonDeserializer<SpawnDetail> {
?: throw IllegalStateException("There is no spawning condition registered with the name '${ctxType.defaultCondition}'")
val detail = registeredSpawnDetail.deserializeDetail(element, ctx)
presets.forEach { it.apply(detail) }
if (detail.bucket.name.isBlank()) {
if (detail.bucket.isBlank()) {
throw IllegalStateException("No bucket was specified for spawn: ${detail.id}")
}
detail.autoLabel()
@@ -6,22 +6,29 @@
"submerged": 1.0,
"surface": 1.0
},
"buckets": [
{
"name": "common",
"weight": 94.3
},
{
"name": "uncommon",
"weight": 5
},
{
"name": "rare",
"weight": 0.5
},
{
"name": "ultra-rare",
"weight": 0.2
}
]
"worldBuckets": {
"common": 94.0,
"uncommon": 5.0,
"rare": 0.5,
"ultra-rare": 0.2,
"boss": 0.3
},
"fishingBuckets": {
"common": 83.25,
"uncommon": 11.25,
"rare": 4.125,
"ultra-rare": 1.375
},
"pokeSnackBuckets": {
"common": 83.25,
"uncommon": 11.25,
"rare": 4.125,
"ultra-rare": 1.375
},
"activatedHabitatBuckets": {
"common": 83.25,
"uncommon": 11.25,
"rare": 4.125,
"ultra-rare": 1.375
}
}