9
0
mirror of https://github.com/Auxilor/EcoMobs.git synced 2025-12-19 15:09:17 +00:00

Initial commit

This commit is contained in:
Auxilor
2023-11-07 16:16:53 +00:00
commit 4154f6c843
82 changed files with 4260 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
group = "com.willfp"
version = rootProject.version

View File

@@ -0,0 +1,40 @@
import org.gradle.internal.impldep.org.junit.experimental.categories.Categories.CategoryFilter.exclude
group = "com.willfp"
version = rootProject.version
dependencies {
compileOnly("io.papermc.paper:paper-api:1.19.3-R0.1-SNAPSHOT")
compileOnly("com.github.lokka30:LevelledMobs:3.1.4")
compileOnly("com.ticxo.modelengine:api:R3.1.8")
compileOnly("LibsDisguises:LibsDisguises:10.0.38")
}
publishing {
publications {
register<MavenPublication>("maven") {
groupId = project.group.toString()
version = project.version.toString()
artifactId = rootProject.name
artifact(rootProject.tasks.shadowJar.get().archiveFile)
}
}
repositories {
maven {
name = "auxilor"
url = uri("https://repo.auxilor.io/repository/maven-releases/")
credentials {
username = System.getenv("MAVEN_USERNAME")
password = System.getenv("MAVEN_PASSWORD")
}
}
}
}
tasks {
build {
dependsOn(publishToMavenLocal)
}
}

View File

@@ -0,0 +1,25 @@
package com.willfp.ecomobs
import com.willfp.eco.core.EcoPlugin
import org.bukkit.Bukkit
import org.bukkit.Keyed
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.bukkit.event.player.PlayerJoinEvent
import org.bukkit.inventory.Recipe
class DiscoverRecipeListener(private val plugin: EcoPlugin) : Listener {
@EventHandler
fun onJoin(event: PlayerJoinEvent) {
if (!plugin.configYml.getBool("discover-recipes")) {
return
}
mutableListOf<Recipe>()
.apply { Bukkit.getServer().recipeIterator().forEachRemaining(this::add) }
.filterIsInstance<Keyed>().map { it.key }
.filter { it.namespace == plugin.name.lowercase() }
.filter { !it.key.contains("displayed") }
.forEach { event.player.discoverRecipe(it) }
}
}

View File

@@ -0,0 +1,69 @@
package com.willfp.ecomobs
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.ecomobs.category.MobCategories
import com.willfp.ecomobs.commands.CommandEcoMobs
import com.willfp.ecomobs.display.SpawnEggDisplay
import com.willfp.ecomobs.goals.entity.EntityGoalRandomTeleport
import com.willfp.ecomobs.handler.DamageModifierHandler
import com.willfp.ecomobs.handler.MountHandler
import com.willfp.ecomobs.handler.SpawnEggHandler
import com.willfp.ecomobs.handler.VanillaCompatibilityHandlers
import com.willfp.ecomobs.integrations.levelledmobs.IntegrationLevelledMobs
import com.willfp.ecomobs.integrations.libsdisguises.IntegrationLibsDisguises
import com.willfp.ecomobs.integrations.modelengine.IntegrationModelEngine
import com.willfp.ecomobs.mob.EcoMobs
import com.willfp.libreforge.loader.LibreforgePlugin
import com.willfp.libreforge.loader.configs.ConfigCategory
import org.bukkit.event.Listener
internal lateinit var plugin: EcoMobsPlugin
private set
class EcoMobsPlugin : LibreforgePlugin() {
init {
plugin = this
}
override fun handleLoad() {
EntityGoals.register(EntityGoalRandomTeleport.Deserializer)
}
override fun loadConfigCategories(): List<ConfigCategory> {
return listOf(
MobCategories,
EcoMobs,
)
}
override fun loadListeners(): List<Listener> {
return listOf(
DamageModifierHandler(),
MountHandler(),
VanillaCompatibilityHandlers(),
DiscoverRecipeListener(this),
SpawnEggHandler(this)
)
}
override fun createDisplayModule(): DisplayModule {
return SpawnEggDisplay(this)
}
override fun loadIntegrationLoaders(): List<IntegrationLoader> {
return listOf(
IntegrationLoader("LevelledMobs") { this.eventManager.registerListener(IntegrationLevelledMobs()) },
IntegrationLoader("ModelEngine") { this.eventManager.registerListener(IntegrationModelEngine()) },
IntegrationLoader("LibsDisguises") { this.eventManager.registerListener(IntegrationLibsDisguises()) },
)
}
override fun loadPluginCommands(): List<PluginCommand> {
return listOf(
CommandEcoMobs(this)
)
}
}

View File

@@ -0,0 +1,34 @@
package com.willfp.ecomobs.category
import com.willfp.eco.core.config.interfaces.Config
import com.willfp.ecomobs.EcoMobsPlugin
import com.willfp.ecomobs.category.impl.ConfigDrivenMobCategory
import com.willfp.ecomobs.config.ConfigViolationException
import com.willfp.ecomobs.mob.EcoMob
import com.willfp.ecomobs.mob.impl.ConfigDrivenEcoMob
import com.willfp.libreforge.ViolationContext
import com.willfp.libreforge.loader.LibreforgePlugin
import com.willfp.libreforge.loader.configs.RegistrableCategory
object MobCategories : RegistrableCategory<MobCategory>("category", "categories") {
override fun clear(plugin: LibreforgePlugin) {
registry.clear()
}
override fun acceptConfig(plugin: LibreforgePlugin, id: String, config: Config) {
val context = ViolationContext(plugin, "Mob $id")
try {
val mob = ConfigDrivenMobCategory(
plugin as EcoMobsPlugin,
id,
config,
context
)
registry.register(mob)
} catch (e: ConfigViolationException) {
context.log(e.violation)
}
}
}

View File

@@ -0,0 +1,6 @@
package com.willfp.ecomobs.category
import com.willfp.eco.core.registry.KRegistrable
interface MobCategory : KRegistrable {
}

View File

@@ -0,0 +1,14 @@
package com.willfp.ecomobs.category.impl
import com.willfp.eco.core.config.interfaces.Config
import com.willfp.ecomobs.EcoMobsPlugin
import com.willfp.ecomobs.category.MobCategory
import com.willfp.libreforge.ViolationContext
class ConfigDrivenMobCategory(
private val plugin: EcoMobsPlugin,
override val id: String,
private val config: Config,
private val context: ViolationContext
) : MobCategory {
}

View File

@@ -0,0 +1,25 @@
package com.willfp.ecomobs.commands
import com.willfp.eco.core.command.impl.PluginCommand
import com.willfp.ecomobs.EcoMobsPlugin
import org.bukkit.command.CommandSender
class CommandEcoMobs(plugin: EcoMobsPlugin) : PluginCommand(
plugin,
"ecomobs",
"ecomobs.command.ecomobs",
false
) {
override fun onExecute(
sender: CommandSender,
args: List<String>
) {
sender.sendMessage(plugin.langYml.getMessage("invalid-command"))
}
init {
this.addSubcommand(CommandReload(plugin))
.addSubcommand(CommandSpawn(plugin))
.addSubcommand(CommandGive(plugin))
}
}

View File

@@ -0,0 +1,101 @@
package com.willfp.ecomobs.commands
import com.willfp.eco.core.command.impl.Subcommand
import com.willfp.eco.core.drops.DropQueue
import com.willfp.eco.util.StringUtils
import com.willfp.ecomobs.EcoMobsPlugin
import com.willfp.ecomobs.mob.EcoMobs
import org.bukkit.Bukkit
import org.bukkit.command.CommandSender
import org.bukkit.entity.Player
import org.bukkit.util.StringUtil
import java.util.stream.Collectors
private val amounts = listOf(1, 2, 3, 4, 5).map { it.toString() }
class CommandGive(plugin: EcoMobsPlugin) : Subcommand(
plugin,
"give",
"ecomobs.command.give",
false
) {
override fun onExecute(
sender: CommandSender,
args: List<String>
) {
if (args.isEmpty()) {
sender.sendMessage(plugin.langYml.getMessage("needs-player"))
return
}
if (args.size == 1) {
sender.sendMessage(plugin.langYml.getMessage("specify-mob"))
return
}
val amount = args.getOrNull(2)?.toIntOrNull() ?: 1
val recipient = Bukkit.getPlayer(args[0])
if (recipient == null) {
sender.sendMessage(plugin.langYml.getMessage("invalid-player"))
return
}
val mob = EcoMobs[args[1]]
val egg = mob?.spawnEgg
if (mob == null || egg == null) {
sender.sendMessage(plugin.langYml.getMessage("specify-mob"))
return
}
sender.sendMessage(
plugin.langYml.getMessage("gave-egg", StringUtils.FormatOption.WITHOUT_PLACEHOLDERS)
.replace("%mob%", mob.id)
.replace("%player%", recipient.name)
)
DropQueue(recipient)
.addItem(egg.getItem(recipient).apply {
setAmount(amount)
})
.forceTelekinesis()
.push()
}
override fun tabComplete(
sender: CommandSender,
args: List<String>
): List<String> {
val completions = mutableListOf<String>()
if (args.size == 1) {
StringUtil.copyPartialMatches(
args[0],
Bukkit.getOnlinePlayers().map { it.name },
completions
)
}
if (args.size == 2) {
StringUtil.copyPartialMatches(
args[1],
EcoMobs.values().filter { it.spawnEgg != null }.map { it.id },
completions
)
}
if (args.size == 3) {
StringUtil.copyPartialMatches(
args[2],
amounts,
completions
)
}
completions.sort()
return completions
}
}

View File

@@ -0,0 +1,20 @@
package com.willfp.ecomobs.commands
import com.willfp.eco.core.command.impl.Subcommand
import com.willfp.ecomobs.EcoMobsPlugin
import org.bukkit.command.CommandSender
class CommandReload(plugin: EcoMobsPlugin) : Subcommand(
plugin,
"reload",
"ecomobs.command.reload",
false
) {
override fun onExecute(
sender: CommandSender,
args: List<String>
) {
plugin.reload()
sender.sendMessage(plugin.langYml.getMessage("reloaded"))
}
}

View File

@@ -0,0 +1,152 @@
package com.willfp.ecomobs.commands
import com.willfp.eco.core.command.impl.Subcommand
import com.willfp.ecomobs.EcoMobsPlugin
import com.willfp.ecomobs.mob.EcoMobs
import com.willfp.ecomobs.mob.SpawnReason
import org.bukkit.Bukkit
import org.bukkit.Location
import org.bukkit.World
import org.bukkit.command.CommandSender
import org.bukkit.entity.Player
import org.bukkit.util.StringUtil
private val tilde = listOf("~")
class CommandSpawn(plugin: EcoMobsPlugin) : Subcommand(
plugin,
"spawn",
"ecomobs.command.spawn",
false
) {
override fun onExecute(
sender: CommandSender,
args: List<String>
) {
if (args.isEmpty()) {
sender.sendMessage(plugin.langYml.getMessage("specify-mob"))
return
}
val mobID = args[0]
val mob = EcoMobs.getByID(mobID.lowercase())
if (mob == null) {
sender.sendMessage(plugin.langYml.getMessage("specify-mob"))
return
}
var location: Location? = null
if (sender is Player) {
location = sender.location
}
if (args.size >= 4) {
val xString = args[1]
val yString = args[2]
val zString = args[3]
val xPos: Double
val yPos: Double
val zPos: Double
if (xString.startsWith("~") && sender is Player) {
val xDiff = xString.replace("~", "")
val yDiff = yString.replace("~", "")
val zDiff = zString.replace("~", "")
xPos = if (xDiff.isEmpty()) {
sender.location.x
} else {
try {
sender.location.x + xDiff.toDouble()
} catch (e: NumberFormatException) {
sender.location.x
}
}
yPos = if (yDiff.isEmpty()) {
sender.location.y
} else {
try {
sender.location.y + yDiff.toDouble()
} catch (e: NumberFormatException) {
sender.location.y
}
}
zPos = if (zDiff.isEmpty()) {
sender.location.z
} else {
try {
sender.location.z + yDiff.toDouble()
} catch (e: NumberFormatException) {
sender.location.z
}
}
} else {
try {
xPos = xString.toDouble()
yPos = yString.toDouble()
zPos = zString.toDouble()
} catch (e: NumberFormatException) {
sender.sendMessage(plugin.langYml.getMessage("invalid-location"))
return
}
}
location = Location(null, xPos, yPos, zPos)
}
var world: World? = null
if (sender is Player) {
world = sender.world
}
if (args.size >= 5) {
world = Bukkit.getWorld(args[4])
}
if (location == null) {
sender.sendMessage(plugin.langYml.getMessage("invalid-location"))
return
}
location.world = world
if (world == null) {
sender.sendMessage(plugin.langYml.getMessage("invalid-world"))
return
}
val living = mob.spawn(location, SpawnReason.COMMAND)
if (living != null) {
sender.sendMessage(
plugin.langYml.getMessage("spawned")
.replace("%mob%", living.displayName)
)
}
}
override fun tabComplete(
sender: CommandSender,
args: List<String>
): List<String> {
val completions = mutableListOf<String>()
if (args.size == 1) {
StringUtil.copyPartialMatches(args[0], EcoMobs.values().map { it.id }, completions)
}
if (args.size == 2) {
StringUtil.copyPartialMatches(args[1], tilde, completions)
}
if (args.size == 3) {
StringUtil.copyPartialMatches(args[2], tilde, completions)
}
if (args.size == 4) {
StringUtil.copyPartialMatches(args[3], tilde, completions)
}
if (args.size == 5) {
StringUtil.copyPartialMatches(args[4], Bukkit.getWorlds().map { it.name }, completions)
}
completions.sort()
return completions
}
}

View File

@@ -0,0 +1,45 @@
package com.willfp.ecomobs.config
import com.willfp.libreforge.ConfigViolation
class ValidationScope<T>(
private val value: T,
private val violated: Boolean
) {
fun validate(predicate: (T) -> Boolean): ValidationScope<T> {
return ValidationScope(
value,
violated || !predicate(value)
)
}
fun unwrap(violation: () -> ConfigViolation): T {
if (violated) {
throw ConfigViolationException(violation())
}
return value
}
}
inline fun <reified T> T.validate(predicate: (T) -> Boolean): ValidationScope<T> {
return ValidationScope(this, !predicate(this))
}
inline fun <reified T: Any> T?.validateNotNull(violation: ConfigViolation): T {
return this ?: throw ConfigViolationException(violation)
}
inline fun <reified T> Boolean.ifTrue(block: () -> T): T? {
return if (this) {
block()
} else {
null
}
}
fun <T : Enum<T>> T.toConfigKey(): String {
return this.name.lowercase().replace("_", "-")
}
@Suppress("UNCHECKED_CAST")
fun <K, V> Map<K, V?>.filterNotNullValues() = filterValues { it != null } as Map<K, V>

View File

@@ -0,0 +1,13 @@
package com.willfp.ecomobs.config
import com.willfp.libreforge.ConfigViolation
import com.willfp.libreforge.ViolationContext
import java.lang.Exception
/**
* Thrown when there is an error in the configuration.
*/
class ConfigViolationException(
val violation: ConfigViolation,
val context: (ViolationContext) -> ViolationContext = { it }
): Exception(violation.message)

View File

@@ -0,0 +1,35 @@
package com.willfp.ecomobs.display
import com.willfp.eco.core.EcoPlugin
import com.willfp.eco.core.display.Display
import com.willfp.eco.core.display.DisplayModule
import com.willfp.eco.core.display.DisplayPriority
import com.willfp.eco.core.fast.fast
import com.willfp.ecomobs.mob.options.ecoMobEgg
import com.willfp.libreforge.BlankHolder
import com.willfp.libreforge.ItemProvidedHolder
import org.bukkit.entity.Player
import org.bukkit.inventory.ItemStack
class SpawnEggDisplay(
plugin: EcoPlugin
) : DisplayModule(
plugin,
DisplayPriority.LOW
) {
override fun display(itemStack: ItemStack, player: Player?, vararg args: Any) {
if (player == null) {
return
}
val fis = itemStack.fast()
val egg = fis.ecoMobEgg?.spawnEgg ?: return
val notMetLines = egg.conditions
.getNotMetLines(player, ItemProvidedHolder(BlankHolder, itemStack))
.map { Display.PREFIX + it }
fis.lore = fis.lore + notMetLines
}
}

View File

@@ -0,0 +1,22 @@
package com.willfp.ecomobs.event
import com.willfp.ecomobs.mob.LivingMob
import org.bukkit.event.Event
import org.bukkit.event.HandlerList
class EcoMobDeathEvent(
val mob: LivingMob,
) : Event() {
override fun getHandlers(): HandlerList {
return HANDLERS
}
companion object {
private val HANDLERS = HandlerList()
@JvmStatic
fun getHandlerList(): HandlerList {
return HANDLERS
}
}
}

View File

@@ -0,0 +1,22 @@
package com.willfp.ecomobs.event
import com.willfp.ecomobs.mob.LivingMob
import org.bukkit.event.Event
import org.bukkit.event.HandlerList
class EcoMobDespawnEvent(
val mob: LivingMob,
) : Event() {
override fun getHandlers(): HandlerList {
return HANDLERS
}
companion object {
private val HANDLERS = HandlerList()
@JvmStatic
fun getHandlerList(): HandlerList {
return HANDLERS
}
}
}

View File

@@ -0,0 +1,24 @@
package com.willfp.ecomobs.event
import com.willfp.ecomobs.mob.LivingMob
import org.bukkit.entity.Player
import org.bukkit.event.HandlerList
import org.bukkit.event.player.PlayerEvent
class EcoMobKillEvent(
val mob: LivingMob,
player: Player,
) : PlayerEvent(player) {
override fun getHandlers(): HandlerList {
return HANDLERS
}
companion object {
private val HANDLERS = HandlerList()
@JvmStatic
fun getHandlerList(): HandlerList {
return HANDLERS
}
}
}

View File

@@ -0,0 +1,35 @@
package com.willfp.ecomobs.event
import com.willfp.ecomobs.mob.EcoMob
import com.willfp.ecomobs.mob.SpawnReason
import org.bukkit.event.Cancellable
import org.bukkit.event.Event
import org.bukkit.event.HandlerList
class EcoMobPreSpawnEvent(
val mob: EcoMob,
val reason: SpawnReason,
) : Event(), Cancellable {
private var isCancelled: Boolean = false
override fun isCancelled(): Boolean {
return isCancelled
}
override fun setCancelled(cancelled: Boolean) {
isCancelled = cancelled
}
override fun getHandlers(): HandlerList {
return HANDLERS
}
companion object {
private val HANDLERS = HandlerList()
@JvmStatic
fun getHandlerList(): HandlerList {
return HANDLERS
}
}
}

View File

@@ -0,0 +1,24 @@
package com.willfp.ecomobs.event
import com.willfp.ecomobs.mob.LivingMob
import com.willfp.ecomobs.mob.SpawnReason
import org.bukkit.event.Event
import org.bukkit.event.HandlerList
class EcoMobSpawnEvent(
val mob: LivingMob,
val reason: SpawnReason,
) : Event() {
override fun getHandlers(): HandlerList {
return HANDLERS
}
companion object {
private val HANDLERS = HandlerList()
@JvmStatic
fun getHandlerList(): HandlerList {
return HANDLERS
}
}
}

View File

@@ -0,0 +1,80 @@
package com.willfp.ecomobs.goals.entity
import com.willfp.eco.core.config.interfaces.Config
import com.willfp.eco.core.entities.ai.CustomGoal
import com.willfp.eco.core.entities.ai.GoalFlag
import com.willfp.eco.core.serialization.KeyedDeserializer
import com.willfp.eco.util.namespacedKeyOf
import org.bukkit.Location
import org.bukkit.Material
import org.bukkit.block.BlockFace
import org.bukkit.entity.Mob
import java.util.EnumSet
class EntityGoalRandomTeleport(
private val interval: Int,
private val range: Int
) : CustomGoal<Mob>() {
private lateinit var mob: Mob
private var tick = 0
override fun initialize(mob: Mob) {
this.mob = mob
}
override fun canUse(): Boolean {
return true
}
override fun tick() {
tick++
if (tick % interval != 0) {
return
}
val validLocations = mutableListOf<Location>()
for (x in -range..range) {
for (y in -range..range) {
for (z in -range..range) {
val location = mob.location.clone().apply {
this.x += x
this.y += y
this.z += z
}
val block = location.block
// From old EcoBosses code
if (block.type == Material.AIR && block.getRelative(BlockFace.UP).type == Material.AIR && !(block.getRelative(
BlockFace.DOWN
).isLiquid || block.getRelative(BlockFace.DOWN).type == Material.AIR)
) {
validLocations.add(location)
}
}
}
}
if (validLocations.isEmpty()) {
return
}
mob.teleport(validLocations.random())
}
override fun getFlags(): EnumSet<GoalFlag> {
return EnumSet.of(GoalFlag.MOVE)
}
object Deserializer : KeyedDeserializer<EntityGoalRandomTeleport> {
override fun getKey() = namespacedKeyOf("ecomobs", "random_teleport")
override fun deserialize(p0: Config): EntityGoalRandomTeleport {
return EntityGoalRandomTeleport(
p0.getInt("interval").coerceAtLeast(1),
p0.getInt("range").coerceAtLeast(1)
)
}
}
}

View File

@@ -0,0 +1,19 @@
package com.willfp.ecomobs.handler
import com.willfp.ecomobs.mob.impl.ecoMob
import org.bukkit.entity.Mob
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.bukkit.event.entity.EntityDamageEvent
class DamageModifierHandler : Listener {
@EventHandler
fun handle(event: EntityDamageEvent) {
val bukkitMob = event.entity as? Mob ?: return
val ecoMob = bukkitMob.ecoMob ?: return
val multiplier = ecoMob.getDamageModifier(event.cause)
event.damage *= multiplier
}
}

View File

@@ -0,0 +1,21 @@
package com.willfp.ecomobs.handler
import com.willfp.ecomobs.mob.impl.ecoMob
import org.bukkit.entity.Mob
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.spigotmc.event.entity.EntityMountEvent
class MountHandler : Listener {
@EventHandler(
ignoreCancelled = true
)
fun handle(event: EntityMountEvent) {
val bukkitEntity = event.entity as? Mob ?: return
val mob = bukkitEntity.ecoMob ?: return
if (!mob.canMount) {
event.isCancelled = true
}
}
}

View File

@@ -0,0 +1,77 @@
package com.willfp.ecomobs.handler
import com.willfp.eco.core.fast.fast
import com.willfp.ecomobs.EcoMobsPlugin
import com.willfp.ecomobs.mob.options.ecoMobEgg
import org.bukkit.Location
import org.bukkit.block.Container
import org.bukkit.block.data.Directional
import org.bukkit.entity.Player
import org.bukkit.event.Event
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.bukkit.event.block.Action
import org.bukkit.event.block.BlockDispenseEvent
import org.bukkit.event.player.PlayerInteractEvent
import org.bukkit.inventory.EquipmentSlot
import org.bukkit.inventory.ItemStack
class SpawnEggHandler(
private val plugin: EcoMobsPlugin
) : Listener {
@EventHandler(ignoreCancelled = true)
fun handle(event: PlayerInteractEvent) {
if (event.action != Action.RIGHT_CLICK_BLOCK) {
return
}
val location = event.clickedBlock?.location?.add(0.0, 1.5, 0.0) ?: return
if (!this.handleSpawnEgg(event.item, location, event.player)) {
return
}
val hand = event.hand ?: return
event.isCancelled = true
event.setUseItemInHand(Event.Result.DENY)
val item = event.player.equipment.getItem(hand)
item.amount -= 1
}
@EventHandler(ignoreCancelled = true)
fun handle(event: BlockDispenseEvent) {
val facing = (event.block.blockData as Directional).facing
// What does the 1.7 do? I don't know, this is from old EcoBosses code.
val location = event.block.location.add(facing.direction.multiply(1.7))
if (!this.handleSpawnEgg(event.item, location, null)) {
return
}
event.isCancelled = true
val dispenser = event.block.state as? Container ?: return
// This is needed as the event must finish first,
// Otherwise the dispenser/dropper thinks the item is already removed from this event.
plugin.scheduler.run {
val item = dispenser.inventory.find { it?.isSimilar(event.item) == true } ?: return@run
item.amount--
dispenser.update()
}
}
private fun handleSpawnEgg(
item: ItemStack?,
location: Location,
player: Player?
): Boolean {
val mob = item?.ecoMobEgg ?: return false
val egg = mob.spawnEgg ?: return false
return egg.trySpawn(location, player) != null
}
}

View File

@@ -0,0 +1,25 @@
package com.willfp.ecomobs.handler
import com.willfp.ecomobs.mob.impl.ecoMob
import org.bukkit.entity.Mob
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.bukkit.event.entity.EntityTransformEvent
import org.bukkit.event.entity.SlimeSplitEvent
class VanillaCompatibilityHandlers : Listener {
@EventHandler
fun handle(event: SlimeSplitEvent) {
if (event.entity.ecoMob != null) {
event.isCancelled = true
}
}
@EventHandler
fun handle(event: EntityTransformEvent) {
val bukkitEntity = event.entity as? Mob ?: return
if (bukkitEntity.ecoMob != null) {
event.isCancelled = true
}
}
}

View File

@@ -0,0 +1,11 @@
package com.willfp.ecomobs.integrations
/**
* A mob integration gives access to a special area of the mob config.
* This can then be used to access the config of the integration, to apply
* special plugin-specific behaviours.
*/
class MobIntegration(
val configKey: String
) {
}

View File

@@ -0,0 +1,27 @@
package com.willfp.ecomobs.integrations.levelledmobs
import com.willfp.eco.core.integrations.Integration
import com.willfp.ecomobs.integrations.MobIntegration
import com.willfp.ecomobs.mob.impl.ecoMob
import me.lokka30.levelledmobs.events.MobPreLevelEvent
import org.bukkit.entity.Mob
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
class IntegrationLevelledMobs : Listener, Integration {
private val integration = MobIntegration("levelled-mobs")
@EventHandler
fun handle(event: MobPreLevelEvent) {
val bukkitEntity = event.entity as? Mob ?: return
val ecoMob = bukkitEntity.ecoMob ?: return
if (!ecoMob.getIntegrationConfig(integration).getBool("can-level")) {
event.isCancelled = true
}
}
override fun getPluginName(): String {
return "LevelledMobs"
}
}

View File

@@ -0,0 +1,28 @@
package com.willfp.ecomobs.integrations.libsdisguises
import com.willfp.eco.core.integrations.Integration
import com.willfp.ecomobs.event.EcoMobSpawnEvent
import com.willfp.ecomobs.integrations.MobIntegration
import me.libraryaddict.disguise.DisguiseAPI
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
class IntegrationLibsDisguises : Listener, Integration {
private val integration = MobIntegration("libs-disguises")
@EventHandler
fun handle(event: EcoMobSpawnEvent) {
val mob = event.mob.mob
val entity = event.mob.entity
val disguiseID = mob.getIntegrationConfig(integration).getStringOrNull("id") ?: return
val disguise = DisguiseAPI.getCustomDisguise(disguiseID) ?: return
DisguiseAPI.disguiseToAll(entity, disguise)
}
override fun getPluginName(): String {
return "LibsDisguises"
}
}

View File

@@ -0,0 +1,33 @@
package com.willfp.ecomobs.integrations.modelengine
import com.ticxo.modelengine.api.ModelEngineAPI
import com.willfp.eco.core.integrations.Integration
import com.willfp.ecomobs.event.EcoMobSpawnEvent
import com.willfp.ecomobs.integrations.MobIntegration
import com.willfp.ecomobs.mob.impl.ecoMob
import me.lokka30.levelledmobs.events.MobPreLevelEvent
import org.bukkit.entity.Mob
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
class IntegrationModelEngine : Listener, Integration {
private val integration = MobIntegration("model-engine")
@EventHandler
fun handle(event: EcoMobSpawnEvent) {
val mob = event.mob.mob
val entity = event.mob.entity
val modelEngineID = mob.getIntegrationConfig(integration).getStringOrNull("id") ?: return
val model = ModelEngineAPI.createActiveModel(modelEngineID) ?: return
val modelled = ModelEngineAPI.createModeledEntity(entity)
modelled.addModel(model, true)
modelled.isBaseEntityVisible = true
}
override fun getPluginName(): String {
return "ModelEngine"
}
}

View File

@@ -0,0 +1,21 @@
package com.willfp.ecomobs.mob
import com.willfp.eco.core.entities.ai.EntityController
import com.willfp.eco.core.entities.ai.EntityGoal
import com.willfp.eco.core.entities.ai.Goal
import com.willfp.eco.core.entities.ai.TargetGoal
import org.bukkit.entity.Mob
data class ConfiguredGoal<T : Goal<*>>(
val priority: Int,
val goal: T
)
fun <T: Mob> EntityController<out T>.addGoal(goal: ConfiguredGoal<out Goal<*>>) {
@Suppress("UNCHECKED_CAST")
if (goal.goal is EntityGoal<*>) {
this.addEntityGoal(goal.priority, goal.goal as EntityGoal<in T>)
} else {
this.addTargetGoal(goal.priority, goal.goal as TargetGoal<in T>)
}
}

View File

@@ -0,0 +1,83 @@
package com.willfp.ecomobs.mob
import com.willfp.eco.core.config.interfaces.Config
import com.willfp.eco.core.registry.KRegistrable
import com.willfp.ecomobs.category.MobCategory
import com.willfp.ecomobs.integrations.MobIntegration
import com.willfp.ecomobs.mob.event.MobEvent
import com.willfp.ecomobs.mob.options.SpawnEgg
import com.willfp.libreforge.triggers.DispatchedTrigger
import org.bukkit.Location
import org.bukkit.entity.Mob
import org.bukkit.entity.Player
import org.bukkit.event.entity.EntityDamageEvent.DamageCause
import java.util.UUID
interface EcoMob : KRegistrable {
/**
* The category of the mob.
*/
val category: MobCategory
/**
* If the mob can mount.
*/
val canMount: Boolean
/**
* The lifespan of the mob, in ticks.
*/
val lifespan: Int
/**
* The raw, unformatted display name.
*/
val rawDisplayName: String
/**
* The spawn egg.
*/
val spawnEgg: SpawnEgg?
/**
* Get a living mob from a bukkit mob.
*/
fun getLivingMob(mob: Mob): LivingMob? {
return getLivingMob(mob.uniqueId)
}
/**
* Get a living mob from a UUID.
*/
fun getLivingMob(uuid: UUID): LivingMob?
/**
* Check if the player can spawn the mob at a location.
*/
fun canPlayerSpawn(player: Player, spawnReason: SpawnReason, location: Location): Boolean
/**
* Spawn the mob at a location.
*/
fun spawn(location: Location, reason: SpawnReason): LivingMob?
/**
* Get the damage modifier for a damage cause.
*/
fun getDamageModifier(cause: DamageCause): Double
/**
* Spawn drops at a location.
*/
fun spawnDrops(location: Location, player: Player?)
/**
* Handle an event.
*/
fun handleEvent(event: MobEvent, trigger: DispatchedTrigger)
/**
* Get the config for a certain integration.
*/
fun getIntegrationConfig(integration: MobIntegration): Config
}

View File

@@ -0,0 +1,32 @@
package com.willfp.ecomobs.mob
import com.willfp.eco.core.config.interfaces.Config
import com.willfp.ecomobs.EcoMobsPlugin
import com.willfp.ecomobs.config.ConfigViolationException
import com.willfp.ecomobs.mob.impl.ConfigDrivenEcoMob
import com.willfp.libreforge.ViolationContext
import com.willfp.libreforge.loader.LibreforgePlugin
import com.willfp.libreforge.loader.configs.RegistrableCategory
object EcoMobs : RegistrableCategory<EcoMob>("mob", "mobs") {
override fun clear(plugin: LibreforgePlugin) {
registry.clear()
}
override fun acceptConfig(plugin: LibreforgePlugin, id: String, config: Config) {
val context = ViolationContext(plugin, "Mob $id")
try {
val mob = ConfigDrivenEcoMob(
plugin as EcoMobsPlugin,
id,
config,
context
)
registry.register(mob)
} catch (e: ConfigViolationException) {
context.log(e.violation)
}
}
}

View File

@@ -0,0 +1,29 @@
package com.willfp.ecomobs.mob
import org.bukkit.entity.Mob
import org.bukkit.entity.Player
interface LivingMob {
val mob: EcoMob
val entity: Mob
val isAlive: Boolean
val displayName: String
/**
* Ticks left until removed due to lifespan.
*/
val ticksLeft: Int
/**
* Kill the mob.
*/
fun kill(player: Player?)
/**
* Despawn the mob.
*/
fun despawn()
}

View File

@@ -0,0 +1,8 @@
package com.willfp.ecomobs.mob
enum class SpawnReason {
NATURAL,
TOTEM,
EGG,
COMMAND
}

View File

@@ -0,0 +1,27 @@
package com.willfp.ecomobs.mob.event
import com.willfp.eco.core.registry.KRegistrable
import com.willfp.ecomobs.plugin
import org.bukkit.Bukkit
import org.bukkit.entity.Mob
import org.bukkit.entity.Player
import org.bukkit.event.Listener
abstract class MobEvent(
final override val id: String
): KRegistrable, Listener {
val configKey = this.id.replace("_", "-")
override fun onRegister() {
plugin.eventManager.registerListener(this)
}
/*
libreforge effects require a player to be passed in, so we just use an arbitrary player.
This means that if there are no players online, then certain events will not fire - but this
shouldn't cause much of an issue.
*/
protected fun getArbitraryPlayer(): Player? {
return Bukkit.getOnlinePlayers().firstOrNull()
}
}

View File

@@ -0,0 +1,30 @@
package com.willfp.ecomobs.mob.event
import com.willfp.eco.core.registry.Registry
import com.willfp.ecomobs.mob.event.impl.MobEventAnyAttack
import com.willfp.ecomobs.mob.event.impl.MobEventDamagePlayer
import com.willfp.ecomobs.mob.event.impl.MobEventDeath
import com.willfp.ecomobs.mob.event.impl.MobEventDespawn
import com.willfp.ecomobs.mob.event.impl.MobEventInteract
import com.willfp.ecomobs.mob.event.impl.MobEventKill
import com.willfp.ecomobs.mob.event.impl.MobEventKillPlayer
import com.willfp.ecomobs.mob.event.impl.MobEventMeleeAttack
import com.willfp.ecomobs.mob.event.impl.MobEventRangedAttack
import com.willfp.ecomobs.mob.event.impl.MobEventSpawn
import com.willfp.ecomobs.mob.event.impl.MobEventTakeDamage
object MobEvents : Registry<MobEvent>() {
init {
register(MobEventAnyAttack)
register(MobEventDamagePlayer)
register(MobEventDeath)
register(MobEventDespawn)
register(MobEventInteract)
register(MobEventKill)
register(MobEventKillPlayer)
register(MobEventMeleeAttack)
register(MobEventRangedAttack)
register(MobEventSpawn)
register(MobEventTakeDamage)
}
}

View File

@@ -0,0 +1,6 @@
package com.willfp.ecomobs.mob.event.impl
import com.willfp.ecomobs.mob.event.MobEvent
// Logic handled by melee attack and ranged attack
object MobEventAnyAttack : MobEvent("any_attack")

View File

@@ -0,0 +1,27 @@
package com.willfp.ecomobs.mob.event.impl
import com.willfp.ecomobs.mob.event.MobEvent
import com.willfp.ecomobs.mob.impl.ecoMob
import com.willfp.libreforge.triggers.TriggerData
import org.bukkit.entity.Mob
import org.bukkit.entity.Player
import org.bukkit.event.EventHandler
import org.bukkit.event.entity.EntityDamageByEntityEvent
object MobEventDamagePlayer : MobEvent("damage_player") {
@EventHandler
fun handle(event: EntityDamageByEntityEvent) {
val player = event.entity as? Player ?: return
val bukkitMob = event.damager as? Mob ?: return
val ecoMob = bukkitMob.ecoMob ?: return
val data = TriggerData(
player = player,
victim = bukkitMob,
location = bukkitMob.location,
event = event
)
ecoMob.handleEvent(this, data.dispatch(player))
}
}

View File

@@ -0,0 +1,37 @@
package com.willfp.ecomobs.mob.event.impl
import com.willfp.ecomobs.event.EcoMobDeathEvent
import com.willfp.ecomobs.mob.event.MobEvent
import com.willfp.ecomobs.mob.impl.ecoMob
import com.willfp.libreforge.triggers.TriggerData
import org.bukkit.Bukkit
import org.bukkit.entity.Mob
import org.bukkit.event.EventHandler
import org.bukkit.event.EventPriority
import org.bukkit.event.entity.EntityDeathEvent
object MobEventDeath : MobEvent("death") {
// Highest priority to let player run first
@EventHandler(priority = EventPriority.HIGHEST)
fun handle(event: EntityDeathEvent) {
val bukkitMob = event.entity as? Mob ?: return
val ecoMob = bukkitMob.ecoMob ?: return
val living = ecoMob.getLivingMob(bukkitMob) ?: return
val data = TriggerData(
victim = bukkitMob,
location = bukkitMob.location,
event = event
)
// Clear default drops
event.drops.clear()
val player = getArbitraryPlayer() ?: return
Bukkit.getPluginManager().callEvent(EcoMobDeathEvent(living))
ecoMob.handleEvent(this, data.dispatch(player))
living.kill(null)
}
}

View File

@@ -0,0 +1,23 @@
package com.willfp.ecomobs.mob.event.impl
import com.willfp.ecomobs.event.EcoMobDespawnEvent
import com.willfp.ecomobs.mob.event.MobEvent
import com.willfp.libreforge.triggers.TriggerData
import org.bukkit.event.EventHandler
object MobEventDespawn: MobEvent("despawn") {
@EventHandler
fun handle(event: EcoMobDespawnEvent) {
val ecoMob = event.mob
val data = TriggerData(
victim = ecoMob.entity,
location = ecoMob.entity.location,
event = event
)
val player = getArbitraryPlayer() ?: return
ecoMob.mob.handleEvent(this, data.dispatch(player))
}
}

View File

@@ -0,0 +1,26 @@
package com.willfp.ecomobs.mob.event.impl
import com.willfp.ecomobs.mob.event.MobEvent
import com.willfp.ecomobs.mob.impl.ecoMob
import com.willfp.libreforge.triggers.TriggerData
import org.bukkit.entity.Mob
import org.bukkit.event.EventHandler
import org.bukkit.event.player.PlayerInteractEntityEvent
object MobEventInteract: MobEvent("interact") {
@EventHandler
fun handle(event: PlayerInteractEntityEvent) {
val bukkitMob = event.rightClicked as? Mob ?: return
val ecoMob = bukkitMob.ecoMob ?: return
val player = event.player
val data = TriggerData(
player = player,
victim = bukkitMob,
location = bukkitMob.location,
event = event
)
ecoMob.handleEvent(this, data.dispatch(player))
}
}

View File

@@ -0,0 +1,34 @@
package com.willfp.ecomobs.mob.event.impl
import com.willfp.eco.core.events.EntityDeathByEntityEvent
import com.willfp.eco.util.tryAsPlayer
import com.willfp.ecomobs.event.EcoMobKillEvent
import com.willfp.ecomobs.mob.event.MobEvent
import com.willfp.ecomobs.mob.impl.ecoMob
import com.willfp.libreforge.triggers.TriggerData
import org.bukkit.Bukkit
import org.bukkit.entity.Mob
import org.bukkit.event.EventHandler
import org.bukkit.event.entity.EntityDeathEvent
object MobEventKill : MobEvent("kill") {
@EventHandler
fun handle(event: EntityDeathByEntityEvent) {
val player = event.killer.tryAsPlayer() ?: return
val bukkitMob = event.victim as? Mob ?: return
val ecoMob = bukkitMob.ecoMob ?: return
val living = ecoMob.getLivingMob(bukkitMob) ?: return
val data = TriggerData(
player = player,
victim = bukkitMob,
location = bukkitMob.location,
event = event
)
Bukkit.getPluginManager().callEvent(EcoMobKillEvent(living, player))
ecoMob.handleEvent(this, data.dispatch(player))
living.kill(player)
}
}

View File

@@ -0,0 +1,27 @@
package com.willfp.ecomobs.mob.event.impl
import com.willfp.eco.core.events.EntityDeathByEntityEvent
import com.willfp.ecomobs.mob.event.MobEvent
import com.willfp.ecomobs.mob.impl.ecoMob
import com.willfp.libreforge.triggers.TriggerData
import org.bukkit.entity.Mob
import org.bukkit.entity.Player
import org.bukkit.event.EventHandler
object MobEventKillPlayer : MobEvent("kill_player") {
@EventHandler
fun handle(event: EntityDeathByEntityEvent) {
val player = event.victim as? Player ?: return
val bukkitMob = event.killer as? Mob ?: return
val ecoMob = bukkitMob.ecoMob ?: return
val data = TriggerData(
player = player,
victim = bukkitMob,
location = bukkitMob.location,
event = event
)
ecoMob.handleEvent(this, data.dispatch(player))
}
}

View File

@@ -0,0 +1,28 @@
package com.willfp.ecomobs.mob.event.impl
import com.willfp.ecomobs.mob.event.MobEvent
import com.willfp.ecomobs.mob.impl.ecoMob
import com.willfp.libreforge.triggers.TriggerData
import org.bukkit.entity.Mob
import org.bukkit.entity.Player
import org.bukkit.event.EventHandler
import org.bukkit.event.entity.EntityDamageByEntityEvent
object MobEventMeleeAttack : MobEvent("melee_attack") {
@EventHandler
fun handle(event: EntityDamageByEntityEvent) {
val bukkitMob = event.entity as? Mob ?: return
val ecoMob = bukkitMob.ecoMob ?: return
val player = event.damager as? Player ?: return
val data = TriggerData(
player = player,
victim = bukkitMob,
location = bukkitMob.location,
event = event
)
ecoMob.handleEvent(this, data.dispatch(player))
ecoMob.handleEvent(MobEventAnyAttack, data.dispatch(player))
}
}

View File

@@ -0,0 +1,28 @@
package com.willfp.ecomobs.mob.event.impl
import com.willfp.eco.util.tryAsPlayer
import com.willfp.ecomobs.mob.event.MobEvent
import com.willfp.ecomobs.mob.impl.ecoMob
import com.willfp.libreforge.triggers.TriggerData
import org.bukkit.entity.Mob
import org.bukkit.event.EventHandler
import org.bukkit.event.entity.EntityDamageByEntityEvent
object MobEventRangedAttack : MobEvent("ranged_attack") {
@EventHandler
fun handle(event: EntityDamageByEntityEvent) {
val bukkitMob = event.entity as? Mob ?: return
val ecoMob = bukkitMob.ecoMob ?: return
val player = event.damager.tryAsPlayer() ?: return
val data = TriggerData(
player = player,
victim = bukkitMob,
location = bukkitMob.location,
event = event
)
ecoMob.handleEvent(this, data.dispatch(player))
ecoMob.handleEvent(MobEventAnyAttack, data.dispatch(player))
}
}

View File

@@ -0,0 +1,24 @@
package com.willfp.ecomobs.mob.event.impl
import com.willfp.ecomobs.event.EcoMobSpawnEvent
import com.willfp.ecomobs.mob.event.MobEvent
import com.willfp.libreforge.triggers.TriggerData
import org.bukkit.Bukkit
import org.bukkit.event.EventHandler
object MobEventSpawn: MobEvent("spawn") {
@EventHandler
fun handle(event: EcoMobSpawnEvent) {
val mob = event.mob
val data = TriggerData(
victim = mob.entity,
location = mob.entity.location,
event = event
)
val player = getArbitraryPlayer() ?: return
mob.mob.handleEvent(this, data.dispatch(player))
}
}

View File

@@ -0,0 +1,29 @@
package com.willfp.ecomobs.mob.event.impl
import com.willfp.ecomobs.mob.event.MobEvent
import com.willfp.ecomobs.mob.impl.ecoMob
import com.willfp.libreforge.triggers.TriggerData
import org.bukkit.Bukkit
import org.bukkit.entity.Mob
import org.bukkit.entity.Player
import org.bukkit.event.EventHandler
import org.bukkit.event.entity.EntityDamageByEntityEvent
import org.bukkit.event.entity.EntityDamageEvent
object MobEventTakeDamage : MobEvent("take_damage") {
@EventHandler
fun handle(event: EntityDamageEvent) {
val bukkitMob = event.entity as? Mob ?: return
val ecoMob = bukkitMob.ecoMob ?: return
val data = TriggerData(
victim = bukkitMob,
location = bukkitMob.location,
event = event
)
val player = Bukkit.getOnlinePlayers().firstOrNull() ?: return
ecoMob.handleEvent(this, data.dispatch(player))
}
}

View File

@@ -0,0 +1,388 @@
package com.willfp.ecomobs.mob.impl
import com.willfp.eco.core.config.interfaces.Config
import com.willfp.eco.core.entities.Entities
import com.willfp.eco.core.entities.ai.EntityGoals
import com.willfp.eco.core.entities.ai.TargetGoals
import com.willfp.eco.core.entities.controller
import com.willfp.eco.core.entities.impl.EmptyTestableEntity
import com.willfp.eco.core.fast.fast
import com.willfp.eco.core.items.CustomItem
import com.willfp.eco.core.items.Items
import com.willfp.eco.core.items.builder.modify
import com.willfp.eco.core.recipe.Recipes
import com.willfp.eco.core.recipe.parts.EmptyTestableItem
import com.willfp.eco.util.NamespacedKeyUtils
import com.willfp.eco.util.namespacedKeyOf
import com.willfp.eco.util.safeNamespacedKeyOf
import com.willfp.eco.util.toComponent
import com.willfp.eco.util.toNiceString
import com.willfp.ecomobs.EcoMobsPlugin
import com.willfp.ecomobs.category.MobCategories
import com.willfp.ecomobs.config.ConfigViolationException
import com.willfp.ecomobs.config.filterNotNullValues
import com.willfp.ecomobs.config.ifTrue
import com.willfp.ecomobs.config.toConfigKey
import com.willfp.ecomobs.config.validate
import com.willfp.ecomobs.config.validateNotNull
import com.willfp.ecomobs.event.EcoMobPreSpawnEvent
import com.willfp.ecomobs.event.EcoMobSpawnEvent
import com.willfp.ecomobs.integrations.MobIntegration
import com.willfp.ecomobs.mob.ConfiguredGoal
import com.willfp.ecomobs.mob.EcoMob
import com.willfp.ecomobs.mob.EcoMobs
import com.willfp.ecomobs.mob.LivingMob
import com.willfp.ecomobs.mob.SpawnReason
import com.willfp.ecomobs.mob.addGoal
import com.willfp.ecomobs.mob.event.MobEvent
import com.willfp.ecomobs.mob.event.MobEvents
import com.willfp.ecomobs.mob.options.BossBarOptions
import com.willfp.ecomobs.mob.options.Drop
import com.willfp.ecomobs.mob.options.MobDrops
import com.willfp.ecomobs.mob.options.SpawnEgg
import com.willfp.ecomobs.mob.options.ecoMobEgg
import com.willfp.ecomobs.tick.TickHandlerBossBar
import com.willfp.ecomobs.tick.TickHandlerDisplayName
import com.willfp.ecomobs.tick.TickHandlerLifespan
import com.willfp.libreforge.ConfigViolation
import com.willfp.libreforge.ViolationContext
import com.willfp.libreforge.conditions.Conditions
import com.willfp.libreforge.effects.Effects
import com.willfp.libreforge.enumValueOfOrNull
import com.willfp.libreforge.triggers.DispatchedTrigger
import net.kyori.adventure.bossbar.BossBar
import org.bukkit.Bukkit
import org.bukkit.Location
import org.bukkit.entity.Mob
import org.bukkit.entity.Player
import org.bukkit.event.entity.EntityDamageEvent.DamageCause
import org.bukkit.inventory.EquipmentSlot
import org.bukkit.persistence.PersistentDataType
import java.util.UUID
val mobKey = namespacedKeyOf("ecomobs", "mob")
internal class ConfigDrivenEcoMob(
private val plugin: EcoMobsPlugin,
override val id: String,
private val config: Config,
private val context: ViolationContext
) : EcoMob {
private val trackedMobs = mutableMapOf<UUID, LivingMob>()
private val onSpawnActions = mutableListOf<(LivingMobImpl) -> Unit>()
val mob = Entities.lookup(config.getString("mob"))
.validate { it !is EmptyTestableEntity }
.unwrap {
ConfigViolation(
"mob",
"Invalid mob",
)
}
override val category = MobCategories[config.getString("category")]
.validateNotNull(
ConfigViolation(
"category",
"Invalid category"
)
)
val equipment = EquipmentSlot.values().associateWith {
config.getStringOrNull("equipment.${it.toConfigKey()}")
?.let { lookup -> Items.lookup(lookup) }
}.onSpawn {
for ((slot, item) in it) {
@Suppress("UNNECESSARY_SAFE_CALL")
this.entity.equipment?.setItem(slot, item?.item)
}
}
val isOverridingAI = config.getBool("custom-ai.clear")
val targetGoals = config.getSubsections("custom-ai.target-goals").mapNotNull {
val key = safeNamespacedKeyOf(it.getString("key")) ?: throw ConfigViolationException(
ConfigViolation(
"key",
"Invalid goal key"
)
) { ctx ->
ctx.with("target goals")
}
val deserializer = TargetGoals.getByKey(key) ?: throw ConfigViolationException(
ConfigViolation(
"key",
"Invalid target goal"
)
) { ctx ->
ctx.with("target goals")
}
val goal = deserializer.deserialize(it.getSubsection("args")) ?: throw ConfigViolationException(
ConfigViolation(
"args",
"Invalid target goal args"
)
) { ctx ->
ctx.with("target goals").with(deserializer.key.toString())
}
ConfiguredGoal(it.getInt("priority"), goal)
}
val entityGoals = config.getSubsections("custom-ai.entity-goals").mapNotNull {
val key = safeNamespacedKeyOf(it.getString("key")) ?: throw ConfigViolationException(
ConfigViolation(
"key",
"Invalid goal key"
)
) { ctx ->
ctx.with("entity goals")
}
val deserializer = EntityGoals.getByKey(key) ?: throw ConfigViolationException(
ConfigViolation(
"key",
"Invalid entity goal"
)
) { ctx ->
ctx.with("entity goals")
}
val goal = deserializer.deserialize(it.getSubsection("args")) ?: throw ConfigViolationException(
ConfigViolation(
"args",
"Invalid entity goal args"
)
) { ctx ->
ctx.with("entity goals").with(deserializer.key.toString())
}
ConfiguredGoal(it.getInt("priority"), goal)
}
val eventEffects = MobEvents.associateWith {
Effects.compileChain(
config.getSubsections("effects.${it.configKey}"),
context.with("effects").with(it.configKey)
)
}.filterNotNullValues()
override val rawDisplayName = config.getString("display-name")
override val lifespan = config.getInt("lifespan")
.let { if (it < 1) Int.MAX_VALUE else it * 20 }
override val canMount = config.getBool("defence.can-mount")
val damageModifiers = DamageCause.values().associateWith {
config.getDoubleOrNull("defence.damage-modifiers.${it.name.lowercase()}") ?: 1.0
}
val bossBarOptions = config.getBool("boss-bar.enabled")
.ifTrue {
val barColor = enumValueOfOrNull<BossBar.Color>(config.getString("boss-bar.color").uppercase())
.validateNotNull(
ConfigViolation(
"boss-bar.color",
"Invalid boss bar color"
)
)
val barStyle = enumValueOfOrNull<BossBar.Overlay>(config.getString("boss-bar.style").uppercase())
.validateNotNull(
ConfigViolation(
"boss-bar.style",
"Invalid boss bar style"
)
)
val barRadius = config.getDouble("boss-bar.radius")
.validate { it > 0 }
.unwrap {
ConfigViolation(
"boss-bar.radius",
"Boss bar radius must be greater than 0"
)
}
BossBarOptions(
barColor,
barStyle,
barRadius
)
}
.onSpawn {
val bar = BossBar.bossBar(
rawDisplayName.toComponent(),
1f,
it.color,
it.style
)
this.addTickHandler(TickHandlerBossBar(bar, it))
}
val drops = MobDrops(
config.getInt("drops.experience"),
config.getSubsections("drops.items").map {
val items = it.getStrings("items")
.map { lookup -> Items.lookup(lookup) }
.filterNot { it is EmptyTestableItem }
val chance = it.getDouble("chance")
Drop(chance, items)
}
)
override val spawnEgg = config.getBool("spawn.egg.enabled").ifTrue {
val conditions = Conditions.compile(
config.getSubsections("spawn.egg.conditions"),
context.with("spawn egg conditions")
)
val name = config.getString("spawn.egg.name")
val lore = config.getStrings("spawn.egg.lore")
val item = Items.lookup(config.getString("spawn.egg.item")).item.apply {
this.fast().ecoMobEgg = this@ConfigDrivenEcoMob
}
CustomItem(
plugin.createNamespacedKey("${this.id}_spawn_egg"),
{ item.fast().ecoMobEgg == this },
item
).register()
val isCraftable = config.getBool("spawn.egg.craftable")
if (isCraftable) {
Recipes.createAndRegisterRecipe(
plugin,
"${this.id}_spawn_egg",
item,
config.getStrings("spawn.egg.recipe")
)
}
SpawnEgg(
this,
conditions,
item,
name,
lore
)
}
/*
----------
*/
private inline fun <T : Any> T?.onSpawn(crossinline block: LivingMobImpl.(T) -> Unit) {
if (this != null) {
onSpawnActions += { it.block(this) }
}
}
/*
----------
*/
override fun getIntegrationConfig(integration: MobIntegration): Config {
return config.getSubsection("integrations.${integration.configKey}")
}
override fun getDamageModifier(cause: DamageCause): Double {
return damageModifiers[cause] ?: 1.0
}
override fun canPlayerSpawn(player: Player, spawnReason: SpawnReason, location: Location): Boolean {
if (spawnReason == SpawnReason.NATURAL) {
throw IllegalArgumentException("Players cannot spawn mobs naturally")
}
return true
}
override fun spawnDrops(location: Location, player: Player?) {
drops.drop(location, player)
}
override fun handleEvent(event: MobEvent, trigger: DispatchedTrigger) {
eventEffects[event]?.trigger(trigger)
}
override fun getLivingMob(uuid: UUID): LivingMob? {
return trackedMobs[uuid]
}
@Suppress("UNCHECKED_CAST")
override fun spawn(location: Location, reason: SpawnReason): LivingMob? {
// Call bukkit event
val preSpawnEvent = EcoMobPreSpawnEvent(this, reason)
Bukkit.getPluginManager().callEvent(preSpawnEvent)
if (preSpawnEvent.isCancelled) {
return null
}
// Spawn bukkit mob
val entity = mob.spawn(location) as? Mob ?: throw IllegalStateException("Mob is not a mob")
// Mark as custom mob
entity.ecoMob = this
// Set custom AI
val controller = entity.controller
if (isOverridingAI) {
controller.clearAllGoals()
}
for (goal in targetGoals) {
controller.addGoal(goal)
}
for (goal in entityGoals) {
controller.addGoal(goal)
}
// Create living mob
val livingMob = LivingMobImpl(plugin, this, entity) {
trackedMobs.remove(entity.uniqueId)
}
// Run on-spawn actions
for (action in onSpawnActions) {
action(livingMob)
}
// Add base tickets
livingMob.addTickHandler(TickHandlerDisplayName())
livingMob.addTickHandler(TickHandlerLifespan())
// Call spawn event
val spawnEvent = EcoMobSpawnEvent(livingMob, reason)
Bukkit.getPluginManager().callEvent(spawnEvent)
// Track mob and start ticking
trackedMobs[entity.uniqueId] = livingMob
livingMob.startTicking()
return livingMob
}
}
var Mob.ecoMob: EcoMob?
get() = persistentDataContainer.get(mobKey, PersistentDataType.STRING)
?.let { EcoMobs[it] }
internal set(value) {
if (value == null) {
persistentDataContainer.remove(mobKey)
return
}
persistentDataContainer.set(mobKey, PersistentDataType.STRING, value.id)
}

View File

@@ -0,0 +1,88 @@
package com.willfp.ecomobs.mob.impl
import com.willfp.eco.util.formatEco
import com.willfp.ecomobs.EcoMobsPlugin
import com.willfp.ecomobs.event.EcoMobDespawnEvent
import com.willfp.ecomobs.mob.EcoMob
import com.willfp.ecomobs.mob.LivingMob
import com.willfp.ecomobs.mob.placeholder.formatMobPlaceholders
import com.willfp.ecomobs.tick.TickHandler
import org.bukkit.Bukkit
import org.bukkit.entity.Mob
import org.bukkit.entity.Player
internal class LivingMobImpl(
plugin: EcoMobsPlugin,
override val mob: EcoMob,
override val entity: Mob,
private val deathCallback: () -> Unit
) : LivingMob {
private val ticker = plugin.runnableFactory.create {
tick(tick)
tick++
if (!isAlive) {
it.cancel()
remove()
}
}
private var isRunning = false
private var tick = 1
private val tickHandlers = mutableListOf<TickHandler>()
override val isAlive: Boolean
get() = entity.isValid
override val displayName: String
get() = mob.rawDisplayName.formatMobPlaceholders(this).formatEco()
override val ticksLeft: Int
get() = mob.lifespan - tick
fun addTickHandler(handler: TickHandler) {
tickHandlers += handler
}
private fun tick(tick: Int) {
for (handler in tickHandlers) {
handler.tick(this, tick)
}
}
fun startTicking() {
if (isRunning) {
throw IllegalStateException("Ticking already started")
}
isRunning = true
ticker.runTaskTimer(1, 1)
}
override fun kill(player: Player?) {
remove()
mob.spawnDrops(entity.location, player)
}
override fun despawn() {
remove()
Bukkit.getPluginManager().callEvent(
EcoMobDespawnEvent(this)
)
}
private fun remove() {
ticker.cancel()
entity.remove()
deathCallback()
for (handler in this.tickHandlers) {
handler.onRemove(this, tick)
}
}
}

View File

@@ -0,0 +1,9 @@
package com.willfp.ecomobs.mob.options
import net.kyori.adventure.bossbar.BossBar
data class BossBarOptions(
val color: BossBar.Color,
val style: BossBar.Overlay,
val radius: Double
)

View File

@@ -0,0 +1,45 @@
package com.willfp.ecomobs.mob.options
import com.willfp.eco.core.drops.DropQueue
import com.willfp.eco.core.items.TestableItem
import com.willfp.eco.util.randDouble
import org.bukkit.Location
import org.bukkit.entity.ExperienceOrb
import org.bukkit.entity.Player
data class Drop(
val chance: Double,
val items: List<TestableItem>
)
data class MobDrops(
val experience: Int,
val drops: List<Drop>
) {
fun drop(location: Location, player: Player?) {
if (player != null) {
val queue = DropQueue(player)
.addXP(experience)
for (drop in drops) {
if (randDouble(0.0, 100.0) <= drop.chance) {
queue.addItems(drop.items.map { it.item })
}
}
queue.push()
} else {
val world = location.world ?: throw IllegalStateException("Location has no world")
for (drop in drops) {
if (randDouble(0.0, 100.0) <= drop.chance) {
world.dropItemNaturally(location, drop.items.random().item)
}
}
world.spawn(location, ExperienceOrb::class.java).apply {
experience = experience
}
}
}
}

View File

@@ -0,0 +1,70 @@
package com.willfp.ecomobs.mob.options
import com.willfp.eco.core.fast.FastItemStack
import com.willfp.eco.core.fast.fast
import com.willfp.eco.core.items.builder.modify
import com.willfp.eco.util.formatEco
import com.willfp.eco.util.namespacedKeyOf
import com.willfp.ecomobs.mob.EcoMob
import com.willfp.ecomobs.mob.EcoMobs
import com.willfp.ecomobs.mob.LivingMob
import com.willfp.ecomobs.mob.SpawnReason
import com.willfp.libreforge.conditions.ConditionList
import com.willfp.libreforge.triggers.TriggerData
import org.bukkit.Location
import org.bukkit.entity.Player
import org.bukkit.inventory.ItemStack
import org.bukkit.persistence.PersistentDataType
import sun.jvm.hotspot.oops.CellTypeState.value
class SpawnEgg internal constructor(
val mob: EcoMob,
val conditions: ConditionList,
private val backingItem: ItemStack,
private val rawDisplayName: String,
private val rawLore: List<String>
) {
fun getItem(player: Player?): ItemStack {
return backingItem.clone().modify {
this.setDisplayName(rawDisplayName.formatEco(player))
this.addLoreLines(rawLore.formatEco(player))
}
}
fun trySpawn(location: Location, player: Player?): LivingMob? {
if (player != null) {
val canSpawn = conditions.areMetAndTrigger(
TriggerData(
player = player
).dispatch(player)
)
if (!canSpawn) {
return null
}
}
return mob.spawn(location, SpawnReason.EGG)
}
}
private val spawnEggKey = namespacedKeyOf("ecomobs", "spawn_egg")
var ItemStack.ecoMobEgg: EcoMob?
get() = this.fast().ecoMobEgg
internal set(value) {
this.fast().ecoMobEgg = value
}
var FastItemStack.ecoMobEgg: EcoMob?
get() {
val id = this.persistentDataContainer.get(spawnEggKey, PersistentDataType.STRING) ?: return null
return EcoMobs[id]
}
internal set(value) {
if (value == null) {
this.persistentDataContainer.remove(spawnEggKey)
} else {
this.persistentDataContainer.set(spawnEggKey, PersistentDataType.STRING, value.id)
}
}

View File

@@ -0,0 +1,17 @@
package com.willfp.ecomobs.mob.placeholder
import com.willfp.eco.core.registry.KRegistrable
import com.willfp.ecomobs.mob.LivingMob
import com.willfp.ecomobs.plugin
import org.bukkit.Bukkit
import org.bukkit.entity.Player
import org.bukkit.event.Listener
abstract class MobPlaceholder(
final override val id: String
) : KRegistrable, Listener {
/**
* Get the value of the placeholder.
*/
abstract fun getValue(mob: LivingMob): String
}

View File

@@ -0,0 +1,32 @@
package com.willfp.ecomobs.mob.placeholder
import com.willfp.eco.core.registry.Registry
import com.willfp.ecomobs.mob.LivingMob
import com.willfp.ecomobs.mob.event.impl.MobEventAnyAttack
import com.willfp.ecomobs.mob.event.impl.MobEventDamagePlayer
import com.willfp.ecomobs.mob.event.impl.MobEventDeath
import com.willfp.ecomobs.mob.event.impl.MobEventDespawn
import com.willfp.ecomobs.mob.event.impl.MobEventInteract
import com.willfp.ecomobs.mob.event.impl.MobEventKill
import com.willfp.ecomobs.mob.event.impl.MobEventKillPlayer
import com.willfp.ecomobs.mob.event.impl.MobEventMeleeAttack
import com.willfp.ecomobs.mob.event.impl.MobEventRangedAttack
import com.willfp.ecomobs.mob.event.impl.MobEventSpawn
import com.willfp.ecomobs.mob.event.impl.MobEventTakeDamage
import com.willfp.ecomobs.mob.placeholder.impl.MobPlaceholderHealth
import com.willfp.ecomobs.mob.placeholder.impl.MobPlaceholderTime
object MobPlaceholders : Registry<MobPlaceholder>() {
init {
register(MobPlaceholderHealth)
register(MobPlaceholderTime)
}
}
fun String.formatMobPlaceholders(mob: LivingMob): String {
var string = this
MobPlaceholders.forEach { placeholder ->
string = string.replace("%${placeholder.id}%", placeholder.getValue(mob))
}
return string
}

View File

@@ -0,0 +1,11 @@
package com.willfp.ecomobs.mob.placeholder.impl
import com.willfp.eco.util.toNiceString
import com.willfp.ecomobs.mob.LivingMob
import com.willfp.ecomobs.mob.placeholder.MobPlaceholder
object MobPlaceholderHealth : MobPlaceholder("health") {
override fun getValue(mob: LivingMob): String {
return mob.entity.health.toNiceString()
}
}

View File

@@ -0,0 +1,13 @@
package com.willfp.ecomobs.mob.placeholder.impl
import com.willfp.ecomobs.mob.LivingMob
import com.willfp.ecomobs.mob.placeholder.MobPlaceholder
object MobPlaceholderTime : MobPlaceholder("time") {
override fun getValue(mob: LivingMob): String {
val ticksLeft = mob.ticksLeft
val secondsLeft = ticksLeft / 20
return String.format("%d:%02d", secondsLeft / 60, secondsLeft % 60)
}
}

View File

@@ -0,0 +1,20 @@
package com.willfp.ecomobs.tick
import com.willfp.ecomobs.mob.LivingMob
/**
* Handle ticking mobs.
*/
interface TickHandler {
/**
* Tick the mob.
*/
fun tick(mob: LivingMob, tick: Int)
/**
* Called when the mob is removed.
*/
fun onRemove(mob: LivingMob, tick: Int) {
// Override when needed
}
}

View File

@@ -0,0 +1,51 @@
package com.willfp.ecomobs.tick
import com.willfp.eco.util.asAudience
import com.willfp.eco.util.toComponent
import com.willfp.ecomobs.mob.LivingMob
import com.willfp.ecomobs.mob.options.BossBarOptions
import net.kyori.adventure.bossbar.BossBar
import org.bukkit.Bukkit
import org.bukkit.attribute.Attribute
import org.bukkit.entity.Player
class TickHandlerBossBar(
private val bar: BossBar,
private val options: BossBarOptions
) : TickHandler {
override fun tick(mob: LivingMob, tick: Int) {
if (tick % 5 != 0) {
return
}
val entity = mob.entity
val maxHealth = entity.getAttribute(Attribute.GENERIC_MAX_HEALTH)?.value
?: throw IllegalStateException("Entity ${entity.type} has no max health attribute")
bar.name(mob.displayName.toComponent())
bar.progress((entity.health / maxHealth).coerceAtMost(1.0).toFloat())
// Only run every 2 seconds to save CPU
if (tick % 40 != 0) {
return
}
for (player in Bukkit.getOnlinePlayers()) {
player.asAudience().hideBossBar(bar)
}
entity.getNearbyEntities(
options.radius,
options.radius,
options.radius
).filterIsInstance<Player>()
.map { it.asAudience() }
.forEach { it.showBossBar(bar) }
}
override fun onRemove(mob: LivingMob, tick: Int) {
for (player in Bukkit.getOnlinePlayers()) {
player.asAudience().hideBossBar(bar)
}
}
}

View File

@@ -0,0 +1,14 @@
package com.willfp.ecomobs.tick
import com.willfp.ecomobs.mob.LivingMob
class TickHandlerDisplayName: TickHandler {
override fun tick(mob: LivingMob, tick: Int) {
if (tick % 5 != 0) {
return
}
@Suppress("DEPRECATION")
mob.entity.customName = mob.displayName
}
}

View File

@@ -0,0 +1,11 @@
package com.willfp.ecomobs.tick
import com.willfp.ecomobs.mob.LivingMob
class TickHandlerLifespan: TickHandler {
override fun tick(mob: LivingMob, tick: Int) {
if (mob.ticksLeft <= 0) {
mob.despawn()
}
}
}

View File

@@ -0,0 +1,208 @@
# The ID of the boss is the name of the .yml file,
# for example steel_golem.yml has the ID of steel_golem
# You can place bosses anywhere in this folder,
# including in subfolders if you want to organize your boss configs
# _example.yml is not loaded.
# A base mob and modifiers
# View an explanation for this system here: https://plugins.auxilor.io/all-plugins/the-entity-lookup-system
mob: iron_golem attack-damage:90 movement-speed:1.5 follow-range:16 health:1200
# If you're using model engine, you can specify the ID here. You can also specify these in the mob with the lookup system.
model-engine-id: ""
# Supported placeholders: %health%, %time% (formats as minutes:seconds, eg 1:56)
display-name: "&8Steel Golem &7| &c%health%♥ &7| &e%time%"
influence: 40 # The distance at which effects will be applied to players
custom-ai: # Custom mob AI using the entity goal system.
enabled: false # If custom AI should be enabled, this will override the vanilla mob behaviour.
target-goals: [ ] # How the boss decides who to attack, if the target mode isn't being used.
ai-goals: [ ] # How the boss should behave.
effects: # Effects are done from the player's perspective: to treat the player as the victim, use the self_as_victim option in args
- id: run_chain
args:
chain: blind
self_as_victim: true
chance: 20
triggers:
- static_20
conditions: [ ] # Conditions to apply effects to players; useful if you don't want to affect low-level players
lifespan: 120 # The lifespan of the boss before it despawns, in seconds. Set to a massive number to disable.
defence:
prevent-mounts: true # If the boss shouldn't be able to get into boats, minecarts, etc
explosion-immune: true # If the boss should be immune to explosions
fire-immune: true # If the boss should be immune to fire damage
drowning-immune: true # If the boss should be immune to drowning damage
suffocation-immune: true # If the boss should be immune to suffocation
melee-damage-multiplier: 0.8 # Incoming melee damage will be multiplied by this value. Set to 0 to render immune against melee
projectile-damage-multiplier: 0.2 # Same as melee multiplier, but for projectiles
teleportation: # Teleport every x ticks in order to avoid being caged in obsidian or similar
enabled: true # If the boss should teleport
interval: 100 # Ticks between teleportation attempts
range: 20 # The range that the boss should check for safe teleportation blocks.
rewards:
xp: # Experience will be randomly generated between these values
minimum: 30000
maximum: 60000
top-damager-commands:
# You can specify as many ranks as you want (adding 4, 5, etc)
# You can use %player% as a placeholder for the player name
1:
- chance: 100 # As a percentage
commands:
- eco give %player% 10000
2: [ ]
3: [ ]
nearby-player-commands:
# Commands to be executed for all players near the boss death location
radius: 10
# Uses the same syntax as top damager commands (chance and a list of commands, can use %player%)
commands: [ ]
# You can specify as many drops as you want, and group several drops together under one chance
drops:
- chance: 100
items:
- diamond_sword unbreaking:1 name:"Example Sword"
target:
# How the boss should choose which player to attack, choices are:
# highest_health, lowest_health, closest, random
mode: highest_health
# The distance to scan for players
range: 40
boss-bar:
# If the boss should have a boss bar
enabled: true
color: white # Options: blue, green, pink, purple, red, white, yellow
style: progress # Options: progress, notched_20, notched_12, notched_10, notched_6
radius: 120 # The distance from the boss where the boss bar is visible
spawn:
# A list of conditions required for a player to be able to spawn a boss, useful to set
# minimum skill levels, etc
conditions: [ ]
autospawn:
# Spawn the boss automatically every x ticks. Picks a random location, but will only
# ever spawn in a world if there are no other bosses of that type in the world.
interval: -1 # The interval in ticks, set to -1 to disable
locations: # Add as many locations as you want
- world: world
x: 100
y: 100
z: 100
totem:
enabled: false # A spawn totem is a set of 3 blocks on top of each other to spawn a boss (like a snow golem)
top: netherite_block
middle: iron_block
bottom: magma_block
not-in-worlds: [ ] # If spawn totems should be disallowed in certain worlds, specify them here
egg:
enabled: true # If the boss should have a spawn egg
item: evoker_spawn_egg unbreaking:1 hide_enchants
name: "&8Steel Golem&f Spawn Egg"
lore:
- ""
- "&8&oPlace on the ground to"
- "&8&osummon a &8Steel Golem"
craftable: true
recipe:
- iron_block
- netherite_block
- iron_block
- air
- ecoitems:boss_core ? nether_star
- air
- iron_block
- netherite_block
- iron_block
commands:
# For each category, you can add as many commands as you want, which will be executed by
# console. Supported placeholders are the same as for messages (see below)
spawn: [ ]
kill: [ ]
despawn: [ ]
injure: [ ]
messages:
# For each category, you can add as many messages as you want, each with their own radius.
# Radius is the distance from the boss where the player will be sent the message
# Set to -1 to broadcast globally to all players online
# Supported placeholders: %x%, %y%, %z% (coordinates)
spawn:
- message:
- ""
- "&fA &8&lSteel Golem&r&f has been spawned!"
- "&fCome fight it at &8%x%&f, &8%y%&f, &8%z%&f!"
- ""
radius: -1
# Supported placeholders: %damage_<x>_player%, %damage_<X>%
# You can include as many ranks as you want - if there is no player at a certain rank,
# it will be replaced with N/A (change in lang.yml)
kill:
- message:
- ""
- "&fThe &8&lSteel Golem&r&f has been killed!"
- "&fMost Damage:"
- "&f - &8%damage_1_player%&f (%damage_1% Damage)"
- "&f - &8%damage_2_player%&f (%damage_2% Damage)"
- "&f - &8%damage_3_player%&f (%damage_3% Damage)"
- ""
radius: -1
despawn:
- message:
- ""
- "&fYou ran out of time to kill the &8&lSteel Golem&r&f!"
- ""
radius: -1
injure: [ ]
# All sounds will be played together at the same time
# A list of sounds can be found here: https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/Sound.html
# Volume functions as the distance at which the sound will be heard
# Pitch is any value between 0.5 and 2
# If you don't want the vanilla mob sounds, add 'silent' as an option to the mob
sounds:
spawn:
- sound: entity_iron_golem_death
pitch: 0.8
volume: 100
- sound: entity_iron_golem_hurt
pitch: 0.5
volume: 100
- sound: entity_ender_dragon_growl
pitch: 0.5
volume: 100
kill:
- sound: entity_ender_dragon_death
pitch: 1.8
volume: 100
- sound: entity_wither_death
pitch: 1.2
volume: 100
despawn:
- sound: entity_ender_dragon_ambient
pitch: 0.5
volume: 50
- sound: entity_enderman_death
pitch: 0.5
volume: 50
injure:
- sound: entity_iron_golem_damage
pitch: 0.7
volume: 10

View File

@@ -0,0 +1,210 @@
mob: illusioner attack-damage:50 health:600 hand:"iron_sword sharpness:5"
model-engine-id: ""
model-engine-animation: ""
display-name: "&9Illusioner &7| &c%health%♥ &7| &e%time%"
influence: 40
custom-ai:
enabled: true
target-goals:
- key: minecraft:hurt_by
priority: 0
args:
blacklist: [ ]
- key: minecraft:nearest_attackable
priority: 1
args:
target:
- player
checkVisibility: false
checkCanNavigate: true
reciprocalChance: 300
- key: minecraft:nearest_attackable
priority: 2
args:
target:
- iron_golem
- villager
checkVisibility: false
checkCanNavigate: true
reciprocalChance: 300
ai-goals:
- key: minecraft:float
priority: 0
- key: minecraft:illusioner_mirror_spell
priority: 1
- key: minecraft:melee_attack
priority: 2
args:
speed: 1.6
pauseWhenMobIdle: false
- key: minecraft:random_stroll
priority: 8
args:
speed: 0.6
interval: 80
canDespawn: false
- key: minecraft:look_at_player
priority: 9
args:
range: 6
chance: 1
effects:
- id: run_chain
args:
chain: blind
self_as_victim: true
chance: 20
triggers:
- static_20
conditions: [ ]
lifespan: 120
defence:
prevent-mounts: true
explosion-immune: true
fire-immune: true
drowning-immune: true
suffocation-immune: true
melee-damage-multiplier: 1
projectile-damage-multiplier: 0.8
teleportation:
enabled: true
interval: 200
range: 20
rewards:
xp:
minimum: 20000
maximum: 40000
top-damager-commands:
1:
- chance: 100 # As a percentage
commands:
- eco give %player% 10000
2: [ ]
3: [ ]
nearby-player-commands:
radius: 10
commands: [ ]
drops: []
target:
mode: closest
range: 40
boss-bar:
enabled: true
color: blue
style: notched_20
radius: 120
spawn:
conditions: [ ]
autospawn:
interval: -1
locations: []
totem:
enabled: false
top: carved_pumpkin
middle: beacon
bottom: diamond_block
not-in-worlds: [ ]
egg:
enabled: true
item: dolphin_spawn_egg unbreaking:1 hide_enchants
name: "&9Illusioner&f Spawn Egg"
lore:
- ""
- "&8&oPlace on the ground to"
- "&8&osummon an &9Illusioner"
craftable: true
recipe:
- ""
- fermented_spider_eye 64
- ""
- fermented_spider_eye 64
- ecoitems:boss_core ? nether_star
- fermented_spider_eye 64
- ""
- fermented_spider_eye 64
- ""
commands:
spawn: [ ]
kill: [ ]
despawn: [ ]
injure: [ ]
messages:
spawn:
- message:
- ""
- "&fAn &9&lIllusioner&r&f has been spawned!"
- "&fCome fight it at &9%x%&f, &9%y%&f, &9%z%&f!"
- ""
radius: -1
kill:
- message:
- ""
- "&fThe &9&lIllusioner&r&f has been killed!"
- "&fMost Damage:"
- "&f - &9%damage_1_player%&f (%damage_1% Damage)"
- "&f - &9%damage_2_player%&f (%damage_2% Damage)"
- "&f - &9%damage_3_player%&f (%damage_3% Damage)"
- ""
radius: -1
despawn:
- message:
- ""
- "&fYou ran out of time to kill the &9&lIllusioner&r&f!"
- ""
radius: -1
injure: [ ]
sounds:
spawn:
- sound: entity_illusioner_mirror_move
pitch: 0.5
volume: 100
- sound: entity_wither_spawn
pitch: 2
volume: 100
kill:
- sound: entity_evoker_prepare_wololo
pitch: 0.8
volume: 100
- sound: entity_illusioner_prepare_blindness
pitch: 1
volume: 100
- sound: entity_wither_death
pitch: 2
volume: 100
despawn:
- sound: entity_ender_dragon_ambient
pitch: 0.6
volume: 50
- sound: entity_enderman_death
pitch: 0.8
volume: 50
injure:
- sound: entity_illusioner_cast_spell
pitch: 2
volume: 10

View File

@@ -0,0 +1,5 @@
# The ID of the category is the name of the .yml file,
# for example bosses.yml has the ID of bosses
# You can place categories anywhere in this folder,
# including in subfolders if you want to organize your category configs
# _example.yml is not loaded.

View File

@@ -0,0 +1,6 @@
#
# EcoMobs
# by Auxilor
#
discover-recipes: true

View File

@@ -0,0 +1,8 @@
environment:
- name: libreforge version
value: ${libreforgeVersion}
options:
resource-id: 525
bstats-id: 10635
color: "&9"

View File

@@ -0,0 +1,14 @@
messages:
prefix: "&9&lEcoMobs &f» "
no-permission: "&cYou don't have permission to do this!"
not-player: "&cThis command must be run by a player"
invalid-command: "&cUnknown subcommand!"
reloaded: "Reloaded!"
needs-player: "&cYou must specify a player"
invalid-player: "&cInvalid player!"
specify-mob: "&cYou must specify a valid mob!"
invalid-location: "&cInvalid location!"
spawned: "Spawned %mob%&f!"
na: "N/A"

View File

@@ -0,0 +1,182 @@
# The ID of the mob is the name of the .yml file,
# for example steel_golem.yml has the ID of steel_golem
# You can place mobs anywhere in this folder,
# including in subfolders if you want to organize your mob configs
# _example.yml is not loaded.
# A base mob and modifiers
# View an explanation for this system here: https://plugins.auxilor.io/all-plugins/the-entity-lookup-system
mob: iron_golem attack-damage:90 movement-speed:1.5 follow-range:16 health:1200
# The ID of the mob category.
category: common
# Supported placeholders: %health%, %time% (formats as minutes:seconds, eg 1:56)
display-name: "&8Steel Golem &7| &c%health%♥ &7| &e%time%"
# If the mob you're using supports equipment, you can specify the items in each slot.
# Remove any slots that you don't want to put equipment in.
equipment:
hand: diamond_sword sharpness:2
off-hand: shield
head: ""
chest: ""
legs: ""
feet: ""
# Options for plugin integrations
# Remove sections for plugins you're not using
integrations:
# Options for LevelledMobs
levelled-mobs:
can-level: true
# Options for ModelEngine
model-engine:
id: ""
# Options for LibsDisguises
libs-disguises:
id: ""
# Custom Mob AI
# Read here: https://plugins.auxilor.io/all-plugins/custom-entity-ai
custom-ai:
# If custom AI should override the vanilla entity AI.
clear: false
# How the mob decides who to attack.
target-goals: [ ]
# How the mob should behave.
entity-goals: [ ]
# Effects are done from the player's perspective: to treat the player as the victim,
# either use self_as_victim in args, or use player_as_victim in mutators.
effects:
# Effects ran when the mob spawns
spawn: [ ]
# Effects ran when the mob despawns
despawn: [ ]
# Effects ran when the player interacts with the mob
interact: [ ]
# Effects ran when the player melee attacks the mob
melee-attack: [ ]
# Effects ran when the player does a ranged attack on the mob
ranged-attack: [ ]
# Effects ran when the player attacks the mob
any-attack: [ ]
# Effects ran when the mob takes damage
take-damage: [ ]
# Effects ran when the player is damaged by the mob
damage-player: [ ]
# Effects ran when the player is killed by the mob
kill-player: [ ]
# Effects ran when the mob dies
death: [ ]
# Effects ran when the mob is killed by the player
kill: [ ]
# The lifespan of the mob, in seconds. Set to -1 to disable.
lifespan: 120
defence:
# If the mob can get into boats, minecarts, etc.
can-mount: true
# A list of damage causes that the mob should multiply incoming damage by.
# The list of damage causes can be found here:
# https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/entity/EntityDamageEvent.DamageCause.html
damage-modifiers:
hot_floor: 1
fire_tick: 1
lava: 1
suffocation: 1
drowning: 1
entity_explosion: 1
block_explosion: 1
# Options for what the mob drops
drops:
# The amount of experience to drop
experience: 30
# You can specify as many drops as you want, and group several drops together under one chance
items:
- chance: 100
items:
- diamond_sword unbreaking:1 name:"Example Sword"
# Options for the boss bar
boss-bar:
# If the mob should have a boss bar
enabled: true
# Options: blue, green, pink, purple, red, white, yellow
color: white
# Options: progress, notched_20, notched_12, notched_10, notched_6
style: progress
# The distance from the mob where the boss bar is visible
radius: 120
# Options for spawning the mob
spawn:
# A spawn totem is a set of 3 blocks on top of each other to spawn a mob (like a snow golem)
totem:
# If spawn totems should be enabled
enabled: false
# The top block
top: netherite_block
# The middle block
middle: iron_block
# The bottom block
bottom: magma_block
# The conditions for the totem to work
conditions: [ ]
# Options for a spawn egg
egg:
# If the mob should have a spawn egg
enabled: true
# The conditions for the spawn egg to work
# not-met-lines will show up on the spawn egg
conditions: [ ]
# The spawn egg item
item: evoker_spawn_egg unbreaking:1 hide_enchants
name: "&8Steel Golem&f Spawn Egg"
lore:
- ""
- "&8&oPlace on the ground to"
- "&8&osummon a &8Steel Golem"
craftable: true
recipe:
- iron_block
- netherite_block
- iron_block
- air
- ecoitems:boss_core ? nether_star
- air
- iron_block
- netherite_block
- iron_block

View File

@@ -0,0 +1,65 @@
name: ${pluginName}
version: ${version}
main: com.willfp.ecomobs.EcoMobsPlugin
api-version: 1.19
dependencies:
- name: eco
required: true
bootstrap: false
- name: libreforge
required: false
bootstrap: false
- name: LevelledMobs
required: false
bootstrap: false
- name: ModelEngine
required: false
bootstrap: false
- name: LibsDisguises
required: false
bootstrap: false
load-after:
- name: eco
bootstrap: false
permissions:
ecomobs.*:
description: All ecomobs permissions
default: op
children:
ecomobs.command.*: true
ecomobs.command.*:
description: All ecomobs commands
default: op
children:
ecomobs.command.ecomobs: true
ecomobs.command.reload: true
ecomobs.command.spawn: true
ecomobs.command.give: true
ecomobs.command.ecomobs:
description: Allows the use of /ecomobs
default: true
ecomobs.command.give:
description: Allows the use of /ecomobs give
default: op
ecomobs.command.spawn:
description: Allows the use of /ecomobs spawn
default: op
ecomobs.command.killall:
description: Allows the use of /ecomobs killall
default: op
ecomobs.command.reload:
description: Allows the use of /ecomobs reload
default: op

View File

@@ -0,0 +1,53 @@
name: ${pluginName}
version: ${version}
main: com.willfp.ecomobs.EcoMobsPlugin
api-version: 1.17
authors: [ Auxilor ]
website: willfp.com
depend:
- eco
softdepend:
- libreforge
- LevelledMobs
- ModelEngine
- LibsDisguises
commands:
ecomobs:
description: Base command
permission: ecomobs.command.ecomobs
permissions:
ecomobs.*:
description: All ecomobs permissions
default: op
children:
ecomobs.command.*: true
ecomobs.command.*:
description: All ecomobs commands
default: op
children:
ecomobs.command.ecomobs: true
ecomobs.command.reload: true
ecomobs.command.spawn: true
ecomobs.command.give: true
ecomobs.command.ecomobs:
description: Allows the use of /ecomobs
default: true
ecomobs.command.give:
description: Allows the use of /ecomobs give
default: op
ecomobs.command.spawn:
description: Allows the use of /ecomobs spawn
default: op
ecomobs.command.killall:
description: Allows the use of /ecomobs killall
default: op
ecomobs.command.reload:
description: Allows the use of /ecomobs reload
default: op