feat: Audienceのタイミング
This commit is contained in:
parent
4a302f5f6f
commit
bc774765c5
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -1,5 +1,3 @@
|
|||
# Ignore Gradle project-specific cache directory
|
||||
.gradle
|
||||
|
||||
# Ignore Gradle build output directory
|
||||
bin
|
||||
build
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 559341c0415699a2b39a1a523f4ef4ed93ce3fdc
|
||||
Subproject commit 9af293122b860d8c65d68a2bdc02a9c2c5e206cc
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package net.hareworks.ghostdisplays.api
|
||||
|
||||
import net.hareworks.ghostdisplays.api.audience.AudienceAction
|
||||
import net.hareworks.ghostdisplays.api.audience.AudiencePredicate
|
||||
import net.hareworks.ghostdisplays.api.click.ClickPriority
|
||||
import net.hareworks.ghostdisplays.api.click.DisplayClickHandler
|
||||
|
|
@ -23,8 +24,24 @@ interface DisplayController<T : Display> {
|
|||
|
||||
fun applyEntityUpdate(mutator: (T) -> Unit)
|
||||
|
||||
/**
|
||||
* Displayの基本可視状態を設定します。
|
||||
* ルールにマッチしなかった場合のデフォルトの振る舞いとなります。
|
||||
*/
|
||||
fun setBaseVisibility(visible: Boolean)
|
||||
|
||||
/**
|
||||
* 従来の簡易メソッド。Action=ADD として登録します。
|
||||
*/
|
||||
fun addAudience(predicate: AudiencePredicate): HandlerRegistration
|
||||
|
||||
/**
|
||||
* ルールを追加します。評価順序は追加順です。
|
||||
*/
|
||||
fun addAudienceRule(predicate: AudiencePredicate, action: AudienceAction): HandlerRegistration
|
||||
|
||||
fun clearAudienceRules()
|
||||
|
||||
fun refreshAudience(target: Player? = null)
|
||||
|
||||
fun destroy()
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package net.hareworks.ghostdisplays.api.audience
|
||||
|
||||
/**
|
||||
* Predicateの評価結果に対するアクション。
|
||||
*/
|
||||
enum class AudienceAction {
|
||||
/**
|
||||
* 表示対象に追加する (Visible = true)
|
||||
*/
|
||||
ADD,
|
||||
|
||||
/**
|
||||
* 表示対象から除外する (Visible = false)
|
||||
*/
|
||||
EXCLUDE
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package net.hareworks.ghostdisplays.api.audience
|
||||
|
||||
|
||||
import org.bukkit.Location
|
||||
import org.bukkit.entity.Player
|
||||
import java.util.UUID
|
||||
|
|
@ -19,7 +20,15 @@ object AudiencePredicates {
|
|||
player.uniqueId == target
|
||||
}
|
||||
|
||||
fun players(targets: Collection<UUID>): AudiencePredicate {
|
||||
val set = targets.toSet()
|
||||
return AudiencePredicate { player ->
|
||||
player.uniqueId in set
|
||||
}
|
||||
}
|
||||
|
||||
fun near(location: Location, radius: Double): AudiencePredicate {
|
||||
|
||||
val radiusSq = radius * radius
|
||||
val worldName = location.world?.name
|
||||
require(worldName != null) { "Location must have a world" }
|
||||
|
|
@ -111,6 +111,9 @@ object CommandRegistrar {
|
|||
|
||||
literal("delete") {
|
||||
string("id") {
|
||||
suggests { prefix ->
|
||||
manager.listDisplays().map { it.id }.filter { it.startsWith(prefix, ignoreCase = true) }
|
||||
}
|
||||
executes {
|
||||
val id = argument<String>("id")
|
||||
try {
|
||||
|
|
@ -139,6 +142,9 @@ object CommandRegistrar {
|
|||
|
||||
literal("info") {
|
||||
string("id") {
|
||||
suggests { prefix ->
|
||||
manager.listDisplays().map { it.id }.filter { it.startsWith(prefix, ignoreCase = true) }
|
||||
}
|
||||
executes {
|
||||
val id = argument<String>("id")
|
||||
val display = manager.findDisplay(id)
|
||||
|
|
@ -161,6 +167,9 @@ object CommandRegistrar {
|
|||
literal("viewer") {
|
||||
literal("add") {
|
||||
string("id") {
|
||||
suggests { prefix ->
|
||||
manager.listDisplays().map { it.id }.filter { it.startsWith(prefix, ignoreCase = true) }
|
||||
}
|
||||
players("targets") {
|
||||
executes {
|
||||
val id = argument<String>("id")
|
||||
|
|
@ -177,6 +186,9 @@ object CommandRegistrar {
|
|||
}
|
||||
literal("remove") {
|
||||
string("id") {
|
||||
suggests { prefix ->
|
||||
manager.listDisplays().map { it.id }.filter { it.startsWith(prefix, ignoreCase = true) }
|
||||
}
|
||||
players("targets") {
|
||||
executes {
|
||||
val id = argument<String>("id")
|
||||
|
|
@ -193,6 +205,9 @@ object CommandRegistrar {
|
|||
}
|
||||
literal("clear") {
|
||||
string("id") {
|
||||
suggests { prefix ->
|
||||
manager.listDisplays().map { it.id }.filter { it.startsWith(prefix, ignoreCase = true) }
|
||||
}
|
||||
executes {
|
||||
val id = argument<String>("id")
|
||||
try {
|
||||
|
|
@ -209,6 +224,12 @@ object CommandRegistrar {
|
|||
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")
|
||||
|
|
@ -224,6 +245,12 @@ object CommandRegistrar {
|
|||
}
|
||||
literal("set") {
|
||||
string("id") {
|
||||
suggests { prefix ->
|
||||
manager.listDisplays()
|
||||
.filter { it.kind == DisplayKind.TEXT }
|
||||
.map { it.id }
|
||||
.filter { it.startsWith(prefix, ignoreCase = true) }
|
||||
}
|
||||
string("content") {
|
||||
executes {
|
||||
val id = argument<String>("id")
|
||||
|
|
@ -254,6 +281,12 @@ object CommandRegistrar {
|
|||
literal("block") {
|
||||
literal("set") {
|
||||
string("id") {
|
||||
suggests { prefix ->
|
||||
manager.listDisplays()
|
||||
.filter { it.kind == DisplayKind.BLOCK }
|
||||
.map { it.id }
|
||||
.filter { it.startsWith(prefix, ignoreCase = true) }
|
||||
}
|
||||
string("state") {
|
||||
executes {
|
||||
val id = argument<String>("id")
|
||||
|
|
@ -276,6 +309,12 @@ object CommandRegistrar {
|
|||
literal("item") {
|
||||
literal("set") {
|
||||
string("id") {
|
||||
suggests { prefix ->
|
||||
manager.listDisplays()
|
||||
.filter { it.kind == DisplayKind.ITEM }
|
||||
.map { it.id }
|
||||
.filter { it.startsWith(prefix, ignoreCase = true) }
|
||||
}
|
||||
string("material") {
|
||||
executes {
|
||||
val id = argument<String>("id")
|
||||
|
|
@ -298,25 +337,67 @@ object CommandRegistrar {
|
|||
}
|
||||
|
||||
literal("audience") {
|
||||
literal("base") {
|
||||
string("id") {
|
||||
suggests { prefix ->
|
||||
manager.listDisplays().map { it.id }.filter { it.startsWith(prefix, ignoreCase = true) }
|
||||
}
|
||||
bool("visible") {
|
||||
executes {
|
||||
val id = argument<String>("id")
|
||||
val visible = argument<Boolean>("visible")
|
||||
try {
|
||||
manager.setBaseVisibility(id, visible)
|
||||
sender.success("Base visibility for '$id' set to $visible.")
|
||||
} catch (ex: DisplayOperationException) {
|
||||
sender.failure(ex.message ?: "Failed to set base visibility.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
literal("permission") {
|
||||
literal("add") {
|
||||
string("id") {
|
||||
suggests { prefix ->
|
||||
manager.listDisplays().map { it.id }.filter { it.startsWith(prefix, ignoreCase = true) }
|
||||
}
|
||||
string("permission") {
|
||||
executes {
|
||||
val id = argument<String>("id")
|
||||
val perm = argument<String>("permission")
|
||||
try {
|
||||
manager.addPermissionAudience(id, perm)
|
||||
sender.success("Permission audience '$perm' added to '$id'.")
|
||||
sender.success("Permission audience '$perm' added to '$id' (ADD).")
|
||||
} catch (ex: DisplayOperationException) {
|
||||
sender.failure(ex.message ?: "Failed to add permission audience.")
|
||||
}
|
||||
}
|
||||
string("action") {
|
||||
suggests { listOf("ADD", "EXCLUDE") }
|
||||
executes {
|
||||
val id = argument<String>("id")
|
||||
val perm = argument<String>("permission")
|
||||
val actionName = argument<String>("action").uppercase()
|
||||
val action = runCatching { net.hareworks.ghostdisplays.api.audience.AudienceAction.valueOf(actionName) }
|
||||
.getOrElse { return@executes sender.failure("Invalid action '$actionName'. Use ADD or EXCLUDE.") }
|
||||
try {
|
||||
manager.addPermissionAudience(id, perm, action)
|
||||
sender.success("Permission audience '$perm' added to '$id' ($action).")
|
||||
} catch (ex: DisplayOperationException) {
|
||||
sender.failure(ex.message ?: "Failed to add permission audience.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
literal("remove") {
|
||||
string("id") {
|
||||
suggests { prefix ->
|
||||
manager.listDisplays().map { it.id }.filter { it.startsWith(prefix, ignoreCase = true) }
|
||||
}
|
||||
string("permission") {
|
||||
executes {
|
||||
val id = argument<String>("id")
|
||||
|
|
@ -337,23 +418,100 @@ object CommandRegistrar {
|
|||
literal("near") {
|
||||
literal("set") {
|
||||
string("id") {
|
||||
suggests { prefix ->
|
||||
manager.listDisplays().map { it.id }.filter { it.startsWith(prefix, ignoreCase = true) }
|
||||
}
|
||||
float("radius", min = 1.0) {
|
||||
executes {
|
||||
val id = argument<String>("id")
|
||||
val radius = argument<Double>("radius")
|
||||
try {
|
||||
manager.setNearAudience(id, radius)
|
||||
sender.success("Radius audience for '$id' set to ${String.format("%.1f", radius)} block(s).")
|
||||
sender.success("Radius audience for '$id' set to ${String.format("%.1f", radius)} block(s) (ADD).")
|
||||
} catch (ex: DisplayOperationException) {
|
||||
sender.failure(ex.message ?: "Failed to update radius audience.")
|
||||
}
|
||||
}
|
||||
string("action") {
|
||||
suggests { listOf("ADD", "EXCLUDE") }
|
||||
executes {
|
||||
val id = argument<String>("id")
|
||||
val radius = argument<Double>("radius")
|
||||
val actionName = argument<String>("action").uppercase()
|
||||
val action = runCatching { net.hareworks.ghostdisplays.api.audience.AudienceAction.valueOf(actionName) }
|
||||
.getOrElse { return@executes sender.failure("Invalid action '$actionName'. Use ADD or EXCLUDE.") }
|
||||
try {
|
||||
manager.setNearAudience(id, radius, action)
|
||||
sender.success("Radius audience for '$id' set to ${String.format("%.1f", radius)} block(s) ($action).")
|
||||
} catch (ex: DisplayOperationException) {
|
||||
sender.failure(ex.message ?: "Failed to update radius audience.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
literal("player") {
|
||||
literal("add") {
|
||||
string("id") {
|
||||
suggests { prefix ->
|
||||
manager.listDisplays().map { it.id }.filter { it.startsWith(prefix, ignoreCase = true) }
|
||||
}
|
||||
players("targets") {
|
||||
executes {
|
||||
val id = argument<String>("id")
|
||||
val targets = argument<List<Player>>("targets")
|
||||
try {
|
||||
targets.forEach { manager.addPlayerAudience(id, it.uniqueId, it.name) }
|
||||
sender.success("Added ${targets.size} player(s) to audience of '$id' (ADD).")
|
||||
} catch (ex: DisplayOperationException) {
|
||||
sender.failure(ex.message ?: "Failed to add player audience.")
|
||||
}
|
||||
}
|
||||
string("action") {
|
||||
suggests { listOf("ADD", "EXCLUDE") }
|
||||
executes {
|
||||
val id = argument<String>("id")
|
||||
val targets = argument<List<Player>>("targets")
|
||||
val actionName = argument<String>("action").uppercase()
|
||||
val action = runCatching { net.hareworks.ghostdisplays.api.audience.AudienceAction.valueOf(actionName) }
|
||||
.getOrElse { return@executes sender.failure("Invalid action '$actionName'. Use ADD or EXCLUDE.") }
|
||||
try {
|
||||
targets.forEach { manager.addPlayerAudience(id, it.uniqueId, it.name, action) }
|
||||
sender.success("Added ${targets.size} player(s) to audience of '$id' ($action).")
|
||||
} catch (ex: DisplayOperationException) {
|
||||
sender.failure(ex.message ?: "Failed to add player audience.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
literal("remove") {
|
||||
string("id") {
|
||||
suggests { prefix ->
|
||||
manager.listDisplays().map { it.id }.filter { it.startsWith(prefix, ignoreCase = true) }
|
||||
}
|
||||
players("targets") {
|
||||
executes {
|
||||
val id = argument<String>("id")
|
||||
val targets = argument<List<Player>>("targets")
|
||||
var count = 0
|
||||
targets.forEach {
|
||||
if (manager.removePlayerAudience(id, it.uniqueId)) count++
|
||||
}
|
||||
sender.success("Removed $count/${targets.size} player(s) from audience of '$id'.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
literal("clear") {
|
||||
string("id") {
|
||||
suggests { prefix ->
|
||||
manager.listDisplays().map { it.id }.filter { it.startsWith(prefix, ignoreCase = true) }
|
||||
}
|
||||
executes {
|
||||
val id = argument<String>("id")
|
||||
try {
|
||||
|
|
@ -379,8 +537,11 @@ private fun CommandSender.showUsage() {
|
|||
info(" /ghostdisplay list | info <id> | delete <id>")
|
||||
}
|
||||
|
||||
private fun Player.anchorLocation(): Location =
|
||||
eyeLocation.clone().add(direction.normalize().multiply(1.5))
|
||||
private fun Player.anchorLocation(): Location {
|
||||
val eye = eyeLocation.clone()
|
||||
val forward = eye.direction.normalize().multiply(1.5)
|
||||
return eye.add(forward)
|
||||
}
|
||||
|
||||
private fun parseBlockData(state: String): BlockData =
|
||||
Bukkit.createBlockData(state)
|
||||
|
|
@ -6,6 +6,7 @@ import java.util.concurrent.ConcurrentHashMap
|
|||
import java.util.logging.Logger
|
||||
import net.hareworks.ghostdisplays.api.DisplayService
|
||||
import net.hareworks.ghostdisplays.api.InteractionOptions
|
||||
import net.hareworks.ghostdisplays.api.audience.AudienceAction
|
||||
import net.hareworks.ghostdisplays.api.audience.AudiencePredicates
|
||||
import net.hareworks.ghostdisplays.display.DisplayManager.DisplayOperationException
|
||||
import net.kyori.adventure.text.Component
|
||||
|
|
@ -79,7 +80,7 @@ class DisplayManager(
|
|||
ensureIdAvailable(normalized)
|
||||
val safeLocation = location.clone()
|
||||
val controller = service.createItemDisplay(safeLocation, itemStack.clone(), INTERACTION_DEFAULT)
|
||||
controller.applyEntityUpdate { it.itemStack = itemStack.clone() }
|
||||
controller.applyEntityUpdate { it.setItemStack(itemStack.clone()) }
|
||||
val managed = ManagedDisplay.Item(
|
||||
id = normalized,
|
||||
controller = controller,
|
||||
|
|
@ -115,7 +116,7 @@ class DisplayManager(
|
|||
fun updateItem(id: String, itemStack: ItemStack) {
|
||||
val display = requireItem(id)
|
||||
val clone = itemStack.clone()
|
||||
display.controller.applyEntityUpdate { it.itemStack = clone }
|
||||
display.controller.applyEntityUpdate { it.setItemStack(clone) }
|
||||
display.itemStack = clone
|
||||
}
|
||||
|
||||
|
|
@ -142,17 +143,22 @@ class DisplayManager(
|
|||
viewers.forEach { display.controller.hide(it) }
|
||||
}
|
||||
|
||||
fun addPermissionAudience(id: String, permission: String) {
|
||||
fun setBaseVisibility(id: String, visible: Boolean) {
|
||||
val display = requireDisplay(id)
|
||||
display.controller.setBaseVisibility(visible)
|
||||
}
|
||||
|
||||
fun addPermissionAudience(id: String, permission: String, action: AudienceAction = AudienceAction.ADD) {
|
||||
val display = requireDisplay(id)
|
||||
val key = "perm:${permission.lowercase()}"
|
||||
if (display.removeAudienceBinding(key)) {
|
||||
logger.info("Replacing permission audience '$permission' for $id")
|
||||
}
|
||||
val registration = display.controller.addAudience(AudiencePredicates.permission(permission))
|
||||
val registration = display.controller.addAudienceRule(AudiencePredicates.permission(permission), action)
|
||||
display.registerAudienceBinding(
|
||||
AudienceBinding(
|
||||
key = key,
|
||||
description = "permission:$permission",
|
||||
description = "permission:$permission [${action.name}]",
|
||||
registration = registration
|
||||
)
|
||||
)
|
||||
|
|
@ -164,24 +170,45 @@ class DisplayManager(
|
|||
return display.removeAudienceBinding(key)
|
||||
}
|
||||
|
||||
fun setNearAudience(id: String, radius: Double) {
|
||||
fun setNearAudience(id: String, radius: Double, action: AudienceAction = AudienceAction.ADD) {
|
||||
require(radius > 0) { "Radius must be positive." }
|
||||
val display = requireDisplay(id)
|
||||
val key = "near"
|
||||
display.removeAudienceBinding(key)
|
||||
val registration = display.controller.addAudience(AudiencePredicates.near(display.location, radius))
|
||||
val registration = display.controller.addAudienceRule(AudiencePredicates.near(display.location, radius), action)
|
||||
display.registerAudienceBinding(
|
||||
AudienceBinding(
|
||||
key = key,
|
||||
description = "radius:${String.format("%.2f", radius)}",
|
||||
description = "radius:${String.format("%.2f", radius)} [${action.name}]",
|
||||
registration = registration
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun addPlayerAudience(id: String, playerId: UUID, playerName: String, action: AudienceAction = AudienceAction.ADD) {
|
||||
val display = requireDisplay(id)
|
||||
val key = "player:$playerId"
|
||||
display.removeAudienceBinding(key)
|
||||
val registration = display.controller.addAudienceRule(AudiencePredicates.uuid(playerId), action)
|
||||
display.registerAudienceBinding(
|
||||
AudienceBinding(
|
||||
key = key,
|
||||
description = "player:$playerName [${action.name}]",
|
||||
registration = registration
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun removePlayerAudience(id: String, playerId: UUID): Boolean {
|
||||
val display = requireDisplay(id)
|
||||
val key = "player:$playerId"
|
||||
return display.removeAudienceBinding(key)
|
||||
}
|
||||
|
||||
fun clearAudiences(id: String) {
|
||||
val display = requireDisplay(id)
|
||||
display.clearAudiences()
|
||||
display.controller.clearAudienceRules()
|
||||
}
|
||||
|
||||
fun destroyAll() {
|
||||
|
|
@ -37,7 +37,7 @@ class EditSessionManager(
|
|||
event.player.sendMessage("GhostDisplays: editing for '$displayId' cancelled.")
|
||||
return
|
||||
}
|
||||
Bukkit.getScheduler().runTask(plugin) {
|
||||
Bukkit.getScheduler().runTask(plugin, Runnable {
|
||||
try {
|
||||
manager.updateText(displayId, message)
|
||||
event.player.sendMessage("GhostDisplays: updated text for '$displayId'.")
|
||||
|
|
@ -46,7 +46,7 @@ class EditSessionManager(
|
|||
} finally {
|
||||
sessions.remove(event.player.uniqueId)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
|
|
@ -46,7 +46,7 @@ internal class DefaultDisplayService(
|
|||
interaction: InteractionOptions,
|
||||
builder: ItemDisplay.() -> Unit
|
||||
): DisplayController<ItemDisplay> = spawnDisplay(location, ItemDisplay::class.java, interaction) {
|
||||
it.itemStack = itemStack.clone()
|
||||
it.setItemStack(itemStack.clone())
|
||||
builder(it)
|
||||
}
|
||||
|
||||
|
|
@ -94,7 +94,7 @@ internal class DefaultDisplayService(
|
|||
action()
|
||||
} else {
|
||||
val future = CompletableFuture<T>()
|
||||
Bukkit.getScheduler().runTask(plugin) { future.complete(action()) }
|
||||
Bukkit.getScheduler().runTask(plugin, Runnable { future.complete(action()) })
|
||||
future.join()
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import java.util.concurrent.ConcurrentHashMap
|
|||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import net.hareworks.ghostdisplays.api.DisplayController
|
||||
import net.hareworks.ghostdisplays.api.audience.AudienceAction
|
||||
import net.hareworks.ghostdisplays.api.audience.AudiencePredicate
|
||||
import net.hareworks.ghostdisplays.api.click.ClickPriority
|
||||
import net.hareworks.ghostdisplays.api.click.ClickSurface
|
||||
|
|
@ -30,7 +31,11 @@ internal class BaseDisplayController<T : Display>(
|
|||
private val destroyed = AtomicBoolean(false)
|
||||
private val viewerCounts = ConcurrentHashMap<UUID, Int>()
|
||||
private val handlers = CopyOnWriteArrayList<HandlerEntry>()
|
||||
private val audiences = CopyOnWriteArrayList<AudienceBindingImpl>()
|
||||
|
||||
// Audience Management
|
||||
private var baseVisibility: Boolean = false
|
||||
private val audienceRules = CopyOnWriteArrayList<RuleEntry>()
|
||||
private val autoVisiblePlayers = ConcurrentHashMap.newKeySet<UUID>()
|
||||
|
||||
override fun show(player: Player) {
|
||||
runSync {
|
||||
|
|
@ -64,7 +69,6 @@ internal class BaseDisplayController<T : Display>(
|
|||
override fun applyEntityUpdate(mutator: (T) -> Unit) {
|
||||
callSync {
|
||||
mutator(display)
|
||||
display.updateDisplay(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -82,8 +86,8 @@ internal class BaseDisplayController<T : Display>(
|
|||
viewerCounts.clear()
|
||||
}
|
||||
handlers.clear()
|
||||
audiences.forEach { it.clear() }
|
||||
audiences.clear()
|
||||
audienceRules.clear()
|
||||
autoVisiblePlayers.clear()
|
||||
}
|
||||
|
||||
override fun onClick(priority: ClickPriority, handler: DisplayClickHandler): HandlerRegistration {
|
||||
|
|
@ -94,26 +98,67 @@ internal class BaseDisplayController<T : Display>(
|
|||
}
|
||||
}
|
||||
|
||||
override fun addAudience(predicate: AudiencePredicate): HandlerRegistration {
|
||||
val binding = AudienceBindingImpl(predicate)
|
||||
audiences += binding
|
||||
refreshAudienceInternal(binding = binding)
|
||||
return HandlerRegistration { binding.unregister() }
|
||||
override fun setBaseVisibility(visible: Boolean) {
|
||||
this.baseVisibility = visible
|
||||
refreshAudience()
|
||||
}
|
||||
|
||||
private fun refreshAudienceInternal(target: Player? = null, binding: AudienceBindingImpl? = null) {
|
||||
override fun addAudience(predicate: AudiencePredicate): HandlerRegistration {
|
||||
return addAudienceRule(predicate, AudienceAction.ADD)
|
||||
}
|
||||
|
||||
override fun addAudienceRule(predicate: AudiencePredicate, action: AudienceAction): HandlerRegistration {
|
||||
val entry = RuleEntry(predicate, action)
|
||||
audienceRules.add(entry)
|
||||
refreshAudience()
|
||||
return HandlerRegistration {
|
||||
audienceRules.remove(entry)
|
||||
refreshAudience()
|
||||
}
|
||||
}
|
||||
|
||||
override fun clearAudienceRules() {
|
||||
audienceRules.clear()
|
||||
refreshAudience()
|
||||
}
|
||||
|
||||
override fun refreshAudience(target: Player?) {
|
||||
refreshAudienceInternal(target)
|
||||
}
|
||||
|
||||
private fun refreshAudienceInternal(target: Player? = null) {
|
||||
runSync {
|
||||
val players = target?.let { listOf(it) } ?: Bukkit.getOnlinePlayers()
|
||||
val targets = binding?.let { listOf(it) } ?: audiences
|
||||
if (players.isEmpty() || targets.isEmpty()) return@runSync
|
||||
if (players.isEmpty()) return@runSync
|
||||
|
||||
players.forEach { player ->
|
||||
targets.forEach { it.evaluate(player) }
|
||||
val uuid = player.uniqueId
|
||||
val shouldBeVisible = evaluateVisibility(player)
|
||||
val isCurrentlyAutoVisible = autoVisiblePlayers.contains(uuid)
|
||||
|
||||
if (shouldBeVisible && !isCurrentlyAutoVisible) {
|
||||
autoVisiblePlayers.add(uuid)
|
||||
show(player)
|
||||
} else if (!shouldBeVisible && isCurrentlyAutoVisible) {
|
||||
autoVisiblePlayers.remove(uuid)
|
||||
hide(player)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun refreshAudience(target: Player?) {
|
||||
refreshAudienceInternal(target, null)
|
||||
private fun evaluateVisibility(player: Player): Boolean {
|
||||
var visible = baseVisibility
|
||||
for (rule in audienceRules) {
|
||||
val matches = runCatching { rule.predicate.test(player) }.getOrDefault(false)
|
||||
if (matches) {
|
||||
when (rule.action) {
|
||||
AudienceAction.ADD -> visible = true
|
||||
AudienceAction.EXCLUDE -> visible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
return visible
|
||||
}
|
||||
|
||||
internal fun handleClick(event: PlayerInteractEntityEvent, clicked: Entity, surface: ClickSurface) {
|
||||
|
|
@ -136,14 +181,17 @@ internal class BaseDisplayController<T : Display>(
|
|||
internal fun handlePlayerQuit(player: Player) {
|
||||
val uuid = player.uniqueId
|
||||
viewerCounts.remove(uuid)
|
||||
audiences.forEach { it.forget(uuid) }
|
||||
autoVisiblePlayers.remove(uuid)
|
||||
// Note: ref count logic doesn't strictly need persistent cleanup if we remove from counts,
|
||||
// but removing from autoVisiblePlayers ensures we don't think we're showing it if they rejoin?
|
||||
// Actually, if they quit, we should probably clear for them.
|
||||
}
|
||||
|
||||
private fun runSync(action: () -> Unit) {
|
||||
if (Bukkit.isPrimaryThread()) {
|
||||
action()
|
||||
} else {
|
||||
Bukkit.getScheduler().runTask(plugin, action)
|
||||
Bukkit.getScheduler().runTask(plugin, Runnable { action() })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -152,45 +200,15 @@ internal class BaseDisplayController<T : Display>(
|
|||
action()
|
||||
} else {
|
||||
val future = CompletableFuture<R>()
|
||||
Bukkit.getScheduler().runTask(plugin) { future.complete(action()) }
|
||||
Bukkit.getScheduler().runTask(plugin, Runnable { future.complete(action()) })
|
||||
future.join()
|
||||
}
|
||||
}
|
||||
|
||||
private inner class AudienceBindingImpl(
|
||||
private val predicate: AudiencePredicate
|
||||
) {
|
||||
private val activeViewers = ConcurrentHashMap.newKeySet<UUID>()
|
||||
|
||||
fun evaluate(player: Player) {
|
||||
val uuid = player.uniqueId
|
||||
val matches = runCatching { predicate.test(player) }.getOrDefault(false)
|
||||
if (matches) {
|
||||
if (activeViewers.add(uuid)) {
|
||||
show(player)
|
||||
}
|
||||
} else {
|
||||
if (activeViewers.remove(uuid)) {
|
||||
hide(player)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun forget(playerId: UUID) {
|
||||
activeViewers.remove(playerId)
|
||||
}
|
||||
|
||||
fun unregister() {
|
||||
audiences.remove(this)
|
||||
val targets = activeViewers.toList()
|
||||
targets.mapNotNull { Bukkit.getPlayer(it) }.forEach { hide(it) }
|
||||
activeViewers.clear()
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
activeViewers.clear()
|
||||
}
|
||||
}
|
||||
private data class RuleEntry(
|
||||
val predicate: AudiencePredicate,
|
||||
val action: AudienceAction
|
||||
)
|
||||
|
||||
private data class HandlerEntry(
|
||||
val priority: ClickPriority,
|
||||
Loading…
Reference in New Issue
Block a user