Compare commits
No commits in common. "f3ea947310c0746a70958eb58e1bc60e762b6f8d" and "4343d01d119a05dca2a0227b9e92a737f9d9fe1d" have entirely different histories.
f3ea947310
...
4343d01d11
|
|
@ -1 +1 @@
|
||||||
Subproject commit 3835c9b9e2d488681b39f4cfd827e3e3371a1e1b
|
Subproject commit e0613cd05278222b0a2ac1c79a2d4ef317c9cea1
|
||||||
|
|
@ -3,6 +3,7 @@ package net.hareworks.ghostdisplays
|
||||||
import net.hareworks.ghostdisplays.api.DisplayService
|
import net.hareworks.ghostdisplays.api.DisplayService
|
||||||
import net.hareworks.ghostdisplays.command.CommandRegistrar
|
import net.hareworks.ghostdisplays.command.CommandRegistrar
|
||||||
import net.hareworks.ghostdisplays.display.DisplayManager
|
import net.hareworks.ghostdisplays.display.DisplayManager
|
||||||
|
import net.hareworks.ghostdisplays.display.EditSessionManager
|
||||||
import net.hareworks.ghostdisplays.internal.DefaultDisplayService
|
import net.hareworks.ghostdisplays.internal.DefaultDisplayService
|
||||||
import net.hareworks.ghostdisplays.internal.controller.DisplayRegistry
|
import net.hareworks.ghostdisplays.internal.controller.DisplayRegistry
|
||||||
import net.hareworks.kommand_lib.KommandLib
|
import net.hareworks.kommand_lib.KommandLib
|
||||||
|
|
@ -16,6 +17,7 @@ class GhostDisplaysPlugin : JavaPlugin() {
|
||||||
private lateinit var displayRegistry: DisplayRegistry
|
private lateinit var displayRegistry: DisplayRegistry
|
||||||
private lateinit var serviceImpl: DisplayService
|
private lateinit var serviceImpl: DisplayService
|
||||||
private lateinit var displayManager: DisplayManager
|
private lateinit var displayManager: DisplayManager
|
||||||
|
private lateinit var editSessions: EditSessionManager
|
||||||
private lateinit var miniMessage: MiniMessage
|
private lateinit var miniMessage: MiniMessage
|
||||||
private var permissionSession: MutationSession? = null
|
private var permissionSession: MutationSession? = null
|
||||||
private var kommand: KommandLib? = null
|
private var kommand: KommandLib? = null
|
||||||
|
|
@ -27,22 +29,26 @@ class GhostDisplaysPlugin : JavaPlugin() {
|
||||||
}
|
}
|
||||||
serviceImpl = DefaultDisplayService(this, displayRegistry)
|
serviceImpl = DefaultDisplayService(this, displayRegistry)
|
||||||
displayManager = DisplayManager(this, serviceImpl, miniMessage)
|
displayManager = DisplayManager(this, serviceImpl, miniMessage)
|
||||||
|
editSessions = EditSessionManager(this, displayManager).also {
|
||||||
|
server.pluginManager.registerEvents(it, this)
|
||||||
|
}
|
||||||
|
|
||||||
server.servicesManager.register(DisplayService::class.java, serviceImpl, this, ServicePriority.Normal)
|
server.servicesManager.register(DisplayService::class.java, serviceImpl, this, ServicePriority.Normal)
|
||||||
permissionSession = PermitsLib.session(this)
|
permissionSession = PermitsLib.session(this)
|
||||||
kommand = CommandRegistrar.register(this, displayManager, permissionSession!!)
|
kommand = CommandRegistrar.register(this, displayManager, editSessions, permissionSession!!)
|
||||||
|
|
||||||
instance = this
|
instance = this
|
||||||
logger.info("GhostDisplays ready: ${displayRegistry.controllerCount()} controllers active.")
|
logger.info("GhostDisplays ready: ${displayRegistry.controllerCount()} controllers active.")
|
||||||
|
|
||||||
server.scheduler.runTaskTimer(this, Runnable {
|
server.scheduler.runTaskTimer(this, Runnable {
|
||||||
displayRegistry.updateAudiences()
|
displayRegistry.tick()
|
||||||
}, 20L, 20L)
|
}, 10L, 10L)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDisable() {
|
override fun onDisable() {
|
||||||
kommand?.unregister()
|
kommand?.unregister()
|
||||||
kommand = null
|
kommand = null
|
||||||
|
runCatching { editSessions.shutdown() }
|
||||||
runCatching { displayManager.destroyAll() }
|
runCatching { displayManager.destroyAll() }
|
||||||
runCatching { serviceImpl.destroyAll() }
|
runCatching { serviceImpl.destroyAll() }
|
||||||
permissionSession = null
|
permissionSession = null
|
||||||
|
|
|
||||||
|
|
@ -44,12 +44,6 @@ interface DisplayController<T : Display> {
|
||||||
|
|
||||||
fun refreshAudience(target: Player? = null)
|
fun refreshAudience(target: Player? = null)
|
||||||
|
|
||||||
/**
|
|
||||||
* 定期的な更新チェックが必要かどうかを返します。
|
|
||||||
* trueの場合、サーバーのtick毎(または定期タスク)にrefreshAudienceが呼び出されます。
|
|
||||||
*/
|
|
||||||
fun needsPeriodicUpdate(): Boolean = false
|
|
||||||
|
|
||||||
fun destroy()
|
fun destroy()
|
||||||
|
|
||||||
fun onClick(priority: ClickPriority = ClickPriority.NORMAL, handler: DisplayClickHandler): HandlerRegistration
|
fun onClick(priority: ClickPriority = ClickPriority.NORMAL, handler: DisplayClickHandler): HandlerRegistration
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,4 @@ import org.bukkit.entity.Player
|
||||||
*/
|
*/
|
||||||
fun interface AudiencePredicate {
|
fun interface AudiencePredicate {
|
||||||
fun test(player: Player): Boolean
|
fun test(player: Player): Boolean
|
||||||
|
|
||||||
/**
|
|
||||||
* 表示対象判定が動的(時間経過や移動で変化する)かどうかを返します。
|
|
||||||
* trueの場合、定期的な再評価の対象となります。
|
|
||||||
*/
|
|
||||||
fun isDynamic(): Boolean {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,15 +28,12 @@ object AudiencePredicates {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun near(location: Location, radius: Double): AudiencePredicate {
|
fun near(location: Location, radius: Double): AudiencePredicate {
|
||||||
|
|
||||||
val radiusSq = radius * radius
|
val radiusSq = radius * radius
|
||||||
val worldName = location.world?.name
|
val worldName = location.world?.name
|
||||||
require(worldName != null) { "Location must have a world" }
|
require(worldName != null) { "Location must have a world" }
|
||||||
|
return AudiencePredicate { player ->
|
||||||
return object : AudiencePredicate {
|
player.world.name == worldName && player.location.distanceSquared(location) <= radiusSq
|
||||||
override fun test(player: Player): Boolean {
|
|
||||||
return player.world.name == worldName && player.location.distanceSquared(location) <= radiusSq
|
|
||||||
}
|
|
||||||
override fun isDynamic(): Boolean = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import java.util.UUID
|
||||||
import net.hareworks.ghostdisplays.display.DisplayManager
|
import net.hareworks.ghostdisplays.display.DisplayManager
|
||||||
import net.hareworks.ghostdisplays.display.DisplayManager.DisplayOperationException
|
import net.hareworks.ghostdisplays.display.DisplayManager.DisplayOperationException
|
||||||
import net.hareworks.ghostdisplays.display.DisplayKind
|
import net.hareworks.ghostdisplays.display.DisplayKind
|
||||||
|
import net.hareworks.ghostdisplays.display.EditSessionManager
|
||||||
import net.hareworks.kommand_lib.KommandLib
|
import net.hareworks.kommand_lib.KommandLib
|
||||||
import net.hareworks.kommand_lib.kommand
|
import net.hareworks.kommand_lib.kommand
|
||||||
import net.hareworks.permits_lib.bukkit.MutationSession
|
import net.hareworks.permits_lib.bukkit.MutationSession
|
||||||
|
|
@ -23,6 +24,7 @@ object CommandRegistrar {
|
||||||
fun register(
|
fun register(
|
||||||
plugin: JavaPlugin,
|
plugin: JavaPlugin,
|
||||||
manager: DisplayManager,
|
manager: DisplayManager,
|
||||||
|
editSessions: EditSessionManager,
|
||||||
permissionSession: MutationSession
|
permissionSession: MutationSession
|
||||||
): KommandLib =
|
): KommandLib =
|
||||||
kommand(plugin) {
|
kommand(plugin) {
|
||||||
|
|
@ -52,7 +54,8 @@ object CommandRegistrar {
|
||||||
val location = player.anchorLocation()
|
val location = player.anchorLocation()
|
||||||
try {
|
try {
|
||||||
val display = manager.createTextDisplay(id, location, "", player)
|
val display = manager.createTextDisplay(id, location, "", player)
|
||||||
sender.success("Text display '${display.id}' created at ${describe(location)}. Use /ghostdisplay text set ${display.id} <content> to set text.")
|
editSessions.begin(player, display.id)
|
||||||
|
sender.success("Text display '${display.id}' created at ${describe(location)}. Type text in chat (or 'cancel') to set content.")
|
||||||
} catch (ex: DisplayOperationException) {
|
} catch (ex: DisplayOperationException) {
|
||||||
sender.failure(ex.message ?: "Failed to create text display.")
|
sender.failure(ex.message ?: "Failed to create text display.")
|
||||||
}
|
}
|
||||||
|
|
@ -219,6 +222,27 @@ object CommandRegistrar {
|
||||||
}
|
}
|
||||||
|
|
||||||
literal("text") {
|
literal("text") {
|
||||||
|
literal("edit") {
|
||||||
|
string("id") {
|
||||||
|
suggests { prefix ->
|
||||||
|
manager.listDisplays()
|
||||||
|
.filter { it.kind == DisplayKind.TEXT }
|
||||||
|
.map { it.id }
|
||||||
|
.filter { it.startsWith(prefix, ignoreCase = true) }
|
||||||
|
}
|
||||||
|
executes {
|
||||||
|
val player = sender.requirePlayer() ?: return@executes
|
||||||
|
val id = argument<String>("id")
|
||||||
|
val display = manager.findDisplay(id)
|
||||||
|
if (display == null || display.kind != DisplayKind.TEXT) {
|
||||||
|
sender.failure("Text display '$id' not found.")
|
||||||
|
return@executes
|
||||||
|
}
|
||||||
|
editSessions.begin(player, display.id)
|
||||||
|
sender.info("Enter new MiniMessage text in chat for '${display.id}'. Type 'cancel' to abort.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
literal("set") {
|
literal("set") {
|
||||||
string("id") {
|
string("id") {
|
||||||
suggests { prefix ->
|
suggests { prefix ->
|
||||||
|
|
@ -234,7 +258,7 @@ object CommandRegistrar {
|
||||||
val raw = token.replace('_', ' ').replace("\\n", "\n")
|
val raw = token.replace('_', ' ').replace("\\n", "\n")
|
||||||
try {
|
try {
|
||||||
manager.updateText(id, raw)
|
manager.updateText(id, raw)
|
||||||
sender.success("Updated text for '$id'.")
|
sender.success("Updated text for '$id'. Use /ghostdisplay text edit $id for multi-line content.")
|
||||||
} catch (ex: DisplayOperationException) {
|
} catch (ex: DisplayOperationException) {
|
||||||
sender.failure(ex.message ?: "Failed to update text.")
|
sender.failure(ex.message ?: "Failed to update text.")
|
||||||
}
|
}
|
||||||
|
|
@ -242,6 +266,16 @@ object CommandRegistrar {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
literal("cancel") {
|
||||||
|
executes {
|
||||||
|
val player = sender.requirePlayer() ?: return@executes
|
||||||
|
if (editSessions.cancel(player)) {
|
||||||
|
sender.success("Editing session cancelled.")
|
||||||
|
} else {
|
||||||
|
sender.failure("You are not editing any display.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
literal("block") {
|
literal("block") {
|
||||||
|
|
@ -497,6 +531,7 @@ object CommandRegistrar {
|
||||||
private fun CommandSender.showUsage() {
|
private fun CommandSender.showUsage() {
|
||||||
info("GhostDisplays commands:")
|
info("GhostDisplays commands:")
|
||||||
info(" /ghostdisplay create <text|block|item> ...")
|
info(" /ghostdisplay create <text|block|item> ...")
|
||||||
|
info(" /ghostdisplay text edit <id> - edit text via chat")
|
||||||
info(" /ghostdisplay viewer <add|remove|clear> ...")
|
info(" /ghostdisplay viewer <add|remove|clear> ...")
|
||||||
info(" /ghostdisplay audience <permission|near|clear> ...")
|
info(" /ghostdisplay audience <permission|near|clear> ...")
|
||||||
info(" /ghostdisplay list | info <id> | delete <id>")
|
info(" /ghostdisplay list | info <id> | delete <id>")
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
package net.hareworks.ghostdisplays.display
|
||||||
|
|
||||||
|
import java.util.UUID
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import net.hareworks.ghostdisplays.display.DisplayManager.DisplayOperationException
|
||||||
|
import org.bukkit.Bukkit
|
||||||
|
import org.bukkit.entity.Player
|
||||||
|
import org.bukkit.event.EventHandler
|
||||||
|
import org.bukkit.event.Listener
|
||||||
|
import org.bukkit.event.player.PlayerQuitEvent
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin
|
||||||
|
|
||||||
|
class EditSessionManager(
|
||||||
|
private val plugin: JavaPlugin,
|
||||||
|
private val manager: DisplayManager
|
||||||
|
) : Listener {
|
||||||
|
private val sessions = ConcurrentHashMap<UUID, String>()
|
||||||
|
|
||||||
|
fun begin(player: Player, displayId: String) {
|
||||||
|
sessions[player.uniqueId] = displayId
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cancel(player: Player): Boolean = sessions.remove(player.uniqueId) != null
|
||||||
|
|
||||||
|
fun shutdown() {
|
||||||
|
sessions.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(ignoreCancelled = true)
|
||||||
|
fun onPlayerChat(event: io.papermc.paper.event.player.AsyncChatEvent) {
|
||||||
|
val displayId = sessions[event.player.uniqueId] ?: return
|
||||||
|
event.isCancelled = true
|
||||||
|
val serializer = net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer.plainText()
|
||||||
|
val message = serializer.serialize(event.message())
|
||||||
|
|
||||||
|
if (message.equals("cancel", ignoreCase = true)) {
|
||||||
|
sessions.remove(event.player.uniqueId)
|
||||||
|
event.player.sendMessage("GhostDisplays: editing for '$displayId' cancelled.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Bukkit.getScheduler().runTask(plugin, Runnable {
|
||||||
|
try {
|
||||||
|
manager.updateText(displayId, message)
|
||||||
|
event.player.sendMessage("GhostDisplays: updated text for '$displayId'.")
|
||||||
|
} catch (ex: DisplayOperationException) {
|
||||||
|
event.player.sendMessage("GhostDisplays: ${ex.message}")
|
||||||
|
} finally {
|
||||||
|
sessions.remove(event.player.uniqueId)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
fun onPlayerQuit(event: PlayerQuitEvent) {
|
||||||
|
sessions.remove(event.player.uniqueId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -32,17 +32,11 @@ internal class BaseDisplayController<T : Display>(
|
||||||
private val viewerCounts = ConcurrentHashMap<UUID, Int>()
|
private val viewerCounts = ConcurrentHashMap<UUID, Int>()
|
||||||
private val handlers = CopyOnWriteArrayList<HandlerEntry>()
|
private val handlers = CopyOnWriteArrayList<HandlerEntry>()
|
||||||
|
|
||||||
// Audience Management
|
|
||||||
// Audience Management
|
// Audience Management
|
||||||
private var baseVisibility: Boolean = false
|
private var baseVisibility: Boolean = false
|
||||||
private val audienceRules = CopyOnWriteArrayList<RuleEntry>()
|
private val audienceRules = CopyOnWriteArrayList<RuleEntry>()
|
||||||
private val autoVisiblePlayers = ConcurrentHashMap.newKeySet<UUID>()
|
private val autoVisiblePlayers = ConcurrentHashMap.newKeySet<UUID>()
|
||||||
|
|
||||||
// 最適化: 定期更新が必要なルールがあるか
|
|
||||||
private var hasDynamicRules: Boolean = false
|
|
||||||
|
|
||||||
override fun needsPeriodicUpdate(): Boolean = hasDynamicRules
|
|
||||||
|
|
||||||
override fun show(player: Player) {
|
override fun show(player: Player) {
|
||||||
runSync {
|
runSync {
|
||||||
val uuid = player.uniqueId
|
val uuid = player.uniqueId
|
||||||
|
|
@ -116,32 +110,13 @@ internal class BaseDisplayController<T : Display>(
|
||||||
override fun addAudienceRule(predicate: AudiencePredicate, action: AudienceAction): HandlerRegistration {
|
override fun addAudienceRule(predicate: AudiencePredicate, action: AudienceAction): HandlerRegistration {
|
||||||
val entry = RuleEntry(predicate, action)
|
val entry = RuleEntry(predicate, action)
|
||||||
audienceRules.add(entry)
|
audienceRules.add(entry)
|
||||||
updateDynamicStatus()
|
|
||||||
refreshAudience()
|
refreshAudience()
|
||||||
return HandlerRegistration {
|
return HandlerRegistration {
|
||||||
audienceRules.remove(entry)
|
audienceRules.remove(entry)
|
||||||
updateDynamicStatus()
|
|
||||||
refreshAudience()
|
refreshAudience()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateDynamicStatus() {
|
|
||||||
// 簡易判定: クラス名に "Near" が含まれる、または特定の実装であることを期待する
|
|
||||||
// ただし現状はラムダなので名前判定は不安定。
|
|
||||||
// AudiencePredicate 側にマーカーインターフェースをつけるのが正しいが、
|
|
||||||
// 今回は「ユーザー定義のPredicate」も含めて、動的かどうかわからない。
|
|
||||||
// -> AudiencePredicates.near() が返すオブジェクトに特徴を持たせる。
|
|
||||||
hasDynamicRules = audienceRules.any { isDynamicPredicate(it.predicate) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun isDynamicPredicate(predicate: AudiencePredicate): Boolean {
|
|
||||||
// AudiencePredicates.near が返すクラスの実装詳細に依存するか、
|
|
||||||
// AudiencePredicate にプロパティを追加する。
|
|
||||||
// ここでは一旦、toString() 等で識別するか?いや、安全ではない。
|
|
||||||
// API変更: AudiencePredicate に default isDynamic() を追加する。
|
|
||||||
return predicate.isDynamic()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun clearAudienceRules() {
|
override fun clearAudienceRules() {
|
||||||
audienceRules.clear()
|
audienceRules.clear()
|
||||||
refreshAudience()
|
refreshAudience()
|
||||||
|
|
|
||||||
|
|
@ -82,18 +82,16 @@ internal class DisplayRegistry : Listener {
|
||||||
controllers.forEach { it.refreshAudience(player) }
|
controllers.forEach { it.refreshAudience(player) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateAudiences() {
|
fun tick() {
|
||||||
val players = org.bukkit.Bukkit.getOnlinePlayers()
|
val players = org.bukkit.Bukkit.getOnlinePlayers()
|
||||||
if (players.isEmpty()) return
|
if (players.isEmpty()) return
|
||||||
val controllers = controllersSnapshot()
|
val controllers = controllersSnapshot()
|
||||||
if (controllers.isEmpty()) return
|
if (controllers.isEmpty()) return
|
||||||
|
|
||||||
// 動的な評価が必要なコントローラーだけを抽出して評価する
|
// 最適化: 動的な評価が必要なコントローラーだけ抽出できれば良いが、
|
||||||
val activeControllers = controllers.filter { it.needsPeriodicUpdate() }
|
// 現状はシンプルに全走査する。
|
||||||
if (activeControllers.isEmpty()) return
|
|
||||||
|
|
||||||
players.forEach { player ->
|
players.forEach { player ->
|
||||||
activeControllers.forEach { controller ->
|
controllers.forEach { controller ->
|
||||||
controller.refreshAudience(player)
|
controller.refreshAudience(player)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user