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
|
.gradle
|
||||||
|
bin
|
||||||
# Ignore Gradle build output directory
|
|
||||||
build
|
build
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 559341c0415699a2b39a1a523f4ef4ed93ce3fdc
|
Subproject commit 9af293122b860d8c65d68a2bdc02a9c2c5e206cc
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package net.hareworks.ghostdisplays.api
|
package net.hareworks.ghostdisplays.api
|
||||||
|
|
||||||
|
import net.hareworks.ghostdisplays.api.audience.AudienceAction
|
||||||
import net.hareworks.ghostdisplays.api.audience.AudiencePredicate
|
import net.hareworks.ghostdisplays.api.audience.AudiencePredicate
|
||||||
import net.hareworks.ghostdisplays.api.click.ClickPriority
|
import net.hareworks.ghostdisplays.api.click.ClickPriority
|
||||||
import net.hareworks.ghostdisplays.api.click.DisplayClickHandler
|
import net.hareworks.ghostdisplays.api.click.DisplayClickHandler
|
||||||
|
|
@ -23,8 +24,24 @@ interface DisplayController<T : Display> {
|
||||||
|
|
||||||
fun applyEntityUpdate(mutator: (T) -> Unit)
|
fun applyEntityUpdate(mutator: (T) -> Unit)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displayの基本可視状態を設定します。
|
||||||
|
* ルールにマッチしなかった場合のデフォルトの振る舞いとなります。
|
||||||
|
*/
|
||||||
|
fun setBaseVisibility(visible: Boolean)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 従来の簡易メソッド。Action=ADD として登録します。
|
||||||
|
*/
|
||||||
fun addAudience(predicate: AudiencePredicate): HandlerRegistration
|
fun addAudience(predicate: AudiencePredicate): HandlerRegistration
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ルールを追加します。評価順序は追加順です。
|
||||||
|
*/
|
||||||
|
fun addAudienceRule(predicate: AudiencePredicate, action: AudienceAction): HandlerRegistration
|
||||||
|
|
||||||
|
fun clearAudienceRules()
|
||||||
|
|
||||||
fun refreshAudience(target: Player? = null)
|
fun refreshAudience(target: Player? = null)
|
||||||
|
|
||||||
fun destroy()
|
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
|
package net.hareworks.ghostdisplays.api.audience
|
||||||
|
|
||||||
|
|
||||||
import org.bukkit.Location
|
import org.bukkit.Location
|
||||||
import org.bukkit.entity.Player
|
import org.bukkit.entity.Player
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
@ -19,7 +20,15 @@ object AudiencePredicates {
|
||||||
player.uniqueId == target
|
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 {
|
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" }
|
||||||
|
|
@ -111,6 +111,9 @@ object CommandRegistrar {
|
||||||
|
|
||||||
literal("delete") {
|
literal("delete") {
|
||||||
string("id") {
|
string("id") {
|
||||||
|
suggests { prefix ->
|
||||||
|
manager.listDisplays().map { it.id }.filter { it.startsWith(prefix, ignoreCase = true) }
|
||||||
|
}
|
||||||
executes {
|
executes {
|
||||||
val id = argument<String>("id")
|
val id = argument<String>("id")
|
||||||
try {
|
try {
|
||||||
|
|
@ -139,6 +142,9 @@ object CommandRegistrar {
|
||||||
|
|
||||||
literal("info") {
|
literal("info") {
|
||||||
string("id") {
|
string("id") {
|
||||||
|
suggests { prefix ->
|
||||||
|
manager.listDisplays().map { it.id }.filter { it.startsWith(prefix, ignoreCase = true) }
|
||||||
|
}
|
||||||
executes {
|
executes {
|
||||||
val id = argument<String>("id")
|
val id = argument<String>("id")
|
||||||
val display = manager.findDisplay(id)
|
val display = manager.findDisplay(id)
|
||||||
|
|
@ -161,6 +167,9 @@ object CommandRegistrar {
|
||||||
literal("viewer") {
|
literal("viewer") {
|
||||||
literal("add") {
|
literal("add") {
|
||||||
string("id") {
|
string("id") {
|
||||||
|
suggests { prefix ->
|
||||||
|
manager.listDisplays().map { it.id }.filter { it.startsWith(prefix, ignoreCase = true) }
|
||||||
|
}
|
||||||
players("targets") {
|
players("targets") {
|
||||||
executes {
|
executes {
|
||||||
val id = argument<String>("id")
|
val id = argument<String>("id")
|
||||||
|
|
@ -177,6 +186,9 @@ object CommandRegistrar {
|
||||||
}
|
}
|
||||||
literal("remove") {
|
literal("remove") {
|
||||||
string("id") {
|
string("id") {
|
||||||
|
suggests { prefix ->
|
||||||
|
manager.listDisplays().map { it.id }.filter { it.startsWith(prefix, ignoreCase = true) }
|
||||||
|
}
|
||||||
players("targets") {
|
players("targets") {
|
||||||
executes {
|
executes {
|
||||||
val id = argument<String>("id")
|
val id = argument<String>("id")
|
||||||
|
|
@ -193,6 +205,9 @@ object CommandRegistrar {
|
||||||
}
|
}
|
||||||
literal("clear") {
|
literal("clear") {
|
||||||
string("id") {
|
string("id") {
|
||||||
|
suggests { prefix ->
|
||||||
|
manager.listDisplays().map { it.id }.filter { it.startsWith(prefix, ignoreCase = true) }
|
||||||
|
}
|
||||||
executes {
|
executes {
|
||||||
val id = argument<String>("id")
|
val id = argument<String>("id")
|
||||||
try {
|
try {
|
||||||
|
|
@ -209,6 +224,12 @@ object CommandRegistrar {
|
||||||
literal("text") {
|
literal("text") {
|
||||||
literal("edit") {
|
literal("edit") {
|
||||||
string("id") {
|
string("id") {
|
||||||
|
suggests { prefix ->
|
||||||
|
manager.listDisplays()
|
||||||
|
.filter { it.kind == DisplayKind.TEXT }
|
||||||
|
.map { it.id }
|
||||||
|
.filter { it.startsWith(prefix, ignoreCase = true) }
|
||||||
|
}
|
||||||
executes {
|
executes {
|
||||||
val player = sender.requirePlayer() ?: return@executes
|
val player = sender.requirePlayer() ?: return@executes
|
||||||
val id = argument<String>("id")
|
val id = argument<String>("id")
|
||||||
|
|
@ -224,6 +245,12 @@ object CommandRegistrar {
|
||||||
}
|
}
|
||||||
literal("set") {
|
literal("set") {
|
||||||
string("id") {
|
string("id") {
|
||||||
|
suggests { prefix ->
|
||||||
|
manager.listDisplays()
|
||||||
|
.filter { it.kind == DisplayKind.TEXT }
|
||||||
|
.map { it.id }
|
||||||
|
.filter { it.startsWith(prefix, ignoreCase = true) }
|
||||||
|
}
|
||||||
string("content") {
|
string("content") {
|
||||||
executes {
|
executes {
|
||||||
val id = argument<String>("id")
|
val id = argument<String>("id")
|
||||||
|
|
@ -254,6 +281,12 @@ object CommandRegistrar {
|
||||||
literal("block") {
|
literal("block") {
|
||||||
literal("set") {
|
literal("set") {
|
||||||
string("id") {
|
string("id") {
|
||||||
|
suggests { prefix ->
|
||||||
|
manager.listDisplays()
|
||||||
|
.filter { it.kind == DisplayKind.BLOCK }
|
||||||
|
.map { it.id }
|
||||||
|
.filter { it.startsWith(prefix, ignoreCase = true) }
|
||||||
|
}
|
||||||
string("state") {
|
string("state") {
|
||||||
executes {
|
executes {
|
||||||
val id = argument<String>("id")
|
val id = argument<String>("id")
|
||||||
|
|
@ -276,6 +309,12 @@ object CommandRegistrar {
|
||||||
literal("item") {
|
literal("item") {
|
||||||
literal("set") {
|
literal("set") {
|
||||||
string("id") {
|
string("id") {
|
||||||
|
suggests { prefix ->
|
||||||
|
manager.listDisplays()
|
||||||
|
.filter { it.kind == DisplayKind.ITEM }
|
||||||
|
.map { it.id }
|
||||||
|
.filter { it.startsWith(prefix, ignoreCase = true) }
|
||||||
|
}
|
||||||
string("material") {
|
string("material") {
|
||||||
executes {
|
executes {
|
||||||
val id = argument<String>("id")
|
val id = argument<String>("id")
|
||||||
|
|
@ -298,16 +337,54 @@ object CommandRegistrar {
|
||||||
}
|
}
|
||||||
|
|
||||||
literal("audience") {
|
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("permission") {
|
||||||
literal("add") {
|
literal("add") {
|
||||||
string("id") {
|
string("id") {
|
||||||
|
suggests { prefix ->
|
||||||
|
manager.listDisplays().map { it.id }.filter { it.startsWith(prefix, ignoreCase = true) }
|
||||||
|
}
|
||||||
string("permission") {
|
string("permission") {
|
||||||
executes {
|
executes {
|
||||||
val id = argument<String>("id")
|
val id = argument<String>("id")
|
||||||
val perm = argument<String>("permission")
|
val perm = argument<String>("permission")
|
||||||
try {
|
try {
|
||||||
manager.addPermissionAudience(id, perm)
|
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) {
|
} catch (ex: DisplayOperationException) {
|
||||||
sender.failure(ex.message ?: "Failed to add permission audience.")
|
sender.failure(ex.message ?: "Failed to add permission audience.")
|
||||||
}
|
}
|
||||||
|
|
@ -315,8 +392,12 @@ object CommandRegistrar {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
literal("remove") {
|
literal("remove") {
|
||||||
string("id") {
|
string("id") {
|
||||||
|
suggests { prefix ->
|
||||||
|
manager.listDisplays().map { it.id }.filter { it.startsWith(prefix, ignoreCase = true) }
|
||||||
|
}
|
||||||
string("permission") {
|
string("permission") {
|
||||||
executes {
|
executes {
|
||||||
val id = argument<String>("id")
|
val id = argument<String>("id")
|
||||||
|
|
@ -337,13 +418,31 @@ object CommandRegistrar {
|
||||||
literal("near") {
|
literal("near") {
|
||||||
literal("set") {
|
literal("set") {
|
||||||
string("id") {
|
string("id") {
|
||||||
|
suggests { prefix ->
|
||||||
|
manager.listDisplays().map { it.id }.filter { it.startsWith(prefix, ignoreCase = true) }
|
||||||
|
}
|
||||||
float("radius", min = 1.0) {
|
float("radius", min = 1.0) {
|
||||||
executes {
|
executes {
|
||||||
val id = argument<String>("id")
|
val id = argument<String>("id")
|
||||||
val radius = argument<Double>("radius")
|
val radius = argument<Double>("radius")
|
||||||
try {
|
try {
|
||||||
manager.setNearAudience(id, radius)
|
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) {
|
} catch (ex: DisplayOperationException) {
|
||||||
sender.failure(ex.message ?: "Failed to update radius audience.")
|
sender.failure(ex.message ?: "Failed to update radius audience.")
|
||||||
}
|
}
|
||||||
|
|
@ -352,8 +451,67 @@ object CommandRegistrar {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
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") {
|
literal("clear") {
|
||||||
string("id") {
|
string("id") {
|
||||||
|
suggests { prefix ->
|
||||||
|
manager.listDisplays().map { it.id }.filter { it.startsWith(prefix, ignoreCase = true) }
|
||||||
|
}
|
||||||
executes {
|
executes {
|
||||||
val id = argument<String>("id")
|
val id = argument<String>("id")
|
||||||
try {
|
try {
|
||||||
|
|
@ -379,8 +537,11 @@ private fun CommandSender.showUsage() {
|
||||||
info(" /ghostdisplay list | info <id> | delete <id>")
|
info(" /ghostdisplay list | info <id> | delete <id>")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Player.anchorLocation(): Location =
|
private fun Player.anchorLocation(): Location {
|
||||||
eyeLocation.clone().add(direction.normalize().multiply(1.5))
|
val eye = eyeLocation.clone()
|
||||||
|
val forward = eye.direction.normalize().multiply(1.5)
|
||||||
|
return eye.add(forward)
|
||||||
|
}
|
||||||
|
|
||||||
private fun parseBlockData(state: String): BlockData =
|
private fun parseBlockData(state: String): BlockData =
|
||||||
Bukkit.createBlockData(state)
|
Bukkit.createBlockData(state)
|
||||||
|
|
@ -6,6 +6,7 @@ import java.util.concurrent.ConcurrentHashMap
|
||||||
import java.util.logging.Logger
|
import java.util.logging.Logger
|
||||||
import net.hareworks.ghostdisplays.api.DisplayService
|
import net.hareworks.ghostdisplays.api.DisplayService
|
||||||
import net.hareworks.ghostdisplays.api.InteractionOptions
|
import net.hareworks.ghostdisplays.api.InteractionOptions
|
||||||
|
import net.hareworks.ghostdisplays.api.audience.AudienceAction
|
||||||
import net.hareworks.ghostdisplays.api.audience.AudiencePredicates
|
import net.hareworks.ghostdisplays.api.audience.AudiencePredicates
|
||||||
import net.hareworks.ghostdisplays.display.DisplayManager.DisplayOperationException
|
import net.hareworks.ghostdisplays.display.DisplayManager.DisplayOperationException
|
||||||
import net.kyori.adventure.text.Component
|
import net.kyori.adventure.text.Component
|
||||||
|
|
@ -79,7 +80,7 @@ class DisplayManager(
|
||||||
ensureIdAvailable(normalized)
|
ensureIdAvailable(normalized)
|
||||||
val safeLocation = location.clone()
|
val safeLocation = location.clone()
|
||||||
val controller = service.createItemDisplay(safeLocation, itemStack.clone(), INTERACTION_DEFAULT)
|
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(
|
val managed = ManagedDisplay.Item(
|
||||||
id = normalized,
|
id = normalized,
|
||||||
controller = controller,
|
controller = controller,
|
||||||
|
|
@ -115,7 +116,7 @@ class DisplayManager(
|
||||||
fun updateItem(id: String, itemStack: ItemStack) {
|
fun updateItem(id: String, itemStack: ItemStack) {
|
||||||
val display = requireItem(id)
|
val display = requireItem(id)
|
||||||
val clone = itemStack.clone()
|
val clone = itemStack.clone()
|
||||||
display.controller.applyEntityUpdate { it.itemStack = clone }
|
display.controller.applyEntityUpdate { it.setItemStack(clone) }
|
||||||
display.itemStack = clone
|
display.itemStack = clone
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -142,17 +143,22 @@ class DisplayManager(
|
||||||
viewers.forEach { display.controller.hide(it) }
|
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 display = requireDisplay(id)
|
||||||
val key = "perm:${permission.lowercase()}"
|
val key = "perm:${permission.lowercase()}"
|
||||||
if (display.removeAudienceBinding(key)) {
|
if (display.removeAudienceBinding(key)) {
|
||||||
logger.info("Replacing permission audience '$permission' for $id")
|
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(
|
display.registerAudienceBinding(
|
||||||
AudienceBinding(
|
AudienceBinding(
|
||||||
key = key,
|
key = key,
|
||||||
description = "permission:$permission",
|
description = "permission:$permission [${action.name}]",
|
||||||
registration = registration
|
registration = registration
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -164,24 +170,45 @@ class DisplayManager(
|
||||||
return display.removeAudienceBinding(key)
|
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." }
|
require(radius > 0) { "Radius must be positive." }
|
||||||
val display = requireDisplay(id)
|
val display = requireDisplay(id)
|
||||||
val key = "near"
|
val key = "near"
|
||||||
display.removeAudienceBinding(key)
|
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(
|
display.registerAudienceBinding(
|
||||||
AudienceBinding(
|
AudienceBinding(
|
||||||
key = key,
|
key = key,
|
||||||
description = "radius:${String.format("%.2f", radius)}",
|
description = "radius:${String.format("%.2f", radius)} [${action.name}]",
|
||||||
registration = registration
|
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) {
|
fun clearAudiences(id: String) {
|
||||||
val display = requireDisplay(id)
|
val display = requireDisplay(id)
|
||||||
display.clearAudiences()
|
display.clearAudiences()
|
||||||
|
display.controller.clearAudienceRules()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun destroyAll() {
|
fun destroyAll() {
|
||||||
|
|
@ -37,7 +37,7 @@ class EditSessionManager(
|
||||||
event.player.sendMessage("GhostDisplays: editing for '$displayId' cancelled.")
|
event.player.sendMessage("GhostDisplays: editing for '$displayId' cancelled.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
Bukkit.getScheduler().runTask(plugin) {
|
Bukkit.getScheduler().runTask(plugin, Runnable {
|
||||||
try {
|
try {
|
||||||
manager.updateText(displayId, message)
|
manager.updateText(displayId, message)
|
||||||
event.player.sendMessage("GhostDisplays: updated text for '$displayId'.")
|
event.player.sendMessage("GhostDisplays: updated text for '$displayId'.")
|
||||||
|
|
@ -46,7 +46,7 @@ class EditSessionManager(
|
||||||
} finally {
|
} finally {
|
||||||
sessions.remove(event.player.uniqueId)
|
sessions.remove(event.player.uniqueId)
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
|
|
@ -46,7 +46,7 @@ internal class DefaultDisplayService(
|
||||||
interaction: InteractionOptions,
|
interaction: InteractionOptions,
|
||||||
builder: ItemDisplay.() -> Unit
|
builder: ItemDisplay.() -> Unit
|
||||||
): DisplayController<ItemDisplay> = spawnDisplay(location, ItemDisplay::class.java, interaction) {
|
): DisplayController<ItemDisplay> = spawnDisplay(location, ItemDisplay::class.java, interaction) {
|
||||||
it.itemStack = itemStack.clone()
|
it.setItemStack(itemStack.clone())
|
||||||
builder(it)
|
builder(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -94,7 +94,7 @@ internal class DefaultDisplayService(
|
||||||
action()
|
action()
|
||||||
} else {
|
} else {
|
||||||
val future = CompletableFuture<T>()
|
val future = CompletableFuture<T>()
|
||||||
Bukkit.getScheduler().runTask(plugin) { future.complete(action()) }
|
Bukkit.getScheduler().runTask(plugin, Runnable { future.complete(action()) })
|
||||||
future.join()
|
future.join()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -7,6 +7,7 @@ import java.util.concurrent.ConcurrentHashMap
|
||||||
import java.util.concurrent.CopyOnWriteArrayList
|
import java.util.concurrent.CopyOnWriteArrayList
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import net.hareworks.ghostdisplays.api.DisplayController
|
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.audience.AudiencePredicate
|
||||||
import net.hareworks.ghostdisplays.api.click.ClickPriority
|
import net.hareworks.ghostdisplays.api.click.ClickPriority
|
||||||
import net.hareworks.ghostdisplays.api.click.ClickSurface
|
import net.hareworks.ghostdisplays.api.click.ClickSurface
|
||||||
|
|
@ -30,7 +31,11 @@ internal class BaseDisplayController<T : Display>(
|
||||||
private val destroyed = AtomicBoolean(false)
|
private val destroyed = AtomicBoolean(false)
|
||||||
private val viewerCounts = ConcurrentHashMap<UUID, Int>()
|
private val viewerCounts = ConcurrentHashMap<UUID, Int>()
|
||||||
private val handlers = CopyOnWriteArrayList<HandlerEntry>()
|
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) {
|
override fun show(player: Player) {
|
||||||
runSync {
|
runSync {
|
||||||
|
|
@ -64,7 +69,6 @@ internal class BaseDisplayController<T : Display>(
|
||||||
override fun applyEntityUpdate(mutator: (T) -> Unit) {
|
override fun applyEntityUpdate(mutator: (T) -> Unit) {
|
||||||
callSync {
|
callSync {
|
||||||
mutator(display)
|
mutator(display)
|
||||||
display.updateDisplay(false)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,8 +86,8 @@ internal class BaseDisplayController<T : Display>(
|
||||||
viewerCounts.clear()
|
viewerCounts.clear()
|
||||||
}
|
}
|
||||||
handlers.clear()
|
handlers.clear()
|
||||||
audiences.forEach { it.clear() }
|
audienceRules.clear()
|
||||||
audiences.clear()
|
autoVisiblePlayers.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onClick(priority: ClickPriority, handler: DisplayClickHandler): HandlerRegistration {
|
override fun onClick(priority: ClickPriority, handler: DisplayClickHandler): HandlerRegistration {
|
||||||
|
|
@ -94,26 +98,67 @@ internal class BaseDisplayController<T : Display>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun addAudience(predicate: AudiencePredicate): HandlerRegistration {
|
override fun setBaseVisibility(visible: Boolean) {
|
||||||
val binding = AudienceBindingImpl(predicate)
|
this.baseVisibility = visible
|
||||||
audiences += binding
|
refreshAudience()
|
||||||
refreshAudienceInternal(binding = binding)
|
|
||||||
return HandlerRegistration { binding.unregister() }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshAudienceInternal(target: Player? = null, binding: AudienceBindingImpl? = null) {
|
override fun addAudience(predicate: AudiencePredicate): HandlerRegistration {
|
||||||
runSync {
|
return addAudienceRule(predicate, AudienceAction.ADD)
|
||||||
val players = target?.let { listOf(it) } ?: Bukkit.getOnlinePlayers()
|
}
|
||||||
val targets = binding?.let { listOf(it) } ?: audiences
|
|
||||||
if (players.isEmpty() || targets.isEmpty()) return@runSync
|
override fun addAudienceRule(predicate: AudiencePredicate, action: AudienceAction): HandlerRegistration {
|
||||||
players.forEach { player ->
|
val entry = RuleEntry(predicate, action)
|
||||||
targets.forEach { it.evaluate(player) }
|
audienceRules.add(entry)
|
||||||
|
refreshAudience()
|
||||||
|
return HandlerRegistration {
|
||||||
|
audienceRules.remove(entry)
|
||||||
|
refreshAudience()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun clearAudienceRules() {
|
||||||
|
audienceRules.clear()
|
||||||
|
refreshAudience()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun refreshAudience(target: Player?) {
|
override fun refreshAudience(target: Player?) {
|
||||||
refreshAudienceInternal(target, null)
|
refreshAudienceInternal(target)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun refreshAudienceInternal(target: Player? = null) {
|
||||||
|
runSync {
|
||||||
|
val players = target?.let { listOf(it) } ?: Bukkit.getOnlinePlayers()
|
||||||
|
if (players.isEmpty()) return@runSync
|
||||||
|
|
||||||
|
players.forEach { 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
internal fun handleClick(event: PlayerInteractEntityEvent, clicked: Entity, surface: ClickSurface) {
|
||||||
|
|
@ -136,14 +181,17 @@ internal class BaseDisplayController<T : Display>(
|
||||||
internal fun handlePlayerQuit(player: Player) {
|
internal fun handlePlayerQuit(player: Player) {
|
||||||
val uuid = player.uniqueId
|
val uuid = player.uniqueId
|
||||||
viewerCounts.remove(uuid)
|
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) {
|
private fun runSync(action: () -> Unit) {
|
||||||
if (Bukkit.isPrimaryThread()) {
|
if (Bukkit.isPrimaryThread()) {
|
||||||
action()
|
action()
|
||||||
} else {
|
} else {
|
||||||
Bukkit.getScheduler().runTask(plugin, action)
|
Bukkit.getScheduler().runTask(plugin, Runnable { action() })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -152,45 +200,15 @@ internal class BaseDisplayController<T : Display>(
|
||||||
action()
|
action()
|
||||||
} else {
|
} else {
|
||||||
val future = CompletableFuture<R>()
|
val future = CompletableFuture<R>()
|
||||||
Bukkit.getScheduler().runTask(plugin) { future.complete(action()) }
|
Bukkit.getScheduler().runTask(plugin, Runnable { future.complete(action()) })
|
||||||
future.join()
|
future.join()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class AudienceBindingImpl(
|
private data class RuleEntry(
|
||||||
private val predicate: AudiencePredicate
|
val predicate: AudiencePredicate,
|
||||||
) {
|
val action: AudienceAction
|
||||||
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 HandlerEntry(
|
private data class HandlerEntry(
|
||||||
val priority: ClickPriority,
|
val priority: ClickPriority,
|
||||||
Loading…
Reference in New Issue
Block a user