Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
824d9a7
fix: Dead guards & missing spawner removal
CallumJohnson Sep 18, 2026
041bc68
feat: Global Async queue (Spawner Displays)
CallumJohnson Sep 18, 2026
4103080
fix: Memory leak & Display cancellation
CallumJohnson Sep 18, 2026
44de0ae
fix: Shared attribute handler
CallumJohnson Sep 18, 2026
c2fb18f
fix: Spawner registration (non-event triggers)
CallumJohnson Sep 18, 2026
5fd92ed
fix: EntityType normalisation
CallumJohnson Sep 18, 2026
f3a8951
chore: Dead 'listener'
CallumJohnson Sep 18, 2026
325ae84
fix: Folia Contract (mob from egg spawning)
CallumJohnson Sep 18, 2026
79f20b0
fix: Spawn method Folia contract
CallumJohnson Sep 18, 2026
7d0f699
feat: Custom spawner spawning (#91)
CallumJohnson Sep 18, 2026
d8ab3e8
fix: Collapsed EcoMobs spawning modes
CallumJohnson Sep 18, 2026
a94c375
fix: Entity budget per chunk (performance)
CallumJohnson Sep 18, 2026
12310d3
feat: Merge budget (performance)
CallumJohnson Sep 18, 2026
bc0365f
chore: Docs
CallumJohnson Sep 18, 2026
2dbe181
feat: Adopting Vanilla Spawners
CallumJohnson Sep 18, 2026
e69e559
fix: Stacked mobs loot replications
CallumJohnson Sep 18, 2026
4ea2ee5
fix: No AI
CallumJohnson Sep 18, 2026
bd35464
fix: Hologram on Single stack
CallumJohnson Sep 18, 2026
6fd013b
Merge branch 'master' into fix/various_issues
CallumJohnson Sep 26, 2026
c5eb3d8
chore: Updated configs & docs
CallumJohnson Sep 26, 2026
4d85e1c
feat: Configurable Vanilla Spawning
CallumJohnson Sep 26, 2026
a2479e3
fix: delay in subcommand
CallumJohnson Sep 26, 2026
de5a1a6
chore: Docs
CallumJohnson Sep 26, 2026
2f72785
fix: Spigot compatibility
CallumJohnson Sep 26, 2026
a1e8c30
fix: LoS on Mob nameplate
CallumJohnson Sep 26, 2026
3e4407c
feat: Disallow/Allow Jockeys from Spawners
CallumJohnson Sep 26, 2026
cd54707
feat: Pick-block Spigot compat
CallumJohnson Sep 26, 2026
dcb0d24
chore: comment in Config
CallumJohnson Sep 26, 2026
97c42ea
feat: Water Sensitivity
CallumJohnson Sep 26, 2026
1261b87
feat: Sunlight Sensitivity
CallumJohnson Sep 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ allprojects {
maven("https://repo.auxilor.io/repository/maven-public/")
maven("https://mvn.lib.co.nz/public")
maven("https://mvn.lumine.io/repository/maven-public/")
maven("https://maven.playpro.com")
}

dependencies {
Expand Down
1 change: 1 addition & 0 deletions eco-core/core-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ dependencies {
compileOnly("io.papermc.paper:paper-api:1.21.8-R0.1-SNAPSHOT")
compileOnly("io.github.arcaneplugins:levelledmobs-plugin:4.0.2")
compileOnly("me.libraryaddict.disguises:libsdisguises:11.0.14")
compileOnly("net.coreprotect:coreprotect:24.0")
implementation("com.willfp:ModelEngineBridge:1.4.0")
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package com.willfp.ecomobs

import com.willfp.eco.core.Prerequisite
import com.willfp.eco.core.bstats.EcoMetricsChart
import com.willfp.eco.core.command.impl.PluginCommand
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.eco.core.items.Items
import com.willfp.eco.util.toSingletonList
import com.willfp.ecomobs.category.MobCategories
import com.willfp.ecomobs.category.spawning.spawnpoints.SpawnPointGenerator
Expand All @@ -15,12 +17,23 @@ import com.willfp.ecomobs.goals.entity.EntityGoalRandomTeleport
import com.willfp.ecomobs.handler.ChunkHandler
import com.willfp.ecomobs.handler.DamageModifierHandler
import com.willfp.ecomobs.handler.DamageStageHandler
import com.willfp.ecomobs.handler.PaperSpawnerPickBlockHandler
import com.willfp.ecomobs.handler.PaperWaterTeleportHandler
import com.willfp.ecomobs.handler.SpigotSpawnerPickBlockHandler
import com.willfp.ecomobs.handler.SpigotWaterTeleportHandler
import com.willfp.ecomobs.handler.SunlightBurningHandler
import com.willfp.ecomobs.handler.SunlightBurningSettings
import com.willfp.ecomobs.handler.WaterSensitivityHandler
import com.willfp.ecomobs.handler.WaterSensitivitySettings
import com.willfp.ecomobs.handler.MountHandler
import com.willfp.ecomobs.handler.SpawnEggHandler
import com.willfp.ecomobs.handler.SpawnTotemHandler
import com.willfp.ecomobs.handler.SpawnerHandler
import com.willfp.ecomobs.handler.SpawnerStackHandler
import com.willfp.ecomobs.handler.StackHandler
import com.willfp.ecomobs.handler.VanillaCompatibilityHandlers
import com.willfp.ecomobs.integrations.bettermodel.IntegrationBetterModel
import com.willfp.ecomobs.integrations.coreprotect.IntegrationCoreProtect
import com.willfp.ecomobs.integrations.levelledmobs.IntegrationLevelledMobs
import com.willfp.ecomobs.integrations.libsdisguises.IntegrationLibsDisguises
import com.willfp.ecomobs.integrations.modelengine.IntegrationModelEngine
Expand All @@ -30,6 +43,14 @@ import com.willfp.ecomobs.mob.impl.ecoMob
import com.willfp.ecomobs.spawner.PlacedSpawners
import com.willfp.ecomobs.spawner.SpawnerAnimations
import com.willfp.ecomobs.spawner.SpawnerDisplay
import com.willfp.ecomobs.spawner.SpawnerHolograms
import com.willfp.ecomobs.spawner.SpawnerItems
import com.willfp.ecomobs.spawner.SpawnerStackSettings
import com.willfp.ecomobs.spawner.SpawnerSettings
import com.willfp.ecomobs.spawner.SpawnerSpawnLoop
import com.willfp.ecomobs.stacking.MobStackTicker
import com.willfp.ecomobs.stacking.StackSettings
import com.willfp.ecomobs.trigger.EcoMobsTriggers
import com.willfp.ecomobs.spawner.particle.SpawnerParticleAnimations
import com.willfp.libreforge.EntityProvidedHolder
import com.willfp.libreforge.loader.LibreforgePlugin
Expand Down Expand Up @@ -59,6 +80,8 @@ class EcoMobsPlugin : LibreforgePlugin() {

override fun handleLoad() {
EntityGoals.register(EntityGoalRandomTeleport.Deserializer)
Items.registerItemProvider(SpawnerItems)
EcoMobsTriggers.registerAll()
}

override fun loadConfigCategories(): List<ConfigCategory> {
Expand All @@ -70,7 +93,26 @@ class EcoMobsPlugin : LibreforgePlugin() {

override fun handleReload() {
SpawnerAnimations.reload()
SpawnerStackSettings.reload()
SpawnerSettings.reload()
// Chunks loaded before the plugin enabled never fire a ChunkLoadEvent, and a
// config change can flip which spawners are tracked, so the index is rebuilt here.
SpawnerHandler.indexLoadedChunks()
SpawnerDisplay.start()
SpawnerSpawnLoop.start()
StackSettings.reload()
WaterSensitivitySettings.reload()
SunlightBurningSettings.reload()
MobStackTicker.start()
SpawnerHolograms.reloadAll()
}

override fun handleDisable() {
SpawnerDisplay.stop()
SpawnerSpawnLoop.stop()
MobStackTicker.stop()
SpawnerHolograms.clear()
PlacedSpawners.clear()
}

override fun loadListeners(): List<Listener> {
Expand All @@ -84,10 +126,32 @@ class EcoMobsPlugin : LibreforgePlugin() {
SpawnTotemHandler,
topDamagerHandler,
SpawnerHandler,
WaterSensitivityHandler,
SunlightBurningHandler,
SpawnerStackHandler,
StackHandler,
ChunkHandler
)
) + platformListeners()
}

/**
* The listeners that differ between Paper and Spigot. Paper's own events don't exist
* on Spigot, so a listener naming one can only be registered on Paper; Spigot gets a
* workaround built from Bukkit's events instead.
*/
private fun platformListeners(): List<Listener> =
if (Prerequisite.HAS_PAPER.isMet) {
listOf(
PaperSpawnerPickBlockHandler,
PaperWaterTeleportHandler
)
} else {
listOf(
SpigotSpawnerPickBlockHandler,
SpigotWaterTeleportHandler
)
}

override fun loadDisplayModules(): List<DisplayModule> {
return listOf(SpawnEggDisplay, SpawnerItemDisplay)
}
Expand All @@ -98,6 +162,7 @@ class EcoMobsPlugin : LibreforgePlugin() {
IntegrationLoader("ModelEngine") { this.eventManager.registerListener(IntegrationModelEngine) },
IntegrationLoader("BetterModel") { this.eventManager.registerListener(IntegrationBetterModel) },
IntegrationLoader("LibsDisguises") { this.eventManager.registerListener(IntegrationLibsDisguises) },
IntegrationLoader("CoreProtect") { this.eventManager.registerListener(IntegrationCoreProtect) },
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,18 @@ abstract class SpawnMethod(
throw IllegalStateException("Already started!")
}

isStarted = true

onStart()
}

fun stop() {
if (!isStarted) {
return
}

isStarted = false

onStop()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ 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.folia.onEntity
import com.willfp.ecomobs.mob.SpawnReason
import com.willfp.ecomobs.plugin
import com.willfp.libreforge.EmptyProvidedHolder
Expand All @@ -16,7 +17,7 @@ import com.willfp.libreforge.conditions.Conditions
import com.willfp.libreforge.enumValueOfOrNull
import com.willfp.libreforge.toDispatcher
import org.bukkit.Bukkit
import org.bukkit.event.Listener
import org.bukkit.entity.Player

object SpawnMethodFactoryCustom : SpawnMethodFactory("custom") {
override fun create(
Expand All @@ -31,7 +32,7 @@ object SpawnMethodFactoryCustom : SpawnMethodFactory("custom") {
category: MobCategory,
config: Config,
context: ViolationContext
) : SpawnMethod(category, config), Listener {
) : SpawnMethod(category, config) {
private val spawnRate = plugin.configYml.getInt("custom-spawning.spawn-rate").toLong()

private val spawnTypes = config.getStrings("spawn-types")
Expand All @@ -48,7 +49,7 @@ object SpawnMethodFactoryCustom : SpawnMethodFactory("custom") {
private var task: EcoTask? = null

override fun onStart() {
task = plugin.scheduler.runTimer(spawnRate, spawnRate) {
task = plugin.scheduler.global().runTimer(spawnRate, spawnRate) {
tick()
}
}
Expand All @@ -59,19 +60,28 @@ object SpawnMethodFactoryCustom : SpawnMethodFactory("custom") {

private fun tick() {
for (player in Bukkit.getOnlinePlayers()) {
for (point in player.spawnPoints.filter { it.type in spawnTypes }) {
val mob = category.mobs.randomOrNull() ?: continue
// Spawn points are found by scanning the blocks around the player, and
// the mobs then go into the world there, so the whole pass runs on the
// player's own region rather than the global one.
onEntity(player) {
tickPlayer(player)
}
}
}

if (!conditions.areMet(point.location.toDispatcher(), EmptyProvidedHolder)) {
continue
}
private fun tickPlayer(player: Player) {
for (point in player.spawnPoints.filter { it.type in spawnTypes }) {
val mob = category.mobs.randomOrNull() ?: continue

if (randDouble(0.0, 100.0) > chance) {
continue
}
if (!conditions.areMet(point.location.toDispatcher(), EmptyProvidedHolder)) {
continue
}

point.spawn(mob, SpawnReason.NATURAL)
if (randDouble(0.0, 100.0) > chance) {
continue
}

point.spawn(mob, SpawnReason.NATURAL)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ class SpawnPointGenerator(plugin: EcoMobsPlugin) {
val blockAbove = world.getBlockAt(x, y + 1, z)
val blockBelow = world.getBlockAt(x, y - 1, z)

if (blockAbove.isSolid) {
if (blockAbove.type.isSolid) {
continue
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
package com.willfp.ecomobs.commands

import com.willfp.eco.core.command.impl.Subcommand
import com.willfp.ecomobs.folia.onEntity
import com.willfp.ecomobs.mob.EcoMobs
import com.willfp.ecomobs.mob.impl.mobKey
import com.willfp.ecomobs.plugin
import org.bukkit.Bukkit
import org.bukkit.command.CommandSender
import org.bukkit.entity.Mob
import org.bukkit.persistence.PersistentDataType
import org.bukkit.util.StringUtil

object CommandKillAll : Subcommand(
Expand Down Expand Up @@ -36,23 +33,18 @@ object CommandKillAll : Subcommand(

var killed = 0

for (world in Bukkit.getWorlds()) {
for (entity in world.entities.filterIsInstance<Mob>()) {
// Read the ID from the entity itself rather than from tracking, so that mobs
// spawned before a restart or reload are still killed.
val id = entity.persistentDataContainer.get(mobKey, PersistentDataType.STRING)
?: continue

if (filter != null && id != filter) {
continue
}

val livingMob = EcoMobs[id]?.getLivingMob(entity)
// Walked over what EcoMobs tracks rather than over world.entities: no thread
// owns every region, so there is no thread that can iterate a whole world.
// Mobs loaded before the plugin enabled are picked up by ChunkHandler, so a
// loaded mob of ours is a tracked mob of ours.
for (ecoMob in EcoMobs.values()) {
if (filter != null && ecoMob.id != filter) {
continue
}

if (livingMob != null) {
livingMob.despawn()
} else {
entity.remove()
for (living in ecoMob.livingMobs.toList()) {
onEntity(living.entity) {
living.despawn()
}

killed++
Expand Down
Loading
Loading