Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b84a27f76
|
||
|
|
a5a0be50c2
|
||
|
|
3e1aa9d39e | ||
|
|
385f505c99 | ||
|
|
fcfcd3c940 | ||
|
|
53f66e0cfb | ||
|
|
8781ff2276 | ||
|
|
ab319e4baa | ||
|
|
0006cf83a0 | ||
|
|
7fd168d6c2 | ||
|
|
cb5bf94e76 | ||
|
|
887cfdad8d | ||
|
|
24adfe121f | ||
|
|
b495a87a57 | ||
|
|
12ede5bc1e | ||
|
|
db855188a8 | ||
|
|
ee6c978b4d | ||
|
|
846b525e3c | ||
|
|
e9d6544263 | ||
|
|
9d3c5f5256 | ||
|
|
5a22d45e39 | ||
|
|
e1ddc32020 | ||
|
|
7b1faa884b | ||
|
|
021a198aeb | ||
|
|
7154210ee4 | ||
|
|
12098133b2 | ||
|
|
0057bb464b | ||
|
|
8295644feb | ||
|
|
751dc33c02 | ||
|
|
94ee975b7f | ||
|
|
a61a058885 | ||
|
|
e918b8ad73 | ||
|
|
31062e6d02 | ||
|
|
2cf0b225b2 | ||
|
|
1e6f00cd0c | ||
|
|
c9309937e5 | ||
|
|
ca5969e25e | ||
|
|
eb546a54d5 | ||
|
|
64a3ea699d | ||
|
|
1c9ab88f78 | ||
|
|
0136949d17 | ||
|
|
50a4c7e2b5 | ||
|
|
9cbe8e9ca2 | ||
|
|
2f505bd7b1 | ||
|
|
3ecd52b928 | ||
|
|
9eb8b04912 | ||
|
|
1a1f9688cc |
No files matched your search
@@ -0,0 +1 @@
|
||||
.gitea/CODEOWNERS linguist-detectable=false
|
||||
@@ -0,0 +1,3 @@
|
||||
# Pull requests request review from the eligible staff team(s).
|
||||
# One approval from either an Admin or Developer satisfies branch protection.
|
||||
* @trueog/Admins @trueog/Developers
|
||||
@@ -73,6 +73,9 @@ Enhance your server's customization by tailoring player name tags, integrating d
|
||||
5. **Configure**
|
||||
Customize the `settings.yml` file located in the plugin's folder to suit your server’s needs.
|
||||
|
||||
6. **Optional `advanced.yml`**
|
||||
For manual helmet → nametag height rules (e.g. ItemsAdder / custom CMD without relying on resource-pack parsing), copy [`advanced.example.yml`](advanced.example.yml) to `plugins/UnlimitedNameTags/advanced.yml` and adjust. The file is never created automatically; `/unt reload` reloads it when present.
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ **Commands**
|
||||
@@ -116,8 +119,6 @@ Unlimited Name Tags works seamlessly with:
|
||||
- **MiniPlaceholders**: Works when using MiniMessage for advanced formatting.
|
||||
- **Custom Plugins**: Easily hook into your custom plugins to extend functionality.
|
||||
|
||||
---
|
||||
|
||||
## 📜 **Supported Versions**
|
||||
- **Paper**: Fully supported from **1.20.1+** *(highly recommended)*.
|
||||
- **Spigot**: Supported from **1.20.2+**, but Paper is preferred for enhanced performance. (Not all spigot versions can be supported)
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
# Copy to plugins/UnlimitedNameTags/advanced.yml and edit.
|
||||
# Optional: if this file is missing, the plugin behaves as before.
|
||||
# Keys use camelCase (ConfigLib default).
|
||||
|
||||
helmetHeightRules:
|
||||
# Example: custom head with a specific CustomModelData
|
||||
- priority: 10
|
||||
height: 28
|
||||
material: PLAYER_HEAD
|
||||
customModelData: 12345
|
||||
|
||||
# Example: any leather helmet with CMD in a range (inclusive)
|
||||
- priority: 5
|
||||
height: 20
|
||||
material: LEATHER_HELMET
|
||||
customModelDataMin: 1000
|
||||
customModelDataMax: 1999
|
||||
|
||||
# Example: equippable item model (1.21.3+)
|
||||
- priority: 8
|
||||
height: 32
|
||||
equippableModel: "mypack:item/tall_hat"
|
||||
|
||||
# Optional filters on any rule:
|
||||
# worlds: [world, world_nether]
|
||||
# permission: "myserver.bighelmet"
|
||||
+59
-19
@@ -8,33 +8,49 @@ plugins {
|
||||
}
|
||||
|
||||
group = "org.alexdev"
|
||||
version = "1.6.6"
|
||||
version = property("version") as String
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven("https://repo.papermc.io/repository/maven-public/")
|
||||
maven("https://repo.nexomc.com/snapshots/")
|
||||
maven("https://oss.sonatype.org/content/groups/public/")
|
||||
maven("https://repo.extendedclip.com/releases")
|
||||
maven("https://repo.minebench.de/")
|
||||
maven("https://oss.sonatype.org/content/repositories/snapshots")
|
||||
maven("https://libraries.minecraft.net/")
|
||||
maven("https://repo.codemc.io/repository/maven-public/")
|
||||
maven("https://repo.codemc.io/repository/maven-snapshots/")
|
||||
maven("https://repo.codemc.io/repository/maven-releases/")
|
||||
maven("https://repo.viaversion.com/")
|
||||
maven("https://maven.pvphub.me/#/tofaa/io/github/tofaa2/")
|
||||
maven("https://repo.opencollab.dev/main/")
|
||||
maven("https://s01.oss.sonatype.org/content/repositories/snapshots/")
|
||||
maven("https://repo.oraxen.com/releases")
|
||||
maven("https://repo.oraxen.com/snapshots")
|
||||
maven("https://jitpack.io")
|
||||
maven("https://repo.viaversion.com")
|
||||
maven("https://maven.evokegames.gg/snapshots")
|
||||
maven("https://repo.alessiodp.com/releases")
|
||||
//maven("https://maven.evokegames.gg/snapshots")
|
||||
maven("https://repo.alessiodp.com/snapshots/")
|
||||
maven("https://maven.typewritermc.com/beta")
|
||||
maven("https://repo.nexomc.com/snapshots/")
|
||||
maven("https://repo.nexomc.com/releases")
|
||||
maven("https://repo.md-5.net/content/groups/public/")
|
||||
maven("https://mvn.lib.co.nz/public")
|
||||
maven {
|
||||
name = "feather-repo"
|
||||
url = uri("https://repo.feathermc.net/artifactory/maven-releases")
|
||||
}
|
||||
maven {
|
||||
name = "labymod"
|
||||
url = uri("https://dist.labymod.net/api/v1/maven/release/")
|
||||
}
|
||||
maven("https://repo.hibiscusmc.com/releases")
|
||||
maven("https://maven.pvphub.me/tofaa")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
compileOnly(libs.paperApi)
|
||||
compileOnly(libs.adventureApi)
|
||||
implementation(libs.entityLib)
|
||||
@@ -49,7 +65,7 @@ dependencies {
|
||||
compileOnly(libs.geyserApi)
|
||||
compileOnly(libs.commonsLang)
|
||||
compileOnly(libs.configlib)
|
||||
compileOnly(libs.configlibPaper)
|
||||
implementation(libs.configlibPaper)
|
||||
compileOnly(libs.packeteventsSpigot)
|
||||
compileOnly(libs.viaVersionApi)
|
||||
compileOnly(libs.bstatsBukkit)
|
||||
@@ -58,14 +74,18 @@ dependencies {
|
||||
compileOnly(libs.nexo)
|
||||
compileOnly(libs.oraxen)
|
||||
compileOnly(libs.itemsAdder)
|
||||
compileOnly(libs.hmccosmetics)
|
||||
compileOnly(libs.creative.rp)
|
||||
compileOnly(libs.creative.serializer)
|
||||
compileOnly(libs.libs.disguises)
|
||||
compileOnly(libs.hmcCosmetics)
|
||||
|
||||
implementation(libs.minedownAdventure)
|
||||
implementation(libs.drink)
|
||||
implementation(libs.universalScheduler)
|
||||
implementation(libs.libbyBukkit)
|
||||
|
||||
compileOnly(libs.gson)
|
||||
compileOnly(libs.feather)
|
||||
compileOnly(libs.labymod)
|
||||
|
||||
compileOnly(libs.lombok)
|
||||
annotationProcessor(libs.lombok)
|
||||
@@ -84,6 +104,8 @@ tasks.named<ShadowJar>("shadowJar") {
|
||||
relocate("net.kyori.adventure.text.serializer", "io.github.retrooper.packetevents.adventure.serializer")
|
||||
relocate("net.byteflux.libby", relocation + "libby.bukkit")
|
||||
|
||||
relocate("de.exlll.configlib", "org.alexdev.unlimitednametags.configlib")
|
||||
|
||||
dependencies {
|
||||
exclude(dependency(":kotlin-stdlib"))
|
||||
exclude(dependency(":slf4j-api"))
|
||||
@@ -113,7 +135,7 @@ tasks.named<ShadowJar>("shadowJar") {
|
||||
|
||||
tasks.withType<JavaCompile> {
|
||||
options.encoding = "UTF-8"
|
||||
options.release.set(21)
|
||||
options.release.set(17)
|
||||
|
||||
}
|
||||
tasks.withType<Javadoc> {
|
||||
@@ -122,6 +144,12 @@ tasks.withType<Javadoc> {
|
||||
|
||||
java {
|
||||
toolchain.languageVersion.set(JavaLanguageVersion.of(21))
|
||||
|
||||
disableAutoTargetJvm()
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvmToolchain(21)
|
||||
}
|
||||
|
||||
tasks.named<Jar>("jar").configure {
|
||||
@@ -131,32 +159,37 @@ tasks.named<Delete>("clean").configure {
|
||||
delete(tasks.named<ShadowJar>("shadowJar").get().archiveFile)
|
||||
}
|
||||
|
||||
tasks.jar {
|
||||
manifest {
|
||||
attributes["paperweight-mappings-namespace"] = "mojang"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
tasks {
|
||||
runServer {
|
||||
minecraftVersion("1.21.3")
|
||||
minecraftVersion("1.21.11")
|
||||
|
||||
downloadPlugins {
|
||||
hangar("PlaceholderAPI", "2.11.6")
|
||||
modrinth("luckperms", "v5.4.145-bukkit")
|
||||
modrinth("multiverse-core", "4.3.14")
|
||||
url("https://github.com/retrooper/packetevents/releases/download/v2.6.0/packetevents-spigot-2.6.0.jar")
|
||||
github("ViaVersion", "ViaVersion", "5.1.1", "ViaVersion-5.1.1.jar")
|
||||
github("ViaVersion", "ViaBackwards", "5.1.1", "ViaBackwards-5.1.1.jar")
|
||||
github("MiniPlaceholders", "MiniPlaceholders", "3.0.1", "MiniPlaceholders-Paper-3.0.1.jar")
|
||||
// github("retrooper", "packetevents", "v2.9.4", "packetevents-spigot-2.9.4.jar")
|
||||
url("https://ci.codemc.io/job/retrooper/job/packetevents/796/artifact/build/libs/packetevents-spigot-2.11.1-SNAPSHOT.jar")
|
||||
github("MilkBowl", "Vault", "1.7.3", "Vault.jar")
|
||||
github("gecolay", "GSit", "1.11.2", "GSit-1.11.2.jar")
|
||||
// url("https://github.com/EssentialsX/Essentials/releases/download/2.20.1/EssentialsX-2.20.1.jar")
|
||||
github("FeatherMC", "feather-server-api", "v0.0.5", "feather-server-api-0.0.5-bukkit.jar")
|
||||
github("LabyMod", "labymod4-server-api", "1.0.6", "labymod-server-api-bukkit-1.0.6.jar")
|
||||
}
|
||||
}
|
||||
runPaper.folia.registerTask {
|
||||
minecraftVersion("1.21.1")
|
||||
minecraftVersion("1.21.11")
|
||||
|
||||
downloadPlugins {
|
||||
github("Anon8281", "PlaceholderAPI", "2.11.7", "PlaceholderAPI-2.11.7-DEV-Folia.jar")
|
||||
|
||||
url("https://cdn.modrinth.com/data/HQyibRsN/versions/J2guR3GH/MiniPlaceholders-Paper-2.2.4.jar")
|
||||
url("https://ci.lucko.me/job/LuckPerms-Folia/lastSuccessfulBuild/artifact/bukkit/loader/build/libs/LuckPerms-Bukkit-5.4.141.jar")
|
||||
url("https://github.com/retrooper/packetevents/releases/download/v2.4.0/packetevents-spigot-2.4.0.jar")
|
||||
github("ViaVersion", "ViaVersion", "5.0.1", "ViaVersion-5.0.1.jar")
|
||||
url("https://ci.codemc.io/job/retrooper/job/packetevents/796/artifact/build/libs/packetevents-spigot-2.11.1-SNAPSHOT.jar")
|
||||
github("ViaVersion", "ViaVersion", "5.4.1", "ViaVersion-5.4.1.jar")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -167,6 +200,12 @@ tasks.processResources {
|
||||
compiled = false
|
||||
}
|
||||
|
||||
filesMatching("**/UnlimitedNameTags.java") {
|
||||
expand(
|
||||
"configlibVersion" to libs.versions.configlibVersion.get(),
|
||||
)
|
||||
}
|
||||
|
||||
from("src/main/resources") {
|
||||
include("plugin.yml")
|
||||
duplicatesStrategy = DuplicatesStrategy.INCLUDE
|
||||
@@ -174,6 +213,7 @@ tasks.processResources {
|
||||
"configlibVersion" to libs.versions.configlibVersion.get(),
|
||||
"expiringMapVersion" to libs.versions.expiringMapVersion.get(),
|
||||
"commonsJexl3Version" to libs.versions.commonsJexl3Version.get(),
|
||||
|
||||
"version" to version,
|
||||
"compiled" to compiled
|
||||
)
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
version = 1.6.11
|
||||
+23
-16
@@ -1,32 +1,34 @@
|
||||
[versions]
|
||||
paperApiVersion = "1.21.1-R0.1-SNAPSHOT"
|
||||
paperApiVersion = "1.21.4-R0.1-SNAPSHOT"
|
||||
adventureApiVersion = "4.16.0"
|
||||
adventureTextMinimessageVersion = "4.17.0"
|
||||
adventureTextSerializerLegacyVersion = "4.17.0"
|
||||
adventurePlatformBukkitVersion = "4.3.3"
|
||||
typeWriterVersion = "0.7.0"
|
||||
typeWriterVersion = "0.9.0"
|
||||
lombokVersion = "1.18.30"
|
||||
placeholderapiVersion = "2.11.6"
|
||||
miniplaceholdersApiVersion = "2.2.3"
|
||||
miniplaceholdersApiVersion = "3.0.1"
|
||||
floodgateApiVersion = "2.2.3-SNAPSHOT"
|
||||
geyserApiVersion = "2.4.2-SNAPSHOT"
|
||||
commonsLangVersion = "3.14.0"
|
||||
configlibVersion = "4.5.0"
|
||||
configlibVersion = "4.6.1"
|
||||
minedownAdventureVersion = "1.7.2-SNAPSHOT"
|
||||
drinkVersion = "1.5.3"
|
||||
entityLibVersion = "+39cce79-SNAPSHOT"
|
||||
packeteventsSpigotVersion = "2.6.0"
|
||||
entityLibVersion = "3.0.3-SNAPSHOT"
|
||||
packeteventsSpigotVersion = "2.10.0-SNAPSHOT"
|
||||
universalSchedulerVersion = "0.1.6"
|
||||
viaVersionApiVersion = "5.0.1"
|
||||
libbyBukkitVersion = "1.3.1"
|
||||
libbyBukkitVersion = "2.0.0-SNAPSHOT"
|
||||
bstatsBukkitVersion = "3.0.2"
|
||||
expiringMapVersion = "0.5.11"
|
||||
commonsJexl3Version = "3.4.0"
|
||||
nexoVersion = "0.9.0-dev.19"
|
||||
oraxenVersion = "1.174.0"
|
||||
nexoVersion = "1.2.0"
|
||||
oraxenVersion = "1.195.0"
|
||||
itemsAdderVersion = "3.6.1"
|
||||
gsonVersion = "2.11.0"
|
||||
hmccosmeticsVersion = "2.7.4"
|
||||
creative-rp = "1.9.2-SNAPSHOT"
|
||||
creative-serializer = "1.9.2-SNAPSHOT"
|
||||
libs-disguises = "11.0.0"
|
||||
featherVersion = "0.0.5"
|
||||
labymodVersion = "1.0.6"
|
||||
hmcCosmetics = "2.8.2-fed13b8e"
|
||||
|
||||
[libraries]
|
||||
paperApi = { module = "io.papermc.paper:paper-api", version.ref = "paperApiVersion" }
|
||||
@@ -42,11 +44,11 @@ configlib = { module = "de.exlll:configlib-yaml", version.ref = "configlibVersio
|
||||
configlibPaper = { module = "de.exlll:configlib-paper", version.ref = "configlibVersion" }
|
||||
minedownAdventure = { module = "de.themoep:minedown-adventure", version.ref = "minedownAdventureVersion" }
|
||||
drink = { module = "com.github.alexdev03:drink", version.ref = "drinkVersion" }
|
||||
entityLib = { module = "me.tofaa.entitylib:spigot", version.ref = "entityLibVersion" }
|
||||
entityLib = { module = "io.github.tofaa2:spigot", version.ref = "entityLibVersion" }
|
||||
packeteventsSpigot = { module = "com.github.retrooper:packetevents-spigot", version.ref = "packeteventsSpigotVersion" }
|
||||
universalScheduler = { module = "com.github.Anon8281:UniversalScheduler", version.ref = "universalSchedulerVersion" }
|
||||
viaVersionApi = { module = "com.viaversion:viaversion-api", version.ref = "viaVersionApiVersion" }
|
||||
libbyBukkit = { module = "net.byteflux:libby-bukkit", version.ref = "libbyBukkitVersion" }
|
||||
libbyBukkit = { module = "com.alessiodp.libby:libby-bukkit", version.ref = "libbyBukkitVersion" }
|
||||
bstatsBukkit = { module = "org.bstats:bstats-bukkit", version.ref = "bstatsBukkitVersion" }
|
||||
expiringMap = { module = "net.jodah:expiringmap", version.ref = "expiringMapVersion" }
|
||||
commonsJexl3 = { module = "org.apache.commons:commons-jexl3", version.ref = "commonsJexl3Version" }
|
||||
@@ -54,4 +56,9 @@ nexo = { module = "com.nexomc:nexo", version.ref = "nexoVersion" }
|
||||
oraxen = { module = "io.th0rgal:oraxen", version.ref = "oraxenVersion" }
|
||||
itemsAdder = { module = "com.github.LoneDev6:API-ItemsAdder", version.ref = "itemsAdderVersion" }
|
||||
gson = { module = "com.google.code.gson:gson", version.ref = "gsonVersion" }
|
||||
hmccosmetics = { module = "com.hibiscusmc:HMCCosmetics", version.ref = "hmccosmeticsVersion" }
|
||||
creative-rp = { module = "team.unnamed:creative-api", version.ref = "creative-rp" }
|
||||
creative-serializer = { module = "team.unnamed:creative-serializer-minecraft", version.ref = "creative-serializer" }
|
||||
libs-disguises = { module = "me.libraryaddict.disguises:libsdisguises", version.ref = "libs-disguises" }
|
||||
feather = { module = "net.digitalingot.feather-server-api:api", version.ref = "featherVersion" }
|
||||
labymod = { module = "net.labymod.serverapi:server-bukkit", version.ref = "labymodVersion" }
|
||||
hmcCosmetics = { module = "com.hibiscusmc:HMCCosmetics", version.ref = "hmcCosmetics" }
|
||||
Vendored
BIN
Binary file not shown.
+2
-1
@@ -1,6 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
@@ -15,6 +15,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
@@ -55,7 +57,7 @@
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
@@ -83,7 +85,8 @@ done
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
@@ -130,10 +133,13 @@ location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
@@ -141,7 +147,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
@@ -149,7 +155,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
@@ -198,11 +204,11 @@ fi
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
|
||||
Vendored
+94
-92
@@ -1,92 +1,94 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@@ -1 +1,5 @@
|
||||
plugins {
|
||||
id("org.gradle.toolchains.foojay-resolver-convention") version "0.9.0"
|
||||
}
|
||||
|
||||
rootProject.name = "UnlimitedNametags"
|
||||
@@ -1,5 +1,7 @@
|
||||
package org.alexdev.unlimitednametags;
|
||||
|
||||
import com.alessiodp.libby.BukkitLibraryManager;
|
||||
import com.alessiodp.libby.Library;
|
||||
import com.github.Anon8281.universalScheduler.UniversalScheduler;
|
||||
import com.github.Anon8281.universalScheduler.scheduling.schedulers.TaskScheduler;
|
||||
import com.google.common.collect.Lists;
|
||||
@@ -7,14 +9,12 @@ import com.google.common.collect.Maps;
|
||||
import com.jonahseguin.drink.CommandService;
|
||||
import com.jonahseguin.drink.Drink;
|
||||
import lombok.Getter;
|
||||
import net.byteflux.libby.BukkitLibraryManager;
|
||||
import net.byteflux.libby.Library;
|
||||
import org.alexdev.unlimitednametags.api.UNTAPI;
|
||||
import org.alexdev.unlimitednametags.commands.MainCommand;
|
||||
import org.alexdev.unlimitednametags.config.ConfigManager;
|
||||
import org.alexdev.unlimitednametags.events.*;
|
||||
import org.alexdev.unlimitednametags.hook.*;
|
||||
import org.alexdev.unlimitednametags.hook.hat.HatHook;
|
||||
import org.alexdev.unlimitednametags.listeners.*;
|
||||
import org.alexdev.unlimitednametags.metrics.Metrics;
|
||||
import org.alexdev.unlimitednametags.nametags.ConditionalManager;
|
||||
import org.alexdev.unlimitednametags.nametags.NameTagManager;
|
||||
@@ -51,7 +51,7 @@ public final class UnlimitedNameTags extends JavaPlugin {
|
||||
|
||||
@Override
|
||||
public void onLoad() {
|
||||
hooks = Maps.newConcurrentMap();
|
||||
hooks = Maps.newHashMap();
|
||||
hatHooks = Lists.newCopyOnWriteArrayList();
|
||||
}
|
||||
|
||||
@@ -68,6 +68,8 @@ public final class UnlimitedNameTags extends JavaPlugin {
|
||||
return;
|
||||
}
|
||||
|
||||
loadListeners();
|
||||
|
||||
trackerManager = new TrackerManager(this);
|
||||
nametagManager = new NameTagManager(this);
|
||||
placeholderManager = new PlaceholderManager(this);
|
||||
@@ -77,7 +79,6 @@ public final class UnlimitedNameTags extends JavaPlugin {
|
||||
|
||||
|
||||
loadCommands();
|
||||
loadListeners();
|
||||
loadHooks();
|
||||
loadStats();
|
||||
|
||||
@@ -96,29 +97,50 @@ public final class UnlimitedNameTags extends JavaPlugin {
|
||||
}
|
||||
|
||||
private CompletableFuture<Void> loadLibraries() {
|
||||
if (isPaper) {
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
final BukkitLibraryManager bukkitLibraryManager = new BukkitLibraryManager(this);
|
||||
bukkitLibraryManager.addRepository("https://maven-central.storage-download.googleapis.com/maven2");
|
||||
bukkitLibraryManager.addMavenCentral();
|
||||
|
||||
final List<Library> libraries = Lists.newArrayList(
|
||||
Library.builder()
|
||||
.groupId("net{}kyori")
|
||||
.artifactId("adventure-text-minimessage")
|
||||
.version("4.17.0")
|
||||
.relocate("net{}]kyori{}adventure{}text{}serializer", "io{}github{}retrooper{}packetevents{}adventure{}serializer")
|
||||
.build()
|
||||
|
||||
);
|
||||
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
libraries.forEach(bukkitLibraryManager::loadLibrary);
|
||||
});
|
||||
if (!isPaper) {
|
||||
libraries.add(Library.builder()
|
||||
.groupId("net{}kyori")
|
||||
.artifactId("adventure-text-minimessage")
|
||||
.version("4.17.0")
|
||||
.relocate("net{}]kyori{}adventure{}text{}serializer", "io{}github{}retrooper{}packetevents{}adventure{}serializer")
|
||||
.build());
|
||||
}
|
||||
|
||||
if (false && Bukkit.getPluginManager().isPluginEnabled("ItemsAdder")) {
|
||||
libraries.add(Library.builder()
|
||||
.groupId("team.unnamed")
|
||||
.artifactId("creative-server")
|
||||
.version("1.8.3-SNAPSHOT")
|
||||
.build());
|
||||
libraries.add(Library.builder()
|
||||
.groupId("team.unnamed")
|
||||
.artifactId("creative-serializer-minecraft")
|
||||
.version("1.8.3-SNAPSHOT")
|
||||
.build());
|
||||
libraries.add(Library.builder()
|
||||
.groupId("team.unnamed")
|
||||
.artifactId("creative-api")
|
||||
.version("1.8.3-SNAPSHOT")
|
||||
.build());
|
||||
}
|
||||
|
||||
return CompletableFuture.runAsync(() -> libraries.forEach(bukkitLibraryManager::loadLibrary));
|
||||
}
|
||||
|
||||
private void loadListeners() {
|
||||
playerListener = new PlayerListener(this);
|
||||
Bukkit.getPluginManager().registerEvents(playerListener, this);
|
||||
Bukkit.getPluginManager().registerEvents(new OtherListener(this), this);
|
||||
|
||||
|
||||
if (isPaper) {
|
||||
getLogger().info("Paper found, using Paper's tracker");
|
||||
@@ -129,6 +151,7 @@ public final class UnlimitedNameTags extends JavaPlugin {
|
||||
getServer().getPluginManager().disablePlugin(this);
|
||||
return;
|
||||
}
|
||||
|
||||
getLogger().info("Paper not found, using Spigot's tracker");
|
||||
Bukkit.getPluginManager().registerEvents(new SpigotTrackerListener(this), this);
|
||||
}
|
||||
@@ -163,6 +186,8 @@ public final class UnlimitedNameTags extends JavaPlugin {
|
||||
}
|
||||
|
||||
private void loadHooks() {
|
||||
hatHooks.add(new AdvancedHatHook(this));
|
||||
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("TypeWriter")) {
|
||||
hooks.put(TypeWriterListener.class, new TypeWriterListener(this));
|
||||
getLogger().info("TypeWriter found, hooking into it");
|
||||
@@ -174,24 +199,24 @@ public final class UnlimitedNameTags extends JavaPlugin {
|
||||
}
|
||||
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("Nexo")) {
|
||||
getLogger().info("Nexo found, hooking into it");
|
||||
final NexoHook hook = new NexoHook(this);
|
||||
hatHooks.add(hook);
|
||||
hooks.put(NexoHook.class, hook);
|
||||
getLogger().info("Nexo found, hooking into it");
|
||||
}
|
||||
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("Oraxen")) {
|
||||
getLogger().info("Oraxen found, hooking into it");
|
||||
final OraxenHook hook = new OraxenHook(this);
|
||||
hatHooks.add(hook);
|
||||
hooks.put(OraxenHook.class, hook);
|
||||
getLogger().info("Oraxen found, hooking into it");
|
||||
}
|
||||
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("ItemsAdder")) {
|
||||
if (false && Bukkit.getPluginManager().isPluginEnabled("ItemsAdder")) {
|
||||
getLogger().info("ItemsAdder found, hooking into it");
|
||||
final ItemsAdderHook hook = new ItemsAdderHook(this);
|
||||
hatHooks.add(hook);
|
||||
hooks.put(ItemsAdderHook.class, hook);
|
||||
getLogger().info("ItemsAdder found, hooking into it");
|
||||
}
|
||||
|
||||
|
||||
@@ -213,6 +238,31 @@ public final class UnlimitedNameTags extends JavaPlugin {
|
||||
getLogger().info("Floodgate found, hooking into it");
|
||||
}
|
||||
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("LibsDisguises")) {
|
||||
final LibsDisguisesHook hook = new LibsDisguisesHook(this);
|
||||
hooks.put(LibsDisguisesHook.class, hook);
|
||||
getLogger().info("LibsDisguises found, hooking into it");
|
||||
}
|
||||
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("FeatherServerAPI")) {
|
||||
final FeatherClientHook hook = new FeatherClientHook(this);
|
||||
hooks.put(FeatherClientHook.class, hook);
|
||||
getLogger().info("FeatherServerAPI found, hooking into it");
|
||||
}
|
||||
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("HMCCosmetics")) {
|
||||
final HMCCosmeticsHook hook = new HMCCosmeticsHook(this);
|
||||
hatHooks.add(hook);
|
||||
hooks.put(HMCCosmeticsHook.class, hook);
|
||||
getLogger().info("HMCCosmetics found, hooking into it");
|
||||
}
|
||||
|
||||
// if (Bukkit.getPluginManager().isPluginEnabled("LabyModServerAPI")) {
|
||||
// final LabyModHook hook = new LabyModHook(this);
|
||||
// hooks.put(LabyModHook.class, hook);
|
||||
// getLogger().info("LabyModServerAPI found, hooking into it");
|
||||
// }
|
||||
|
||||
hooks.values().forEach(Hook::onEnable);
|
||||
}
|
||||
|
||||
@@ -262,6 +312,7 @@ public final class UnlimitedNameTags extends JavaPlugin {
|
||||
placeholderManager.close();
|
||||
packetManager.close();
|
||||
taskScheduler.cancelTasks();
|
||||
playerListener.close();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
package org.alexdev.unlimitednametags.api;
|
||||
|
||||
|
||||
import me.tofaa.entitylib.meta.display.AbstractDisplayMeta;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.alexdev.unlimitednametags.UnlimitedNameTags;
|
||||
import org.alexdev.unlimitednametags.config.Settings;
|
||||
import org.alexdev.unlimitednametags.hook.hat.HatHook;
|
||||
import org.alexdev.unlimitednametags.packet.PacketNameTag;
|
||||
import org.alexdev.unlimitednametags.vanish.VanishIntegration;
|
||||
@@ -9,7 +12,9 @@ import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* The UnlimitedNameTags API class.
|
||||
@@ -115,7 +120,7 @@ public class UNTAPI {
|
||||
* @since 1.5
|
||||
*/
|
||||
public void showNametag(@NotNull Player player) {
|
||||
plugin.getNametagManager().showToTrackedPlayers(player, plugin.getTrackerManager().getTrackedPlayers(player.getUniqueId()));
|
||||
plugin.getNametagManager().showToTrackedPlayers(player);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -147,6 +152,268 @@ public class UNTAPI {
|
||||
plugin.getHatHooks().remove(hook);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a complete nametag override for the specified player.
|
||||
* If the player already has a nametag, it will be swapped immediately.
|
||||
* If the player doesn't have a nametag yet, it will be cached and applied when the nametag is created.
|
||||
*
|
||||
* @param player the player to set the override for
|
||||
* @param nameTag the nametag override to set
|
||||
*/
|
||||
public void setNametagOverride(@NotNull Player player, @NotNull Settings.NameTag nameTag) {
|
||||
plugin.getNametagManager().setNametagOverride(player, nameTag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies a single property of the player's nametag by cloning the current nametag (override or config)
|
||||
* and applying the modification, then saving it as an override.
|
||||
*
|
||||
* @param player the player whose nametag should be modified
|
||||
* @param modifier a function that takes the current nametag and returns a modified nametag
|
||||
*/
|
||||
public void modifyNametagProperty(@NotNull Player player, @NotNull Function<Settings.NameTag, Settings.NameTag> modifier) {
|
||||
final Settings.NameTag current = plugin.getNametagManager().getEffectiveNametag(player);
|
||||
final Settings.NameTag modified = modifier.apply(current);
|
||||
plugin.getNametagManager().setNametagOverride(player, modified);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the nametag override for the specified player, reverting to the config nametag.
|
||||
*
|
||||
* @param player the player to remove the override for
|
||||
*/
|
||||
public void removeNametagOverride(@NotNull Player player) {
|
||||
plugin.getNametagManager().removeNametagOverride(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the specified player has an active nametag override.
|
||||
*
|
||||
* @param player the player to check
|
||||
* @return true if the player has an override, false otherwise
|
||||
*/
|
||||
public boolean hasNametagOverride(@NotNull Player player) {
|
||||
return plugin.getNametagManager().hasNametagOverride(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the nametag override for the specified player, if present.
|
||||
*
|
||||
* @param player the player to get the override for
|
||||
* @return an Optional containing the override if present, empty otherwise
|
||||
*/
|
||||
@NotNull
|
||||
public Optional<Settings.NameTag> getNametagOverride(@NotNull Player player) {
|
||||
return plugin.getNametagManager().getNametagOverride(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the effective nametag for the specified player (override if present, otherwise config).
|
||||
*
|
||||
* @param player the player to get the effective nametag for
|
||||
* @return the effective nametag
|
||||
*/
|
||||
@NotNull
|
||||
public Settings.NameTag getEffectiveNametag(@NotNull Player player) {
|
||||
return plugin.getNametagManager().getEffectiveNametag(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the config nametag for the specified player (without any override).
|
||||
*
|
||||
* @param player the player to get the config nametag for
|
||||
* @return the config nametag
|
||||
*/
|
||||
@NotNull
|
||||
public Settings.NameTag getConfigNametag(@NotNull Player player) {
|
||||
return plugin.getNametagManager().getConfigNametag(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the shift system (sneak opacity) is blocked for the specified player.
|
||||
*
|
||||
* @param player the player to set the block for
|
||||
* @param blocked true to block the shift system, false to allow it
|
||||
*/
|
||||
public void setShiftSystemBlocked(@NotNull Player player, boolean blocked) {
|
||||
plugin.getNametagManager().setShiftSystemBlocked(player, blocked);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the shift system is blocked for the specified player.
|
||||
*
|
||||
* @param player the player to check
|
||||
* @return true if the shift system is blocked, false otherwise
|
||||
*/
|
||||
public boolean isShiftSystemBlocked(@NotNull Player player) {
|
||||
return plugin.getNametagManager().isShiftSystemBlocked(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces an immediate refresh of the player's nametag.
|
||||
*
|
||||
* @param player the player to refresh
|
||||
*/
|
||||
public void forceRefresh(@NotNull Player player) {
|
||||
plugin.getNametagManager().refresh(player, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces an immediate refresh of the player's nametag with the specified force option.
|
||||
*
|
||||
* @param player the player to refresh
|
||||
* @param force whether to force the refresh
|
||||
*/
|
||||
public void forceRefresh(@NotNull Player player, boolean force) {
|
||||
plugin.getNametagManager().refresh(player, force);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the scale of the player's nametag by cloning the current nametag and applying the new scale.
|
||||
*
|
||||
* @param player the player whose nametag scale should be modified
|
||||
* @param scale the new scale value
|
||||
*/
|
||||
public void setNametagScale(@NotNull Player player, float scale) {
|
||||
final Settings.NameTag current = plugin.getNametagManager().getEffectiveNametag(player);
|
||||
final Settings.NameTag modified = current.withScale(scale);
|
||||
plugin.getNametagManager().setNametagOverride(player, modified);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the background of the player's nametag by cloning the current nametag and applying the new background.
|
||||
*
|
||||
* @param player the player whose nametag background should be modified
|
||||
* @param background the new background
|
||||
*/
|
||||
public void setNametagBackground(@NotNull Player player, @NotNull Settings.Background background) {
|
||||
final Settings.NameTag current = plugin.getNametagManager().getEffectiveNametag(player);
|
||||
final Settings.NameTag modified = current.withBackground(background);
|
||||
plugin.getNametagManager().setNametagOverride(player, modified);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the lines groups of the player's nametag by cloning the current nametag and applying the new lines groups.
|
||||
*
|
||||
* @param player the player whose nametag lines should be modified
|
||||
* @param linesGroups the new lines groups
|
||||
*/
|
||||
public void setNametagLines(@NotNull Player player, @NotNull List<Settings.LinesGroup> linesGroups) {
|
||||
final Settings.NameTag current = plugin.getNametagManager().getEffectiveNametag(player);
|
||||
final Settings.NameTag modified = current.withLinesGroups(linesGroups);
|
||||
plugin.getNametagManager().setNametagOverride(player, modified);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the billboard constraints of the player's nametag.
|
||||
* Note: This modifies the display directly and doesn't require cloning the nametag.
|
||||
*
|
||||
* @param player the player whose nametag billboard should be modified
|
||||
* @param billboard the new billboard constraints
|
||||
*/
|
||||
public void setNametagBillboard(@NotNull Player player, @NotNull AbstractDisplayMeta.BillboardConstraints billboard) {
|
||||
plugin.getNametagManager().getPacketDisplayText(player).ifPresent(packetNameTag -> {
|
||||
packetNameTag.setBillboard(billboard);
|
||||
packetNameTag.refresh();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the shadowed property of the player's nametag background by cloning the current nametag and applying the modification.
|
||||
*
|
||||
* @param player the player whose nametag shadowed property should be modified
|
||||
* @param shadowed the new shadowed value
|
||||
*/
|
||||
public void setNametagShadowed(@NotNull Player player, boolean shadowed) {
|
||||
final Settings.NameTag current = plugin.getNametagManager().getEffectiveNametag(player);
|
||||
final Settings.Background bg = current.background();
|
||||
Settings.Background newBg;
|
||||
if (bg instanceof Settings.IntegerBackground intBg) {
|
||||
newBg = new Settings.IntegerBackground(bg.enabled(), intBg.getRed(), intBg.getGreen(), intBg.getBlue(), bg.opacity(), shadowed, bg.seeThrough());
|
||||
} else if (bg instanceof Settings.HexBackground hexBg) {
|
||||
newBg = new Settings.HexBackground(bg.enabled(), hexBg.getHex(), bg.opacity(), shadowed, bg.seeThrough());
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
final Settings.NameTag modified = current.withBackground(newBg);
|
||||
plugin.getNametagManager().setNametagOverride(player, modified);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the seeThrough property of the player's nametag background by cloning the current nametag and applying the modification.
|
||||
*
|
||||
* @param player the player whose nametag seeThrough property should be modified
|
||||
* @param seeThrough the new seeThrough value
|
||||
*/
|
||||
public void setNametagSeeThrough(@NotNull Player player, boolean seeThrough) {
|
||||
final Settings.NameTag current = plugin.getNametagManager().getEffectiveNametag(player);
|
||||
final Settings.Background bg = current.background();
|
||||
Settings.Background newBg;
|
||||
if (bg instanceof Settings.IntegerBackground intBg) {
|
||||
newBg = new Settings.IntegerBackground(bg.enabled(), intBg.getRed(), intBg.getGreen(), intBg.getBlue(), bg.opacity(), bg.shadowed(), seeThrough);
|
||||
} else if (bg instanceof Settings.HexBackground hexBg) {
|
||||
newBg = new Settings.HexBackground(bg.enabled(), hexBg.getHex(), bg.opacity(), bg.shadowed(), seeThrough);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
final Settings.NameTag modified = current.withBackground(newBg);
|
||||
plugin.getNametagManager().setNametagOverride(player, modified);
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces a specific component as the nametag for a player.
|
||||
* This overrides any existing nametag configuration or overrides.
|
||||
*
|
||||
* @param player the player whose nametag should be forced
|
||||
* @param component the component to display as the nametag
|
||||
*/
|
||||
public void setForcedNametag(@NotNull Player player, @NotNull Component component) {
|
||||
plugin.getNametagManager().getPacketDisplayText(player).ifPresent(packetNameTag -> {
|
||||
packetNameTag.setForcedNameTag(component);
|
||||
packetNameTag.refresh();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces a specific component as the nametag for a player, visible only to a specific viewer.
|
||||
* This overrides any existing nametag configuration or overrides for that specific viewer.
|
||||
*
|
||||
* @param player the player whose nametag should be forced
|
||||
* @param viewer the player who will see the forced nametag
|
||||
* @param component the component to display as the nametag
|
||||
*/
|
||||
public void setForcedNametag(@NotNull Player player, @NotNull Player viewer, @NotNull Component component) {
|
||||
plugin.getNametagManager().getPacketDisplayText(player).ifPresent(packetNameTag -> {
|
||||
packetNameTag.setForcedNameTag(viewer.getUniqueId(), component);
|
||||
packetNameTag.refreshForPlayer(viewer);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the forced nametag for a player.
|
||||
*
|
||||
* @param player the player whose forced nametag should be cleared
|
||||
*/
|
||||
public void clearForcedNametag(@NotNull Player player) {
|
||||
plugin.getNametagManager().getPacketDisplayText(player).ifPresent(packetNameTag -> {
|
||||
packetNameTag.clearForcedNameTag();
|
||||
packetNameTag.refresh();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the forced nametag for a player, for a specific viewer.
|
||||
*
|
||||
* @param player the player whose forced nametag should be cleared
|
||||
* @param viewer the player who was seeing the forced nametag
|
||||
*/
|
||||
public void clearForcedNametag(@NotNull Player player, @NotNull Player viewer) {
|
||||
plugin.getNametagManager().getPacketDisplayText(player).ifPresent(packetNameTag -> {
|
||||
packetNameTag.clearForcedNameTag(viewer.getUniqueId());
|
||||
packetNameTag.refreshForPlayer(viewer);
|
||||
});
|
||||
}
|
||||
|
||||
static final class NotRegisteredException extends IllegalStateException {
|
||||
|
||||
private static final String MESSAGE = """
|
||||
|
||||
@@ -30,6 +30,7 @@ public class MainCommand {
|
||||
public void onReload(@Sender CommandSender sender) {
|
||||
plugin.getConfigManager().reload();
|
||||
plugin.getNametagManager().reload();
|
||||
plugin.getPlaceholderManager().reload();
|
||||
plugin.getKyoriManager().sendMessage(sender, Formatter.LEGACY.format(plugin, sender, "&aUnlimitedNameTags has been reloaded!"));
|
||||
}
|
||||
|
||||
@@ -55,7 +56,7 @@ public class MainCommand {
|
||||
@Command(name = "show", desc = "Shows the nametag", usage = "show")
|
||||
@Require(value = "unt.show", message = "&cYou do not have permission to show the nametag")
|
||||
public void onShow(@Sender CommandSender sender, Player target) {
|
||||
plugin.getNametagManager().showToTrackedPlayers(target, plugin.getTrackerManager().getTrackedPlayers(target.getUniqueId()));
|
||||
plugin.getNametagManager().showToTrackedPlayers(target);
|
||||
}
|
||||
|
||||
@Command(name = "refresh", desc = "Refreshes the nametag of a player for you", usage = "refresh")
|
||||
@@ -105,5 +106,4 @@ public class MainCommand {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package org.alexdev.unlimitednametags.config;
|
||||
|
||||
import de.exlll.configlib.Comment;
|
||||
import de.exlll.configlib.Configuration;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Configuration
|
||||
@SuppressWarnings({"FieldMayBeFinal", "FieldCanBeLocal"})
|
||||
@Getter
|
||||
public class Advanced {
|
||||
|
||||
@Comment({
|
||||
"Optional rules to raise the nametag when the worn helmet matches.",
|
||||
"Higher priority is evaluated first. The first matching rule wins (same behavior as other hat hooks).",
|
||||
"Create advanced.yml in the plugin folder manually; it is never auto-generated."
|
||||
})
|
||||
private List<HelmetHeightRule> helmetHeightRules = new ArrayList<>();
|
||||
|
||||
@Configuration
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
public static class HelmetHeightRule {
|
||||
|
||||
@Comment("Higher value = checked before lower priority rules.")
|
||||
private int priority;
|
||||
|
||||
@Comment("Vertical offset (same unit as Nexo/Oraxen hat hooks). Must be > 0.")
|
||||
private double height;
|
||||
|
||||
@Comment("Bukkit material name, e.g. LEATHER_HELMET. Omit to ignore material.")
|
||||
private String material;
|
||||
|
||||
@Comment("Exact CustomModelData. Ignored if custom-model-data range below is set.")
|
||||
private Integer customModelData;
|
||||
|
||||
@Comment("Inclusive CMD range; both must be set to use a range.")
|
||||
private Integer customModelDataMin;
|
||||
|
||||
private Integer customModelDataMax;
|
||||
|
||||
@Comment("Equippable model key (1.21.3+), e.g. mypack:item/my_hat")
|
||||
private String equippableModel;
|
||||
|
||||
@Comment("If non-empty, the player must be in one of these world names.")
|
||||
private List<String> worlds = new ArrayList<>();
|
||||
|
||||
@Comment("If set, the player must have this permission.")
|
||||
private String permission;
|
||||
|
||||
public boolean definesItemMatch() {
|
||||
final boolean mat = material != null && !material.isEmpty();
|
||||
final boolean eq = equippableModel != null && !equippableModel.isEmpty();
|
||||
final boolean cmdExact = customModelData != null;
|
||||
final boolean cmdRange = customModelDataMin != null && customModelDataMax != null;
|
||||
return mat || eq || cmdExact || cmdRange;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,13 +7,16 @@ import de.exlll.configlib.YamlConfigurations;
|
||||
import lombok.Getter;
|
||||
import org.alexdev.unlimitednametags.UnlimitedNameTags;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.Material;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Level;
|
||||
|
||||
@Getter
|
||||
public class ConfigManager {
|
||||
@@ -27,6 +30,7 @@ public class ConfigManager {
|
||||
|
||||
private final UnlimitedNameTags plugin;
|
||||
private Settings settings;
|
||||
private Advanced advanced = new Advanced();
|
||||
private boolean compiled;
|
||||
|
||||
public ConfigManager(@NotNull UnlimitedNameTags plugin) {
|
||||
@@ -55,6 +59,7 @@ public class ConfigManager {
|
||||
PROPERTIES
|
||||
);
|
||||
checkData();
|
||||
loadAdvancedConfig(false);
|
||||
return Optional.empty();
|
||||
} catch (Exception e) {
|
||||
return Optional.of(e);
|
||||
@@ -64,6 +69,58 @@ public class ConfigManager {
|
||||
public void reload() {
|
||||
settings = YamlConfigurations.load(new File(plugin.getDataFolder(), "settings.yml").toPath(), Settings.class, PROPERTIES);
|
||||
checkData();
|
||||
loadAdvancedConfig(true);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Advanced getAdvanced() {
|
||||
return advanced;
|
||||
}
|
||||
|
||||
private void loadAdvancedConfig(boolean keepPreviousOnFailure) {
|
||||
final File file = new File(plugin.getDataFolder(), "advanced.yml");
|
||||
if (!file.exists()) {
|
||||
advanced = new Advanced();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
advanced = YamlConfigurations.load(file.toPath(), Advanced.class, PROPERTIES);
|
||||
validateAdvancedRules();
|
||||
plugin.getLogger().info("Loaded optional advanced.yml");
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().log(Level.WARNING, "Failed to load advanced.yml", e);
|
||||
if (!keepPreviousOnFailure) {
|
||||
advanced = new Advanced();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void validateAdvancedRules() {
|
||||
final List<Advanced.HelmetHeightRule> rules = advanced.getHelmetHeightRules();
|
||||
if (rules == null) {
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < rules.size(); i++) {
|
||||
final Advanced.HelmetHeightRule rule = rules.get(i);
|
||||
if (!rule.definesItemMatch()) {
|
||||
plugin.getLogger().warning("advanced.yml helmetHeightRules[" + i + "] has no item matcher; it will never apply.");
|
||||
}
|
||||
if (rule.getHeight() <= 0) {
|
||||
plugin.getLogger().warning("advanced.yml helmetHeightRules[" + i + "] height must be > 0; it will never apply.");
|
||||
}
|
||||
final boolean minSet = rule.getCustomModelDataMin() != null;
|
||||
final boolean maxSet = rule.getCustomModelDataMax() != null;
|
||||
if (minSet != maxSet) {
|
||||
plugin.getLogger().warning("advanced.yml helmetHeightRules[" + i + "] must set both customModelDataMin and customModelDataMax for a CMD range.");
|
||||
}
|
||||
if (minSet && maxSet && rule.getCustomModelData() != null) {
|
||||
plugin.getLogger().warning("advanced.yml helmetHeightRules[" + i + "] customModelData is ignored when customModelDataMin/Max range is set.");
|
||||
}
|
||||
if (rule.getMaterial() != null && !rule.getMaterial().isEmpty()
|
||||
&& Material.matchMaterial(rule.getMaterial(), false) == null) {
|
||||
plugin.getLogger().warning("advanced.yml helmetHeightRules[" + i + "] unknown material: " + rule.getMaterial());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void checkData() {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.alexdev.unlimitednametags.config;
|
||||
|
||||
import de.themoep.minedown.adventure.MineDown;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import net.kyori.adventure.text.Component;
|
||||
@@ -19,10 +18,6 @@ import java.util.regex.Pattern;
|
||||
@SuppressWarnings("unused")
|
||||
public enum Formatter {
|
||||
|
||||
MINEDOWN(
|
||||
(plugin, player, text) -> new MineDown(text).toComponent(),
|
||||
"MineDown"
|
||||
),
|
||||
MINIMESSAGE(
|
||||
(plugin, player, text) -> plugin.getHook(MiniPlaceholdersHook.class)
|
||||
.map(hook -> hook.format(text, player))
|
||||
|
||||
@@ -14,6 +14,7 @@ import org.alexdev.unlimitednametags.UnlimitedNameTags;
|
||||
import org.bukkit.Color;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@@ -24,7 +25,7 @@ public class Settings {
|
||||
|
||||
private Map<String, NameTag> nameTags = new LinkedHashMap<>() {{
|
||||
put("staffer", new NameTag("nametag.staffer", List.of(new LinesGroup(List.of("%luckperms_prefix% %player_name% %luckperms_suffix%"), List.of(new GlobalModifier(true)))),
|
||||
new IntegerBackground(true, 255, 0, 0, 255, true, false), 1f));
|
||||
new IntegerBackground(false, 255, 0, 0, 255, true, false), 1f));
|
||||
put("default", new NameTag("nametag.default", List.of(new LinesGroup(List.of("%luckperms_prefix% %player_name% %luckperms_suffix%"), List.of(new GlobalModifier(true))),
|
||||
new LinesGroup(List.of("Rich Player"), List.of(new ConditionalModifier("%vault_eco_balance%", ">", "1000")))),
|
||||
new HexBackground(false, "#ffffff", 255, false, false), 1f));
|
||||
@@ -36,7 +37,7 @@ public class Settings {
|
||||
|
||||
public NameTag getNametag(Player player) {
|
||||
return nameTags.entrySet().stream()
|
||||
.filter(entry -> player.hasPermission(entry.getValue().permission))
|
||||
.filter(entry -> entry.getValue().permission == null || player.hasPermission(entry.getValue().permission))
|
||||
.findFirst()
|
||||
.map(Map.Entry::getValue)
|
||||
.orElse(nameTags.get("default"));
|
||||
@@ -57,7 +58,7 @@ public class Settings {
|
||||
@Comment("""
|
||||
Which text formatter to use (MINIMESSAGE, MINEDOWN, LEGACY or UNIVERSAL)\s
|
||||
Take note that UNIVERSAL is the most resource intensive but it supports all formatting options (except for MINEDOWN)\s
|
||||
|
||||
|
||||
(&x&0&8&4&c&f&bc LEGACY OF LEGACY - Tcfbc LEGACY - Tcfbc& MINEDOWN - <color:#084cfbc> MINIMESSAGE)""")
|
||||
@Setter
|
||||
private Formatter format = Formatter.MINIMESSAGE;
|
||||
@@ -65,6 +66,10 @@ public class Settings {
|
||||
@Comment("Whether to disable the default name tag or not.")
|
||||
private boolean disableDefaultNameTag = true;
|
||||
|
||||
@Comment("This option works if disableDefaultNameTag is enabled. This will skip the team internal cache and will make all default nametags invisible. " +
|
||||
"The use case is when there is a npc from a plugin that doesn't use display entities / armorstands for nametags, and there is an online player with the name of the npc.")
|
||||
private boolean forceDisableDefaultNameTag = false;
|
||||
|
||||
private boolean removeEmptyLines = true;
|
||||
|
||||
@Comment("Whether to see the NameTag of a user only while pointing at them")
|
||||
@@ -73,6 +78,17 @@ public class Settings {
|
||||
@Comment("Whether to see your own NameTag (Similar to nametag mod of Lunar Client)")
|
||||
private boolean showCurrentNameTag = false;
|
||||
|
||||
@Comment("""
|
||||
Whether to cache components for some time and reuse them or not.
|
||||
This is useful for performance improvements where a lot of gradients are used, but it uses a bit more memory.
|
||||
It could create problems with MiniPlaceholders, so it is disabled by default.""")
|
||||
private boolean componentCaching = false;
|
||||
|
||||
@Comment("How long to cache placeholders for (in ticks)")
|
||||
private int placeholderCacheTime = 1;
|
||||
|
||||
private boolean enableRelationalPlaceholders = false;
|
||||
|
||||
public float getViewDistance() {
|
||||
return viewDistance / 160;
|
||||
}
|
||||
@@ -85,10 +101,35 @@ public class Settings {
|
||||
public record PlaceholderReplacement(String placeholder, String replacement) {
|
||||
}
|
||||
|
||||
public record NameTag(String permission, List<LinesGroup> linesGroups, Background background, float scale) {
|
||||
public record NameTag(@Nullable String permission, @NotNull List<LinesGroup> linesGroups, @NotNull Background background, float scale) {
|
||||
|
||||
public NameTag(@NotNull List<LinesGroup> linesGroups, @NotNull Background background, float scale) {
|
||||
this(null, linesGroups, background, scale);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public NameTag withPermission(@Nullable String permission) {
|
||||
return new NameTag(permission, linesGroups, background, scale);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public NameTag withLinesGroups(@NotNull List<LinesGroup> linesGroups) {
|
||||
return new NameTag(permission, linesGroups, background, scale);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public NameTag withBackground(@NotNull Background background) {
|
||||
return new NameTag(permission, linesGroups, background, scale);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public NameTag withScale(float scale) {
|
||||
return new NameTag(permission, linesGroups, background, scale);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public record LinesGroup(List<String> lines, List<Modifier> modifiers) {
|
||||
public record LinesGroup(@NotNull List<String> lines, @Nullable List<Modifier> modifiers) {
|
||||
|
||||
}
|
||||
|
||||
@@ -179,7 +220,12 @@ public class Settings {
|
||||
|
||||
@Override
|
||||
public Color getColor() {
|
||||
return !enabled ? Color.BLACK.setAlpha(0) : Color.fromRGB(red, green, blue).setAlpha(opacity);
|
||||
return !enabled() ? Color.BLACK.setAlpha(0) : Color.fromRGB(red, green, blue).setAlpha(opacity());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public IntegerBackground clone() {
|
||||
return new IntegerBackground(enabled(), getRed(), getGreen(), getBlue(), opacity(), shadowed(), seeThrough());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,7 +244,12 @@ public class Settings {
|
||||
@Override
|
||||
public Color getColor() {
|
||||
int hex = Integer.parseInt(this.hex.substring(1), 16);
|
||||
return !enabled ? Color.BLACK.setAlpha(0) : Color.fromRGB(hex).setAlpha(opacity);
|
||||
return !enabled() ? Color.BLACK.setAlpha(0) : Color.fromRGB(hex).setAlpha(opacity());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public HexBackground clone() {
|
||||
return new HexBackground(enabled(), getHex(), opacity(), shadowed(), seeThrough());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,336 @@
|
||||
package org.alexdev.unlimitednametags.data;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* High-performance thread-safe Multimap implementation optimized for concurrent access.
|
||||
* Uses ConcurrentHashMap for both the main map and value collections.
|
||||
*
|
||||
* @param <K> the type of keys
|
||||
* @param <V> the type of values
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class ConcurrentMultimap<K, V> {
|
||||
|
||||
private final ConcurrentMap<K, Set<V>> map;
|
||||
private final Supplier<Set<V>> setSupplier;
|
||||
|
||||
/**
|
||||
* Creates a new ConcurrentMultimap with ConcurrentHashMap.newKeySet() as the set supplier.
|
||||
*/
|
||||
public ConcurrentMultimap() {
|
||||
this(ConcurrentHashMap::newKeySet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new ConcurrentMultimap with a custom set supplier.
|
||||
*
|
||||
* @param setSupplier supplier for creating new value sets (must be thread-safe)
|
||||
*/
|
||||
public ConcurrentMultimap(@NotNull Supplier<Set<V>> setSupplier) {
|
||||
this.map = new ConcurrentHashMap<>();
|
||||
this.setSupplier = setSupplier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new ConcurrentMultimap with an expected size hint.
|
||||
*
|
||||
* @param expectedSize the expected number of keys
|
||||
*/
|
||||
public ConcurrentMultimap(int expectedSize) {
|
||||
this(expectedSize, ConcurrentHashMap::newKeySet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new ConcurrentMultimap with an expected size hint and custom set supplier.
|
||||
*
|
||||
* @param expectedSize the expected number of keys
|
||||
* @param setSupplier supplier for creating new value sets (must be thread-safe)
|
||||
*/
|
||||
public ConcurrentMultimap(int expectedSize, @NotNull Supplier<Set<V>> setSupplier) {
|
||||
this.map = new ConcurrentHashMap<>(expectedSize);
|
||||
this.setSupplier = setSupplier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Associates the specified value with the specified key.
|
||||
*
|
||||
* @param key the key
|
||||
* @param value the value to add
|
||||
* @return true if the value was added, false if it was already present
|
||||
*/
|
||||
public boolean put(@NotNull K key, @NotNull V value) {
|
||||
return map.computeIfAbsent(key, k -> setSupplier.get()).add(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all values to the specified key.
|
||||
*
|
||||
* @param key the key
|
||||
* @param values the values to add
|
||||
* @return true if any value was added
|
||||
*/
|
||||
public boolean putAll(@NotNull K key, @NotNull Collection<? extends V> values) {
|
||||
if (values.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return map.computeIfAbsent(key, k -> setSupplier.get()).addAll(values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all values associated with the specified key.
|
||||
* The returned collection is a live view - modifications will affect this multimap.
|
||||
*
|
||||
* @param key the key
|
||||
* @return the collection of values, never null (may be empty)
|
||||
*/
|
||||
@NotNull
|
||||
public Collection<V> get(@NotNull K key) {
|
||||
Set<V> values = map.get(key);
|
||||
return values != null ? values : Collections.emptySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all values associated with the specified key as a defensive copy.
|
||||
*
|
||||
* @param key the key
|
||||
* @return a new collection containing all values
|
||||
*/
|
||||
@NotNull
|
||||
public Collection<V> getCopy(@NotNull K key) {
|
||||
Set<V> values = map.get(key);
|
||||
if (values == null || values.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return new ArrayList<>(values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a single key-value pair.
|
||||
*
|
||||
* @param key the key
|
||||
* @param value the value to remove
|
||||
* @return true if the value was removed
|
||||
*/
|
||||
public boolean remove(@NotNull K key, @NotNull V value) {
|
||||
Set<V> values = map.get(key);
|
||||
if (values == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean removed = values.remove(value);
|
||||
|
||||
// Clean up empty sets to prevent memory leaks
|
||||
if (removed && values.isEmpty()) {
|
||||
map.remove(key, values);
|
||||
}
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all values associated with the specified key.
|
||||
*
|
||||
* @param key the key
|
||||
* @return the collection of values that were removed, never null
|
||||
*/
|
||||
@NotNull
|
||||
public Collection<V> removeAll(@NotNull K key) {
|
||||
Set<V> removed = map.remove(key);
|
||||
return removed != null ? removed : Collections.emptySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces all values for a key with the specified values.
|
||||
*
|
||||
* @param key the key
|
||||
* @param values the new values
|
||||
* @return the previous values associated with the key
|
||||
*/
|
||||
@NotNull
|
||||
public Collection<V> replaceValues(@NotNull K key, @NotNull Collection<? extends V> values) {
|
||||
if (values.isEmpty()) {
|
||||
return removeAll(key);
|
||||
}
|
||||
|
||||
Set<V> newSet = setSupplier.get();
|
||||
newSet.addAll(values);
|
||||
|
||||
Set<V> previous = map.put(key, newSet);
|
||||
return previous != null ? previous : Collections.emptySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this multimap contains the specified key.
|
||||
*
|
||||
* @param key the key
|
||||
* @return true if the key exists
|
||||
*/
|
||||
public boolean containsKey(@NotNull K key) {
|
||||
return map.containsKey(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this multimap contains the specified key-value pair.
|
||||
*
|
||||
* @param key the key
|
||||
* @param value the value
|
||||
* @return true if the pair exists
|
||||
*/
|
||||
public boolean containsEntry(@NotNull K key, @NotNull V value) {
|
||||
Set<V> values = map.get(key);
|
||||
return values != null && values.contains(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of key-value pairs in this multimap.
|
||||
*
|
||||
* @return the total number of values across all keys
|
||||
*/
|
||||
public int size() {
|
||||
return map.values().stream().mapToInt(Set::size).sum();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of values associated with the specified key.
|
||||
*
|
||||
* @param key the key
|
||||
* @return the number of values for the key
|
||||
*/
|
||||
public int size(@NotNull K key) {
|
||||
Set<V> values = map.get(key);
|
||||
return values != null ? values.size() : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this multimap contains no key-value pairs.
|
||||
*
|
||||
* @return true if empty
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return map.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all key-value pairs from this multimap.
|
||||
*/
|
||||
public void clear() {
|
||||
map.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a set view of all keys in this multimap.
|
||||
*
|
||||
* @return the set of keys
|
||||
*/
|
||||
@NotNull
|
||||
public Set<K> keySet() {
|
||||
return map.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a collection view of all values in this multimap.
|
||||
* This creates a new collection on each call.
|
||||
*
|
||||
* @return all values
|
||||
*/
|
||||
@NotNull
|
||||
public Collection<V> values() {
|
||||
List<V> allValues = new ArrayList<>();
|
||||
for (Set<V> values : map.values()) {
|
||||
allValues.addAll(values);
|
||||
}
|
||||
return allValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a collection view of all entries in this multimap.
|
||||
*
|
||||
* @return all key-value pairs as Map.Entry objects
|
||||
*/
|
||||
@NotNull
|
||||
public Collection<Map.Entry<K, V>> entries() {
|
||||
List<Map.Entry<K, V>> entries = new ArrayList<>();
|
||||
for (Map.Entry<K, Set<V>> mapEntry : map.entrySet()) {
|
||||
K key = mapEntry.getKey();
|
||||
for (V value : mapEntry.getValue()) {
|
||||
entries.add(new AbstractMap.SimpleImmutableEntry<>(key, value));
|
||||
}
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map view where each key is associated with a collection of values.
|
||||
* The returned map is a snapshot - modifications will not affect this multimap.
|
||||
*
|
||||
* @return a map representation
|
||||
*/
|
||||
@NotNull
|
||||
public Map<K, Collection<V>> asMap() {
|
||||
Map<K, Collection<V>> result = new HashMap<>();
|
||||
for (Map.Entry<K, Set<V>> entry : map.entrySet()) {
|
||||
result.put(entry.getKey(), new ArrayList<>(entry.getValue()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the given action for each key-value pair.
|
||||
*
|
||||
* @param action the action to perform
|
||||
*/
|
||||
public void forEach(@NotNull java.util.function.BiConsumer<? super K, ? super V> action) {
|
||||
for (Map.Entry<K, Set<V>> entry : map.entrySet()) {
|
||||
K key = entry.getKey();
|
||||
for (V value : entry.getValue()) {
|
||||
action.accept(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a specific value from all keys.
|
||||
* Optimized for performance - avoids iteration over all values.
|
||||
*
|
||||
* @param value the value to remove from all keys
|
||||
* @return the number of keys from which the value was removed
|
||||
*/
|
||||
public int removeValueFromAll(@NotNull V value) {
|
||||
int removedCount = 0;
|
||||
for (Map.Entry<K, Set<V>> entry : map.entrySet()) {
|
||||
Set<V> values = entry.getValue();
|
||||
if (values.remove(value)) {
|
||||
removedCount++;
|
||||
// Clean up empty sets
|
||||
if (values.isEmpty()) {
|
||||
map.remove(entry.getKey(), values);
|
||||
}
|
||||
}
|
||||
}
|
||||
return removedCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return map.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof ConcurrentMultimap)) return false;
|
||||
ConcurrentMultimap<?, ?> that = (ConcurrentMultimap<?, ?>) o;
|
||||
return map.equals(that.map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return map.hashCode();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
package org.alexdev.unlimitednametags.data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
public class ConcurrentSetMultimap<K, V> {
|
||||
|
||||
private final ConcurrentHashMap<K, ConcurrentLinkedQueue<V>> map = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Adds the value to the specified key.
|
||||
* If the value is already present for the key, it will not be added.
|
||||
*
|
||||
* @param key the key
|
||||
* @param value the value to add
|
||||
* @return true if the value was added, false otherwise
|
||||
*/
|
||||
public boolean put(K key, V value) {
|
||||
// Create a new queue if one does not already exist for the key
|
||||
ConcurrentLinkedQueue<V> queue = map.computeIfAbsent(key, k -> new ConcurrentLinkedQueue<>());
|
||||
// Ensure uniqueness by synchronizing on the queue for this key
|
||||
synchronized (queue) {
|
||||
if (!queue.contains(value)) {
|
||||
return queue.add(value);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all values from the provided collection to the specified key.
|
||||
* Only adds values that are not already associated with the key.
|
||||
*
|
||||
* @param key the key
|
||||
* @param values the collection of values to add
|
||||
* @return true if at least one value was added, false otherwise
|
||||
*/
|
||||
public boolean putAll(K key, Collection<? extends V> values) {
|
||||
// Create a new queue if one does not already exist for the key
|
||||
ConcurrentLinkedQueue<V> queue = map.computeIfAbsent(key, k -> new ConcurrentLinkedQueue<>());
|
||||
boolean changed = false;
|
||||
synchronized (queue) {
|
||||
for (V value : values) {
|
||||
if (!queue.contains(value)) {
|
||||
queue.add(value);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a set of values associated with the specified key.
|
||||
*
|
||||
* @param key the key
|
||||
* @return a Set containing the associated values, or an empty Set if the key does not exist
|
||||
*/
|
||||
public Set<V> get(K key) {
|
||||
ConcurrentLinkedQueue<V> queue = map.get(key);
|
||||
if (queue == null) {
|
||||
return new HashSet<>();
|
||||
}
|
||||
// Create a HashSet of the values; synchronize to avoid concurrent modification issues
|
||||
synchronized (queue) {
|
||||
return new HashSet<>(queue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the specified value associated with the key.
|
||||
* If the queue becomes empty after removal, the key is removed from the map.
|
||||
*
|
||||
* @param key the key
|
||||
* @param value the value to remove
|
||||
* @return true if the value was removed, false otherwise
|
||||
*/
|
||||
public boolean remove(K key, V value) {
|
||||
ConcurrentLinkedQueue<V> queue = map.get(key);
|
||||
if (queue == null) {
|
||||
return false;
|
||||
}
|
||||
synchronized (queue) {
|
||||
boolean removed = queue.remove(value);
|
||||
if (queue.isEmpty()) {
|
||||
map.remove(key, queue);
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all values associated with the specified key.
|
||||
*
|
||||
* @param key the key
|
||||
* @return a Set containing the removed values, or an empty Set if the key did not exist
|
||||
*/
|
||||
public Set<V> removeAll(K key) {
|
||||
ConcurrentLinkedQueue<V> queue = map.remove(key);
|
||||
if (queue == null) {
|
||||
return new HashSet<>();
|
||||
}
|
||||
synchronized (queue) {
|
||||
return new HashSet<>(queue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all key-value associations in the multimap.
|
||||
*/
|
||||
public void clear() {
|
||||
map.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a collection view of all values present in the multimap.
|
||||
* Note: This is a snapshot of the values at the time of invocation.
|
||||
*
|
||||
* @return a Collection containing all values from all keys
|
||||
*/
|
||||
public Collection<V> values() {
|
||||
Collection<V> allValues = new ArrayList<>();
|
||||
for (ConcurrentLinkedQueue<V> queue : map.values()) {
|
||||
synchronized (queue) {
|
||||
allValues.addAll(queue);
|
||||
}
|
||||
}
|
||||
return allValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of keys present in the multimap.
|
||||
*
|
||||
* @return a Set containing all keys
|
||||
*/
|
||||
public Set<K> keySet() {
|
||||
return map.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a set of key-value pairs in the multimap.
|
||||
* Each entry consists of a key and the corresponding set of values.
|
||||
*
|
||||
* @return a Set of Map.Entry containing keys and their associated value sets
|
||||
*/
|
||||
public Set<java.util.Map.Entry<K, Set<V>>> entrySet() {
|
||||
Set<java.util.Map.Entry<K, Set<V>>> entries = new HashSet<>();
|
||||
for (K key : map.keySet()) {
|
||||
entries.add(new java.util.AbstractMap.SimpleEntry<>(key, get(key)));
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
//forEach <key, Set<value>>
|
||||
public void forEach(java.util.function.BiConsumer<K, Set<V>> action) {
|
||||
for (K key : map.keySet()) {
|
||||
action.accept(key, get(key));
|
||||
}
|
||||
}
|
||||
|
||||
public boolean containsEntry(K key, V value) {
|
||||
ConcurrentLinkedQueue<V> queue = map.get(key);
|
||||
if (queue == null) {
|
||||
return false;
|
||||
}
|
||||
synchronized (queue) {
|
||||
return queue.contains(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package org.alexdev.unlimitednametags.data;
|
||||
|
||||
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerTeams;
|
||||
import com.google.common.collect.Sets;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class TeamData {
|
||||
|
||||
private final String teamName;
|
||||
private WrapperPlayServerTeams.ScoreBoardTeamInfo teamInfo;
|
||||
private final Set<String> members;
|
||||
private boolean changedVisibility = false;
|
||||
|
||||
public TeamData(@NotNull String teamName, @NotNull WrapperPlayServerTeams.ScoreBoardTeamInfo teamInfo) {
|
||||
this.teamName = teamName;
|
||||
this.teamInfo = teamInfo;
|
||||
this.members = Sets.newConcurrentHashSet();
|
||||
}
|
||||
|
||||
public TeamData(@NotNull String teamName, @NotNull WrapperPlayServerTeams.ScoreBoardTeamInfo teamInfo, @NotNull Set<String> members) {
|
||||
this.teamName = teamName;
|
||||
this.teamInfo = teamInfo;
|
||||
this.members = Sets.newConcurrentHashSet(members);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
package org.alexdev.unlimitednametags.events;
|
||||
|
||||
import com.github.retrooper.packetevents.PacketEvents;
|
||||
import com.github.retrooper.packetevents.event.PacketListenerAbstract;
|
||||
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||
import com.github.retrooper.packetevents.event.PacketSendEvent;
|
||||
import com.github.retrooper.packetevents.protocol.entity.data.EntityData;
|
||||
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
|
||||
import com.github.retrooper.packetevents.util.Vector3f;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientEntityAction;
|
||||
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerEntityMetadata;
|
||||
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerSetPassengers;
|
||||
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerTeams;
|
||||
import org.alexdev.unlimitednametags.UnlimitedNameTags;
|
||||
import org.alexdev.unlimitednametags.hook.ViaVersionHook;
|
||||
import org.alexdev.unlimitednametags.packet.PacketNameTag;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class PacketEventsListener extends PacketListenerAbstract {
|
||||
|
||||
private final UnlimitedNameTags plugin;
|
||||
|
||||
public PacketEventsListener(UnlimitedNameTags plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public void onEnable() {
|
||||
PacketEvents.getAPI().getEventManager().registerListener(this);
|
||||
}
|
||||
|
||||
public void onPacketSend(@NotNull PacketSendEvent event) {
|
||||
if (event.getPacketType() == PacketType.Play.Server.TEAMS) {
|
||||
handleTeams(event);
|
||||
} else if (event.getPacketType() == PacketType.Play.Server.SET_PASSENGERS) {
|
||||
handlePassengers(event);
|
||||
} else if (event.getPacketType() == PacketType.Play.Server.ENTITY_METADATA) {
|
||||
handleMetaData(event);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceive(@NotNull PacketReceiveEvent event) {
|
||||
if (event.getPacketType() == PacketType.Play.Client.ENTITY_ACTION) {
|
||||
handleUseEntity(event);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleUseEntity(PacketReceiveEvent event) {
|
||||
if (!(event.getPlayer() instanceof Player)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final WrapperPlayClientEntityAction packet = new WrapperPlayClientEntityAction(event);
|
||||
final Optional<? extends Player> player = plugin.getPlayerListener().getPlayerFromEntityId(packet.getEntityId());
|
||||
if (player.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
switch (packet.getAction()) {
|
||||
case START_SNEAKING -> plugin.getNametagManager().updateSneaking(player.get(), true);
|
||||
case STOP_SNEAKING -> plugin.getNametagManager().updateSneaking(player.get(), false);
|
||||
case START_FLYING_WITH_ELYTRA -> plugin.getPlayerListener().logicElytra(player.get());
|
||||
}
|
||||
}
|
||||
|
||||
private void handlePassengers(@NotNull PacketSendEvent event) {
|
||||
final WrapperPlayServerSetPassengers packet = new WrapperPlayServerSetPassengers(event);
|
||||
final Optional<? extends Player> player = plugin.getPlayerListener().getPlayerFromEntityId(packet.getEntityId());
|
||||
if (player.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Optional<PacketNameTag> optionalPacketDisplayText = plugin.getNametagManager().getPacketDisplayText(player.get());
|
||||
if (optionalPacketDisplayText.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final List<Integer> passengers = Arrays.stream(packet.getPassengers()).boxed().collect(Collectors.toList());
|
||||
|
||||
plugin.getPacketManager().setPassengers(player.get(), passengers);
|
||||
if(!passengers.contains(optionalPacketDisplayText.get().getEntityId())) {
|
||||
passengers.add(optionalPacketDisplayText.get().getEntityId());
|
||||
packet.setPassengers(passengers.stream().mapToInt(i -> i).toArray());
|
||||
}
|
||||
}
|
||||
|
||||
private void handleTeams(@NotNull PacketSendEvent event) {
|
||||
if (!plugin.getConfigManager().getSettings().isDisableDefaultNameTag()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Player player = Bukkit.getPlayer(event.getUser().getUUID());
|
||||
if(player == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(plugin.getHook(ViaVersionHook.class).map(h -> h.hasNotTextDisplays(player)).orElse(false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final WrapperPlayServerTeams packet = new WrapperPlayServerTeams(event);
|
||||
if (packet.getTeamMode() == WrapperPlayServerTeams.TeamMode.CREATE || packet.getTeamMode() == WrapperPlayServerTeams.TeamMode.UPDATE) {
|
||||
packet.getTeamInfo().ifPresent(t -> t.setTagVisibility(WrapperPlayServerTeams.NameTagVisibility.NEVER));
|
||||
event.markForReEncode(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleMetaData(@NotNull PacketSendEvent event) {
|
||||
if (!(event.getPlayer() instanceof Player)) {
|
||||
return;
|
||||
}
|
||||
int protocol = event.getUser().getClientVersion().getProtocolVersion();
|
||||
//handle metadata for : bedrock players && client with version 1.20.1 or lower
|
||||
if (protocol >= 764) {
|
||||
return;
|
||||
}
|
||||
|
||||
final WrapperPlayServerEntityMetadata packet = new WrapperPlayServerEntityMetadata(event);
|
||||
final Optional<PacketNameTag> textDisplay = plugin.getNametagManager().getPacketDisplayText(packet.getEntityId());
|
||||
if (textDisplay.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (final EntityData eData : packet.getEntityMetadata()) {
|
||||
if (eData.getIndex() == 11) {
|
||||
final Vector3f old = (Vector3f) eData.getValue();
|
||||
final Vector3f newV = new Vector3f(old.getX(), old.getY() + 0.45f, old.getZ());
|
||||
eData.setValue(newV);
|
||||
event.markForReEncode(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onDisable() {
|
||||
PacketEvents.getAPI().getEventManager().unregisterListener(this);
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
package org.alexdev.unlimitednametags.events;
|
||||
|
||||
import com.github.retrooper.packetevents.PacketEvents;
|
||||
import com.google.common.collect.*;
|
||||
import lombok.Getter;
|
||||
import org.alexdev.unlimitednametags.UnlimitedNameTags;
|
||||
import org.alexdev.unlimitednametags.packet.PacketNameTag;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
public class TrackerManager {
|
||||
|
||||
private final UnlimitedNameTags plugin;
|
||||
@Getter
|
||||
private final SetMultimap<UUID, UUID> trackedPlayers;
|
||||
|
||||
public TrackerManager(UnlimitedNameTags plugin) {
|
||||
this.plugin = plugin;
|
||||
this.trackedPlayers = Multimaps.newSetMultimap(Maps.newConcurrentMap(), Sets::newConcurrentHashSet);
|
||||
loadTracker();
|
||||
}
|
||||
|
||||
@SuppressWarnings({"deprecation"})
|
||||
private void loadTracker() {
|
||||
final boolean isPaper = plugin.isPaper();
|
||||
Bukkit.getOnlinePlayers().forEach(player -> {
|
||||
final Set<Player> currentTracked = (isPaper ? player.getTrackedPlayers() : player.getTrackedBy()).stream()
|
||||
.filter(p -> Bukkit.getPlayer(p.getUniqueId()) != null)
|
||||
.collect(ImmutableSet.toImmutableSet());
|
||||
trackedPlayers.putAll(player.getUniqueId(), currentTracked
|
||||
.stream()
|
||||
.map(Player::getUniqueId)
|
||||
.collect(ImmutableSet.toImmutableSet()));
|
||||
});
|
||||
}
|
||||
|
||||
public void onDisable() {
|
||||
trackedPlayers.clear();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Set<UUID> getTrackedPlayers(@NotNull UUID player) {
|
||||
return trackedPlayers.get(player);
|
||||
}
|
||||
|
||||
public void handleAdd(@NotNull Player player, @NotNull Player target) {
|
||||
if (!target.isConnected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if it's a real player
|
||||
if (Bukkit.getPlayer(target.getUniqueId()) == null || Bukkit.getPlayer(player.getUniqueId()) == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (PacketEvents.getAPI().getPlayerManager().getUser(target) == null || PacketEvents.getAPI().getPlayerManager().getUser(player) == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.getTaskScheduler().runTaskLaterAsynchronously(() -> {
|
||||
trackedPlayers.put(player.getUniqueId(), target.getUniqueId());
|
||||
|
||||
final boolean isVanished = plugin.getVanishManager().isVanished(target);
|
||||
if (isVanished && !plugin.getVanishManager().canSee(player, target)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (target.hasPotionEffect(PotionEffectType.INVISIBILITY)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Optional<PacketNameTag> display = plugin.getNametagManager().getPacketDisplayText(target);
|
||||
|
||||
if (display.isEmpty()) {
|
||||
// plugin.getLogger().warning("Display is empty for " + target.getName());
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.getNametagManager().updateDisplay(player, target);
|
||||
}, 3);
|
||||
|
||||
}
|
||||
|
||||
public void handleRemove(@NotNull Player player, @NotNull Player target) {
|
||||
plugin.getTaskScheduler().runTaskAsynchronously(() -> {
|
||||
trackedPlayers.remove(player.getUniqueId(), target.getUniqueId());
|
||||
|
||||
plugin.getNametagManager().removeDisplay(player, target);
|
||||
});
|
||||
}
|
||||
|
||||
public void handleQuit(@NotNull Player player) {
|
||||
plugin.getTaskScheduler().runTaskAsynchronously(() -> {
|
||||
trackedPlayers.removeAll(player.getUniqueId());
|
||||
trackedPlayers.values().remove(player.getUniqueId());
|
||||
});
|
||||
}
|
||||
|
||||
public void forceUntrack(@NotNull Player player) {
|
||||
plugin.getTaskScheduler().runTaskAsynchronously(() -> {
|
||||
trackedPlayers.get(player.getUniqueId())
|
||||
.stream()
|
||||
.map(Bukkit::getPlayer)
|
||||
.filter(Objects::nonNull)
|
||||
.forEach(p -> handleRemove(p, player));
|
||||
Bukkit.getOnlinePlayers().stream()
|
||||
.filter(p -> trackedPlayers.get(player.getUniqueId()).contains(p.getUniqueId()))
|
||||
.forEach(p -> handleRemove(player, p));
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package org.alexdev.unlimitednametags.hook;
|
||||
|
||||
import org.alexdev.unlimitednametags.UnlimitedNameTags;
|
||||
import org.alexdev.unlimitednametags.config.Advanced;
|
||||
import org.alexdev.unlimitednametags.hook.hat.HatHook;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
public final class AdvancedHatHook implements HatHook {
|
||||
|
||||
private final UnlimitedNameTags plugin;
|
||||
|
||||
public AdvancedHatHook(@NotNull UnlimitedNameTags plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getHigh(@NotNull Player player) {
|
||||
final List<Advanced.HelmetHeightRule> rules = plugin.getConfigManager().getAdvanced().getHelmetHeightRules();
|
||||
if (rules == null || rules.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
final ItemStack helmet = player.getInventory().getHelmet();
|
||||
if (helmet == null || helmet.getType().isAir()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return rules.stream()
|
||||
.sorted(Comparator.comparingInt(Advanced.HelmetHeightRule::getPriority).reversed())
|
||||
.filter(rule -> rule.getHeight() > 0)
|
||||
.filter(Advanced.HelmetHeightRule::definesItemMatch)
|
||||
.filter(rule -> matches(rule, player, helmet))
|
||||
.mapToDouble(Advanced.HelmetHeightRule::getHeight)
|
||||
.findFirst()
|
||||
.orElse(0);
|
||||
}
|
||||
|
||||
private boolean matches(@NotNull Advanced.HelmetHeightRule rule, @NotNull Player player, @NotNull ItemStack helmet) {
|
||||
if (rule.getPermission() != null && !rule.getPermission().isEmpty() && !player.hasPermission(rule.getPermission())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (rule.getWorlds() != null && !rule.getWorlds().isEmpty()
|
||||
&& !rule.getWorlds().contains(player.getWorld().getName())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (rule.getMaterial() != null && !rule.getMaterial().isEmpty()) {
|
||||
final Material mat = Material.matchMaterial(rule.getMaterial(), false);
|
||||
if (mat == null || helmet.getType() != mat) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (rule.getEquippableModel() != null && !rule.getEquippableModel().isEmpty()) {
|
||||
final ItemMeta meta = helmet.getItemMeta();
|
||||
if (meta == null || !meta.hasEquippable()) {
|
||||
return false;
|
||||
}
|
||||
final NamespacedKey modelKey = meta.getEquippable().getModel();
|
||||
if (modelKey == null) {
|
||||
return false;
|
||||
}
|
||||
final NamespacedKey wanted = NamespacedKey.fromString(rule.getEquippableModel());
|
||||
if (wanted == null || !wanted.equals(modelKey)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
final boolean wantsRange = rule.getCustomModelDataMin() != null && rule.getCustomModelDataMax() != null;
|
||||
if (wantsRange) {
|
||||
final ItemMeta meta = helmet.getItemMeta();
|
||||
if (meta == null || !meta.hasCustomModelData()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int cmd = meta.getCustomModelData();
|
||||
return cmd >= rule.getCustomModelDataMin() && cmd <= rule.getCustomModelDataMax();
|
||||
} else if (rule.getCustomModelData() != null) {
|
||||
final ItemMeta meta = helmet.getItemMeta();
|
||||
if (meta == null || !meta.hasCustomModelData()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return meta.getCustomModelData() == rule.getCustomModelData();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package org.alexdev.unlimitednametags.hook;
|
||||
|
||||
import com.github.retrooper.packetevents.PacketEvents;
|
||||
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
|
||||
import com.github.retrooper.packetevents.protocol.player.User;
|
||||
import net.digitalingot.feather.serverapi.api.FeatherAPI;
|
||||
import net.digitalingot.feather.serverapi.api.event.player.PlayerHelloEvent;
|
||||
import net.digitalingot.feather.serverapi.api.model.FeatherMod;
|
||||
import org.alexdev.unlimitednametags.UnlimitedNameTags;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class FeatherClientHook extends Hook {
|
||||
|
||||
private final List<FeatherMod> modsToBlock = Collections.singletonList(new FeatherMod("nametags"));
|
||||
|
||||
public FeatherClientHook(UnlimitedNameTags plugin) {
|
||||
super(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
FeatherAPI.getEventService().subscribe(PlayerHelloEvent.class, event -> {
|
||||
final Player player = plugin.getPlayerListener().getPlayer(event.getPlayer().getUniqueId());
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
final User user = PacketEvents.getAPI().getPlayerManager().getUser(player);
|
||||
|
||||
if (user.getClientVersion().isOlderThan(ClientVersion.V_1_19_3)) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.getPlayer().blockMods(modsToBlock);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -5,12 +5,15 @@ import com.hibiscusmc.hmccosmetics.cosmetic.Cosmetic;
|
||||
import com.hibiscusmc.hmccosmetics.cosmetic.CosmeticSlot;
|
||||
import com.hibiscusmc.hmccosmetics.user.CosmeticUser;
|
||||
import org.alexdev.unlimitednametags.UnlimitedNameTags;
|
||||
import org.alexdev.unlimitednametags.hook.creative.CreativeHook;
|
||||
import org.alexdev.unlimitednametags.hook.hat.HatHook;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class HMCCosmeticsHook extends Hook implements HatHook {
|
||||
|
||||
private CreativeHook creativeHook;
|
||||
|
||||
public HMCCosmeticsHook(UnlimitedNameTags plugin) {
|
||||
super(plugin);
|
||||
@@ -18,12 +21,24 @@ public class HMCCosmeticsHook extends Hook implements HatHook {
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
plugin.getTaskScheduler().runTaskLater(() -> {
|
||||
creativeHook = plugin.getHooks().values()
|
||||
.stream()
|
||||
.filter(hook -> hook instanceof CreativeHook)
|
||||
.map(hook -> (CreativeHook) hook)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
}
|
||||
|
||||
public boolean hasBackpack(@NotNull Player player) {
|
||||
final CosmeticUser user = HMCCosmeticsAPI.getUser(player.getUniqueId());
|
||||
return user != null && user.hasCosmeticInSlot(CosmeticSlot.BACKPACK);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getHigh(@NotNull Player player) {
|
||||
@@ -37,6 +52,15 @@ public class HMCCosmeticsHook extends Hook implements HatHook {
|
||||
return 0;
|
||||
}
|
||||
|
||||
final ItemStack item = cosmetic.getItem();
|
||||
if (item == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (creativeHook != null) {
|
||||
return creativeHook.getHigh(item);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -1,33 +1,47 @@
|
||||
package org.alexdev.unlimitednametags.hook;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import dev.lone.itemsadder.api.Events.ItemsAdderLoadDataEvent;
|
||||
import dev.lone.itemsadder.api.Events.ItemsAdderPackCompressedEvent;
|
||||
import lombok.Getter;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import org.alexdev.unlimitednametags.UnlimitedNameTags;
|
||||
import org.alexdev.unlimitednametags.hook.creative.CreativeHook;
|
||||
import org.alexdev.unlimitednametags.hook.creative.CustomMinecraftResourcePackReaderImpl;
|
||||
import org.alexdev.unlimitednametags.hook.hat.HatHook;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import team.unnamed.creative.ResourcePack;
|
||||
import team.unnamed.creative.model.Model;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
public class ItemsAdderHook extends Hook implements Listener, HatHook {
|
||||
@Getter
|
||||
public class ItemsAdderHook extends Hook implements Listener, HatHook, CreativeHook {
|
||||
|
||||
private final Map<String, Double> height;
|
||||
private static final Path generatedPath = new File(Bukkit.getPluginsFolder(),"ItemsAdder" + File.separator + "output" + File.separator + "generated.zip").toPath();
|
||||
|
||||
private final Map<Key, Map<Integer, Model>> cmdCache;
|
||||
private ResourcePack resourcePack;
|
||||
|
||||
public ItemsAdderHook(@NotNull UnlimitedNameTags plugin) {
|
||||
super(plugin);
|
||||
this.height = Maps.newConcurrentMap();
|
||||
this.cmdCache = Maps.newConcurrentMap();
|
||||
loadTexture();
|
||||
}
|
||||
|
||||
public double getHigh(@NotNull Player player) {
|
||||
return 0;
|
||||
return CreativeHook.super.getHigh(player);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onEnable(ItemsAdderLoadDataEvent event) {
|
||||
height.clear();
|
||||
public void onLoad(ItemsAdderPackCompressedEvent event) {
|
||||
cmdCache.clear();
|
||||
loadTexture();
|
||||
plugin.getLogger().info("ItemsAdder items loaded, clearing cache");
|
||||
}
|
||||
|
||||
@@ -38,6 +52,24 @@ public class ItemsAdderHook extends Hook implements Listener, HatHook {
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
cmdCache.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadTexture() {
|
||||
final File generated = generatedPath.toFile();
|
||||
if (!generated.exists()) {
|
||||
plugin.getLogger().warning("ItemsAdder generated.zip not found, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// resourcePack = MinecraftResourcePackReader.builder().lenient(true).build().readFromZipFile(generated);
|
||||
resourcePack = CustomMinecraftResourcePackReaderImpl.INSTANCE.readFromZipFile(generated);
|
||||
plugin.getLogger().info("ItemsAdder's resource pack loaded from " + generated.getAbsolutePath());
|
||||
} catch (Throwable e) {
|
||||
resourcePack = null;
|
||||
plugin.getLogger().log(java.util.logging.Level.SEVERE, "Failed to load ItemsAdder resource pack for file at " + generated.getAbsolutePath(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.alexdev.unlimitednametags.hook;
|
||||
|
||||
import net.labymod.serverapi.server.bukkit.event.LabyModPlayerJoinEvent;
|
||||
import org.alexdev.unlimitednametags.UnlimitedNameTags;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
|
||||
public class LabyModHook extends Hook implements Listener {
|
||||
|
||||
// private final List<String> modsToBlock = List.of("Nametag", "nametag");
|
||||
|
||||
|
||||
public LabyModHook(UnlimitedNameTags plugin) {
|
||||
super(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
Bukkit.getPluginManager().registerEvents(this, plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onJoin(LabyModPlayerJoinEvent event) {
|
||||
// event.labyModPlayer().disableAddons(modsToBlock);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package org.alexdev.unlimitednametags.hook;
|
||||
|
||||
import me.libraryaddict.disguise.events.DisguiseEvent;
|
||||
import me.libraryaddict.disguise.events.UndisguiseEvent;
|
||||
import org.alexdev.unlimitednametags.UnlimitedNameTags;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class LibsDisguisesHook extends Hook implements Listener {
|
||||
|
||||
public LibsDisguisesHook(@NotNull UnlimitedNameTags plugin) {
|
||||
super(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onDisguise(DisguiseEvent event) {
|
||||
if (event.getDisguise().getEntity() instanceof Player player) {
|
||||
plugin.getNametagManager().removeAllViewers(player);
|
||||
plugin.getNametagManager().blockPlayer(player);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onUnDisguise(UndisguiseEvent event) {
|
||||
if (event.isBeingReplaced()) {
|
||||
return;
|
||||
}
|
||||
if (event.getDisguise().getEntity() instanceof Player player) {
|
||||
plugin.getNametagManager().unblockPlayer(player);
|
||||
plugin.getNametagManager().showToTrackedPlayers(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,12 +15,7 @@ public class MiniPlaceholdersHook extends Hook {
|
||||
|
||||
@NotNull
|
||||
public Component format(@NotNull String text, @NotNull Audience player) {
|
||||
return MiniMessage.miniMessage().deserialize(text, MiniPlaceholders.getAudienceGlobalPlaceholders(player));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Component format(@NotNull Component component, @NotNull Audience player) {
|
||||
return format(MiniMessage.miniMessage().serialize(component), player);
|
||||
return MiniMessage.miniMessage().deserialize(text, player, MiniPlaceholders.audienceGlobalPlaceholders());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,114 +1,66 @@
|
||||
package org.alexdev.unlimitednametags.hook;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.nexomc.nexo.api.NexoItems;
|
||||
import com.nexomc.nexo.api.NexoPack;
|
||||
import com.nexomc.nexo.api.events.NexoItemsLoadedEvent;
|
||||
import com.nexomc.nexo.items.ItemBuilder;
|
||||
import com.nexomc.nexo.items.NexoMeta;
|
||||
import lombok.Getter;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import org.alexdev.unlimitednametags.UnlimitedNameTags;
|
||||
import org.alexdev.unlimitednametags.hook.creative.CreativeHook;
|
||||
import org.alexdev.unlimitednametags.hook.hat.HatHook;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import team.unnamed.creative.ResourcePack;
|
||||
import team.unnamed.creative.model.Model;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
public class NexoHook extends Hook implements Listener, HatHook {
|
||||
@Getter
|
||||
public class NexoHook extends Hook implements Listener, CreativeHook, HatHook {
|
||||
|
||||
private static final File NEXO_FOLDER = new File("plugins/Nexo/pack/assets/minecraft/models");
|
||||
|
||||
private final Map<String, Double> height;
|
||||
private final Gson jsonParser;
|
||||
private final Map<Key, Map<Integer, Model>> cmdCache;
|
||||
private ResourcePack resourcePack;
|
||||
|
||||
public NexoHook(@NotNull UnlimitedNameTags plugin) {
|
||||
super(plugin);
|
||||
this.height = Maps.newConcurrentMap();
|
||||
this.jsonParser = new Gson();
|
||||
this.cmdCache = Maps.newConcurrentMap();
|
||||
loadTexture();
|
||||
}
|
||||
|
||||
public void loadTexture() {
|
||||
resourcePack = NexoPack.resourcePack();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getHigh(@NotNull Player player) {
|
||||
final ItemStack helmet = player.getInventory().getHelmet();
|
||||
if (helmet == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!helmet.hasItemMeta() || !helmet.getItemMeta().hasCustomModelData()) {
|
||||
return 0;
|
||||
}
|
||||
final Optional<ItemBuilder> optionalItemBuilder = findItem(helmet);
|
||||
return optionalItemBuilder.map(itemBuilder -> getHigh(getModelName(itemBuilder.getNexoMeta()) + ".json")).orElse(0.0);
|
||||
return CreativeHook.super.getHigh(player);
|
||||
}
|
||||
|
||||
private Optional<ItemBuilder> findItem(@NotNull ItemStack item) {
|
||||
return Optional.ofNullable(NexoItems.builderFromItem(item));
|
||||
}
|
||||
|
||||
private String getModelName(@NotNull NexoMeta meta) {
|
||||
return meta.getModelKey().value();
|
||||
}
|
||||
|
||||
private double getHigh(@NotNull String model) {
|
||||
if (height.containsKey(model)) {
|
||||
return height.get(model);
|
||||
}
|
||||
final File file = new File(NEXO_FOLDER, model);
|
||||
if (!file.exists()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
final JsonObject jsonObject = parseFile(file);
|
||||
final JsonArray elements = jsonObject.getAsJsonArray("elements");
|
||||
if (elements.size() == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
double highest = 0;
|
||||
for (int i = 0; i < elements.size(); i++) {
|
||||
final JsonObject element = elements.get(i).getAsJsonObject();
|
||||
final double to = element.getAsJsonArray("to").get(1).getAsDouble();
|
||||
if (to > highest) {
|
||||
highest = to;
|
||||
public Optional<Model> findModel(@NotNull ItemStack item) {
|
||||
final Optional<ItemBuilder> optionalItemBuilder = Optional.ofNullable(NexoItems.builderFromItem(item));
|
||||
if (optionalItemBuilder.isPresent()) {
|
||||
final Optional<Model> optionalModel = optionalItemBuilder.map(ItemBuilder::getNexoMeta)
|
||||
.map(NexoMeta::getModel)
|
||||
.map(key -> resourcePack.model(key));
|
||||
if (optionalModel.isPresent()) {
|
||||
return optionalModel;
|
||||
}
|
||||
}
|
||||
|
||||
final JsonObject display = jsonObject.getAsJsonObject("display");
|
||||
if (!display.has("head")) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
final JsonObject head = display.getAsJsonObject("head");
|
||||
final double scale = head.has("scale") ? head.getAsJsonArray("scale").get(1).getAsDouble() : 1;
|
||||
highest *= scale;
|
||||
final double translation = head.has("translation") ? head.getAsJsonArray("translation").get(1).getAsDouble() : 0;
|
||||
|
||||
|
||||
final double value = highest + translation;
|
||||
height.put(model, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private JsonObject parseFile(@NotNull File file) {
|
||||
try (final FileReader reader = new FileReader(file)) {
|
||||
return jsonParser.fromJson(reader, JsonObject.class);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to parse file: " + file, e);
|
||||
}
|
||||
return CreativeHook.super.findModel(item);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onEnable(NexoItemsLoadedEvent event) {
|
||||
height.clear();
|
||||
public void onLoad(NexoItemsLoadedEvent event) {
|
||||
cmdCache.clear();
|
||||
loadTexture();
|
||||
plugin.getLogger().info("Nexo items loaded, clearing cache");
|
||||
}
|
||||
|
||||
@@ -119,6 +71,6 @@ public class NexoHook extends Hook implements Listener, HatHook {
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
|
||||
cmdCache.clear();
|
||||
}
|
||||
}
|
||||
@@ -59,7 +59,16 @@ public class OraxenHook extends Hook implements Listener, HatHook {
|
||||
}
|
||||
|
||||
private int getCustomModelData(@NotNull OraxenMeta meta) {
|
||||
return meta.getCustomModelData();
|
||||
try {
|
||||
if (meta.getCustomModelData() == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return meta.getCustomModelData();
|
||||
} catch (NoSuchMethodError | NullPointerException e) {
|
||||
// Compatibility with older Oraxen versions
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private String getModelName(OraxenMeta meta) {
|
||||
|
||||
@@ -35,6 +35,7 @@ public class TypeWriterListener extends Hook implements Listener {
|
||||
.map(Bukkit::getPlayer)
|
||||
.filter(p -> p != null && p != event.getPlayer())
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
plugin.getNametagManager().getPacketDisplayText(event.getPlayer()).ifPresent(display -> display.showToPlayers(viewers));
|
||||
},
|
||||
1);
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
package org.alexdev.unlimitednametags.hook.creative;
|
||||
|
||||
import com.github.retrooper.packetevents.PacketEvents;
|
||||
import com.github.retrooper.packetevents.manager.server.ServerVersion;
|
||||
import com.google.common.collect.Maps;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import team.unnamed.creative.ResourcePack;
|
||||
import team.unnamed.creative.base.Vector3Float;
|
||||
import team.unnamed.creative.model.ItemOverride;
|
||||
import team.unnamed.creative.model.ItemTransform;
|
||||
import team.unnamed.creative.model.Model;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface CreativeHook {
|
||||
|
||||
double MULTIPLIER = 1.1;
|
||||
|
||||
void loadTexture();
|
||||
|
||||
@Nullable
|
||||
ResourcePack getResourcePack();
|
||||
|
||||
@NotNull
|
||||
Map<Key, Map<Integer, Model>> getCmdCache();
|
||||
|
||||
default double getHigh(@NotNull ItemStack helmet) {
|
||||
if (!helmet.hasItemMeta() || !helmet.getItemMeta().hasCustomModelData()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
final Optional<Model> optionalModel = findModel(helmet);
|
||||
return optionalModel.map(this::getHighFromModel).orElse(0.0);
|
||||
}
|
||||
|
||||
default double getHigh(@NotNull Player player) {
|
||||
final ItemStack helmet = player.getInventory().getHelmet();
|
||||
if (helmet == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return getHigh(helmet);
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
default Optional<Model> findModel(@NotNull ItemStack item) {
|
||||
final ResourcePack pack = getResourcePack();
|
||||
if (pack == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
final Map<Integer, Model> cmdCache = getCmdCache().computeIfAbsent(item.getType().getKey(), k -> Maps.newConcurrentMap());
|
||||
|
||||
final ItemMeta itemMeta = item.getItemMeta();
|
||||
if (itemMeta.hasCustomModelData()) {
|
||||
final int customModelData = itemMeta.getCustomModelData();
|
||||
final String asString = Integer.toString(customModelData);
|
||||
if (cmdCache.containsKey(customModelData)) {
|
||||
return Optional.of(cmdCache.get(customModelData));
|
||||
}
|
||||
|
||||
var key = new NamespacedKey(item.getType().getKey().namespace(), "item/" + item.getType().getKey().value());
|
||||
var modelIn = pack.model(key);
|
||||
|
||||
if (modelIn != null) {
|
||||
final Optional<ItemOverride> optionalOverride = findItemOverride(modelIn, customModelData, asString);
|
||||
if (optionalOverride.isPresent()) {
|
||||
final Model model = pack.model(optionalOverride.get().model());
|
||||
if (model != null) {
|
||||
cmdCache.put(customModelData, model);
|
||||
return Optional.of(model);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Fallback
|
||||
Optional<ItemOverride> optionalOverride = Optional.empty();
|
||||
for (Model model : pack.models()) {
|
||||
optionalOverride = findItemOverride(model, customModelData, asString);
|
||||
if (optionalOverride.isPresent()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return optionalOverride.map(override -> {
|
||||
final Model model = pack.model(override.model());
|
||||
cmdCache.put(customModelData, model);
|
||||
return model;
|
||||
});
|
||||
}
|
||||
|
||||
if (PacketEvents.getAPI().getServerManager().getVersion().isOlderThan(ServerVersion.V_1_21_3)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
if (itemMeta.hasEquippable()) {
|
||||
final NamespacedKey model = itemMeta.getEquippable().getModel();
|
||||
if (model == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.ofNullable(pack.model(model));
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
default Optional<ItemOverride> findItemOverride(@NotNull Model model, int customModelData, @NotNull String asString) {
|
||||
return model.overrides().stream()
|
||||
.filter(e -> e.predicate().stream().anyMatch(p ->
|
||||
p.name().equalsIgnoreCase("custom_model_data") &&
|
||||
(p.value().equals(customModelData) || p.value().toString().equals(asString))))
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
default double getHighFromModel(@NotNull Model model) {
|
||||
double highest = 0;
|
||||
for (int i = 0; i < model.elements().size(); i++) {
|
||||
final double to = model.elements().get(i).to().y();
|
||||
if (to > highest) {
|
||||
highest = to;
|
||||
}
|
||||
}
|
||||
|
||||
if (!model.display().containsKey(ItemTransform.Type.HEAD)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
final ItemTransform itemTransform = model.display().get(ItemTransform.Type.HEAD);
|
||||
final Vector3Float scale = itemTransform.scale();
|
||||
highest *= scale.y();
|
||||
highest *= MULTIPLIER;
|
||||
|
||||
final double translation = itemTransform.translation().y();
|
||||
return highest + translation;
|
||||
}
|
||||
|
||||
}
|
||||
+357
@@ -0,0 +1,357 @@
|
||||
/*
|
||||
* This file is part of creative, licensed under the MIT license
|
||||
*
|
||||
* Copyright (c) 2021-2025 Unnamed Team
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package org.alexdev.unlimitednametags.hook.creative;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import org.intellij.lang.annotations.Subst;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import team.unnamed.creative.ResourcePack;
|
||||
import team.unnamed.creative.base.Writable;
|
||||
import team.unnamed.creative.metadata.Metadata;
|
||||
import team.unnamed.creative.metadata.overlays.OverlayEntry;
|
||||
import team.unnamed.creative.metadata.overlays.OverlaysMeta;
|
||||
import team.unnamed.creative.metadata.pack.PackMeta;
|
||||
import team.unnamed.creative.overlay.Overlay;
|
||||
import team.unnamed.creative.overlay.ResourceContainer;
|
||||
import team.unnamed.creative.part.ResourcePackPart;
|
||||
import team.unnamed.creative.serialize.minecraft.*;
|
||||
import team.unnamed.creative.serialize.minecraft.fs.FileTreeReader;
|
||||
import team.unnamed.creative.serialize.minecraft.io.BinaryResourceDeserializer;
|
||||
import team.unnamed.creative.serialize.minecraft.io.JsonResourceDeserializer;
|
||||
import team.unnamed.creative.serialize.minecraft.io.ResourceDeserializer;
|
||||
import team.unnamed.creative.serialize.minecraft.metadata.MetadataSerializer;
|
||||
import team.unnamed.creative.serialize.minecraft.sound.SoundRegistrySerializer;
|
||||
import team.unnamed.creative.texture.Texture;
|
||||
import team.unnamed.creative.util.Keys;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static team.unnamed.creative.serialize.minecraft.MinecraftResourcePackStructure.*;
|
||||
|
||||
public final class CustomMinecraftResourcePackReaderImpl implements MinecraftResourcePackReader {
|
||||
public static final CustomMinecraftResourcePackReaderImpl INSTANCE = new CustomMinecraftResourcePackReaderImpl(false);
|
||||
public static final String OVERLAYS_FOLDER = "overlays";
|
||||
|
||||
private final boolean lenient;
|
||||
|
||||
private CustomMinecraftResourcePackReaderImpl(
|
||||
final boolean lenient
|
||||
) {
|
||||
this.lenient = lenient;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("PatternValidation")
|
||||
public @NotNull ResourcePack read(final @NotNull FileTreeReader reader) {
|
||||
ResourcePack resourcePack = ResourcePack.resourcePack();
|
||||
|
||||
// textures that are waiting for metadata, or metadata
|
||||
// waiting for textures (because we can't know the order
|
||||
// they come in)
|
||||
// (null key means it is root resource pack)
|
||||
Map<@Nullable String, Map<Key, Texture>> incompleteTextures = new LinkedHashMap<>();
|
||||
|
||||
// fill in with the default ones first (pack format is unknown at the start)
|
||||
Map<String, ResourceCategory<?>> categoriesByFolderThisPackFormat = ResourceCategories.buildCategoryMapByFolder(-1);
|
||||
Map<String, Integer> packFormatsByOverlayDir = new HashMap<>();
|
||||
int packFormat = -1;
|
||||
|
||||
while (reader.hasNext()) {
|
||||
String path = reader.next();
|
||||
|
||||
// tokenize path in sections, e.g.: [ assets, minecraft, textures, ... ]
|
||||
Queue<String> tokens = tokenize(path);
|
||||
|
||||
if (tokens.isEmpty()) {
|
||||
// this should never happen
|
||||
throw new IllegalStateException("Token collection is empty!");
|
||||
}
|
||||
|
||||
// single token means the file is on the
|
||||
// root level (top level files) so it may be:
|
||||
// - pack.mcmeta
|
||||
// - pack.png
|
||||
if (tokens.size() == 1) {
|
||||
switch (tokens.poll()) {
|
||||
case PACK_METADATA_FILE: {
|
||||
// found pack.mcmeta file, deserialize and add
|
||||
Metadata metadata = MetadataSerializer.INSTANCE.readFromTree(parseJson(reader.stream()));
|
||||
resourcePack.metadata(metadata);
|
||||
|
||||
// get the pack format from the metadata
|
||||
PackMeta packMeta = metadata.meta(PackMeta.class);
|
||||
if (packMeta == null) {
|
||||
// TODO: better warning system
|
||||
System.err.println("Reading a resource-pack with no pack meta in its pack.mcmeta file! Unknown pack format version :(");
|
||||
} else {
|
||||
// update the pack format and categories
|
||||
packFormat = packMeta.formats().min();
|
||||
categoriesByFolderThisPackFormat = ResourceCategories.buildCategoryMapByFolder(packFormat);
|
||||
}
|
||||
|
||||
// overlays info
|
||||
OverlaysMeta overlaysMeta = metadata.meta(OverlaysMeta.class);
|
||||
if (overlaysMeta != null) for (OverlayEntry entry : overlaysMeta.entries()) {
|
||||
packFormatsByOverlayDir.put(entry.directory(), entry.formats().min());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
case PACK_ICON_FILE: {
|
||||
// found pack.png file, add
|
||||
resourcePack.icon(reader.content().asWritable());
|
||||
continue;
|
||||
}
|
||||
default: {
|
||||
// unknown top level file
|
||||
resourcePack.unknownFile(path, reader.content().asWritable());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// the container to use, it is initially the default resource-pack,
|
||||
// but it may change if the file is inside an overlay folder
|
||||
@Subst("dir")
|
||||
@Nullable String overlayDir = null;
|
||||
int localPackFormat = packFormat;
|
||||
|
||||
// the file path, relative to the container
|
||||
String containerPath = path;
|
||||
ResourceContainer container = resourcePack;
|
||||
|
||||
// if there are two or more tokens, it means the
|
||||
// file is inside a folder, in a Minecraft resource
|
||||
// pack, the first folder is always "assets"
|
||||
String folder = tokens.poll();
|
||||
|
||||
if (folder.equals(OVERLAYS_FOLDER)) {
|
||||
// gets the overlay name, set after the
|
||||
// "overlays" folder, e.g. "overlays/foo",
|
||||
// or "overlays/bar"
|
||||
overlayDir = tokens.poll();
|
||||
if (tokens.isEmpty()) {
|
||||
// this means that there is a file directly
|
||||
// inside the "overlays" folder, this is illegal
|
||||
resourcePack.unknownFile(containerPath, reader.content().asWritable());
|
||||
continue;
|
||||
}
|
||||
|
||||
Overlay overlay = resourcePack.overlay(overlayDir);
|
||||
if (overlay == null) {
|
||||
// first occurrence, register overlay
|
||||
overlay = Overlay.overlay(overlayDir);
|
||||
resourcePack.overlay(overlay);
|
||||
}
|
||||
|
||||
container = overlay;
|
||||
folder = tokens.poll();
|
||||
containerPath = path.substring((OVERLAYS_FOLDER + '/' + overlayDir + '/').length());
|
||||
localPackFormat = packFormatsByOverlayDir.getOrDefault(overlayDir, -1);
|
||||
}
|
||||
|
||||
// null check to make ide happy
|
||||
if (folder == null || !folder.equals(ASSETS_FOLDER) || tokens.isEmpty()) {
|
||||
// not assets! this is an unknown file
|
||||
container.unknownFile(containerPath, reader.content().asWritable());
|
||||
continue;
|
||||
}
|
||||
|
||||
// inside "assets", we should always have a folder
|
||||
// with any name, which is a namespace, e.g. "minecraft"
|
||||
String namespace = tokens.poll();
|
||||
|
||||
if (!Keys.isValidNamespace(namespace)) {
|
||||
// invalid namespace found
|
||||
container.unknownFile(containerPath, reader.content().asWritable());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tokens.isEmpty()) {
|
||||
// found a file directly inside "assets", like
|
||||
// assets/<file>, it is not allowed
|
||||
container.unknownFile(containerPath, reader.content().asWritable());
|
||||
continue;
|
||||
}
|
||||
|
||||
// so we already have "assets/<namespace>/", most files inside
|
||||
// the namespace folder always have a "category", e.g. textures,
|
||||
// lang, font, etc. But not always! There is sounds.json file and
|
||||
// gpu_warnlist.json file
|
||||
String categoryName = tokens.poll();
|
||||
|
||||
if (tokens.isEmpty()) {
|
||||
// this means "category" is a file
|
||||
// (remember: last tokens are always files)
|
||||
if (categoryName.equals(SOUNDS_FILE)) {
|
||||
// found a sound registry!
|
||||
container.soundRegistry(SoundRegistrySerializer.INSTANCE.readFromTree(
|
||||
parseJson(reader.stream()),
|
||||
namespace
|
||||
));
|
||||
continue;
|
||||
} else {
|
||||
// TODO: gpu_warnlist.json?
|
||||
container.unknownFile(containerPath, reader.content().asWritable());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// so "category" is actually a category like "textures",
|
||||
// "lang", "font", etc. next we can compute the relative
|
||||
// path inside the category
|
||||
String categoryPath = path(tokens);
|
||||
|
||||
if (categoryName.equals(TEXTURES_FOLDER)) {
|
||||
String keyOfMetadata = withoutExtension(categoryPath, METADATA_EXTENSION);
|
||||
if (keyOfMetadata != null) {
|
||||
// found metadata for texture
|
||||
Key key = Key.key(namespace, keyOfMetadata);
|
||||
Metadata metadata = MetadataSerializer.INSTANCE.readFromTree(parseJson(reader.stream()));
|
||||
|
||||
Map<Key, Texture> incompleteTexturesThisContainer = incompleteTextures.computeIfAbsent(overlayDir, k -> new LinkedHashMap<>());
|
||||
Texture texture = incompleteTexturesThisContainer.remove(key);
|
||||
if (texture == null) {
|
||||
// metadata was found first, put
|
||||
incompleteTexturesThisContainer.put(key, Texture.texture(key, Writable.EMPTY, metadata));
|
||||
} else {
|
||||
// texture was found before the metadata, nice!
|
||||
container.texture(texture.meta(metadata));
|
||||
}
|
||||
} else {
|
||||
Key key = Key.key(namespace, categoryPath);
|
||||
Writable data = reader.content().asWritable();
|
||||
Map<Key, Texture> incompleteTexturesThisContainer = incompleteTextures.computeIfAbsent(overlayDir, k -> new LinkedHashMap<>());
|
||||
Texture waiting = incompleteTexturesThisContainer.remove(key);
|
||||
|
||||
if (waiting == null) {
|
||||
// found texture before metadata
|
||||
incompleteTexturesThisContainer.put(key, Texture.texture(key, data));
|
||||
} else {
|
||||
// metadata was found first
|
||||
container.texture(Texture.texture(
|
||||
key,
|
||||
data,
|
||||
waiting.meta()
|
||||
));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// get the resource category, if the local pack format (overlay or root) is the same as the
|
||||
// root pack format, we can use the previously computed map, otherwise we need to compute it
|
||||
// (we could save some time by caching the computed map, but, is it worth it?)
|
||||
ResourceCategory<?> category = (localPackFormat == packFormat
|
||||
? categoriesByFolderThisPackFormat
|
||||
: ResourceCategories.buildCategoryMapByFolder(localPackFormat)).get(categoryName);
|
||||
if (category == null) {
|
||||
// unknown category
|
||||
container.unknownFile(containerPath, reader.content().asWritable());
|
||||
continue;
|
||||
}
|
||||
String keyValue = withoutExtension(categoryPath, category.extension(-1));
|
||||
if (keyValue == null) {
|
||||
// wrong extension
|
||||
container.unknownFile(containerPath, reader.content().asWritable());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (keyValue.startsWith("equipment/")) {
|
||||
// skip trying to load "namespace:equipment/X.json" as a normal model
|
||||
container.unknownFile(containerPath, reader.content().asWritable());
|
||||
continue;
|
||||
}
|
||||
|
||||
Key key = Key.key(namespace, keyValue);
|
||||
try {
|
||||
ResourceDeserializer<? extends ResourcePackPart> deserializer = category.deserializer();
|
||||
ResourcePackPart resource;
|
||||
if (deserializer instanceof BinaryResourceDeserializer) {
|
||||
resource = ((BinaryResourceDeserializer<? extends ResourcePackPart>) deserializer)
|
||||
.deserializeBinary(reader.content().asWritable(), key);
|
||||
} else if (deserializer instanceof JsonResourceDeserializer) {
|
||||
resource = ((JsonResourceDeserializer<? extends ResourcePackPart>) deserializer)
|
||||
.deserializeFromJson(parseJson(reader.stream()), key);
|
||||
} else {
|
||||
resource = deserializer.deserialize(reader.stream(), key);
|
||||
}
|
||||
resource.addTo(container);
|
||||
} catch (Throwable e) {
|
||||
// throw new UncheckedIOException("Failed to deserialize resource at: '" + path + "'", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry<String, Map<Key, Texture>> entry : incompleteTextures.entrySet()) {
|
||||
@Subst("dir")
|
||||
@Nullable String overlayDir = entry.getKey();
|
||||
Map<Key, Texture> incompleteTexturesThisContainer = entry.getValue();
|
||||
ResourceContainer container;
|
||||
|
||||
if (overlayDir == null) {
|
||||
// root
|
||||
container = resourcePack;
|
||||
} else {
|
||||
// from an overlay
|
||||
container = resourcePack.overlay(overlayDir);
|
||||
requireNonNull(container, "container"); // should never happen, but make ide happy
|
||||
}
|
||||
|
||||
for (Texture texture : incompleteTexturesThisContainer.values()) {
|
||||
if (texture.data() != Writable.EMPTY) {
|
||||
container.texture(texture);
|
||||
}
|
||||
}
|
||||
}
|
||||
return resourcePack;
|
||||
}
|
||||
|
||||
private static @Nullable String withoutExtension(String string, String extension) {
|
||||
if (string.endsWith(extension)) {
|
||||
return string.substring(0, string.length() - extension.length());
|
||||
} else {
|
||||
// string doesn't end with extension
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private @NotNull JsonElement parseJson(final @NotNull InputStream input) {
|
||||
try (final JsonReader jsonReader = new JsonReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {
|
||||
jsonReader.setLenient(lenient);
|
||||
return GsonUtil.parseReader(jsonReader);
|
||||
} catch (final IOException e) {
|
||||
throw new UncheckedIOException("Failed to close JSON reader", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.alexdev.unlimitednametags.listeners;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.alexdev.unlimitednametags.UnlimitedNameTags;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class OtherListener implements Listener {
|
||||
|
||||
private final UnlimitedNameTags plugin;
|
||||
|
||||
@EventHandler
|
||||
private void onPluginEnable(org.bukkit.event.server.PluginEnableEvent event) {
|
||||
if (event.getPlugin().getName().equals("PlaceholderAPI")) {
|
||||
plugin.getPlaceholderManager().getPapiManager().checkPapiEnabled();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
package org.alexdev.unlimitednametags.listeners;
|
||||
|
||||
import com.github.retrooper.packetevents.PacketEvents;
|
||||
import com.github.retrooper.packetevents.event.PacketListenerAbstract;
|
||||
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||
import com.github.retrooper.packetevents.event.PacketSendEvent;
|
||||
import com.github.retrooper.packetevents.protocol.entity.data.EntityData;
|
||||
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
|
||||
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
|
||||
import com.github.retrooper.packetevents.util.Vector3f;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientEntityAction;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerInput;
|
||||
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerCamera;
|
||||
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerEntityMetadata;
|
||||
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerSetPassengers;
|
||||
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerTeams;
|
||||
import com.google.common.collect.Maps;
|
||||
import org.alexdev.unlimitednametags.UnlimitedNameTags;
|
||||
import org.alexdev.unlimitednametags.data.TeamData;
|
||||
import org.alexdev.unlimitednametags.packet.PacketNameTag;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class PacketEventsListener extends PacketListenerAbstract {
|
||||
|
||||
private final UnlimitedNameTags plugin;
|
||||
private final Map<UUID, Map<String, TeamData>> teams;
|
||||
|
||||
public PacketEventsListener(UnlimitedNameTags plugin) {
|
||||
this.plugin = plugin;
|
||||
this.teams = Maps.newConcurrentMap();
|
||||
}
|
||||
|
||||
public void onEnable() {
|
||||
PacketEvents.getAPI().getEventManager().registerListener(this);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Map<String, TeamData> getTeams(@NotNull UUID player) {
|
||||
return teams.computeIfAbsent(player, p -> Maps.newConcurrentMap());
|
||||
}
|
||||
|
||||
public void onPacketSend(@NotNull PacketSendEvent event) {
|
||||
if (event.getPacketType() == PacketType.Play.Server.TEAMS) {
|
||||
handleTeams(event);
|
||||
} else if (event.getPacketType() == PacketType.Play.Server.SET_PASSENGERS) {
|
||||
handlePassengers(event);
|
||||
} else if (event.getPacketType() == PacketType.Play.Server.ENTITY_METADATA) {
|
||||
handleMetaData(event);
|
||||
} else if (event.getPacketType() == PacketType.Play.Server.CAMERA) {
|
||||
handleCamera(event);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleCamera(@NotNull PacketSendEvent event) {
|
||||
if (!(event.getPlayer() instanceof Player player)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!plugin.getConfigManager().getSettings().isShowCurrentNameTag()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final WrapperPlayServerCamera camera = new WrapperPlayServerCamera(event);
|
||||
if (camera.getCameraId() == player.getEntityId()) {
|
||||
plugin.getNametagManager().getPacketDisplayText(player).ifPresent(PacketNameTag::showForOwner);
|
||||
} else {
|
||||
plugin.getNametagManager().getPacketDisplayText(player).ifPresent(PacketNameTag::hideForOwner);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceive(@NotNull PacketReceiveEvent event) {
|
||||
if (event.getPacketType() == PacketType.Play.Client.ENTITY_ACTION) {
|
||||
handleUseEntity(event);
|
||||
} else if (event.getPacketType() == PacketType.Play.Client.PLAYER_INPUT) {
|
||||
handlePlayerInput(event);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleUseEntity(PacketReceiveEvent event) {
|
||||
if (!(event.getPlayer() instanceof Player)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final WrapperPlayClientEntityAction packet = new WrapperPlayClientEntityAction(event);
|
||||
final Optional<? extends Player> player = plugin.getPlayerListener().getPlayerFromEntityId(packet.getEntityId());
|
||||
if (player.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
switch (packet.getAction()) {
|
||||
case START_SNEAKING -> plugin.getNametagManager().updateSneaking(player.get(), true);
|
||||
case STOP_SNEAKING -> plugin.getNametagManager().updateSneaking(player.get(), false);
|
||||
case START_FLYING_WITH_ELYTRA -> plugin.getPlayerListener().logicElytra(player.get());
|
||||
}
|
||||
}
|
||||
|
||||
private void handlePlayerInput(PacketReceiveEvent event) {
|
||||
if (!(event.getPlayer() instanceof Player player)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final WrapperPlayClientPlayerInput packet = new WrapperPlayClientPlayerInput(event);
|
||||
final Optional<PacketNameTag> optionalPacketDisplayText = plugin.getNametagManager().getPacketDisplayText(player);
|
||||
if (optionalPacketDisplayText.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (packet.isShift() != optionalPacketDisplayText.get().isSneaking()) {
|
||||
plugin.getNametagManager().updateSneaking(player, packet.isShift());
|
||||
}
|
||||
}
|
||||
|
||||
private void handlePassengers(@NotNull PacketSendEvent event) {
|
||||
final WrapperPlayServerSetPassengers packet = new WrapperPlayServerSetPassengers(event);
|
||||
final Optional<? extends Player> player = plugin.getPlayerListener().getPlayerFromEntityId(packet.getEntityId());
|
||||
if (player.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final List<Integer> passengers = collectPassengers(packet.getPassengers());
|
||||
final Optional<PacketNameTag> optionalPacketDisplayText = plugin.getNametagManager().getPacketDisplayText(player.get());
|
||||
if (optionalPacketDisplayText.isEmpty()) {
|
||||
plugin.getPacketManager().setPassengers(player.get(), passengers);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!passengers.contains(optionalPacketDisplayText.get().getEntityId())) {
|
||||
passengers.add(optionalPacketDisplayText.get().getEntityId());
|
||||
passengers.sort(Comparator.naturalOrder());
|
||||
packet.setPassengers(passengers.stream().mapToInt(i -> i).toArray());
|
||||
event.markForReEncode(true);
|
||||
}
|
||||
|
||||
plugin.getPacketManager().setPassengers(player.get(), passengers);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private List<Integer> collectPassengers(int[] passengers) {
|
||||
final List<Integer> passengerList = new ArrayList<>(passengers.length);
|
||||
for (int passenger : passengers) {
|
||||
passengerList.add(passenger);
|
||||
}
|
||||
|
||||
return passengerList;
|
||||
}
|
||||
|
||||
private boolean preTeamsChecks(@NotNull PacketSendEvent event) {
|
||||
if (!plugin.getConfigManager().getSettings().isDisableDefaultNameTag()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return event.getUser().getClientVersion().isNewerThan(ClientVersion.V_1_19_3);
|
||||
}
|
||||
|
||||
private void handleTeams(@NotNull PacketSendEvent event) {
|
||||
if (!preTeamsChecks(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final WrapperPlayServerTeams packet = new WrapperPlayServerTeams(event);
|
||||
if (handleForceDisableDefaultNameTag(event, packet)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Map<String, TeamData> teams = getTeams(event.getUser().getUUID());
|
||||
final String teamName = packet.getTeamName();
|
||||
|
||||
switch (packet.getTeamMode()) {
|
||||
case ADD_ENTITIES -> handleAddEntities(event, packet, teams, teamName);
|
||||
case REMOVE_ENTITIES -> handleRemoveEntities(packet, teams, teamName);
|
||||
case CREATE -> handleCreateTeam(event, packet, teams, teamName);
|
||||
case UPDATE -> handleUpdateTeam(event, packet, teams, teamName);
|
||||
case REMOVE -> teams.remove(teamName);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean handleForceDisableDefaultNameTag(@NotNull PacketSendEvent event, @NotNull WrapperPlayServerTeams packet) {
|
||||
if (plugin.getConfigManager().getSettings().isForceDisableDefaultNameTag()) {
|
||||
if (packet.getTeamMode() == WrapperPlayServerTeams.TeamMode.CREATE || packet.getTeamMode() == WrapperPlayServerTeams.TeamMode.UPDATE) {
|
||||
packet.getTeamInfo().ifPresent(t -> t.setTagVisibility(WrapperPlayServerTeams.NameTagVisibility.NEVER));
|
||||
event.markForReEncode(true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void handleAddEntities(@NotNull PacketSendEvent event, @NotNull WrapperPlayServerTeams packet,
|
||||
@NotNull Map<String, TeamData> teams, @NotNull String teamName) {
|
||||
final Optional<TeamData> teamDataOpt = Optional.ofNullable(teams.get(teamName));
|
||||
if (teamDataOpt.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final TeamData teamData = teamDataOpt.get();
|
||||
teamData.getMembers().addAll(packet.getPlayers());
|
||||
|
||||
if (!teamData.isChangedVisibility() && packet.getPlayers().stream().anyMatch(this::existsPlayer)) {
|
||||
teamData.setChangedVisibility(true);
|
||||
final WrapperPlayServerTeams.ScoreBoardTeamInfo teamInfo = teamData.getTeamInfo();
|
||||
teamInfo.setTagVisibility(WrapperPlayServerTeams.NameTagVisibility.NEVER);
|
||||
if (teamData.getTeamInfo() != null) {
|
||||
event.getUser().sendPacket(new WrapperPlayServerTeams(teamName, WrapperPlayServerTeams.TeamMode.UPDATE, teamData.getTeamInfo(), teamData.getMembers()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleRemoveEntities(@NotNull WrapperPlayServerTeams packet, @NotNull Map<String, TeamData> teams, @NotNull String teamName) {
|
||||
final Optional<TeamData> teamDataOpt = Optional.ofNullable(teams.get(teamName));
|
||||
teamDataOpt.ifPresent(teamData -> teamData.getMembers().removeAll(packet.getPlayers()));
|
||||
}
|
||||
|
||||
private void handleCreateTeam(@NotNull PacketSendEvent event, @NotNull WrapperPlayServerTeams packet,
|
||||
@NotNull Map<String, TeamData> teams, @NotNull String teamName) {
|
||||
if (teams.containsKey(teamName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
packet.getTeamInfo().ifPresent(teamInfo -> {
|
||||
final TeamData teamData = new TeamData(teamName, teamInfo, Set.copyOf(packet.getPlayers()));
|
||||
teams.put(teamName, teamData);
|
||||
|
||||
if (teamData.getMembers().stream().anyMatch(this::existsPlayer)) {
|
||||
teamData.setChangedVisibility(true);
|
||||
// Ensure teamInfo is not null before setting visibility
|
||||
if (teamData.getTeamInfo() != null) {
|
||||
teamData.getTeamInfo().setTagVisibility(WrapperPlayServerTeams.NameTagVisibility.NEVER);
|
||||
event.markForReEncode(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void handleUpdateTeam(@NotNull PacketSendEvent event, @NotNull WrapperPlayServerTeams packet, @NotNull Map<String, TeamData> teams, @NotNull String teamName) {
|
||||
final Optional<TeamData> teamDataOpt = Optional.ofNullable(teams.get(teamName));
|
||||
if (teamDataOpt.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final TeamData teamData = teamDataOpt.get();
|
||||
packet.getTeamInfo().ifPresent(teamInfoFromPacket -> {
|
||||
if (teamData.isChangedVisibility() && teamInfoFromPacket.getTagVisibility() != WrapperPlayServerTeams.NameTagVisibility.NEVER) {
|
||||
teamInfoFromPacket.setTagVisibility(WrapperPlayServerTeams.NameTagVisibility.NEVER);
|
||||
event.markForReEncode(true);
|
||||
}
|
||||
teamData.setTeamInfo(teamInfoFromPacket);
|
||||
});
|
||||
}
|
||||
|
||||
public void removePlayerData(@NotNull Player player) {
|
||||
teams.remove(player.getUniqueId());
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
private void handleMetaData(@NotNull PacketSendEvent event) {
|
||||
if (!(event.getPlayer() instanceof Player)) {
|
||||
return;
|
||||
}
|
||||
int protocol = event.getUser().getClientVersion().getProtocolVersion();
|
||||
//handle metadata for : bedrock players && client with version 1.20.1 or lower
|
||||
if (protocol >= 764) {
|
||||
return;
|
||||
}
|
||||
|
||||
final WrapperPlayServerEntityMetadata packet = new WrapperPlayServerEntityMetadata(event);
|
||||
final Optional<PacketNameTag> textDisplay = plugin.getNametagManager().getPacketDisplayText(packet.getEntityId());
|
||||
if (textDisplay.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (final EntityData eData : packet.getEntityMetadata()) {
|
||||
if (eData.getIndex() == 11) {
|
||||
final Vector3f old = (Vector3f) eData.getValue();
|
||||
final Vector3f newV = new Vector3f(old.getX(), old.getY() + 0.45f, old.getZ());
|
||||
eData.setValue(newV);
|
||||
event.markForReEncode(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean existsPlayer(@NotNull String name) {
|
||||
// return plugin.getPlayerListener().getPlayerNameId().containsKey(name);
|
||||
return Bukkit.getPlayer(name) != null;
|
||||
}
|
||||
|
||||
public void onDisable() {
|
||||
PacketEvents.getAPI().getEventManager().unregisterListener(this);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package org.alexdev.unlimitednametags.events;
|
||||
package org.alexdev.unlimitednametags.listeners;
|
||||
|
||||
import io.papermc.paper.event.player.PlayerTrackEntityEvent;
|
||||
import io.papermc.paper.event.player.PlayerUntrackEntityEvent;
|
||||
+81
-36
@@ -1,8 +1,6 @@
|
||||
package org.alexdev.unlimitednametags.events;
|
||||
package org.alexdev.unlimitednametags.listeners;
|
||||
|
||||
import com.github.Anon8281.universalScheduler.foliaScheduler.FoliaScheduler;
|
||||
import com.github.retrooper.packetevents.PacketEvents;
|
||||
import com.github.retrooper.packetevents.manager.server.ServerVersion;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
import lombok.Getter;
|
||||
@@ -18,11 +16,9 @@ import org.bukkit.event.entity.PlayerDeathEvent;
|
||||
import org.bukkit.event.player.*;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
|
||||
public class PlayerListener implements PackSendHandler {
|
||||
|
||||
@@ -30,17 +26,26 @@ public class PlayerListener implements PackSendHandler {
|
||||
private final UnlimitedNameTags plugin;
|
||||
private final Set<UUID> diedPlayers;
|
||||
private final Map<Integer, UUID> playerEntityId;
|
||||
@Getter
|
||||
private final Map<String, UUID> playerNameId;
|
||||
@Getter
|
||||
private final Map<UUID, Player> onlinePlayers;
|
||||
|
||||
public PlayerListener(UnlimitedNameTags plugin) {
|
||||
this.plugin = plugin;
|
||||
this.diedPlayers = Sets.newConcurrentHashSet();
|
||||
this.playerEntityId = Maps.newConcurrentMap();
|
||||
this.playerNameId = Maps.newConcurrentMap();
|
||||
this.onlinePlayers = Maps.newConcurrentMap();
|
||||
this.loadFoliaRespawnTask();
|
||||
this.loadEntityIds();
|
||||
}
|
||||
|
||||
private void loadEntityIds() {
|
||||
Bukkit.getOnlinePlayers().forEach(player -> playerEntityId.put(player.getEntityId(), player.getUniqueId()));
|
||||
Bukkit.getOnlinePlayers().forEach(player -> {
|
||||
playerEntityId.put(player.getEntityId(), player.getUniqueId());
|
||||
onlinePlayers.put(player.getUniqueId(), player);
|
||||
});
|
||||
}
|
||||
|
||||
private void loadFoliaRespawnTask() {
|
||||
@@ -48,13 +53,14 @@ public class PlayerListener implements PackSendHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.getTaskScheduler().runTaskTimerAsynchronously(() -> diedPlayers.forEach(player -> {
|
||||
final Player p = plugin.getServer().getPlayer(player);
|
||||
if (p == null || p.isDead()) {
|
||||
plugin.getTaskScheduler().runTaskTimerAsynchronously(() -> diedPlayers.forEach(died -> {
|
||||
final Player player = plugin.getServer().getPlayer(died);
|
||||
if (player == null || player.isDead()) {
|
||||
return;
|
||||
}
|
||||
plugin.getNametagManager().addPlayer(p);
|
||||
diedPlayers.remove(player);
|
||||
|
||||
plugin.getNametagManager().showToTrackedPlayers(player);
|
||||
diedPlayers.remove(died);
|
||||
}), 1, 1);
|
||||
}
|
||||
|
||||
@@ -63,45 +69,70 @@ public class PlayerListener implements PackSendHandler {
|
||||
if (player == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return Optional.ofNullable(plugin.getServer().getPlayer(player));
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onJoin(@NotNull PlayerJoinEvent event) {
|
||||
plugin.getTaskScheduler().runTaskLaterAsynchronously(() -> plugin.getNametagManager().addPlayer(event.getPlayer()), 2);
|
||||
onlinePlayers.put(event.getPlayer().getUniqueId(), event.getPlayer());
|
||||
plugin.getTaskScheduler()
|
||||
.runTaskLaterAsynchronously(() -> plugin.getNametagManager().addPlayer(event.getPlayer(), true), 6);
|
||||
playerEntityId.put(event.getPlayer().getEntityId(), event.getPlayer().getUniqueId());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onQuit(@NotNull PlayerQuitEvent event) {
|
||||
plugin.getPacketEventsListener().removePlayerData(event.getPlayer());
|
||||
playerNameId.remove(event.getPlayer().getName());
|
||||
diedPlayers.remove(event.getPlayer().getUniqueId());
|
||||
plugin.getTaskScheduler().runTaskLaterAsynchronously(() -> plugin.getNametagManager().removePlayer(event.getPlayer(), true), 1);
|
||||
playerEntityId.remove(event.getPlayer().getEntityId());
|
||||
plugin.getTaskScheduler().runTaskAsynchronously(() -> {
|
||||
plugin.getNametagManager().removePlayer(event.getPlayer());
|
||||
plugin.getNametagManager().clearCache(event.getPlayer().getUniqueId());
|
||||
plugin.getPlaceholderManager().removePlayer(event.getPlayer());
|
||||
diedPlayers.remove(event.getPlayer().getUniqueId());
|
||||
onlinePlayers.remove(event.getPlayer().getUniqueId());
|
||||
playerEntityId.remove(event.getPlayer().getEntityId());
|
||||
});
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onPotion(@NotNull EntityPotionEffectEvent event) {
|
||||
if (!(event.getEntity() instanceof Player player)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (player.getGameMode() == GameMode.SPECTATOR) {
|
||||
if (plugin.getNametagManager().isDebug()) {
|
||||
plugin.getLogger().info("Player is in spectator mode, skipping potion event");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.getAction() == EntityPotionEffectEvent.Action.ADDED) {
|
||||
if (event.getNewEffect() == null || event.getNewEffect().getType() != PotionEffectType.INVISIBILITY) {
|
||||
if (event.getNewEffect() == null
|
||||
|| (!event.getNewEffect().getType().equals(PotionEffectType.INVISIBILITY))) {
|
||||
if (plugin.getNametagManager().isDebug()) {
|
||||
plugin.getLogger().info("Potion effect is not invisibility, skipping potion event");
|
||||
}
|
||||
return;
|
||||
}
|
||||
plugin.getNametagManager().removeAllViewers(player);
|
||||
} else if (event.getAction() == EntityPotionEffectEvent.Action.REMOVED || event.getAction() == EntityPotionEffectEvent.Action.CLEARED) {
|
||||
if (event.getOldEffect() == null || event.getOldEffect().getType() != PotionEffectType.INVISIBILITY) {
|
||||
plugin.getNametagManager().blockPlayer(player);
|
||||
} else if (event.getAction() == EntityPotionEffectEvent.Action.REMOVED
|
||||
|| event.getAction() == EntityPotionEffectEvent.Action.CLEARED) {
|
||||
if (event.getOldEffect() == null || !event.getOldEffect().getType().equals(PotionEffectType.INVISIBILITY)) {
|
||||
if (plugin.getNametagManager().isDebug()) {
|
||||
plugin.getLogger().info("Potion effect is not invisibility, skipping potion event: "
|
||||
+ (event.getOldEffect() != null ? event.getOldEffect().getType() : null));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.getTaskScheduler().runTaskLaterAsynchronously(() -> {
|
||||
plugin.getNametagManager().unblockPlayer(player);
|
||||
plugin.getNametagManager().showToTrackedPlayers(player, plugin.getTrackerManager().getTrackedPlayers().get(player.getUniqueId()));
|
||||
}, 1);
|
||||
plugin.getNametagManager().showToTrackedPlayers(player);
|
||||
}, 3);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -109,27 +140,30 @@ public class PlayerListener implements PackSendHandler {
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onGameModeChange(@NotNull PlayerGameModeChangeEvent e) {
|
||||
if (e.getPlayer().getGameMode() == GameMode.SPECTATOR) {
|
||||
plugin.getNametagManager().showToTrackedPlayers(e.getPlayer(), plugin.getTrackerManager().getTrackedPlayers().get(e.getPlayer().getUniqueId()));
|
||||
plugin.getNametagManager().unblockPlayer(e.getPlayer());
|
||||
plugin.getNametagManager().showToTrackedPlayers(e.getPlayer());
|
||||
} else if (e.getNewGameMode() == GameMode.SPECTATOR) {
|
||||
plugin.getNametagManager().removeAllViewers(e.getPlayer());
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onPlayerDeath(@NotNull PlayerDeathEvent event) {
|
||||
diedPlayers.add(event.getEntity().getUniqueId());
|
||||
plugin.getNametagManager().removeAllViewers(event.getEntity());
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onPlayerRespawn(@NotNull PlayerRespawnEvent event) {
|
||||
diedPlayers.remove(event.getPlayer().getUniqueId());
|
||||
plugin.getTaskScheduler().runTaskLaterAsynchronously(() -> {
|
||||
plugin.getNametagManager().getPacketDisplayText(event.getPlayer()).ifPresent(d -> d.setVisible(true));
|
||||
}, 1);
|
||||
if (event.getPlayer().getGameMode() == GameMode.SPECTATOR) {
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.getTaskScheduler().runTaskLaterAsynchronously(() -> plugin.getNametagManager().showToTrackedPlayers(event.getPlayer()), 5);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onPlayerVanishes(@NotNull PlayerShowEntityEvent event) {
|
||||
if (!(event.getEntity() instanceof Player player)) {
|
||||
return;
|
||||
@@ -144,7 +178,7 @@ public class PlayerListener implements PackSendHandler {
|
||||
});
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onPlayerUnvanishes(@NotNull PlayerHideEntityEvent event) {
|
||||
if (!(event.getEntity() instanceof Player player)) {
|
||||
return;
|
||||
@@ -155,14 +189,12 @@ public class PlayerListener implements PackSendHandler {
|
||||
});
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onTeleport(@NotNull PlayerTeleportEvent event) {
|
||||
//1.20.1+ bug
|
||||
if (PacketEvents.getAPI().getServerManager().getVersion().isOlderThan(ServerVersion.V_1_21_1) &&
|
||||
event.getFrom().getWorld() != event.getTo().getWorld()
|
||||
) {
|
||||
if (event.getFrom().getWorld() != event.getTo().getWorld()) {
|
||||
plugin.getTrackerManager().forceUntrack(event.getPlayer());
|
||||
}
|
||||
|
||||
if (!plugin.getConfigManager().getSettings().isShowCurrentNameTag()) {
|
||||
return;
|
||||
}
|
||||
@@ -175,7 +207,8 @@ public class PlayerListener implements PackSendHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.getTaskScheduler().runTaskLaterAsynchronously(() -> plugin.getNametagManager().showToOwner(event.getPlayer()), 5);
|
||||
plugin.getTaskScheduler()
|
||||
.runTaskLaterAsynchronously(() -> plugin.getNametagManager().showToOwner(event.getPlayer()), 5);
|
||||
}
|
||||
|
||||
public void logicElytra(Player player) {
|
||||
@@ -189,5 +222,17 @@ public class PlayerListener implements PackSendHandler {
|
||||
plugin.getTaskScheduler().runTaskLaterAsynchronously(packetDisplayText::showForOwner, 5);
|
||||
});
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Player getPlayer(@NotNull UUID uuid) {
|
||||
return onlinePlayers.get(uuid);
|
||||
}
|
||||
|
||||
public void close() {
|
||||
diedPlayers.clear();
|
||||
playerEntityId.clear();
|
||||
playerNameId.clear();
|
||||
onlinePlayers.clear();
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package org.alexdev.unlimitednametags.events;
|
||||
package org.alexdev.unlimitednametags.listeners;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import org.alexdev.unlimitednametags.UnlimitedNameTags;
|
||||
@@ -0,0 +1,188 @@
|
||||
package org.alexdev.unlimitednametags.listeners;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.alexdev.unlimitednametags.UnlimitedNameTags;
|
||||
import org.alexdev.unlimitednametags.data.ConcurrentSetMultimap;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class TrackerManager {
|
||||
|
||||
private final UnlimitedNameTags plugin;
|
||||
|
||||
/**
|
||||
* Map associating a player (key) with the players they are tracking (value).
|
||||
* A -> [B, C] means A sees B and C.
|
||||
*/
|
||||
@Getter
|
||||
private final ConcurrentSetMultimap<UUID, UUID> trackedPlayers;
|
||||
|
||||
/**
|
||||
* Reverse map associating a player (key) with those who are tracking them
|
||||
* (value).
|
||||
* B -> [A] means B is seen by A.
|
||||
* Used to optimize reverse lookups.
|
||||
*/
|
||||
private final ConcurrentSetMultimap<UUID, UUID> trackedBy;
|
||||
|
||||
public TrackerManager(UnlimitedNameTags plugin) {
|
||||
this.plugin = plugin;
|
||||
this.trackedPlayers = new ConcurrentSetMultimap<>();
|
||||
this.trackedBy = new ConcurrentSetMultimap<>();
|
||||
loadTracker();
|
||||
}
|
||||
|
||||
private void loadTracker() {
|
||||
Bukkit.getOnlinePlayers().forEach(player -> {
|
||||
// Unsafe call, but run only on startup
|
||||
List<Entity> nearbyEntities = player.getNearbyEntities(
|
||||
Bukkit.getViewDistance() * 16,
|
||||
256,
|
||||
Bukkit.getViewDistance() * 16);
|
||||
|
||||
for (Entity entity : nearbyEntities) {
|
||||
if (entity instanceof Player target) {
|
||||
trackedPlayers.put(player.getUniqueId(), target.getUniqueId());
|
||||
trackedBy.put(target.getUniqueId(), player.getUniqueId());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void onDisable() {
|
||||
trackedPlayers.clear();
|
||||
trackedBy.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles adding a target to a player's tracking list.
|
||||
* Updates both direct and reverse maps asynchronously.
|
||||
*
|
||||
* @param player The observer player.
|
||||
* @param target The target player being observed.
|
||||
*/
|
||||
public void handleAdd(@NotNull Player player, @NotNull Player target) {
|
||||
if (player.hasMetadata("NPC") || target.hasMetadata("NPC"))
|
||||
return;
|
||||
if (target.hasPotionEffect(org.bukkit.potion.PotionEffectType.INVISIBILITY))
|
||||
return;
|
||||
|
||||
if (trackedPlayers.containsEntry(player.getUniqueId(), target.getUniqueId())) {
|
||||
return;
|
||||
}
|
||||
|
||||
trackedPlayers.put(player.getUniqueId(), target.getUniqueId());
|
||||
trackedBy.put(target.getUniqueId(), player.getUniqueId());
|
||||
|
||||
plugin.getNametagManager().updateDisplay(player, target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes tracking between two players.
|
||||
*
|
||||
* @param player The observer player.
|
||||
* @param target The target player being observed.
|
||||
*/
|
||||
public void handleRemove(@NotNull Player player, @NotNull Player target) {
|
||||
plugin.getTaskScheduler().runTaskAsynchronously(() -> removePlayerInternal(player, target));
|
||||
}
|
||||
|
||||
private void removePlayerInternal(@NotNull Player player, @NotNull Player target) {
|
||||
trackedPlayers.remove(player.getUniqueId(), target.getUniqueId());
|
||||
trackedBy.remove(target.getUniqueId(), player.getUniqueId());
|
||||
plugin.getNametagManager().removeDisplay(player, target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a player quitting the server.
|
||||
* Efficiently cleans up all references in both maps.
|
||||
*
|
||||
* @param player The player who is quitting.
|
||||
*/
|
||||
public void handleQuit(@NotNull Player player) {
|
||||
plugin.getTaskScheduler().runTaskAsynchronously(() -> {
|
||||
UUID quittingUuid = player.getUniqueId();
|
||||
|
||||
// 1. Remove the player from the lists of those who were watching them.
|
||||
// trackedBy.removeAll returns who was watching the quitting player.
|
||||
Set<UUID> watchers = trackedBy.removeAll(quittingUuid);
|
||||
if (watchers != null) {
|
||||
for (UUID watcherUuid : watchers) {
|
||||
trackedPlayers.remove(watcherUuid, quittingUuid);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Remove the lists of players the quitting player was watching.
|
||||
// trackedPlayers.removeAll returns who the quitting player was watching.
|
||||
Set<UUID> targets = trackedPlayers.removeAll(quittingUuid);
|
||||
if (targets != null) {
|
||||
for (UUID targetUuid : targets) {
|
||||
trackedBy.remove(targetUuid, quittingUuid);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces removal of all targets tracked by a specific player.
|
||||
* Useful during world changes or distant teleports.
|
||||
*
|
||||
* @param player The player to reset.
|
||||
*/
|
||||
public void forceUntrack(@NotNull Player player) {
|
||||
Set<UUID> tracked = trackedPlayers.get(player.getUniqueId());
|
||||
if (tracked == null || tracked.isEmpty())
|
||||
return;
|
||||
|
||||
// Copy to avoid ConcurrentModification during iteration if necessary,
|
||||
// although removePlayerInternal handles concurrency.
|
||||
Set<UUID> targetsSnapshot = new HashSet<>(tracked);
|
||||
|
||||
Map<UUID, Player> onlinePlayers = plugin.getPlayerListener().getOnlinePlayers();
|
||||
|
||||
for (UUID targetUuid : targetsSnapshot) {
|
||||
Player target = onlinePlayers.get(targetUuid);
|
||||
if (target != null) {
|
||||
removePlayerInternal(player, target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the list of players currently tracking the specified target.
|
||||
* Uses the reverse map for O(1) performance.
|
||||
*
|
||||
* @param target The target player.
|
||||
* @return List of players seeing the target.
|
||||
*/
|
||||
@NotNull
|
||||
public List<Player> getWhoTracks(@NotNull Player target) {
|
||||
final List<Player> trackers = new ArrayList<>();
|
||||
final Map<UUID, Player> onlinePlayers = plugin.getPlayerListener().getOnlinePlayers();
|
||||
|
||||
Set<UUID> trackerUuids = trackedBy.get(target.getUniqueId());
|
||||
if (trackerUuids != null) {
|
||||
for (UUID uuid : trackerUuids) {
|
||||
Player p = onlinePlayers.get(uuid);
|
||||
if (p != null) {
|
||||
trackers.add(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
return trackers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the UUIDs of players that the specified tracker is observing.
|
||||
*
|
||||
* @param uuid UUID of the tracker.
|
||||
* @return Set of UUIDs of the targets.
|
||||
*/
|
||||
public Set<UUID> getTrackedPlayers(@NotNull UUID uuid) {
|
||||
return trackedPlayers.get(uuid);
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
@SuppressWarnings("all")
|
||||
public class Metrics {
|
||||
|
||||
private final Plugin plugin;
|
||||
|
||||
@@ -44,12 +44,13 @@ public class ConditionalManager {
|
||||
}
|
||||
|
||||
public boolean evaluateExpression(@NotNull Settings.ConditionalModifier modifier, @NotNull Player player) {
|
||||
final String entireExpression = plugin.getPlaceholderManager().getPapiManager().isPAPIEnabled() ?
|
||||
final String entireExpression = plugin.getPlaceholderManager().getPapiManager().isPapiEnabled() ?
|
||||
PlaceholderAPI.setPlaceholders(player, modifier.getExpression()) :
|
||||
modifier.getExpression();
|
||||
|
||||
if (cachedExpressions.containsKey(entireExpression)) {
|
||||
return (boolean) cachedExpressions.get(entireExpression);
|
||||
final Boolean cached = cachedExpressions.getOrDefault(entireExpression, null) instanceof Boolean b ? b : null;
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
JexlEngine jexlEngine = null;
|
||||
|
||||
@@ -14,11 +14,10 @@ import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import org.alexdev.unlimitednametags.UnlimitedNameTags;
|
||||
import org.alexdev.unlimitednametags.config.Settings;
|
||||
import org.alexdev.unlimitednametags.hook.HMCCosmeticsHook;
|
||||
import org.alexdev.unlimitednametags.hook.ViaVersionHook;
|
||||
import org.alexdev.unlimitednametags.packet.PacketNameTag;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Color;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.attribute.Attribute;
|
||||
import org.bukkit.attribute.AttributeInstance;
|
||||
import org.bukkit.command.CommandSender;
|
||||
@@ -28,7 +27,6 @@ import org.bukkit.util.Vector;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -40,10 +38,13 @@ public class NameTagManager {
|
||||
private final Map<Integer, PacketNameTag> entityIdToDisplay;
|
||||
private final Set<UUID> creating;
|
||||
private final Set<UUID> blocked;
|
||||
private final Set<UUID> hiddenOtherNametags;
|
||||
private final Set<UUID> hideNametags;
|
||||
private final Map<UUID, Settings.NameTag> nameTagOverrides;
|
||||
private final Map<UUID, Boolean> shiftSystemBlocked;
|
||||
private final List<MyScheduledTask> tasks;
|
||||
@Setter
|
||||
private boolean debug = false;
|
||||
private final Attribute scaleAttribute;
|
||||
|
||||
public NameTagManager(@NotNull UnlimitedNameTags plugin) {
|
||||
this.plugin = plugin;
|
||||
@@ -52,13 +53,16 @@ public class NameTagManager {
|
||||
this.tasks = Lists.newCopyOnWriteArrayList();
|
||||
this.creating = Sets.newConcurrentHashSet();
|
||||
this.blocked = Sets.newConcurrentHashSet();
|
||||
this.hiddenOtherNametags = Sets.newConcurrentHashSet();
|
||||
this.hideNametags = Sets.newConcurrentHashSet();
|
||||
this.nameTagOverrides = Maps.newConcurrentMap();
|
||||
this.shiftSystemBlocked = Maps.newConcurrentMap();
|
||||
this.loadAll();
|
||||
this.scaleAttribute = loadScaleAttribute();
|
||||
}
|
||||
|
||||
private void loadAll() {
|
||||
plugin.getTaskScheduler().runTaskLaterAsynchronously(() -> {
|
||||
Bukkit.getOnlinePlayers().forEach(this::addPlayer);
|
||||
plugin.getPlayerListener().getOnlinePlayers().values().forEach(p -> addPlayer(p, true));
|
||||
this.startTask();
|
||||
}, 5);
|
||||
}
|
||||
@@ -66,55 +70,55 @@ public class NameTagManager {
|
||||
private void startTask() {
|
||||
tasks.forEach(MyScheduledTask::cancel);
|
||||
final MyScheduledTask refresh = plugin.getTaskScheduler().runTaskTimerAsynchronously(
|
||||
() -> Bukkit.getOnlinePlayers().forEach(p -> refresh(p, false)),
|
||||
() -> {
|
||||
if (plugin.isPaper() && plugin.getServer().isStopping()) {
|
||||
return;
|
||||
}
|
||||
nameTags.values().forEach(tag -> refresh(tag.getOwner(), false));
|
||||
},
|
||||
10, plugin.getConfigManager().getSettings().getTaskInterval());
|
||||
|
||||
// Refresh passengers
|
||||
final MyScheduledTask passengers = plugin.getTaskScheduler().runTaskTimerAsynchronously(() ->
|
||||
Bukkit.getOnlinePlayers().forEach(player ->
|
||||
getPacketDisplayText(player)
|
||||
.ifPresent(PacketNameTag::sendPassengerPacketToViewers))
|
||||
, 20, 20 * 5L);
|
||||
final MyScheduledTask passengers = plugin.getTaskScheduler().runTaskTimerAsynchronously(() -> nameTags.values()
|
||||
.stream()
|
||||
.map(PacketNameTag::getOwner)
|
||||
.filter(p -> plugin.getHook(HMCCosmeticsHook.class).map(h -> !h.hasBackpack(p)).orElse(true))
|
||||
.forEach(player -> getPacketDisplayText(player)
|
||||
.ifPresent(PacketNameTag::sendPassengerPacketToViewers)),
|
||||
20, 20 * 5L);
|
||||
|
||||
// Scale task
|
||||
if (isScalePresent()) {
|
||||
final MyScheduledTask scale = plugin.getTaskScheduler().runTaskTimerAsynchronously(() ->
|
||||
Bukkit.getOnlinePlayers().forEach(player ->
|
||||
getPacketDisplayText(player)
|
||||
.filter(PacketNameTag::checkScale)
|
||||
.ifPresent(PacketNameTag::refresh))
|
||||
, 20, 10);
|
||||
final MyScheduledTask scale = plugin.getTaskScheduler()
|
||||
.runTaskTimerAsynchronously(() -> nameTags.values().forEach(tag -> {
|
||||
if (tag.checkScale()) {
|
||||
tag.refresh();
|
||||
}
|
||||
}), 20, 10);
|
||||
tasks.add(scale);
|
||||
}
|
||||
|
||||
if (plugin.getConfigManager().getSettings().isShowWhileLooking()) {
|
||||
final boolean current = plugin.getConfigManager().getSettings().isShowCurrentNameTag();
|
||||
final MyScheduledTask point = plugin.getTaskScheduler().runTaskTimerAsynchronously(() ->
|
||||
Bukkit.getOnlinePlayers().forEach(player1 -> {
|
||||
final Optional<PacketNameTag> display = getPacketDisplayText(player1);
|
||||
if (display.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Bukkit.getOnlinePlayers().forEach(player2 -> {
|
||||
if (plugin.getHook(ViaVersionHook.class).map(h -> h.hasNotTextDisplays(player2)).orElse(false)) {
|
||||
return;
|
||||
}
|
||||
final MyScheduledTask point = plugin.getTaskScheduler().runTaskTimerAsynchronously(() -> {
|
||||
nameTags.values().forEach(tag -> {
|
||||
final Player targetOwner = tag.getOwner();
|
||||
final List<Player> viewers = plugin.getTrackerManager().getWhoTracks(targetOwner);
|
||||
|
||||
if (player1.getWorld() != player2.getWorld()) {
|
||||
return;
|
||||
}
|
||||
for (Player viewer : viewers) {
|
||||
if (plugin.getHook(ViaVersionHook.class).map(h -> h.hasNotTextDisplays(viewer)).orElse(false)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final boolean isPointing = isPlayerPointingAt(player2, player1) || (current && player1 == player2);
|
||||
if (display.get().canPlayerSee(player2) && !isPointing) {
|
||||
display.get().hideFromPlayer(player2);
|
||||
} else if (!display.get().canPlayerSee(player2) && isPointing) {
|
||||
display.get().showToPlayer(player2);
|
||||
}
|
||||
final boolean isPointing = isPlayerPointingAt(viewer, targetOwner);
|
||||
|
||||
});
|
||||
})
|
||||
|
||||
, 5, 5);
|
||||
if (tag.canPlayerSee(viewer) && !isPointing) {
|
||||
tag.hideFromPlayer(viewer);
|
||||
} else if (!tag.canPlayerSee(viewer) && isPointing) {
|
||||
tag.showToPlayer(viewer);
|
||||
}
|
||||
}
|
||||
});
|
||||
}, 5, 5);
|
||||
tasks.add(point);
|
||||
}
|
||||
|
||||
@@ -122,6 +126,17 @@ public class NameTagManager {
|
||||
tasks.add(passengers);
|
||||
}
|
||||
|
||||
private Attribute loadScaleAttribute() {
|
||||
if (PacketEvents.getAPI().getServerManager().getVersion().isOlderThan(ServerVersion.V_1_20_5)) {
|
||||
return null;
|
||||
}
|
||||
if (PacketEvents.getAPI().getServerManager().getVersion().isNewerThan(ServerVersion.V_1_21_1)) {
|
||||
return Attribute.SCALE;
|
||||
} else {
|
||||
return Registry.ATTRIBUTE.get(NamespacedKey.minecraft("generic.scale"));
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isPlayerPointingAt(Player player1, Player player2) {
|
||||
if (player1.getWorld() != player2.getWorld()) {
|
||||
return false;
|
||||
@@ -148,11 +163,11 @@ public class NameTagManager {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(PacketEvents.getAPI().getServerManager().getVersion().isOlderThan(ServerVersion.V_1_20_5)) {
|
||||
if (PacketEvents.getAPI().getServerManager().getVersion().isOlderThan(ServerVersion.V_1_20_5)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
final AttributeInstance attribute = player.getAttribute(Attribute.GENERIC_SCALE);
|
||||
final AttributeInstance attribute = player.getAttribute(scaleAttribute);
|
||||
|
||||
if (attribute == null) {
|
||||
return 1;
|
||||
@@ -163,42 +178,110 @@ public class NameTagManager {
|
||||
|
||||
public void blockPlayer(@NotNull Player player) {
|
||||
blocked.add(player.getUniqueId());
|
||||
if (debug) {
|
||||
plugin.getLogger().info("Blocked " + player.getName());
|
||||
}
|
||||
}
|
||||
|
||||
public void unblockPlayer(@NotNull Player player) {
|
||||
blocked.remove(player.getUniqueId());
|
||||
if (debug) {
|
||||
plugin.getLogger().info("Unblocked " + player.getName());
|
||||
}
|
||||
}
|
||||
|
||||
public void addPlayer(@NotNull Player player) {
|
||||
public void clearCache(@NotNull UUID uuid) {
|
||||
blocked.remove(uuid);
|
||||
creating.remove(uuid);
|
||||
hideNametags.remove(uuid);
|
||||
nameTagOverrides.remove(uuid);
|
||||
shiftSystemBlocked.remove(uuid);
|
||||
}
|
||||
|
||||
public boolean hasNametagOverride(@NotNull Player player) {
|
||||
return nameTagOverrides.containsKey(player.getUniqueId());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Optional<Settings.NameTag> getNametagOverride(@NotNull Player player) {
|
||||
return Optional.ofNullable(nameTagOverrides.get(player.getUniqueId()));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Settings.NameTag getEffectiveNametag(@NotNull Player player) {
|
||||
return nameTagOverrides.getOrDefault(player.getUniqueId(),
|
||||
plugin.getConfigManager().getSettings().getNametag(player));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Settings.NameTag getConfigNametag(@NotNull Player player) {
|
||||
return plugin.getConfigManager().getSettings().getNametag(player);
|
||||
}
|
||||
|
||||
private boolean preAddChecks(@NotNull Player player, boolean canBlock) {
|
||||
if (nameTags.containsKey(player.getUniqueId())) {
|
||||
if (debug) {
|
||||
plugin.getLogger().info("Player " + player.getName() + " already has a nametag");
|
||||
}
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (creating.contains(player.getUniqueId())) {
|
||||
if (debug) {
|
||||
plugin.getLogger().info("Player " + player.getName() + " is already creating a nametag");
|
||||
}
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (blocked.contains(player.getUniqueId())) {
|
||||
if (debug) {
|
||||
plugin.getLogger().info("Player " + player.getName() + " is blocked");
|
||||
}
|
||||
return;
|
||||
return false;
|
||||
} else {
|
||||
if (debug) {
|
||||
plugin.getLogger().info("Player " + player.getName() + " is not blocked");
|
||||
}
|
||||
}
|
||||
|
||||
if (PacketEvents.getAPI().getPlayerManager().getUser(player) == null) {
|
||||
if (debug) {
|
||||
plugin.getLogger().info("Player " + player.getName() + " is not loaded");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (player.hasPotionEffect(PotionEffectType.INVISIBILITY)) {
|
||||
if (debug) {
|
||||
plugin.getLogger().info("Player " + player.getName() + " has invisibility potion effect, blocking");
|
||||
}
|
||||
blockPlayer(player);
|
||||
if (canBlock) {
|
||||
blockPlayer(player);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (player.getGameMode() == GameMode.SPECTATOR) {
|
||||
if (debug) {
|
||||
plugin.getLogger().info("Player " + player.getName() + " is in spectator mode, skipping");
|
||||
}
|
||||
if (canBlock) {
|
||||
blockPlayer(player);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void addPlayer(@NotNull Player player, boolean canBlock) {
|
||||
if (!preAddChecks(player, canBlock)) {
|
||||
return;
|
||||
}
|
||||
final Settings.NameTag nametag = plugin.getConfigManager().getSettings().getNametag(player);
|
||||
|
||||
creating.add(player.getUniqueId());
|
||||
|
||||
final Settings.NameTag nametag = getEffectiveNametag(player);
|
||||
final PacketNameTag display = new PacketNameTag(plugin, player, nametag);
|
||||
display.text(player, Component.empty());
|
||||
display.spawn(player);
|
||||
@@ -210,23 +293,25 @@ public class NameTagManager {
|
||||
handleVanish(player, display);
|
||||
|
||||
nameTags.put(player.getUniqueId(), display);
|
||||
if (debug) {
|
||||
plugin.getLogger().info("Added nametag for " + player.getName());
|
||||
}
|
||||
entityIdToDisplay.put(display.getEntityId(), display);
|
||||
|
||||
creating.add(player.getUniqueId());
|
||||
|
||||
plugin.getPlaceholderManager().applyPlaceholders(player, nametag.linesGroups(), List.of(player))
|
||||
.thenAccept(lines -> loadDisplay(player, lines.get(player), nametag, display))
|
||||
.exceptionally(throwable -> {
|
||||
plugin.getLogger().log(java.util.logging.Level.SEVERE, "Failed to create nametag for " + player.getName(), throwable);
|
||||
plugin.getLogger().log(java.util.logging.Level.SEVERE,
|
||||
"Failed to create nametag for " + player.getName(), throwable);
|
||||
creating.remove(player.getUniqueId());
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
public void refresh(@NotNull Player player, boolean force) {
|
||||
final Settings.NameTag nametag = plugin.getConfigManager().getSettings().getNametag(player);
|
||||
final Settings.NameTag nametag = getEffectiveNametag(player);
|
||||
|
||||
if(PacketEvents.getAPI().getPlayerManager().getUser(player) == null) {
|
||||
if (PacketEvents.getAPI().getPlayerManager().getUser(player) == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -246,6 +331,14 @@ public class NameTagManager {
|
||||
display.hideFromPlayer(player);
|
||||
}
|
||||
|
||||
if (force) {
|
||||
if (show) {
|
||||
display.showToPlayer(display.getOwner());
|
||||
} else {
|
||||
display.hideFromPlayer(display.getOwner());
|
||||
}
|
||||
}
|
||||
|
||||
final List<Player> relationalPlayers = display.getViewers().stream()
|
||||
.map(Bukkit::getPlayer)
|
||||
.filter(Objects::nonNull)
|
||||
@@ -254,13 +347,14 @@ public class NameTagManager {
|
||||
plugin.getPlaceholderManager().applyPlaceholders(player, nametag.linesGroups(), relationalPlayers)
|
||||
.thenAccept(lines -> editDisplay(player, lines, nametag, force))
|
||||
.exceptionally(throwable -> {
|
||||
plugin.getLogger().log(java.util.logging.Level.SEVERE, "Failed to edit nametag for " + player.getName(), throwable);
|
||||
plugin.getLogger().log(java.util.logging.Level.SEVERE,
|
||||
"Failed to edit nametag for " + player.getName(), throwable);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private void editDisplay(@NotNull Player player, Map<Player, Component> components,
|
||||
@NotNull Settings.NameTag nameTag, boolean force) {
|
||||
@NotNull Settings.NameTag nameTag, boolean force) {
|
||||
getPacketDisplayText(player).ifPresent(packetNameTag -> {
|
||||
if (!packetNameTag.getNameTag().equals(nameTag)) {
|
||||
packetNameTag.setNameTag(nameTag);
|
||||
@@ -268,35 +362,37 @@ public class NameTagManager {
|
||||
if (force && isScalePresent()) {
|
||||
packetNameTag.checkScale();
|
||||
}
|
||||
|
||||
final boolean shadowed = nameTag.background().shadowed();
|
||||
final boolean seeThrough = nameTag.background().seeThrough() && !packetNameTag.isSneaking();
|
||||
final int backgroundColor = nameTag.background().getColor().asARGB();
|
||||
|
||||
components.forEach((p, c) -> {
|
||||
final AtomicBoolean update = new AtomicBoolean(false);
|
||||
update.set(packetNameTag.text(p, c) || force);
|
||||
final boolean[] updateRef = { packetNameTag.text(p, c) || force };
|
||||
final User user = PacketEvents.getAPI().getPlayerManager().getUser(p);
|
||||
if (user == null) {
|
||||
return;
|
||||
}
|
||||
packetNameTag.modify(user, m -> {
|
||||
|
||||
if(force) {
|
||||
m.setShadow(nameTag.background().shadowed());
|
||||
m.setSeeThrough(nameTag.background().seeThrough());
|
||||
m.setBackgroundColor(nameTag.background().getColor().asARGB());
|
||||
update.set(true);
|
||||
if (force) {
|
||||
m.setShadow(shadowed);
|
||||
m.setSeeThrough(seeThrough);
|
||||
m.setBackgroundColor(backgroundColor);
|
||||
} else {
|
||||
if(m.isShadow() != nameTag.background().shadowed()) {
|
||||
m.setShadow(nameTag.background().shadowed());
|
||||
update.set(true);
|
||||
if (m.isShadow() != shadowed) {
|
||||
m.setShadow(shadowed);
|
||||
updateRef[0] = true;
|
||||
}
|
||||
if(m.isSeeThrough() != nameTag.background().seeThrough()) {
|
||||
m.setSeeThrough(nameTag.background().seeThrough());
|
||||
update.set(true);
|
||||
if (m.isSeeThrough() != seeThrough) {
|
||||
m.setSeeThrough(seeThrough);
|
||||
updateRef[0] = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
|
||||
if (update.get()) {
|
||||
if (updateRef[0]) {
|
||||
packetNameTag.refreshForPlayer(p);
|
||||
}
|
||||
});
|
||||
@@ -304,11 +400,11 @@ public class NameTagManager {
|
||||
}
|
||||
|
||||
private void loadDisplay(@NotNull Player player, @NotNull Component component,
|
||||
@NotNull Settings.NameTag nameTag,
|
||||
@NotNull PacketNameTag display) {
|
||||
@NotNull Settings.NameTag nameTag,
|
||||
@NotNull PacketNameTag display) {
|
||||
try {
|
||||
final Location location = player.getLocation().clone();
|
||||
//add 1.80 to make a perfect tp animation
|
||||
// add 1.80 to make a perfect tp animation
|
||||
location.setY(location.getY() + 1.80);
|
||||
|
||||
creating.remove(player.getUniqueId());
|
||||
@@ -316,11 +412,10 @@ public class NameTagManager {
|
||||
display.text(player, component);
|
||||
display.setBillboard(plugin.getConfigManager().getSettings().getDefaultBillboard());
|
||||
display.setShadowed(nameTag.background().shadowed());
|
||||
display.setSeeThrough(nameTag.background().seeThrough());
|
||||
//background color, if disabled, set to transparent
|
||||
display.setSeeThrough(nameTag.background().seeThrough() && !display.isSneaking());
|
||||
// background color, if disabled, set to transparent
|
||||
display.setBackgroundColor(nameTag.background().getColor());
|
||||
|
||||
// display.setTransformation(new Vector3f(0, plugin.getConfigManager().getSettings().getYOffset(), 0));
|
||||
display.resetOffset(plugin.getConfigManager().getSettings().getYOffset());
|
||||
|
||||
display.setViewRange(plugin.getConfigManager().getSettings().getViewDistance());
|
||||
@@ -332,15 +427,17 @@ public class NameTagManager {
|
||||
handleVanish(player, display);
|
||||
|
||||
} catch (Throwable e) {
|
||||
plugin.getLogger().log(java.util.logging.Level.SEVERE, "Failed to create nametag for " + player.getName(), e);
|
||||
plugin.getLogger().log(java.util.logging.Level.SEVERE, "Failed to create nametag for " + player.getName(),
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleVanish(@NotNull Player player, @NotNull PacketNameTag display) {
|
||||
final boolean isVanished = plugin.getVanishManager().isVanished(player);
|
||||
|
||||
//if player is vanished, hide display for all players except for who can see the player
|
||||
Bukkit.getOnlinePlayers().stream()
|
||||
// if player is vanished, hide display for all players except for who can see
|
||||
// the player
|
||||
plugin.getPlayerListener().getOnlinePlayers().values().stream()
|
||||
.filter(p -> p != player)
|
||||
.filter(p -> p.getLocation().getWorld() == player.getLocation().getWorld())
|
||||
.filter(p -> !isVanished || plugin.getVanishManager().canSee(p, player))
|
||||
@@ -349,21 +446,15 @@ public class NameTagManager {
|
||||
.forEach(display::showToPlayer);
|
||||
}
|
||||
|
||||
|
||||
public void removePlayer(@NotNull Player player, boolean quit) {
|
||||
public void removePlayer(@NotNull Player player) {
|
||||
final PacketNameTag packetNameTag = nameTags.remove(player.getUniqueId());
|
||||
if (packetNameTag != null) {
|
||||
packetNameTag.remove();
|
||||
entityIdToDisplay.remove(packetNameTag.getEntityId());
|
||||
}
|
||||
|
||||
entityIdToDisplay.remove(player.getEntityId());
|
||||
|
||||
nameTags.forEach((uuid, display) -> {
|
||||
if (quit) {
|
||||
display.handleQuit(player);
|
||||
} else {
|
||||
display.hideFromPlayerSilently(player);
|
||||
}
|
||||
display.handleQuit(player);
|
||||
display.getBlocked().remove(player.getUniqueId());
|
||||
});
|
||||
}
|
||||
@@ -376,18 +467,27 @@ public class NameTagManager {
|
||||
}
|
||||
}
|
||||
|
||||
public void showToTrackedPlayers(@NotNull Player player, @NotNull Collection<UUID> tracked) {
|
||||
public void showToTrackedPlayers(@NotNull Player player) {
|
||||
showToTrackedPlayers(player, plugin.getTrackerManager().getWhoTracks(player));
|
||||
}
|
||||
|
||||
public void showToTrackedPlayers(@NotNull Player player, @NotNull Collection<Player> tracked) {
|
||||
final PacketNameTag packetNameTag = nameTags.get(player.getUniqueId());
|
||||
if (packetNameTag != null) {
|
||||
packetNameTag.setVisible(true);
|
||||
packetNameTag.showToPlayers(tracked.stream()
|
||||
.map(Bukkit::getPlayer)
|
||||
final Set<Player> players = tracked.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet()));
|
||||
.collect(Collectors.toSet());
|
||||
players.add(packetNameTag.getOwner());
|
||||
packetNameTag.showToPlayers(players);
|
||||
if (debug) {
|
||||
plugin.getLogger().info("Showing nametag of " + player.getName() + " to tracked players: " +
|
||||
players.stream().map(Player::getName).collect(Collectors.joining(", ")));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
addPlayer(player);
|
||||
addPlayer(player, false);
|
||||
}
|
||||
|
||||
public void hideAllDisplays(@NotNull Player player) {
|
||||
@@ -406,11 +506,18 @@ public class NameTagManager {
|
||||
}
|
||||
|
||||
public void updateSneaking(@NotNull Player player, boolean sneaking) {
|
||||
if (shiftSystemBlocked.getOrDefault(player.getUniqueId(), false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
getPacketDisplayText(player).ifPresent(packetNameTag -> {
|
||||
if (packetNameTag.getNameTag().background().seeThrough()) {
|
||||
packetNameTag.setSeeThrough(!sneaking);
|
||||
}
|
||||
packetNameTag.setTextOpacity((byte) (sneaking ? plugin.getConfigManager().getSettings().getSneakOpacity() : -1));
|
||||
|
||||
packetNameTag.setSneaking(sneaking);
|
||||
packetNameTag
|
||||
.setTextOpacity((byte) (sneaking ? plugin.getConfigManager().getSettings().getSneakOpacity() : -1));
|
||||
packetNameTag.refresh();
|
||||
});
|
||||
}
|
||||
@@ -418,22 +525,27 @@ public class NameTagManager {
|
||||
public void reload() {
|
||||
final float yOffset = plugin.getConfigManager().getSettings().getYOffset();
|
||||
final float viewDistance = plugin.getConfigManager().getSettings().getViewDistance();
|
||||
final AbstractDisplayMeta.BillboardConstraints billboard = plugin.getConfigManager().getSettings().getDefaultBillboard();
|
||||
final AbstractDisplayMeta.BillboardConstraints billboard = plugin.getConfigManager().getSettings()
|
||||
.getDefaultBillboard();
|
||||
|
||||
plugin.getTaskScheduler().runTaskAsynchronously(() -> Bukkit.getOnlinePlayers().forEach(p -> {
|
||||
setYOffset(p, yOffset);
|
||||
setViewDistance(p, viewDistance);
|
||||
setBillBoard(p, billboard);
|
||||
refresh(p, true);
|
||||
}));
|
||||
plugin.getTaskScheduler()
|
||||
.runTaskAsynchronously(() -> plugin.getPlayerListener().getOnlinePlayers().values().forEach(p -> {
|
||||
setYOffset(p, yOffset);
|
||||
setViewDistance(p, viewDistance);
|
||||
setBillBoard(p, billboard);
|
||||
refresh(p, true);
|
||||
}));
|
||||
startTask();
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
public void debug(@NotNull CommandSender audience) {
|
||||
audience.sendRichMessage("<red>UnlimitedNameTags v" + plugin.getPluginMeta().getVersion() + " . Compiled: " + plugin.getConfigManager().isCompiled());
|
||||
final AtomicReference<Component> component = new AtomicReference<>(Component.text("Nametags:").colorIfAbsent(TextColor.color(0xFF0000)));
|
||||
audience.sendRichMessage("<red>UnlimitedNameTags v" + plugin.getPluginMeta().getVersion() + " . Compiled: "
|
||||
+ plugin.getConfigManager().isCompiled());
|
||||
final AtomicReference<Component> component = new AtomicReference<>(
|
||||
Component.text("Nametags:").colorIfAbsent(TextColor.color(0xFF0000)));
|
||||
nameTags.forEach((uuid, display) -> {
|
||||
final Player player = Bukkit.getPlayer(uuid);
|
||||
final Player player = plugin.getPlayerListener().getPlayer(uuid);
|
||||
|
||||
if (player == null) {
|
||||
return;
|
||||
@@ -443,7 +555,12 @@ public class NameTagManager {
|
||||
.map(Bukkit::getPlayer)
|
||||
.filter(Objects::nonNull)
|
||||
.map(Player::getName)
|
||||
.toList();
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (!plugin.getConfigManager().getSettings().isShowCurrentNameTag()) {
|
||||
viewers.remove(player.getName());
|
||||
}
|
||||
|
||||
final long lastUpdate = display.getLastUpdate();
|
||||
|
||||
final Component text = getComponent(display, viewers, player, lastUpdate);
|
||||
@@ -455,7 +572,7 @@ public class NameTagManager {
|
||||
|
||||
@NotNull
|
||||
private Component getComponent(@NotNull PacketNameTag display, @NotNull List<String> viewers,
|
||||
@NotNull Player player, long lastUpdate) {
|
||||
@NotNull Player player, long lastUpdate) {
|
||||
final int seconds = (int) ((System.currentTimeMillis() - lastUpdate) / 1000);
|
||||
final Map<String, String> properties = display.properties();
|
||||
Component hover = Component.text("Viewers: " + viewers).appendNewline()
|
||||
@@ -487,13 +604,12 @@ public class NameTagManager {
|
||||
getPacketDisplayText(player).ifPresent(packetNameTag -> packetNameTag.setViewRange(viewDistance));
|
||||
}
|
||||
|
||||
|
||||
public void vanishPlayer(@NotNull Player player) {
|
||||
getPacketDisplayText(player).ifPresent(packetNameTag -> {
|
||||
final Set<UUID> viewers = new HashSet<>(packetNameTag.getViewers());
|
||||
final boolean isVanished = plugin.getVanishManager().isVanished(player);
|
||||
viewers.forEach(uuid -> {
|
||||
final Player viewer = Bukkit.getPlayer(uuid);
|
||||
final Player viewer = plugin.getPlayerListener().getPlayer(uuid);
|
||||
if (viewer == null || viewer == player) {
|
||||
return;
|
||||
}
|
||||
@@ -509,7 +625,7 @@ public class NameTagManager {
|
||||
getPacketDisplayText(player).ifPresent(packetNameTag -> {
|
||||
final Set<UUID> viewers = new HashSet<>(packetNameTag.getViewers());
|
||||
viewers.forEach(uuid -> {
|
||||
final Player viewer = Bukkit.getPlayer(uuid);
|
||||
final Player viewer = plugin.getPlayerListener().getPlayer(uuid);
|
||||
if (viewer == null || viewer == player) {
|
||||
return;
|
||||
}
|
||||
@@ -518,7 +634,6 @@ public class NameTagManager {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@NotNull
|
||||
public Optional<PacketNameTag> getPacketDisplayText(@NotNull Player player) {
|
||||
return Optional.ofNullable(nameTags.get(player.getUniqueId()));
|
||||
@@ -596,7 +711,7 @@ public class NameTagManager {
|
||||
}
|
||||
|
||||
public void hideOtherNametags(@NotNull Player player) {
|
||||
hiddenOtherNametags.add(player.getUniqueId());
|
||||
hideNametags.add(player.getUniqueId());
|
||||
nameTags.forEach((uuid, display) -> {
|
||||
if (display.canPlayerSee(player)) {
|
||||
display.hideFromPlayer(player);
|
||||
@@ -605,17 +720,91 @@ public class NameTagManager {
|
||||
}
|
||||
|
||||
public void showOtherNametags(@NotNull Player player) {
|
||||
hiddenOtherNametags.remove(player.getUniqueId());
|
||||
hideNametags.remove(player.getUniqueId());
|
||||
plugin.getTrackerManager().getTrackedPlayers(player.getUniqueId()).forEach(uuid -> {
|
||||
final Player tracked = Bukkit.getPlayer(uuid);
|
||||
final Player tracked = plugin.getPlayerListener().getPlayer(uuid);
|
||||
if (tracked == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
getPacketDisplayText(tracked).ifPresent(display -> display.showToPlayer(player));
|
||||
});
|
||||
}
|
||||
|
||||
public boolean isHiddenOtherNametags(@NotNull Player player) {
|
||||
return hiddenOtherNametags.contains(player.getUniqueId());
|
||||
return hideNametags.contains(player.getUniqueId());
|
||||
}
|
||||
|
||||
public void swapNametag(@NotNull Player player, @NotNull Settings.NameTag nameTag) {
|
||||
final PacketNameTag display = nameTags.get(player.getUniqueId());
|
||||
if (display == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
display.setNameTag(nameTag);
|
||||
|
||||
final List<Player> relationalPlayers = display.getViewers().stream()
|
||||
.map(Bukkit::getPlayer)
|
||||
.filter(Objects::nonNull)
|
||||
.toList();
|
||||
|
||||
plugin.getPlaceholderManager().applyPlaceholders(player, nameTag.linesGroups(), relationalPlayers)
|
||||
.thenAccept(lines -> {
|
||||
final Component component = lines.get(player);
|
||||
display.text(player, component);
|
||||
display.setBillboard(plugin.getConfigManager().getSettings().getDefaultBillboard());
|
||||
display.setShadowed(nameTag.background().shadowed());
|
||||
display.setSeeThrough(nameTag.background().seeThrough() && !display.isSneaking());
|
||||
display.setBackgroundColor(nameTag.background().getColor());
|
||||
display.resetOffset(plugin.getConfigManager().getSettings().getYOffset());
|
||||
display.setViewRange(plugin.getConfigManager().getSettings().getViewDistance());
|
||||
|
||||
plugin.getTaskScheduler().runTaskLater(() -> display.modifyOwner(meta -> meta.setText(component)),
|
||||
1);
|
||||
|
||||
lines.forEach((p, c) -> {
|
||||
if (!p.equals(player)) {
|
||||
display.text(p, c);
|
||||
}
|
||||
});
|
||||
|
||||
display.refresh();
|
||||
})
|
||||
.exceptionally(throwable -> {
|
||||
plugin.getLogger().log(java.util.logging.Level.SEVERE,
|
||||
"Failed to swap nametag for " + player.getName(), throwable);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
public void setNametagOverride(@NotNull Player player, @NotNull Settings.NameTag nameTag) {
|
||||
nameTagOverrides.put(player.getUniqueId(), nameTag);
|
||||
|
||||
final PacketNameTag display = nameTags.get(player.getUniqueId());
|
||||
if (display != null) {
|
||||
swapNametag(player, nameTag);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeNametagOverride(@NotNull Player player) {
|
||||
nameTagOverrides.remove(player.getUniqueId());
|
||||
|
||||
final PacketNameTag display = nameTags.get(player.getUniqueId());
|
||||
if (display != null) {
|
||||
final Settings.NameTag configNametag = getConfigNametag(player);
|
||||
swapNametag(player, configNametag);
|
||||
}
|
||||
}
|
||||
|
||||
public void setShiftSystemBlocked(@NotNull Player player, boolean blocked) {
|
||||
if (blocked) {
|
||||
shiftSystemBlocked.put(player.getUniqueId(), true);
|
||||
} else {
|
||||
shiftSystemBlocked.remove(player.getUniqueId());
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isShiftSystemBlocked(@NotNull Player player) {
|
||||
return shiftSystemBlocked.getOrDefault(player.getUniqueId(), false);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
package org.alexdev.unlimitednametags.packet;
|
||||
|
||||
import com.github.retrooper.packetevents.PacketEvents;
|
||||
import io.github.retrooper.packetevents.adventure.serializer.legacy.LegacyComponentSerializer;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
import org.alexdev.unlimitednametags.UnlimitedNameTags;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
@@ -1,40 +1,41 @@
|
||||
package org.alexdev.unlimitednametags.packet;
|
||||
|
||||
import com.github.retrooper.packetevents.PacketEvents;
|
||||
import com.github.retrooper.packetevents.protocol.player.User;
|
||||
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerSetPassengers;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.collect.Multimaps;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import io.github.retrooper.packetevents.util.SpigotReflectionUtil;
|
||||
import me.tofaa.entitylib.APIConfig;
|
||||
import me.tofaa.entitylib.EntityLib;
|
||||
import me.tofaa.entitylib.spigot.SpigotEntityLibPlatform;
|
||||
import org.alexdev.unlimitednametags.UnlimitedNameTags;
|
||||
import org.alexdev.unlimitednametags.data.ConcurrentMultimap;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
public class PacketManager {
|
||||
|
||||
private final UnlimitedNameTags plugin;
|
||||
private final Multimap<UUID, Integer> passengers;
|
||||
private final ConcurrentMultimap<UUID, Integer> passengers;
|
||||
private final ExecutorService executorService;
|
||||
|
||||
public PacketManager(@NotNull UnlimitedNameTags plugin) {
|
||||
this.plugin = plugin;
|
||||
this.initialize();
|
||||
this.passengers = (Multimaps.newSetMultimap(Maps.newConcurrentMap(), Sets::newConcurrentHashSet)); //Multimaps.synchronizedMultimap
|
||||
this.executorService = Executors.newFixedThreadPool(2, r -> {
|
||||
final Thread thread = new Thread(r);
|
||||
thread.setName("UnlimitedNameTags-PacketManager");
|
||||
return thread;
|
||||
});
|
||||
this.passengers = new ConcurrentMultimap<>();
|
||||
final ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
|
||||
.setNameFormat("UnlimitedNameTags-PacketManager-%d")
|
||||
.build();
|
||||
this.executorService = Executors.newFixedThreadPool(
|
||||
Math.max(2, Runtime.getRuntime().availableProcessors() / 2),
|
||||
namedThreadFactory
|
||||
);
|
||||
}
|
||||
|
||||
private void initialize() {
|
||||
@@ -49,34 +50,37 @@ public class PacketManager {
|
||||
}
|
||||
|
||||
public void setPassengers(@NotNull Player player, @NotNull List<Integer> passengers) {
|
||||
final List<Integer> clone = List.copyOf(passengers);
|
||||
plugin.getTaskScheduler().runTaskAsynchronously(() -> {
|
||||
executorService.submit(() -> this.passengers.replaceValues(player.getUniqueId(), clone));
|
||||
});
|
||||
this.passengers.replaceValues(player.getUniqueId(), passengers);
|
||||
}
|
||||
|
||||
public void sendPassengersPacket(@NotNull Player player, @NotNull PacketNameTag packetNameTag) {
|
||||
public void sendPassengersPacket(@NotNull User player, @NotNull PacketNameTag packetNameTag) {
|
||||
final int entityId = packetNameTag.getEntityId();
|
||||
final int ownerId = packetNameTag.getOwner().getEntityId();
|
||||
executorService.submit(() -> {
|
||||
final Set<Integer> passengers = Sets.newHashSet(this.passengers.get(packetNameTag.getOwner().getUniqueId()));
|
||||
if (player.getChannel() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Collection<Integer> ownerPassengers = this.passengers.get(packetNameTag.getOwner().getUniqueId());
|
||||
final Set<Integer> passengers = Sets.newHashSetWithExpectedSize(ownerPassengers.size() + 1);
|
||||
passengers.addAll(ownerPassengers);
|
||||
passengers.add(entityId);
|
||||
final int[] passengersArray = passengers.stream().mapToInt(i -> i).toArray();
|
||||
final int[] passengersArray = passengers.stream().sorted().mapToInt(i -> i).toArray();
|
||||
final WrapperPlayServerSetPassengers packet = new WrapperPlayServerSetPassengers(ownerId, passengersArray);
|
||||
PacketEvents.getAPI().getPlayerManager().sendPacket(player, packet);
|
||||
player.sendPacketSilently(packet);
|
||||
});
|
||||
}
|
||||
|
||||
public void removePassenger(@NotNull Player player, int passenger) {
|
||||
executorService.submit(() -> this.passengers.remove(player.getUniqueId(), passenger));
|
||||
this.passengers.remove(player.getUniqueId(), passenger);
|
||||
}
|
||||
|
||||
public synchronized int getEntityIndex() {
|
||||
public int getEntityIndex() {
|
||||
return SpigotReflectionUtil.generateEntityId();
|
||||
}
|
||||
|
||||
public void removePassenger(int passenger) {
|
||||
this.passengers.values().remove(passenger);
|
||||
this.passengers.removeValueFromAll(passenger);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,7 +11,6 @@ import com.google.common.collect.Sets;
|
||||
import io.github.retrooper.packetevents.util.SpigotConversionUtil;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.SneakyThrows;
|
||||
import me.tofaa.entitylib.meta.Metadata;
|
||||
import me.tofaa.entitylib.meta.display.AbstractDisplayMeta;
|
||||
import me.tofaa.entitylib.meta.display.TextDisplayMeta;
|
||||
@@ -22,14 +21,12 @@ import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import org.alexdev.unlimitednametags.UnlimitedNameTags;
|
||||
import org.alexdev.unlimitednametags.config.Settings;
|
||||
import org.alexdev.unlimitednametags.hook.ViaVersionHook;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Color;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.attribute.Attribute;
|
||||
import org.bukkit.attribute.AttributeInstance;
|
||||
import org.bukkit.entity.Display;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
@@ -40,13 +37,15 @@ public class PacketNameTag {
|
||||
|
||||
private final UnlimitedNameTags plugin;
|
||||
private final WrapperPerPlayerEntity perPlayerEntity;
|
||||
private final Set<UUID> viewers;
|
||||
private final int entityId;
|
||||
private final UUID entityIdUuid;
|
||||
private final Player owner;
|
||||
private final Set<UUID> blocked;
|
||||
@NotNull
|
||||
private final Map<UUID, Component> relationalCache;
|
||||
private final Map<UUID, Component> calculatedTextCache = Maps.newConcurrentMap();
|
||||
private final Map<UUID, Component> forcedRelationalText = Maps.newConcurrentMap();
|
||||
private volatile Component forcedGlobalText = null;
|
||||
private long lastUpdate;
|
||||
@Setter
|
||||
private boolean visible;
|
||||
@@ -55,6 +54,9 @@ public class PacketNameTag {
|
||||
private float scale;
|
||||
private float offset;
|
||||
private float increasedOffset;
|
||||
private volatile boolean removed;
|
||||
@Setter
|
||||
private boolean sneaking;
|
||||
|
||||
public PacketNameTag(@NotNull UnlimitedNameTags plugin, @NotNull Player owner, @NotNull Settings.NameTag nameTag) {
|
||||
this.plugin = plugin;
|
||||
@@ -63,7 +65,6 @@ public class PacketNameTag {
|
||||
this.entityId = plugin.getPacketManager().getEntityIndex();
|
||||
this.entityIdUuid = UUID.randomUUID();
|
||||
this.perPlayerEntity = new WrapperPerPlayerEntity(this.getBaseSupplier());
|
||||
this.viewers = Sets.newConcurrentHashSet();
|
||||
this.blocked = Sets.newConcurrentHashSet();
|
||||
this.lastUpdate = System.currentTimeMillis();
|
||||
this.nameTag = nameTag;
|
||||
@@ -82,6 +83,20 @@ public class PacketNameTag {
|
||||
}
|
||||
|
||||
public boolean text(@NotNull Player player, @NotNull Component text) {
|
||||
if (removed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
calculatedTextCache.put(player.getUniqueId(), text);
|
||||
|
||||
if (forcedRelationalText.containsKey(player.getUniqueId()) || forcedGlobalText != null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return applyText(player, text);
|
||||
}
|
||||
|
||||
private boolean applyText(@NotNull Player player, @NotNull Component text) {
|
||||
if (text.equals(relationalCache.get(player.getUniqueId()))) {
|
||||
return false;
|
||||
}
|
||||
@@ -97,44 +112,74 @@ public class PacketNameTag {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void modify(User user, Consumer<TextDisplayMeta> consumer) {
|
||||
public void modify(@Nullable User user, @NotNull Consumer<TextDisplayMeta> consumer) {
|
||||
if (removed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (user == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
perPlayerEntity.modify(user, e -> {
|
||||
if (e == null) {
|
||||
return; // Entity removed (e.g. player disconnected during async refresh)
|
||||
}
|
||||
final TextDisplayMeta meta = (TextDisplayMeta) e.getEntityMeta();
|
||||
consumer.accept(meta);
|
||||
});
|
||||
}
|
||||
|
||||
public void modifyOwner(Consumer<TextDisplayMeta> consumer) {
|
||||
public void modifyOwner(@NotNull Consumer<TextDisplayMeta> consumer) {
|
||||
if (removed) {
|
||||
return;
|
||||
}
|
||||
|
||||
final User owner = PacketEvents.getAPI().getPlayerManager().getUser(this.owner);
|
||||
if (owner == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
modify(owner, consumer);
|
||||
}
|
||||
|
||||
public void modifyOwnerEntity(Consumer<WrapperEntity> consumer) {
|
||||
public void modifyOwnerEntity(@NotNull Consumer<WrapperEntity> consumer) {
|
||||
if (removed) {
|
||||
return;
|
||||
}
|
||||
|
||||
final User owner = PacketEvents.getAPI().getPlayerManager().getUser(this.owner);
|
||||
if (owner == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
modifyEntity(owner, consumer);
|
||||
}
|
||||
|
||||
public void modify(Consumer<TextDisplayMeta> consumer) {
|
||||
public void modify(@NotNull Consumer<TextDisplayMeta> consumer) {
|
||||
if (removed) {
|
||||
return;
|
||||
}
|
||||
|
||||
perPlayerEntity.execute(e -> {
|
||||
final TextDisplayMeta meta = (TextDisplayMeta) e.getEntityMeta();
|
||||
consumer.accept(meta);
|
||||
});
|
||||
}
|
||||
|
||||
public void modifyEntity(User user, Consumer<WrapperEntity> consumer) {
|
||||
public void modifyEntity(@Nullable User user, @NotNull Consumer<WrapperEntity> consumer) {
|
||||
if (removed) {
|
||||
return;
|
||||
}
|
||||
|
||||
perPlayerEntity.modify(user, consumer);
|
||||
}
|
||||
|
||||
public void modifyEntity(Consumer<WrapperEntity> consumer) {
|
||||
public void modifyEntity(@NotNull Consumer<WrapperEntity> consumer) {
|
||||
if (removed) {
|
||||
return;
|
||||
}
|
||||
|
||||
perPlayerEntity.execute(consumer);
|
||||
}
|
||||
|
||||
@@ -143,7 +188,7 @@ public class PacketNameTag {
|
||||
}
|
||||
|
||||
public boolean checkScale() {
|
||||
final AttributeInstance attribute = owner.getAttribute(Attribute.GENERIC_SCALE);
|
||||
final AttributeInstance attribute = owner.getAttribute(plugin.getNametagManager().getScaleAttribute());
|
||||
if (attribute == null) {
|
||||
if (scale != getDefaultScale()) {
|
||||
setScale(getDefaultScale());
|
||||
@@ -168,11 +213,6 @@ public class PacketNameTag {
|
||||
modify(meta -> meta.setScale(new Vector3f(scale, scale, scale)));
|
||||
}
|
||||
|
||||
public void setBillboard(@NotNull Display.Billboard billboard) {
|
||||
final AbstractDisplayMeta.BillboardConstraints billboardConstraints = AbstractDisplayMeta.BillboardConstraints.valueOf(billboard.name());
|
||||
modify(meta -> meta.setBillboardConstraints(billboardConstraints));
|
||||
}
|
||||
|
||||
public void setBillboard(@NotNull AbstractDisplayMeta.BillboardConstraints billboard) {
|
||||
modify(meta -> meta.setBillboardConstraints(billboard));
|
||||
}
|
||||
@@ -208,11 +248,18 @@ public class PacketNameTag {
|
||||
|
||||
public void showToPlayer(@NotNull Player player) {
|
||||
if (!isEligibleToShow(player)) {
|
||||
if (plugin.getNametagManager().isDebug()) {
|
||||
plugin.getLogger()
|
||||
.info("Player " + player.getName() + " is not eligible to show nametag for " + owner.getName());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!player.getUniqueId().equals(owner.getUniqueId())) {
|
||||
applyOwnerData(perPlayerEntity.getEntityOf(getUser(player)));
|
||||
WrapperEntity entity = perPlayerEntity.getEntityOf(getUser(player));
|
||||
if (entity != null) {
|
||||
applyOwnerData(entity);
|
||||
}
|
||||
}
|
||||
|
||||
spawn(player);
|
||||
@@ -223,47 +270,103 @@ public class PacketNameTag {
|
||||
setPosition();
|
||||
}
|
||||
|
||||
viewers.add(player.getUniqueId());
|
||||
perPlayerEntity.addViewer(getUser(player));
|
||||
|
||||
plugin.getTaskScheduler().runTaskLaterAsynchronously(() -> sendPassengersPacket(player), 1);
|
||||
updateViewer(player.getUniqueId());
|
||||
|
||||
plugin.getTaskScheduler().runTaskLaterAsynchronously(() -> {
|
||||
final User user = getUser(player);
|
||||
if (user == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
sendPassengersPacket(user);
|
||||
}, 1);
|
||||
}
|
||||
|
||||
private boolean isEligibleToShow(@NotNull Player player) {
|
||||
if (!visible) {
|
||||
if (plugin.getNametagManager().isDebug()) {
|
||||
plugin.getLogger().info("Nametag is not visible for " + owner.getName());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (plugin.getHook(ViaVersionHook.class).map(h -> h.hasNotTextDisplays(player)).orElse(false)) {
|
||||
if (plugin.getNametagManager().isDebug()) {
|
||||
plugin.getLogger()
|
||||
.info("Player " + player.getName() + " is on a version that does not support text displays.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (blocked.contains(player.getUniqueId())) {
|
||||
if (plugin.getNametagManager().isDebug()) {
|
||||
plugin.getLogger()
|
||||
.info("Player " + player.getName() + " is blocked from seeing nametag of " + owner.getName());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!player.canSee(owner)) {
|
||||
if (plugin.getNametagManager().isDebug()) {
|
||||
plugin.getLogger().info("Player " + player.getName() + " cannot see owner " + owner.getName());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (player.getWorld() != owner.getWorld()) {
|
||||
if (plugin.getNametagManager().isDebug()) {
|
||||
plugin.getLogger()
|
||||
.info("Player " + player.getName() + " is in a different world than owner " + owner.getName());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isPlayerChannelNotValid(player) || isPlayerChannelNotValid(owner)) {
|
||||
if (plugin.getNametagManager().isDebug()) {
|
||||
plugin.getLogger().info("Player " + player.getName() + " or owner " + owner.getName()
|
||||
+ " has an invalid channel/user.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!hasRequiredPermissions(player)) {
|
||||
if (plugin.getNametagManager().isDebug()) {
|
||||
plugin.getLogger().info("Player " + player.getName()
|
||||
+ " does not have required permissions to see nametag of " + owner.getName());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (plugin.getNametagManager().isHiddenOtherNametags(player)) {
|
||||
if (plugin.getNametagManager().isDebug()) {
|
||||
plugin.getLogger().info("Player " + player.getName() + " has other nametags hidden.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (plugin.getConfigManager().getSettings().isShowWhileLooking() &&
|
||||
!plugin.getNametagManager().isPlayerPointingAt(player, owner)) {
|
||||
if (plugin.getNametagManager().isDebug()) {
|
||||
plugin.getLogger().info("Player " + player.getName() + " is not looking at owner " + owner.getName());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return !viewers.contains(player.getUniqueId());
|
||||
if (plugin.getConfigManager().getSettings().isShowCurrentNameTag()
|
||||
&& player.getUniqueId() == owner.getUniqueId()) {
|
||||
if (plugin.getNametagManager().isDebug()) {
|
||||
plugin.getLogger()
|
||||
.info("Player " + player.getName() + " is the owner and show current nametag is enabled.");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (plugin.getNametagManager().isDebug() && !getViewers().contains(player.getUniqueId())) {
|
||||
plugin.getLogger().info("Player " + player.getName() + " is eligible to see nametag of " + owner.getName());
|
||||
}
|
||||
return !getViewers().contains(player.getUniqueId());
|
||||
}
|
||||
|
||||
private boolean isPlayerChannelNotValid(@NotNull Player player) {
|
||||
@@ -280,7 +383,7 @@ public class PacketNameTag {
|
||||
}
|
||||
|
||||
private boolean hasRequiredPermissions(@NotNull Player player) {
|
||||
boolean isOwner = isOwner(player);
|
||||
final boolean isOwner = isOwner(player);
|
||||
if (!isOwner && !player.hasPermission("unt.shownametags")) {
|
||||
return false;
|
||||
}
|
||||
@@ -300,11 +403,18 @@ public class PacketNameTag {
|
||||
});
|
||||
|
||||
plugin.getTaskScheduler().runTaskLaterAsynchronously(() -> {
|
||||
sendPassengersPacket(owner);
|
||||
final User ownerUser = getUser(owner);
|
||||
if (ownerUser == null) {
|
||||
return;
|
||||
}
|
||||
sendPassengersPacket(ownerUser);
|
||||
}, 1);
|
||||
}
|
||||
|
||||
public void sendPassengersPacket(@NotNull Player player) {
|
||||
public void sendPassengersPacket(@NotNull User player) {
|
||||
if (removed) {
|
||||
return;
|
||||
}
|
||||
plugin.getPacketManager().sendPassengersPacket(player, this);
|
||||
}
|
||||
|
||||
@@ -313,10 +423,14 @@ public class PacketNameTag {
|
||||
return;
|
||||
}
|
||||
|
||||
viewers.forEach(u -> {
|
||||
final Player player = Bukkit.getPlayer(u);
|
||||
if (player != null) {
|
||||
sendPassengersPacket(player);
|
||||
getViewers().forEach(u -> {
|
||||
final Player player = plugin.getPlayerListener().getPlayer(u);
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
final User user = PacketEvents.getAPI().getPlayerManager().getUser(player);
|
||||
if (user != null) {
|
||||
sendPassengersPacket(user);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -327,14 +441,14 @@ public class PacketNameTag {
|
||||
}
|
||||
|
||||
private void setOwnerPosition() {
|
||||
final Location location = getOffsetLocation().add(0, 0.25, 0);
|
||||
final Location location = getOffsetLocation(); // .add(0, 0.25, 0)
|
||||
modifyOwnerEntity(meta -> meta.setLocation(SpigotConversionUtil.fromBukkitLocation(location)));
|
||||
}
|
||||
|
||||
public Location getOffsetLocation() {
|
||||
final Location location = owner.getLocation().clone();
|
||||
final Location location = owner.getLocation();
|
||||
location.setPitch(0);
|
||||
location.setYaw(0);
|
||||
location.setYaw(-180);
|
||||
location.setY(location.getY() + (1.8) * scale);
|
||||
return location;
|
||||
}
|
||||
@@ -343,9 +457,9 @@ public class PacketNameTag {
|
||||
if (blocked.contains(player.getUniqueId())) {
|
||||
return;
|
||||
}
|
||||
viewers.remove(player.getUniqueId());
|
||||
final User user = PacketEvents.getAPI().getPlayerManager().getUser(player);
|
||||
if (user == null) {
|
||||
perPlayerEntity.getEntities().remove(player.getUniqueId());
|
||||
return;
|
||||
}
|
||||
final WrapperEntity wrapperEntity = perPlayerEntity.getEntityOf(user);
|
||||
@@ -354,19 +468,22 @@ public class PacketNameTag {
|
||||
}
|
||||
if (!player.getUniqueId().equals(owner.getUniqueId())) {
|
||||
perPlayerEntity.getEntities().remove(user.getUUID());
|
||||
|
||||
}
|
||||
relationalCache.remove(player.getUniqueId());
|
||||
calculatedTextCache.remove(player.getUniqueId());
|
||||
|
||||
plugin.getPacketManager().removePassenger(player, entityId);
|
||||
}
|
||||
|
||||
public void clearViewers() {
|
||||
viewers.forEach(u -> {
|
||||
final Player player = Bukkit.getPlayer(u);
|
||||
getViewers().forEach(u -> {
|
||||
final Player player = plugin.getPlayerListener().getPlayer(u);
|
||||
if (player != null) {
|
||||
hideFromPlayer(player);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public void showToPlayers(Set<Player> players) {
|
||||
@@ -377,13 +494,13 @@ public class PacketNameTag {
|
||||
if (blocked.contains(player.getUniqueId())) {
|
||||
return;
|
||||
}
|
||||
viewers.remove(player.getUniqueId());
|
||||
perPlayerEntity.getEntities().remove(player.getUniqueId());
|
||||
relationalCache.remove(player.getUniqueId());
|
||||
calculatedTextCache.remove(player.getUniqueId());
|
||||
}
|
||||
|
||||
public boolean canPlayerSee(@NotNull Player player) {
|
||||
return viewers.contains(player.getUniqueId());
|
||||
return getViewers().contains(player.getUniqueId());
|
||||
}
|
||||
|
||||
public void spawn(@NotNull Player player) {
|
||||
@@ -392,12 +509,13 @@ public class PacketNameTag {
|
||||
if (user == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
modifyEntity(user, e -> e.spawn(SpigotConversionUtil.fromBukkitLocation(getOffsetLocation())));
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
perPlayerEntity.getEntities().forEach((u, e) -> {
|
||||
if(blocked.contains(u)) {
|
||||
if (blocked.contains(u)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -411,41 +529,44 @@ public class PacketNameTag {
|
||||
return;
|
||||
}
|
||||
|
||||
if(blocked.contains(player.getUniqueId())) {
|
||||
if (blocked.contains(player.getUniqueId())) {
|
||||
return;
|
||||
}
|
||||
|
||||
perPlayerEntity.getEntityOf(user).refresh();
|
||||
final WrapperEntity entity = perPlayerEntity.getEntityOf(user);
|
||||
if (entity != null) {
|
||||
entity.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
viewers.forEach(u -> {
|
||||
final Player player = Bukkit.getPlayer(u);
|
||||
removed = true;
|
||||
perPlayerEntity.getEntities().keySet().forEach(u -> {
|
||||
final Player player = plugin.getPlayerListener().getPlayer(u);
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final User user = PacketEvents.getAPI().getPlayerManager().getUser(player);
|
||||
if (user != null) {
|
||||
if (user != null && user.getChannel() != null) {
|
||||
perPlayerEntity.removeViewer(user);
|
||||
}
|
||||
});
|
||||
viewers.clear();
|
||||
|
||||
perPlayerEntity.getEntities().values().forEach(WrapperEntity::remove);
|
||||
perPlayerEntity.getEntities().clear();
|
||||
|
||||
plugin.getPacketManager().removePassenger(entityId);
|
||||
relationalCache.clear();
|
||||
calculatedTextCache.clear();
|
||||
forcedRelationalText.clear();
|
||||
forcedGlobalText = null;
|
||||
}
|
||||
|
||||
public void handleQuit(@NotNull Player player) {
|
||||
viewers.remove(player.getUniqueId());
|
||||
clearCache(player.getUniqueId());
|
||||
perPlayerEntity.getEntities().remove(player.getUniqueId());
|
||||
plugin.getPacketManager().removePassenger(player, entityId);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private void clearCache(@NotNull UUID uuid) {
|
||||
perPlayerEntity.getEntities().remove(uuid);
|
||||
}
|
||||
|
||||
public void setTextOpacity(byte b) {
|
||||
modify(meta -> meta.setTextOpacity(b));
|
||||
}
|
||||
@@ -463,20 +584,22 @@ public class PacketNameTag {
|
||||
private void applyOwnerData(@NotNull WrapperEntity wrapper) {
|
||||
final User ownerUser = PacketEvents.getAPI().getPlayerManager().getUser(owner);
|
||||
final Metadata metadata = wrapper.getEntityMeta().getMetadata();
|
||||
final Optional<EntityData> component = wrapper.getEntityMeta().entityData()
|
||||
final Optional<EntityData<?>> component = wrapper.getEntityMeta().entityData()
|
||||
.stream()
|
||||
.filter(e -> e.getType() == EntityDataTypes.ADV_COMPONENT)
|
||||
.findFirst();
|
||||
final Metadata ownerMetadata = perPlayerEntity.getEntityOf(ownerUser).getEntityMeta().getMetadata();
|
||||
metadata.copyFrom(ownerMetadata);
|
||||
component.ifPresent(entityData -> ((TextDisplayMeta) wrapper.getEntityMeta()).setText((Component) entityData.getValue()));
|
||||
component.ifPresent(
|
||||
entityData -> ((TextDisplayMeta) wrapper.getEntityMeta()).setText((Component) entityData.getValue()));
|
||||
metadata.setNotifyAboutChanges(false);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Map<String, String> properties() {
|
||||
final Map<String, String> properties = new LinkedHashMap<>();
|
||||
final TextDisplayMeta meta = (TextDisplayMeta) this.perPlayerEntity.getEntityOf(PacketEvents.getAPI().getPlayerManager().getUser(owner)).getEntityMeta();
|
||||
final TextDisplayMeta meta = (TextDisplayMeta) this.perPlayerEntity
|
||||
.getEntityOf(PacketEvents.getAPI().getPlayerManager().getUser(owner)).getEntityMeta();
|
||||
properties.put("text", MiniMessage.miniMessage().serialize(meta.getText()));
|
||||
properties.put("billboard", meta.getBillboardConstraints().name());
|
||||
properties.put("shadowed", String.valueOf(meta.isShadow()));
|
||||
@@ -490,4 +613,58 @@ public class PacketNameTag {
|
||||
return properties;
|
||||
}
|
||||
|
||||
}
|
||||
public void setForcedNameTag(@NotNull Component component) {
|
||||
this.forcedGlobalText = component;
|
||||
updateAllViewers();
|
||||
}
|
||||
|
||||
public void setForcedNameTag(@NotNull UUID viewer, @NotNull Component component) {
|
||||
this.forcedRelationalText.put(viewer, component);
|
||||
updateViewer(viewer);
|
||||
}
|
||||
|
||||
public void clearForcedNameTag() {
|
||||
this.forcedGlobalText = null;
|
||||
updateAllViewers();
|
||||
}
|
||||
|
||||
public void clearForcedNameTag(@NotNull UUID viewer) {
|
||||
this.forcedRelationalText.remove(viewer);
|
||||
updateViewer(viewer);
|
||||
}
|
||||
|
||||
private void updateAllViewers() {
|
||||
for (final UUID uuid : getViewers()) {
|
||||
updateViewer(uuid);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateViewer(@NotNull UUID uuid) {
|
||||
final Player player = plugin.getPlayerListener().getPlayer(uuid);
|
||||
if (player == null)
|
||||
return;
|
||||
|
||||
Component textToUse = calculatedTextCache.get(uuid);
|
||||
|
||||
if (forcedRelationalText.containsKey(uuid)) {
|
||||
textToUse = forcedRelationalText.get(uuid);
|
||||
} else if (forcedGlobalText != null) {
|
||||
textToUse = forcedGlobalText;
|
||||
}
|
||||
|
||||
if (textToUse != null) {
|
||||
if (applyText(player, textToUse)) {
|
||||
refreshForPlayer(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an unmodifiable set containing the viewers of the nametag.
|
||||
*
|
||||
* @return the viewers of the nametag
|
||||
*/
|
||||
public Set<UUID> getViewers() {
|
||||
return Collections.unmodifiableSet(perPlayerEntity.getEntities().keySet());
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,39 @@
|
||||
package org.alexdev.unlimitednametags.placeholders;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import me.clip.placeholderapi.PlaceholderAPI;
|
||||
import org.alexdev.unlimitednametags.UnlimitedNameTags;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class PAPIManager {
|
||||
|
||||
private final UnlimitedNameTags plugin;
|
||||
@Getter
|
||||
private boolean papiEnabled;
|
||||
private UntPapiExpansion untPapiExpansion;
|
||||
|
||||
public boolean isPAPIEnabled() {
|
||||
return plugin.getServer().getPluginManager().isPluginEnabled("PlaceholderAPI");
|
||||
public PAPIManager(@NotNull UnlimitedNameTags plugin) {
|
||||
this.plugin = plugin;
|
||||
this.checkPapiEnabled();
|
||||
}
|
||||
|
||||
public void checkPapiEnabled() {
|
||||
if (papiEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.papiEnabled = plugin.getServer().getPluginManager().isPluginEnabled("PlaceholderAPI");
|
||||
if (papiEnabled) {
|
||||
try {
|
||||
this.untPapiExpansion = new UntPapiExpansion(plugin);
|
||||
this.untPapiExpansion.register();
|
||||
} catch (Throwable e) {
|
||||
plugin.getLogger().warning("Failed to register PlaceholderAPI expansion: " + e.getMessage());
|
||||
}
|
||||
} else {
|
||||
plugin.getLogger().info("PlaceholderAPI is not enabled, some features may not work.");
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -20,8 +41,12 @@ public class PAPIManager {
|
||||
if (text.isEmpty()) {
|
||||
return text;
|
||||
}
|
||||
if (!papiEnabled) {
|
||||
return text;
|
||||
}
|
||||
try {
|
||||
return PlaceholderAPI.setPlaceholders(player, text);
|
||||
final String firstReplacement = PlaceholderAPI.setPlaceholders(player, text);
|
||||
return PlaceholderAPI.setPlaceholders(player, firstReplacement);
|
||||
} catch (Throwable e) {
|
||||
plugin.getLogger().log(java.util.logging.Level.SEVERE, "Failed to set placeholders for text: " + text, e);
|
||||
return text;
|
||||
@@ -33,12 +58,22 @@ public class PAPIManager {
|
||||
if (text.isEmpty()) {
|
||||
return text;
|
||||
}
|
||||
if (!papiEnabled) {
|
||||
return text;
|
||||
}
|
||||
try {
|
||||
return PlaceholderAPI.setRelationalPlaceholders(whoSees, target, text);
|
||||
final String firstReplacement = PlaceholderAPI.setRelationalPlaceholders(whoSees, target, text);
|
||||
return PlaceholderAPI.setRelationalPlaceholders(whoSees, target, firstReplacement);
|
||||
} catch (Throwable e) {
|
||||
plugin.getLogger().log(java.util.logging.Level.SEVERE, "Failed to set relational placeholders for text: " + text, e);
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
if (papiEnabled && untPapiExpansion != null) {
|
||||
untPapiExpansion.unregister();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,54 +1,109 @@
|
||||
package org.alexdev.unlimitednametags.placeholders;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import lombok.Getter;
|
||||
import net.jodah.expiringmap.ExpiringMap;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.JoinConfiguration;
|
||||
import org.alexdev.unlimitednametags.UnlimitedNameTags;
|
||||
import org.alexdev.unlimitednametags.config.Settings;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.DecimalFormatSymbols;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@Getter
|
||||
public class PlaceholderManager {
|
||||
|
||||
public static final String PHASE_MD_KEY = "phase-md";
|
||||
public static final String PHASE_MM_KEY = "phase-mm";
|
||||
public static final String PHASE_MM_G_KEY = "phase-mm-g";
|
||||
public static final String NEG_PHASE_MD_KEY = "-phase-md";
|
||||
public static final String NEG_PHASE_MM_KEY = "-phase-mm";
|
||||
public static final String NEG_PHASE_MM_G_KEY = "-phase-mm-g";
|
||||
|
||||
private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("%.*?%", Pattern.DOTALL);
|
||||
private static final JoinConfiguration JOIN_CONFIGURATION = JoinConfiguration.separator(Component.newline());
|
||||
|
||||
private static final String ELSE_PLACEHOLDER = "ELSE";
|
||||
private static final int maxIndex = 16777215;
|
||||
private static final int maxMIndex = 10;
|
||||
private static final double minMGIndex = -1.0;
|
||||
private static final int MORE_LINES = 15;
|
||||
private static final int MORE_LINES = 14;
|
||||
private final UnlimitedNameTags plugin;
|
||||
private final ExecutorService executorService;
|
||||
private final PAPIManager papiManager;
|
||||
private int index = maxIndex;
|
||||
private int mmIndex = maxMIndex;
|
||||
private double mGIndex = 1.0;
|
||||
private DecimalFormat decimalFormat;
|
||||
|
||||
private BigDecimal miniGradientIndexBD = new BigDecimal("-1.0");
|
||||
private final BigDecimal stepBD = new BigDecimal("0.1");
|
||||
private final BigDecimal one = new BigDecimal("1.0");
|
||||
private final BigDecimal minusOne = new BigDecimal("-1.0");
|
||||
private final Map<String, Component> cachedComponents;
|
||||
private Map<UUID, Map<String, String>> cachedPlaceholders;
|
||||
private final Map<String, String> formattedPhaseValues;
|
||||
private final Map<String, Map<String, String>> placeholdersReplacements;
|
||||
|
||||
public PlaceholderManager(@NotNull UnlimitedNameTags plugin) {
|
||||
this.plugin = plugin;
|
||||
this.executorService = Executors.newFixedThreadPool(5);
|
||||
this.executorService = Executors.newWorkStealingPool(
|
||||
Math.max(2, Runtime.getRuntime().availableProcessors() / 2)
|
||||
);
|
||||
this.papiManager = new PAPIManager(plugin);
|
||||
startIndexTask();
|
||||
this.cachedComponents = ExpiringMap.builder()
|
||||
.expiration(2, TimeUnit.MINUTES)
|
||||
.build();
|
||||
this.formattedPhaseValues = Maps.newConcurrentMap();
|
||||
this.placeholdersReplacements = Maps.newConcurrentMap();
|
||||
reloadPlaceholdersReplacements();
|
||||
createDecimalFormat();
|
||||
reload();
|
||||
updateFormattedPhaseValues(); // Populate immediately to avoid race: gradient with #-phase-mm-g# fails when map is empty
|
||||
startIndexTask();
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
cachedComponents.clear();
|
||||
this.cachedPlaceholders = Maps.newConcurrentMap();
|
||||
plugin.getPlayerListener().getOnlinePlayers().values().forEach(p -> cachedPlaceholders.put(p.getUniqueId(), ExpiringMap.builder()
|
||||
.expiration(plugin.getConfigManager().getSettings().getPlaceholderCacheTime() * 50L, TimeUnit.MILLISECONDS) // Adjusted unit based on previous code
|
||||
.build()));
|
||||
reloadPlaceholdersReplacements();
|
||||
}
|
||||
|
||||
private void reloadPlaceholdersReplacements() {
|
||||
placeholdersReplacements.clear();
|
||||
plugin.getConfigManager().getSettings().getPlaceholdersReplacements().forEach((key, value) -> {
|
||||
final Map<String, String> replacements = Maps.newHashMapWithExpectedSize(value.size());
|
||||
value.forEach((pr) -> replacements.put(pr.placeholder().toLowerCase(Locale.ROOT), pr.replacement()));
|
||||
placeholdersReplacements.put(key.toLowerCase(Locale.ROOT), replacements);
|
||||
});
|
||||
}
|
||||
|
||||
public void removePlayer(@NotNull Player player) {
|
||||
cachedPlaceholders.remove(player.getUniqueId());
|
||||
}
|
||||
|
||||
private void createDecimalFormat() {
|
||||
decimalFormat = new DecimalFormat("#.#");
|
||||
decimalFormat = new DecimalFormat("#.#", DecimalFormatSymbols.getInstance(Locale.US));
|
||||
decimalFormat.setRoundingMode(RoundingMode.DOWN);
|
||||
|
||||
final DecimalFormatSymbols symbols = new DecimalFormatSymbols();
|
||||
final DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.US);
|
||||
symbols.setDecimalSeparator('.');
|
||||
decimalFormat.setDecimalFormatSymbols(symbols);
|
||||
}
|
||||
@@ -57,7 +112,7 @@ public class PlaceholderManager {
|
||||
plugin.getTaskScheduler().runTaskTimerAsynchronously(() -> {
|
||||
index -= 1;
|
||||
if (index == 0) {
|
||||
index = 16777215;
|
||||
index = maxIndex;
|
||||
}
|
||||
}, 0, 1);
|
||||
plugin.getTaskScheduler().runTaskTimerAsynchronously(() -> {
|
||||
@@ -65,33 +120,56 @@ public class PlaceholderManager {
|
||||
if (mmIndex == 1) {
|
||||
mmIndex = maxMIndex;
|
||||
}
|
||||
}, 0, 2);
|
||||
}, 0, 1);
|
||||
plugin.getTaskScheduler().runTaskTimerAsynchronously(() -> {
|
||||
mGIndex += 0.1;
|
||||
if (mGIndex >= 1d) {
|
||||
mGIndex = minMGIndex;
|
||||
miniGradientIndexBD = miniGradientIndexBD.add(stepBD);
|
||||
if (miniGradientIndexBD.compareTo(one) > 0) {
|
||||
miniGradientIndexBD = minusOne;
|
||||
}
|
||||
}, 0, 2);
|
||||
|
||||
updateFormattedPhaseValues();
|
||||
}, 0, 1);
|
||||
}
|
||||
|
||||
private void updateFormattedPhaseValues() {
|
||||
final int currentIndex = this.index;
|
||||
final int currentMmIndex = this.mmIndex;
|
||||
final BigDecimal currentMiniGradientIndexBD = this.miniGradientIndexBD;
|
||||
|
||||
final String indexStr = Integer.toString(currentIndex);
|
||||
final String mmIndexStr = Integer.toString(currentMmIndex);
|
||||
final String phaseMmGStr = decimalFormat.format(currentMiniGradientIndexBD);
|
||||
final String negIndexStr = Integer.toString(maxIndex - currentIndex);
|
||||
final String negMmIndexStr = Integer.toString(maxMIndex - currentMmIndex);
|
||||
final String negPhaseMmGStr = decimalFormat.format(currentMiniGradientIndexBD.multiply(minusOne));
|
||||
|
||||
formattedPhaseValues.put(PHASE_MD_KEY, indexStr);
|
||||
formattedPhaseValues.put(PHASE_MM_KEY, mmIndexStr);
|
||||
formattedPhaseValues.put(PHASE_MM_G_KEY, phaseMmGStr);
|
||||
formattedPhaseValues.put(NEG_PHASE_MD_KEY, negIndexStr);
|
||||
formattedPhaseValues.put(NEG_PHASE_MM_KEY, negMmIndexStr);
|
||||
formattedPhaseValues.put(NEG_PHASE_MM_G_KEY, negPhaseMmGStr);
|
||||
}
|
||||
|
||||
public void close() {
|
||||
this.executorService.shutdown();
|
||||
papiManager.close();
|
||||
}
|
||||
|
||||
|
||||
@NotNull
|
||||
public CompletableFuture<Map<Player, Component>> applyPlaceholders(@NotNull Player player, @NotNull List<Settings.LinesGroup> lines,
|
||||
@NotNull List<Player> relationalPlayers) {
|
||||
return getCheckedLines(player, lines).thenApply(strings -> createComponent(player, strings, relationalPlayers));
|
||||
return getCheckedLines(player, lines).thenApplyAsync(strings -> createComponent(player, strings, relationalPlayers), executorService);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private CompletableFuture<List<String>> getCheckedLines(@NotNull Player player, @NotNull List<Settings.LinesGroup> lines) {
|
||||
return CompletableFuture.supplyAsync(() -> lines.parallelStream()
|
||||
.filter(l -> l.modifiers().stream().allMatch(m -> m.isVisible(player, plugin)))
|
||||
.map(Settings.LinesGroup::lines)
|
||||
.flatMap(List::stream)
|
||||
.toList(), executorService);
|
||||
return CompletableFuture.supplyAsync(() -> lines.stream()
|
||||
.filter(l -> l.modifiers() == null || l.modifiers().isEmpty() || l.modifiers().stream().allMatch(m -> m.isVisible(player, plugin)))
|
||||
.map(Settings.LinesGroup::lines)
|
||||
.flatMap(List::stream)
|
||||
.toList(), executorService);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -102,76 +180,165 @@ public class PlaceholderManager {
|
||||
.findFirst()
|
||||
.orElse(0d);
|
||||
|
||||
if (moreLines > 0) {
|
||||
strings = new ArrayList<>(strings);
|
||||
int lines = (int) (moreLines / MORE_LINES);
|
||||
for (int i = 0; i < lines; i++) {
|
||||
strings.add(" ");
|
||||
final int moreLinesInt = (int) Math.ceil(moreLines);
|
||||
Component emptyLines = Component.empty();
|
||||
if (moreLinesInt > 0) {
|
||||
int linesCount = moreLinesInt / MORE_LINES;
|
||||
for (int i = 0; i < linesCount; i++) {
|
||||
emptyLines = emptyLines.append(Component.newline());
|
||||
}
|
||||
}
|
||||
|
||||
final List<String> stringsCopy = papiManager.isPAPIEnabled() ?
|
||||
final Component hatLines = emptyLines;
|
||||
final Settings settings = plugin.getConfigManager().getSettings();
|
||||
final boolean removeEmptyLines = settings.isRemoveEmptyLines();
|
||||
final boolean enableRelationalPlaceholders = settings.isEnableRelationalPlaceholders();
|
||||
|
||||
final List<String> baseStrings = papiManager.isPapiEnabled() ?
|
||||
strings.stream()
|
||||
.map(s -> replacePlaceholders(s, player))
|
||||
.map(s -> replacePlaceholders(s, player, null))
|
||||
.toList()
|
||||
: strings;
|
||||
return relationalPlayers.stream()
|
||||
.map(r -> Map.entry(r, Component.join(JoinConfiguration.separator(Component.newline()), stringsCopy.parallelStream()
|
||||
.map(t -> papiManager.isPAPIEnabled() ? papiManager.setRelationalPlaceholders(r, player, t) : t)
|
||||
.filter(s -> !plugin.getConfigManager().getSettings().isRemoveEmptyLines() || !s.isEmpty())
|
||||
.map(this::formatPhases)
|
||||
.map(t -> format(t, player))
|
||||
.filter(c -> !plugin.getConfigManager().getSettings().isRemoveEmptyLines() || !c.equals(Component.empty()))
|
||||
.toList())
|
||||
.compact()))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
|
||||
if (enableRelationalPlaceholders) {
|
||||
final Map<Player, Component> result = Maps.newHashMapWithExpectedSize(relationalPlayers.size());
|
||||
for (Player viewer : relationalPlayers) {
|
||||
List<Component> processedLines = baseStrings.stream()
|
||||
.map(line -> replacePlaceholders(line, player, viewer))
|
||||
.filter(s -> !removeEmptyLines || !s.isEmpty())
|
||||
.map(this::formatPhases)
|
||||
.map(line -> format(line, player))
|
||||
.filter(c -> !removeEmptyLines || !c.equals(Component.empty()))
|
||||
.toList();
|
||||
Component finalComponent = joinLines(processedLines).append(hatLines);
|
||||
result.put(viewer, finalComponent);
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
List<Component> processedLines = baseStrings.stream()
|
||||
.map(line -> replacePlaceholders(line, player, null))
|
||||
.filter(s -> !removeEmptyLines || !s.isEmpty())
|
||||
.map(this::formatPhases)
|
||||
.map(line -> format(line, player))
|
||||
.filter(c -> !removeEmptyLines || !c.equals(Component.empty()))
|
||||
.toList();
|
||||
|
||||
Component finalComponent = joinLines(processedLines).append(hatLines);
|
||||
|
||||
final Map<Player, Component> result = Maps.newHashMapWithExpectedSize(relationalPlayers.size());
|
||||
for (Player viewer : relationalPlayers) {
|
||||
result.put(viewer, finalComponent);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private Component joinLines(List<Component> lines) {
|
||||
return Component.join(JOIN_CONFIGURATION, lines);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private String formatPhases(@NotNull String value) {
|
||||
return value.replace("#phase-md#", Integer.toString(index))
|
||||
.replace("#phase-mm#", Integer.toString(mmIndex))
|
||||
.replace("#phase-mm-g#", decimalFormat.format(mGIndex))
|
||||
.replace("#-phase-md#", Integer.toString(maxIndex - index))
|
||||
.replace("#-phase-mm#", Integer.toString(maxMIndex - mmIndex))
|
||||
.replace("#-phase-mm-g#", decimalFormat.format(mGIndex * -1));
|
||||
if (value.indexOf('#') == -1) {
|
||||
return value;
|
||||
}
|
||||
|
||||
String result = value.replace("#phase-mm-g#", formattedPhaseValues.getOrDefault(PHASE_MM_G_KEY, "0"));
|
||||
if (result.indexOf('#') == -1) return result;
|
||||
|
||||
result = result.replace("#-phase-mm-g#", formattedPhaseValues.getOrDefault(NEG_PHASE_MM_G_KEY, "0"));
|
||||
if (result.indexOf('#') == -1) return result;
|
||||
|
||||
result = result.replace("#phase-md#", formattedPhaseValues.getOrDefault(PHASE_MD_KEY, ""));
|
||||
if (result.indexOf('#') == -1) return result;
|
||||
|
||||
result = result.replace("#phase-mm#", formattedPhaseValues.getOrDefault(PHASE_MM_KEY, ""));
|
||||
if (result.indexOf('#') == -1) return result;
|
||||
|
||||
result = result.replace("#-phase-md#", formattedPhaseValues.getOrDefault(NEG_PHASE_MD_KEY, ""));
|
||||
if (result.indexOf('#') == -1) return result;
|
||||
|
||||
return result.replace("#-phase-mm#", formattedPhaseValues.getOrDefault(NEG_PHASE_MM_KEY, ""));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getFormattedPhases(@NotNull String text) {
|
||||
return formattedPhaseValues.getOrDefault(text, "");
|
||||
}
|
||||
|
||||
private boolean containsAnyPlaceholders(String text) {
|
||||
return text != null && text.indexOf('%') != -1;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Component format(@NotNull String value, @NotNull Player player) {
|
||||
if (plugin.getConfigManager().getSettings().isComponentCaching()) {
|
||||
return cachedComponents.computeIfAbsent(value, v ->
|
||||
plugin.getConfigManager().getSettings().getFormat().format(plugin, player, v)
|
||||
);
|
||||
}
|
||||
|
||||
return plugin.getConfigManager().getSettings().getFormat().format(plugin, player, value);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private String replacePlaceholders(@NotNull String string, @NotNull Player player) {
|
||||
for (Map.Entry<String, List<Settings.PlaceholderReplacement>> entry : plugin.getConfigManager().getSettings().getPlaceholdersReplacements().entrySet()) {
|
||||
if (!string.contains(entry.getKey())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final String replaced = papiManager.setPlaceholders(player, entry.getKey());
|
||||
final Optional<Settings.PlaceholderReplacement> replacement = entry.getValue().stream()
|
||||
.filter(r -> r.placeholder().equals(replaced))
|
||||
.findFirst();
|
||||
|
||||
if (replacement.isPresent()) {
|
||||
string = string.replace(entry.getKey(), replacement.get().replacement());
|
||||
} else {
|
||||
final Optional<Settings.PlaceholderReplacement> elseReplacement = entry.getValue().stream()
|
||||
.filter(r -> r.placeholder().equalsIgnoreCase(ELSE_PLACEHOLDER))
|
||||
.findFirst();
|
||||
if (elseReplacement.isPresent()) {
|
||||
string = string.replace(entry.getKey(), elseReplacement.get().replacement());
|
||||
}
|
||||
}
|
||||
|
||||
private String replacePlaceholders(@NotNull String string, @NotNull Player player, @Nullable Player viewer) {
|
||||
if (!containsAnyPlaceholders(string)) {
|
||||
return string;
|
||||
}
|
||||
|
||||
if (papiManager.isPAPIEnabled()) {
|
||||
return papiManager.setPlaceholders(player, string);
|
||||
final StringBuilder builder = new StringBuilder(string.length() + 32); // Pre-allocate extra space
|
||||
final Matcher matcher = PLACEHOLDER_PATTERN.matcher(string);
|
||||
int lastAppendPosition = 0;
|
||||
|
||||
while (matcher.find()) {
|
||||
builder.append(string, lastAppendPosition, matcher.start());
|
||||
final String placeholder = matcher.group();
|
||||
final String value = viewer == null ? getCachedPlaceholder(player, placeholder)
|
||||
: papiManager.setRelationalPlaceholders(viewer, player, placeholder);
|
||||
final String customReplacement = getReplacement(placeholder, value);
|
||||
builder.append(customReplacement != null ? customReplacement : value);
|
||||
lastAppendPosition = matcher.end();
|
||||
}
|
||||
|
||||
return string;
|
||||
builder.append(string, lastAppendPosition, string.length());
|
||||
|
||||
final String intermediateResult = builder.toString();
|
||||
|
||||
if (papiManager.isPapiEnabled()) {
|
||||
return viewer == null ? papiManager.setPlaceholders(player, intermediateResult)
|
||||
: papiManager.setRelationalPlaceholders(viewer, player, intermediateResult);
|
||||
}
|
||||
|
||||
return intermediateResult;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getReplacement(@NotNull String placeholder, @NotNull String value) {
|
||||
var replacements = placeholdersReplacements.get(placeholder.toLowerCase(Locale.ROOT));
|
||||
if (replacements == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final String replacement = replacements.get(value.toLowerCase(Locale.ROOT));
|
||||
if (replacement != null) {
|
||||
return replacement;
|
||||
}
|
||||
|
||||
return replacements.get(ELSE_PLACEHOLDER.toLowerCase(Locale.ROOT));
|
||||
}
|
||||
|
||||
|
||||
private Map<String, String> getCachedPlaceholders(@NotNull Player player) {
|
||||
return cachedPlaceholders.computeIfAbsent(player.getUniqueId(), u -> ExpiringMap.builder()
|
||||
.expiration(plugin.getConfigManager().getSettings().getPlaceholderCacheTime() * 50L, TimeUnit.MILLISECONDS)
|
||||
.build());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getCachedPlaceholder(@NotNull Player player, @NotNull String placeholder) {
|
||||
final Map<String, String> playerCache = getCachedPlaceholders(player);
|
||||
return playerCache.computeIfAbsent(placeholder, p -> papiManager.setPlaceholders(player, p));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package org.alexdev.unlimitednametags.placeholders;
|
||||
|
||||
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
|
||||
import org.alexdev.unlimitednametags.UnlimitedNameTags;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class UntPapiExpansion extends PlaceholderExpansion {
|
||||
|
||||
private final UnlimitedNameTags plugin;
|
||||
|
||||
public UntPapiExpansion(UnlimitedNameTags plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getIdentifier() {
|
||||
return "unt";
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getAuthor() {
|
||||
return "AlexDev";
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
public @NotNull String getVersion() {
|
||||
return plugin.getPluginMeta().getVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean persist() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String onPlaceholderRequest(Player player, @NotNull String params) {
|
||||
return switch (params) {
|
||||
case "phase-mm" -> plugin.getPlaceholderManager().getFormattedPhases(PlaceholderManager.PHASE_MM_KEY);
|
||||
case "phase-md" -> plugin.getPlaceholderManager().getFormattedPhases(PlaceholderManager.PHASE_MD_KEY);
|
||||
case "phase-mm-g" -> plugin.getPlaceholderManager().getFormattedPhases(PlaceholderManager.PHASE_MM_G_KEY);
|
||||
case "-phase-mm" -> plugin.getPlaceholderManager().getFormattedPhases(PlaceholderManager.NEG_PHASE_MM_KEY);
|
||||
case "-phase-md" -> plugin.getPlaceholderManager().getFormattedPhases(PlaceholderManager.NEG_PHASE_MD_KEY);
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
//Placeholders:
|
||||
}
|
||||
@@ -5,10 +5,11 @@ api-version: '1.19'
|
||||
author: AlexDev_
|
||||
folia-supported: true
|
||||
libraries:
|
||||
- de.exlll:configlib-yaml:${configlibVersion}
|
||||
- de.exlll:configlib-paper:${configlibVersion}
|
||||
# - de.exlll:configlib-yaml:${configlibVersion}
|
||||
# - de.exlll:configlib-paper:${configlibVersion}
|
||||
- net.jodah:expiringmap:${expiringMapVersion}
|
||||
- org.apache.commons:commons-jexl3:${commonsJexl3Version}
|
||||
|
||||
depend:
|
||||
- packetevents
|
||||
softdepend:
|
||||
@@ -19,6 +20,9 @@ softdepend:
|
||||
- ViaBackwards
|
||||
- ViaRewind
|
||||
- Oraxen
|
||||
- Nexo
|
||||
- ItemsAdder
|
||||
- HMCCosmetics
|
||||
permissions:
|
||||
unt.shownametags:
|
||||
default: true
|
||||
|
||||
Reference in new issue
Block a user