diff --git a/build.gradle.kts b/build.gradle.kts index ce690c4..8e11585 100755 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -14,8 +14,8 @@ plugins { val javaVersion = 24 group = "dev.marrow" -version = "1.0" -description = "paper plugin template" +version = "1.1" +description = "listener of labels and maps" // paper-plugin.yml paper { @@ -25,10 +25,10 @@ paper { // version is version value foliaSupported = true - author = "You" + author = "Lonewoss" main = "$group.${project.name.lowercase()}.Core" apiVersion = "1.21" - prefix = "example" + prefix = "ZSign" load = BukkitPluginDescription.PluginLoadOrder.STARTUP @@ -80,7 +80,7 @@ publishing { tasks { runServer { - //dependsOn(build) + serverJar(file("run/prismatic-bundler-1.21.8-R0.1-SNAPSHOT-mojmap.jar")) version("1.21.7") } diff --git a/settings.gradle.kts b/settings.gradle.kts index d7fafab..30f7a16 100755 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1 +1 @@ -rootProject.name = "plugin" \ No newline at end of file +rootProject.name = "ZSign" \ No newline at end of file diff --git a/src/main/kotlin/dev/marrow/plugin/commands/Example.kt b/src/main/kotlin/dev/marrow/plugin/commands/Example.kt deleted file mode 100755 index d8a61c5..0000000 --- a/src/main/kotlin/dev/marrow/plugin/commands/Example.kt +++ /dev/null @@ -1,40 +0,0 @@ -package dev.marrow.plugin.commands - -import com.mojang.brigadier.arguments.StringArgumentType -import com.mojang.brigadier.tree.LiteralCommandNode -import kotlinx.coroutines.* -import net.kyori.adventure.text.Component -import com.github.shynixn.mccoroutine.bukkit.launch -import com.mojang.brigadier.Command -import dev.marrow.plugin.Core -import io.papermc.paper.command.brigadier.CommandSourceStack -import io.papermc.paper.command.brigadier.Commands -import io.papermc.paper.command.brigadier.Commands.argument -import io.papermc.paper.command.brigadier.argument.ArgumentTypes -import io.papermc.paper.command.brigadier.argument.resolvers.selector.PlayerSelectorArgumentResolver - -class Example private constructor() { - companion object { - operator fun invoke(command: String): LiteralCommandNode { - return Commands.literal(command).apply { - then(argument("player", ArgumentTypes.player()).apply { - then(argument("message", StringArgumentType.greedyString()).apply { - executes { - val sender = it.source.sender - val targetResolver = it.getArgument("player", PlayerSelectorArgumentResolver::class.java) - val target = targetResolver.resolve(it.source).first() - val message = it.getArgument("message", String::class.java) - sender.sendMessage("${target.name} will receive your message in 1 second") - - Core.instance.launch { - delay(1000) - target.sendMessage(Component.text("${sender.name}: $message")) - } - Command.SINGLE_SUCCESS - } - }) - }) - }.build() - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/dev/marrow/plugin/Core.kt b/src/main/kotlin/dev/marrow/zsign/Core.kt similarity index 80% rename from src/main/kotlin/dev/marrow/plugin/Core.kt rename to src/main/kotlin/dev/marrow/zsign/Core.kt index 01fb19e..77235a8 100755 --- a/src/main/kotlin/dev/marrow/plugin/Core.kt +++ b/src/main/kotlin/dev/marrow/zsign/Core.kt @@ -1,9 +1,9 @@ -package dev.marrow.plugin +package dev.marrow.zsign import com.github.shynixn.mccoroutine.bukkit.SuspendingJavaPlugin -import dev.marrow.plugin.commands.Example -import dev.marrow.plugin.utils.CommonUtils.gson -import dev.marrow.plugin.utils.ComponentExtension.deserializeMiniMessage +import dev.marrow.zsign.listeners.MapLaminationListener +import dev.marrow.zsign.utils.CommonUtils.gson +import dev.marrow.zsign.utils.ComponentExtension.deserializeMiniMessage import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents import java.io.BufferedReader import java.io.File @@ -12,7 +12,6 @@ import java.io.FileWriter import java.util.logging.Logger class Core : SuspendingJavaPlugin() { - companion object { lateinit var instance: Core lateinit var config: Configuration @@ -23,9 +22,11 @@ class Core : SuspendingJavaPlugin() { instance = this console = logger - this.lifecycleManager.registerEventHandler(LifecycleEvents.COMMANDS, { - it.registrar().register(Example("example"), listOf("example_alias")) - }) + // Только наш cartography-слушатель + server.pluginManager.registerEvents(MapLaminationListener(this), this) + + this.lifecycleManager.registerEventHandler(LifecycleEvents.COMMANDS) { + } server.consoleSender.sendMessage(Companion.config.enabledMessage.deserializeMiniMessage()) } diff --git a/src/main/kotlin/dev/marrow/zsign/listeners/MapLamintationListener.kt b/src/main/kotlin/dev/marrow/zsign/listeners/MapLamintationListener.kt new file mode 100644 index 0000000..e4ad3f2 --- /dev/null +++ b/src/main/kotlin/dev/marrow/zsign/listeners/MapLamintationListener.kt @@ -0,0 +1,248 @@ +package dev.marrow.zsign.listeners + +import com.destroystokyo.paper.event.inventory.PrepareResultEvent +import dev.marrow.zsign.Core +import io.papermc.paper.event.player.CartographyItemEvent +import io.papermc.paper.threadedregions.scheduler.ScheduledTask +import net.kyori.adventure.text.Component +import org.bukkit.Material +import org.bukkit.NamespacedKey +import org.bukkit.entity.Player +import org.bukkit.event.EventHandler +import org.bukkit.event.EventPriority +import org.bukkit.event.Listener +import org.bukkit.event.inventory.* +import org.bukkit.inventory.CartographyInventory +import org.bukkit.inventory.Inventory +import org.bukkit.inventory.ItemStack +import org.bukkit.inventory.meta.MapMeta +import org.bukkit.persistence.PersistentDataType + +class MapLaminationListener(private val plugin: Core) : Listener { + companion object { + private const val TOP_SLOT = 1 + private val RED_PANE = Material.RED_STAINED_GLASS_PANE + + val laminatedKey: NamespacedKey by lazy { NamespacedKey(Core.instance, "laminated") } + + private fun ItemStack?.isAir(): Boolean = this == null || type == Material.AIR + private fun ItemStack?.isRedPane(): Boolean = this != null && type == RED_PANE + private fun air(): ItemStack = ItemStack(Material.AIR) + + private fun isLaminated(item: ItemStack?): Boolean { + if (item == null || item.type != Material.FILLED_MAP) return false + val mm = item.itemMeta as? MapMeta ?: return false + return mm.persistentDataContainer.has(laminatedKey, PersistentDataType.BYTE) + } + + private fun markLaminated(copy: ItemStack): ItemStack = copy.apply { + val mm = itemMeta as MapMeta + mm.persistentDataContainer.set(laminatedKey, PersistentDataType.BYTE, 1.toByte()) + mm.lore(listOf(Component.text("Заламинирована"))) + itemMeta = mm + } + + private fun MapMeta.isAlreadyLaminated(): Boolean = + persistentDataContainer.has(laminatedKey, PersistentDataType.BYTE) + } + + private fun scheduleRecalcFor(player: Player, inv: Inventory) { + player.scheduler.runDelayed( + plugin, + { _: ScheduledTask -> + try { + if (player.openInventory.topInventory == inv) { + player.updateInventory() + } + } catch (t: Throwable) { + plugin.logger.warning("Failed to recalc inventory for ${player.name}: ${t.message}") + } + }, + null, + 1L + ) + } + + private fun Player.sync() = updateInventory() + + private fun CartographyInventory.spaceInTop(): Int { + val target = getItem(TOP_SLOT) + return when { + target.isAir() -> RED_PANE.maxStackSize + target?.type == RED_PANE -> (RED_PANE.maxStackSize - target.amount).coerceAtLeast(0) + else -> 0 + } + } + + private fun CartographyInventory.addToTop(amount: Int) { + val target = getItem(TOP_SLOT) + if (target.isAir()) { + setItem(TOP_SLOT, ItemStack(RED_PANE, amount)) + } else { + target!!.amount += amount + setItem(TOP_SLOT, target) + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + fun onPrepareResult(e: PrepareResultEvent) { + val inv = e.inventory as? CartographyInventory ?: return + val base = inv.getItem(0) ?: return + if (base.type != Material.FILLED_MAP) return + val baseMeta = base.itemMeta as? MapMeta ?: return + + if (baseMeta.isAlreadyLaminated()) { + e.result = air() + return + } + + val top = inv.getItem(TOP_SLOT) + val cursor = inv.viewers.firstOrNull()?.itemOnCursor + if (top.isRedPane() || (top.isAir() && cursor.isRedPane())) { + val out = markLaminated(base.clone()) + out.amount = 1 + e.result = out + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + fun onCartographyInsertRedPane(e: InventoryClickEvent) { + val inv = e.inventory as? CartographyInventory ?: return + val p = e.whoClicked as? Player ?: return + val cursor = p.itemOnCursor + + if (e.clickedInventory === inv && e.rawSlot == TOP_SLOT && cursor.type == RED_PANE) { + e.isCancelled = true + val placeAmount = if (e.click == ClickType.RIGHT) 1 else cursor.amount + val toPlace = placeAmount.coerceAtMost(inv.spaceInTop()) + if (toPlace > 0) { + inv.addToTop(toPlace) + if (cursor.amount == toPlace) p.setItemOnCursor(air()) else { + cursor.amount -= toPlace + p.setItemOnCursor(cursor) + } + } + p.updateInventory() + scheduleRecalcFor(p, inv) + return + } + + if (e.clickedInventory === inv && e.rawSlot == TOP_SLOT && e.click == ClickType.NUMBER_KEY) { + val idx = e.hotbarButton + if (idx in 0..8) { + val hot = p.inventory.getItem(idx) + if (hot?.type == RED_PANE && hot.amount > 0) { + e.isCancelled = true + val toMove = hot.amount.coerceAtMost(inv.spaceInTop()) + if (toMove > 0) { + inv.addToTop(toMove) + hot.amount -= toMove + p.inventory.setItem(idx, if (hot.amount > 0) hot else air()) + p.updateInventory() + scheduleRecalcFor(p, inv) + } + } + } + return + } + + if (e.clickedInventory !== inv && e.isShiftClick) { + val clicked = e.currentItem ?: return + if (clicked.type != RED_PANE) return + e.isCancelled = true + val toMove = clicked.amount.coerceAtMost(inv.spaceInTop()) + if (toMove > 0) { + inv.addToTop(toMove) + clicked.amount -= toMove + e.clickedInventory?.setItem(e.slot, if (clicked.amount > 0) clicked else air()) + p.updateInventory() + scheduleRecalcFor(p, inv) + } + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + fun onCartographyDrag(e: InventoryDragEvent) { + if (TOP_SLOT in e.rawSlots && e.oldCursor.type == RED_PANE) { + e.isCancelled = true + (e.whoClicked as? Player)?.sync() + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + fun onCartographyTake(e: CartographyItemEvent) { + val inv = e.inventory + val base = inv.getItem(0) ?: return + if (base.type != Material.FILLED_MAP) return + val baseMeta = base.itemMeta as? MapMeta ?: return + + if (baseMeta.isAlreadyLaminated()) { + e.isCancelled = true + inv.result = air() + return + } + + val res = inv.result ?: return + val rMeta = res.itemMeta as? MapMeta ?: return + if (!rMeta.isAlreadyLaminated()) return + + val p = e.whoClicked as? Player ?: return + val add = inv.getItem(TOP_SLOT) + val paneInTop = add.isRedPane() && add!!.amount > 0 + + if (!paneInTop) { + when { + p.itemOnCursor.type == RED_PANE && p.itemOnCursor.amount > 0 -> { + val cur = p.itemOnCursor + cur.amount -= 1 + p.setItemOnCursor(if (cur.amount > 0) cur else air()) + } + else -> { + val idx = p.inventory.first(RED_PANE) + if (idx < 0) { + e.isCancelled = true + return + } + val st = p.inventory.getItem(idx) + if (st != null && st.amount > 1) { + st.amount -= 1 + p.inventory.setItem(idx, st) + } else { + p.inventory.setItem(idx, air()) + } + } + } + + if (base.amount <= 1) { + inv.setItem(0, air()) + } else { + base.amount -= 1 + inv.setItem(0, base) + } + } + baseMeta.mapView?.isLocked = true + base.itemMeta = baseMeta + + scheduleRecalcFor(p, inv) + p.updateInventory() + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + fun onPrepareCraft(e: PrepareItemCraftEvent) { + val inv = e.inventory + val matrix = inv.matrix + if (matrix.any { isLaminated(it) }) { + inv.result = air() + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + fun onCraftClick(e: CraftItemEvent) { + val inv = e.inventory + val matrix = inv.matrix + if (matrix.any { isLaminated(it) }) { + e.isCancelled = true + inv.result = air() + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/marrow/plugin/utils/CommonUtils.kt b/src/main/kotlin/dev/marrow/zsign/utils/CommonUtils.kt similarity index 88% rename from src/main/kotlin/dev/marrow/plugin/utils/CommonUtils.kt rename to src/main/kotlin/dev/marrow/zsign/utils/CommonUtils.kt index e1204de..b1c3d51 100644 --- a/src/main/kotlin/dev/marrow/plugin/utils/CommonUtils.kt +++ b/src/main/kotlin/dev/marrow/zsign/utils/CommonUtils.kt @@ -1,4 +1,4 @@ -package dev.marrow.plugin.utils +package dev.marrow.zsign.utils import com.google.gson.Gson import com.google.gson.GsonBuilder diff --git a/src/main/kotlin/dev/marrow/plugin/utils/ComponentExtension.kt b/src/main/kotlin/dev/marrow/zsign/utils/ComponentExtension.kt similarity index 98% rename from src/main/kotlin/dev/marrow/plugin/utils/ComponentExtension.kt rename to src/main/kotlin/dev/marrow/zsign/utils/ComponentExtension.kt index 948f2ea..59b11ed 100755 --- a/src/main/kotlin/dev/marrow/plugin/utils/ComponentExtension.kt +++ b/src/main/kotlin/dev/marrow/zsign/utils/ComponentExtension.kt @@ -1,6 +1,6 @@ @file:Suppress("unused") -package dev.marrow.plugin.utils +package dev.marrow.zsign.utils import net.kyori.adventure.text.Component import net.kyori.adventure.text.format.TextDecoration diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 4b048fb..d8fdf17 100755 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1 +1 @@ -enabled-message: "plugin enabled!" \ No newline at end of file +enabled-message: "ZSign enabled!" \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..e69de29