Compare commits

...

117 Commits

Author SHA1 Message Date
PiggyPiglet
9022daf07f 2.12.2-dev 2026-02-03 17:17:36 +08:00
PiggyPiglet
675b305cac 2.12.1 release 2026-02-03 17:16:29 +08:00
PiggyPiglet
d49c76c560 exclamation marks are important 2026-02-03 17:15:46 +08:00
PiggyPiglet
13e492cf44 Show version in papi ecloud list hover 2026-02-03 16:55:12 +08:00
PiggyPiglet
d561afbb63 Use modrinth for update checker 2026-02-03 16:52:25 +08:00
PiggyPiglet
2a3f4482a0 check for perms on all tab complete 2026-02-03 16:28:42 +08:00
Funnycube
4ee2840a0a Update download and server usage statistics in README 2026-02-02 22:24:12 +11:00
PiggyPiglet
c52d117f12 2.12.1 dev 2026-02-02 18:28:07 +08:00
PiggyPiglet
e307aba414 2.12.0 release 2026-02-02 17:58:26 +08:00
PiggyPiglet
5ea5a18fe8 fix config option path 2026-02-02 17:43:41 +08:00
PiggyPiglet
9c1db4b48a fix newlines in ecloud command error & restrict api to bukkit platform 2026-02-02 17:29:15 +08:00
PiggyPiglet
b233c92ca1 Add warning message to failed ecloud download 2026-02-02 17:10:50 +08:00
PiggyPiglet
1b1d2e61b9 Add config option to use adventure replacer, add bracket {} support 2026-02-02 16:35:13 +08:00
PiggyPiglet
2dc5b93133 WIP custom component replacer no regex!! 2026-01-21 22:14:46 +08:00
PiggyPiglet
880ddc22b5 bump version to 2.12.0 2026-01-19 17:20:13 +08:00
PiggyPiglet
d378f782b4 Update bstats 2026-01-19 17:11:20 +08:00
PiggyPiglet
1f2d969a69 set plain jar classifier as "" when publishing so maven actually knows what to do with it 2026-01-19 17:02:03 +08:00
PiggyPiglet
cac79a26af plainJar depends on compilePaper 2026-01-19 16:59:05 +08:00
PiggyPiglet
8b078f9058 downgrade gradle 2026-01-19 16:56:03 +08:00
PiggyPiglet
d3a5d01f55 this one's really gonna do it this time 2026-01-19 16:47:57 +08:00
PiggyPiglet
9a677d46a1 Potential gradle publish fix 2026-01-19 16:36:34 +08:00
PiggyPiglet
8116cbb385 back to one jar pls 2026-01-19 16:13:15 +08:00
PiggyPiglet
b9affd0879 Remove debug code 2026-01-18 23:09:57 +08:00
PiggyPiglet
ec8657015c jenkins attempt #2 2026-01-18 23:02:29 +08:00
PiggyPiglet
38a86e6d2d will jenkins build now idk let's see 2026-01-18 22:36:55 +08:00
PiggyPiglet
35376e43ca Merge pull request #1157 from PlaceholderAPI/feature/components
Let's have component support finally :D
2026-01-18 22:24:16 +08:00
PiggyPiglet
1c233ec297 We in 2026 bruh 2026-01-18 22:23:28 +08:00
PiggyPiglet
967dec09ff Component support !?! 2026-01-18 22:20:29 +08:00
Funnycube
85aa6ae8ae Corrected ecloud link 2026-01-15 18:18:03 +11:00
PiggyPiglet
83aad38b09 Update readme api url 2026-01-12 18:22:42 +08:00
PiggyPiglet
b0fb784079 Move verification boolean to expansion version, move to v3 api 2026-01-12 17:40:35 +08:00
PiggyPiglet
84948101f4 Update API URL 2026-01-12 17:00:33 +08:00
PiggyPiglet
5dea70532c less "malicious" more "safety" 2025-11-21 20:07:28 +08:00
PiggyPiglet
403adeb217 cube doesn't like calling it malware 2025-11-21 20:01:53 +08:00
PiggyPiglet
a81ed63c0f Change malicious expansion message 2025-11-21 19:03:57 +08:00
PiggyPiglet
38da700168 Add checks for known malicious expansion checksums before expansion load 2025-11-21 18:56:49 +08:00
Andre_601
4a085682dc Merge pull request #1147 from petulikan1/master
Corrected grammar errors
2025-11-15 09:16:42 +01:00
Petulikan Dálnevím
cfed3ce31b Fix typo in pull request template 2025-11-15 05:29:41 +01:00
Petulikan Dálnevím
4c62318338 Fix grammatical error in CONTRIBUTING.md 2025-11-15 05:26:47 +01:00
PiggyPiglet
7247cbb9f7 2.11.8 dev 2025-11-03 16:58:54 +08:00
PiggyPiglet
9ed7a7ae56 Merge remote-tracking branch 'origin/master' 2025-11-03 16:56:42 +08:00
PiggyPiglet
9a356ceecf 2.11.7 2025-11-03 16:56:35 +08:00
Funnycube
e0ac7b1c66 Remove BuiltByBit link from README
Removed BuiltByBit link from the README.
2025-11-03 19:45:47 +11:00
PiggyPiglet
82230d8156 2.11.8 dev 2025-11-03 16:19:54 +08:00
PiggyPiglet
cda55f20aa fix javadoc build 2025-11-03 16:17:48 +08:00
PiggyPiglet
0df7a01fd8 Update NMS Version 2025-11-03 16:15:34 +08:00
PiggyPiglet
b49668ddee 2.11.7 Update 2025-11-03 16:07:19 +08:00
Funnycube
f14b081f0b Add modrinth 2025-08-15 17:06:52 +10:00
PiggyPiglet
b51fbf4e13 Fix jenkins build
disableAutoTargetJvm in gradle
2025-07-03 13:40:53 +08:00
PiggyPiglet
e48b6fe702 Update NMSVersion enum 2025-07-01 20:44:21 +08:00
PiggyPiglet
984f944daf Merge pull request #1127 from PlaceholderAPI/folia
Merge UniversalScheduler (Folia Support) #980
2025-07-01 20:24:13 +08:00
PiggyPiglet
0575c8cf41 Merge UniversalScheduler 2025-07-01 09:39:48 +08:00
PiggyPiglet
c181d18f5e Revert "Merge pull request #1092 from CoderKuo/master"
This reverts commit 2e0d9eb846, reversing
changes made to 33d1009d6a.
2025-06-16 11:08:43 +08:00
PiggyPiglet
2e0d9eb846 Merge pull request #1092 from CoderKuo/master
Optimize text replacement algorithm performance
2025-04-21 15:23:40 +08:00
PiggyPiglet
33d1009d6a Merge pull request #1095 from Kqliber/fix/1094
fix for all expansions stop loading after an exception is thrown (#1094)
2025-03-30 20:46:34 +08:00
PiggyPiglet
b16d62fb75 fix maven repo publishing (maybe?) 2025-03-30 20:41:51 +08:00
Funnycube
e019d6370c Merge pull request #1098 from Funny-cube/master
Revert api links
2025-01-16 13:31:08 +11:00
Funnycube
581b73dbc7 revert home link 2025-01-16 13:28:43 +11:00
Funnycube
25f7eafe32 revert api link 2025-01-16 13:27:45 +11:00
Kqliber
ef22d564f3 remove imports 2025-01-08 18:23:49 +00:00
Kqliber
787a053d98 fix for all expansions stop loading after an exception is thrown (#1094) 2025-01-08 18:21:43 +00:00
大阔
f31dd2bea9 Add fast path check for closure.head and closure.tail 2025-01-03 03:18:40 +08:00
大阔
5c8086150a Optimize text replacement algorithm performance 2025-01-03 02:23:17 +08:00
PiggyPiglet
b838d1c52a Update ecloud api link 2024-11-27 19:58:05 +08:00
Andre_601
0a712e6530 Fix API version badge + update flex 2024-11-13 14:40:41 +01:00
Gabriel Dumitru
272e2e7904 Merge pull request #1000 from mfnalex/master
Added missing NotNull annotations to setBracketPlaceholders methods
2024-07-10 22:35:49 +03:00
PiggyPiglet
98082398fc Target java release 8 for compilation 2024-07-04 17:46:04 +08:00
PiggyPiglet
c66806ecf8 Merge pull request #1067 from PlaceholderAPI/feat/1.21
feat: initial work on 1.21
2024-07-04 17:26:16 +08:00
Andre_601
c97f5aa5a6 Bump download stats 2024-06-18 22:45:15 +02:00
Glare
c1487898a9 chore(deps): update gradle + shadow fork 2024-06-14 10:07:06 -05:00
Glare
907ced6d7d feat: initial work on 1.21 2024-06-13 18:08:58 -05:00
PiggyPiglet
ac771207c3 bump to dev version 2024-05-21 18:51:05 +08:00
PiggyPiglet
8b031576aa bump version for release 2024-05-21 18:31:02 +08:00
PiggyPiglet
2d1a0ee157 Merge pull request #1060 from PlaceholderAPI/fix/parse-command-improvements
Parse command improvements
2024-05-21 18:08:19 +08:00
PiggyPiglet
b9e2bf9429 Merge pull request #1061 from PlaceholderAPI/feature/update-adventure-platform-bukkit
Update Adventure-platform-bukkit to 4.3.2
2024-05-21 18:05:13 +08:00
PiggyPiglet
a35923a117 cast instead of #getplayer 2024-05-21 17:59:57 +08:00
PiggyPiglet
d5e96bd6a6 exclude module-info fix #894 2024-05-20 22:44:44 +08:00
Andre601
2523b6c094 Update Adventure-platform-bukkit to 4.3.2 2024-05-10 14:41:51 +02:00
Andre601
7b230fc679 Fix output for parse command + "me" support in parserel 2024-05-06 16:42:24 +02:00
Andre_601
68f467ab29 Add 1.20.2 - 1.20.5 to NMSVersion.java (#1057) 2024-04-30 10:35:18 -05:00
PiggyPiglet
068b5a31b2 Fix #1034 & Deprecate VersionSpecific & update copyright (#1035)
Co-authored-by: Andre_601 <github@andre601.ch>
2024-04-30 10:34:43 -05:00
Gabriel Dumitru
882b7c5965 Merge pull request #1046 from PlaceholderAPI/feature/add-plugin-authors
Add Plugin Authors to /papi dump
2024-03-07 22:59:42 +02:00
Gabriel Dumitru
7a0be5edf8 Merge pull request #1040 from DevCyntrix/fix-class-cast-exception
Use the OfflinePlayer$getPlayer method instead of casting to Player class
2024-03-07 22:59:24 +02:00
Andre601
e94328935d Add Plugin Authors to /papi dump 2024-02-25 14:42:48 +01:00
Andre_601
604fed36a4 Man, this file is outdated... 2024-02-25 00:14:41 +01:00
Ricardo Borutta
403622d205 Use the OfflinePlayer$getPlayer method instead of casting to Player class
You should use this to avoid a class cast exception if some other plugins uses an own Implementation of the offline player.
2024-01-29 10:21:24 +01:00
Funnycube
1cd4d93f7f Merge pull request #1038 from Funny-cube/master
Update config.yml with papi domain for wiki and ecloud
2024-01-27 21:57:48 +11:00
Funnycube
a83ef75bf9 Update config.yml 2024-01-27 20:45:10 +11:00
Andre_601
b96e535aaa Update wiki link in PR template 2024-01-27 00:58:11 +01:00
Andre_601
2e67272aea Fix logo and update link in README 2024-01-26 23:05:04 +01:00
Andre_601
2c7767e77d fix github admonition blocks 2024-01-22 20:12:39 +01:00
Andre_601
c4a046ff24 Merge pull request #1006 from PlaceholderAPI/feature/add-new-download-pages
Add Hangar and BuiltByBit pages to readme
2023-11-26 16:58:08 +01:00
PiggyPiglet
57fa68c896 change version to .6-dev 2023-10-28 21:50:05 +08:00
PiggyPiglet
b64f024eb2 fix config version variable and set release version 2023-10-28 21:49:39 +08:00
PiggyPiglet
c7a4900aa3 remove ability to download unverified expansions 2023-10-28 21:42:18 +08:00
Andre_601
491abd6238 BuilT not BuilD 2023-09-25 15:11:49 +02:00
Andre_601
d3646a9874 Change Built By Bit to BuiltByBit Page 2023-09-25 15:07:13 +02:00
Andre_601
af0b475330 Add Hangar and BuiltByBit pages to readme 2023-09-25 14:58:30 +02:00
Funnycube
d35a499e31 Update plugin.yml
remove unused toggle permissions
2023-09-24 13:49:40 +10:00
Funnycube
c0f824450e Update build.gradle.kts
start 2.11.5-DEV
2023-09-24 12:59:01 +10:00
Funnycube
5b1a8ef4ba 2.11.4 release 2023-09-24 12:40:46 +10:00
PiggyPiglet
5623a00f9b Merge pull request #978 from JulianVennen/block-unverified-expansions
Add environment variable to override cloud_allowunverified_expansions
2023-09-21 17:38:54 +08:00
PiggyPiglet
4926907b47 Merge pull request #969 from PlaceholderAPI/feature/support-1.20
1.20 support
2023-09-04 17:26:11 +08:00
mfnalex
32f3d14682 added missing @NotNull to the returned List's type parameter and the parameter List's type parameter (is that proper English?) 2023-08-29 00:57:42 +02:00
mfnalex
152105017d added missing @NotNull to return value and String parameter of setBracketPlaceholders(Player, String) 2023-08-29 00:56:12 +02:00
mfnalex
be956f52b0 added missing @NotNull to return value and List parameter of setBracketPlaceholders(Player, List) 2023-08-29 00:55:28 +02:00
Andre_601
195158b18b Use new format for note block [skip ci] 2023-08-01 03:27:15 +02:00
Gabriel Dumitru
36fa9ac96d feat: distinguish expansions (external or internal) (fixes #945) (#953) 2023-07-24 11:48:29 +03:00
Julian
42cfe1dc80 simplify env values to true/false 2023-06-23 17:06:11 +02:00
Julian
92a7d54664 Add environment variable to override cloud_allowunverified_expansions
This adds the environment variable PAPI_ALLOW_UNVERIFIED_EXPANSIONS.
It allows overriding the option set in the config.yml.
2023-06-23 14:04:00 +02:00
Gabriel Dumitru
f91b4e3752 feat: fetch all expansions (#952) 2023-06-21 21:26:52 +03:00
PiggyPiglet
a497e05e55 Merge pull request #947 from JulianVennen/block-extensions-env
Add environment variable to block individual ecloud expansions.
2023-06-21 20:25:16 +08:00
Andre601
74d8fec6bd Bump Spigot version 2023-06-10 18:13:10 +02:00
Andre601
28287c7fbd 1.20 NMS version support 2023-06-10 17:59:55 +02:00
Gabriel Dumitru
744cf6d8c0 Merge pull request #946 from JulianVennen/block-load-path-traversal
Prevent loading of extensions outside of the expansions folder using the register command
2023-04-04 17:28:00 +03:00
Julian
ecd4c002c8 Add environment variable to block individual ecloud expansions.
This commit adds the environment variable "PAPI_BLOCKED_EXPANSIONS"
which can contain a case insensitive, comma separated list of blocked
expansions.
Expansions on this list can no longer be downloaded using commands.
2023-03-30 13:41:01 +02:00
Julian
b4e60b7db5 Prevent loading of extensions outside of the expansions folder using the register command 2023-03-30 12:47:23 +02:00
85 changed files with 6117 additions and 4083 deletions

View File

@@ -1,7 +1,7 @@
[issue]: https://github.com/PlaceholderAPI/PlaceholderAPI/issues/new
[discord]: https://helpch.at/discord
[code of conduct]: https://github.com/PlaceholderAPI/PlaceholderAPI/blob/master/CODE_OF_CONDUCT.md
[wiki]: https://github.com/PlaceholderAPI/PlaceholderAPI/blob/docs/wiki/wiki
[wiki]: https://github.com/PlaceholderAPI/PlaceholderAPI/blob/wiki
[master]: https://github.com/PlaceholderAPI/PlaceholderAPI/tree/master
[docs-wiki]: https://github.com/PlaceholderAPI/PlaceholderAPI/tree/docs/wiki
[style]: https://github.com/PlaceholderAPI/PlaceholderAPI/tree/master/config/style
@@ -38,9 +38,10 @@ PlaceholderAPI provides a feature to have expansions (separate jar files) for pl
In those cases should you report the issue to the issue tracker of the expansion or plugin.
## Pull requests
As an open source project are we welcoming all contributions to improve PlaceholderAPI, being it changes to its code, or contributions to its documentation such as the [Wiki] or the Javadocs.
As an open source project we are welcoming all contributions to improve PlaceholderAPI, being it changes to its code, or contributions to its documentation such as the [Wiki] or the Javadocs.
**When contributing, make sure to both base of and target the mentioned branch. Pull requests targeting the wrong branch may get closed without a warning.**
> [!IMPORTANT]
> When contributing, make sure to both base of and target the mentioned branch. Pull requests targeting the wrong branch may get closed without a warning.
### Code contributions
> **Source and Target Branch:** [`master`][master]
@@ -56,12 +57,10 @@ Javadocs changes should usually be combined with [code contributions](#code-cont
\*This branch may change in the future.
### Wiki contributions
> **Source and Target Branch:** [`docs/wiki`][docs-wiki]
> **Source and Target Branch:** [`wiki`][docs-wiki]
The Wiki of PlaceholderAPI has a unique quirk by having a dedicated [`wiki` folder][wiki] hosting the pages found on the actual wiki itself.
If you find outdated information, want to add missing expansions/plugins or just like to correct some wrong spelling should you both base of and target your contributions on the `docs/wiki` branch.
**Please only PR changes for the `wiki` folder on the `docs/wiki` branch! Pull requests not following this guidelines will be closed unnanounced.**
The Wiki of PlaceholderAPI is located on its own dedicated branch, hosting all the assets and files that get used to create it through the usage of GitHub Actions and GitHub Pages.
We welcome contributions that update outdated information, add new expansions/plugins supporting PlaceholderAPI or even correct spelling mistakes and typos.
## Code of Conduct
We have a [Code of Conduct] to maintain a welcoming atmosphere in this project.

View File

@@ -21,8 +21,8 @@
### Description
<!-- What does your Pull request change? -->
Closes N/A <!-- If your PR is based on an issue, change "N/A" the the issue ID (#id) -->
Closes N/A <!-- If your PR is based on an issue, change "N/A" to the issue ID (#id) -->
<!-- DO NOT ALTER ANYTHING BELOW THIS LINE! -->
[Wiki]: https://github.com/PlaceholderAPI/PlaceholderAPI/wiki
[Wiki]: https://wiki.placeholderapi.com

View File

@@ -1,8 +1,8 @@
comment:
footer: "\
----\n\n
> **Note**\n
> *This is an automated response created by a **GitHub Action***\n
> [!NOTE]\n
> *This is an automated response created by a **GitHub Action***<br>
> *Mentioning the bot won't have any effect!*
"

View File

@@ -5,8 +5,10 @@ on:
branches:
- development
paths:
- "src/**"
- "build.gradle"
- "../../spigot/**"
- "../../paper/**"
- "build.gradle.kts"
- "settings.gradle.kts"
jobs:
testBuilds:

View File

@@ -7,18 +7,20 @@
[discord]: https://helpch.at/discord
[spigot]: https://www.spigotmc.org/resources/6245/
[Expansions cloud]: https://api.extendedclip.com/home
[hangar]: https://hangar.papermc.io/HelpChat/PlaceholderAPI
[modrinth]: https://modrinth.com/plugin/placeholderapi
[Expansions cloud]: https://ecloud.placeholderapi.com
[placeholder list]: https://helpch.at/placeholders
[statistics]: https://bstats.org/plugin/bukkit/PlaceholderAPI
[ci]: http://ci.extendedclip.com/job/PlaceholderAPI/
[ciImg]: http://ci.extendedclip.com/buildStatus/icon?job=PlaceholderAPI
[APIversionImg]: https://img.shields.io/nexus/placeholderapi/me.clip/placeholderapi?server=https%3A%2F%2Frepo.extendedclip.com&label=API%20Version
[logo]: https://raw.githubusercontent.com/PlaceholderAPI/PlaceholderAPI/wiki/img/papi-logo.png
[APIversionImg]: https://repo.extendedclip.com/api/badge/latest/releases/me/clip/placeholderapi?name=API%20Version
[logo]: https://wiki.placeholderapi.com/assets/img/papi-logo.png
[contributing]: https://github.com/PlaceholderAPI/PlaceholderAPI/blob/master/.github/CONTRIBUTING.md
[placeholderexpansion]: https://github.com/PlaceholderAPI/PlaceholderAPI/wiki/PlaceholderExpansion
[placeholderexpansion]: https://wiki.placeholderapi.com/developers/creating-a-placeholderexpansion/
<!-- The stuff above isn't visible in the readme -->
[![logo]][spigot]
@@ -30,7 +32,7 @@
Support for specific plugins are provided either by the plugin itself or through expansions. The expansions may be downloaded in-game through the PAPI Expansion Cloud. There are currently over 240+ expansions that support a wide variety of plugins, such as Essentials, Factions, LuckPerms, and Vault.
PlaceholderAPI has been downloaded over 1,000,000 times and has been used concurrently on over 40,000 servers, which makes it a must-have for a server of any type or scale.
PlaceholderAPI has been downloaded over 2,000,000 times on Spigot and has been used concurrently on over 50,000 servers, which makes it a must-have for a server of any type or scale.
## Contribute
If you would like to contribute towards PlaceholderAPI should you take a look at our [Contributing file][contributing] for the ins and outs on how you can do that and what you need to keep in mind.
@@ -47,4 +49,6 @@ If you would like to create your own Placeholder Expansion for PlaceholderAPI, t
- [Expansions Cloud]
- [Placeholder List]
- [Spigot Page][spigot]
- [Hangar Page][hangar]
- [Modrinth Page][modrinth]
- [Plugin Statistics][statistics]

View File

@@ -3,15 +3,23 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
plugins {
`java-library`
`maven-publish`
id("com.github.hierynomus.license") version "0.16.1"
id("com.github.johnrengelman.shadow") version "8.1.0"
// id("com.github.hierynomus.license") version "0.16.1"
id("io.github.goooler.shadow") version "8.1.7"
}
group = "me.clip"
version = "2.11.4-DEV-${System.getProperty("BUILD_NUMBER")}"
version = "2.12.2-DEV-${System.getProperty("BUILD_NUMBER")}"
description = "An awesome placeholder provider!"
val paper by sourceSets.creating {
java.srcDir("src/paper/java")
// paper can see main code
compileClasspath += sourceSets.main.get().output
runtimeClasspath += output + compileClasspath
}
repositories {
maven("https://oss.sonatype.org/content/repositories/snapshots/")
@@ -20,18 +28,21 @@ repositories {
maven("https://repo.codemc.org/repository/maven-public/")
maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/")
maven("https://repo.papermc.io/repository/maven-public/")
}
dependencies {
implementation("org.bstats:bstats-bukkit:3.0.1")
implementation("net.kyori:adventure-platform-bukkit:4.3.0")
implementation("org.bstats:bstats-bukkit:3.1.0")
implementation("net.kyori:adventure-platform-bukkit:4.4.1")
compileOnly("org.spigotmc:spigot-api:1.19-R0.1-SNAPSHOT")
add(paper.compileOnlyConfigurationName, "net.kyori:adventure-platform-bukkit:4.4.1")
add(paper.compileOnlyConfigurationName, "dev.folia:folia-api:1.21.11-R0.1-SNAPSHOT")
compileOnly("dev.folia:folia-api:1.21.11-R0.1-SNAPSHOT")
compileOnlyApi("org.jetbrains:annotations:23.0.0")
testImplementation("org.openjdk.jmh:jmh-core:1.32")
testImplementation("org.openjdk.jmh:jmh-generator-annprocess:1.32")
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.8.2")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.1")
}
@@ -43,19 +54,8 @@ java {
withJavadocJar()
withSourcesJar()
}
license {
header = rootProject.file("config/headers/main.txt")
include("**/*.java")
mapping("java", "JAVADOC_STYLE")
encoding = "UTF-8"
ext {
set("year", 2021)
}
disableAutoTargetJvm()
}
val javaComponent: SoftwareComponent = components["java"]
@@ -69,13 +69,36 @@ tasks {
dependsOn(named("shadowJar"))
}
withType<JavaCompile> {
register<JavaCompile>("compilePaper") {
source = paper.java
classpath = paper.compileClasspath
destinationDirectory.set(layout.buildDirectory.dir("classes/java/paper"))
options.encoding = "UTF-8"
options.release = 8
}
withType<Javadoc> {
val plainJar by registering(Jar::class) {
dependsOn("compilePaper")
archiveClassifier.set("plain")
from(sourceSets.main.get().output)
from(paper.output)
}
val combinedSourcesJar by registering(Jar::class) {
archiveClassifier.set("sources")
from(sourceSets.main.get().allSource)
from(paper.allSource)
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
val combinedJavadoc by registering(Javadoc::class) {
isFailOnError = false
source = sourceSets.main.get().allJava + paper.allJava
classpath = sourceSets.main.get().compileClasspath + paper.compileClasspath
with(options as StandardJavadocDocletOptions) {
addStringOption("Xdoclint:none", "-quiet")
addStringOption("encoding", "UTF-8")
@@ -83,11 +106,41 @@ tasks {
}
}
val combinedJavadocJar by registering(Jar::class) {
archiveClassifier.set("javadoc")
dependsOn(combinedJavadoc)
from(combinedJavadoc.get().destinationDir)
}
withType<JavaCompile> {
options.encoding = "UTF-8"
options.release = 8
}
withType<ShadowJar> {
configurations = listOf(project.configurations.runtimeClasspath.get())
from(sourceSets.main.get().output)
archiveClassifier.set("")
relocate("org.bstats", "me.clip.placeholderapi.metrics")
relocate("net.kyori", "me.clip.placeholderapi.libs.kyori")
exclude("META-INF/versions/**")
dependsOn("compilePaper")
doLast {
val paperDir = layout.buildDirectory.dir("classes/java/paper").get().asFile
val jarFile = archiveFile.get().asFile
ant.invokeMethod("zip", mapOf(
"destfile" to jarFile,
"update" to "true",
"basedir" to paperDir
))
}
}
test {
@@ -98,16 +151,28 @@ tasks {
publications {
create<MavenPublication>("maven") {
artifactId = "placeholderapi"
from(javaComponent)
artifact(plainJar) {
builtBy(plainJar)
classifier = ""
}
artifact(combinedSourcesJar) {
builtBy(combinedSourcesJar)
}
artifact(combinedJavadocJar) {
builtBy(combinedJavadocJar)
}
}
}
repositories {
maven {
if ("-DEV" in version.toString()) {
url = uri("https://repo.extendedclip.com/content/repositories/dev/")
url = uri("https://repo.extendedclip.com/snapshots")
} else {
url = uri("https://repo.extendedclip.com/content/repositories/placeholderapi/")
url = uri("https://repo.extendedclip.com/releases")
}
credentials {

View File

@@ -1,4 +1,4 @@
Copyright (c) 2018-2021 Peter Blood
Copyright (c) 2018-2026 Peter Blood
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:

Binary file not shown.

View File

@@ -1,5 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

28
gradlew vendored
View File

@@ -1,7 +1,7 @@
#!/bin/sh
#
# Copyright <EFBFBD> 2015-2021 the original authors.
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -32,10 +32,10 @@
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions <EFBFBD>$var<EFBFBD>, <EFBFBD>${var}<EFBFBD>, <EFBFBD>${var:-default}<EFBFBD>, <EFBFBD>${var+SET}<EFBFBD>,
# <EFBFBD>${var#prefix}<EFBFBD>, <EFBFBD>${var%suffix}<EFBFBD>, and <EFBFBD>$( cmd )<EFBFBD>;
# * compound commands having a testable exit status, especially <EFBFBD>case<EFBFBD>;
# * various built-in commands including <EFBFBD>command<EFBFBD>, <EFBFBD>set<EFBFBD>, and <EFBFBD>ulimit<EFBFBD>.
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
@@ -55,7 +55,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -80,10 +80,10 @@ do
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# 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"'
@@ -143,12 +143,16 @@ fi
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
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -205,6 +209,12 @@ set -- \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.

15
gradlew.bat vendored
View File

@@ -14,7 +14,7 @@
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@@ -25,7 +25,8 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
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!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
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

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -21,6 +21,7 @@
package me.clip.placeholderapi;
import com.google.common.collect.ImmutableSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -28,6 +29,7 @@ import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import me.clip.placeholderapi.expansion.Relational;
import me.clip.placeholderapi.expansion.manager.LocalExpansionManager;
@@ -46,9 +48,9 @@ public final class PlaceholderAPI {
private static final Replacer REPLACER_PERCENT = new CharsReplacer(Closure.PERCENT);
private static final Replacer REPLACER_BRACKET = new CharsReplacer(Closure.BRACKET);
private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("[%]([^%]+)[%]");
private static final Pattern BRACKET_PLACEHOLDER_PATTERN = Pattern.compile("[{]([^{}]+)[}]");
private static final Pattern RELATIONAL_PLACEHOLDER_PATTERN = Pattern
static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("[%]([^%]+)[%]");
static final Pattern BRACKET_PLACEHOLDER_PATTERN = Pattern.compile("[{]([^{}]+)[}]");
static final Pattern RELATIONAL_PLACEHOLDER_PATTERN = Pattern
.compile("[%](rel_)([^%]+)[%]");
@@ -136,8 +138,8 @@ public final class PlaceholderAPI {
* @return String containing all translated placeholders
*/
@NotNull
public static List<String> setBracketPlaceholders(final OfflinePlayer player,
@NotNull final List<String> text) {
public static List<@NotNull String> setBracketPlaceholders(final OfflinePlayer player,
@NotNull final List<@NotNull String> text) {
return text.stream().map(line -> setBracketPlaceholders(player, line))
.collect(Collectors.toList());
}
@@ -150,7 +152,8 @@ public final class PlaceholderAPI {
* @param text Text to set the placeholder values in
* @return String containing all translated placeholders
*/
public static String setBracketPlaceholders(Player player, String text) {
@NotNull
public static String setBracketPlaceholders(Player player, @NotNull String text) {
return setBracketPlaceholders((OfflinePlayer) player, text);
}
@@ -162,7 +165,8 @@ public final class PlaceholderAPI {
* @param text List of Strings to set the placeholder values in
* @return String containing all translated placeholders
*/
public static List<String> setBracketPlaceholders(Player player, List<String> text) {
@NotNull
public static List<String> setBracketPlaceholders(Player player, @NotNull List<String> text) {
return setBracketPlaceholders((OfflinePlayer) player, text);
}
@@ -294,24 +298,21 @@ public final class PlaceholderAPI {
@Deprecated
@ApiStatus.ScheduledForRemoval(inVersion = "2.13.0")
public static boolean registerExpansion(PlaceholderExpansion expansion)
{
public static boolean registerExpansion(PlaceholderExpansion expansion) {
return expansion.register();
}
@Deprecated
@ApiStatus.ScheduledForRemoval(inVersion = "2.13.0")
public static boolean unregisterExpansion(PlaceholderExpansion expansion)
{
public static boolean unregisterExpansion(PlaceholderExpansion expansion) {
return expansion.unregister();
}
/**
* Get map of registered placeholders
*
* @deprecated Use {@link LocalExpansionManager#getExpansions()} instead.
*
* @return Map of registered placeholders
* @deprecated Use {@link LocalExpansionManager#getExpansions()} instead.
*/
@Deprecated
@ApiStatus.ScheduledForRemoval(inVersion = "2.13.0")
@@ -322,12 +323,11 @@ public final class PlaceholderAPI {
}
/**
* @deprecated Please use {@link PlaceholderExpansion} to
* register placeholders instead
*
* @param plugin The Plugin to register with this {@link PlaceholderHook}
* @param placeholderHook The {@link PlaceholderHook} to register
* @return always false
* @deprecated Please use {@link PlaceholderExpansion} to
* register placeholders instead
*/
@Deprecated
@ApiStatus.ScheduledForRemoval(inVersion = "2.13.0")
@@ -339,12 +339,11 @@ public final class PlaceholderAPI {
}
/**
* @deprecated Please use {@link PlaceholderExpansion} to
* register placeholders instead
*
* @param identifier The identifier to use for the {@link PlaceholderHook}
* @param placeholderHook The {@link PlaceholderHook} to register
* @return always false
* @deprecated Please use {@link PlaceholderExpansion} to
* register placeholders instead
*/
@Deprecated
@ApiStatus.ScheduledForRemoval(inVersion = "2.13.0")
@@ -356,11 +355,10 @@ public final class PlaceholderAPI {
}
/**
* @deprecated Please use {@link PlaceholderExpansion} to
* unregister placeholders instead
*
* @param plugin The plugin to unregister
* @return always false
* @deprecated Please use {@link PlaceholderExpansion} to
* unregister placeholders instead
*/
@Deprecated
@ApiStatus.ScheduledForRemoval(inVersion = "2.13.0")
@@ -372,11 +370,10 @@ public final class PlaceholderAPI {
}
/**
* @deprecated Please use {@link PlaceholderExpansion} to
* unregister placeholders instead
*
* @param identifier The identifier to unregister
* @return always false
* @deprecated Please use {@link PlaceholderExpansion} to
* unregister placeholders instead
*/
@Deprecated
@ApiStatus.ScheduledForRemoval(inVersion = "2.13.0")
@@ -388,9 +385,8 @@ public final class PlaceholderAPI {
}
/**
* @deprecated Will be removed in a future release.
*
* @return Set of registered identifiers
* @deprecated Will be removed in a future release.
*/
@Deprecated
@ApiStatus.ScheduledForRemoval(inVersion = "2.13.0")
@@ -399,9 +395,8 @@ public final class PlaceholderAPI {
}
/**
* @deprecated Will be removed in a future release.
*
* @return always null
* @deprecated Will be removed in a future release.
*/
@Deprecated
@ApiStatus.ScheduledForRemoval(inVersion = "2.13.0")
@@ -410,13 +405,12 @@ public final class PlaceholderAPI {
}
/**
* @deprecated Please use {@link #setPlaceholders(OfflinePlayer, String)} instead
*
* @param player The offline player to parse the placeholders against
* @param text The text to parse
* @param pattern The Pattern to use
* @param colorize If PlaceholderAPI should also parse color codes
* @return String with the parsed placeholders
* @deprecated Please use {@link #setPlaceholders(OfflinePlayer, String)} instead
*/
@Deprecated
@ApiStatus.ScheduledForRemoval(inVersion = "2.13.0")
@@ -426,13 +420,12 @@ public final class PlaceholderAPI {
}
/**
* @deprecated Please use {@link #setPlaceholders(OfflinePlayer, List)} instead
*
* @param player The offline player to parse the placeholders against
* @param text The List of text to parse
* @param pattern The Pattern to use
* @param colorize If PlaceholderAPI should also parse color codes
* @return String with the parsed placeholders
* @deprecated Please use {@link #setPlaceholders(OfflinePlayer, List)} instead
*/
@Deprecated
@ApiStatus.ScheduledForRemoval(inVersion = "2.13.0")
@@ -442,12 +435,11 @@ public final class PlaceholderAPI {
}
/**
* @deprecated Use {@link #setPlaceholders(OfflinePlayer, List)} instead.
*
* @param player The offline player to parse the placeholders against
* @param text The List of text to parse
* @param colorize If PlaceholderAPI should also parse color codes
* @return String with the parsed placeholders
* @deprecated Use {@link #setPlaceholders(OfflinePlayer, List)} instead.
*/
@Deprecated
@ApiStatus.ScheduledForRemoval(inVersion = "2.13.0")
@@ -457,12 +449,11 @@ public final class PlaceholderAPI {
}
/**
* @deprecated Use {@link #setPlaceholders(OfflinePlayer, List)} instead.
*
* @param player The offline player to parse the placeholders against
* @param text The List of text to parse
* @param pattern The Pattern to use
* @return String with the parsed placeholders
* @deprecated Use {@link #setPlaceholders(OfflinePlayer, List)} instead.
*/
@Deprecated
@ApiStatus.ScheduledForRemoval(inVersion = "2.13.0")
@@ -470,13 +461,13 @@ public final class PlaceholderAPI {
Pattern pattern) {
return setPlaceholders(player, text);
}
/**
* @deprecated Will be removed in a future release.
*
* @param player The offline player to parse the placeholders against
* @param text The text to parse
* @param colorize If PlaceholderAPI should also parse color codes
* @return String with the parsed placeholders
* @deprecated Will be removed in a future release.
*/
@Deprecated
@ApiStatus.ScheduledForRemoval(inVersion = "2.13.0")
@@ -485,12 +476,11 @@ public final class PlaceholderAPI {
}
/**
* @deprecated Will be removed in a future release.
*
* @param player The offline player to parse the placeholders against
* @param text The List of text to parse
* @param colorize If PlaceholderAPI should also parse color codes
* @return String with the parsed placeholders
* @deprecated Will be removed in a future release.
*/
@Deprecated
@ApiStatus.ScheduledForRemoval(inVersion = "2.13.0")
@@ -499,12 +489,11 @@ public final class PlaceholderAPI {
}
/**
* @deprecated Use {@link #setPlaceholders(OfflinePlayer, String)} instead.
*
* @param player The offline player to parse the placeholders against
* @param text The text to parse
* @param colorize If PlaceholderAPI should also parse color codes
* @return String with the parsed placeholders
* @deprecated Use {@link #setPlaceholders(OfflinePlayer, String)} instead.
*/
@Deprecated
@ApiStatus.ScheduledForRemoval(inVersion = "2.13.0")
@@ -513,12 +502,11 @@ public final class PlaceholderAPI {
}
/**
* @deprecated Use {@link #setPlaceholders(OfflinePlayer, String)} instead.
*
* @param player The offline player to parse the placeholders against
* @param text The text to parse
* @param pattern The Pattern to use
* @return String with the parsed placeholders
* @deprecated Use {@link #setPlaceholders(OfflinePlayer, String)} instead.
*/
@Deprecated
@ApiStatus.ScheduledForRemoval(inVersion = "2.13.0")
@@ -527,12 +515,11 @@ public final class PlaceholderAPI {
}
/**
* @deprecated Use {@link #setPlaceholders(OfflinePlayer, List)} instead.
*
* @param player The offline player to parse the placeholders against
* @param text The List of text to parse
* @param colorize If PlaceholderAPI should also parse color codes
* @return String with the parsed placeholders
* @deprecated Use {@link #setPlaceholders(OfflinePlayer, List)} instead.
*/
@Deprecated
@ApiStatus.ScheduledForRemoval(inVersion = "2.13.0")
@@ -542,12 +529,11 @@ public final class PlaceholderAPI {
}
/**
* @deprecated Use {@link #setPlaceholders(OfflinePlayer, String)} instead.
*
* @param player The offline player to parse the placeholders against
* @param text The text to parse
* @param colorize If PlaceholderAPI should also parse color codes
* @return String with the parsed placeholders
* @deprecated Use {@link #setPlaceholders(OfflinePlayer, String)} instead.
*/
@Deprecated
@ApiStatus.ScheduledForRemoval(inVersion = "2.13.0")
@@ -556,12 +542,11 @@ public final class PlaceholderAPI {
}
/**
* @deprecated Will be removed in a future release.
*
* @param player The offline player to parse the placeholders against
* @param text The text to parse
* @param colorize If PlaceholderAPI should also parse color codes
* @return String with the parsed placeholders
* @deprecated Will be removed in a future release.
*/
@Deprecated
@ApiStatus.ScheduledForRemoval(inVersion = "2.13.0")
@@ -570,12 +555,11 @@ public final class PlaceholderAPI {
}
/**
* @deprecated Will be removed in a future release.
*
* @param player The offline player to parse the placeholders against
* @param text The List of text to parse
* @param colorize If PlaceholderAPI should also parse color codes
* @return String with the parsed placeholders
* @deprecated Will be removed in a future release.
*/
@Deprecated
@ApiStatus.ScheduledForRemoval(inVersion = "2.13.0")

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -23,6 +23,7 @@ package me.clip.placeholderapi;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.Map;
import me.clip.placeholderapi.commands.PlaceholderCommandRouter;
import me.clip.placeholderapi.configuration.PlaceholderAPIConfig;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
@@ -30,7 +31,10 @@ import me.clip.placeholderapi.expansion.Version;
import me.clip.placeholderapi.expansion.manager.CloudExpansionManager;
import me.clip.placeholderapi.expansion.manager.LocalExpansionManager;
import me.clip.placeholderapi.listeners.ServerLoadEventListener;
import me.clip.placeholderapi.scheduler.UniversalScheduler;
import me.clip.placeholderapi.scheduler.scheduling.schedulers.TaskScheduler;
import me.clip.placeholderapi.updatechecker.UpdateChecker;
import me.clip.placeholderapi.util.ExpansionSafetyCheck;
import me.clip.placeholderapi.util.Msg;
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
import org.bstats.bukkit.Metrics;
@@ -40,6 +44,7 @@ import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.command.PluginCommand;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
@@ -55,7 +60,17 @@ public final class PlaceholderAPIPlugin extends JavaPlugin {
private static PlaceholderAPIPlugin instance;
static {
final String version = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
String version = Bukkit.getServer().getBukkitVersion().split("-")[0];
String suffix;
if (version.chars()
.filter(c -> c == '.')
.count() == 1) {
suffix = "R1";
version = 'v' + version.replace('.', '_') + '_' + suffix;
} else {
int minor = Integer.parseInt(version.split("\\.")[2].charAt(0) + "");
version = 'v' + version.replace('.', '_').replace("_" + minor, "") + '_' + "R" + (minor - 1);
}
boolean isSpigot;
try {
@@ -75,8 +90,12 @@ public final class PlaceholderAPIPlugin extends JavaPlugin {
private final LocalExpansionManager localExpansionManager = new LocalExpansionManager(this);
@NotNull
private final CloudExpansionManager cloudExpansionManager = new CloudExpansionManager(this);
@NotNull
private final TaskScheduler scheduler = UniversalScheduler.getScheduler(this);
private BukkitAudiences adventure;
private boolean safetyCheck = false;
/**
* Gets the static instance of the main class for PlaceholderAPI. This class is not the actual API
@@ -127,19 +146,30 @@ public final class PlaceholderAPIPlugin extends JavaPlugin {
}
}
@Deprecated
public static Version getServerVersion() {
return VERSION;
}
@Override
public void onLoad() {
instance = this;
saveDefaultConfig();
safetyCheck = new ExpansionSafetyCheck(this).runChecks();
if (safetyCheck) {
return;
}
instance = this;
}
@Override
public void onEnable() {
if (safetyCheck) {
return;
}
setupCommand();
setupMetrics();
setupExpansions();
@@ -157,12 +187,16 @@ public final class PlaceholderAPIPlugin extends JavaPlugin {
@Override
public void onDisable() {
if (safetyCheck) {
return;
}
getCloudExpansionManager().kill();
getLocalExpansionManager().kill();
HandlerList.unregisterAll(this);
Bukkit.getScheduler().cancelTasks(this);
scheduler.cancelTasks(this);
adventure.close();
adventure = null;
@@ -196,13 +230,18 @@ public final class PlaceholderAPIPlugin extends JavaPlugin {
@NotNull
public BukkitAudiences getAdventure() {
if(adventure == null) {
if (adventure == null) {
throw new IllegalStateException("Tried to access Adventure when the plugin was disabled!");
}
return adventure;
}
@NotNull
public TaskScheduler getScheduler() {
return scheduler;
}
/**
* Obtain the configuration class for PlaceholderAPI.
*
@@ -250,8 +289,8 @@ public final class PlaceholderAPIPlugin extends JavaPlugin {
Class.forName("org.bukkit.event.server.ServerLoadEvent");
new ServerLoadEventListener(this);
} catch (final ClassNotFoundException ignored) {
Bukkit.getScheduler()
.runTaskLater(this, () -> getLocalExpansionManager().load(Bukkit.getConsoleSender()), 1);
scheduler
.runTaskLater(() -> getLocalExpansionManager().load(Bukkit.getConsoleSender()), 1);
}
}

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -29,7 +29,7 @@ public abstract class PlaceholderHook {
@Nullable
public String onRequest(final OfflinePlayer player, @NotNull final String params) {
if (player != null && player.isOnline()) {
return onPlaceholderRequest((Player) player, params);
return onPlaceholderRequest(player.getPlayer(), params);
}
return onPlaceholderRequest(null, params);

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -22,10 +22,12 @@ package me.clip.placeholderapi.commands;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Stream;
import me.clip.placeholderapi.PlaceholderAPIPlugin;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -22,6 +22,7 @@ package me.clip.placeholderapi.commands;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -30,6 +31,8 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Stream;
import com.google.common.collect.Lists;
import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.commands.impl.cloud.CommandECloud;
import me.clip.placeholderapi.commands.impl.local.CommandDump;
@@ -117,14 +120,18 @@ public final class PlaceholderCommandRouter implements CommandExecutor, TabCompl
}
@Override
public List<String> onTabComplete(@NotNull final CommandSender sender,
@NotNull final Command command, @NotNull final String alias, @NotNull final String[] args) {
public List<String> onTabComplete(@NotNull final CommandSender sender, @NotNull final Command command,
@NotNull final String alias, @NotNull final String[] args) {
final List<String> suggestions = new ArrayList<>();
if (args.length > 1) {
final PlaceholderCommand target = this.commands.get(args[0].toLowerCase(Locale.ROOT));
if (target != null) {
if (target.getPermission() != null && !target.getPermission().isEmpty() && !sender.hasPermission(target.getPermission())) {
return suggestions;
}
target.complete(plugin, sender, args[0].toLowerCase(Locale.ROOT),
Arrays.asList(Arrays.copyOfRange(args, 1, args.length)), suggestions);
}

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -118,6 +118,11 @@ public final class CommandECloud extends PlaceholderCommand {
return;
}
if (!target.getLabel().equalsIgnoreCase("refresh") && plugin.getCloudExpansionManager().isEmpty()) {
Msg.msg(sender, "&cThere is no available data from the eCloud. Please try running &f/papi ecloud refresh&c. If this does not resolve the issue, the eCloud may be blocked by your firewall, server host, or service provider.\n&r\n&cMore information: &fhttps://placeholderapi.com/ecloud-blocked");
return;
}
target.evaluate(plugin, sender, search, params.subList(1, params.size()));
}

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -21,6 +21,7 @@
package me.clip.placeholderapi.commands.impl.cloud;
import java.util.List;
import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.commands.PlaceholderCommand;
import me.clip.placeholderapi.util.Msg;

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -20,9 +20,11 @@
package me.clip.placeholderapi.commands.impl.cloud;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.commands.PlaceholderCommand;
import me.clip.placeholderapi.expansion.cloud.CloudExpansion;
@@ -37,6 +39,16 @@ public final class CommandECloudDownload extends PlaceholderCommand {
super("download");
}
private boolean isBlockedExpansion(String name) {
String env = System.getenv("PAPI_BLOCKED_EXPANSIONS");
if (env == null) {
return false;
}
return Arrays.stream(env.split(","))
.anyMatch(s -> s.equalsIgnoreCase(name));
}
@Override
public void evaluate(@NotNull final PlaceholderAPIPlugin plugin,
@NotNull final CommandSender sender, @NotNull final String alias,
@@ -47,6 +59,12 @@ public final class CommandECloudDownload extends PlaceholderCommand {
return;
}
if (isBlockedExpansion(params.get(0))) {
Msg.msg(sender,
"&cThis expansion can't be downloaded.");
return;
}
final CloudExpansion expansion = plugin.getCloudExpansionManager()
.findCloudExpansionByName(params.get(0)).orElse(null);
if (expansion == null) {
@@ -73,6 +91,11 @@ public final class CommandECloudDownload extends PlaceholderCommand {
}
}
if (!version.isVerified()) {
Msg.msg(sender, "&cThe expansion '&f" + params.get(0) + "&c' is not verified and can only be downloaded manually from &fhttps://ecloud.placeholderapi.com");
return;
}
plugin.getCloudExpansionManager().downloadExpansion(expansion, version)
.whenComplete((file, exception) -> {
if (exception != null) {
@@ -86,9 +109,7 @@ public final class CommandECloudDownload extends PlaceholderCommand {
.getVersion() + "] &ato file: &f" + file.getName(),
"&aMake sure to type &f/papi reload &ato enable your new expansion!");
plugin.getCloudExpansionManager().clean();
plugin.getCloudExpansionManager()
.fetch(plugin.getPlaceholderAPIConfig().cloudAllowUnverifiedExpansions());
plugin.getCloudExpansionManager().load();
});
}

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -23,6 +23,7 @@ package me.clip.placeholderapi.commands.impl.cloud;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.commands.PlaceholderCommand;
import me.clip.placeholderapi.expansion.cloud.CloudExpansion;
@@ -63,9 +64,6 @@ public final class CommandECloudExpansionInfo extends PlaceholderCommand {
.append('\n')
.append("&bAuthor: &f")
.append(expansion.getAuthor())
.append('\n')
.append("&bVerified: ")
.append(expansion.isVerified() ? "&a&l✔" : "&c&l❌")
.append('\n');
if (params.size() < 2) {
@@ -76,6 +74,9 @@ public final class CommandECloudExpansionInfo extends PlaceholderCommand {
.append(expansion.getTimeSinceLastUpdate())
.append(" ago")
.append('\n')
.append("&bVerified: ")
.append(expansion.getVersion().isVerified() ? "&a&l✔" : "&c&l❌")
.append('\n')
.append("&bRelease Notes: &f")
.append(expansion.getVersion().getReleaseNotes())
.append('\n');
@@ -91,6 +92,9 @@ public final class CommandECloudExpansionInfo extends PlaceholderCommand {
builder.append("&bVersion: &f")
.append(version.getVersion())
.append('\n')
.append("&bVerified: ")
.append(version.isVerified() ? "&a&l✔" : "&c&l❌")
.append('\n')
.append("&bRelease Notes: &f")
.append(version.getReleaseNotes())
.append('\n')

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -25,12 +25,14 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.primitives.Ints;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.commands.PlaceholderCommand;
import me.clip.placeholderapi.configuration.ExpansionSort;
@@ -65,7 +67,7 @@ public final class CommandECloudExpansionList extends PlaceholderCommand {
expansion -> "&f" + expansion.getAuthor();
@NotNull
private static final Function<CloudExpansion, Object> EXPANSION_VERIFIED =
expansion -> expansion.isVerified() ? "&aY" : "&cN";
expansion -> expansion.getVersion().isVerified() ? "&aY" : "&cN";
@NotNull
private static final Function<CloudExpansion, Object> EXPANSION_LATEST_VERSION =
expansion -> "&f" + expansion.getLatestVersion();
@@ -168,7 +170,9 @@ public final class CommandECloudExpansionList extends PlaceholderCommand {
.append(newline()).append(newline())
.append(text("Author: ", AQUA)).append(text(expansion.getAuthor(), WHITE))
.append(newline())
.append(text("Verified: ", AQUA)).append(text(expansion.isVerified() ? "" : "", expansion.isVerified() ? GREEN : RED, TextDecoration.BOLD))
.append(text("Version: ", AQUA)).append(text(expansion.getVersion().getVersion(), WHITE))
.append(newline())
.append(text("Verified: ", AQUA)).append(text(expansion.getVersion().isVerified() ? "" : "", expansion.getVersion().isVerified() ? GREEN : RED, TextDecoration.BOLD))
.append(newline())
.append(text("Released: ", AQUA)).append(text(format.format(expansion.getLastUpdate()), WHITE))
.toBuilder();

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -21,9 +21,11 @@
package me.clip.placeholderapi.commands.impl.cloud;
import com.google.common.collect.Lists;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.commands.PlaceholderCommand;
import me.clip.placeholderapi.expansion.cloud.CloudExpansion;

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -21,6 +21,7 @@
package me.clip.placeholderapi.commands.impl.cloud;
import java.util.List;
import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.commands.PlaceholderCommand;
import me.clip.placeholderapi.util.Msg;
@@ -38,9 +39,7 @@ public final class CommandECloudRefresh extends PlaceholderCommand {
public void evaluate(@NotNull final PlaceholderAPIPlugin plugin,
@NotNull final CommandSender sender, @NotNull final String alias,
@NotNull @Unmodifiable final List<String> params) {
plugin.getCloudExpansionManager().clean();
plugin.getCloudExpansionManager()
.fetch(plugin.getPlaceholderAPIConfig().cloudAllowUnverifiedExpansions());
plugin.getCloudExpansionManager().load();
Msg.msg(sender,
"&aThe eCloud manager has been refreshed!");

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -21,6 +21,7 @@
package me.clip.placeholderapi.commands.impl.cloud;
import java.util.List;
import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.commands.PlaceholderCommand;
import me.clip.placeholderapi.expansion.manager.CloudExpansionManager;

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -21,6 +21,7 @@
package me.clip.placeholderapi.commands.impl.cloud;
import com.google.common.collect.Lists;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
@@ -28,6 +29,7 @@ import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.commands.PlaceholderCommand;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -200,7 +200,9 @@ public final class CommandDump extends PlaceholderCommand {
for (final Plugin other : plugins) {
builder.append(" ")
.append(String.format("%-" + size + "s", other.getName()))
.append(" [Version: ")
.append(" [Authors: [")
.append(String.join(", ", other.getDescription().getAuthors()))
.append("], Version: ")
.append(other.getDescription().getVersion())
.append("]")
.append("\n");

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -25,6 +25,7 @@ import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.logging.Level;
import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.commands.PlaceholderCommand;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
@@ -54,7 +55,7 @@ public final class CommandExpansionRegister extends PlaceholderCommand {
final LocalExpansionManager manager = plugin.getLocalExpansionManager();
final File file = new File(manager.getExpansionsFolder(), params.get(0));
if (!file.exists()) {
if (!file.exists() || !file.getParentFile().equals(manager.getExpansionsFolder())) {
Msg.msg(sender,
"&cThe file &f" + file.getName() + "&c doesn't exist!");
return;

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -22,6 +22,7 @@ package me.clip.placeholderapi.commands.impl.local;
import java.util.List;
import java.util.Optional;
import me.clip.placeholderapi.PlaceholderAPI;
import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.commands.PlaceholderCommand;

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -21,6 +21,7 @@
package me.clip.placeholderapi.commands.impl.local;
import java.util.List;
import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.commands.PlaceholderCommand;
import me.clip.placeholderapi.util.Msg;

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -21,6 +21,7 @@
package me.clip.placeholderapi.commands.impl.local;
import java.util.List;
import me.clip.placeholderapi.PlaceholderAPI;
import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.commands.PlaceholderCommand;

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -21,9 +21,11 @@
package me.clip.placeholderapi.commands.impl.local;
import com.google.common.collect.Lists;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import me.clip.placeholderapi.PlaceholderAPI;
import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.commands.PlaceholderCommand;

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -25,6 +25,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Stream;
import me.clip.placeholderapi.PlaceholderAPI;
import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.commands.PlaceholderCommand;
@@ -87,7 +88,8 @@ public final class CommandParse extends PlaceholderCommand {
final boolean command) {
if (params.size() < 2) {
Msg.msg(sender,
"&cYou must supply a target, and a message: &b/papi " + (broadcast ? "bcparse" : "parse")
"&cYou must provide a target and message: &b/papi "
+ (command ? "cmdparse" : (broadcast ? "bcparse" : "parse"))
+ " &7{target} &a{message}");
return;
}
@@ -132,24 +134,49 @@ public final class CommandParse extends PlaceholderCommand {
@NotNull @Unmodifiable final List<String> params) {
if (params.size() < 3) {
Msg.msg(sender,
"&cYou must supply two targets, and a message: &b/papi parserel &7{target one} {target two} &a{message}");
"&cYou must supply two targets, and a message: &b/papi parserel &7{target one} "
+ "{target two} &a{message}");
return;
}
final OfflinePlayer targetOne = resolvePlayer(params.get(0));
if (targetOne == null || !targetOne.isOnline()) {
OfflinePlayer playerOne;
if ("me".equalsIgnoreCase(params.get(0))) {
if (!(sender instanceof Player)) {
Msg.msg(sender, "&cYou must be a player to use &7me&c as a target!");
return;
}
playerOne = ((Player) sender);
} else {
playerOne = resolvePlayer(params.get(0));
}
if (playerOne == null || !playerOne.isOnline()) {
Msg.msg(sender, "&cFailed to find player: &f" + params.get(0));
return;
}
final OfflinePlayer targetTwo = resolvePlayer(params.get(1));
if (targetTwo == null || !targetTwo.isOnline()) {
OfflinePlayer playerTwo;
if ("me".equalsIgnoreCase(params.get(1))) {
if (!(sender instanceof Player)) {
Msg.msg(sender, "&cYou must be a player to use &7me&c as a target!");
return;
}
playerTwo = ((Player) sender);
} else {
playerTwo = resolvePlayer(params.get(1));
}
if (playerTwo == null || !playerTwo.isOnline()) {
Msg.msg(sender, "&cFailed to find player: &f" + params.get(1));
return;
}
final String message = PlaceholderAPI
.setRelationalPlaceholders(((Player) targetOne), ((Player) targetTwo),
.setRelationalPlaceholders((Player) playerOne, (Player) playerTwo,
String.join(" ", params.subList(2, params.size())));
sender.sendMessage(message);

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -21,8 +21,10 @@
package me.clip.placeholderapi.commands.impl.local;
import java.util.List;
import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.commands.PlaceholderCommand;
import me.clip.placeholderapi.util.ExpansionSafetyCheck;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Unmodifiable;
@@ -37,7 +39,9 @@ public final class CommandReload extends PlaceholderCommand {
public void evaluate(@NotNull final PlaceholderAPIPlugin plugin,
@NotNull final CommandSender sender, @NotNull final String alias,
@NotNull @Unmodifiable final List<String> params) {
if (!new ExpansionSafetyCheck(plugin).runChecks()) {
plugin.reloadConf(sender);
}
}
}

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -21,6 +21,7 @@
package me.clip.placeholderapi.commands.impl.local;
import java.util.List;
import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.commands.PlaceholderCommand;
import me.clip.placeholderapi.util.Msg;

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -21,6 +21,7 @@
package me.clip.placeholderapi.configuration;
import java.util.Comparator;
import me.clip.placeholderapi.expansion.cloud.CloudExpansion;
import org.jetbrains.annotations.NotNull;

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -21,6 +21,7 @@
package me.clip.placeholderapi.configuration;
import java.util.Optional;
import me.clip.placeholderapi.PlaceholderAPIPlugin;
import org.jetbrains.annotations.NotNull;
@@ -38,10 +39,6 @@ public final class PlaceholderAPIConfig {
return plugin.getConfig().getBoolean("check_updates");
}
public boolean cloudAllowUnverifiedExpansions() {
return plugin.getConfig().getBoolean("cloud_allow_unverified_expansions");
}
public boolean isCloudEnabled() {
return plugin.getConfig().getBoolean("cloud_enabled");
@@ -57,6 +54,10 @@ public final class PlaceholderAPIConfig {
return plugin.getConfig().getBoolean("debug", false);
}
public boolean useAdventureReplacer() {
return plugin.getConfig().getBoolean("use_adventure_provided_replacer", false);
}
public Optional<ExpansionSort> getExpansionSort() {
final String option = plugin.getConfig()
@@ -90,4 +91,11 @@ public final class PlaceholderAPIConfig {
return plugin.getConfig().getString("boolean.false", "false");
}
public boolean useAdventureProvidedReplacer() {
return plugin.getConfig().getBoolean("use_adventure_provided_replacer", false);
}
public boolean detectMaliciousExpansions() {
return plugin.getConfig().getBoolean("detect_malicious_expansions", true);
}
}

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -23,6 +23,7 @@ package me.clip.placeholderapi.events;
import java.util.Collections;
import java.util.List;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
@@ -53,7 +54,7 @@ public class ExpansionsLoadedEvent extends Event {
* @return List of {@link PlaceholderExpansion registered PlaceholderExpansions}.
*/
@NotNull
public final List<PlaceholderExpansion> getExpansions(){
public final List<PlaceholderExpansion> getExpansions() {
return expansions;
}

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -46,7 +46,17 @@ public enum NMSVersion {
SPIGOT_1_18_R1("v1_18_R1"),
SPIGOT_1_19_R1("v1_19_R1"),
SPIGOT_1_19_R2("v1_19_R2"),
SPIGOT_1_19_R3("v1_19_R3");
SPIGOT_1_19_R3("v1_19_R3"),
SPIGOT_1_20_R1("v1_20_R1"),
SPIGOT_1_20_R2("v1_20_R2"),
SPIGOT_1_20_R3("v1_20_R3"),
SPIGOT_1_20_R4("v1_20_R4"),
SPIGOT_1_21_R1("v1_21_R1"),
SPIGOT_1_21_R2("V1_21_R2"),
SPIGOT_1_21_R3("V1_21_R3"),
SPIGOT_1_21_R4("V1_21_R4"),
SPIGOT_1_21_R5("V1_21_R5"),
SPIGOT_1_21_R6("V1_21_R6");
private final String version;

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -23,6 +23,7 @@ package me.clip.placeholderapi.expansion;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.PlaceholderHook;
import org.bukkit.Bukkit;
@@ -40,6 +41,15 @@ import org.jetbrains.annotations.Nullable;
*/
public abstract class PlaceholderExpansion extends PlaceholderHook {
/**
* The type is {@link Type#INTERNAL} by default.
* For external expansions, the type is updated on {@link me.clip.placeholderapi.expansion.manager.LocalExpansionManager#register(Class) register}.
*
* @since 2.11.4
*/
@ApiStatus.Internal
protected Type expansionType = Type.INTERNAL;
/**
* The placeholder identifier of this expansion. May not contain {@literal %},
* {@literal {}} or _
@@ -159,6 +169,28 @@ public abstract class PlaceholderExpansion extends PlaceholderHook {
return PlaceholderAPIPlugin.getInstance();
}
/**
* Get the type of the expansion
*
* @return the type of the expansion
* @since 2.11.4
*/
@ApiStatus.Internal
public Type getExpansionType() {
return expansionType;
}
/**
* Set the type of the expansion
*
* @param expansionType the new type
* @since 2.11.4
*/
@ApiStatus.Internal
public void setExpansionType(Type expansionType) {
this.expansionType = expansionType;
}
// === Configuration ===
/**
@@ -166,7 +198,7 @@ public abstract class PlaceholderExpansion extends PlaceholderHook {
* null when not specified.
* <br>You may use the {@link Configurable} interface to define default values set
*
* @return ConfigurationSection that this epxpansion has.
* @return ConfigurationSection that this expansion has.
*/
@Nullable
public final ConfigurationSection getConfigSection() {
@@ -394,16 +426,15 @@ public abstract class PlaceholderExpansion extends PlaceholderHook {
*/
@Override
public final String toString() {
return String.format("PlaceholderExpansion[name: '%s', author: '%s', version: '%s']", getName(),
getAuthor(), getVersion());
return String.format("PlaceholderExpansion[name: '%s', author: '%s', version: '%s', type: '%s']", getName(),
getAuthor(), getVersion(), getExpansionType());
}
// === Deprecated API ===
/**
* @deprecated As of versions greater than 2.8.7, use {@link #getRequiredPlugin()}
*
* @return The plugin name.
* @deprecated As of versions greater than 2.8.7, use {@link #getRequiredPlugin()}
*/
@Deprecated
@ApiStatus.ScheduledForRemoval(inVersion = "2.13.0")
@@ -412,9 +443,8 @@ public abstract class PlaceholderExpansion extends PlaceholderHook {
}
/**
* @deprecated As of versions greater than 2.8.7, use the expansion cloud to show a description
*
* @return The description of the expansion.
* @deprecated As of versions greater than 2.8.7, use the expansion cloud to show a description
*/
@Deprecated
@ApiStatus.ScheduledForRemoval(inVersion = "2.13.0")
@@ -423,13 +453,27 @@ public abstract class PlaceholderExpansion extends PlaceholderHook {
}
/**
* @deprecated As of versions greater than 2.8.7, use the expansion cloud to display a link
*
* @return The link for the expansion.
* @deprecated As of versions greater than 2.8.7, use the expansion cloud to display a link
*/
@Deprecated
@ApiStatus.ScheduledForRemoval(inVersion = "2.13.0")
public String getLink() {
return null;
}
public enum Type {
/**
* An expansion provided by a plugin is considered internal
*/
INTERNAL,
/**
* An expansion loaded from the expansions folder is considered external
*/
EXTERNAL
}
}

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -20,6 +20,7 @@
package me.clip.placeholderapi.expansion;
@Deprecated
public final class Version {
private final boolean isSpigot;

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -27,7 +27,9 @@ package me.clip.placeholderapi.expansion;
* with that version.
*
* @author Ryan McCarthy
* @deprecated Will be removed in a future release.
*/
@Deprecated
public interface VersionSpecific {
/**
@@ -35,7 +37,6 @@ public interface VersionSpecific {
* will be passed to this method so you know what version the server is currently running.
*
* @param v The {@link Version} to check against
*
* @return true if your expansion is compatible with the version the server is running.
*/
boolean isCompatibleWith(Version v);

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -23,6 +23,7 @@ package me.clip.placeholderapi.expansion.cloud;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import me.clip.placeholderapi.util.TimeUtil;
@@ -36,8 +37,7 @@ public class CloudExpansion {
dependency_url;
private boolean hasExpansion,
shouldUpdate,
verified;
shouldUpdate;
private long last_update,
ratings_count;
@@ -135,10 +135,6 @@ public class CloudExpansion {
this.shouldUpdate = shouldUpdate;
}
public boolean isVerified() {
return verified;
}
public long getLastUpdate() {
return last_update;
}
@@ -174,6 +170,7 @@ public class CloudExpansion {
public static class Version {
private String url, version, release_notes;
private boolean verified;
public String getUrl() {
return url;
@@ -198,5 +195,13 @@ public class CloudExpansion {
public void setReleaseNotes(String release_notes) {
this.release_notes = release_notes;
}
public boolean isVerified() {
return verified;
}
public void setVerified(boolean verified) {
this.verified = verified;
}
}
}

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -25,11 +25,13 @@ import com.google.common.io.Resources;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Type;
import java.net.URL;
import java.net.UnknownHostException;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.charset.StandardCharsets;
@@ -50,6 +52,7 @@ import java.util.function.Function;
import java.util.logging.Level;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import me.clip.placeholderapi.expansion.cloud.CloudExpansion;
@@ -60,7 +63,7 @@ import org.jetbrains.annotations.Unmodifiable;
public final class CloudExpansionManager {
@NotNull
private static final String API_URL = "http://api.extendedclip.com/v2/";
private static final String API_URL = "https://ecloud.placeholderapi.com/api/v3/?platform=bukkit";
@NotNull
private static final Gson GSON = new Gson();
@@ -100,7 +103,7 @@ public final class CloudExpansionManager {
public void load() {
clean();
fetch(plugin.getPlaceholderAPIConfig().cloudAllowUnverifiedExpansions());
fetch();
}
public void kill() {
@@ -113,6 +116,10 @@ public final class CloudExpansionManager {
return ImmutableMap.copyOf(cache);
}
public boolean isEmpty() {
return cache.isEmpty();
}
@NotNull
@Unmodifiable
public Map<String, CloudExpansion> getCloudExpansionsInstalled() {
@@ -170,7 +177,7 @@ public final class CloudExpansionManager {
await.clear();
}
public void fetch(final boolean allowUnverified) {
public void fetch() {
plugin.getLogger().info("Fetching available expansion information...");
ASYNC_EXECUTOR.submit(
@@ -190,25 +197,22 @@ public final class CloudExpansionManager {
|| expansion.getVersion(expansion.getLatestVersion()) == null) {
toRemove.add(entry.getKey());
}
if (!allowUnverified && !expansion.isVerified()) {
toRemove.add(entry.getKey());
}
}
for (String name : toRemove) {
values.remove(name);
}
} catch (UnknownHostException e) {
plugin.getLogger().log(Level.WARNING, "There is no data available from the eCloud. Please try running /papi refresh. If this does not resolve the issue, the eCloud may be blocked by your firewall, server host, or service provider.\n\nMore information: https://placeholderapi.com/ecloud-blocked", e);
} catch (Throwable e) {
// ugly swallowing of every throwable, but we have to be defensive
plugin.getLogger().log(Level.WARNING, "Failed to download expansion information", e);
}
// loop thru what's left on the main thread
// loop through what's left on the main thread
plugin
.getServer()
.getScheduler()
.runTask(
plugin,
() -> {
try {
for (Map.Entry<String, CloudExpansion> entry : values.entrySet()) {

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -22,6 +22,7 @@ package me.clip.placeholderapi.expansion.manager;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import java.io.File;
import java.lang.reflect.Modifier;
import java.util.Arrays;
@@ -34,10 +35,11 @@ import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.stream.Collectors;
import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.events.ExpansionRegisterEvent;
import me.clip.placeholderapi.events.ExpansionUnregisterEvent;
@@ -172,7 +174,7 @@ public final class LocalExpansionManager implements Listener {
try {
final PlaceholderExpansion expansion = createExpansionInstance(clazz);
if(expansion == null){
if (expansion == null) {
return Optional.empty();
}
@@ -188,6 +190,8 @@ public final class LocalExpansionManager implements Listener {
}
}
expansion.setExpansionType(PlaceholderExpansion.Type.EXTERNAL);
if (!expansion.register()) {
Msg.warn("Cannot load expansion %s due to an unknown issue.", expansion.getIdentifier());
return Optional.empty();
@@ -209,6 +213,12 @@ public final class LocalExpansionManager implements Listener {
return Optional.empty();
}
/**
* Attempt to register a {@link PlaceholderExpansion}
*
* @param expansion the expansion to register
* @return if the expansion was registered
*/
@ApiStatus.Internal
public boolean register(@NotNull final PlaceholderExpansion expansion) {
final String identifier = expansion.getIdentifier().toLowerCase(Locale.ROOT);
@@ -217,9 +227,9 @@ public final class LocalExpansionManager implements Listener {
return false;
}
if (expansions.containsKey(identifier)) {
Msg.warn("Failed to load expansion %s. Identifier is already in use.",
expansion.getIdentifier());
// Avoid loading two external expansions with the same identifier
if (expansion.getExpansionType() == PlaceholderExpansion.Type.EXTERNAL && expansions.containsKey(identifier)) {
Msg.warn("Failed to load external expansion %s. Identifier is already in use.", expansion.getIdentifier());
return false;
}
@@ -287,21 +297,24 @@ public final class LocalExpansionManager implements Listener {
Bukkit.getPluginManager().registerEvents(((Listener) expansion), plugin);
}
Msg.info("Successfully registered expansion: %s [%s]", expansion.getIdentifier(),
expansion.getVersion());
Msg.info(
"Successfully registered %s expansion: %s [%s]",
expansion.getExpansionType().name().toLowerCase(),
expansion.getIdentifier(),
expansion.getVersion()
);
if (expansion instanceof Taskable) {
((Taskable) expansion).start();
}
if (plugin.getPlaceholderAPIConfig().isCloudEnabled()) {
final Optional<CloudExpansion> cloudExpansionOptional =
plugin.getCloudExpansionManager().findCloudExpansionByName(identifier);
// Check eCloud for updates only if the expansion is external
if (plugin.getPlaceholderAPIConfig().isCloudEnabled() && expansion.getExpansionType() == PlaceholderExpansion.Type.EXTERNAL) {
final Optional<CloudExpansion> cloudExpansionOptional = plugin.getCloudExpansionManager().findCloudExpansionByName(identifier);
if (cloudExpansionOptional.isPresent()) {
CloudExpansion cloudExpansion = cloudExpansionOptional.get();
cloudExpansion.setHasExpansion(true);
cloudExpansion.setShouldUpdate(
!cloudExpansion.getLatestVersion().equals(expansion.getVersion()));
cloudExpansion.setShouldUpdate(!cloudExpansion.getLatestVersion().equals(expansion.getVersion()));
}
}
@@ -430,7 +443,8 @@ public final class LocalExpansionManager implements Listener {
Msg.severe("Failed to load expansion %s (is a dependency missing?)", e, file.getName());
return null;
} catch (Exception e) {
throw new CompletionException(e.getMessage() + " (expansion file: " + file.getAbsolutePath() + ")", e);
plugin.getLogger().log(Level.SEVERE, "Failed to load expansion file: " + file.getAbsolutePath(), e);
return null;
}
});
}

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -22,6 +22,7 @@ package me.clip.placeholderapi.replacer;
import java.util.Locale;
import java.util.function.Function;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import org.bukkit.ChatColor;
import org.bukkit.OfflinePlayer;

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -21,6 +21,7 @@
package me.clip.placeholderapi.replacer;
import java.util.function.Function;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import org.bukkit.OfflinePlayer;
import org.jetbrains.annotations.NotNull;

View File

@@ -0,0 +1,180 @@
/*
* MIT License
*
* Copyright (c) 2023 Sevastjan
*
* 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 me.clip.placeholderapi.scheduler;
import me.clip.placeholderapi.scheduler.scheduling.schedulers.TaskScheduler;
import me.clip.placeholderapi.scheduler.scheduling.tasks.MyScheduledTask;
import org.bukkit.plugin.Plugin;
/**
* Just modified BukkitRunnable
*/
public abstract class UniversalRunnable implements Runnable {
MyScheduledTask task;
public synchronized void cancel() throws IllegalStateException {
checkScheduled();
task.cancel();
}
/**
* Returns true if this task has been cancelled.
*
* @return true if the task has been cancelled
* @throws IllegalStateException if task was not scheduled yet
*/
public synchronized boolean isCancelled() throws IllegalStateException {
checkScheduled();
return task.isCancelled();
}
/**
* Schedules this in the Bukkit scheduler to run on next tick.
*
* @param plugin the reference to the plugin scheduling task
* @return {@link MyScheduledTask}
* @throws IllegalArgumentException if plugin is null
* @throws IllegalStateException if this was already scheduled
* @see TaskScheduler#runTask(Runnable)
*/
public synchronized MyScheduledTask runTask(Plugin plugin) throws IllegalArgumentException, IllegalStateException {
checkNotYetScheduled();
return setupTask(UniversalScheduler.getScheduler(plugin).runTask(this));
}
/**
* <b>Asynchronous tasks should never access any API in Bukkit. Great care
* should be taken to assure the thread-safety of asynchronous tasks.</b>
* <p>
* Schedules this in the Bukkit scheduler to run asynchronously.
*
* @param plugin the reference to the plugin scheduling task
* @return {@link MyScheduledTask}
* @throws IllegalArgumentException if plugin is null
* @throws IllegalStateException if this was already scheduled
* @see TaskScheduler#runTaskAsynchronously(Runnable)
*/
public synchronized MyScheduledTask runTaskAsynchronously(Plugin plugin) throws IllegalArgumentException, IllegalStateException {
checkNotYetScheduled();
return setupTask(UniversalScheduler.getScheduler(plugin).runTaskAsynchronously(this));
}
/**
* Schedules this to run after the specified number of server ticks.
*
* @param plugin the reference to the plugin scheduling task
* @param delay the ticks to wait before running the task
* @return {@link MyScheduledTask}
* @throws IllegalArgumentException if plugin is null
* @throws IllegalStateException if this was already scheduled
* @see TaskScheduler#runTaskLater(Runnable, long)
*/
public synchronized MyScheduledTask runTaskLater(Plugin plugin, long delay) throws IllegalArgumentException, IllegalStateException {
checkNotYetScheduled();
return setupTask(UniversalScheduler.getScheduler(plugin).runTaskLater(this, delay));
}
/**
* <b>Asynchronous tasks should never access any API in Bukkit. Great care
* should be taken to assure the thread-safety of asynchronous tasks.</b>
* <p>
* Schedules this to run asynchronously after the specified number of
* server ticks.
*
* @param plugin the reference to the plugin scheduling task
* @param delay the ticks to wait before running the task
* @return {@link MyScheduledTask}
* @throws IllegalArgumentException if plugin is null
* @throws IllegalStateException if this was already scheduled
* @see TaskScheduler#runTaskLaterAsynchronously(Runnable, long)
*/
public synchronized MyScheduledTask runTaskLaterAsynchronously(Plugin plugin, long delay) throws IllegalArgumentException, IllegalStateException {
checkNotYetScheduled();
return setupTask(UniversalScheduler.getScheduler(plugin).runTaskLaterAsynchronously(this, delay));
}
/**
* Schedules this to repeatedly run until cancelled, starting after the
* specified number of server ticks.
*
* @param plugin the reference to the plugin scheduling task
* @param delay the ticks to wait before running the task
* @param period the ticks to wait between runs
* @return {@link MyScheduledTask}
* @throws IllegalArgumentException if plugin is null
* @throws IllegalStateException if this was already scheduled
* @see TaskScheduler#runTaskTimer(Runnable, long, long)
*/
public synchronized MyScheduledTask runTaskTimer(Plugin plugin, long delay, long period) throws IllegalArgumentException, IllegalStateException {
checkNotYetScheduled();
return setupTask(UniversalScheduler.getScheduler(plugin).runTaskTimer(this, delay, period));
}
/**
* <b>Asynchronous tasks should never access any API in Bukkit. Great care
* should be taken to assure the thread-safety of asynchronous tasks.</b>
* <p>
* Schedules this to repeatedly run asynchronously until cancelled,
* starting after the specified number of server ticks.
*
* @param plugin the reference to the plugin scheduling task
* @param delay the ticks to wait before running the task for the first
* time
* @param period the ticks to wait between runs
* @return {@link MyScheduledTask}
* @throws IllegalArgumentException if plugin is null
* @throws IllegalStateException if this was already scheduled
* @see TaskScheduler#runTaskTimerAsynchronously(Runnable, long, long)
*/
public synchronized MyScheduledTask runTaskTimerAsynchronously(Plugin plugin, long delay, long period) throws IllegalArgumentException, IllegalStateException {
checkNotYetScheduled();
return setupTask(UniversalScheduler.getScheduler(plugin).runTaskTimerAsynchronously(this, delay, period));
}
private void checkScheduled() {
if (task == null) {
throw new IllegalStateException("Not scheduled yet");
}
}
private void checkNotYetScheduled() {
if (task != null) {
throw new IllegalStateException("Already scheduled");
}
}
private MyScheduledTask setupTask(final MyScheduledTask task) {
this.task = task;
return task;
}
}

View File

@@ -0,0 +1,42 @@
/*
* MIT License
*
* Copyright (c) 2023 Sevastjan
*
* 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 me.clip.placeholderapi.scheduler;
import me.clip.placeholderapi.scheduler.bukkit.BukkitScheduler;
import me.clip.placeholderapi.scheduler.folia.FoliaScheduler;
import me.clip.placeholderapi.scheduler.paper.PaperScheduler;
import me.clip.placeholderapi.scheduler.scheduling.schedulers.TaskScheduler;
import me.clip.placeholderapi.scheduler.utils.JavaUtil;
import org.bukkit.plugin.Plugin;
public class UniversalScheduler {
private static final boolean IS_FOLIA = JavaUtil.classExists("io.papermc.paper.threadedregions.RegionizedServer");
private static final boolean IS_CANVAS = JavaUtil.classExists("io.canvasmc.canvas.server.ThreadedServer");
private static final boolean IS_EXPANDED_SCHEDULING_AVAILABLE = JavaUtil.classExists("io.papermc.paper.threadedregions.scheduler.ScheduledTask");
public static TaskScheduler getScheduler(Plugin plugin) {
return IS_FOLIA || IS_CANVAS ? new FoliaScheduler(plugin) : (IS_EXPANDED_SCHEDULING_AVAILABLE ? new PaperScheduler(plugin) : new BukkitScheduler(plugin));
}
}

View File

@@ -0,0 +1,71 @@
/*
* MIT License
*
* Copyright (c) 2023 Sevastjan
*
* 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 me.clip.placeholderapi.scheduler.bukkit;
import me.clip.placeholderapi.scheduler.scheduling.tasks.MyScheduledTask;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitTask;
public class BukkitScheduledTask implements MyScheduledTask {
BukkitTask task;
boolean isRepeating;
public BukkitScheduledTask(final BukkitTask task) {
this.task = task;
this.isRepeating = false;
}
public BukkitScheduledTask(final BukkitTask task, boolean isRepeating) {
this.task = task;
this.isRepeating = isRepeating;
}
@Override
public void cancel() {
task.cancel();
}
@Override
public boolean isCancelled() {
return task.isCancelled();
}
@Override
public Plugin getOwningPlugin() {
return task.getOwner();
}
@Override
public boolean isCurrentlyRunning() {
return Bukkit.getServer().getScheduler().isCurrentlyRunning(this.task.getTaskId()); //There's no other way. Fuck bukkit
}
@Override
public boolean isRepeatingTask() {
return isRepeating;
}
}

View File

@@ -0,0 +1,129 @@
/*
* MIT License
*
* Copyright (c) 2023 Sevastjan
*
* 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 me.clip.placeholderapi.scheduler.bukkit;
import me.clip.placeholderapi.scheduler.scheduling.schedulers.TaskScheduler;
import me.clip.placeholderapi.scheduler.scheduling.tasks.MyScheduledTask;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.plugin.Plugin;
public class BukkitScheduler implements TaskScheduler {
final Plugin plugin;
public BukkitScheduler(Plugin plugin) {
this.plugin = plugin;
}
@Override
public boolean isGlobalThread() {
return Bukkit.getServer().isPrimaryThread();
}
@Override
public boolean isEntityThread(Entity entity) {
return Bukkit.getServer().isPrimaryThread();
}
@Override
public boolean isRegionThread(Location location) {
return Bukkit.getServer().isPrimaryThread();
}
@Override
public MyScheduledTask runTask(Runnable runnable) {
return new BukkitScheduledTask(Bukkit.getScheduler().runTask(plugin, runnable));
}
@Override
public MyScheduledTask runTaskLater(Runnable runnable, long delay) {
return new BukkitScheduledTask(Bukkit.getScheduler().runTaskLater(plugin, runnable, delay));
}
@Override
public MyScheduledTask runTaskTimer(Runnable runnable, long delay, long period) {
return new BukkitScheduledTask(Bukkit.getScheduler().runTaskTimer(plugin, runnable, delay, period));
}
@Override
public MyScheduledTask runTaskAsynchronously(Runnable runnable) {
return new BukkitScheduledTask(Bukkit.getScheduler().runTaskAsynchronously(plugin, runnable));
}
@Override
public MyScheduledTask runTaskLaterAsynchronously(Runnable runnable, long delay) {
return new BukkitScheduledTask(Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, runnable, delay));
}
@Override
public MyScheduledTask runTaskTimerAsynchronously(Runnable runnable, long delay, long period) {
return new BukkitScheduledTask(Bukkit.getScheduler().runTaskTimerAsynchronously(plugin, runnable, delay, period));
}
//Useless? Or...
public MyScheduledTask runTask(Plugin plugin, Runnable runnable) {
return new BukkitScheduledTask(Bukkit.getScheduler().runTask(plugin, runnable));
}
@Override
public MyScheduledTask runTaskLater(Plugin plugin, Runnable runnable, long delay) {
return new BukkitScheduledTask(Bukkit.getScheduler().runTaskLater(plugin, runnable, delay));
}
@Override
public MyScheduledTask runTaskTimer(Plugin plugin, Runnable runnable, long delay, long period) {
return new BukkitScheduledTask(Bukkit.getScheduler().runTaskTimer(plugin, runnable, delay, period));
}
@Override
public MyScheduledTask runTaskAsynchronously(Plugin plugin, Runnable runnable) {
return new BukkitScheduledTask(Bukkit.getScheduler().runTaskAsynchronously(plugin, runnable));
}
@Override
public MyScheduledTask runTaskLaterAsynchronously(Plugin plugin, Runnable runnable, long delay) {
return new BukkitScheduledTask(Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, runnable, delay));
}
@Override
public MyScheduledTask runTaskTimerAsynchronously(Plugin plugin, Runnable runnable, long delay, long period) {
return new BukkitScheduledTask(Bukkit.getScheduler().runTaskTimerAsynchronously(plugin, runnable, delay, period));
}
@Override
public void execute(Runnable runnable) {
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, runnable);
}
@Override
public void cancelTasks() {
Bukkit.getScheduler().cancelTasks(plugin);
}
@Override
public void cancelTasks(Plugin plugin) {
Bukkit.getScheduler().cancelTasks(plugin);
}
}

View File

@@ -0,0 +1,57 @@
/*
* MIT License
*
* Copyright (c) 2023 Sevastjan
*
* 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 me.clip.placeholderapi.scheduler.folia;
import me.clip.placeholderapi.scheduler.scheduling.tasks.MyScheduledTask;
import io.papermc.paper.threadedregions.scheduler.ScheduledTask;
import org.bukkit.plugin.Plugin;
public class FoliaScheduledTask implements MyScheduledTask {
private final ScheduledTask task;
public FoliaScheduledTask(final ScheduledTask task) {
this.task = task;
}
public void cancel() {
this.task.cancel();
}
public boolean isCancelled() {
return this.task.isCancelled();
}
public Plugin getOwningPlugin() {
return this.task.getOwningPlugin();
}
public boolean isCurrentlyRunning() {
final ScheduledTask.ExecutionState state = this.task.getExecutionState();
return state == ScheduledTask.ExecutionState.RUNNING || state == ScheduledTask.ExecutionState.CANCELLED_RUNNING;
}
public boolean isRepeatingTask() {
return this.task.isRepeatingTask();
}
}

View File

@@ -0,0 +1,220 @@
/*
* MIT License
*
* Copyright (c) 2023 Sevastjan
*
* 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 me.clip.placeholderapi.scheduler.folia;
import me.clip.placeholderapi.scheduler.scheduling.schedulers.TaskScheduler;
import me.clip.placeholderapi.scheduler.scheduling.tasks.MyScheduledTask;
import io.papermc.paper.threadedregions.scheduler.AsyncScheduler;
import io.papermc.paper.threadedregions.scheduler.GlobalRegionScheduler;
import io.papermc.paper.threadedregions.scheduler.RegionScheduler;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.plugin.Plugin;
import java.util.concurrent.TimeUnit;
public class FoliaScheduler implements TaskScheduler {
final Plugin plugin;
public FoliaScheduler(Plugin plugin) {
this.plugin = plugin;
}
private final RegionScheduler regionScheduler = Bukkit.getServer().getRegionScheduler();
private final GlobalRegionScheduler globalRegionScheduler = Bukkit.getServer().getGlobalRegionScheduler();
private final AsyncScheduler asyncScheduler = Bukkit.getServer().getAsyncScheduler();
@Override
public boolean isGlobalThread() {
return Bukkit.getServer().isGlobalTickThread();
}
@Override
public boolean isTickThread() {
return Bukkit.getServer().isPrimaryThread(); // The Paper implementation checks whether this is a tick thread, this method exists to avoid confusion.
}
@Override
public boolean isEntityThread(Entity entity) {
return Bukkit.getServer().isOwnedByCurrentRegion(entity);
}
@Override
public boolean isRegionThread(Location location) {
return Bukkit.getServer().isOwnedByCurrentRegion(location);
}
@Override
public MyScheduledTask runTask(Runnable runnable) {
return new FoliaScheduledTask(globalRegionScheduler.run(plugin, task -> runnable.run()));
}
@Override
public MyScheduledTask runTaskLater(Runnable runnable, long delay) {
//Folia exception: Delay ticks may not be <= 0
if (delay <= 0) {
return runTask(runnable);
}
return new FoliaScheduledTask(globalRegionScheduler.runDelayed(plugin, task -> runnable.run(), delay));
}
@Override
public MyScheduledTask runTaskTimer(Runnable runnable, long delay, long period) {
//Folia exception: Delay ticks may not be <= 0
delay = getOneIfNotPositive(delay);
return new FoliaScheduledTask(globalRegionScheduler.runAtFixedRate(plugin, task -> runnable.run(), delay, period));
}
@Override
public MyScheduledTask runTask(Plugin plugin, Runnable runnable) {
return new FoliaScheduledTask(globalRegionScheduler.run(plugin, task -> runnable.run()));
}
@Override
public MyScheduledTask runTaskLater(Plugin plugin, Runnable runnable, long delay) {
//Folia exception: Delay ticks may not be <= 0
if (delay <= 0) {
return runTask(plugin, runnable);
}
return new FoliaScheduledTask(globalRegionScheduler.runDelayed(plugin, task -> runnable.run(), delay));
}
@Override
public MyScheduledTask runTaskTimer(Plugin plugin, Runnable runnable, long delay, long period) {
//Folia exception: Delay ticks may not be <= 0
delay = getOneIfNotPositive(delay);
return new FoliaScheduledTask(globalRegionScheduler.runAtFixedRate(plugin, task -> runnable.run(), delay, period));
}
@Override
public MyScheduledTask runTask(Location location, Runnable runnable) {
return new FoliaScheduledTask(regionScheduler.run(plugin, location, task -> runnable.run()));
}
@Override
public MyScheduledTask runTaskLater(Location location, Runnable runnable, long delay) {
//Folia exception: Delay ticks may not be <= 0
if (delay <= 0) {
return runTask(runnable);
}
return new FoliaScheduledTask(regionScheduler.runDelayed(plugin, location, task -> runnable.run(), delay));
}
@Override
public MyScheduledTask runTaskTimer(Location location, Runnable runnable, long delay, long period) {
//Folia exception: Delay ticks may not be <= 0
delay = getOneIfNotPositive(delay);
return new FoliaScheduledTask(regionScheduler.runAtFixedRate(plugin, location, task -> runnable.run(), delay, period));
}
@Override
public MyScheduledTask runTask(Entity entity, Runnable runnable) {
return new FoliaScheduledTask(entity.getScheduler().run(plugin, task -> runnable.run(), null));
}
@Override
public MyScheduledTask runTaskLater(Entity entity, Runnable runnable, long delay) {
//Folia exception: Delay ticks may not be <= 0
if (delay <= 0) {
return runTask(entity, runnable);
}
return new FoliaScheduledTask(entity.getScheduler().runDelayed(plugin, task -> runnable.run(), null, delay));
}
@Override
public MyScheduledTask runTaskTimer(Entity entity, Runnable runnable, long delay, long period) {
//Folia exception: Delay ticks may not be <= 0
delay = getOneIfNotPositive(delay);
return new FoliaScheduledTask(entity.getScheduler().runAtFixedRate(plugin, task -> runnable.run(), null, delay, period));
}
@Override
public MyScheduledTask runTaskAsynchronously(Runnable runnable) {
return new FoliaScheduledTask(asyncScheduler.runNow(plugin, task -> runnable.run()));
}
@Override
public MyScheduledTask runTaskLaterAsynchronously(Runnable runnable, long delay) {
//Folia exception: Delay ticks may not be <= 0
delay = getOneIfNotPositive(delay);
return new FoliaScheduledTask(asyncScheduler.runDelayed(plugin, task -> runnable.run(), delay * 50L, TimeUnit.MILLISECONDS));
}
@Override
public MyScheduledTask runTaskTimerAsynchronously(Runnable runnable, long delay, long period) {
return new FoliaScheduledTask(asyncScheduler.runAtFixedRate(plugin, task -> runnable.run(), delay * 50, period * 50, TimeUnit.MILLISECONDS));
}
@Override
public MyScheduledTask runTaskAsynchronously(Plugin plugin, Runnable runnable) {
return new FoliaScheduledTask(asyncScheduler.runNow(plugin, task -> runnable.run()));
}
@Override
public MyScheduledTask runTaskLaterAsynchronously(Plugin plugin, Runnable runnable, long delay) {
//Folia exception: Delay ticks may not be <= 0
delay = getOneIfNotPositive(delay);
return new FoliaScheduledTask(asyncScheduler.runDelayed(plugin, task -> runnable.run(), delay * 50L, TimeUnit.MILLISECONDS));
}
@Override
public MyScheduledTask runTaskTimerAsynchronously(Plugin plugin, Runnable runnable, long delay, long period) {
//Folia exception: Delay ticks may not be <= 0
delay = getOneIfNotPositive(delay);
return new FoliaScheduledTask(asyncScheduler.runAtFixedRate(plugin, task -> runnable.run(), delay * 50, period * 50, TimeUnit.MILLISECONDS));
}
@Override
public void execute(Runnable runnable) {
globalRegionScheduler.execute(plugin, runnable);
}
@Override
public void execute(Location location, Runnable runnable) {
regionScheduler.execute(plugin, location, runnable);
}
@Override
public void execute(Entity entity, Runnable runnable) {
entity.getScheduler().execute(plugin, runnable, null, 1L);
}
@Override
public void cancelTasks() {
globalRegionScheduler.cancelTasks(plugin);
asyncScheduler.cancelTasks(plugin);
}
@Override
public void cancelTasks(Plugin plugin) {
globalRegionScheduler.cancelTasks(plugin);
asyncScheduler.cancelTasks(plugin);
}
private long getOneIfNotPositive(long x) {
return x <= 0 ? 1L : x;
}
}

View File

@@ -0,0 +1,41 @@
/*
* MIT License
*
* Copyright (c) 2023 Sevastjan
*
* 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 me.clip.placeholderapi.scheduler.paper;
import me.clip.placeholderapi.scheduler.folia.FoliaScheduler;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
//Thanks to Towny
public class PaperScheduler extends FoliaScheduler {
public PaperScheduler(Plugin plugin) {
super(plugin);
}
@Override
public boolean isGlobalThread() {
// isGlobalThread does not exist on paper, match the bukkit task scheduler's behaviour.
return Bukkit.getServer().isPrimaryThread();
}
}

View File

@@ -0,0 +1,346 @@
/*
* MIT License
*
* Copyright (c) 2023 Sevastjan
*
* 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 me.clip.placeholderapi.scheduler.scheduling.schedulers;
import me.clip.placeholderapi.scheduler.scheduling.tasks.MyScheduledTask;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.plugin.Plugin;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
public interface TaskScheduler {
/**
* <b>Folia</b>: Returns whether the current thread is ticking the global region <br>
* <b>Paper and Bukkit</b>: Returns {@link org.bukkit.Server#isPrimaryThread}
*/
boolean isGlobalThread();
/**
* @return {@link org.bukkit.Server#isPrimaryThread}
*/
default boolean isTickThread() {
return Bukkit.getServer().isPrimaryThread();
}
/**
* <b>Folia and Paper</b>: Returns whether the current thread is ticking a region and that the region
* being ticked owns the specified entity. Note that this function is the only appropriate method of
* checking for ownership of an entity, as retrieving the entity's location is undefined unless the
* entity is owned by the current region
* <p>
* <b>Bukkit</b>: returns {@link org.bukkit.Server#isPrimaryThread}
*
* @param entity Specified entity
*/
boolean isEntityThread(Entity entity);
/**
* <b>Folia and Paper</b>: Returns whether the current thread is ticking a region and that the region
* being ticked owns the chunk at the specified world and block position as included in the specified location
* <p>
* <b>Bukkit</b>: returns {@link org.bukkit.Server#isPrimaryThread}
*
* @param location Specified location, must have a non-null world.
*/
boolean isRegionThread(Location location);
/**
* Schedules a task to be executed on the next tick <br>
* <b>Folia and Paper</b>: ...on the global region <br>
* <b>Bukkit</b>: ...on the main thread
*
* @param runnable The task to execute
*/
MyScheduledTask runTask(Runnable runnable);
/**
* Schedules a task to be executed after the specified delay in ticks <br>
* <b>Folia and Paper</b>: ...on the global region <br>
* <b>Bukkit</b>: ...on the main thread
*
* @param runnable The task to execute
* @param delay The delay, in ticks
*/
MyScheduledTask runTaskLater(Runnable runnable, long delay);
/**
* Schedules a repeating task to be executed after the initial delay with the specified period <br>
* <b>Folia and Paper</b>: ...on the global region <br>
* <b>Bukkit</b>: ...on the main thread
*
* @param runnable The task to execute
* @param delay The initial delay, in ticks.
* @param period The period, in ticks.
*/
MyScheduledTask runTaskTimer(Runnable runnable, long delay, long period);
/**
* Deprecated: use {@link #runTask(Runnable)}
*/
@Deprecated
default MyScheduledTask runTask(Plugin plugin, Runnable runnable) {
return runTask(runnable);
}
/**
* Deprecated: use {@link #runTaskLater(Runnable, long)}
*/
@Deprecated
default MyScheduledTask runTaskLater(Plugin plugin, Runnable runnable, long delay) {
return runTaskLater(runnable, delay);
}
/**
* Deprecated: use {@link #runTaskTimer(Runnable, long, long)}
*/
@Deprecated
default MyScheduledTask runTaskTimer(Plugin plugin, Runnable runnable, long delay, long period) {
return runTaskTimer(runnable, delay, period);
}
/**
* <b>Folia and Paper</b>: Schedules a task to be executed on the region which owns the location on the next tick
* <p>
* <b>Bukkit</b>: same as {@link #runTask(Runnable)}
*
* @param location The location which the region executing should own
* @param runnable The task to execute
*/
default MyScheduledTask runTask(Location location, Runnable runnable) {
return runTask(runnable);
}
/**
* <b>Folia and Paper</b>: Schedules a task to be executed on the region which owns the location after the
* specified delay in ticks
* <p>
* <b>Bukkit</b>: same as {@link #runTaskLater(Runnable, long)}
*
* @param location The location which the region executing should own
* @param runnable The task to execute
* @param delay The delay, in ticks.
*/
default MyScheduledTask runTaskLater(Location location, Runnable runnable, long delay) {
return runTaskLater(runnable, delay);
}
/**
* <b>Folia and Paper</b>: Schedules a repeating task to be executed on the region which owns the location
* after the initial delay with the specified period
* <p>
* <b>Bukkit</b>: same as {@link #runTaskTimer(Runnable, long, long)}
*
* @param location The location which the region executing should own
* @param runnable The task to execute
* @param delay The initial delay, in ticks.
* @param period The period, in ticks.
*/
default MyScheduledTask runTaskTimer(Location location, Runnable runnable, long delay, long period) {
return runTaskTimer(runnable, delay, period);
}
/**
* Deprecated: use {@link #runTaskLater(Runnable, long)}
*/
@Deprecated
default MyScheduledTask scheduleSyncDelayedTask(Runnable runnable, long delay) {
return runTaskLater(runnable, delay);
}
/**
* Deprecated: use {@link #execute(Runnable)} or {@link #runTask(Runnable)}
*/
@Deprecated
default MyScheduledTask scheduleSyncDelayedTask(Runnable runnable) {
return runTask(runnable);
}
/**
* Deprecated: use {@link #runTaskTimer(Runnable, long, long)}
*/
@Deprecated
default MyScheduledTask scheduleSyncRepeatingTask(Runnable runnable, long delay, long period) {
return runTaskTimer(runnable, delay, period);
}
/**
* <b>Folia and Paper</b>: Schedules a task to be executed on the region which owns the location
* of given entity on the next tick
* <p>
* <b>Bukkit</b>: same as {@link #runTask(Runnable)}
*
* @param entity The entity whose location the region executing should own
* @param runnable The task to execute
*/
default MyScheduledTask runTask(Entity entity, Runnable runnable) {
return runTask(runnable);
}
/**
* <b>Folia and Paper</b>: Schedules a task to be executed on the region which owns the location
* of given entity after the specified delay in ticks
* <p>
* <b>Bukkit</b>: same as {@link #runTaskLater(Runnable, long)}
*
* @param entity The entity whose location the region executing should own
* @param runnable The task to execute
* @param delay The delay, in ticks.
*/
default MyScheduledTask runTaskLater(Entity entity, Runnable runnable, long delay) {
return runTaskLater(runnable, delay);
}
/**
* <b>Folia and Paper</b>: Schedules a repeating task to be executed on the region which owns the
* location of given entity after the initial delay with the specified period
* <p>
* <b>Bukkit</b>: same as {@link #runTaskTimer(Runnable, long, long)}
*
* @param entity The entity whose location the region executing should own
* @param runnable The task to execute
* @param delay The initial delay, in ticks.
* @param period The period, in ticks.
*/
default MyScheduledTask runTaskTimer(Entity entity, Runnable runnable, long delay, long period) {
return runTaskTimer(runnable, delay, period);
}
/**
* Schedules the specified task to be executed asynchronously immediately
*
* @param runnable The task to execute
* @return The {@link MyScheduledTask} that represents the scheduled task
*/
MyScheduledTask runTaskAsynchronously(Runnable runnable);
/**
* Schedules the specified task to be executed asynchronously after the time delay has passed
*
* @param runnable The task to execute
* @param delay The time delay to pass before the task should be executed
* @return The {@link MyScheduledTask} that represents the scheduled task
*/
MyScheduledTask runTaskLaterAsynchronously(Runnable runnable, long delay);
/**
* Schedules the specified task to be executed asynchronously after the initial delay has passed,
* and then periodically executed with the specified period
*
* @param runnable The task to execute
* @param delay The time delay to pass before the first execution of the task, in ticks
* @param period The time between task executions after the first execution of the task, in ticks
* @return The {@link MyScheduledTask} that represents the scheduled task
*/
MyScheduledTask runTaskTimerAsynchronously(Runnable runnable, long delay, long period);
/**
* Deprecated: use {@link #runTaskAsynchronously(Runnable)}
*/
@Deprecated
default MyScheduledTask runTaskAsynchronously(Plugin plugin, Runnable runnable) {
return runTaskAsynchronously(runnable);
}
/**
* Deprecated: use {@link #runTaskLaterAsynchronously(Runnable, long)}
*/
@Deprecated
default MyScheduledTask runTaskLaterAsynchronously(Plugin plugin, Runnable runnable, long delay) {
return runTaskLaterAsynchronously(runnable, delay);
}
/**
* Deprecated: use {@link #runTaskTimerAsynchronously(Runnable, long, long)}
*/
@Deprecated
default MyScheduledTask runTaskTimerAsynchronously(Plugin plugin, Runnable runnable, long delay, long period) {
return runTaskTimerAsynchronously(runnable, delay, period);
}
/**
* Calls a method on the main thread and returns a Future object. This task will be executed
* by the main(Bukkit)/global(FoliaandPaper) server thread.
* <p>
* Note: The Future.get() methods must NOT be called from the main thread.
* <p>
* Note2: There is at least an average of 10ms latency until the isDone() method returns true.
*
* @param task Task to be executed
*/
default <T> Future<T> callSyncMethod(final Callable<T> task) {
CompletableFuture<T> completableFuture = new CompletableFuture<>();
execute(() -> {
try {
completableFuture.complete(task.call());
} catch (Exception e) {
throw new RuntimeException(e);
}
});
return completableFuture;
}
/**
* Schedules a task to be executed on the global region
*
* @param runnable The task to execute
*/
void execute(Runnable runnable);
/**
* Schedules a task to be executed on the region which owns the location
*
* @param location The location which the region executing should own
* @param runnable The task to execute
*/
default void execute(Location location, Runnable runnable) {
execute(runnable);
}
/**
* Schedules a task to be executed on the region which owns the location of given entity
*
* @param entity The entity which location the region executing should own
* @param runnable The task to execute
*/
default void execute(Entity entity, Runnable runnable) {
execute(runnable);
}
/**
* Attempts to cancel all tasks scheduled by this plugin
*/
void cancelTasks();
/**
* Attempts to cancel all tasks scheduled by the specified plugin
*
* @param plugin specified plugin
*/
void cancelTasks(Plugin plugin);
}

View File

@@ -0,0 +1,53 @@
/*
* MIT License
*
* Copyright (c) 2023 Sevastjan
*
* 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 me.clip.placeholderapi.scheduler.scheduling.tasks;
import org.bukkit.plugin.Plugin;
public interface MyScheduledTask {
/**
* Cancels executing task
*/
void cancel();
/**
* @return true if task is cancelled, false otherwise
*/
boolean isCancelled();
/**
* @return The plugin under which the task was scheduled.
*/
Plugin getOwningPlugin();
/**
* @return true if task is currently executing, false otherwise
*/
boolean isCurrentlyRunning();
/**
* @return true if task is repeating, false otherwise
*/
boolean isRepeatingTask();
}

View File

@@ -0,0 +1,12 @@
package me.clip.placeholderapi.scheduler.utils;
public class JavaUtil {
public static boolean classExists(String className) {
try {
Class.forName(className);
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
}

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -25,7 +25,11 @@ import java.io.InputStreamReader;
import java.net.URL;
import java.util.Arrays;
import javax.net.ssl.HttpsURLConnection;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.scheduler.scheduling.schedulers.TaskScheduler;
import me.clip.placeholderapi.util.Msg;
import org.bukkit.Bukkit;
import org.bukkit.event.EventHandler;
@@ -34,65 +38,68 @@ import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
public class UpdateChecker implements Listener {
private static final String MODRINTH_URL = "https://api.modrinth.com/v2/project/lKEzGugV/version";
private final int RESOURCE_ID = 6245;
private static final int RESOURCE_ID = 6245;
private final PlaceholderAPIPlugin plugin;
private final TaskScheduler scheduler;
private final String pluginVersion;
private String spigotVersion;
private String modrinthVersion;
private boolean updateAvailable;
public UpdateChecker(PlaceholderAPIPlugin i) {
plugin = i;
pluginVersion = i.getDescription().getVersion();
public UpdateChecker(PlaceholderAPIPlugin plugin) {
this.plugin = plugin;
scheduler = plugin.getScheduler();
pluginVersion = plugin.getDescription().getVersion();
}
public boolean hasUpdateAvailable() {
return updateAvailable;
}
public String getSpigotVersion() {
return spigotVersion;
public String getModrinthVersion() {
return modrinthVersion;
}
public void fetch() {
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
scheduler.runTaskAsynchronously(() -> {
try {
HttpsURLConnection con = (HttpsURLConnection) new URL(
"https://api.spigotmc.org/legacy/update.php?resource=" + RESOURCE_ID).openConnection();
HttpsURLConnection con = (HttpsURLConnection) new URL(MODRINTH_URL).openConnection();
con.setRequestMethod("GET");
spigotVersion = new BufferedReader(new InputStreamReader(con.getInputStream())).readLine();
final JsonElement json = JsonParser.parseReader(new BufferedReader(new InputStreamReader(con.getInputStream())));
modrinthVersion = json.getAsJsonArray().get(0).getAsJsonObject().get("version_number").getAsString();
} catch (Exception ex) {
plugin.getLogger().info("Failed to check for updates on spigot.");
plugin.getLogger().info("Failed to check for updates on modrinth.");
return;
}
if (spigotVersion == null || spigotVersion.isEmpty()) {
if (modrinthVersion == null || modrinthVersion.isEmpty()) {
return;
}
updateAvailable = spigotIsNewer();
updateAvailable = modrinthIsNewer();
if (!updateAvailable) {
return;
}
Bukkit.getScheduler().runTask(plugin, () -> {
scheduler.runTask(() -> {
plugin.getLogger()
.info("An update for PlaceholderAPI (v" + getSpigotVersion() + ") is available at:");
.info("An update for PlaceholderAPI (v" + getModrinthVersion() + ") is available at:");
plugin.getLogger()
.info("https://www.spigotmc.org/resources/placeholderapi." + RESOURCE_ID + "/");
.info("https://modrinth.com/plugin/placeholderapi");
Bukkit.getPluginManager().registerEvents(this, plugin);
});
});
}
private boolean spigotIsNewer() {
if (spigotVersion == null || spigotVersion.isEmpty()) {
private boolean modrinthIsNewer() {
if (modrinthVersion == null || modrinthVersion.isEmpty()) {
return false;
}
int[] plV = toReadable(pluginVersion);
int[] spV = toReadable(spigotVersion);
int[] spV = toReadable(modrinthVersion);
if (plV[0] < spV[0]) {
return true;
@@ -115,10 +122,9 @@ public class UpdateChecker implements Listener {
public void onJoin(PlayerJoinEvent e) {
if (e.getPlayer().hasPermission("placeholderapi.updatenotify")) {
Msg.msg(e.getPlayer(),
"&bAn update for &fPlaceholder&7API &e(&fPlaceholder&7API &fv" + getSpigotVersion()
"&bAn update for &fPlaceholder&7API &e(&fPlaceholder&7API &fv" + getModrinthVersion()
+ "&e)"
, "&bis available at &ehttps://www.spigotmc.org/resources/placeholderapi." + RESOURCE_ID
+ "/");
, "&bis available at &ehttps://modrinth.com/plugin/placeholderapi");
}
}
}

View File

@@ -0,0 +1,80 @@
package me.clip.placeholderapi.util;
import com.google.common.hash.Hashing;
import com.google.common.io.Files;
import com.google.common.io.Resources;
import me.clip.placeholderapi.PlaceholderAPIPlugin;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;
import java.util.stream.Collectors;
public final class ExpansionSafetyCheck {
private static final String MESSAGE =
"\n###############################################\n" +
"###############################################\n" +
"PlaceholderAPI performs checks at startup and /papi reload for known malicious expansions. If you're seeing this message, there are the following malicious expansions in plugins/PlaceholderAPI/expansions.\n" +
"%s" +
"To prevent further infection PlaceholderAPI has stopped the server.\n" +
"If you're seeing this message after updating PAPI, your server may have been infected for some time, so best practice is a complete system wipe and reinstall of your server software and plugins to be safe.\n" +
"If you're seeing this after downloading an expansion however, PAPI hasn't loaded any of the malicious expansions above so you should be safe to simply delete the expansion in question.\n" +
"###############################################\n" +
"###############################################";
private final PlaceholderAPIPlugin main;
public ExpansionSafetyCheck(@NotNull final PlaceholderAPIPlugin main) {
this.main = main;
}
public boolean runChecks() {
if (!main.getPlaceholderAPIConfig().detectMaliciousExpansions()) {
return false;
}
final File expansionsFolder = new File(main.getDataFolder(), "expansions");
if (!expansionsFolder.exists()) {
return false;
}
final Set<String> knownMaliciousExpansions;
try {
final String hashes = Resources.toString(new URL("https://check.placeholderapi.com"), StandardCharsets.UTF_8);
knownMaliciousExpansions = Arrays.stream(hashes.split("\n")).collect(Collectors.toSet());
} catch (Exception e) {
main.getLogger().log(Level.SEVERE, "Failed to download anti malware hash check list from https://check.placeholderapi.com", e);
return false;
}
final Set<String> maliciousPaths = new HashSet<>();
for (File file : expansionsFolder.listFiles()) {
try {
final String hash = Hashing.sha256().hashBytes(Files.asByteSource(file).read()).toString();
if (knownMaliciousExpansions.contains(hash)) {
maliciousPaths.add(file.getAbsolutePath());
}
} catch (Exception e) {
main.getLogger().log(Level.SEVERE, "Error occurred while trying to read " + file.getAbsolutePath(), e);
}
}
if (maliciousPaths.isEmpty()) {
return false;
}
main.getLogger().severe(String.format(MESSAGE, maliciousPaths.stream().map(p -> "HASH OF " + p + " MATCHES KNOWN MALICIOUS EXPANSION DELETE IMMEDIATELY\n").collect(Collectors.joining())));
main.getServer().shutdown();
return true;
}
}

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -29,6 +29,7 @@ import static java.util.stream.IntStream.range;
import java.util.List;
import java.util.Optional;
import org.jetbrains.annotations.NotNull;
/**

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -27,6 +27,8 @@ import java.util.function.BiConsumer;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import me.clip.placeholderapi.PlaceholderAPIPlugin;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
@@ -36,14 +38,14 @@ public final class Futures {
private Futures() {}
public static <T> void onMainThread(@NotNull final Plugin plugin,
public static <T> void onMainThread(@NotNull final PlaceholderAPIPlugin plugin,
@NotNull final CompletableFuture<T> future,
@NotNull final BiConsumer<T, Throwable> consumer) {
future.whenComplete((value, exception) -> {
if (Bukkit.isPrimaryThread()) {
consumer.accept(value, exception);
} else {
Bukkit.getScheduler().runTask(plugin, () -> consumer.accept(value, exception));
plugin.getScheduler().runTask(() -> consumer.accept(value, exception));
}
});
}

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -23,6 +23,7 @@ package me.clip.placeholderapi.util;
import java.util.Arrays;
import java.util.logging.Level;
import java.util.stream.Collectors;
import me.clip.placeholderapi.PlaceholderAPIPlugin;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
@@ -43,7 +44,7 @@ public final class Msg {
log(Level.WARNING, msg, args);
}
public static void warn(String msg, Throwable throwable, Object... args){
public static void warn(String msg, Throwable throwable, Object... args) {
PlaceholderAPIPlugin.getInstance().getLogger().log(Level.WARNING, String.format(msg, args), throwable);
}

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@@ -1,19 +1,22 @@
# PlaceholderAPI
# Version: @version@
# Version: ${version}
# Created by: extended_clip
# Contributors: https://github.com/PlaceholderAPI/PlaceholderAPI/graphs/contributors
# Issues: https://github.com/PlaceholderAPI/PlaceholderAPI/issues
# Expansions: https://api.extendedclip.com/all/
# Wiki: https://github.com/PlaceholderAPI/PlaceholderAPI/wiki
# Expansions: https://placeholderapi.com/ecloud
# Wiki: https://wiki.placeholderapi.com/
# Discord: https://helpch.at/discord
# If you're seeing performance issues with plugins that use component replacement, or things don't look quite right,
# switch to the replacer provided by adventure itself by changing use_adventure_provided_replacer to true
# No placeholders are provided with this plugin by default.
# Download placeholders: /papi ecloud
check_updates: true
cloud_enabled: true
cloud_sorting: "name"
cloud_allow_unverified_expansions: false
boolean:
'true': 'yes'
'false': 'no'
date_format: MM/dd/yy HH:mm:ss
detect_malicious_expansions: true
use_adventure_provided_replacer: false
debug: false

View File

@@ -1,5 +1,6 @@
name: PlaceholderAPI
main: "me.clip.placeholderapi.PlaceholderAPIPlugin"
folia-supported: true
version: ${version}
author: HelpChat
@@ -37,7 +38,6 @@ permissions:
placeholderapi.ecloud.info: true
placeholderapi.ecloud.list: true
placeholderapi.ecloud.clear: true
placeholderapi.ecloud.toggle: true
placeholderapi.ecloud.status: true
placeholderapi.ecloud.update: true
placeholderapi.ecloud.refresh: true
@@ -82,9 +82,6 @@ permissions:
placeholderapi.ecloud.clear:
default: "op"
description: "Allows you to clear the local eCloud expansion cache"
placeholderapi.ecloud.toggle:
default: "op"
description: "Allows you to toggle/enable/disable the eCloud manager"
placeholderapi.ecloud.status:
default: "op"
description: "Allows you to view the status of eCloud expansions"

View File

@@ -0,0 +1,184 @@
/*
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* PlaceholderAPI is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package me.clip.placeholderapi;
import me.clip.placeholderapi.replacer.ComponentReplacer;
import me.clip.placeholderapi.replacer.ExactReplacer;
import me.clip.placeholderapi.replacer.RelationalExactReplacer;
import me.clip.placeholderapi.replacer.Replacer;
import net.kyori.adventure.text.Component;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.stream.Collectors;
import static me.clip.placeholderapi.PlaceholderAPI.RELATIONAL_PLACEHOLDER_PATTERN;
public final class PAPIComponents {
private static final Replacer PERCENT_EXACT_REPLACER = new ExactReplacer('%', '%');
private static final Replacer BRACKET_EXACT_REPLACER = new ExactReplacer('{', '}');
private static final RelationalExactReplacer RELATIONAL_EXACT_REPLACER = new RelationalExactReplacer();
/**
* Translates all placeholders into their corresponding values.
* <br>The pattern of a valid placeholder is {@literal %<identifier>_<params>%}.
*
* @param player Player to parse the placeholders against
* @param component Component to set the placeholder values in
* @return Component containing all translated placeholders
*/
@NotNull
public static Component setPlaceholders(final OfflinePlayer player, @NotNull final Component component) {
if (PlaceholderAPIPlugin.getInstance().getPlaceholderAPIConfig().useAdventureProvidedReplacer()) {
return component.replaceText(config -> config.match(PlaceholderAPI.PLACEHOLDER_PATTERN).replacement((result, builder) ->
builder.content(PERCENT_EXACT_REPLACER.apply(result.group(), player, PlaceholderAPIPlugin.getInstance().getLocalExpansionManager()::getExpansion))));
}
return ComponentReplacer.replace(component, str -> PlaceholderAPI.setPlaceholders(player, str));
}
/**
* Translates all placeholders into their corresponding values.
* <br>The pattern of a valid placeholder is {@literal %<identifier>_<params>%}.
*
* @param player Player to parse the placeholders against
* @param components List of Components to set the placeholder values in
* @return List of Components containing all translated placeholders
*/
@NotNull
public static List<Component> setPlaceholders(final OfflinePlayer player, @NotNull final List<Component> components) {
return components.stream().map(component -> setPlaceholders(player, component)).collect(Collectors.toList());
}
/**
* Translates all placeholders into their corresponding values.
* <br>The pattern of a valid placeholder is {@literal %<identifier>_<params>%}.
*
* @param player Player to parse the placeholders against
* @param component Component to set the placeholder values in
* @return Component containing all translated placeholders
*/
@NotNull
public static Component setPlaceholders(final Player player, @NotNull final Component component) {
return setPlaceholders((OfflinePlayer) player, component);
}
/**
* Translates all placeholders into their corresponding values.
* <br>The pattern of a valid placeholder is {@literal %<identifier>_<params>%}.
*
* @param player Player to parse the placeholders against
* @param components List of Components to set the placeholder values in
* @return List of components containing all translated placeholders
*/
@NotNull
public static List<Component> setPlaceholders(final Player player, @NotNull final List<Component> components) {
return setPlaceholders((OfflinePlayer) player, components);
}
/**
* Translates all placeholders into their corresponding values.
* <br>The pattern of a valid placeholder is {@literal {<identifier>_<params>}}.
*
* @param player Player to parse the placeholders against
* @param component Component to set the placeholder values in
* @return Component containing all translated placeholders
*/
@NotNull
public static Component setBracketPlaceholders(final OfflinePlayer player, @NotNull final Component component) {
if (PlaceholderAPIPlugin.getInstance().getPlaceholderAPIConfig().useAdventureReplacer()) {
return component.replaceText(config -> config.match(PlaceholderAPI.BRACKET_PLACEHOLDER_PATTERN).replacement((result, builder) ->
builder.content(BRACKET_EXACT_REPLACER.apply(result.group(), player, PlaceholderAPIPlugin.getInstance().getLocalExpansionManager()::getExpansion))));
}
return ComponentReplacer.replace(component, str -> PlaceholderAPI.setBracketPlaceholders(player, str));
}
/**
* Translates all placeholders into their corresponding values.
* <br>The pattern of a valid placeholder is {@literal {<identifier>_<params>}}.
*
* @param player Player to parse the placeholders against
* @param components List of Components to set the placeholder values in
* @return List of Components containing all translated placeholders
*/
@NotNull
public static List<Component> setBracketPlaceholders(final OfflinePlayer player, @NotNull final List<Component> components) {
return components.stream().map(component -> setBracketPlaceholders(player, component)).collect(Collectors.toList());
}
/**
* Translates all placeholders into their corresponding values.
* <br>The pattern of a valid placeholder is {@literal {<identifier>_<params>}}.
*
* @param player Player to parse the placeholders against
* @param component Component to set the placeholder values in
* @return Component containing all translated placeholders
*/
@NotNull
public static Component setBracketPlaceholders(final Player player, @NotNull final Component component) {
return setBracketPlaceholders((OfflinePlayer) player, component);
}
/**
* Translates all placeholders into their corresponding values.
* <br>The pattern of a valid placeholder is {@literal {<identifier>_<params>}}.
*
* @param player Player to parse the placeholders against
* @param components List of Components to set the placeholder values in
* @return List of Components containing all translated placeholders
*/
@NotNull
public static List<Component> setBracketPlaceholders(final Player player, @NotNull final List<Component> components) {
return setBracketPlaceholders((OfflinePlayer) player, components);
}
/**
* set relational placeholders in the text specified placeholders are matched with the pattern
* {@literal %<rel_(identifier)_(params)>%} when set with this method
*
* @param one First player to compare
* @param two Second player to compare
* @param component Component to parse the placeholders in
* @return The Component containing the parsed relational placeholders
*/
public static Component setRelationalPlaceholders(Player one, Player two, Component component) {
//todo: custom replacer
return component.replaceText(config -> config.match(RELATIONAL_PLACEHOLDER_PATTERN).replacement((result, builder) ->
builder.content(RELATIONAL_EXACT_REPLACER.apply(result.group(2), one, two, PlaceholderAPIPlugin.getInstance().getLocalExpansionManager()::getExpansion))));
}
/**
* Translate placeholders in the provided List based on the relation of the two provided players.
* <br>The pattern of a valid placeholder is {@literal %rel_<identifier>_<param>%}.
*
* @param one Player to compare
* @param two Player to compare
* @param components List of Components to parse the placeholder values to
* @return The List of Components containing the parsed relational placeholders
*/
public static List<Component> setRelationalPlaceholders(Player one, Player two, List<Component> components) {
return components.stream().map(line -> setRelationalPlaceholders(one, two, line))
.collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,174 @@
package me.clip.placeholderapi.replacer;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.nbt.api.BinaryTagHolder;
import net.kyori.adventure.text.*;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.DataComponentValue;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.Style;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
public class ComponentReplacer {
@NotNull
public static Component replace(@NotNull final Component component, @NotNull final Function<String, String> replacer) {
return rebuild(component, replacer);
}
@NotNull
private static Component rebuild(@NotNull final Component component, @NotNull final Function<String, String> replacer) {
Component rebuilt;
if (component instanceof TextComponent) {
final TextComponent text = (TextComponent) component;
final String replaced = replacer.apply(text.content());
rebuilt = Component.text(replaced);
} else if (component instanceof TranslatableComponent) {
final TranslatableComponent translatable = (TranslatableComponent) component;
final List<Component> arguments = new ArrayList<>();
for (final ComponentLike arg : translatable.arguments()) {
arguments.add(rebuild(arg.asComponent(), replacer));
}
rebuilt = Component.translatable(translatable.key(), arguments);
} else if (component instanceof KeybindComponent) {
final KeybindComponent keybind = (KeybindComponent) component;
rebuilt = Component.keybind(keybind.keybind());
} else if (component instanceof ScoreComponent) {
final ScoreComponent score = (ScoreComponent) component;
rebuilt = Component.score(score.name(), score.objective());
} else if (component instanceof SelectorComponent) {
final SelectorComponent selector = (SelectorComponent) component;
rebuilt = Component.selector(selector.pattern());
} else {
rebuilt = Component.empty();
}
rebuilt = rebuilt.style(rebuildStyle(component.style(), replacer));
if (!component.children().isEmpty()) {
final List<Component> children = new ArrayList<>();
for (Component child : component.children()) {
children.add(rebuild(child, replacer));
}
rebuilt = rebuilt.children(children);
}
return rebuilt;
}
@NotNull
private static Style rebuildStyle(@NotNull final Style style, @NotNull final Function<String, String> replacer) {
final Style.Builder builder = style.toBuilder();
final ClickEvent click = style.clickEvent();
if (click != null) {
builder.clickEvent(rebuildClickEvent(click, replacer));
}
final HoverEvent<?> hover = style.hoverEvent();
if (hover != null) {
builder.hoverEvent(rebuildHoverEvent(hover, replacer));
}
return builder.build();
}
@NotNull
private static ClickEvent rebuildClickEvent(@NotNull final ClickEvent click, @NotNull final Function<String, String> replacer) {
final ClickEvent.Payload payload = click.payload();
if (!(payload instanceof ClickEvent.Payload.Text)) {
return click;
}
final String original = ((ClickEvent.Payload.Text) payload).value();
final String replaced = replacer.apply(original);
final ClickEvent.Action action = click.action();
switch (action) {
case OPEN_URL:
return ClickEvent.openUrl(replaced);
case OPEN_FILE:
return ClickEvent.openFile(replaced);
case RUN_COMMAND:
return ClickEvent.runCommand(replaced);
case SUGGEST_COMMAND:
return ClickEvent.suggestCommand(replaced);
case COPY_TO_CLIPBOARD:
return ClickEvent.copyToClipboard(replaced);
default:
return click;
}
}
@NotNull
private static HoverEvent<?> rebuildHoverEvent(@NotNull final HoverEvent<?> hover, @NotNull final Function<String, String> replacer) {
final Object value = hover.value();
if (value instanceof Component) {
final Component rebuilt = rebuild((Component) value, replacer);
return HoverEvent.showText(rebuilt);
}
if (value instanceof HoverEvent.ShowItem) {
return rebuildShowItem((HoverEvent.ShowItem) value, replacer);
}
if (value instanceof HoverEvent.ShowEntity) {
final HoverEvent.ShowEntity entity = (HoverEvent.ShowEntity) value;
Component rebuiltName = null;
if (entity.name() != null) {
rebuiltName = rebuild(entity.name(), replacer);
}
return HoverEvent.showEntity(entity.type(), entity.id(), rebuiltName);
}
return hover;
}
@NotNull
private static HoverEvent<?> rebuildShowItem(@NotNull final HoverEvent.ShowItem item, @NotNull final Function<String, String> replacer) {
final BinaryTagHolder nbt = item.nbt();
if (nbt != null && !nbt.string().isEmpty()) {
final String replaced = replacer.apply(nbt.string());
return HoverEvent.showItem(item.item(), item.count(), BinaryTagHolder.binaryTagHolder(replaced));
}
//I'm not 100% sure this is how we're meant to support data components but let's give it a go and see if it causes any issues :)
final Map<Key, DataComponentValue> components = item.dataComponents();
if (!components.isEmpty()) {
final Map<Key, DataComponentValue> rebuilt = new HashMap<>();
for (final Map.Entry<Key, DataComponentValue> entry : components.entrySet()) {
final DataComponentValue value = entry.getValue();
if (!(value instanceof BinaryTagHolder)) {
rebuilt.put(entry.getKey(), value);
continue;
}
rebuilt.put(entry.getKey(), BinaryTagHolder.binaryTagHolder(replacer.apply(((BinaryTagHolder) value).string())));
}
return HoverEvent.showItem(item.item(), item.count(), rebuilt);
}
return HoverEvent.showItem(item);
}
}

View File

@@ -0,0 +1,76 @@
/*
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* PlaceholderAPI is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package me.clip.placeholderapi.replacer;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import org.bukkit.OfflinePlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.function.Function;
import java.util.regex.Pattern;
public final class ExactReplacer implements Replacer {
private static final Pattern DELIMITER = Pattern.compile("_");
private final char start;
private final char end;
public ExactReplacer(final char start, final char end) {
this.start = start;
this.end = end;
}
@NotNull
@Override
public String apply(@NotNull String text, @Nullable final OfflinePlayer player,
@NotNull final Function<String, @Nullable PlaceholderExpansion> lookup) {
text = text.substring(1, text.length() - 1);
final String[] parts = DELIMITER.split(text);
final PlaceholderExpansion expansion;
if (parts.length == 0) {
expansion = lookup.apply(text);
} else {
expansion = lookup.apply(parts[0]);
}
if (expansion == null) {
return start + text + end;
}
final String params;
if (text.endsWith("_")) {
params = "";
} else {
params = text.substring(text.indexOf('_') + 1);
}
final String result = expansion.onRequest(player, params);
if (result == null) {
return start + text + end;
}
return result;
}
}

View File

@@ -0,0 +1,73 @@
/*
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* PlaceholderAPI is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package me.clip.placeholderapi.replacer;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import me.clip.placeholderapi.expansion.Relational;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.function.Function;
import java.util.regex.Pattern;
public final class RelationalExactReplacer {
private static final Pattern DELIMITER = Pattern.compile("_");
@NotNull
public String apply(@NotNull String text, @Nullable final Player player1,
@Nullable final Player player2, @NotNull final Function<String,
@Nullable PlaceholderExpansion> lookup) {
final String[] parts = DELIMITER.split(text);
final PlaceholderExpansion expansion;
if (parts.length == 0) {
expansion = lookup.apply(text);
} else {
expansion = lookup.apply(parts[0]);
}
if (expansion == null) {
return "%rel_" + text + '%';
}
if (!(expansion instanceof Relational)) {
return "%rel_" + text + '%';
}
final String params;
if (text.endsWith("_")) {
params = "";
} else {
params = text.substring(text.indexOf('_') + 1);
}
final String result = ((Relational) expansion).onPlaceholderRequest(player1, player2, params);
if (result == null) {
return "%rel_" + text + '%';
}
return result;
}
}

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@@ -2,7 +2,7 @@
* This file is part of PlaceholderAPI
*
* PlaceholderAPI
* Copyright (c) 2015 - 2021 PlaceholderAPI Team
* Copyright (c) 2015 - 2026 PlaceholderAPI Team
*
* PlaceholderAPI free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by