Skip to content

Commit

Permalink
Tracking fixes, entity type, MM conversion (#20)
Browse files Browse the repository at this point in the history
* Consider having prefabs as having components, fixes some old bugged entities

* Add GearyEntityAddToWorldEvent, add migrators for swapping entity types or swapping to MythicMobs

* Fix encoding components on chunk unload or server restart

* Bump version
  • Loading branch information
0ffz authored Dec 25, 2023
1 parent c378f29 commit d8ba11b
Show file tree
Hide file tree
Showing 16 changed files with 203 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package com.mineinabyss.geary.papermc.plugin

import com.mineinabyss.geary.helpers.entity
import com.mineinabyss.geary.modules.archetypes
import com.mineinabyss.geary.modules.geary
import com.mineinabyss.geary.papermc.gearyPaper
import com.mineinabyss.geary.papermc.plugin.commands.locate
import com.mineinabyss.geary.papermc.plugin.commands.query
Expand All @@ -22,7 +21,6 @@ import com.mineinabyss.idofront.commands.extensions.actions.playerAction
import com.mineinabyss.idofront.messaging.error
import com.mineinabyss.idofront.messaging.info
import com.mineinabyss.idofront.messaging.success
import kotlinx.coroutines.launch
import okio.Path.Companion.toOkioPath
import org.bukkit.Bukkit
import org.bukkit.command.CommandSender
Expand All @@ -35,7 +33,6 @@ internal class GearyCommands : IdofrontCommandExecutor(), TabCompleter {
private val plugin get() = gearyPaper.plugin
private val prefabLoader get() = prefabs.loader
private val prefabManager get() = prefabs.manager
private val engine get() = geary.engine

override val commands = commands(plugin) {
"geary" {
Expand Down Expand Up @@ -116,33 +113,29 @@ internal class GearyCommands : IdofrontCommandExecutor(), TabCompleter {
"reload" {
val prefab by stringArg()
action {
engine.launch {
runCatching { prefabLoader.reread(PrefabKey.of(prefab).toEntity()) }
.onSuccess { sender.success("Reread prefab $prefab") }
.onFailure { sender.error("Failed to reread prefab $prefab:\n${it.message}") }
}
runCatching { prefabLoader.reread(PrefabKey.of(prefab).toEntity()) }
.onSuccess { sender.success("Reread prefab $prefab") }
.onFailure { sender.error("Failed to reread prefab $prefab:\n${it.message}") }
}
}
"load" {
val namespace by stringArg()
val path by stringArg()
action {
engine.launch {
// Ensure not already registered
if (prefabManager[PrefabKey.of(namespace, Path(path).nameWithoutExtension)] != null) {
sender.error("Prefab $namespace:$path already exists")
return@launch
}

// Try to load from file
prefabLoader.loadFromPath(
namespace,
plugin.dataFolder.resolve(namespace).resolve(path).toOkioPath()
).onSuccess {
it.inheritPrefabs()
sender.success("Read prefab $namespace:$path")
}.onFailure { sender.error("Failed to read prefab $namespace:$path:\n${it.message}") }
// Ensure not already registered
if (prefabManager[PrefabKey.of(namespace, Path(path).nameWithoutExtension)] != null) {
sender.error("Prefab $namespace:$path already exists")
return@action
}

// Try to load from file
prefabLoader.loadFromPath(
namespace,
plugin.dataFolder.resolve(namespace).resolve(path).toOkioPath()
).onSuccess {
it.inheritPrefabs()
sender.success("Read prefab $namespace:$path")
}.onFailure { sender.error("Failed to read prefab $namespace:$path:\n${it.message}") }
}
}
}
Expand Down Expand Up @@ -195,28 +188,29 @@ internal class GearyCommands : IdofrontCommandExecutor(), TabCompleter {
}
}

"prefab" -> when (if (args.size == 2) return listOf("load", "reload") else args[1]) {
"reload" -> return prefabManager.keys.filter {
val arg = args[2].lowercase()
it.key.startsWith(arg) || it.full.startsWith(arg)
}.map { it.toString() }

"load" -> return when (args.size) {
3 -> plugin.dataFolder.listFiles()?.filter {
it.isDirectory && it.name.startsWith(args[2].lowercase())
}?.map { it.name } ?: listOf()

4 -> plugin.dataFolder.resolve(args[2]).walkTopDown().toList().filter {
it.isFile && it.extension == "yml" && it.nameWithoutExtension.startsWith(args[3].lowercase())
&& "${args[2]}:${it.nameWithoutExtension}" !in prefabManager.keys.map(PrefabKey::full)
}.map {
it.absolutePath.split(plugin.dataFolder.absolutePath + "\\" + args[2] + "\\")[1]
.replace("\\", "/")
}
"prefab" -> {
if (!sender.hasPermission("geary.prefab")) return emptyList()
when (if (args.size == 2) return listOf("load", "reload") else args[1]) {
"reload" -> return prefabManager.keys.filter {
val arg = args[2].lowercase()
it.key.startsWith(arg) || it.full.startsWith(arg)
}.map { it.toString() }

"load" -> return when (args.size) {
3 -> plugin.dataFolder.listFiles()?.filter {
it.isDirectory && it.name.startsWith(args[2].lowercase())
}?.map { it.name } ?: listOf()

4 -> plugin.dataFolder.resolve(args[2]).walkTopDown().toList().filter {
it.isFile && it.extension == "yml" && it.nameWithoutExtension.startsWith(args[3].lowercase())
&& "${args[2]}:${it.nameWithoutExtension}" !in prefabManager.keys.map(PrefabKey::full)
}.map {
it.relativeTo(plugin.dataFolder.resolve(args[2])).toString()
}

else -> return listOf()
else -> return listOf()
}
}

}

else -> return listOf()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.mineinabyss.geary.papermc.GearyPlugin
import com.mineinabyss.geary.papermc.GearyProductionPaperConfigModule
import com.mineinabyss.geary.papermc.bridge.PaperBridge
import com.mineinabyss.geary.papermc.configlang.ConfigLang
import com.mineinabyss.geary.papermc.datastore.encodeComponentsTo
import com.mineinabyss.geary.papermc.datastore.withUUIDSerializer
import com.mineinabyss.geary.papermc.tracking.blocks.BlockTracking
import com.mineinabyss.geary.papermc.tracking.blocks.gearyBlocks
Expand All @@ -29,7 +30,6 @@ import com.mineinabyss.idofront.serialization.SerializablePrefabItemService
import com.mineinabyss.serialization.formats.YamlFormat
import okio.FileSystem
import okio.Path.Companion.toOkioPath
import org.bukkit.entity.Player
import org.bukkit.event.Listener
import org.bukkit.inventory.ItemStack
import kotlin.io.path.isDirectory
Expand Down Expand Up @@ -118,8 +118,9 @@ class GearyPluginImpl : GearyPlugin() {
override fun onDisable() {
server.worlds.forEach { world ->
world.entities.forEach entities@{ entity ->
if (entity is Player) return@entities
entity.toGearyOrNull()?.removeEntity()
val gearyEntity = entity.toGearyOrNull() ?: return@entities
gearyEntity.encodeComponentsTo(entity)
gearyEntity.removeEntity()
}
}
server.scheduler.cancelTasks(this)
Expand Down
1 change: 1 addition & 0 deletions geary-papermc-tracking/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ repositories {
dependencies {
compileOnly(libs.minecraft.plugin.mythic.dist)
compileOnly(libs.idofront.nms)
compileOnly(libs.minecraft.mccoroutine)
implementation(gearyLibs.uuid)
api(project(":geary-papermc-datastore"))
api(project(":geary-papermc-core"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package com.mineinabyss.geary.papermc.tracking.entities
import com.mineinabyss.geary.datatypes.GearyEntity
import com.mineinabyss.geary.helpers.entity
import com.mineinabyss.geary.helpers.toGeary
import com.mineinabyss.geary.papermc.tracking.entities.components.AddedToWorld
import com.mineinabyss.geary.papermc.tracking.entities.events.GearyEntityAddToWorldEvent
import com.mineinabyss.idofront.typealiases.BukkitEntity
import it.unimi.dsi.fastutil.ints.Int2LongOpenHashMap
import org.spigotmc.AsyncCatcher
Expand Down Expand Up @@ -30,7 +32,10 @@ class BukkitEntity2Geary(val forceMainThread: Boolean = true) {
fun getOrCreate(bukkit: BukkitEntity): GearyEntity {
return get(bukkit) ?: run {
if (forceMainThread) AsyncCatcher.catchOp("Async geary entity creation for $bukkit")
entity { set(bukkit) }
entity { set(bukkit) }.also {
it.add<AddedToWorld>()
GearyEntityAddToWorldEvent(it, bukkit).callEvent()
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,17 @@ import com.mineinabyss.geary.helpers.componentId
import com.mineinabyss.geary.modules.geary
import com.mineinabyss.geary.papermc.gearyPaper
import com.mineinabyss.geary.papermc.tracking.entities.helpers.GearyMobPrefabQuery
import com.mineinabyss.geary.papermc.tracking.entities.systems.*
import com.mineinabyss.geary.papermc.tracking.entities.systems.EntityWorldEventTracker
import com.mineinabyss.geary.papermc.tracking.entities.systems.TrackOnSetBukkitComponent
import com.mineinabyss.geary.papermc.tracking.entities.systems.UntrackOnRemoveBukkitComponent
import com.mineinabyss.geary.papermc.tracking.entities.systems.attemptspawn.AttemptSpawnListener
import com.mineinabyss.geary.papermc.tracking.entities.systems.attemptspawn.AttemptSpawnMythicMob
import com.mineinabyss.geary.papermc.tracking.entities.systems.updatemobtype.ConvertEntityTypesListener
import com.mineinabyss.geary.papermc.tracking.entities.systems.updatemobtype.ConvertToMythicMobListener
import com.mineinabyss.idofront.di.DI
import com.mineinabyss.idofront.plugin.listeners
import com.mineinabyss.idofront.typealiases.BukkitEntity
import org.bukkit.Bukkit

val gearyMobs by DI.observe<EntityTracking>()

Expand All @@ -31,10 +38,22 @@ interface EntityTracking {
TrackOnSetBukkitComponent(),
UntrackOnRemoveBukkitComponent(),
AttemptSpawnListener(),
AttemptSpawnMythicMob()
)
geary.pipeline.runOnOrAfter(GearyPhase.ENABLE) {
gearyPaper.plugin.listeners(EntityWorldEventTracker())
gearyPaper.plugin.listeners(
EntityWorldEventTracker(),
ConvertEntityTypesListener(),
)

if (Bukkit.getPluginManager().plugins.any { it.name == "MythicMobs" }) {
geary.pipeline.addSystems(
AttemptSpawnMythicMob(),
)

gearyPaper.plugin.listeners(
ConvertToMythicMobListener(),
)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.mineinabyss.geary.papermc.tracking.entities.events

import com.mineinabyss.geary.datatypes.GearyEntity
import com.mineinabyss.idofront.typealiases.BukkitEntity
import org.bukkit.event.Event
import org.bukkit.event.HandlerList


/**
* Called when a bukkit entity is added to the world and registered with Geary.
*
* Will avoid multiple calls like the Paper event currently does
*/
class GearyEntityAddToWorldEvent(
val gearyEntity: GearyEntity,
val entity: BukkitEntity,
) : Event() {
companion object {
@JvmStatic
private val HANDLER_LIST = HandlerList()

@JvmStatic
fun getHandlerList() = HANDLER_LIST
}

override fun getHandlers() = HANDLER_LIST

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,25 @@ package com.mineinabyss.geary.papermc.tracking.entities.helpers

import com.mineinabyss.geary.datatypes.GearyEntity
import com.mineinabyss.geary.helpers.entity
import com.mineinabyss.geary.papermc.datastore.loadComponentsFrom
import com.mineinabyss.geary.papermc.tracking.entities.components.AttemptSpawn
import com.mineinabyss.geary.prefabs.PrefabKey
import com.mineinabyss.geary.prefabs.helpers.addPrefab
import com.mineinabyss.geary.prefabs.prefabs
import com.mineinabyss.idofront.typealiases.BukkitEntity
import org.bukkit.Location
import org.bukkit.persistence.PersistentDataContainer


fun Location.spawnFromPrefab(prefab: PrefabKey): Result<BukkitEntity> {
val entity = prefabs.manager[prefab] ?: return Result.failure(IllegalArgumentException("No prefab found"))
return spawnFromPrefab(entity)
}

fun Location.spawnFromPrefab(prefab: GearyEntity): Result<BukkitEntity> {
fun Location.spawnFromPrefab(prefab: GearyEntity, existingPDC: PersistentDataContainer? = null): Result<BukkitEntity> {
return runCatching {
entity {
if (existingPDC != null) loadComponentsFrom(existingPDC)
addPrefab(prefab)
callEvent(AttemptSpawn(this@spawnFromPrefab))
}.get<BukkitEntity>() ?: error("Entity was not created when spawning from prefab")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,58 @@ package com.mineinabyss.geary.papermc.tracking.entities.systems

import com.destroystokyo.paper.event.entity.EntityAddToWorldEvent
import com.destroystokyo.paper.event.entity.EntityRemoveFromWorldEvent
import com.mineinabyss.geary.papermc.tracking.entities.components.AddedToWorld
import com.mineinabyss.geary.papermc.datastore.encodeComponentsTo
import com.mineinabyss.geary.papermc.gearyPaper
import com.mineinabyss.geary.papermc.tracking.entities.gearyMobs
import com.mineinabyss.geary.papermc.tracking.entities.toGearyOrNull
import org.bukkit.Bukkit
import org.bukkit.entity.Player
import org.bukkit.event.EventHandler
import org.bukkit.event.EventPriority
import org.bukkit.event.Listener
import org.bukkit.event.player.PlayerJoinEvent
import org.bukkit.event.player.PlayerQuitEvent
import org.bukkit.event.world.EntitiesUnloadEvent

class EntityWorldEventTracker : Listener {
/** Remove entities from ECS when they are removed from Bukkit for any reason (Uses PaperMC event) */
@EventHandler(priority = EventPriority.LOWEST)
fun EntityAddToWorldEvent.onBukkitEntityAdd() {
// Only remove player from ECS on disconnect, not death
if (entity is Player) return
val gearyEntity = gearyMobs.bukkit2Geary.getOrCreate(entity)
gearyEntity.add<AddedToWorld>()
gearyMobs.bukkit2Geary.getOrCreate(entity)
}

/** Remove entities from ECS when they are removed from Bukkit for any reason (Uses PaperMC event) */
@EventHandler(priority = EventPriority.HIGHEST)
fun EntityRemoveFromWorldEvent.onBukkitEntityRemove() {
// Only remove player from ECS on disconnect, not death
if (entity is Player) return
// We remove the geary entity one tick after the Bukkit one has been removed to ensure nothing
// else that tries to access the geary entity from Bukkit will create a new entity.
entity.toGearyOrNull()?.removeEntity()

// We remove the geary entity a bit later because paper has a bug where stored entities call load/unload/load
Bukkit.getScheduler().scheduleSyncDelayedTask(gearyPaper.plugin, {
if (entity.isValid) return@scheduleSyncDelayedTask // If the entity is still valid, it's the paper bug
entity.toGearyOrNull()?.removeEntity()
}, 10)
}

@EventHandler(priority = EventPriority.HIGHEST)
fun EntitiesUnloadEvent.onEntitiesUnload() {
entities.forEach {
val gearyEntity = it.toGearyOrNull() ?: return@forEach
gearyEntity.encodeComponentsTo(it)
}
}

@EventHandler(priority = EventPriority.LOWEST)
fun PlayerJoinEvent.onPlayerLogin() {
gearyMobs.bukkit2Geary.getOrCreate(player).set(player.world)
gearyMobs.bukkit2Geary.getOrCreate(player)
}

@EventHandler(priority = EventPriority.HIGHEST)
fun PlayerQuitEvent.onPlayerLogout() {
player.toGearyOrNull()?.removeEntity()
val gearyEntity = player.toGearyOrNull() ?: return
gearyEntity.encodeComponentsTo(player)
gearyEntity.removeEntity()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package com.mineinabyss.geary.papermc.tracking.entities.systems
import com.mineinabyss.geary.annotations.optin.UnsafeAccessors
import com.mineinabyss.geary.components.events.EntityRemoved
import com.mineinabyss.geary.datatypes.family.family
import com.mineinabyss.geary.papermc.datastore.encodeComponentsTo
import com.mineinabyss.geary.papermc.tracking.entities.gearyMobs
import com.mineinabyss.geary.systems.GearyListener
import com.mineinabyss.geary.systems.accessors.Pointers
Expand All @@ -16,6 +15,5 @@ class UntrackOnRemoveBukkitComponent : GearyListener() {
@OptIn(UnsafeAccessors::class)
override fun Pointers.handle() {
gearyMobs.bukkit2Geary.remove(bukkit.entityId)
target.entity.encodeComponentsTo(bukkit)
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.mineinabyss.geary.papermc.tracking.entities.systems
package com.mineinabyss.geary.papermc.tracking.entities.systems.attemptspawn

import com.mineinabyss.geary.annotations.optin.UnsafeAccessors
import com.mineinabyss.geary.datatypes.family.family
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.mineinabyss.geary.papermc.tracking.entities.systems
package com.mineinabyss.geary.papermc.tracking.entities.systems.attemptspawn

import com.mineinabyss.geary.annotations.optin.UnsafeAccessors
import com.mineinabyss.geary.datatypes.family.family
Expand All @@ -10,6 +10,7 @@ import com.mineinabyss.idofront.typealiases.BukkitEntity
import io.lumine.mythic.api.mobs.entities.SpawnReason
import io.lumine.mythic.bukkit.BukkitAdapter
import io.lumine.mythic.bukkit.MythicBukkit
import kotlin.jvm.optionals.getOrNull


class AttemptSpawnMythicMob : GearyListener() {
Expand All @@ -22,9 +23,10 @@ class AttemptSpawnMythicMob : GearyListener() {

@OptIn(UnsafeAccessors::class)
override fun Pointers.handle() {
val mythicMob = MythicBukkit.inst().mobManager.getMythicMob(mobType.id).orElse(null) ?: return
val mythicMob = MythicBukkit.inst().mobManager.getMythicMob(mobType.id).getOrNull() ?: return
mythicMob.spawn(BukkitAdapter.adapt(attemptSpawn.location), 1.0, SpawnReason.NATURAL) { mob ->
target.entity.set(mob)
target.entity.set(mythicMob)
}
}
}
Loading

0 comments on commit d8ba11b

Please sign in to comment.