diff options
| author | Freya Murphy <freya@freyacat.org> | 2026-03-26 23:15:33 -0400 |
|---|---|---|
| committer | Freya Murphy <freya@freyacat.org> | 2026-03-27 23:09:23 -0400 |
| commit | f8322cd21cde68a72b05efbad3a05b8e67c0bdd0 (patch) | |
| tree | d7e60bc8fedadc8fa7ae725571cad1f398eaf6dc /bukkit/src | |
| download | kenshinshideandseek2-f8322cd21cde68a72b05efbad3a05b8e67c0bdd0.tar.gz kenshinshideandseek2-f8322cd21cde68a72b05efbad3a05b8e67c0bdd0.tar.bz2 kenshinshideandseek2-f8322cd21cde68a72b05efbad3a05b8e67c0bdd0.zip | |
initial
Diffstat (limited to 'bukkit/src')
25 files changed, 1966 insertions, 0 deletions
diff --git a/bukkit/src/Board.kt b/bukkit/src/Board.kt new file mode 100644 index 0000000..041d39f --- /dev/null +++ b/bukkit/src/Board.kt @@ -0,0 +1,126 @@ +@file:Suppress("DEPRECATION") + +package cat.freya.khs.bukkit + +import cat.freya.khs.game.Board as KhsBoard +import java.util.UUID +import org.bukkit.scoreboard.DisplaySlot +import org.bukkit.scoreboard.NameTagVisibility +import org.bukkit.scoreboard.Objective +import org.bukkit.scoreboard.Scoreboard as BukkitBoard +import org.bukkit.scoreboard.Team as BukkitTeam + +class BukkitKhsTeam(val shim: BukkitKhsShim, val inner: BukkitTeam) : KhsBoard.Team { + override var prefix: String + get() = inner.prefix + set(prefix: String) { + inner.prefix = formatText(prefix) + } + + enum class NameTagsVisible { + FOR_OWN_TEAM, + FOR_OTHER_TEAMS, + NEVER, + } + + // options + override var canCollide: Boolean + get() { + if (!shim.supports(9)) return false + return inner.getOption(BukkitTeam.Option.COLLISION_RULE) == + BukkitTeam.OptionStatus.NEVER + } + set(b: Boolean) { + if (shim.supports(9)) { + val v = if (b) BukkitTeam.OptionStatus.ALWAYS else BukkitTeam.OptionStatus.NEVER + inner.setOption(BukkitTeam.Option.COLLISION_RULE, v) + } + } + + override var nameTagsVisible: Boolean + get() { + if (shim.supports(9)) { + return inner.getOption(BukkitTeam.Option.NAME_TAG_VISIBILITY) != + BukkitTeam.OptionStatus.NEVER + } else { + return inner.nameTagVisibility != NameTagVisibility.NEVER + } + } + set(b: Boolean) { + if (shim.supports(9)) { + val v = + if (b) BukkitTeam.OptionStatus.FOR_OWN_TEAM else BukkitTeam.OptionStatus.NEVER + inner.setOption(BukkitTeam.Option.NAME_TAG_VISIBILITY, v) + } else { + val v = if (b) NameTagVisibility.HIDE_FOR_OTHER_TEAMS else NameTagVisibility.NEVER + inner.nameTagVisibility = v + } + } + + // players + override var players: Set<UUID> + get() = inner.entries.map { shim.getPlayer(it)?.uuid }.filterNotNull().toSet() + set(new: Set<UUID>) { + for (entry in inner.entries) { + val player = shim.plugin.server.getPlayer(entry) ?: null + if (!new.contains(player?.uniqueId)) inner.removeEntry(entry) + } + for (uuid in new) { + val player = shim.plugin.server.getPlayer(uuid) ?: continue + inner.addEntry(player.name) + } + } +} + +class BukkitKhsBoard(val shim: BukkitKhsShim, val inner: BukkitBoard) : KhsBoard { + private var objective: Objective? = null + private var blanks: Int = 0 + + private fun resetObjective() { + if (shim.supports(13)) { + objective = inner.registerNewObjective("Scoreboard", "dummy", "") + } else { + objective = inner.registerNewObjective("Scoreboard", "dummy") + } + blanks = 0 + } + + private fun addLine(i: Int, line: String) { + val score = objective?.getScore(formatText(line)) + score?.setScore(i + 1) + } + + private fun addBlank(i: Int) { + blanks++ + addLine(i, " ".repeat(blanks)) + } + + override fun setText(title: String, text: List<String>) { + resetObjective() + + // set title + objective?.displayName = formatText(title) + + // set content + for ((i, line) in text.withIndex()) { + if (line.trim().isEmpty()) { + addBlank(i) + continue + } + + addLine(i, line) + } + } + + override fun getTeam(name: String): KhsBoard.Team { + runCatching { inner.registerNewTeam(name) } + val team = inner.getTeam(name) ?: error("failed to make team ?!?") + return BukkitKhsTeam(shim, team) + } + + override fun display(uuid: UUID) { + val player = shim.getPlayer(uuid) ?: return + objective?.setDisplaySlot(DisplaySlot.SIDEBAR) + (player as BukkitKhsPlayer).inner.setScoreboard(inner) + } +} diff --git a/bukkit/src/Inventory.kt b/bukkit/src/Inventory.kt new file mode 100644 index 0000000..2a1d7ea --- /dev/null +++ b/bukkit/src/Inventory.kt @@ -0,0 +1,59 @@ +package cat.freya.khs.bukkit + +import cat.freya.khs.player.Inventory as KhsInventory +import cat.freya.khs.player.PlayerInventory as KhsPlayerInventory +import cat.freya.khs.world.Item +import org.bukkit.inventory.Inventory as BukkitInventory +import org.bukkit.inventory.PlayerInventory as BukkitPlayerInventory + +open class BukkitKhsInventory( + open val shim: BukkitKhsShim, + open val inner: BukkitInventory, + open override val title: String?, +) : KhsInventory { + override fun get(index: UInt): Item? = inner.getItem(index.toInt())?.let { toKhsItem(it) } + + override fun set(index: UInt, item: Item) = + inner.setItem(index.toInt(), (item as BukkitKhsItem).inner) + + override fun remove(item: Item) = inner.remove((item as BukkitKhsItem).inner) + + override var contents: List<Item?> + get() = inner.contents.map { toKhsItem(it) } + set(contents: List<Item?>) = + inner.setContents(contents.map { (it as BukkitKhsItem).inner }.toTypedArray()) + + override fun clear() { + inner.clear() + } +} + +class BukkitKhsPlayerInventory( + override val shim: BukkitKhsShim, + override val inner: BukkitPlayerInventory, + override val title: String?, +) : BukkitKhsInventory(shim, inner, title), KhsPlayerInventory { + override var helmet: Item? + get() = toKhsItem(inner.helmet) + set(item: Item?) { + inner.helmet = (item as? BukkitKhsItem)?.inner + } + + override var chestplate: Item? + get() = toKhsItem(inner.chestplate) + set(item: Item?) { + inner.chestplate = (item as? BukkitKhsItem)?.inner + } + + override var leggings: Item? + get() = toKhsItem(inner.leggings) + set(item: Item?) { + inner.leggings = (item as? BukkitKhsItem)?.inner + } + + override var boots: Item? + get() = toKhsItem(inner.boots) + set(item: Item?) { + inner.boots = (item as? BukkitKhsItem)?.inner + } +} diff --git a/bukkit/src/Item.kt b/bukkit/src/Item.kt new file mode 100644 index 0000000..40646e2 --- /dev/null +++ b/bukkit/src/Item.kt @@ -0,0 +1,97 @@ +package cat.freya.khs.bukkit + +import cat.freya.khs.config.EffectConfig +import cat.freya.khs.config.ItemConfig +import cat.freya.khs.world.Effect as KhsEffect +import cat.freya.khs.world.Item as KhsItem +import com.cryptomorin.xseries.XItemStack +import com.cryptomorin.xseries.XMaterial +import kotlin.collections.emptyMap +import org.bukkit.configuration.file.YamlConfiguration +import org.bukkit.inventory.ItemStack +import org.bukkit.inventory.meta.SkullMeta +import org.bukkit.potion.PotionEffect + +class BukkitKhsItem(val inner: ItemStack, override val config: ItemConfig) : KhsItem { + override val name: String? = inner.itemMeta?.displayName + override val material: String = inner.type?.name ?: "NONE" + + override fun clone(): KhsItem = BukkitKhsItem(inner, config) + + override fun similar(config: ItemConfig): Boolean { + var item = parseBukkitItem(config) ?: return false + return inner.isSimilar(item.inner) + } + + override fun similar(material: String): Boolean { + val xMaterial = XMaterial.matchXMaterial(material).orElse(null) ?: return false + return xMaterial.isSimilar(inner) + } +} + +class BukkitKhsEffect(val inner: PotionEffect, override val config: EffectConfig) : KhsEffect { + @Suppress("DEPRECATION") override val name: String? = inner.type.name + + override fun clone(): KhsEffect = BukkitKhsEffect(inner, config) +} + +fun parseBukkitItem(itemConfig: ItemConfig): BukkitKhsItem? { + var config = YamlConfiguration().createSection("temp") + var materialParts = itemConfig.material.uppercase().split(":") + var material = materialParts.first() + + // set name and material + config.set("name", itemConfig.name?.let { formatText(it) }) + config.set("material", material) + if (!itemConfig.lore.isEmpty()) config.set("lore", itemConfig.lore.map { formatText(it) }) + config.set("unbreakable", itemConfig.unbreakable ?: false) + + // parse enchantments + var enchantments = YamlConfiguration().createSection("enchantments") + for ((enchantment, value) in itemConfig.enchantments) enchantments.set( + enchantment, + value.toInt(), + ) + config.set("enchants", enchantments) + + // set custom model data (1.14+) + if (itemConfig.modelData != null) config.set("model-data", itemConfig.modelData?.toInt()) + + // TODO: potions are broken on 1.8 + + // set potion data + if (material.endsWith("POTION")) { + var potionType = materialParts.getOrNull(1) ?: "AKWARD" + config.set("base-type", potionType) + } + + val item = runCatching { + val item = (XItemStack.Deserializer()).withConfig(config).read() ?: return null + + // set player head owner (if skull) + if (itemConfig.owner != null && itemConfig.material == "PLAYER_HEAD") { + val meta = item.itemMeta as SkullMeta + meta.setOwner(itemConfig.owner) + item.itemMeta = meta + } + + BukkitKhsItem(item, itemConfig) + } + + return item.getOrDefault(null) +} + +fun toKhsItem(inner: ItemStack?): BukkitKhsItem? { + if (inner == null) return null + + val config = ItemConfig() + config.name = inner.itemMeta?.displayName + config.material = inner.type?.name ?: "NONE" + config.lore = inner.itemMeta?.lore ?: listOf() + @Suppress("DEPRECATION") + config.enchantments = + inner.itemMeta?.enchants?.mapKeys { it.key.name }?.mapValues { it.value.toUInt() } + ?: emptyMap() + config.unbreakable = inner.itemMeta?.isUnbreakable + return BukkitKhsItem(inner, config) +} diff --git a/bukkit/src/Player.kt b/bukkit/src/Player.kt new file mode 100644 index 0000000..a551ee5 --- /dev/null +++ b/bukkit/src/Player.kt @@ -0,0 +1,232 @@ +package cat.freya.khs.bukkit + +import cat.freya.khs.bukkit.packet.EntityMetadataPacket +import cat.freya.khs.player.Inventory as KhsInventory +import cat.freya.khs.player.Player as KhsPlayer +import cat.freya.khs.player.Player.GameMode as KhsGameMode +import cat.freya.khs.player.PlayerInventory as KhsPlayerInventory +import cat.freya.khs.world.Effect +import cat.freya.khs.world.Location +import cat.freya.khs.world.Position +import cat.freya.khs.world.World as KhsWorld +import com.cryptomorin.xseries.XMaterial +import com.cryptomorin.xseries.XSound +import com.cryptomorin.xseries.messages.ActionBar +import com.cryptomorin.xseries.messages.Titles +import com.google.common.io.ByteStreams +import org.bukkit.Color +import org.bukkit.FireworkEffect +import org.bukkit.GameMode as BukkitGameMode +import org.bukkit.attribute.Attribute +import org.bukkit.entity.EntityType +import org.bukkit.entity.Firework +import org.bukkit.entity.Player as BukkitPlayer +import org.bukkit.potion.PotionEffect +import org.bukkit.potion.PotionEffectType +import org.bukkit.util.Vector + +class BukkitKhsPlayer(val shim: BukkitKhsShim, val inner: BukkitPlayer) : KhsPlayer { + override val uuid = inner.uniqueId + override val name = inner.name + + override val location: Location + get() { + val loc = inner.location + return Location(loc.x, loc.y, loc.z, inner.world.name.intern()) + } + + override val world: KhsWorld? + get() = shim.getWorld(location.worldName) + + override var health: Double + get() = inner.health + set(v: Double) { + inner.health = v + } + + override var hunger: UInt + get() = inner.foodLevel.toUInt() + set(v: UInt) { + inner.foodLevel = v.toInt() + } + + override fun heal() { + if (shim.supports(9)) { + val attribName = if (shim.supports(21, 6)) "MAX_HEALTH" else "GENERIC_MAX_HEALTH" + var attrib = inner.getAttribute(Attribute.valueOf(attribName)) + health = attrib?.value ?: 20.0 + } else { + @Suppress("DEPRECATION") + health = inner.maxHealth + } + } + + override var allowFlight + get() = inner.allowFlight + set(v: Boolean) { + inner.allowFlight = v + } + + override var flying + get() = inner.isFlying + set(flying: Boolean) { + if (this.flying != flying) inner.setFallDistance(0f) + runCatching { inner.setFlying(flying) } + } + + override fun teleport(position: Position) { + val loc = Location(position.x, position.y, position.z, inner.world.name) + teleport(loc) + } + + override fun teleport(location: Location) { + var world = shim.plugin.server.getWorld(location.worldName) + if (world == null) { + // attempt to load the world + val loader = shim.getWorldLoader(location.worldName) + loader.load() + world = shim.plugin.server.getWorld(location.worldName) + } + val x = location.x + val y = location.y + val z = location.z + val pos = org.bukkit.Location(world, x, y, z) + + // sanity check + if (world == null) { + shim.logger.warning("Could not teleport $name to $x,$y,$z in ${location.worldName}") + return + } + + inner.teleport(pos) + } + + override fun sendToServer(server: String) { + val out = ByteStreams.newDataOutput() + out.writeUTF("Connect") + out.writeUTF(shim.plugin.khs.config.leaveServer) + inner.sendPluginMessage(shim.plugin, "BungeeCord", out.toByteArray()) + } + + override val inventory: KhsPlayerInventory + get() = BukkitKhsPlayerInventory(shim, inner.inventory, null) + + override fun showInventory(inv: KhsInventory) { + inner.openInventory((inv as BukkitKhsInventory).inner) + } + + override fun closeInventory() { + inner.closeInventory() + } + + override fun clearEffects() { + for (effect in inner.activePotionEffects) inner.removePotionEffect(effect.type) + } + + override fun giveEffect(effect: Effect) { + inner.addPotionEffect((effect as BukkitKhsEffect).inner) + } + + override fun setSpeed(amplifier: UInt) { + inner.addPotionEffect(PotionEffect(PotionEffectType.SPEED, 1000000, 5, false, false)) + } + + override fun setGlow(target: KhsPlayer, glow: Boolean) { + val entity = (target as BukkitKhsPlayer).inner + val packet = EntityMetadataPacket(entity, glow) + packet.send(inner) + } + + override fun setHidden(target: KhsPlayer, hidden: Boolean) { + var other = (target as BukkitKhsPlayer).inner + if (shim.supports(12, 2)) { + if (hidden) inner.hidePlayer(shim.plugin, other) + else inner.showPlayer(shim.plugin, other) + } else { + @Suppress("DEPRECATION") + if (hidden) inner.hidePlayer(other) else inner.showPlayer(other) + } + } + + override fun message(message: String) { + inner.sendMessage(formatText(message)) + } + + override fun actionBar(message: String) { + ActionBar.clearActionBar(inner) + ActionBar.sendActionBar(inner, formatText(message)) + } + + override fun title(title: String, subTitle: String) { + Titles.clearTitle(inner) + Titles.sendTitle(inner, 10, 40, 10, formatText(title), formatText(subTitle)) + } + + override fun playSound(sound: String, volume: Double, pitch: Double) { + XSound.REGISTRY.getByName(sound).ifPresent { + it.play(inner, volume.toFloat(), pitch.toFloat()) + } + } + + override fun isDisguised(): Boolean = shim.plugin.disguiser.getDisguise(inner) != null + + override fun disguise(material: String) { + runCatching { + val xmat = XMaterial.matchXMaterial(material).orElse(null) ?: return + val mat = xmat.get() ?: return + shim.plugin.disguiser.disguise(inner, mat) + } + } + + override fun revealDisguise() { + shim.plugin.disguiser.reveal(inner) + } + + override fun hasPermission(permission: String): Boolean { + return inner.hasPermission(permission) + } + + override fun setGameMode(gameMode: KhsGameMode) { + inner.setGameMode( + when (gameMode) { + KhsGameMode.CREATIVE -> BukkitGameMode.CREATIVE + KhsGameMode.SURVIVAL -> BukkitGameMode.SURVIVAL + KhsGameMode.ADVENTURE -> BukkitGameMode.ADVENTURE + KhsGameMode.SPECTATOR -> BukkitGameMode.SPECTATOR + } + ) + } + + override fun hideBoards() { + val manager = shim.plugin.server.scoreboardManager ?: return + inner.setScoreboard(manager.mainScoreboard) + } + + override fun taunt() { + val world = this.world?.let { it as BukkitKhsWorld } ?: return + val loc = org.bukkit.Location(world.inner, location.x, location.y, location.z) + + // spawn firework + val fwMatName = if (shim.supports(13)) "FIREWORK_ROCKET" else "FIREWORK" + val fwMat = EntityType.valueOf(fwMatName) + val fw = world.inner.spawnEntity(loc, fwMat) as Firework + fw.setVelocity(Vector(0, 1, 0)) + + // make it pretty + val meta = fw.fireworkMeta + meta.setPower(4) + meta.addEffect( + FireworkEffect.builder() + .withColor(Color.BLUE) + .withColor(Color.RED) + .withColor(Color.YELLOW) + .with(FireworkEffect.Type.STAR) + .with(FireworkEffect.Type.BALL) + .with(FireworkEffect.Type.BALL_LARGE) + .flicker(true) + .withTrail() + .build() + ) + fw.fireworkMeta = meta + } +} diff --git a/bukkit/src/Plugin.kt b/bukkit/src/Plugin.kt new file mode 100644 index 0000000..325d4cd --- /dev/null +++ b/bukkit/src/Plugin.kt @@ -0,0 +1,87 @@ +package cat.freya.khs.bukkit + +import cat.freya.khs.Khs +import cat.freya.khs.bukkit.disguise.Disguiser +import cat.freya.khs.bukkit.disguise.EntityHider +import cat.freya.khs.bukkit.event.* +import org.bukkit.command.Command +import org.bukkit.command.CommandSender +import org.bukkit.entity.Player as BukkitPlayer +import org.bukkit.plugin.java.JavaPlugin +import org.bukkit.scheduler.BukkitRunnable + +class KhsPlugin : JavaPlugin() { + val shim: BukkitKhsShim = BukkitKhsShim(this) + val khs: Khs = Khs(shim) + + // for blockhunt + val disguiser: Disguiser = Disguiser(this) + val entityHider: EntityHider = EntityHider() + + override fun onEnable() { + khs.init() + + if (!this.isEnabled()) return + + // make sure onTick is run + object : BukkitRunnable() { + override fun run() { + khs.onTick() + disguiser.update() + } + } + .runTaskTimer(this, 0, 1) + + // register bungee cord + server.messenger.registerOutgoingPluginChannel(this, "BungeeCord") + + registerListeners() + } + + override fun onDisable() { + khs.cleanup() + disguiser.cleanup() + } + + private fun registerListeners() { + BreakListener(this) + ChatListener(this) + CommandListener(this) + DamageListener(this) + InteractListener(this) + InventoryListener(this) + JoinLeaveListener(this) + MovementListener(this) + PlayerListener(this) + PacketListener(this) + RespawnListener(this) + } + + fun scheduleTask(fn: () -> Unit) { + if (!isEnabled) return + server.scheduler.runTask(this, fn) + } + + override fun onCommand( + sender: CommandSender, + cmd: Command, + label: String, + args: Array<String>, + ): Boolean { + val player = sender as? BukkitPlayer ?: return false + val khsPlayer = BukkitKhsPlayer(shim, player) + khs.commandGroup.handleCommand(khsPlayer, args.toList()) + return true + } + + override fun onTabComplete( + sender: CommandSender, + cmd: Command, + label: String, + args: Array<String>, + ): List<String> { + val player = sender as? BukkitPlayer ?: return listOf() + val khsPlayer = BukkitKhsPlayer(shim, player) + return khs.commandGroup.handleTabComplete(khsPlayer, args.toList()) + } +} diff --git a/bukkit/src/Shim.kt b/bukkit/src/Shim.kt new file mode 100644 index 0000000..fa568f9 --- /dev/null +++ b/bukkit/src/Shim.kt @@ -0,0 +1,180 @@ +package cat.freya.khs.bukkit + +import cat.freya.khs.KhsShim +import cat.freya.khs.Logger +import cat.freya.khs.config.EffectConfig +import cat.freya.khs.config.ItemConfig +import cat.freya.khs.game.Board as KhsBoard +import cat.freya.khs.player.Inventory as KhsInventory +import cat.freya.khs.player.Player as KhsPlayer +import cat.freya.khs.world.Effect as KhsEffect +import cat.freya.khs.world.Item as KhsItem +import cat.freya.khs.world.World as KhsWorld +import com.cryptomorin.xseries.XMaterial +import java.io.File +import java.io.InputStream +import java.util.UUID +import kotlin.jvm.optionals.getOrNull +import org.bukkit.ChatColor +import org.bukkit.World as BukkitWorld +import org.bukkit.WorldCreator +import org.bukkit.WorldType +import org.bukkit.potion.PotionEffect +import org.bukkit.potion.PotionEffectType + +class BukkitLogger(val plugin: KhsPlugin) : Logger { + override fun info(message: String) = plugin.logger.info(message) + + override fun warning(message: String) = plugin.logger.warning(message) + + override fun error(message: String) = plugin.logger.severe(message) +} + +class BukkitKhsShim(val plugin: KhsPlugin) : KhsShim { + override val pluginVersion: String + override val mcVersion: List<UInt> + override val platform: String = "bukkit" + + init { + // parse mc version + mcVersion = + Regex("""MC:\s*([\d.]+)""") + .find(plugin.server.version) + ?.groupValues + ?.get(1) + ?.split('.') + ?.asSequence() + ?.mapNotNull { it.toUIntOrNull() } + ?.let { seq -> if (seq.firstOrNull() == 1u) seq.drop(1) else seq } + ?.toList() ?: emptyList() + + pluginVersion = plugin.description.version + } + + override val logger = BukkitLogger(plugin) + + override val players: List<KhsPlayer> + get() = plugin.server.onlinePlayers.map { BukkitKhsPlayer(this, it) } + + override val worlds: List<String> + get() = + plugin.server.worldContainer + .listFiles() + .filter { + if (!it.isDirectory) return@filter false + + val session = File(it, "session.lock") + val level = File(it, "level.dat") + + session.exists() && level.exists() + } + .map { it.name } + + override val sqliteDatabasePath: String + get() { + val legacy = File(plugin.dataFolder.path, "database.db") + if (legacy.exists()) return legacy.path + + return File(plugin.dataFolder.path, "khs.db").path + } + + override fun readConfigFile(fileName: String): InputStream? { + val dir = plugin.dataFolder + if (!dir.exists()) { + dir.mkdirs() || error("Failed to make plugin config directory") + } + val file = File(dir, fileName) + return if (file.exists()) file.inputStream() else null + } + + override fun writeConfigFile(fileName: String, content: String) { + val dir = plugin.dataFolder + if (!dir.exists()) { + dir.mkdirs() || error("Failed to make plugin config directory") + } + val file = File(dir, fileName) + file.writeText(content) + } + + override fun parseMaterial(materialName: String): String? { + return XMaterial.matchXMaterial(materialName).getOrNull()?.get()?.toString() + } + + override fun parseItem(itemConfig: ItemConfig): KhsItem? { + return parseBukkitItem(itemConfig) + } + + override fun parseEffect(effectConfig: EffectConfig): KhsEffect? { + @Suppress("DEPRECATION") + val type = PotionEffectType.getByName(effectConfig.type.uppercase()) ?: return null + val inner = + PotionEffect( + type, + effectConfig.duration.toInt(), + effectConfig.amplifier.toInt(), + effectConfig.ambient, + effectConfig.particles, + ) + + return BukkitKhsEffect(inner, effectConfig) + } + + override fun getPlayer(uuid: UUID): KhsPlayer? { + return plugin.server.getPlayer(uuid)?.let { BukkitKhsPlayer(this, it) } + } + + override fun getPlayer(name: String): KhsPlayer? { + return plugin.server.getPlayer(name)?.let { BukkitKhsPlayer(this, it) } + } + + override fun getWorld(worldName: String): KhsWorld? { + return plugin.server.getWorld(worldName)?.let { BukkitKhsWorld(this, it) } + } + + override fun getWorldLoader(worldName: String): KhsWorld.Loader { + return BukkitKhsWorldLoader(plugin, worldName) + } + + override fun createWorld(worldName: String, type: KhsWorld.Type): KhsWorld? { + val worldType = if (type == KhsWorld.Type.FLAT) WorldType.FLAT else WorldType.NORMAL + val env = + when (type) { + KhsWorld.Type.NETHER -> BukkitWorld.Environment.NETHER + KhsWorld.Type.END -> BukkitWorld.Environment.THE_END + else -> BukkitWorld.Environment.NORMAL + } + val creator = WorldCreator(worldName) + creator.type(worldType) + creator.environment(env) + plugin.server.createWorld(creator) + var world = plugin.server.getWorld(worldName) ?: return null + world.save() + return BukkitKhsWorld(plugin.shim, world) + } + + override fun createInventory(title: String, size: UInt): KhsInventory? { + val inv = plugin.server.createInventory(null, size.toInt(), title) + return BukkitKhsInventory(this, inv, title) + } + + override fun getBoard(name: String): KhsBoard? { + val board = plugin.server.scoreboardManager?.getNewScoreboard() ?: return null + return BukkitKhsBoard(this, board) + } + + override fun broadcast(message: String) { + plugin.server.broadcastMessage(formatText(message)) + } + + override fun disable() { + plugin.server.pluginManager.disablePlugin(plugin) + } + + override fun scheduleEvent(ticks: ULong, event: () -> Unit) { + plugin.server.scheduler.scheduleSyncDelayedTask(plugin, event, ticks.toLong()) + } +} + +fun formatText(message: String): String { + return ChatColor.translateAlternateColorCodes('&', message) +} diff --git a/bukkit/src/World.kt b/bukkit/src/World.kt new file mode 100644 index 0000000..afda77a --- /dev/null +++ b/bukkit/src/World.kt @@ -0,0 +1,157 @@ +package cat.freya.khs.bukkit + +import cat.freya.khs.world.Position +import cat.freya.khs.world.World as KhsWorld +import cat.freya.khs.world.World.Border as KhsWorldBorder +import cat.freya.khs.world.World.Loader as KhsWorldLoader +import java.io.File +import java.util.Random +import org.bukkit.GameRule +import org.bukkit.Location as BukkitLocation +import org.bukkit.World as BukkitWorld +import org.bukkit.WorldBorder as BukkitWorldBorder +import org.bukkit.WorldCreator +import org.bukkit.WorldType +import org.bukkit.block.Biome +import org.bukkit.generator.BlockPopulator +import org.bukkit.generator.ChunkGenerator + +class VoidGenerator : ChunkGenerator() { + // 1.14 And On + override fun getDefaultPopulators(world: BukkitWorld): List<BlockPopulator> { + return listOf() + } + + override fun canSpawn(world: BukkitWorld, x: Int, z: Int): Boolean { + return true + } + + override fun getFixedSpawnLocation(world: BukkitWorld, random: Random): BukkitLocation { + return BukkitLocation(world, 0.0, 0.0, 0.0) + } + + // 1.13 And Prev + @Suppress("DEPRECATION", "OVERRIDE_DEPRECATION") + override fun generateChunkData( + world: BukkitWorld, + random: Random, + chunkX: Int, + chunkZ: Int, + biome: BiomeGrid, + ): ChunkData { + val chunkData = createChunkData(world) + + for (x in 0 until 16) for (z in 0 until 16) biome.setBiome(x, z, Biome.PLAINS) + + return chunkData + } + + // 1.8 + fun generate(world: BukkitWorld, random: Random, x: Int, z: Int): ByteArray { + return ByteArray(world.maxHeight / 16) + } + + @Suppress("DEPRECATION") + fun generateBlockSections( + world: BukkitWorld, + random: Random, + x: Int, + z: Int, + biomes: ChunkGenerator.BiomeGrid, + ): Array<ByteArray> { + return Array(world.maxHeight / 16) { ByteArray(0) } + } +} + +class BukkitKhsWorldBorder(val world: BukkitKhsWorld, val inner: BukkitWorldBorder) : + KhsWorldBorder { + override val x: Double = inner.center.x + override val z: Double = inner.center.z + override val size: Double = inner.size + + override fun move(newX: Double, newZ: Double, newSize: ULong, delay: ULong) { + inner.setCenter(newX, newZ) + move(newSize, delay) + } + + override fun move(newSize: ULong, delay: ULong) { + inner.setSize(newSize.toDouble(), delay.toLong()) + } +} + +class BukkitKhsWorldLoader(val plugin: KhsPlugin, val worldName: String) : KhsWorldLoader { + override val name: String = worldName + override val world: KhsWorld? = plugin.shim.getWorld(worldName) + + override val dir: File + get() = File(plugin.server.worldContainer, name) + + override val saveDir: File + get() = File(plugin.server.worldContainer, "hs_$name") + + override val tempSaveDir: File + get() = File(plugin.server.worldContainer, "temp_hs_$name") + + override fun load() { + plugin.server.createWorld(WorldCreator(name).generator(VoidGenerator())) + val world = plugin.server.getWorld(name) + if (world == null) { + plugin.shim.logger.error("could not load world: $name") + } + world?.setAutoSave(false) + if (plugin.shim.supports(21, 6)) world?.setGameRule(GameRule.LOCATOR_BAR, false) + } + + override fun unload() { + val world = plugin.server.getWorld(name) + if (world == null) return // world already unloaded + + world.players.forEach { player -> + val khsPlayer = BukkitKhsPlayer(plugin.shim, player) + plugin.khs.config.exit?.teleport(khsPlayer) + } + + if (plugin.server.unloadWorld(name, false) == false) + plugin.shim.logger.error("could not unload world: $name") + } + + override fun rollback() { + unload() + load() + } +} + +class BukkitKhsWorld(val shim: BukkitKhsShim, val inner: BukkitWorld) : KhsWorld { + override val name = inner.name + override val type: KhsWorld.Type + get() { + val env = inner.environment + if (env == BukkitWorld.Environment.NETHER) return KhsWorld.Type.NETHER + if (env == BukkitWorld.Environment.THE_END) return KhsWorld.Type.END + + @Suppress("DEPRECATION") + return when (inner.worldType) { + WorldType.NORMAL -> KhsWorld.Type.NORMAL + WorldType.FLAT -> KhsWorld.Type.FLAT + else -> KhsWorld.Type.UNKNOWN + } + } + + override val minY: Int + get() = if (shim.supports(18)) -64 else 0 + + override val maxY: Int + get() = if (shim.supports(18)) 320 else 256 + + override val spawn: Position + get() { + val loc = inner.spawnLocation + return Position(loc.x, loc.y, loc.z) + } + + override val border: KhsWorldBorder + get() = BukkitKhsWorldBorder(this, inner.worldBorder) + + override val loader: KhsWorldLoader + get() = shim.getWorldLoader(name) +} diff --git a/bukkit/src/disguise/Disguise.kt b/bukkit/src/disguise/Disguise.kt new file mode 100644 index 0000000..8c86188 --- /dev/null +++ b/bukkit/src/disguise/Disguise.kt @@ -0,0 +1,244 @@ +package cat.freya.khs.bukkit.disguise + +import cat.freya.khs.bukkit.KhsPlugin +import cat.freya.khs.bukkit.packet.BlockChangePacket +import cat.freya.khs.bukkit.packet.EntityTeleportPacket +import com.cryptomorin.xseries.XSound +import com.cryptomorin.xseries.messages.ActionBar +import kotlin.math.round +import org.bukkit.Location +import org.bukkit.Material +import org.bukkit.entity.AbstractHorse +import org.bukkit.entity.Entity as BukkitEntity +import org.bukkit.entity.EntityType +import org.bukkit.entity.FallingBlock +import org.bukkit.entity.LivingEntity +import org.bukkit.entity.Player as BukkitPlayer +import org.bukkit.potion.PotionEffect +import org.bukkit.potion.PotionEffectType +import org.bukkit.scoreboard.Team + +private fun makeInvisible(entity: LivingEntity) { + if (entity.hasPotionEffect(PotionEffectType.INVISIBILITY)) return + + entity.addPotionEffect(PotionEffect(PotionEffectType.INVISIBILITY, 1_000_000, 0, false, false)) +} + +private fun getCollidesTeam(plugin: KhsPlugin): Team? { + val KHS_DISGUISE_TEAM_NAME = "KHS_disguised" + val scoreboard = plugin.server.scoreboardManager?.mainScoreboard ?: return null + val team = + scoreboard.getTeam(KHS_DISGUISE_TEAM_NAME) + ?: scoreboard.registerNewTeam(KHS_DISGUISE_TEAM_NAME) + team.setOption(Team.Option.COLLISION_RULE, Team.OptionStatus.NEVER) + team.setCanSeeFriendlyInvisibles(false) + return team +} + +private fun setCollides(plugin: KhsPlugin, player: BukkitPlayer, collides: Boolean) { + if (plugin.shim.supports(9)) { + val team = getCollidesTeam(plugin) + if (collides) team?.removeEntry(player.name) else team?.addEntry(player.name) + } else { + val method = player.spigot().javaClass.getMethod("setCollidesWithEntities") + method.invoke(player, collides) + } +} + +class Disguise(val plugin: KhsPlugin, val player: BukkitPlayer, val material: Material) { + var block: FallingBlock? = null + var blockLocation: Location? = null + var hitBox: AbstractHorse? = null + var timer: UInt = 0u + + @Volatile var shouldBeSolid: Boolean = false + @Volatile var isSolid: Boolean = false + @Volatile var hasSolidifyingTask: Boolean = false + + init { + // make sure the player does not collide + setCollides(plugin, player, false) + } + + val entityId: Int? + get() = block?.entityId + + val hitBoxId: Int? + get() = hitBox?.entityId + + fun update() { + if (block?.isDead() != false) { + block?.remove() + respawnFallingBlock() + } + + if (shouldBeSolid) { + if (!isSolid) { + isSolid = true + blockLocation = player.location.block.location + respawnHitbox() + } + sendBlockUpdate(blockLocation, material) + } else if (isSolid) { + isSolid = false + removeHitbox() + removeFallingBlock() + respawnFallingBlock() + sendBlockUpdate(blockLocation, Material.AIR) + } + + updateVisiblity() + teleportEntity(hitBox, true) + teleportEntity(block, isSolid) + + // do this here is it can be + // cleared by the core game logic + makeInvisible(player) + } + + fun remove() { + block?.remove() + removeHitbox() + setCollides(plugin, player, true) + player.removePotionEffect(PotionEffectType.INVISIBILITY) + if (isSolid) sendBlockUpdate(blockLocation, Material.AIR) + } + + fun removeFallingBlock() { + block?.remove() + block = null + } + + fun respawnFallingBlock() { + val world = player.location.world ?: return + val loc = player.location.add(0.0, 1000.0, 0.0) + + val block: FallingBlock? = + runCatching { world.spawnFallingBlock(loc, material.createBlockData()) } + .getOrElse { null } + if (block == null) return + + if (plugin.shim.supports(10)) block.setGravity(false) + + block.setDropItem(false) + block.setInvulnerable(true) + + this.block = block + } + + fun respawnHitbox() { + val world = player.location.world ?: return + + // we only want the hitbox to be at our postion + // when we are solidified + val loc = player.location.add(0.0, 1000.0, 0.0) + + val hitBox: AbstractHorse? = + if (plugin.shim.supports(11)) { + world.spawnEntity(loc, EntityType.SKELETON_HORSE) as AbstractHorse + } else { + world.spawnEntity(loc, EntityType.HORSE) as AbstractHorse + } + + if (hitBox == null) return + + if (plugin.shim.supports(10)) hitBox.setGravity(false) + + val id = hitBox.uniqueId.toString() + if (plugin.shim.supports(9)) getCollidesTeam(plugin)?.addEntry(id) + + hitBox.setAI(false) + hitBox.setInvulnerable(true) + hitBox.setCanPickupItems(false) + hitBox.setCollidable(false) + makeInvisible(hitBox) + + this.hitBox = hitBox + } + + fun removeHitbox() { + val hb = hitBox ?: return + val id = hb.uniqueId.toString() + + hb.remove() + if (plugin.shim.supports(9)) getCollidesTeam(plugin)?.removeEntry(id) + + hitBox == null + } + + fun teleportEntity(entity: BukkitEntity?, center: Boolean) { + if (entity == null) return + + val loc = player.location.clone() + if (center) { + loc.x = round(loc.x + 0.5) - 0.5 + loc.y = round(loc.y) + loc.z = round(loc.z + 0.5) - 0.5 + } + + val packet = EntityTeleportPacket(entity, loc) + plugin.server.onlinePlayers.forEach { packet.send(it) } + } + + fun sendBlockUpdate(location: Location?, material: Material) { + if (location == null) return + + val packet = BlockChangePacket(location, material) + plugin.server.onlinePlayers.forEach { + if (it.uniqueId == player.uniqueId) return@forEach + packet.send(it) + } + } + + fun updateVisiblity() { + val block = block ?: return + val show = !isSolid + plugin.server.onlinePlayers.forEach { target -> + if (target.uniqueId == player.uniqueId) return@forEach + + if (show) { + plugin.entityHider.showEntity(target, block) + } else { + plugin.entityHider.hideEntity(target, block) + } + } + } + + fun startSolidifying() { + if (isSolid || hasSolidifyingTask) return + hasSolidifyingTask = true + plugin.server.scheduler.scheduleSyncDelayedTask( + plugin, + { solidifyUpdate(player.location.clone(), 3u) }, + 10, + ) + } + + fun solidifyUpdate(lastLocation: Location, time: UInt) { + val location = player.location + + if ((lastLocation.world != location.world) || (lastLocation.distance(location) > 0.1)) { + hasSolidifyingTask = false + return + } + + // we have solidified! + if (time == 0u) { + ActionBar.clearActionBar(player) + shouldBeSolid = true + hasSolidifyingTask = false + return + } + + // still waiting + ActionBar.sendActionBar(player, "▪".repeat(time.toInt())) + XSound.BLOCK_NOTE_BLOCK_PLING.play(player, 1f, 1f) + + // schedule next update + plugin.server.scheduler.scheduleSyncDelayedTask( + plugin, + { solidifyUpdate(lastLocation, time - 1u) }, + 20, + ) + } +} diff --git a/bukkit/src/disguise/Disguiser.kt b/bukkit/src/disguise/Disguiser.kt new file mode 100644 index 0000000..771c8f6 --- /dev/null +++ b/bukkit/src/disguise/Disguiser.kt @@ -0,0 +1,43 @@ +package cat.freya.khs.bukkit.disguise + +import cat.freya.khs.bukkit.KhsPlugin +import java.util.concurrent.ConcurrentHashMap +import org.bukkit.Material +import org.bukkit.entity.Player as BukkitPlayer + +class Disguiser(val plugin: KhsPlugin) { + val disguises = ConcurrentHashMap<BukkitPlayer, Disguise>() + + fun cleanup() { + disguises.forEach { it.value.remove() } + disguises.clear() + } + + fun getDisguise(player: BukkitPlayer): Disguise? = disguises.get(player) + + fun getByEntityId(id: Int): Disguise? = disguises.values.firstOrNull { it.entityId == id } + + fun getByHitboxId(id: Int): Disguise? = disguises.values.firstOrNull { it.hitBoxId == id } + + fun update() { + for ((player, disguise) in disguises) { + if (!player.isOnline) { + disguise.remove() + disguises.remove(player) + } else { + disguise.update() + } + } + } + + fun disguise(player: BukkitPlayer, material: Material) { + // remove old disguise (if exists) + reveal(player) + // make new one + disguises.put(player, Disguise(plugin, player, material)) + } + + fun reveal(player: BukkitPlayer) { + disguises.remove(player)?.remove() + } +} diff --git a/bukkit/src/disguise/EntityHider.kt b/bukkit/src/disguise/EntityHider.kt new file mode 100644 index 0000000..c3ee9c3 --- /dev/null +++ b/bukkit/src/disguise/EntityHider.kt @@ -0,0 +1,58 @@ +package cat.freya.khs.bukkit.disguise + +import cat.freya.khs.bukkit.packet.EntityDestroyPacket +import cat.freya.khs.bukkit.packet.EntityMetadataPacket +import com.google.common.collect.HashBasedTable +import com.google.common.collect.Table +import org.bukkit.entity.Entity as BukkitEntity +import org.bukkit.entity.Player as BukkitPlayer + +class EntityHider { + val map: Table<Int, Int, Boolean> = HashBasedTable.create() + + // is entity visible for the observer + fun isVisible(observer: BukkitPlayer, entityId: Int): Boolean = + runCatching { + val observerId = observer.entityId + !map.contains(observerId, entityId) + } + .getOrElse { true } + + // is entity visible for the observer + fun isVisible(observer: BukkitPlayer, entity: BukkitEntity): Boolean = + runCatching { isVisible(observer, entity.entityId) }.getOrElse { true } + + // set if the entity is hidden for the observer + fun setVisible(observer: BukkitPlayer, entity: BukkitEntity, visible: Boolean) = runCatching { + val observerId = observer.entityId + val entityId = entity.entityId + val ret = + if (visible) map.put(entityId, observerId, true) else map.remove(entityId, observerId) + ret ?: visible + } + + // removes a hidden entity from the map + fun removeEntity(entity: BukkitEntity) = + runCatching { entity.entityId } + .onSuccess { for (row in map.rowMap().values) row.remove(it) } + + // removes a player from the map + fun removePlayer(player: BukkitPlayer) = + runCatching { player.entityId }.onSuccess { map.rowMap().remove(it) } + + // hides and entity + fun hideEntity(observer: BukkitPlayer, entity: BukkitEntity) { + setVisible(observer, entity, false) + + val packet = EntityDestroyPacket(entity) + packet.send(observer) + } + + // unhides the entity + fun showEntity(observer: BukkitPlayer, entity: BukkitEntity) { + setVisible(observer, entity, true) + + val packet = EntityMetadataPacket(entity, false) + packet.send(observer) + } +} diff --git a/bukkit/src/event/BreakListener.kt b/bukkit/src/event/BreakListener.kt new file mode 100644 index 0000000..832aa72 --- /dev/null +++ b/bukkit/src/event/BreakListener.kt @@ -0,0 +1,56 @@ +package cat.freya.khs.bukkit.event + +import cat.freya.khs.bukkit.BukkitKhsPlayer +import cat.freya.khs.bukkit.KhsPlugin +import cat.freya.khs.event.BreakEvent +import cat.freya.khs.event.onBreak +import org.bukkit.entity.Player as BukkitPlayer +import org.bukkit.event.EventHandler +import org.bukkit.event.EventPriority +import org.bukkit.event.Listener +import org.bukkit.event.block.BlockBreakEvent +import org.bukkit.event.entity.EntityBreakDoorEvent +import org.bukkit.event.hanging.HangingBreakByEntityEvent + +class BreakListener(val plugin: KhsPlugin) : Listener { + + init { + plugin.server.pluginManager.registerEvents(this, plugin) + } + + @EventHandler(priority = EventPriority.HIGHEST) + fun onBlockBreak(event: BlockBreakEvent) { + val bukkitPlayer = event.player ?: return + val block = event.block?.type?.name ?: return + + val khsPlayer = BukkitKhsPlayer(plugin.shim, bukkitPlayer) + val khsEvent = BreakEvent(plugin.khs, khsPlayer, block) + onBreak(khsEvent) + + if (khsEvent.cancelled) event.setCancelled(true) + } + + @EventHandler(priority = EventPriority.HIGHEST) + fun onEntityBreakDoor(event: EntityBreakDoorEvent) { + val bukkitPlayer = event.entity as? BukkitPlayer ?: return + val block = event.block?.type?.name ?: return + + val khsPlayer = BukkitKhsPlayer(plugin.shim, bukkitPlayer) + val khsEvent = BreakEvent(plugin.khs, khsPlayer, block) + onBreak(khsEvent) + + if (khsEvent.cancelled) event.setCancelled(true) + } + + @EventHandler(priority = EventPriority.HIGHEST) + fun onHangingBreakByEntity(event: HangingBreakByEntityEvent) { + val bukkitPlayer = event.remover as? BukkitPlayer ?: return + val block = event.entity?.type?.name ?: return + + val khsPlayer = BukkitKhsPlayer(plugin.shim, bukkitPlayer) + val khsEvent = BreakEvent(plugin.khs, khsPlayer, block) + onBreak(khsEvent) + + if (khsEvent.cancelled) event.setCancelled(true) + } +} diff --git a/bukkit/src/event/ChatListener.kt b/bukkit/src/event/ChatListener.kt new file mode 100644 index 0000000..530fa25 --- /dev/null +++ b/bukkit/src/event/ChatListener.kt @@ -0,0 +1,29 @@ +package cat.freya.khs.bukkit.event + +import cat.freya.khs.bukkit.BukkitKhsPlayer +import cat.freya.khs.bukkit.KhsPlugin +import cat.freya.khs.event.ChatEvent +import cat.freya.khs.event.onChat +import org.bukkit.event.EventHandler +import org.bukkit.event.EventPriority +import org.bukkit.event.Listener +import org.bukkit.event.player.AsyncPlayerChatEvent + +class ChatListener(val plugin: KhsPlugin) : Listener { + + init { + plugin.server.pluginManager.registerEvents(this, plugin) + } + + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) + fun onChat(event: AsyncPlayerChatEvent) { + val bukkitPlayer = event.player ?: return + val message = event.message ?: return + + val khsPlayer = BukkitKhsPlayer(plugin.shim, bukkitPlayer) + val khsEvent = ChatEvent(plugin.khs, khsPlayer, message) + onChat(khsEvent) + + if (khsEvent.cancelled) event.setCancelled(true) + } +} diff --git a/bukkit/src/event/CommandListener.kt b/bukkit/src/event/CommandListener.kt new file mode 100644 index 0000000..7ed25ca --- /dev/null +++ b/bukkit/src/event/CommandListener.kt @@ -0,0 +1,29 @@ +package cat.freya.khs.bukkit.event + +import cat.freya.khs.bukkit.BukkitKhsPlayer +import cat.freya.khs.bukkit.KhsPlugin +import cat.freya.khs.event.CommandEvent +import cat.freya.khs.event.onCommand +import org.bukkit.event.EventHandler +import org.bukkit.event.EventPriority +import org.bukkit.event.Listener +import org.bukkit.event.player.PlayerCommandPreprocessEvent + +class CommandListener(val plugin: KhsPlugin) : Listener { + + init { + plugin.server.pluginManager.registerEvents(this, plugin) + } + + @EventHandler(priority = EventPriority.HIGHEST) + fun onPlayerCommand(event: PlayerCommandPreprocessEvent) { + val bukkitPlayer = event.player ?: return + val message = event.message ?: return + + val khsPlayer = BukkitKhsPlayer(plugin.shim, bukkitPlayer) + val khsEvent = CommandEvent(plugin.khs, khsPlayer, message) + onCommand(khsEvent) + + if (khsEvent.cancelled) event.setCancelled(true) + } +} diff --git a/bukkit/src/event/DamageListener.kt b/bukkit/src/event/DamageListener.kt new file mode 100644 index 0000000..81d5d12 --- /dev/null +++ b/bukkit/src/event/DamageListener.kt @@ -0,0 +1,56 @@ +package cat.freya.khs.bukkit.event + +import cat.freya.khs.bukkit.BukkitKhsPlayer +import cat.freya.khs.bukkit.KhsPlugin +import cat.freya.khs.event.DamageEvent +import cat.freya.khs.event.onDamage +import org.bukkit.entity.Player as BukkitPlayer +import org.bukkit.entity.Projectile +import org.bukkit.event.EventHandler +import org.bukkit.event.EventPriority +import org.bukkit.event.Listener +import org.bukkit.event.entity.EntityDamageByEntityEvent +import org.bukkit.event.entity.EntityDamageEvent + +class DamageListener(val plugin: KhsPlugin) : Listener { + + init { + plugin.server.pluginManager.registerEvents(this, plugin) + } + + @EventHandler(priority = EventPriority.HIGHEST) + fun onEntityDamageByEntity(event: EntityDamageByEntityEvent) { + val bukkitPlayer = (event.entity as? BukkitPlayer) ?: return + + // get attacker + val damager = event.damager + val attackerEntity: BukkitPlayer? = + when { + damager is Projectile -> damager.shooter as? BukkitPlayer + else -> damager as? BukkitPlayer + } + + if (attackerEntity == null) { + onEntityDamage(event) + return + } + + val khsPlayer = BukkitKhsPlayer(plugin.shim, bukkitPlayer) + val khsAttacker = BukkitKhsPlayer(plugin.shim, attackerEntity) + val khsEvent = DamageEvent(plugin.khs, khsPlayer, khsAttacker, event.damage) + onDamage(khsEvent) + + if (khsEvent.cancelled) event.setCancelled(true) + } + + @EventHandler(priority = EventPriority.HIGH) + fun onEntityDamage(event: EntityDamageEvent) { + val bukkitPlayer = (event.entity as? BukkitPlayer) ?: return + + val khsPlayer = BukkitKhsPlayer(plugin.shim, bukkitPlayer) + val khsEvent = DamageEvent(plugin.khs, khsPlayer, null, event.damage) + onDamage(khsEvent) + + if (khsEvent.cancelled) event.setCancelled(true) + } +} diff --git a/bukkit/src/event/InteractListener.kt b/bukkit/src/event/InteractListener.kt new file mode 100644 index 0000000..cc9b2a7 --- /dev/null +++ b/bukkit/src/event/InteractListener.kt @@ -0,0 +1,50 @@ +package cat.freya.khs.bukkit.event + +import cat.freya.khs.bukkit.BukkitKhsPlayer +import cat.freya.khs.bukkit.KhsPlugin +import cat.freya.khs.bukkit.toKhsItem +import cat.freya.khs.event.InteractEvent +import cat.freya.khs.event.UseEvent +import cat.freya.khs.event.onInteract +import cat.freya.khs.event.onUse +import org.bukkit.event.EventHandler +import org.bukkit.event.EventPriority +import org.bukkit.event.Listener +import org.bukkit.event.block.Action +import org.bukkit.event.player.PlayerInteractEvent + +class InteractListener(val plugin: KhsPlugin) : Listener { + + init { + plugin.server.pluginManager.registerEvents(this, plugin) + } + + @EventHandler(priority = EventPriority.HIGHEST) + fun onPlayerInteract(event: PlayerInteractEvent) { + val bukkitPlayer = event.player ?: return + + val khsPlayer = BukkitKhsPlayer(plugin.shim, bukkitPlayer) + + val block = event.clickedBlock?.type?.name + if (event.action == Action.RIGHT_CLICK_BLOCK && block != null) { + val khsEvent = InteractEvent(plugin.khs, khsPlayer, block) + onInteract(khsEvent) + + if (khsEvent.cancelled) { + event.setCancelled(true) + return + } + } + + val item = toKhsItem(event.item) + if (item != null) { + val khsEvent = UseEvent(plugin.khs, khsPlayer, item) + onUse(khsEvent) + + if (khsEvent.cancelled) { + event.setCancelled(true) + return + } + } + } +} diff --git a/bukkit/src/event/InventoryListener.kt b/bukkit/src/event/InventoryListener.kt new file mode 100644 index 0000000..4ec0a83 --- /dev/null +++ b/bukkit/src/event/InventoryListener.kt @@ -0,0 +1,61 @@ +package cat.freya.khs.bukkit.event + +import cat.freya.khs.bukkit.BukkitKhsInventory +import cat.freya.khs.bukkit.BukkitKhsPlayer +import cat.freya.khs.bukkit.KhsPlugin +import cat.freya.khs.bukkit.toKhsItem +import cat.freya.khs.event.ClickEvent +import cat.freya.khs.event.CloseEvent +import cat.freya.khs.event.onClick +import cat.freya.khs.event.onClose +import org.bukkit.entity.Player as BukkitPlayer +import org.bukkit.event.EventHandler +import org.bukkit.event.EventPriority +import org.bukkit.event.Listener +import org.bukkit.event.inventory.InventoryClickEvent +import org.bukkit.event.inventory.InventoryCloseEvent +import org.bukkit.event.inventory.InventoryEvent +import org.bukkit.inventory.Inventory + +class InventoryListener(val plugin: KhsPlugin) : Listener { + + init { + plugin.server.pluginManager.registerEvents(this, plugin) + } + + private fun getInv(event: InventoryEvent): Pair<Inventory, String?> { + if (plugin.shim.supports(14)) { + var inv = event.view.topInventory + return inv to event.view.title + } else { + var inv = event.inventory + var title = inv::class.java.getMethod("getName").invoke(inv) as String + return inv to title + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + fun onInventoryClick(event: InventoryClickEvent) { + val (inventory, title) = getInv(event) + val bukkitPlayer = event.whoClicked as? BukkitPlayer ?: return + val item = toKhsItem(event.currentItem) ?: return + + val khsPlayer = BukkitKhsPlayer(plugin.shim, bukkitPlayer) + val khsInventory = BukkitKhsInventory(plugin.shim, inventory, title) + val khsEvent = ClickEvent(plugin.khs, khsPlayer, khsInventory, item) + onClick(khsEvent) + + if (khsEvent.cancelled) event.setCancelled(true) + } + + @EventHandler(priority = EventPriority.HIGHEST) + fun onInventoryClose(event: InventoryCloseEvent) { + val (inventory, title) = getInv(event) + val bukkitPlayer = event.player as? BukkitPlayer ?: return + + val khsPlayer = BukkitKhsPlayer(plugin.shim, bukkitPlayer) + val khsInventory = BukkitKhsInventory(plugin.shim, inventory, title) + val khsEvent = CloseEvent(plugin.khs, khsPlayer, khsInventory) + onClose(khsEvent) + } +} diff --git a/bukkit/src/event/JoinLeaveListener.kt b/bukkit/src/event/JoinLeaveListener.kt new file mode 100644 index 0000000..3bb2832 --- /dev/null +++ b/bukkit/src/event/JoinLeaveListener.kt @@ -0,0 +1,52 @@ +package cat.freya.khs.bukkit.event + +import cat.freya.khs.bukkit.BukkitKhsPlayer +import cat.freya.khs.bukkit.KhsPlugin +import cat.freya.khs.event.JoinEvent +import cat.freya.khs.event.KickEvent +import cat.freya.khs.event.LeaveEvent +import cat.freya.khs.event.onJoin +import cat.freya.khs.event.onKick +import cat.freya.khs.event.onLeave +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.PlayerKickEvent +import org.bukkit.event.player.PlayerQuitEvent + +class JoinLeaveListener(val plugin: KhsPlugin) : Listener { + + init { + plugin.server.pluginManager.registerEvents(this, plugin) + } + + @EventHandler(priority = EventPriority.HIGHEST) + fun onPlayerJoin(event: PlayerJoinEvent) { + val bukkitPlayer = event.player ?: return + val khsPlayer = BukkitKhsPlayer(plugin.shim, bukkitPlayer) + val khsEvent = JoinEvent(plugin.khs, khsPlayer) + onJoin(khsEvent) + } + + @EventHandler(priority = EventPriority.HIGHEST) + fun onPlayerQuit(event: PlayerQuitEvent) { + val bukkitPlayer = event.player ?: return + val khsPlayer = BukkitKhsPlayer(plugin.shim, bukkitPlayer) + val khsEvent = LeaveEvent(plugin.khs, khsPlayer) + onLeave(khsEvent) + + // remove player from disguiser + plugin.entityHider.removePlayer(bukkitPlayer) + } + + @EventHandler(priority = EventPriority.HIGHEST) + fun onPlayerKick(event: PlayerKickEvent) { + val bukkitPlayer = event.player ?: return + val khsPlayer = BukkitKhsPlayer(plugin.shim, bukkitPlayer) + val khsEvent = KickEvent(plugin.khs, khsPlayer, event.reason ?: "") + onKick(khsEvent) + + if (khsEvent.cancelled) event.setCancelled(true) + } +} diff --git a/bukkit/src/event/MovementListener.kt b/bukkit/src/event/MovementListener.kt new file mode 100644 index 0000000..9b80415 --- /dev/null +++ b/bukkit/src/event/MovementListener.kt @@ -0,0 +1,71 @@ +package cat.freya.khs.bukkit.event + +import cat.freya.khs.bukkit.BukkitKhsPlayer +import cat.freya.khs.bukkit.KhsPlugin +import cat.freya.khs.event.JumpEvent +import cat.freya.khs.event.MoveEvent +import cat.freya.khs.event.onJump +import cat.freya.khs.event.onMove +import cat.freya.khs.world.Position as KhsPosition +import java.util.UUID +import java.util.concurrent.ConcurrentHashMap +import org.bukkit.Material +import org.bukkit.entity.Player as BukkitPlayer +import org.bukkit.event.EventHandler +import org.bukkit.event.EventPriority +import org.bukkit.event.Listener +import org.bukkit.event.player.PlayerMoveEvent + +class MovementListener(val plugin: KhsPlugin) : Listener { + + private val prevPlayersOnGround: MutableSet<UUID> = ConcurrentHashMap.newKeySet<UUID>() + + init { + plugin.server.pluginManager.registerEvents(this, plugin) + } + + private fun isOnGround(player: BukkitPlayer): Boolean { + if (plugin.shim.supports(16, 1)) { + val below = player.location.clone().subtract(0.0, 0.1, 0.0).block + return below.type.isSolid + } else { + @Suppress("DEPRECATION") + return player.isOnGround() + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + fun onPlayerMove(event: PlayerMoveEvent) { + val bukkitPlayer = event.player ?: return + val khsPlayer = BukkitKhsPlayer(plugin.shim, bukkitPlayer) + + // check jumping + if (bukkitPlayer.velocity.y > 0.0) { + val block = bukkitPlayer.location?.block?.type + if ( + block != Material.LADDER && + prevPlayersOnGround.contains(bukkitPlayer.uniqueId) && + isOnGround(bukkitPlayer) + ) { + // trigger jump event + val khsEvent = JumpEvent(plugin.khs, khsPlayer) + onJump(khsEvent) + } + // update set + if (isOnGround(bukkitPlayer)) prevPlayersOnGround.add(bukkitPlayer.uniqueId) + else prevPlayersOnGround.remove(bukkitPlayer.uniqueId) + } + + val to = event.to?.let { KhsPosition(it.x, it.y, it.z) } ?: return + val khsEvent = MoveEvent(plugin.khs, khsPlayer, to) + onMove(khsEvent) + + if (khsEvent.cancelled) event.setCancelled(true) + + // update disguise (if exists) + val disguise = plugin.disguiser.getDisguise(bukkitPlayer) ?: return + val dest = event.to ?: return + if (!khsEvent.cancelled && event.from.distance(dest) > 0.1) disguise.shouldBeSolid = false + disguise.startSolidifying() + } +} diff --git a/bukkit/src/event/PacketListener.kt b/bukkit/src/event/PacketListener.kt new file mode 100644 index 0000000..d49af60 --- /dev/null +++ b/bukkit/src/event/PacketListener.kt @@ -0,0 +1,110 @@ +package cat.freya.khs.bukkit.event + +import cat.freya.khs.bukkit.BukkitKhsPlayer +import cat.freya.khs.bukkit.KhsPlugin +import cat.freya.khs.bukkit.disguise.Disguise +import cat.freya.khs.event.DamageEvent +import cat.freya.khs.event.onDamage +import com.github.retrooper.packetevents.PacketEvents +import com.github.retrooper.packetevents.event.PacketListener as PacketListenerPE +import com.github.retrooper.packetevents.event.PacketListenerPriority +import com.github.retrooper.packetevents.event.PacketReceiveEvent +import com.github.retrooper.packetevents.event.PacketSendEvent +import com.github.retrooper.packetevents.protocol.packettype.PacketType.Play.Client.INTERACT_ENTITY +import com.github.retrooper.packetevents.protocol.packettype.PacketType.Play.Server.* +import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity +import com.github.retrooper.packetevents.wrapper.play.server.* +import java.util.UUID +import org.bukkit.GameMode +import org.bukkit.attribute.Attribute +import org.bukkit.entity.Player as BukkitPlayer + +class PacketListener(val plugin: KhsPlugin) : PacketListenerPE { + private val debounce = mutableSetOf<UUID>() + + init { + PacketEvents.getAPI().eventManager.registerListener(this, PacketListenerPriority.NORMAL) + } + + // intercept entity-related packets of entities that + // are supposed to be hidden + override fun onPacketSend(event: PacketSendEvent) { + val player = event.getPlayer() as? BukkitPlayer ?: return + val entityId = + when (event.packetType) { + ENTITY_EQUIPMENT -> WrapperPlayServerEntityEquipment(event).entityId + ENTITY_ANIMATION -> WrapperPlayServerEntityAnimation(event).entityId + SPAWN_ENTITY -> WrapperPlayServerSpawnEntity(event).entityId + ENTITY_VELOCITY -> WrapperPlayServerEntityVelocity(event).entityId + ENTITY_HEAD_LOOK -> WrapperPlayServerEntityHeadLook(event).entityId + ENTITY_TELEPORT -> WrapperPlayServerEntityTeleport(event).entityId + ENTITY_STATUS -> WrapperPlayServerEntityStatus(event).entityId + ENTITY_METADATA -> WrapperPlayServerEntityMetadata(event).entityId + ENTITY_EFFECT -> WrapperPlayServerEntityEffect(event).entityId + REMOVE_ENTITY_EFFECT -> WrapperPlayServerRemoveEntityEffect(event).entityId + else -> return + } + + if (!plugin.entityHider.isVisible(player, entityId)) { + event.setCancelled(true) + } + } + + // check when a player is trying to attack a disguise + override fun onPacketReceive(event: PacketReceiveEvent) { + val player = event.getPlayer() as? BukkitPlayer ?: return + + // we want interact event + if (event.packetType != INTERACT_ENTITY) return + + val packet = WrapperPlayClientInteractEntity(event) + + // attacking only + val action = packet.action ?: return + if (action != WrapperPlayClientInteractEntity.InteractAction.ATTACK) return + + val disguise = + plugin.disguiser.getByEntityId(packet.entityId) + ?: plugin.disguiser.getByHitboxId(packet.entityId) + ?: return + + if (disguise.player.gameMode == GameMode.CREATIVE) return + + event.setCancelled(true) + handleAttack(disguise, player) + } + + private fun handleAttack(disguise: Disguise, seeker: BukkitPlayer) { + if (disguise.player.uniqueId == seeker.uniqueId) return + + val fallbackAmount = 7.0 + val amount = + if (plugin.shim.supports(9)) { + val attribName = + if (plugin.shim.supports(21)) "ATTACK_DAMAGE" else "GENERIC_ATTACK_DAMAGE" + val attrib = Attribute.valueOf(attribName) + seeker.getAttribute(attrib)?.value ?: fallbackAmount + } else { + fallbackAmount // uhhh i dunno how to do this for 1.8 + } + + val debounceUUID = disguise.player.uniqueId + + disguise.shouldBeSolid = false + if (debounce.contains(debounceUUID)) return + + // trigger an attack event + val khsPlayer = BukkitKhsPlayer(plugin.shim, disguise.player) + val khsSeeker = BukkitKhsPlayer(plugin.shim, seeker) + val khsEvent = DamageEvent(plugin.khs, khsPlayer, khsSeeker, amount) + onDamage(khsEvent) + + // set and soon turn off debounce + debounce.add(debounceUUID) + plugin.server.scheduler.scheduleSyncDelayedTask( + plugin, + { debounce.remove(debounceUUID) }, + 10, + ) + } +} diff --git a/bukkit/src/event/PlayerListener.kt b/bukkit/src/event/PlayerListener.kt new file mode 100644 index 0000000..23b0a91 --- /dev/null +++ b/bukkit/src/event/PlayerListener.kt @@ -0,0 +1,59 @@ +package cat.freya.khs.bukkit.event + +import cat.freya.khs.bukkit.BukkitKhsPlayer +import cat.freya.khs.bukkit.KhsPlugin +import cat.freya.khs.bukkit.toKhsItem +import cat.freya.khs.event.DropEvent +import cat.freya.khs.event.HungerEvent +import cat.freya.khs.event.RegenEvent +import cat.freya.khs.event.onDrop +import cat.freya.khs.event.onHunger +import cat.freya.khs.event.onRegen +import org.bukkit.entity.Player as BukkitPlayer +import org.bukkit.event.EventHandler +import org.bukkit.event.EventPriority +import org.bukkit.event.Listener +import org.bukkit.event.entity.EntityRegainHealthEvent +import org.bukkit.event.entity.FoodLevelChangeEvent +import org.bukkit.event.player.PlayerDropItemEvent + +class PlayerListener(val plugin: KhsPlugin) : Listener { + + init { + plugin.server.pluginManager.registerEvents(this, plugin) + } + + @EventHandler(priority = EventPriority.HIGHEST) + fun onFoodLevelChange(event: FoodLevelChangeEvent) { + val bukkitPlayer = event.entity as? BukkitPlayer ?: return + val khsPlayer = BukkitKhsPlayer(plugin.shim, bukkitPlayer) + val khsEvent = HungerEvent(plugin.khs, khsPlayer) + onHunger(khsEvent) + + if (khsEvent.cancelled) event.setCancelled(true) + } + + @EventHandler(priority = EventPriority.HIGHEST) + fun onEntityRegainHealth(event: EntityRegainHealthEvent) { + val bukkitPlayer = event.entity as? BukkitPlayer ?: return + val khsPlayer = BukkitKhsPlayer(plugin.shim, bukkitPlayer) + val natural = + event.regainReason == EntityRegainHealthEvent.RegainReason.SATIATED || + event.regainReason == EntityRegainHealthEvent.RegainReason.REGEN + val khsEvent = RegenEvent(plugin.khs, khsPlayer, natural) + onRegen(khsEvent) + + if (khsEvent.cancelled) event.setCancelled(true) + } + + @EventHandler(priority = EventPriority.HIGHEST) + fun onPlayerDropItem(event: PlayerDropItemEvent) { + val bukkitPlayer = event.player ?: return + val item = toKhsItem(event.itemDrop?.itemStack) ?: return + val khsPlayer = BukkitKhsPlayer(plugin.shim, bukkitPlayer) + val khsEvent = DropEvent(plugin.khs, khsPlayer, item) + onDrop(khsEvent) + + if (khsEvent.cancelled) event.setCancelled(true) + } +} diff --git a/bukkit/src/event/RespawnListener.kt b/bukkit/src/event/RespawnListener.kt new file mode 100644 index 0000000..a2b4bc5 --- /dev/null +++ b/bukkit/src/event/RespawnListener.kt @@ -0,0 +1,41 @@ +package cat.freya.khs.bukkit.event + +import cat.freya.khs.bukkit.BukkitKhsPlayer +import cat.freya.khs.bukkit.KhsPlugin +import cat.freya.khs.event.DeathEvent +import cat.freya.khs.event.onDeath +import cat.freya.khs.world.Location +import java.util.UUID +import java.util.concurrent.ConcurrentHashMap +import org.bukkit.event.EventHandler +import org.bukkit.event.EventPriority +import org.bukkit.event.Listener +import org.bukkit.event.entity.PlayerDeathEvent +import org.bukkit.event.player.PlayerRespawnEvent + +class RespawnListener(val plugin: KhsPlugin) : Listener { + + private val respawnLocation: MutableMap<UUID, Location> = ConcurrentHashMap<UUID, Location>() + + init { + plugin.server.pluginManager.registerEvents(this, plugin) + } + + @EventHandler(priority = EventPriority.HIGHEST) + fun onPlayerDeath(event: PlayerDeathEvent) { + val bukkitPlayer = event.entity ?: return + val khsPlayer = BukkitKhsPlayer(plugin.shim, bukkitPlayer) + val khsEvent = DeathEvent(plugin.khs, khsPlayer) + onDeath(khsEvent) + + if (khsEvent.cancelled) respawnLocation[khsPlayer.uuid] = khsPlayer.location + } + + @EventHandler(priority = EventPriority.HIGHEST) + fun onPlayerRespawn(event: PlayerRespawnEvent) { + val bukkitPlayer = event.player ?: return + val khsPlayer = BukkitKhsPlayer(plugin.shim, bukkitPlayer) + val location = respawnLocation.remove(khsPlayer.uuid) ?: return + khsPlayer.teleport(location) + } +} diff --git a/bukkit/src/packet/BlockChangePacket.kt b/bukkit/src/packet/BlockChangePacket.kt new file mode 100644 index 0000000..7d11f4e --- /dev/null +++ b/bukkit/src/packet/BlockChangePacket.kt @@ -0,0 +1,20 @@ +package cat.freya.khs.bukkit.packet + +import com.github.retrooper.packetevents.PacketEvents +import com.github.retrooper.packetevents.util.Vector3i +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerBlockChange +import io.github.retrooper.packetevents.util.SpigotConversionUtil +import org.bukkit.Bukkit +import org.bukkit.Location +import org.bukkit.Material +import org.bukkit.entity.Player as BukkitPlayer + +data class BlockChangePacket(val location: Location, val material: Material) { + fun send(player: BukkitPlayer) { + val blockData = Bukkit.createBlockData(material) + val state = SpigotConversionUtil.fromBukkitBlockData(blockData) + val vector = Vector3i(location.blockX, location.blockY, location.blockZ) + val packet = WrapperPlayServerBlockChange(vector, state) + PacketEvents.getAPI().playerManager.sendPacket(player, packet) + } +} diff --git a/bukkit/src/packet/EntityDestroyPacket.kt b/bukkit/src/packet/EntityDestroyPacket.kt new file mode 100644 index 0000000..3240b81 --- /dev/null +++ b/bukkit/src/packet/EntityDestroyPacket.kt @@ -0,0 +1,13 @@ +package cat.freya.khs.bukkit.packet + +import com.github.retrooper.packetevents.PacketEvents +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerDestroyEntities +import org.bukkit.entity.Entity +import org.bukkit.entity.Player as BukkitPlayer + +data class EntityDestroyPacket(val entiy: Entity) { + fun send(player: BukkitPlayer) { + val packet = WrapperPlayServerDestroyEntities(entiy.entityId) + PacketEvents.getAPI().playerManager.sendPacket(player, packet) + } +} diff --git a/bukkit/src/packet/EntityMetadataPacket.kt b/bukkit/src/packet/EntityMetadataPacket.kt new file mode 100644 index 0000000..6d5978a --- /dev/null +++ b/bukkit/src/packet/EntityMetadataPacket.kt @@ -0,0 +1,17 @@ +package cat.freya.khs.bukkit.packet + +import com.github.retrooper.packetevents.PacketEvents +import com.github.retrooper.packetevents.protocol.entity.data.EntityData +import com.github.retrooper.packetevents.protocol.entity.data.EntityDataTypes +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerEntityMetadata +import org.bukkit.entity.Entity +import org.bukkit.entity.Player as BukkitPlayer + +data class EntityMetadataPacket(val entiy: Entity, val glow: Boolean) { + fun send(player: BukkitPlayer) { + val glowingByte = if (glow) 0x40 else 0x0 + val data = EntityData(0x0, EntityDataTypes.BYTE, glowingByte.toByte()) + val packet = WrapperPlayServerEntityMetadata(entiy.entityId, listOf(data)) + PacketEvents.getAPI().playerManager.sendPacket(player, packet) + } +} diff --git a/bukkit/src/packet/EntityTeleportPacket.kt b/bukkit/src/packet/EntityTeleportPacket.kt new file mode 100644 index 0000000..7e3beb1 --- /dev/null +++ b/bukkit/src/packet/EntityTeleportPacket.kt @@ -0,0 +1,19 @@ +package cat.freya.khs.bukkit.packet + +import com.github.retrooper.packetevents.PacketEvents +import com.github.retrooper.packetevents.util.Vector3d +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerEntityTeleport +import org.bukkit.Location +import org.bukkit.entity.Entity +import org.bukkit.entity.Player as BukkitPlayer + +data class EntityTeleportPacket(val entity: Entity, val position: Location) { + fun send(player: BukkitPlayer) { + val vector = Vector3d(position.x, position.y, position.z) + val yaw = 0f + val pitch = 0f + val onGround = false + val packet = WrapperPlayServerEntityTeleport(entity.entityId, vector, yaw, pitch, onGround) + PacketEvents.getAPI().playerManager.sendPacket(player, packet) + } +} |