From 393c9d62d10506be37d332f2dbf40e6da040927f Mon Sep 17 00:00:00 2001 From: Will FP Date: Tue, 28 Nov 2023 13:40:35 +0000 Subject: [PATCH] Added custom spawn method --- build.gradle.kts | 1 + .../com/willfp/ecomobs/EcoMobsPlugin.kt | 3 + .../category/impl/ConfigDrivenMobCategory.kt | 7 +- .../category/spawning/SpawnMethodFactories.kt | 2 + .../category/spawning/SpawnMethodFactory.kt | 8 +- .../spawning/impl/SpawnMethodFactoryCustom.kt | 83 +++++++++++++++++++ .../spawning/impl/SpawnMethodFactoryNone.kt | 8 +- .../impl/SpawnMethodFactoryReplace.kt | 8 +- .../spawning/spawnpoints/SpawnPoint.kt | 20 +++++ .../spawnpoints/SpawnPointGenerator.kt | 77 +++++++++++++++++ .../spawning/spawnpoints/SpawnPointType.kt | 6 ++ .../kotlin/com/willfp/ecomobs/math/Int3.kt | 67 +++++++++++++++ .../main/resources/categories/_example.yml | 8 ++ .../core-plugin/src/main/resources/config.yml | 14 ++++ 14 files changed, 308 insertions(+), 4 deletions(-) create mode 100644 eco-core/core-plugin/src/main/kotlin/com/willfp/ecomobs/category/spawning/impl/SpawnMethodFactoryCustom.kt create mode 100644 eco-core/core-plugin/src/main/kotlin/com/willfp/ecomobs/category/spawning/spawnpoints/SpawnPoint.kt create mode 100644 eco-core/core-plugin/src/main/kotlin/com/willfp/ecomobs/category/spawning/spawnpoints/SpawnPointGenerator.kt create mode 100644 eco-core/core-plugin/src/main/kotlin/com/willfp/ecomobs/category/spawning/spawnpoints/SpawnPointType.kt create mode 100644 eco-core/core-plugin/src/main/kotlin/com/willfp/ecomobs/math/Int3.kt diff --git a/build.gradle.kts b/build.gradle.kts index 4eb7e24..5f18f9f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -40,6 +40,7 @@ allprojects { compileOnly("com.willfp:eco:6.65.0") compileOnly("org.jetbrains:annotations:23.0.0") compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.7.10") + compileOnly("com.github.ben-manes.caffeine:caffeine:3.1.5") } java { diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecomobs/EcoMobsPlugin.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecomobs/EcoMobsPlugin.kt index fa55b4c..44a6e6a 100644 --- a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecomobs/EcoMobsPlugin.kt +++ b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecomobs/EcoMobsPlugin.kt @@ -5,6 +5,7 @@ import com.willfp.eco.core.display.DisplayModule import com.willfp.eco.core.entities.ai.EntityGoals import com.willfp.eco.core.integrations.IntegrationLoader import com.willfp.ecomobs.category.MobCategories +import com.willfp.ecomobs.category.spawning.spawnpoints.SpawnPointGenerator import com.willfp.ecomobs.commands.CommandEcoMobs import com.willfp.ecomobs.display.SpawnEggDisplay import com.willfp.ecomobs.goals.entity.EntityGoalRandomTeleport @@ -25,6 +26,8 @@ internal lateinit var plugin: EcoMobsPlugin private set class EcoMobsPlugin : LibreforgePlugin() { + val spawnPointGenerator = SpawnPointGenerator(this) + init { plugin = this } diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecomobs/category/impl/ConfigDrivenMobCategory.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecomobs/category/impl/ConfigDrivenMobCategory.kt index 6c685ee..dfeabfe 100644 --- a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecomobs/category/impl/ConfigDrivenMobCategory.kt +++ b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecomobs/category/impl/ConfigDrivenMobCategory.kt @@ -15,7 +15,12 @@ class ConfigDrivenMobCategory( private val context: ViolationContext ) : MobCategory { override val spawnMethod = SpawnMethodFactories[config.getString("spawning.type")] - ?.create(this, config.getSubsection("spawning.${config.getString("spawning.type")}"), plugin) + ?.create( + this, + config.getSubsection("spawning.${config.getString("spawning.type")}"), + plugin, + context.with("spawning").with(config.getString("spawning.type")) + ) ?: throw ConfigViolationException( ConfigViolation( "type", diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecomobs/category/spawning/SpawnMethodFactories.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecomobs/category/spawning/SpawnMethodFactories.kt index 9a02fa5..38631e8 100644 --- a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecomobs/category/spawning/SpawnMethodFactories.kt +++ b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecomobs/category/spawning/SpawnMethodFactories.kt @@ -1,6 +1,7 @@ package com.willfp.ecomobs.category.spawning import com.willfp.eco.core.registry.Registry +import com.willfp.ecomobs.category.spawning.impl.SpawnMethodFactoryCustom import com.willfp.ecomobs.category.spawning.impl.SpawnMethodFactoryNone import com.willfp.ecomobs.category.spawning.impl.SpawnMethodFactoryReplace @@ -8,5 +9,6 @@ object SpawnMethodFactories : Registry() { init { register(SpawnMethodFactoryReplace) register(SpawnMethodFactoryNone) + register(SpawnMethodFactoryCustom) } } diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecomobs/category/spawning/SpawnMethodFactory.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecomobs/category/spawning/SpawnMethodFactory.kt index b58839f..c2d0df1 100644 --- a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecomobs/category/spawning/SpawnMethodFactory.kt +++ b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecomobs/category/spawning/SpawnMethodFactory.kt @@ -4,7 +4,13 @@ import com.willfp.eco.core.EcoPlugin import com.willfp.eco.core.config.interfaces.Config import com.willfp.eco.core.registry.KRegistrable import com.willfp.ecomobs.category.MobCategory +import com.willfp.libreforge.ViolationContext abstract class SpawnMethodFactory(override val id: String) : KRegistrable { - abstract fun create(category: MobCategory, config: Config, plugin: EcoPlugin): SpawnMethod + abstract fun create( + category: MobCategory, + config: Config, + plugin: EcoPlugin, + context: ViolationContext + ): SpawnMethod } diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecomobs/category/spawning/impl/SpawnMethodFactoryCustom.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecomobs/category/spawning/impl/SpawnMethodFactoryCustom.kt new file mode 100644 index 0000000..e233dc5 --- /dev/null +++ b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecomobs/category/spawning/impl/SpawnMethodFactoryCustom.kt @@ -0,0 +1,83 @@ +package com.willfp.ecomobs.category.spawning.impl + +import com.willfp.eco.core.EcoPlugin +import com.willfp.eco.core.config.interfaces.Config +import com.willfp.eco.core.entities.Entities +import com.willfp.eco.util.randDouble +import com.willfp.ecomobs.category.MobCategory +import com.willfp.ecomobs.category.spawning.SpawnMethod +import com.willfp.ecomobs.category.spawning.SpawnMethodFactory +import com.willfp.ecomobs.category.spawning.spawnpoints.SpawnPointType +import com.willfp.ecomobs.category.spawning.spawnpoints.spawnPoints +import com.willfp.ecomobs.mob.SpawnReason +import com.willfp.libreforge.EmptyProvidedHolder +import com.willfp.libreforge.ViolationContext +import com.willfp.libreforge.conditions.Conditions +import com.willfp.libreforge.enumValueOfOrNull +import com.willfp.libreforge.toDispatcher +import org.bukkit.Bukkit +import org.bukkit.event.EventHandler +import org.bukkit.event.EventPriority +import org.bukkit.event.Listener +import org.bukkit.event.entity.CreatureSpawnEvent +import org.bukkit.scheduler.BukkitTask + +object SpawnMethodFactoryCustom : SpawnMethodFactory("custom") { + override fun create( + category: MobCategory, + config: Config, + plugin: EcoPlugin, + context: ViolationContext + ): SpawnMethod { + return SpawnMethodCustom(category, config, plugin, context) + } + + class SpawnMethodCustom( + category: MobCategory, + config: Config, + plugin: EcoPlugin, + context: ViolationContext + ) : SpawnMethod(category, config, plugin), Listener { + private val spawnRate = plugin.configYml.getInt("custom-spawning.spawn-rate").toLong() + + private val spawnTypes = config.getStrings("spawn-types") + .mapNotNull { enumValueOfOrNull(it.uppercase()) } + .toSet() + + private val conditions = Conditions.compile( + config.getSubsections("conditions"), + context.with("conditions") + ) + + private val chance = config.getDouble("chance") + + private var task: BukkitTask? = null + + override fun onStart() { + task = plugin.scheduler.runTimer(spawnRate, spawnRate) { + tick() + } + } + + override fun onStop() { + task?.cancel() + } + + private fun tick() { + for (player in Bukkit.getOnlinePlayers()) { + for (point in player.spawnPoints.filter { it.type in spawnTypes }) { + if (randDouble(0.0, 100.0) > chance) { + continue + } + + if (!conditions.areMet(point.location.toDispatcher(), EmptyProvidedHolder)) { + continue + } + + val mob = category.mobs.randomOrNull() ?: continue + mob.spawn(point.location, SpawnReason.NATURAL) + } + } + } + } +} diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecomobs/category/spawning/impl/SpawnMethodFactoryNone.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecomobs/category/spawning/impl/SpawnMethodFactoryNone.kt index dc7a984..2d94196 100644 --- a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecomobs/category/spawning/impl/SpawnMethodFactoryNone.kt +++ b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecomobs/category/spawning/impl/SpawnMethodFactoryNone.kt @@ -7,6 +7,7 @@ import com.willfp.ecomobs.category.MobCategory import com.willfp.ecomobs.category.spawning.SpawnMethod import com.willfp.ecomobs.category.spawning.SpawnMethodFactory import com.willfp.ecomobs.mob.SpawnReason +import com.willfp.libreforge.ViolationContext import com.willfp.libreforge.enumValueOfOrNull import org.bukkit.entity.EntityType import org.bukkit.event.EventHandler @@ -15,7 +16,12 @@ import org.bukkit.event.Listener import org.bukkit.event.entity.CreatureSpawnEvent object SpawnMethodFactoryNone : SpawnMethodFactory("replace") { - override fun create(category: MobCategory, config: Config, plugin: EcoPlugin): SpawnMethod { + override fun create( + category: MobCategory, + config: Config, + plugin: EcoPlugin, + context: ViolationContext + ): SpawnMethod { return SpawnMethodNone(category, config, plugin) } diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecomobs/category/spawning/impl/SpawnMethodFactoryReplace.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecomobs/category/spawning/impl/SpawnMethodFactoryReplace.kt index 0170eba..d4e6e35 100644 --- a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecomobs/category/spawning/impl/SpawnMethodFactoryReplace.kt +++ b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecomobs/category/spawning/impl/SpawnMethodFactoryReplace.kt @@ -7,6 +7,7 @@ import com.willfp.ecomobs.category.MobCategory import com.willfp.ecomobs.category.spawning.SpawnMethod import com.willfp.ecomobs.category.spawning.SpawnMethodFactory import com.willfp.ecomobs.mob.SpawnReason +import com.willfp.libreforge.ViolationContext import com.willfp.libreforge.enumValueOfOrNull import org.bukkit.entity.EntityType import org.bukkit.event.EventHandler @@ -15,7 +16,12 @@ import org.bukkit.event.Listener import org.bukkit.event.entity.CreatureSpawnEvent object SpawnMethodFactoryReplace : SpawnMethodFactory("replace") { - override fun create(category: MobCategory, config: Config, plugin: EcoPlugin): SpawnMethod { + override fun create( + category: MobCategory, + config: Config, + plugin: EcoPlugin, + context: ViolationContext + ): SpawnMethod { return SpawnMethodReplace(category, config, plugin) } diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecomobs/category/spawning/spawnpoints/SpawnPoint.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecomobs/category/spawning/spawnpoints/SpawnPoint.kt new file mode 100644 index 0000000..548cbd7 --- /dev/null +++ b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecomobs/category/spawning/spawnpoints/SpawnPoint.kt @@ -0,0 +1,20 @@ +package com.willfp.ecomobs.category.spawning.spawnpoints + +import org.bukkit.Location + +class SpawnPoint( + val location: Location, + val type: SpawnPointType +) { + override fun hashCode(): Int { + var result = location.hashCode() + result = 31 * result + type.hashCode() + return result + } + + override fun equals(other: Any?): Boolean { + return other is SpawnPoint && + this.location == other.location && + this.type == other.type + } +} diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecomobs/category/spawning/spawnpoints/SpawnPointGenerator.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecomobs/category/spawning/spawnpoints/SpawnPointGenerator.kt new file mode 100644 index 0000000..0863f9e --- /dev/null +++ b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecomobs/category/spawning/spawnpoints/SpawnPointGenerator.kt @@ -0,0 +1,77 @@ +package com.willfp.ecomobs.category.spawning.spawnpoints + +import com.github.benmanes.caffeine.cache.Caffeine +import com.willfp.eco.core.EcoPlugin +import com.willfp.ecomobs.math.Int3 +import com.willfp.ecomobs.plugin +import org.bukkit.Material +import org.bukkit.entity.Player +import java.util.UUID +import java.util.concurrent.TimeUnit + +val spawnPointCache = Caffeine.newBuilder() + .expireAfterWrite(2, TimeUnit.SECONDS) + .build>() + +val Player.spawnPoints: Set + get() = spawnPointCache.get(this.uniqueId) { + plugin.spawnPointGenerator.generate(this) + } + +class SpawnPointGenerator( + private val plugin: EcoPlugin +) { + private val radius = plugin.configYml.getInt("custom-spawning.radius-around-player") + private val max = plugin.configYml.getInt("custom-spawning.max-points-per-player") + private val maxAttempts = plugin.configYml.getInt("custom-spawning.max-attempts") + + fun generate(player: Player): Set { + val points = mutableSetOf() + + for (i in 1..max) { + val point = generatePoint(player) ?: continue + points.add(point) + } + + return points + } + + private fun generatePoint(player: Player): SpawnPoint? { + val playerLocation = player.location + val world = playerLocation.world ?: return null + + val bottomCorner = Int3( + playerLocation.x.toInt() - radius, playerLocation.y.toInt() - radius, playerLocation.z.toInt() - radius + ) + + val topCorner = Int3( + playerLocation.x.toInt() + radius, playerLocation.y.toInt() + radius, playerLocation.z.toInt() + radius + ) + + var attempts = 0 + val iterator = (bottomCorner..topCorner).randomIterator() + + while (attempts < maxAttempts) { + attempts++ + val (x, y, z) = iterator.next() + + val block = world.getBlockAt(x, y, z) + val blockAbove = world.getBlockAt(x, y + 1, z) + val blockBelow = world.getBlockAt(x, y - 1, z) + + if (blockAbove.isSolid) { + continue + } + + if (block.isPassable && blockAbove.isPassable && blockBelow.type.isSolid) { + return SpawnPoint(block.location.add(0.5, 0.5, 0.5), SpawnPointType.LAND) + } + + if (block.type == Material.WATER && blockAbove.type == Material.WATER) { + return SpawnPoint(block.location.add(0.5, 0.5, 0.5), SpawnPointType.WATER) + } + } + + return null + } +} diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecomobs/category/spawning/spawnpoints/SpawnPointType.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecomobs/category/spawning/spawnpoints/SpawnPointType.kt new file mode 100644 index 0000000..11ba9b9 --- /dev/null +++ b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecomobs/category/spawning/spawnpoints/SpawnPointType.kt @@ -0,0 +1,6 @@ +package com.willfp.ecomobs.category.spawning.spawnpoints + +enum class SpawnPointType { + LAND, + WATER +} diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecomobs/math/Int3.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecomobs/math/Int3.kt new file mode 100644 index 0000000..14f0533 --- /dev/null +++ b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecomobs/math/Int3.kt @@ -0,0 +1,67 @@ +package com.willfp.ecomobs.math + +data class Int3( + val x: Int, + val y: Int, + val z: Int +) { + operator fun rangeTo(other: Int3): Int3Range { + return Int3Range(this, other) + } +} + +data class Int3Range( + val start: Int3, + val end: Int3 +) : Iterable { + override fun iterator(): Iterator { + return Int3Iterator(this) + } + + fun randomIterator(): Iterator { + return RandomInt3Iterator(this) + } +} + +class Int3Iterator( + private val range: Int3Range +) : Iterator { + private var current = range.start + + override fun hasNext(): Boolean { + return current.x <= range.end.x && + current.y <= range.end.y && + current.z <= range.end.z + } + + override fun next(): Int3 { + val toReturn = current + + if (current.x < range.end.x) { + current = current.copy(x = current.x + 1) + } else if (current.y < range.end.y) { + current = current.copy(x = range.start.x, y = current.y + 1) + } else if (current.z < range.end.z) { + current = current.copy(x = range.start.x, y = range.start.y, z = current.z + 1) + } + + return toReturn + } +} + +class RandomInt3Iterator( + private val range: Int3Range +) : Iterator { + override fun hasNext(): Boolean { + return true + } + + override fun next(): Int3 { + // Generate a random Int3 within the bounds + return Int3( + range.start.x + ((range.end.x - range.start.x) * Math.random()).toInt(), + range.start.y + ((range.end.y - range.start.y) * Math.random()).toInt(), + range.start.z + ((range.end.z - range.start.z) * Math.random()).toInt() + ) + } +} diff --git a/eco-core/core-plugin/src/main/resources/categories/_example.yml b/eco-core/core-plugin/src/main/resources/categories/_example.yml index 7d9596a..8435994 100644 --- a/eco-core/core-plugin/src/main/resources/categories/_example.yml +++ b/eco-core/core-plugin/src/main/resources/categories/_example.yml @@ -29,5 +29,13 @@ spawning: # Options for custom spawning custom: + # Spawn types (choose from land, water) + spawn-types: + - land + # Conditions that the location must match in order for the mob to spawn + # Read here: https://plugins.auxilor.io/effects/configuring-a-condition + conditions: [ ] + # The chance for the mob to spawn if a valid spawn point is found (as a percentage) + chance: 100 \ No newline at end of file diff --git a/eco-core/core-plugin/src/main/resources/config.yml b/eco-core/core-plugin/src/main/resources/config.yml index b893330..79c545d 100644 --- a/eco-core/core-plugin/src/main/resources/config.yml +++ b/eco-core/core-plugin/src/main/resources/config.yml @@ -4,3 +4,17 @@ # discover-recipes: true + +custom-spawning: + # The spawn rate is the number of ticks to wait between trying to spawn a mob. + # 20 ticks = 1 second; the higher the number, the less often mobs will spawn. + spawn-rate: 5 + + # The radius to generate spawn points for around the player + radius-around-player: 32 + + # The max amount of spawn points to generate per player + max-points-per-player: 8 + + # The max amount of attempts to generate a spawn point per player + max-attempts: 64 \ No newline at end of file