Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c8332906a | ||
|
|
b0acf41a11
|
||
|
|
8e84836e75
|
||
|
|
68dbd9406d
|
||
|
|
c5720d02f6 | ||
|
|
433d28e7fd | ||
|
|
65cab28e16 | ||
|
|
f8a1ee792f | ||
|
|
23deb1c7c1 | ||
|
|
95ee414738 | ||
|
|
79c4177af7 | ||
|
|
6804f900ef | ||
|
|
4cc64a8864 | ||
|
|
0a42960a4b | ||
|
|
39909a94ef | ||
|
|
6ca1283c63 | ||
|
|
06d854d0b8 | ||
|
|
b5b28e1372 | ||
|
|
3153ae3383 | ||
|
|
4645b8f601 | ||
|
|
a7adb984e9 | ||
|
|
548ed82ff4 | ||
|
|
c78a51ee07 | ||
|
|
641e7532bf | ||
|
|
03a5a4ab91 | ||
|
|
b20ca80350 | ||
|
|
566d0b2f18 | ||
|
|
9b44062fac | ||
|
|
80d184a9a3 | ||
|
|
af873a7d7f | ||
|
|
aa1f4d9321 | ||
|
|
f487493b73 | ||
|
|
ed97bf7bb6 | ||
|
|
004accd011 | ||
|
|
f4dca338fe | ||
|
|
a5f5ffe29a | ||
|
|
de11efac71 | ||
|
|
45a9e126fc | ||
|
|
655cf82b50 | ||
|
|
a9ff08fb30 | ||
|
|
9933b43254 | ||
|
|
4ed37c6a9d | ||
|
|
055ecedf22 | ||
|
|
955c7c2a1f | ||
|
|
c6ff5bb877 | ||
|
|
25d5a33d86 | ||
|
|
f322213986 | ||
|
|
ffb7d97b1d | ||
|
|
0e678dd0e0 | ||
|
|
25ef6fad6b | ||
|
|
ab16089659 | ||
|
|
3f5c99094d | ||
|
|
c15650847e | ||
|
|
a7770d9956 | ||
|
|
a51c7777a2 | ||
|
|
17c59ea00e | ||
|
|
347ab60b88 | ||
|
|
076d999005 | ||
|
|
d421ff2a85 | ||
|
|
494e18b128 | ||
|
|
a786471adb | ||
|
|
2d09397800 |
No files matched your search
@@ -1 +1,2 @@
|
||||
* text=auto eol=lf
|
||||
.gitea/CODEOWNERS linguist-detectable=false
|
||||
@@ -0,0 +1,3 @@
|
||||
# Pull requests request review from the eligible staff team(s).
|
||||
# One approval from either an Admin or Developer satisfies branch protection.
|
||||
* @trueog/Admins @trueog/Developers
|
||||
@@ -1,30 +0,0 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# Download code from repository
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Setup Java
|
||||
- name: Setup JDK
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '21'
|
||||
|
||||
# Build plugin
|
||||
- name: Build with Maven
|
||||
run: mvn --batch-mode --update-snapshots verify
|
||||
|
||||
# Upload release
|
||||
- name: Upload release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: gh release upload ${GITHUB_REF##*/} ./target/YamipaPlugin-*.jar
|
||||
@@ -1,138 +0,0 @@
|
||||
name: Tests
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 8
|
||||
continue-on-error: ${{ matrix.version == '1.20.4' }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
flavor: ['bukkit', 'spigot', 'paper', 'purpur']
|
||||
version: ['1.20.4', '1.19.4', '1.18.2', '1.17.1', '1.16.5']
|
||||
steps:
|
||||
# Download code from repository
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Setup Java (for building)
|
||||
- name: Setup JDK (for building)
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '21'
|
||||
|
||||
# Build plugin
|
||||
- name: Build with Maven
|
||||
run: mvn --batch-mode --update-snapshots verify
|
||||
|
||||
# Setup Java (for testing)
|
||||
- name: Setup JDK (for testing)
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: |
|
||||
${{
|
||||
startsWith(matrix.version, '1.16') && '11' ||
|
||||
startsWith(matrix.version, '1.17') && '16' ||
|
||||
startsWith(matrix.version, '1.18') && '17' ||
|
||||
startsWith(matrix.version, '1.19') && '17' ||
|
||||
'21'
|
||||
}}
|
||||
|
||||
# Setup Node.js
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
# Setup automata
|
||||
- name: Setup automata
|
||||
working-directory: ./automata
|
||||
run: npm install
|
||||
|
||||
# Setup server
|
||||
- name: Setup server
|
||||
env:
|
||||
FLAVOR: ${{ matrix.flavor }}
|
||||
VERSION: ${{ matrix.version }}
|
||||
GETBUKKIT_SUBDOMAIN: ${{ startsWith(matrix.version, '1.16') && 'cdn' || 'download' }}
|
||||
run: |
|
||||
if [ $FLAVOR == "bukkit" ]; then
|
||||
url="https://$GETBUKKIT_SUBDOMAIN.getbukkit.org/craftbukkit/craftbukkit-$VERSION.jar"
|
||||
elif [ $FLAVOR == "spigot" ]; then
|
||||
url="https://$GETBUKKIT_SUBDOMAIN.getbukkit.org/spigot/spigot-$VERSION.jar"
|
||||
elif [ $FLAVOR == "paper" ]; then
|
||||
build=$(curl "https://papermc.io/api/v2/projects/paper/versions/$VERSION" | jq '.builds[-1]')
|
||||
url="https://papermc.io/api/v2/projects/paper/versions/$VERSION/builds/$build/downloads/paper-$VERSION-$build.jar"
|
||||
elif [ $FLAVOR == "purpur" ]; then
|
||||
url="https://api.purpurmc.org/v2/purpur/$VERSION/latest/download"
|
||||
fi
|
||||
mkdir ./server && cd ./server
|
||||
wget -nv "$url" -O server.jar
|
||||
java -jar server.jar || true
|
||||
sed -i 's/eula=false/eula=true/g' eula.txt
|
||||
echo 'online-mode=false' >> server.properties
|
||||
echo 'enable-rcon=true' >> server.properties
|
||||
echo 'rcon.password=rcon' >> server.properties
|
||||
echo 'allow-nether=false' >> server.properties
|
||||
echo 'allow-end=false' >> server.properties
|
||||
echo 'generate-structures=false' >> server.properties
|
||||
echo 'level-type=flat' >> server.properties
|
||||
|
||||
# Install dependencies
|
||||
- name: Install plugin dependencies
|
||||
run: |
|
||||
url="https://ci.dmulloy2.net/job/ProtocolLib/lastSuccessfulBuild/artifact/build/libs/ProtocolLib.jar"
|
||||
mkdir -p ./server/plugins/ProtocolLib
|
||||
wget -q "$url" -O ./server/plugins/ProtocolLib.jar
|
||||
echo -e "global:\n auto updater:\n notify: false\n download: false" > ./server/plugins/ProtocolLib/config.yml
|
||||
|
||||
# Install plugin
|
||||
- name: Install plugin
|
||||
run: |
|
||||
mkdir -p ./server/plugins/YamipaPlugin/images
|
||||
cp ./target/YamipaPlugin-*.jar ./server/plugins/
|
||||
echo "verbose: true" > ./server/plugins/YamipaPlugin/config.yml
|
||||
for i in {1..5}; do
|
||||
wget -q "https://picsum.photos/400" -O "./server/plugins/YamipaPlugin/images/pic-$i.jpg"
|
||||
done
|
||||
|
||||
# Test plugin
|
||||
- name: Run plugin in server
|
||||
working-directory: ./server
|
||||
env:
|
||||
FLAVOR: ${{ matrix.flavor }}
|
||||
run: |
|
||||
(cd ../automata && npm run start) &
|
||||
timeout -s SIGINT -k 110s 90s java -DIReallyKnowWhatIAmDoingISwear -jar server.jar nogui | tee server.log || true
|
||||
if ! grep -Fq '[YamipaPlugin] ' server.log; then
|
||||
echo "Plugin did not load"
|
||||
exit 1
|
||||
fi
|
||||
if ! grep -Fq '[YamipaPlugin] Running on VERBOSE mode' server.log; then
|
||||
echo "Plugin did not enabled verbose mode"
|
||||
exit 1
|
||||
fi
|
||||
if ! grep -Fq '[YamipaPlugin] [ImageStorage] Found 5 file(s) in images directory' server.log; then
|
||||
echo "Plugin did not read image directory"
|
||||
exit 1
|
||||
fi
|
||||
if ! grep -Fq '[YamipaPlugin] [ImageCommandBridge] Fixed command permissions' server.log; then
|
||||
echo "Plugin did not fixed command permissions"
|
||||
exit 1
|
||||
fi
|
||||
if ! grep -Fq '[YamipaPlugin] [FakeImage] Created FakeImage' server.log; then
|
||||
echo "Plugin did not place the fake image"
|
||||
exit 1
|
||||
fi
|
||||
if ! grep -Fq '[YamipaPlugin] [FakeImage] Invalidated FakeImage' server.log; then
|
||||
echo "Plugin did not remove the fake image"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$FLAVOR" != "bukkit" ] && grep -iFq 'exception' server.log; then
|
||||
echo "Server threw an exception"
|
||||
exit 1
|
||||
fi
|
||||
+6
-3
@@ -1,4 +1,7 @@
|
||||
/.idea
|
||||
/target
|
||||
/*.iml
|
||||
.gradle/
|
||||
build/
|
||||
bin/
|
||||
.project
|
||||
.classpath
|
||||
.settings/
|
||||
node_modules
|
||||
@@ -0,0 +1,3 @@
|
||||
[submodule "libs/Utilities-OG"]
|
||||
path = libs/Utilities-OG
|
||||
url = https://git.trueog.net/trueog/Utilities-OG
|
||||
@@ -5,6 +5,8 @@
|
||||
[](https://bstats.org/plugin/bukkit/Yamipa/10243)
|
||||
[](LICENSE)
|
||||
|
||||
Yamipa-OG is a fork of Yamipa maintained by [TrueOG Network](https://git.trueog.net/trueog/Yamipa-OG) for Purpur 1.19.4.
|
||||
|
||||
Yamipa is a Spigot plugin that allows players to place images (even **animated**!) on any surface in your Minecraft server
|
||||
without having to install any local client mod.
|
||||
|
||||
@@ -25,7 +27,7 @@ Download the JAR file for the [latest release](https://github.com/josemmo/yamipa
|
||||
Before installing Yamipa make sure you meet the following requirements:
|
||||
|
||||
- CraftBukkit, Spigot or PaperMC 1.16 or higher
|
||||
- [ProtocolLib](https://www.spigotmc.org/resources/protocollib.1997/) v5.0.0 or higher
|
||||
- [ProtocolLib](https://www.spigotmc.org/resources/protocollib.1997/) v5.3.0 or higher
|
||||
|
||||
Here are the Minecraft distributions where Yamipa should be able to run:
|
||||
| Minecraft version | CraftBukkit | Spigot | PaperMC |
|
||||
@@ -35,6 +37,7 @@ Here are the Minecraft distributions where Yamipa should be able to run:
|
||||
| 1.18 & 1.18.x | ✅ | ✅ | ✅ |
|
||||
| 1.19 & 1.19.x | ✅ | ✅ | ✅ |
|
||||
| 1.20 & 1.20.x | ✅ | ✅ | ✅ |
|
||||
| 1.21 & 1.21.x | ✅ | ✅ | ✅ |
|
||||
|
||||
## Configuration
|
||||
Yamipa is ready-to-go right out of the box. By default, it creates the following files and directories under the
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
import { placeBlockOnTheFloor, startBot } from './src/bot.js'
|
||||
import { wait } from './src/common.js'
|
||||
import { getRconClient, waitForServer } from './src/rcon.js'
|
||||
|
||||
(async() => {
|
||||
console.log('[AUTOMATA] Waiting for Minecraft server to be ready...')
|
||||
await waitForServer()
|
||||
|
||||
console.log('[AUTOMATA] Logging in as test account...')
|
||||
const bot = await startBot()
|
||||
await bot.look(0, -Math.PI/2)
|
||||
|
||||
console.log('[AUTOMATA] Making test account an OP...')
|
||||
const conn = await getRconClient()
|
||||
await conn.send('op test')
|
||||
await conn.send('gamemode creative test')
|
||||
await wait(2000)
|
||||
|
||||
console.log('[AUTOMATA] Preparing test account...')
|
||||
bot.chat('/clear @p')
|
||||
await wait(2000)
|
||||
bot.chat('/give @p minecraft:dirt')
|
||||
await wait(2000)
|
||||
|
||||
console.log('[AUTOMATA] Placing image on the floor...')
|
||||
bot.chat('/image place pic-1.jpg 4 4')
|
||||
await wait(2000)
|
||||
placeBlockOnTheFloor(bot)
|
||||
await wait(2000)
|
||||
|
||||
console.log('[AUTOMATA] Removing image from floor...')
|
||||
bot.chat('/image remove')
|
||||
await wait(2000)
|
||||
placeBlockOnTheFloor(bot)
|
||||
await wait(2000)
|
||||
|
||||
console.log('[AUTOMATA] Logging off...')
|
||||
bot.end()
|
||||
|
||||
console.log('[AUTOMATA] Stopping server...')
|
||||
await conn.send('stop')
|
||||
await conn.end()
|
||||
|
||||
console.log('[AUTOMATA] Done!')
|
||||
})()
|
||||
Generated
-1672
File diff suppressed because it is too large.
Load diff
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"name": "@yamipa/automata",
|
||||
"description": "Minecraft Automata for testing Yamipa",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"mineflayer": "^4.20.1",
|
||||
"rcon-client": "^4.2.4"
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
import mineflayer from 'mineflayer'
|
||||
|
||||
/**
|
||||
* @return {Promise<mineflayer.Bot>} Bot instance
|
||||
*/
|
||||
export function startBot() {
|
||||
return new Promise(resolve => {
|
||||
const bot = mineflayer.createBot({
|
||||
host: '127.0.0.1',
|
||||
username: 'test'
|
||||
})
|
||||
bot.once('spawn', () => resolve(bot))
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {mineflayer.Bot} bot Bot instance
|
||||
*/
|
||||
export function placeBlockOnTheFloor(bot) {
|
||||
bot._client.write('block_place', {
|
||||
location: bot.entity.position.offset(0, -1, 0),
|
||||
direction: 1,
|
||||
hand: 0,
|
||||
cursorX: 0,
|
||||
cursorY: 0,
|
||||
cursorZ: 0
|
||||
})
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
export function wait(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import { Rcon } from 'rcon-client'
|
||||
import { wait } from './common.js'
|
||||
|
||||
const CONNECTION = {
|
||||
host: '127.0.0.1',
|
||||
port: 25575,
|
||||
password: 'rcon',
|
||||
timeout: 1000*20
|
||||
}
|
||||
|
||||
export async function waitForServer() {
|
||||
while (true) {
|
||||
try {
|
||||
const rcon = await Rcon.connect(CONNECTION)
|
||||
await rcon.end()
|
||||
break
|
||||
} catch (_) {
|
||||
// Not ready yet
|
||||
}
|
||||
wait(2000)
|
||||
}
|
||||
}
|
||||
|
||||
export function getRconClient() {
|
||||
return Rcon.connect(CONNECTION)
|
||||
}
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
# Fetch all submodule content.
|
||||
git submodule update --force --recursive --init
|
||||
@@ -0,0 +1,161 @@
|
||||
/* This is free and unencumbered software released into the public domain */
|
||||
|
||||
import org.gradle.kotlin.dsl.provideDelegate
|
||||
|
||||
/* ------------------------------ Plugins ------------------------------ */
|
||||
plugins {
|
||||
id("java") // Import Java plugin.
|
||||
id("java-library") // Import Java Library plugin.
|
||||
id("com.diffplug.spotless") version "8.1.0" // Import Spotless plugin.
|
||||
id("com.gradleup.shadow") version "8.3.9" // Import Shadow plugin.
|
||||
id("checkstyle") // Import Checkstyle plugin.
|
||||
eclipse // Import Eclipse plugin.
|
||||
kotlin("jvm") version "2.1.21" // Import Kotlin JVM plugin.
|
||||
}
|
||||
|
||||
extra["kotlinAttribute"] = Attribute.of("kotlin-tag", Boolean::class.javaObjectType)
|
||||
|
||||
val kotlinAttribute: Attribute<Boolean> by rootProject.extra
|
||||
|
||||
/* --------------------------- JDK / Kotlin ---------------------------- */
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_17 // Compile with JDK 17 compatibility.
|
||||
toolchain { // Select Java toolchain.
|
||||
languageVersion.set(JavaLanguageVersion.of(17)) // Use JDK 17.
|
||||
vendor.set(JvmVendorSpec.GRAAL_VM) // Use GraalVM CE.
|
||||
}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvmToolchain {
|
||||
languageVersion.set(JavaLanguageVersion.of(17))
|
||||
vendor.set(JvmVendorSpec.GRAAL_VM)
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------------------- Metadata ------------------------------ */
|
||||
group = "io.josemmo.bukkit.plugin" // Declare bundle identifier.
|
||||
|
||||
version = "1.3.3" // Declare plugin version (will be in .jar).
|
||||
|
||||
val apiVersion = "1.19" // Declare minecraft server target version.
|
||||
|
||||
/* ----------------------------- Resources ----------------------------- */
|
||||
tasks.named<ProcessResources>("processResources") {
|
||||
val props = mapOf("version" to version, "apiVersion" to apiVersion)
|
||||
inputs.properties(props) // Indicates to rerun if version changes.
|
||||
filesMatching("plugin.yml") { expand(props) }
|
||||
from("LICENSE") { into("/") } // Bundle licenses into jarfiles.
|
||||
}
|
||||
|
||||
/* ---------------------------- Repos ---------------------------------- */
|
||||
repositories {
|
||||
mavenCentral() // Import the Maven Central Maven Repository.
|
||||
gradlePluginPortal() // Import the Gradle Plugin Portal Maven Repository.
|
||||
maven { url = uri("https://repo.purpurmc.org/snapshots") } // Import the PurpurMC Maven Repository.
|
||||
maven { url = uri("https://hub.spigotmc.org/nexus/content/groups/public/") }
|
||||
maven { url = uri("https://libraries.minecraft.net") }
|
||||
maven { url = uri("https://maven.enginehub.org/repo/") }
|
||||
maven { url = uri("https://jitpack.io") }
|
||||
maven { url = uri("file://${System.getProperty("user.home")}/.m2/repository") }
|
||||
System.getProperty("SELF_MAVEN_LOCAL_REPO")?.let { // TrueOG Bootstrap mavenLocal().
|
||||
val dir = file(it)
|
||||
if (dir.isDirectory) {
|
||||
println("Using SELF_MAVEN_LOCAL_REPO at: $it")
|
||||
maven { url = uri("file://${dir.absolutePath}") }
|
||||
} else {
|
||||
logger.error("TrueOG Bootstrap not found, defaulting to ~/.m2 for mavenLocal()")
|
||||
mavenLocal()
|
||||
}
|
||||
} ?: logger.error("TrueOG Bootstrap not found, defaulting to ~/.m2 for mavenLocal()")
|
||||
}
|
||||
|
||||
/* ---------------------- Java project deps ---------------------------- */
|
||||
dependencies {
|
||||
compileOnly("org.purpurmc.purpur:purpur-api:1.19.4-R0.1-SNAPSHOT") // Declare Purpur API version to be packaged.
|
||||
compileOnlyApi(project(":libs:Utilities-OG")) // Import TrueOG Network Utilities-OG Java API (from source).
|
||||
compileOnly(files("libs/ProtocolLib-5.0.jar")) // Import Legacy ProtocolLib API.
|
||||
compileOnly("com.mojang:brigadier:1.0.18")
|
||||
compileOnly("net.luckperms:api:5.5")
|
||||
compileOnly("com.github.ElgarL:groupmanager:3.2")
|
||||
compileOnly("com.sk89q.worldedit:worldedit-bukkit:7.3.0-SNAPSHOT") // Import WorldEdit API.
|
||||
compileOnly(
|
||||
"com.sk89q.worldguard:worldguard-bukkit:7.0.9"
|
||||
) { // Import WorldGuard but without its bundled WorldEdit.
|
||||
exclude(group = "com.sk89q.worldedit")
|
||||
}
|
||||
compileOnly("com.github.GriefPrevention:GriefPrevention:16.18.4")
|
||||
compileOnly("com.github.TownyAdvanced.Towny:towny:0.101.2.0")
|
||||
compileOnly("com.github.Angeschossen:LandsAPI:7.15.20")
|
||||
compileOnly("org.jetbrains:annotations:26.0.2")
|
||||
}
|
||||
|
||||
apply(from = "eclipse.gradle.kts") // Import eclipse classpath support script.
|
||||
|
||||
/* ---------------------- Reproducible jars ---------------------------- */
|
||||
tasks.withType<AbstractArchiveTask>().configureEach { // Ensure reproducible .jars
|
||||
isPreserveFileTimestamps = false
|
||||
isReproducibleFileOrder = true
|
||||
}
|
||||
|
||||
/* ----------------------------- Shadow -------------------------------- */
|
||||
tasks.shadowJar {
|
||||
exclude("io.github.miniplaceholders.*") // Exclude the MiniPlaceholders package from being shadowed.
|
||||
exclude("META-INF/**")
|
||||
exclude("LICENSE")
|
||||
archiveClassifier.set("") // Use empty string instead of null.
|
||||
archiveFileName.set("Yamipa-OG-${project.version}.jar")
|
||||
}
|
||||
|
||||
tasks.jar {
|
||||
archiveClassifier.set("part") // Applies to root jarfile only.
|
||||
archiveFileName.set("Yamipa-OG-${project.version}-part.jar")
|
||||
}
|
||||
|
||||
tasks.build { dependsOn(tasks.spotlessApply, tasks.shadowJar) } // Build depends on spotless and shadow.
|
||||
|
||||
/* --------------------------- Javac opts ------------------------------- */
|
||||
tasks.withType<JavaCompile>().configureEach {
|
||||
options.compilerArgs.add("-parameters") // Enable reflection for java code.
|
||||
options.isFork = true // Run javac in its own process.
|
||||
options.compilerArgs.add("-Xlint:deprecation") // Trigger deprecation warning messages.
|
||||
options.encoding = "UTF-8" // Use UTF-8 file encoding.
|
||||
}
|
||||
|
||||
/* ----------------------------- Auto Formatting ------------------------ */
|
||||
spotless {
|
||||
java {
|
||||
eclipse().configFile("config/formatter/eclipse-java-formatter.xml") // Eclipse java formatting.
|
||||
leadingTabsToSpaces() // Convert leftover leading tabs to spaces.
|
||||
removeUnusedImports() // Remove imports that aren't being called.
|
||||
}
|
||||
kotlinGradle {
|
||||
ktfmt().kotlinlangStyle().configure { it.setMaxWidth(120) } // JetBrains Kotlin formatting.
|
||||
target("build.gradle.kts", "settings.gradle.kts") // Gradle files to format.
|
||||
}
|
||||
}
|
||||
|
||||
checkstyle {
|
||||
toolVersion = "10.18.1" // Declare checkstyle version to use.
|
||||
configFile = file("config/checkstyle/checkstyle.xml") // Point checkstyle to config file.
|
||||
isIgnoreFailures = true // Don't fail the build if checkstyle does not pass.
|
||||
isShowViolations = true // Show the violations in any IDE with the checkstyle plugin.
|
||||
}
|
||||
|
||||
tasks.named("compileJava") {
|
||||
dependsOn("spotlessApply") // Run spotless before compiling with the JDK.
|
||||
}
|
||||
|
||||
tasks.named("spotlessCheck") {
|
||||
dependsOn("spotlessApply") // Run spotless before checking if spotless ran.
|
||||
}
|
||||
|
||||
/* ------------------------------ Eclipse SHIM ------------------------- */
|
||||
|
||||
// This can't be put in eclipse.gradle.kts because Gradle is weird.
|
||||
subprojects {
|
||||
apply(plugin = "java-library")
|
||||
apply(plugin = "eclipse")
|
||||
eclipse.project.name = "${project.name}-${rootProject.name}"
|
||||
tasks.withType<Jar>().configureEach { archiveBaseName.set("${project.name}-${rootProject.name}") }
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE module PUBLIC
|
||||
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
|
||||
"https://checkstyle.org/dtds/configuration_1_3.dtd">
|
||||
<module name="Checker">
|
||||
|
||||
<module name="FileTabCharacter">
|
||||
<property name="eachLine" value="true"/>
|
||||
<property name="severity" value="error"/>
|
||||
</module>
|
||||
|
||||
<module name="TreeWalker">
|
||||
<module name="FinalLocalVariable"/>
|
||||
<module name="AvoidStarImport"/>
|
||||
<module name="Indentation">
|
||||
<property name="severity" value="ignore"/>
|
||||
</module>
|
||||
</module>
|
||||
|
||||
<module name="RegexpSingleline">
|
||||
<property name="format" value="\borg\.junit\.jupiter\.api\.Assertions\.fail\s*\(\s*"Not yet implemented"\s*\)\s*;"/>
|
||||
<property name="message" value="Remove placeholder fail() and implement the test."/>
|
||||
<property name="ignoreCase" value="true"/>
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
<module name="RegexpSingleline">
|
||||
<property name="format" value="(?<!\w)fail\s*\(\s*"Not yet implemented"\s*\)\s*;"/>
|
||||
<property name="message" value="Remove placeholder fail() and implement the test."/>
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
</module>
|
||||
|
||||
@@ -0,0 +1,404 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<profiles version="23">
|
||||
<profile kind="CodeFormatterProfile" name="TrueOG Network Formatting" version="23">
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indentation.size" value="4"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_with_spaces" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation" value="2"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_before_code_block" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_switch_case_expressions" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.enabling_tag" value="@formatter:on"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_record_components" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_multiplicative_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_logical_operator" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_annotation_declaration_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_record_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_multiplicative_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_abstract_method" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_enum_constant_declaration_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_variable_declarations_on_columns" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiplicative_operator" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_anonymous_type_declaration_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_switch_case_expressions" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_shift_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block" value="next_line_on_wrap"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_code_block" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_bitwise_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_type_parameters" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_loops" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_simple_for_body_on_same_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_switch_case_arrow_operator" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_enum_constant" value="49"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.text_block_indentation" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_type_members_on_columns" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_assignment" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_module_statements" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.align_tags_names_descriptions" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_if_then_body_block_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_permitted_types" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" value="next_line_on_wrap"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression_chain" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_header" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_type_annotations" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_assertion_message_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_method_declaration" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines" value="2147483647"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_bitwise_operator" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration" value="next_line_on_wrap"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_resources_in_try" value="80"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_source_code" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_not_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_html" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_if" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_empty_lines" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_type_arguments" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_package" value="49"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_label" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_case" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_permitted_types_in_type_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_record_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_bitwise_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.javadoc_do_not_separate_block_tags" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.indent_tag_description" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_record_constructor" value="next_line_on_wrap"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_string_concatenation" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" value="next_line_on_wrap"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_shift_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_shift_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_simple_do_while_body_on_same_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_record_components" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_additive_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_simple_getter_setter_on_one_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case_after_arrow" value="next_line_on_wrap"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_string_concatenation" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_record_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_relational_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_logical_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_record_declaration" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_default" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.disabling_tag" value="@formatter:off"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_method_body" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_case" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_switch_body_block_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value="next_line_on_wrap"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_switch_case_with_arrow" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.align_tags_descriptions_grouped" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="80"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.use_on_off_tags" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_method_body_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_loop_body_block_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value="next_line_on_wrap"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_type_declaration_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_additive_operator" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_record_constructor" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_relational_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_record_declaration_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value="next_line_on_wrap"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_lambda_body" value="next_line_on_wrap"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.compact_else_if" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_parameter" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_relational_operator" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_arrows_in_switch_on_columns" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_additive_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_string_concatenation" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_line_comments" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_selector_in_method_invocation_on_expression_first_line" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_record_declaration" value="next_line_on_wrap"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_switch_case_with_arrow_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_switch_case_with_colon" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_after_code_block" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value="80"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_type" value="49"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_local_variable" value="49"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value="next_line_on_wrap"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_default" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_between_different_tags" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_additive_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_field" value="49"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_conditional_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.join_line_comments" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_shift_operator" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_code_block_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_record_components" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.tabulation.size" value="4"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_bitwise_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_record_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_assignment_operator" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch" value="next_line_on_wrap"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_switch_case_with_arrow" value="20"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_lambda_body_block_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_method" value="49"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_record_constructor_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_record_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_assertion_message" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_logical_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_record_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_relational_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_block_comments" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_last_class_body_declaration" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_simple_while_body_on_same_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_logical_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_statement_group_in_switch" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_permitted_types" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_enum_declaration_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value="next_line_on_wrap"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value="next_line_on_wrap"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_multiplicative_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_code_block" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="space"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_string_concatenation" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.lineSplit" value="120"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/>
|
||||
</profile>
|
||||
</profiles>
|
||||
@@ -0,0 +1,151 @@
|
||||
/* This is free and unencumbered software released into the public domain */
|
||||
|
||||
import org.gradle.plugins.ide.eclipse.model.EclipseModel
|
||||
import java.nio.file.Files
|
||||
import javax.xml.parsers.DocumentBuilderFactory
|
||||
import javax.xml.transform.OutputKeys
|
||||
import javax.xml.transform.TransformerFactory
|
||||
import javax.xml.transform.dom.DOMSource
|
||||
import javax.xml.transform.stream.StreamResult
|
||||
|
||||
val kotlinAttribute: Attribute<Boolean> by rootProject.extra
|
||||
|
||||
val kotlinPluginProjects = configurations
|
||||
.getByName("compileClasspath")
|
||||
.allDependencies
|
||||
.filterIsInstance<ProjectDependency>()
|
||||
.filter { it -> it.attributes.getAttribute(kotlinAttribute) != null }
|
||||
.map { it.path }
|
||||
kotlinPluginProjects.forEach { evaluationDependsOn(it) }
|
||||
|
||||
val ideLibs: Configuration by
|
||||
configurations.creating {
|
||||
isCanBeResolved = true
|
||||
isCanBeConsumed = false
|
||||
}
|
||||
|
||||
configure<EclipseModel> {
|
||||
project {
|
||||
name = "Template-OG-Plugin"
|
||||
}
|
||||
classpath {
|
||||
plusConfigurations.add(ideLibs)
|
||||
}
|
||||
}
|
||||
|
||||
val ideLibDir = layout.buildDirectory.dir("ide-libs")
|
||||
val hashRegex = Regex("-[0-9a-fA-F]{10}(?=\\.jar$)") // Black magic.
|
||||
|
||||
val copyTasks = mutableListOf<TaskProvider<Copy>>()
|
||||
|
||||
kotlinPluginProjects.forEach { path ->
|
||||
val sub = project(path)
|
||||
val shadowJarProv = sub.tasks.named("shadowJar")
|
||||
val copyTask =
|
||||
tasks.register<Copy>("ideCopy${sub.name.replaceFirstChar(Char::titlecase)}") {
|
||||
dependsOn(shadowJarProv)
|
||||
from(shadowJarProv)
|
||||
into(ideLibDir)
|
||||
rename { it.replace(hashRegex, "") } // Remove git commit hash from jarfile.
|
||||
}
|
||||
copyTasks += copyTask
|
||||
}
|
||||
|
||||
/* Ensure the jars exist before .classpath is generated */
|
||||
tasks.named("eclipseClasspath").configure { dependsOn(copyTasks) }
|
||||
|
||||
/* Supply those jars to the ideLibs configuration (after evaluation) */
|
||||
afterEvaluate { dependencies { add("ideLibs", fileTree(ideLibDir) { include("*.jar") }) } }
|
||||
|
||||
val injectIdeLibs =
|
||||
tasks.register("injectIdeLibs") {
|
||||
dependsOn("eclipse") // run after all eclipse files are generated
|
||||
doLast {
|
||||
val cpFile = file(".classpath")
|
||||
if (!cpFile.exists()) return@doLast
|
||||
|
||||
val jars = fileTree(ideLibDir.get()) { include("*.jar") }.files.sortedBy { it.name }
|
||||
if (jars.isEmpty()) return@doLast
|
||||
|
||||
// Parse DOM
|
||||
val dbf = DocumentBuilderFactory.newInstance()
|
||||
val doc = dbf.newDocumentBuilder().parse(cpFile)
|
||||
val root = doc.documentElement
|
||||
|
||||
// Helper to see if an entry already exists
|
||||
fun exists(path: String): Boolean =
|
||||
root.getElementsByTagName("classpathentry").let { list ->
|
||||
(0 until list.length).any { i ->
|
||||
val n = list.item(i)
|
||||
val kind = n.attributes?.getNamedItem("kind")?.nodeValue
|
||||
val p = n.attributes?.getNamedItem("path")?.nodeValue
|
||||
kind == "lib" && p == path
|
||||
}
|
||||
}
|
||||
|
||||
// Remove any entry that points to ide-libs dir (folder or jars) to avoid dupes
|
||||
val toRemove = mutableListOf<org.w3c.dom.Node>()
|
||||
val list = root.getElementsByTagName("classpathentry")
|
||||
val dirPath = ideLibDir.get().asFile.absolutePath
|
||||
for (i in 0 until list.length) {
|
||||
val n = list.item(i)
|
||||
val kind = n.attributes?.getNamedItem("kind")?.nodeValue
|
||||
val p = n.attributes?.getNamedItem("path")?.nodeValue ?: ""
|
||||
if (kind == "lib" && (p == dirPath || p.startsWith("$dirPath/"))) {
|
||||
toRemove += n
|
||||
}
|
||||
}
|
||||
toRemove.forEach { root.removeChild(it) }
|
||||
|
||||
// Append our jar entries LAST
|
||||
jars.forEach { f ->
|
||||
val abs = f.absolutePath
|
||||
if (!exists(abs)) {
|
||||
val entry = doc.createElement("classpathentry")
|
||||
entry.setAttribute("kind", "lib")
|
||||
entry.setAttribute("path", abs)
|
||||
root.appendChild(entry)
|
||||
}
|
||||
}
|
||||
|
||||
// Write back pretty
|
||||
val tf =
|
||||
TransformerFactory.newInstance().newTransformer().apply {
|
||||
setOutputProperty(OutputKeys.INDENT, "yes")
|
||||
setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "1")
|
||||
setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no")
|
||||
setOutputProperty(OutputKeys.ENCODING, "UTF-8")
|
||||
}
|
||||
Files.newBufferedWriter(cpFile.toPath()).use { w -> tf.transform(DOMSource(doc), StreamResult(w)) }
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named("eclipse").configure { finalizedBy(injectIdeLibs) }
|
||||
|
||||
val eclipseModel = extensions.getByType<EclipseModel>()
|
||||
|
||||
fun Project.addResolvableEclipseConfigs() {
|
||||
val jarAttr =
|
||||
objects.named(org.gradle.api.attributes.LibraryElements::class, org.gradle.api.attributes.LibraryElements.JAR)
|
||||
val apiAttr = objects.named(org.gradle.api.attributes.Usage::class, org.gradle.api.attributes.Usage.JAVA_API)
|
||||
|
||||
val compileOnlyRes =
|
||||
configurations.create("eclipseCompileOnly") {
|
||||
extendsFrom(configurations.getByName("compileOnly"))
|
||||
isCanBeResolved = true
|
||||
isCanBeConsumed = false
|
||||
attributes.attribute(org.gradle.api.attributes.Usage.USAGE_ATTRIBUTE, apiAttr)
|
||||
attributes.attribute(org.gradle.api.attributes.LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, jarAttr)
|
||||
}
|
||||
val compileOnlyApiRes =
|
||||
configurations.create("eclipseCompileOnlyApi") {
|
||||
extendsFrom(configurations.getByName("compileOnlyApi"))
|
||||
isCanBeResolved = true
|
||||
isCanBeConsumed = false
|
||||
attributes.attribute(org.gradle.api.attributes.Usage.USAGE_ATTRIBUTE, apiAttr)
|
||||
attributes.attribute(org.gradle.api.attributes.LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, jarAttr)
|
||||
}
|
||||
eclipseModel.classpath.plusConfigurations.addAll(listOf(compileOnlyRes, compileOnlyApiRes))
|
||||
}
|
||||
|
||||
addResolvableEclipseConfigs()
|
||||
Vendored
BIN
Binary file not shown.
+7
@@ -0,0 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
@@ -0,0 +1,251 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# 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.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * 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:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH="\\\"\\\""
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
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=SC2039,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=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
"$@"
|
||||
|
||||
# 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.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
Vendored
+94
@@ -0,0 +1,94 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@@ -0,0 +1,339 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is 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 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program 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, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
||||
Binary file not shown.
Submodule
+1
Submodule libs/Utilities-OG added at 8b5c86cc9c.
@@ -1,191 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>io.josemmo.bukkit.plugin</groupId>
|
||||
<artifactId>YamipaPlugin</artifactId>
|
||||
<version>1.3.2-SNAPSHOT-b2</version>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>8</maven.compiler.source>
|
||||
<maven.compiler.target>8</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>spigot-repo</id>
|
||||
<url>https://hub.spigotmc.org/nexus/content/groups/public/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>dmulloy2-repo</id>
|
||||
<url>https://repo.dmulloy2.net/nexus/repository/public/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>minecraft-libraries</id>
|
||||
<url>https://libraries.minecraft.net</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>enginehub-repo</id>
|
||||
<url>https://maven.enginehub.org/repo/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>jitpack.io</id>
|
||||
<url>https://jitpack.io</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<!-- https://hub.spigotmc.org/nexus/content/groups/public/org/spigotmc/spigot-api/ -->
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.20.6-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- https://repo.dmulloy2.net/service/rest/repository/browse/public/com/comphenix/protocol/ProtocolLib/ -->
|
||||
<dependency>
|
||||
<groupId>com.comphenix.protocol</groupId>
|
||||
<artifactId>ProtocolLib</artifactId>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- https://github.com/Mojang/brigadier/releases -->
|
||||
<dependency>
|
||||
<groupId>com.mojang</groupId>
|
||||
<artifactId>brigadier</artifactId>
|
||||
<version>1.0.18</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- https://central.sonatype.com/artifact/org.bstats/bstats-bukkit -->
|
||||
<dependency>
|
||||
<groupId>org.bstats</groupId>
|
||||
<artifactId>bstats-bukkit</artifactId>
|
||||
<version>3.0.2</version>
|
||||
</dependency>
|
||||
|
||||
<!-- https://central.sonatype.com/artifact/net.luckperms/api -->
|
||||
<dependency>
|
||||
<groupId>net.luckperms</groupId>
|
||||
<artifactId>api</artifactId>
|
||||
<version>5.4</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- https://jitpack.io/com/github/ElgarL/groupmanager/ -->
|
||||
<dependency>
|
||||
<groupId>com.github.ElgarL</groupId>
|
||||
<artifactId>groupmanager</artifactId>
|
||||
<version>3.2</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- https://maven.enginehub.org/artifactory/repo/com/sk89q/worldguard/worldguard-bukkit/ -->
|
||||
<dependency>
|
||||
<groupId>com.sk89q.worldguard</groupId>
|
||||
<artifactId>worldguard-bukkit</artifactId>
|
||||
<version>7.0.9</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- https://jitpack.io/com/github/TechFortress/GriefPrevention/ -->
|
||||
<dependency>
|
||||
<groupId>com.github.TechFortress</groupId>
|
||||
<artifactId>GriefPrevention</artifactId>
|
||||
<version>16.18.3</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- https://jitpack.io/com/github/TownyAdvanced/towny/ -->
|
||||
<dependency>
|
||||
<groupId>com.github.TownyAdvanced</groupId>
|
||||
<artifactId>towny</artifactId>
|
||||
<version>0.100.2.11</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- https://jitpack.io/com/github/Angeschossen/LandsAPI/ -->
|
||||
<dependency>
|
||||
<groupId>com.github.angeschossen</groupId>
|
||||
<artifactId>LandsAPI</artifactId>
|
||||
<version>7.1.12</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- https://central.sonatype.com/artifact/org.jetbrains/annotations -->
|
||||
<dependency>
|
||||
<groupId>org.jetbrains</groupId>
|
||||
<artifactId>annotations</artifactId>
|
||||
<version>24.1.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>${basedir}/src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
<finalName>${project.name}-${project.version}</finalName>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.5.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>shade</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||
<filters>
|
||||
<filter>
|
||||
<artifact>*:*</artifact>
|
||||
<excludes>
|
||||
<exclude>META-INF/**</exclude>
|
||||
<exclude>LICENSE</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
<relocations>
|
||||
<relocation>
|
||||
<pattern>org.bstats</pattern>
|
||||
<shadedPattern>${project.groupId}.bstats</shadedPattern>
|
||||
</relocation>
|
||||
</relocations>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-antrun-plugin</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>run</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<target>
|
||||
<delete file="${project.build.directory}/${project.build.finalName}-shaded.jar" />
|
||||
<delete file="${project.build.directory}/original-${project.build.finalName}.jar" />
|
||||
</target>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,13 @@
|
||||
rootProject.name = "Yamipa-OG"
|
||||
|
||||
ProcessBuilder("sh", "bootstrap.sh").directory(rootDir).inheritIO().start().let {
|
||||
if (it.waitFor() != 0) throw GradleException("bootstrap.sh failed")
|
||||
}
|
||||
|
||||
file("libs")
|
||||
.listFiles()
|
||||
?.filter { it.isDirectory && !it.name.startsWith(".") }
|
||||
?.forEach { dir ->
|
||||
include(":libs:${dir.name}")
|
||||
project(":libs:${dir.name}").projectDir = dir
|
||||
}
|
||||
@@ -1,118 +1,129 @@
|
||||
package io.josemmo.bukkit.plugin;
|
||||
|
||||
import io.josemmo.bukkit.plugin.commands.ImageCommandBridge;
|
||||
import io.josemmo.bukkit.plugin.renderer.*;
|
||||
import io.josemmo.bukkit.plugin.storage.ImageStorage;
|
||||
import io.josemmo.bukkit.plugin.utils.Logger;
|
||||
import org.bstats.bukkit.Metrics;
|
||||
import org.bstats.charts.SimplePie;
|
||||
import java.awt.Color;
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import java.awt.Color;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.function.Function;
|
||||
|
||||
import io.josemmo.bukkit.plugin.commands.ImageCommandBridge;
|
||||
import io.josemmo.bukkit.plugin.renderer.FakeEntity;
|
||||
import io.josemmo.bukkit.plugin.renderer.FakeImage;
|
||||
import io.josemmo.bukkit.plugin.renderer.FakeMap;
|
||||
import io.josemmo.bukkit.plugin.renderer.ImageRenderer;
|
||||
import io.josemmo.bukkit.plugin.renderer.ItemService;
|
||||
import io.josemmo.bukkit.plugin.storage.ImageStorage;
|
||||
import net.trueog.utilitiesog.UtilitiesOG;
|
||||
|
||||
public class YamipaPlugin extends JavaPlugin {
|
||||
public static final int BSTATS_PLUGIN_ID = 10243;
|
||||
private static final Logger LOGGER = Logger.getLogger();
|
||||
private static @Nullable YamipaPlugin INSTANCE;
|
||||
|
||||
private static YamipaPlugin instance;
|
||||
private boolean verbose;
|
||||
private @Nullable ImageStorage storage;
|
||||
private @Nullable ImageRenderer renderer;
|
||||
private @Nullable ItemService itemService;
|
||||
private @Nullable ScheduledExecutorService scheduler;
|
||||
private @Nullable Metrics metrics;
|
||||
private ImageStorage storage;
|
||||
private ImageRenderer renderer;
|
||||
private ItemService itemService;
|
||||
private ScheduledExecutorService scheduler;
|
||||
|
||||
/**
|
||||
* Get plugin instance
|
||||
*
|
||||
* @return Plugin instance
|
||||
*/
|
||||
public static @NotNull YamipaPlugin getInstance() {
|
||||
Objects.requireNonNull(INSTANCE, "Cannot get plugin instance if plugin is not running");
|
||||
return INSTANCE;
|
||||
|
||||
return instance;
|
||||
|
||||
}
|
||||
|
||||
public static String getPrefix() {
|
||||
|
||||
return "&7[&eYamipa&f-&4OG&7] ";
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get image storage instance
|
||||
*
|
||||
* @return Image storage instance
|
||||
*/
|
||||
public @NotNull ImageStorage getStorage() {
|
||||
Objects.requireNonNull(storage, "Cannot get storage instance if plugin is not running");
|
||||
|
||||
return storage;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get image renderer instance
|
||||
*
|
||||
* @return Image renderer instance
|
||||
*/
|
||||
public @NotNull ImageRenderer getRenderer() {
|
||||
Objects.requireNonNull(renderer, "Cannot get renderer instance if plugin is not running");
|
||||
|
||||
return renderer;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get internal tasks scheduler
|
||||
*
|
||||
* @return Tasks scheduler
|
||||
*/
|
||||
public @NotNull ScheduledExecutorService getScheduler() {
|
||||
Objects.requireNonNull(scheduler, "Cannot get scheduler instance if plugin is not running");
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is verbose
|
||||
* @return Whether plugin is running in verbose mode
|
||||
*/
|
||||
public boolean isVerbose() {
|
||||
return verbose;
|
||||
return scheduler;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoad() {
|
||||
INSTANCE = this;
|
||||
|
||||
instance = this;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
|
||||
// Initialize logger
|
||||
verbose = getConfig().getBoolean("verbose", false);
|
||||
if (verbose) {
|
||||
LOGGER.info("Running on VERBOSE mode");
|
||||
|
||||
info("Running on VERBOSE mode");
|
||||
|
||||
}
|
||||
|
||||
// Register plugin commands
|
||||
ImageCommandBridge.register(this);
|
||||
|
||||
// Read plugin configuration paths
|
||||
Path basePath = getDataFolder().toPath();
|
||||
String imagesPath = getConfig().getString("images-path", "images");
|
||||
String cachePath = getConfig().getString("cache-path", "cache");
|
||||
String dataPath = getConfig().getString("data-path", "images.dat");
|
||||
final Path basePath = getDataFolder().toPath();
|
||||
final String imagesPath = getConfig().getString("images-path", "images");
|
||||
final String cachePath = getConfig().getString("cache-path", "cache");
|
||||
final String dataPath = getConfig().getString("data-path", "images.dat");
|
||||
|
||||
// Create image storage
|
||||
String allowedPaths = getConfig().getString("allowed-paths", "");
|
||||
storage = new ImageStorage(
|
||||
basePath.resolve(imagesPath).toAbsolutePath().normalize(),
|
||||
basePath.resolve(cachePath).toAbsolutePath().normalize(),
|
||||
allowedPaths
|
||||
);
|
||||
storage = new ImageStorage(basePath.resolve(imagesPath).toString(), basePath.resolve(cachePath).toString());
|
||||
try {
|
||||
|
||||
storage.start();
|
||||
|
||||
} catch (Exception e) {
|
||||
LOGGER.severe("Failed to initialize image storage", e);
|
||||
|
||||
log(Level.SEVERE, "Failed to initialize image storage", e);
|
||||
|
||||
}
|
||||
|
||||
// Create image renderer
|
||||
boolean animateImages = getConfig().getBoolean("animate-images", true);
|
||||
LOGGER.info(animateImages ? "Enabled image animation support" : "Image animation support is disabled");
|
||||
int maxImageDimension = getConfig().getInt("max-image-dimension", 30);
|
||||
renderer = new ImageRenderer(basePath.resolve(dataPath), animateImages, maxImageDimension);
|
||||
final boolean animateImages = getConfig().getBoolean("animate-images", true);
|
||||
FakeImage.configure(animateImages);
|
||||
info(animateImages ? "Enabled image animation support" : "Image animation support is disabled");
|
||||
renderer = new ImageRenderer(basePath.resolve(dataPath).toString());
|
||||
renderer.start();
|
||||
|
||||
// Create image item service
|
||||
@@ -123,66 +134,119 @@ public class YamipaPlugin extends JavaPlugin {
|
||||
scheduler = Executors.newScheduledThreadPool(6);
|
||||
|
||||
// Warm-up plugin dependencies
|
||||
LOGGER.fine("Waiting for ProtocolLib to be ready...");
|
||||
fine("Waiting for ProtocolLib to be ready...");
|
||||
scheduler.execute(() -> {
|
||||
|
||||
FakeEntity.waitForProtocolLib();
|
||||
LOGGER.fine("ProtocolLib is now ready");
|
||||
fine("ProtocolLib is now ready");
|
||||
|
||||
});
|
||||
LOGGER.fine("Triggered map color cache warm-up");
|
||||
fine("Triggered map color cache warm-up");
|
||||
FakeMap.pixelToIndex(Color.RED.getRGB()); // Ask for a color index to force cache generation
|
||||
|
||||
// Initialize bStats
|
||||
Function<Integer, String> toStats = number -> {
|
||||
if (number >= 1000) return "1000+";
|
||||
if (number >= 500) return "500-999";
|
||||
if (number >= 100) return "100-499";
|
||||
if (number >= 50) return "50-99";
|
||||
if (number >= 10) return "10-49";
|
||||
return "0-9";
|
||||
};
|
||||
metrics = new Metrics(this, BSTATS_PLUGIN_ID);
|
||||
metrics.addCustomChart(new SimplePie("animate_images", () -> animateImages ? "true" : "false"));
|
||||
metrics.addCustomChart(new SimplePie("number_of_image_files", () -> toStats.apply(storage.size())));
|
||||
metrics.addCustomChart(new SimplePie("number_of_placed_images", () -> toStats.apply(renderer.size())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
// Stop metrics
|
||||
if (metrics != null) {
|
||||
metrics.shutdown();
|
||||
metrics = null;
|
||||
}
|
||||
|
||||
// Stop item service
|
||||
if (itemService != null) {
|
||||
itemService.stop();
|
||||
itemService = null;
|
||||
}
|
||||
|
||||
// Stop image renderer
|
||||
if (renderer != null) {
|
||||
renderer.stop();
|
||||
renderer = null;
|
||||
}
|
||||
|
||||
// Stop image storage
|
||||
if (storage != null) {
|
||||
storage.stop();
|
||||
storage = null;
|
||||
}
|
||||
// Stop plugin components
|
||||
storage.stop();
|
||||
renderer.stop();
|
||||
itemService.stop();
|
||||
storage = null;
|
||||
renderer = null;
|
||||
itemService = null;
|
||||
|
||||
// Stop internal scheduler
|
||||
if (scheduler != null) {
|
||||
scheduler.shutdownNow();
|
||||
scheduler = null;
|
||||
}
|
||||
scheduler.shutdownNow();
|
||||
scheduler = null;
|
||||
|
||||
// Remove Bukkit listeners and tasks
|
||||
HandlerList.unregisterAll(this);
|
||||
Bukkit.getScheduler().cancelTasks(this);
|
||||
|
||||
// Unlink reference to instance
|
||||
INSTANCE = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log message
|
||||
*
|
||||
* @param level Record level
|
||||
* @param message Message
|
||||
* @param e Throwable instance, NULL to ignore
|
||||
*/
|
||||
public void log(@NotNull Level level, @NotNull String message, @Nullable Throwable e) {
|
||||
|
||||
// Fix log level
|
||||
if (level.intValue() < Level.INFO.intValue()) {
|
||||
|
||||
if (!verbose) {
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
level = Level.INFO;
|
||||
|
||||
}
|
||||
|
||||
// Proxy record to real logger
|
||||
if (e == null) {
|
||||
|
||||
UtilitiesOG.logToConsole(getPrefix(), message);
|
||||
|
||||
} else {
|
||||
|
||||
UtilitiesOG.logToConsole(getPrefix(), message);
|
||||
|
||||
e.printStackTrace();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Log message
|
||||
*
|
||||
* @param level Record level
|
||||
* @param message Message
|
||||
*/
|
||||
public void log(@NotNull Level level, @NotNull String message) {
|
||||
|
||||
log(level, message, null);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Log warning message
|
||||
*
|
||||
* @param message Message
|
||||
*/
|
||||
public void warning(@NotNull String message) {
|
||||
|
||||
log(Level.WARNING, message);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Log info message
|
||||
*
|
||||
* @param message Message
|
||||
*/
|
||||
public void info(@NotNull String message) {
|
||||
|
||||
log(Level.INFO, message);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Log fine message
|
||||
*
|
||||
* @param message Message
|
||||
*/
|
||||
public void fine(@NotNull String message) {
|
||||
|
||||
log(Level.FINE, message);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,179 +1,244 @@
|
||||
package io.josemmo.bukkit.plugin.commands;
|
||||
|
||||
import com.mojang.brigadier.builder.ArgumentBuilder;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import io.josemmo.bukkit.plugin.commands.arguments.Argument;
|
||||
import io.josemmo.bukkit.plugin.utils.Internals;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import com.mojang.brigadier.builder.ArgumentBuilder;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
|
||||
import io.josemmo.bukkit.plugin.YamipaPlugin;
|
||||
import io.josemmo.bukkit.plugin.commands.arguments.Argument;
|
||||
import io.josemmo.bukkit.plugin.utils.Internals;
|
||||
import net.trueog.utilitiesog.UtilitiesOG;
|
||||
|
||||
public class Command {
|
||||
|
||||
private final String name;
|
||||
private final List<Argument> arguments = new ArrayList<>();
|
||||
private Predicate<CommandSender> requirementHandler = __ -> true;
|
||||
private @Nullable BiConsumer<CommandSender, Object[]> executesHandler;
|
||||
private @Nullable BiConsumer<Player, Object[]> executesPlayerHandler;
|
||||
private BiConsumer<CommandSender, Object[]> executesHandler = null;
|
||||
private BiConsumer<Player, Object[]> executesPlayerHandler = null;
|
||||
private final List<Command> subcommands = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Command constructor
|
||||
*
|
||||
* @param name Command literal name
|
||||
*/
|
||||
public Command(@NotNull String name) {
|
||||
|
||||
this.name = name;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Add argument to command
|
||||
* @param argument Argument instance
|
||||
* @return This instance
|
||||
*
|
||||
* @param argument Argument instance
|
||||
* @return This instance
|
||||
*/
|
||||
public @NotNull Command withArgument(@NotNull Argument argument) {
|
||||
|
||||
arguments.add(argument);
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Add requirement handler to this command
|
||||
* @param handler Handler to determine command availability
|
||||
* @return This instance
|
||||
*
|
||||
* @param handler Handler to determine command availability
|
||||
* @return This instance
|
||||
*/
|
||||
public @NotNull Command withRequirement(@NotNull Predicate<CommandSender> handler) {
|
||||
|
||||
requirementHandler = handler;
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Add permission requirement to this command
|
||||
* @param permissions Permission names (match as least one)
|
||||
* @return This instance
|
||||
*
|
||||
* @param permissions Permission names (match as least one)
|
||||
* @return This instance
|
||||
*/
|
||||
public @NotNull Command withPermission(@NotNull String ...permissions) {
|
||||
public @NotNull Command withPermission(@NotNull String... permissions) {
|
||||
|
||||
return withRequirement(sender -> {
|
||||
|
||||
for (String permission : permissions) {
|
||||
|
||||
if (sender.hasPermission(permission)) {
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Add handler that will be executed when the command gets called
|
||||
* @param handler Command handler
|
||||
* @return This instance
|
||||
*
|
||||
* @param handler Command handler
|
||||
* @return This instance
|
||||
*/
|
||||
public @NotNull Command executes(@NotNull BiConsumer<CommandSender, Object[]> handler) {
|
||||
|
||||
executesHandler = handler;
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Add handler that will be executed when the command gets called by a player
|
||||
* @param handler Command handler
|
||||
* @return This instance
|
||||
*
|
||||
* @param handler Command handler
|
||||
* @return This instance
|
||||
*/
|
||||
@SuppressWarnings("UnusedReturnValue")
|
||||
public @NotNull Command executesPlayer(@NotNull BiConsumer<Player, Object[]> handler) {
|
||||
|
||||
executesPlayerHandler = handler;
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Add subcommand to this command
|
||||
* @param name Subcommand literal name
|
||||
* @return The new subcommand
|
||||
*
|
||||
* @param name Subcommand literal name
|
||||
* @return The new subcommand
|
||||
*/
|
||||
public @NotNull Command addSubcommand(@NotNull String name) {
|
||||
Command subcommand = new Command(name);
|
||||
|
||||
final Command subcommand = new Command(name);
|
||||
subcommands.add(subcommand);
|
||||
return subcommand;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Build command and all of its children
|
||||
*
|
||||
* @return Literal argument builder instance
|
||||
*/
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
public @NotNull LiteralArgumentBuilder<?> build() {
|
||||
LiteralArgumentBuilder<?> root = LiteralArgumentBuilder.literal(name);
|
||||
|
||||
final LiteralArgumentBuilder<?> root = LiteralArgumentBuilder.literal(name);
|
||||
|
||||
// Add arguments and execution handler
|
||||
buildElement(root, 0);
|
||||
|
||||
// Add subcommands
|
||||
for (Command subcommand : subcommands) {
|
||||
root.then((ArgumentBuilder) subcommand.build());
|
||||
}
|
||||
subcommands.forEach(subcommand -> root.then((ArgumentBuilder) subcommand.build()));
|
||||
|
||||
return root;
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private @NotNull ArgumentBuilder buildElement(@NotNull ArgumentBuilder parent, int argIndex) {
|
||||
|
||||
// Attach requirement handler to each command element
|
||||
parent.requires(source -> {
|
||||
CommandSender sender = Internals.getBukkitSender(source);
|
||||
|
||||
final CommandSender sender = Internals.getBukkitSender(source);
|
||||
return requirementHandler.test(sender);
|
||||
|
||||
});
|
||||
|
||||
// Chain command elements from the bottom-up
|
||||
if (argIndex < arguments.size()) {
|
||||
Argument argument = arguments.get(argIndex);
|
||||
ArgumentBuilder argumentBuilder = argument.build().suggests((ctx, builder) -> {
|
||||
CommandSender sender = Internals.getBukkitSender(ctx.getSource());
|
||||
return argument.suggest(sender, builder);
|
||||
});
|
||||
parent.then(buildElement(argumentBuilder, argIndex+1)).executes(ctx -> {
|
||||
CommandSender sender = Internals.getBukkitSender(ctx.getSource());
|
||||
sender.sendMessage(ChatColor.RED + "Missing required arguments");
|
||||
|
||||
parent.then(buildElement(arguments.get(argIndex).build(), argIndex + 1)).executes(ctx -> {
|
||||
|
||||
final CommandSender sender = Internals.getBukkitSender(ctx.getSource());
|
||||
final String argsErrorMessage = "&cERROR: Missing required arguments!";
|
||||
if (sender instanceof Player) {
|
||||
|
||||
UtilitiesOG.trueogMessage((Player) sender, argsErrorMessage);
|
||||
|
||||
} else {
|
||||
|
||||
UtilitiesOG.logToConsole(YamipaPlugin.getPrefix(), argsErrorMessage);
|
||||
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
||||
});
|
||||
return parent;
|
||||
|
||||
}
|
||||
|
||||
// Attach execution handler to last command element
|
||||
if (executesPlayerHandler != null) {
|
||||
|
||||
parent.executes(ctx -> {
|
||||
CommandSender sender = Internals.getBukkitSender(ctx.getSource());
|
||||
|
||||
final CommandSender sender = Internals.getBukkitSender(ctx.getSource());
|
||||
if (sender instanceof Player) {
|
||||
|
||||
executesPlayerHandler.accept((Player) sender, getArgumentValues(sender, ctx));
|
||||
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.RED + "Only in-game players can execute this command!");
|
||||
|
||||
UtilitiesOG.logToConsole(YamipaPlugin.getPrefix(),
|
||||
"Only in-game players can execute this command!");
|
||||
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
});
|
||||
|
||||
} else if (executesHandler != null) {
|
||||
|
||||
parent.executes(ctx -> {
|
||||
CommandSender sender = Internals.getBukkitSender(ctx.getSource());
|
||||
|
||||
final CommandSender sender = Internals.getBukkitSender(ctx.getSource());
|
||||
executesHandler.accept(sender, getArgumentValues(sender, ctx));
|
||||
return 0;
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return parent;
|
||||
|
||||
}
|
||||
|
||||
private @NotNull Object[] getArgumentValues(
|
||||
@NotNull CommandSender sender,
|
||||
@NotNull CommandContext<?> ctx
|
||||
) throws CommandSyntaxException {
|
||||
List<Object> argValues = new ArrayList<>();
|
||||
private @NotNull Object[] getArgumentValues(@NotNull CommandSender sender, @NotNull CommandContext<?> ctx)
|
||||
throws CommandSyntaxException
|
||||
{
|
||||
|
||||
final List<Object> argValues = new ArrayList<>();
|
||||
argValues.add(ctx.getNodes().get(0).getNode().getName());
|
||||
for (Argument argument : arguments) {
|
||||
|
||||
Object value = ctx.getArgument(argument.getName(), Object.class);
|
||||
value = argument.parse(sender, value);
|
||||
argValues.add(value);
|
||||
|
||||
}
|
||||
|
||||
return argValues.toArray(new Object[0]);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,485 +1,604 @@
|
||||
package io.josemmo.bukkit.plugin.commands;
|
||||
|
||||
import io.josemmo.bukkit.plugin.YamipaPlugin;
|
||||
import io.josemmo.bukkit.plugin.renderer.FakeImage;
|
||||
import io.josemmo.bukkit.plugin.renderer.ImageRenderer;
|
||||
import io.josemmo.bukkit.plugin.renderer.ItemService;
|
||||
import io.josemmo.bukkit.plugin.storage.ImageFile;
|
||||
import io.josemmo.bukkit.plugin.storage.ImageStorage;
|
||||
import io.josemmo.bukkit.plugin.utils.Logger;
|
||||
import io.josemmo.bukkit.plugin.utils.Permissions;
|
||||
import io.josemmo.bukkit.plugin.utils.SelectBlockTask;
|
||||
import io.josemmo.bukkit.plugin.utils.ActionBar;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.plugin.PluginDescriptionFile;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.Dimension;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.Rotation;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import io.josemmo.bukkit.plugin.YamipaPlugin;
|
||||
import io.josemmo.bukkit.plugin.renderer.FakeImage;
|
||||
import io.josemmo.bukkit.plugin.renderer.ImageRenderer;
|
||||
import io.josemmo.bukkit.plugin.renderer.ItemService;
|
||||
import io.josemmo.bukkit.plugin.storage.ImageFile;
|
||||
import io.josemmo.bukkit.plugin.utils.ActionBar;
|
||||
import io.josemmo.bukkit.plugin.utils.Permissions;
|
||||
import io.josemmo.bukkit.plugin.utils.SelectBlockTask;
|
||||
import net.trueog.utilitiesog.UtilitiesOG;
|
||||
|
||||
public class ImageCommand {
|
||||
private static final int ITEMS_PER_PAGE = 9;
|
||||
private static final int MAX_PATH_DEPTH = 10;
|
||||
private static final Logger LOGGER = Logger.getLogger("ImageCommand");
|
||||
|
||||
public static final int ITEMS_PER_PAGE = 9;
|
||||
|
||||
private static void sendMessage(@NotNull CommandSender sender, @NotNull String message) {
|
||||
|
||||
if (sender instanceof Player) {
|
||||
|
||||
UtilitiesOG.trueogMessage((Player) sender, message);
|
||||
|
||||
} else {
|
||||
|
||||
UtilitiesOG.logToConsole(YamipaPlugin.getPrefix(), message);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void showHelp(@NotNull CommandSender s, @NotNull String commandName) {
|
||||
String cmd = "/" + commandName;
|
||||
s.sendMessage(ChatColor.BOLD + "=== Yamipa Plugin Help ===");
|
||||
s.sendMessage(ChatColor.AQUA + cmd + ChatColor.RESET + " - Show this help");
|
||||
|
||||
final String cmd = "/" + commandName;
|
||||
sendMessage(s, "&l=== Yamipa-OG Plugin Help ===");
|
||||
sendMessage(s, "&b" + cmd + "&r - Show this help");
|
||||
if (s.hasPermission("yamipa.command.clear") || s.hasPermission("yamipa.clear")) {
|
||||
s.sendMessage(ChatColor.AQUA + cmd + " clear <x z w> <r> [<player>]" + ChatColor.RESET + " - Remove placed images");
|
||||
|
||||
sendMessage(s, "&b" + cmd + " clear <x z w> <r> [<player>]&r - Remove placed images");
|
||||
|
||||
}
|
||||
|
||||
if (s.hasPermission("yamipa.command.describe") || s.hasPermission("yamipa.describe")) {
|
||||
s.sendMessage(ChatColor.AQUA + cmd + " describe" + ChatColor.RESET + " - Describe placed image");
|
||||
|
||||
sendMessage(s, "&b" + cmd + " describe&r - Describe placed image");
|
||||
|
||||
}
|
||||
|
||||
if (s.hasPermission("yamipa.command.download") || s.hasPermission("yamipa.download")) {
|
||||
s.sendMessage(ChatColor.AQUA + cmd + " download <url> <filename>" + ChatColor.RESET + " - Download image");
|
||||
|
||||
sendMessage(s, "&b" + cmd + " download <url> <filename>&r - Download image");
|
||||
|
||||
}
|
||||
|
||||
if (s.hasPermission("yamipa.command.give") || s.hasPermission("yamipa.give")) {
|
||||
s.sendMessage(ChatColor.AQUA + cmd + " give <p> <filename> <#> <w> [<h>] [<f>]" + ChatColor.RESET + " - Give items");
|
||||
|
||||
sendMessage(s, "&b" + cmd + " give <p> <filename> <#> <w> [<h>] [<f>]&r - Give items");
|
||||
|
||||
}
|
||||
|
||||
if (s.hasPermission("yamipa.command.list") || s.hasPermission("yamipa.list")) {
|
||||
s.sendMessage(ChatColor.AQUA + cmd + " list [<page>]" + ChatColor.RESET + " - List all images");
|
||||
|
||||
sendMessage(s, "&b" + cmd + " list [<page>]&r - List all images");
|
||||
|
||||
}
|
||||
|
||||
if (s.hasPermission("yamipa.command.place") || s.hasPermission("yamipa.place")) {
|
||||
s.sendMessage(ChatColor.AQUA + cmd + " place <filename> <w> [<h>] [<f>]" + ChatColor.RESET + " - Place image");
|
||||
|
||||
sendMessage(s, "&b" + cmd + " place <filename> <w> [<h>] [<f>]&r - Place image");
|
||||
|
||||
}
|
||||
|
||||
if (s.hasPermission("yamipa.command.remove.own") || s.hasPermission("yamipa.remove")) {
|
||||
s.sendMessage(ChatColor.AQUA + cmd + " remove" + ChatColor.RESET + " - Remove a single placed image");
|
||||
|
||||
sendMessage(s, "&b" + cmd + " remove&r - Remove a single placed image");
|
||||
|
||||
}
|
||||
|
||||
if (s.hasPermission("yamipa.command.top") || s.hasPermission("yamipa.top")) {
|
||||
s.sendMessage(ChatColor.AQUA + cmd + " top" + ChatColor.RESET + " - List players with the most images");
|
||||
|
||||
sendMessage(s, "&b" + cmd + " top&r - List players with the most images");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void listImages(@NotNull CommandSender sender, int page) {
|
||||
ImageStorage storage = YamipaPlugin.getInstance().getStorage();
|
||||
List<String> filenames = storage.getFilenames(sender);
|
||||
int numOfImages = filenames.size();
|
||||
|
||||
final String[] filenames = YamipaPlugin.getInstance().getStorage().getAllFilenames();
|
||||
final int numOfImages = filenames.length;
|
||||
|
||||
// Are there any images available?
|
||||
if (numOfImages == 0) {
|
||||
sender.sendMessage(ChatColor.RED + "No images found in the images directory");
|
||||
|
||||
sendMessage(sender, "&cNo images found in the images directory");
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
// Is the page number valid?
|
||||
int firstImageIndex = Math.max(page-1, 0) * ITEMS_PER_PAGE;
|
||||
final int firstImageIndex = Math.max(page - 1, 0) * ITEMS_PER_PAGE;
|
||||
if (firstImageIndex >= numOfImages) {
|
||||
sender.sendMessage(ChatColor.RED + "Page " + page + " not found");
|
||||
|
||||
sendMessage(sender, "&cPage " + page + " not found");
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
// Render list of images
|
||||
int stopImageIndex = (page == 0) ? numOfImages : Math.min(numOfImages, firstImageIndex+ITEMS_PER_PAGE);
|
||||
final int stopImageIndex = (page == 0) ? numOfImages : Math.min(numOfImages, firstImageIndex + ITEMS_PER_PAGE);
|
||||
if (page > 0) {
|
||||
int maxPage = (int) Math.ceil((float) numOfImages / ITEMS_PER_PAGE);
|
||||
sender.sendMessage("=== Page " + page + " out of " + maxPage + " ===");
|
||||
|
||||
final int maxPage = (int) Math.ceil((float) numOfImages / ITEMS_PER_PAGE);
|
||||
sendMessage(sender, "=== Page " + page + " out of " + maxPage + " ===");
|
||||
|
||||
}
|
||||
for (int i=firstImageIndex; i<stopImageIndex; ++i) {
|
||||
sender.sendMessage(ChatColor.GOLD + filenames.get(i));
|
||||
|
||||
for (int i = firstImageIndex; i < stopImageIndex; ++i) {
|
||||
|
||||
sendMessage(sender, "&6" + filenames[i]);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void downloadImage(@NotNull CommandSender sender, @NotNull String rawUrl, @NotNull String filename) {
|
||||
YamipaPlugin plugin = YamipaPlugin.getInstance();
|
||||
ImageStorage storage = plugin.getStorage();
|
||||
Path basePath = storage.getBasePath();
|
||||
|
||||
// Resolve destination path
|
||||
Path destPath;
|
||||
try {
|
||||
destPath = basePath.resolve(filename).normalize();
|
||||
} catch (InvalidPathException e) {
|
||||
sender.sendMessage(ChatColor.RED + "Malformed destination path");
|
||||
return;
|
||||
}
|
||||
final YamipaPlugin plugin = YamipaPlugin.getInstance();
|
||||
|
||||
// Validate destination file
|
||||
if (!destPath.startsWith(basePath)) {
|
||||
sender.sendMessage(ChatColor.RED + "Not a valid destination filename");
|
||||
return;
|
||||
}
|
||||
if (!storage.isPathAllowed(destPath, sender)) {
|
||||
sender.sendMessage(ChatColor.RED + "Not allowed to download a file here");
|
||||
final Path basePath = Paths.get(plugin.getStorage().getBasePath());
|
||||
final Path destPath = basePath.resolve(filename);
|
||||
if (!destPath.getParent().equals(basePath)) {
|
||||
|
||||
sendMessage(sender, "&cNot a valid destination filename");
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
if (destPath.toFile().exists()) {
|
||||
sender.sendMessage(ChatColor.RED + "There's already a file with that name");
|
||||
return;
|
||||
}
|
||||
int depth = destPath.getNameCount() - basePath.getNameCount();
|
||||
if (depth > MAX_PATH_DEPTH) {
|
||||
sender.sendMessage(ChatColor.RED + "Destination path has too many directories");
|
||||
|
||||
sendMessage(sender, "&cThere's already a file with that name");
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
// Validate and fix remote URL
|
||||
URL url;
|
||||
String referrer = null;
|
||||
try {
|
||||
url = new URL(rawUrl);
|
||||
|
||||
url = new URL(rawUrl);
|
||||
// Giphy.com
|
||||
if (url.getHost().equals("giphy.com")) {
|
||||
String path = url.getPath();
|
||||
String id = path.substring(path.lastIndexOf('-')+1);
|
||||
if ("giphy.com".equals(url.getHost())) {
|
||||
|
||||
final String path = url.getPath();
|
||||
final String id = StringUtils.substring(path, path.lastIndexOf('-') + 1);
|
||||
url = new URL("https://media.giphy.com/media/" + id + "/giphy.gif");
|
||||
referrer = "https://giphy.com/";
|
||||
|
||||
}
|
||||
|
||||
// Imgur.com
|
||||
if (url.getHost().equals("imgur.com")) {
|
||||
String[] parts = url.getPath().replaceAll("^/|/$", "").split("/");
|
||||
if (parts.length == 2 && (parts[0].equals("a") || parts[0].equals("gallery"))) {
|
||||
if ("imgur.com".equals(url.getHost())) {
|
||||
|
||||
final String[] parts = url.getPath().replaceAll("^/|/$", "").split("/");
|
||||
if (parts.length == 2 && ("a".equals(parts[0]) || "gallery".equals(parts[0]))) {
|
||||
|
||||
url = new URL("https://imgur.com/a/" + parts[1] + "/zip");
|
||||
referrer = "https://imgur.com/a/" + parts[1];
|
||||
|
||||
} else {
|
||||
url = new URL("https://imgur.com/download/" + parts[parts.length-1] + "/");
|
||||
referrer = "https://imgur.com/" + parts[parts.length-1];
|
||||
|
||||
url = new URL("https://imgur.com/download/" + parts[parts.length - 1] + "/");
|
||||
referrer = "https://imgur.com/" + parts[parts.length - 1];
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
} catch (MalformedURLException e) {
|
||||
sender.sendMessage(ChatColor.RED + "The remote URL is not valid");
|
||||
|
||||
} catch (MalformedURLException malformedURLException) {
|
||||
|
||||
sendMessage(sender, "&cThe remote URL is not valid");
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
// Download and validate remote file
|
||||
final URL finalUrl = url;
|
||||
final String finalReferrer = referrer;
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
|
||||
try {
|
||||
URLConnection conn = finalUrl.openConnection();
|
||||
PluginDescriptionFile desc = plugin.getDescription();
|
||||
|
||||
final URLConnection conn = finalUrl.openConnection();
|
||||
conn.setRequestProperty("Accept", "*/*");
|
||||
conn.setRequestProperty("User-Agent", desc.getName() + "/" + desc.getVersion());
|
||||
conn.setRequestProperty("User-Agent",
|
||||
plugin.getPluginMeta().getName() + "/" + plugin.getPluginMeta().getVersion());
|
||||
if (finalReferrer != null) {
|
||||
|
||||
conn.setRequestProperty("Referer", finalReferrer);
|
||||
|
||||
}
|
||||
|
||||
// Download file
|
||||
sender.sendMessage("Downloading file...");
|
||||
Files.createDirectories(destPath.getParent());
|
||||
sendMessage(sender, "Downloading file...");
|
||||
Files.copy(conn.getInputStream(), destPath);
|
||||
|
||||
// Validate downloaded file
|
||||
if (ImageIO.read(destPath.toFile()) == null) {
|
||||
|
||||
throw new IllegalArgumentException("The downloaded file is not a valid image");
|
||||
|
||||
}
|
||||
|
||||
// Notify sender
|
||||
sender.sendMessage(ChatColor.GREEN + "Done!");
|
||||
sendMessage(sender, "&aDone!");
|
||||
|
||||
} catch (IOException e) {
|
||||
sender.sendMessage(ChatColor.RED + "An error occurred trying to download the remote file");
|
||||
LOGGER.warning("Failed to download file from \"" + finalUrl + "\": " + e.getClass().getName());
|
||||
|
||||
sendMessage(sender, "&cAn error occurred trying to download the remote file");
|
||||
plugin.warning("Failed to download file from \"" + finalUrl + "\": " + e.getClass().getName());
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
|
||||
if (Files.exists(destPath) && !destPath.toFile().delete()) {
|
||||
LOGGER.warning("Failed to delete corrupted file \"" + destPath + "\"");
|
||||
|
||||
plugin.warning("Failed to delete corrupted file \"" + destPath + "\"");
|
||||
|
||||
}
|
||||
sender.sendMessage(ChatColor.RED + e.getMessage());
|
||||
|
||||
sendMessage(sender, "&c" + e.getMessage());
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public static void placeImage(
|
||||
@NotNull Player player,
|
||||
@NotNull ImageFile image,
|
||||
int width,
|
||||
int height,
|
||||
int flags
|
||||
) {
|
||||
public static void placeImage(@NotNull Player player, @NotNull ImageFile image, int width, int height, int flags) {
|
||||
|
||||
// Get image size in blocks
|
||||
Dimension sizeInPixels = image.getSize();
|
||||
final Dimension sizeInPixels = image.getSize();
|
||||
if (sizeInPixels == null) {
|
||||
player.sendMessage(ChatColor.RED + "The requested file is not a valid image");
|
||||
|
||||
UtilitiesOG.trueogMessage(player, "&cThe requested file is not a valid image");
|
||||
return;
|
||||
|
||||
}
|
||||
final int finalHeight = (height == 0) ? FakeImage.getProportionalHeight(sizeInPixels, player, width) : height;
|
||||
|
||||
final int finalHeight = (height == 0) ? FakeImage.getProportionalHeight(sizeInPixels, width) : height;
|
||||
|
||||
// Ask player where to place image
|
||||
SelectBlockTask task = new SelectBlockTask(player);
|
||||
final SelectBlockTask task = new SelectBlockTask(player);
|
||||
task.onSuccess((location, face) -> placeImage(player, image, width, finalHeight, flags, location, face));
|
||||
task.onFailure(() -> ActionBar.send(player, ChatColor.RED + "Image placing canceled"));
|
||||
task.onFailure(() -> ActionBar.send(player, "&cImage placing canceled"));
|
||||
task.run("Right click a block to continue");
|
||||
|
||||
}
|
||||
|
||||
public static boolean placeImage(
|
||||
@NotNull Player player,
|
||||
@NotNull ImageFile image,
|
||||
int width,
|
||||
int height,
|
||||
int flags,
|
||||
@NotNull Location location,
|
||||
@NotNull BlockFace face
|
||||
) {
|
||||
ImageRenderer renderer = YamipaPlugin.getInstance().getRenderer();
|
||||
public static boolean placeImage(@NotNull Player player, @NotNull ImageFile image, int width, int height, int flags,
|
||||
@NotNull Location location, @NotNull BlockFace face)
|
||||
{
|
||||
|
||||
final ImageRenderer renderer = YamipaPlugin.getInstance().getRenderer();
|
||||
|
||||
// Create new fake image instance
|
||||
Rotation rotation = FakeImage.getRotationFromPlayerEyesight(face, player.getEyeLocation());
|
||||
FakeImage fakeImage = new FakeImage(image.getFilename(), location, face, rotation,
|
||||
width, height, new Date(), player, flags);
|
||||
final Rotation rotation = FakeImage.getRotationFromPlayerEyesight(face, player.getEyeLocation());
|
||||
final FakeImage fakeImage = new FakeImage(image.getName(), location, face, rotation, width, height, new Date(),
|
||||
player, flags);
|
||||
|
||||
// Make sure image can be placed
|
||||
for (Location loc : fakeImage.getAllLocations()) {
|
||||
|
||||
if (!Permissions.canBuild(player, loc)) {
|
||||
ActionBar.send(player, ChatColor.RED + "You're not allowed to place an image here!");
|
||||
|
||||
ActionBar.send(player, "&cYou're not allowed to place an image here!");
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
if (renderer.getImage(loc, face) != null) {
|
||||
ActionBar.send(player, ChatColor.RED + "There's already an image there!");
|
||||
|
||||
ActionBar.send(player, "&cThere's already an image there!");
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Show loading status to player
|
||||
ActionBar loadingActionBar = ActionBar.repeat(player, ChatColor.AQUA + "Loading image...");
|
||||
final ActionBar loadingActionBar = ActionBar.repeat(player, "&bLoading image...");
|
||||
fakeImage.setOnLoadedListener(loadingActionBar::clear);
|
||||
|
||||
// Add fake image to renderer
|
||||
renderer.addImage(fakeImage);
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
public static void removeImage(@NotNull Player player) {
|
||||
SelectBlockTask task = new SelectBlockTask(player);
|
||||
|
||||
final SelectBlockTask task = new SelectBlockTask(player);
|
||||
task.onSuccess((location, face) -> {
|
||||
FakeImage image = YamipaPlugin.getInstance().getRenderer().getImage(location, face);
|
||||
|
||||
final FakeImage image = YamipaPlugin.getInstance().getRenderer().getImage(location, face);
|
||||
if (image == null) {
|
||||
ActionBar.send(player, ChatColor.RED + "That is not a valid image!");
|
||||
|
||||
ActionBar.send(player, "&cThat is not a valid image!");
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
// Check player's command permissions
|
||||
if (
|
||||
!player.getUniqueId().equals(image.getPlacedBy().getUniqueId()) &&
|
||||
!player.hasPermission("yamipa.command.remove") &&
|
||||
!player.hasPermission("yamipa.remove")
|
||||
) {
|
||||
ActionBar.send(player, ChatColor.RED + "You cannot remove images from other players!");
|
||||
if (!player.getUniqueId().equals(image.getPlacedBy().getUniqueId())
|
||||
&& !player.hasPermission("yamipa.command.remove") && !player.hasPermission("yamipa.remove"))
|
||||
{
|
||||
|
||||
ActionBar.send(player, "&cYou cannot remove images from other players!");
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
// Attempt to remove image
|
||||
removeImage(player, image);
|
||||
|
||||
});
|
||||
task.onFailure(() -> ActionBar.send(player, ChatColor.RED + "Image removing canceled"));
|
||||
|
||||
task.onFailure(() -> ActionBar.send(player, "&cImage removing canceled"));
|
||||
task.run("Right click an image to continue");
|
||||
|
||||
}
|
||||
|
||||
public static boolean removeImage(@NotNull Player player, @NotNull FakeImage image) {
|
||||
|
||||
// Check block permissions
|
||||
for (Location loc : image.getAllLocations()) {
|
||||
|
||||
if (!Permissions.canDestroy(player, loc)) {
|
||||
ActionBar.send(player, ChatColor.RED + "You're not allowed to remove this image!");
|
||||
|
||||
ActionBar.send(player, "&cYou're not allowed to remove this image!");
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Trigger image removal
|
||||
YamipaPlugin.getInstance().getRenderer().removeImage(image);
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
public static void clearImages(
|
||||
@NotNull CommandSender sender,
|
||||
@NotNull Location origin,
|
||||
int radius,
|
||||
@Nullable OfflinePlayer placedBy
|
||||
) {
|
||||
ImageRenderer renderer = YamipaPlugin.getInstance().getRenderer();
|
||||
public static void clearImages(@NotNull CommandSender sender, @NotNull Location origin, int radius,
|
||||
@Nullable OfflinePlayer placedBy)
|
||||
{
|
||||
|
||||
final ImageRenderer renderer = YamipaPlugin.getInstance().getRenderer();
|
||||
|
||||
// Get images in area
|
||||
Set<FakeImage> images = renderer.getImages(
|
||||
Objects.requireNonNull(origin.getWorld()),
|
||||
origin.getBlockX()-radius+1,
|
||||
origin.getBlockX()+radius-1,
|
||||
origin.getBlockZ()-radius+1,
|
||||
origin.getBlockZ()+radius-1
|
||||
);
|
||||
final Set<FakeImage> images = renderer.getImages(origin.getWorld(), origin.getBlockX() - radius + 1,
|
||||
origin.getBlockX() + radius - 1, origin.getBlockZ() - radius + 1, origin.getBlockZ() + radius - 1);
|
||||
|
||||
// Filter out images not placed by targeted player
|
||||
if (placedBy != null) {
|
||||
UUID target = placedBy.getUniqueId();
|
||||
|
||||
final UUID target = placedBy.getUniqueId();
|
||||
images.removeIf(image -> !target.equals(image.getPlacedBy().getUniqueId()));
|
||||
|
||||
}
|
||||
|
||||
// Filter out images outside the permission scope of the sender
|
||||
if (sender instanceof Player) {
|
||||
Player senderAsPlayer = (Player) sender;
|
||||
if (sender instanceof Player senderAsPlayer) {
|
||||
|
||||
images.removeIf(image -> {
|
||||
|
||||
for (Location loc : image.getAllLocations()) {
|
||||
|
||||
if (!Permissions.canDestroy(senderAsPlayer, loc)) {
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// Remove found images
|
||||
for (FakeImage image : images) {
|
||||
renderer.removeImage(image);
|
||||
}
|
||||
sender.sendMessage("Removed " + images.size() + " placed image(s)");
|
||||
images.forEach(renderer::removeImage);
|
||||
|
||||
sendMessage(sender, "Removed " + images.size() + " placed image(s)");
|
||||
|
||||
}
|
||||
|
||||
public static void describeImage(@NotNull Player player) {
|
||||
ImageRenderer renderer = YamipaPlugin.getInstance().getRenderer();
|
||||
|
||||
final ImageRenderer renderer = YamipaPlugin.getInstance().getRenderer();
|
||||
|
||||
// Ask user to select fake image
|
||||
SelectBlockTask task = new SelectBlockTask(player);
|
||||
final SelectBlockTask task = new SelectBlockTask(player);
|
||||
task.onSuccess((location, face) -> {
|
||||
FakeImage image = renderer.getImage(location, face);
|
||||
|
||||
final FakeImage image = renderer.getImage(location, face);
|
||||
if (image == null) {
|
||||
ActionBar.send(player, ChatColor.RED + "That is not a valid image!");
|
||||
|
||||
ActionBar.send(player, "&cThat is not a valid image!");
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
// Separate previous messages
|
||||
player.sendMessage("");
|
||||
UtilitiesOG.trueogMessage(player, "");
|
||||
|
||||
// Basic information
|
||||
player.sendMessage(ChatColor.GOLD + "Filename: " + ChatColor.RESET + image.getFilename());
|
||||
player.sendMessage(ChatColor.GOLD + "World: " + ChatColor.RESET +
|
||||
image.getLocation().getChunk().getWorld().getName());
|
||||
player.sendMessage(ChatColor.GOLD + "Coordinates: " + ChatColor.RESET +
|
||||
image.getLocation().getBlockX() + ", " +
|
||||
image.getLocation().getBlockY() + ", " +
|
||||
image.getLocation().getBlockZ());
|
||||
player.sendMessage(ChatColor.GOLD + "Block Face: " + ChatColor.RESET + image.getBlockFace());
|
||||
player.sendMessage(ChatColor.GOLD + "Rotation: " + ChatColor.RESET + image.getRotation());
|
||||
player.sendMessage(ChatColor.GOLD + "Dimensions: " + ChatColor.RESET +
|
||||
image.getWidth() + "x" + image.getHeight() + " blocks");
|
||||
UtilitiesOG.trueogMessage(player, "&6Filename: &r" + image.getFilename());
|
||||
UtilitiesOG.trueogMessage(player, "&6World: &r" + image.getLocation().getChunk().getWorld().getName());
|
||||
UtilitiesOG.trueogMessage(player, "&6Coordinates: &r" + image.getLocation().getBlockX() + ", "
|
||||
+ image.getLocation().getBlockY() + ", " + image.getLocation().getBlockZ());
|
||||
UtilitiesOG.trueogMessage(player, "&6Block Face: &r" + image.getBlockFace());
|
||||
UtilitiesOG.trueogMessage(player, "&6Rotation: &r" + image.getRotation());
|
||||
UtilitiesOG.trueogMessage(player,
|
||||
"&6Dimensions: &r" + image.getWidth() + "x" + image.getHeight() + " blocks");
|
||||
|
||||
// Speed
|
||||
int delay = image.getDelay() * 50;
|
||||
String delayStr = (delay > 0) ? delay + " ms per step" : ChatColor.GRAY + "N/A";
|
||||
player.sendMessage(ChatColor.GOLD + "Speed: " + ChatColor.RESET + delayStr);
|
||||
final int delay = image.getDelay() * 50;
|
||||
final String delayStr = (delay > 0) ? delay + " ms per step" : "&7N/A";
|
||||
UtilitiesOG.trueogMessage(player, "&6Speed: &r" + delayStr);
|
||||
|
||||
// Placed At
|
||||
String dateStr = (image.getPlacedAt() == null) ?
|
||||
ChatColor.GRAY + "Some point in time" :
|
||||
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z").format(image.getPlacedAt());
|
||||
player.sendMessage(ChatColor.GOLD + "Placed At: " + ChatColor.RESET + dateStr);
|
||||
final String dateStr = (image.getPlacedAt() == null) ? "&7Some point in time"
|
||||
: new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z").format(image.getPlacedAt());
|
||||
UtilitiesOG.trueogMessage(player, "&6Placed At: &r" + dateStr);
|
||||
|
||||
// Placed By
|
||||
String playerStr;
|
||||
final String playerStr;
|
||||
if (image.getPlacedBy().getUniqueId().equals(FakeImage.UNKNOWN_PLAYER_ID)) {
|
||||
playerStr = ChatColor.GRAY + "Someone";
|
||||
|
||||
playerStr = "&7Someone";
|
||||
|
||||
} else if (image.getPlacedBy().getName() == null) {
|
||||
playerStr = ChatColor.DARK_AQUA + image.getPlacedBy().getUniqueId().toString();
|
||||
|
||||
playerStr = "&3" + image.getPlacedBy().getUniqueId();
|
||||
|
||||
} else {
|
||||
|
||||
playerStr = image.getPlacedBy().getName();
|
||||
|
||||
}
|
||||
player.sendMessage(ChatColor.GOLD + "Placed By: " + ChatColor.RESET + playerStr);
|
||||
|
||||
UtilitiesOG.trueogMessage(player, "&6Placed By: &r" + playerStr);
|
||||
|
||||
// Flags
|
||||
String flagsStr = "";
|
||||
if (image.hasFlag(FakeImage.FLAG_ANIMATABLE)) {
|
||||
flagsStr += ChatColor.AQUA + "ANIM ";
|
||||
|
||||
flagsStr += "&bANIM ";
|
||||
|
||||
}
|
||||
|
||||
if (image.hasFlag(FakeImage.FLAG_REMOVABLE)) {
|
||||
flagsStr += ChatColor.RED + "REMO ";
|
||||
|
||||
flagsStr += "&cREMO ";
|
||||
|
||||
}
|
||||
|
||||
if (image.hasFlag(FakeImage.FLAG_DROPPABLE)) {
|
||||
flagsStr += ChatColor.LIGHT_PURPLE + "DROP ";
|
||||
|
||||
flagsStr += "&dDROP ";
|
||||
|
||||
}
|
||||
|
||||
if (image.hasFlag(FakeImage.FLAG_GLOWING)) {
|
||||
flagsStr += ChatColor.GREEN + "GLOW ";
|
||||
|
||||
flagsStr += "&aGLOW ";
|
||||
|
||||
}
|
||||
if (flagsStr.isEmpty()) {
|
||||
flagsStr = ChatColor.GRAY + "N/A";
|
||||
|
||||
if (StringUtils.isEmpty(flagsStr)) {
|
||||
|
||||
flagsStr = "&7N/A";
|
||||
|
||||
}
|
||||
player.sendMessage(ChatColor.GOLD + "Flags: " + ChatColor.RESET + flagsStr);
|
||||
|
||||
UtilitiesOG.trueogMessage(player, "&6Flags: &r" + flagsStr);
|
||||
|
||||
});
|
||||
task.onFailure(() -> ActionBar.send(player, ChatColor.RED + "Image describing canceled"));
|
||||
task.onFailure(() -> ActionBar.send(player, "&cImage describing canceled"));
|
||||
task.run("Right click the image to describe");
|
||||
|
||||
}
|
||||
|
||||
public static void showTopPlayers(@NotNull CommandSender sender) {
|
||||
UUID senderId = (sender instanceof Player) ? ((Player) sender).getUniqueId() : null;
|
||||
Map<OfflinePlayer, Integer> stats = YamipaPlugin.getInstance().getRenderer().getImagesCountByPlayer();
|
||||
|
||||
final UUID senderId = (sender instanceof Player) ? ((Player) sender).getUniqueId() : null;
|
||||
final Map<OfflinePlayer, Integer> stats = YamipaPlugin.getInstance().getRenderer().getImagesCountByPlayer();
|
||||
|
||||
// Render header
|
||||
sender.sendMessage("=== Top players with the most placed images ===");
|
||||
sendMessage(sender, "=== Top players with the most placed images ===");
|
||||
if (stats.isEmpty()) {
|
||||
sender.sendMessage(ChatColor.RED + "No one on this server has placed a single image!");
|
||||
|
||||
sendMessage(sender, "&cNo one on this server has placed a single image!");
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
int rank = 0;
|
||||
int printedLines = 0;
|
||||
boolean hasShownSender = (senderId == null); // Assume sender has already been shown if it's not a player
|
||||
for (Map.Entry<OfflinePlayer, Integer> item : stats.entrySet()) {
|
||||
OfflinePlayer player = item.getKey();
|
||||
int value = item.getValue();
|
||||
|
||||
final OfflinePlayer player = item.getKey();
|
||||
final int value = item.getValue();
|
||||
++rank;
|
||||
|
||||
// Skip line if irrelevant
|
||||
if (player.getUniqueId().equals(senderId)) {
|
||||
|
||||
hasShownSender = true;
|
||||
} else if (!hasShownSender && printedLines == ITEMS_PER_PAGE-1) {
|
||||
|
||||
} else if (!hasShownSender && printedLines == ITEMS_PER_PAGE - 1) {
|
||||
|
||||
continue; // Leave last line empty for sender rank
|
||||
|
||||
} else if (printedLines >= ITEMS_PER_PAGE) {
|
||||
|
||||
break; // Stop printing players when chat is filled
|
||||
|
||||
}
|
||||
|
||||
// Prepare player name or UUID
|
||||
String playerName = (player.getName() == null) ?
|
||||
ChatColor.GOLD + player.getUniqueId().toString() :
|
||||
ChatColor.GREEN + player.getName();
|
||||
final String playerName = (player.getName() == null) ? "&6" + player.getUniqueId()
|
||||
: "&a" + player.getName();
|
||||
|
||||
// Render player line
|
||||
sender.sendMessage(
|
||||
"" + ChatColor.BOLD + (rank > 1000 ? "1000+" : rank) + ChatColor.RESET + ". " +
|
||||
playerName + ChatColor.RESET +
|
||||
ChatColor.GRAY + " - " + value + " " + (value == 1 ? "image" : "images")
|
||||
);
|
||||
sendMessage(sender, "&l" + (rank > 1000 ? "1000+" : rank) + "&r. " + playerName + "&r&7 - " + value + " "
|
||||
+ (value == 1 ? "image" : "images"));
|
||||
++printedLines;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void giveImageItems(
|
||||
@NotNull CommandSender sender,
|
||||
@NotNull Player player,
|
||||
@NotNull ImageFile image,
|
||||
int amount,
|
||||
int width,
|
||||
int height,
|
||||
int flags
|
||||
) {
|
||||
public static void giveImageItems(@NotNull CommandSender sender, @NotNull Player player, @NotNull ImageFile image,
|
||||
int amount, int width, int height, int flags)
|
||||
{
|
||||
|
||||
// Get image size in blocks
|
||||
Dimension sizeInPixels = image.getSize();
|
||||
final Dimension sizeInPixels = image.getSize();
|
||||
if (sizeInPixels == null) {
|
||||
sender.sendMessage(ChatColor.RED + "The requested file is not a valid image");
|
||||
|
||||
sendMessage(sender, "&cThe requested file is not a valid image");
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
if (height == 0) {
|
||||
height = FakeImage.getProportionalHeight(sizeInPixels, sender, width);
|
||||
|
||||
height = FakeImage.getProportionalHeight(sizeInPixels, width);
|
||||
|
||||
}
|
||||
|
||||
// Create item stack
|
||||
ItemStack itemStack = ItemService.getImageItem(image, amount, width, height, flags);
|
||||
final ItemStack itemStack = ItemService.getImageItem(image, amount, width, height, flags);
|
||||
|
||||
// Add item stack to player's inventory
|
||||
player.getInventory().addItem(itemStack);
|
||||
sender.sendMessage(
|
||||
ChatColor.ITALIC + "Added " + amount + " " +
|
||||
(amount == 1 ? "image item" : "image items") +
|
||||
" to " + player.getName() + "'s inventory"
|
||||
);
|
||||
sendMessage(sender, "&oAdded " + amount + " " + (amount == 1 ? "image item" : "image items") + " to "
|
||||
+ player.getName() + "'s inventory");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import io.josemmo.bukkit.plugin.commands.arguments.*;
|
||||
import io.josemmo.bukkit.plugin.renderer.FakeImage;
|
||||
import io.josemmo.bukkit.plugin.storage.ImageFile;
|
||||
import io.josemmo.bukkit.plugin.utils.Internals;
|
||||
import io.josemmo.bukkit.plugin.utils.Logger;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
@@ -16,16 +15,18 @@ import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class ImageCommandBridge {
|
||||
private static final String COMMAND_NAME = "yamipa";
|
||||
private static final String[] COMMAND_ALIASES = new String[] {"image", "images"};
|
||||
private static final Logger LOGGER = Logger.getLogger("ImageCommandBridge");
|
||||
|
||||
public static final String COMMAND_NAME = "yamipa";
|
||||
public static final String[] COMMAND_ALIASES = new String[] { "image" };
|
||||
|
||||
/**
|
||||
* Register command
|
||||
*
|
||||
* @param plugin Plugin instance
|
||||
*/
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
public static void register(@NotNull YamipaPlugin plugin) {
|
||||
|
||||
CommandDispatcher dispatcher = Internals.getDispatcher();
|
||||
|
||||
// Register command
|
||||
@@ -34,184 +35,210 @@ public class ImageCommandBridge {
|
||||
|
||||
// Register aliases
|
||||
for (String alias : COMMAND_ALIASES) {
|
||||
LiteralCommandNode<?> aliasNode = new LiteralCommandNode(
|
||||
alias,
|
||||
commandNode.getCommand(),
|
||||
commandNode.getRequirement(),
|
||||
commandNode,
|
||||
commandNode.getRedirectModifier(),
|
||||
commandNode.isFork()
|
||||
);
|
||||
|
||||
LiteralCommandNode<?> aliasNode = new LiteralCommandNode(alias, commandNode.getCommand(),
|
||||
commandNode.getRequirement(), commandNode, commandNode.getRedirectModifier(), commandNode.isFork());
|
||||
dispatcher.getRoot().addChild(aliasNode);
|
||||
|
||||
}
|
||||
LOGGER.fine("Registered plugin command and aliases");
|
||||
|
||||
plugin.fine("Registered plugin command and aliases");
|
||||
|
||||
// Fix "minecraft.command.*" permissions
|
||||
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, () -> {
|
||||
|
||||
fixPermissions(COMMAND_NAME);
|
||||
for (String alias : COMMAND_ALIASES) {
|
||||
|
||||
fixPermissions(alias);
|
||||
|
||||
}
|
||||
LOGGER.fine("Fixed command permissions");
|
||||
|
||||
try {
|
||||
|
||||
Internals.syncCommands();
|
||||
|
||||
} catch (RuntimeException e) {
|
||||
|
||||
plugin.warning("Failed to synchronize Bukkit commands with Brigadier");
|
||||
plugin.log(java.util.logging.Level.FINE, e.getMessage(), e);
|
||||
|
||||
}
|
||||
plugin.fine("Fixed command permissions");
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("CodeBlock2Expr")
|
||||
private static @NotNull Command getRootCommand() {
|
||||
|
||||
Command root = new Command(COMMAND_NAME);
|
||||
|
||||
// Help command
|
||||
root.withPermission(
|
||||
"yamipa.command.clear", "yamipa.clear",
|
||||
"yamipa.command.describe", "yamipa.describe",
|
||||
"yamipa.command.download", "yamipa.download",
|
||||
"yamipa.command.give", "yamipa.give",
|
||||
"yamipa.command.list", "yamipa.list",
|
||||
"yamipa.command.place", "yamipa.place",
|
||||
"yamipa.command.remove.own", "yamipa.remove",
|
||||
"yamipa.command.top", "yamipa.top"
|
||||
)
|
||||
.executes((sender, args) -> {
|
||||
ImageCommand.showHelp(sender, (String) args[0]);
|
||||
});
|
||||
root.withPermission("yamipa.command.clear", "yamipa.clear", "yamipa.command.describe", "yamipa.describe",
|
||||
"yamipa.command.download", "yamipa.download", "yamipa.command.give", "yamipa.give",
|
||||
"yamipa.command.list", "yamipa.list", "yamipa.command.place", "yamipa.place",
|
||||
"yamipa.command.remove.own", "yamipa.remove", "yamipa.command.top", "yamipa.top")
|
||||
.executes((sender, args) ->
|
||||
{
|
||||
|
||||
ImageCommand.showHelp(sender, (String) args[0]);
|
||||
|
||||
});
|
||||
|
||||
// Clear command
|
||||
root.addSubcommand("clear")
|
||||
.withPermission("yamipa.command.clear", "yamipa.clear")
|
||||
.withArgument(new IntegerArgument("x"))
|
||||
.withArgument(new IntegerArgument("z"))
|
||||
.withArgument(new WorldArgument("world"))
|
||||
.withArgument(new IntegerArgument("radius", 1))
|
||||
.withArgument(new PlacedByArgument("placedBy"))
|
||||
.executes((sender, args) -> {
|
||||
Location origin = new Location((World) args[3], (int) args[1], 0, (int) args[2]);
|
||||
ImageCommand.clearImages(sender, origin, (int) args[4], (OfflinePlayer) args[5]);
|
||||
});
|
||||
root.addSubcommand("clear")
|
||||
.withPermission("yamipa.command.clear", "yamipa.clear")
|
||||
.withArgument(new IntegerArgument("x"))
|
||||
.withArgument(new IntegerArgument("z"))
|
||||
.withArgument(new WorldArgument("world"))
|
||||
.withArgument(new IntegerArgument("radius", 1))
|
||||
.executes((sender, args) -> {
|
||||
Location origin = new Location((World) args[3], (int) args[1], 0, (int) args[2]);
|
||||
ImageCommand.clearImages(sender, origin, (int) args[4], null);
|
||||
});
|
||||
root.addSubcommand("clear").withPermission("yamipa.command.clear", "yamipa.clear")
|
||||
.withArgument(new IntegerArgument("x")).withArgument(new IntegerArgument("z"))
|
||||
.withArgument(new WorldArgument("world")).withArgument(new IntegerArgument("radius", 1))
|
||||
.withArgument(new PlacedByArgument("placedBy")).executes((sender, args) ->
|
||||
{
|
||||
|
||||
Location origin = new Location((World) args[3], (int) args[1], 0, (int) args[2]);
|
||||
ImageCommand.clearImages(sender, origin, (int) args[4], (OfflinePlayer) args[5]);
|
||||
|
||||
});
|
||||
root.addSubcommand("clear").withPermission("yamipa.command.clear", "yamipa.clear")
|
||||
.withArgument(new IntegerArgument("x")).withArgument(new IntegerArgument("z"))
|
||||
.withArgument(new WorldArgument("world")).withArgument(new IntegerArgument("radius", 1))
|
||||
.executes((sender, args) ->
|
||||
{
|
||||
|
||||
Location origin = new Location((World) args[3], (int) args[1], 0, (int) args[2]);
|
||||
ImageCommand.clearImages(sender, origin, (int) args[4], null);
|
||||
|
||||
});
|
||||
|
||||
// Describe command
|
||||
root.addSubcommand("describe")
|
||||
.withPermission("yamipa.command.describe", "yamipa.describe")
|
||||
.executesPlayer((player, __) -> {
|
||||
ImageCommand.describeImage(player);
|
||||
});
|
||||
root.addSubcommand("describe").withPermission("yamipa.command.describe", "yamipa.describe")
|
||||
.executesPlayer((player, __) ->
|
||||
{
|
||||
|
||||
ImageCommand.describeImage(player);
|
||||
|
||||
});
|
||||
|
||||
// Download command
|
||||
root.addSubcommand("download")
|
||||
.withPermission("yamipa.command.download", "yamipa.download")
|
||||
.withArgument(new StringArgument("url"))
|
||||
.withArgument(new StringArgument("filename"))
|
||||
.executes((sender, args) -> {
|
||||
ImageCommand.downloadImage(sender, (String) args[1], (String) args[2]);
|
||||
});
|
||||
root.addSubcommand("download").withPermission("yamipa.command.download", "yamipa.download")
|
||||
.withArgument(new StringArgument("url")).withArgument(new StringArgument("filename"))
|
||||
.executes((sender, args) ->
|
||||
{
|
||||
|
||||
ImageCommand.downloadImage(sender, (String) args[1], (String) args[2]);
|
||||
|
||||
});
|
||||
|
||||
// Give subcommand
|
||||
root.addSubcommand("give")
|
||||
.withPermission("yamipa.command.give", "yamipa.give")
|
||||
.withArgument(new OnlinePlayerArgument("player"))
|
||||
.withArgument(new ImageFileArgument("filename"))
|
||||
.withArgument(new IntegerArgument("amount", 1, 64))
|
||||
.withArgument(new ImageDimensionArgument("width"))
|
||||
.withArgument(new ImageDimensionArgument("height"))
|
||||
.withArgument(new ImageFlagsArgument("flags", FakeImage.DEFAULT_GIVE_FLAGS))
|
||||
.executes((sender, args) -> {
|
||||
ImageCommand.giveImageItems(sender, (Player) args[1], (ImageFile) args[2], (int) args[3],
|
||||
(int) args[4], (int) args[5], (int) args[6]);
|
||||
});
|
||||
root.addSubcommand("give")
|
||||
.withPermission("yamipa.command.give", "yamipa.give")
|
||||
.withArgument(new OnlinePlayerArgument("player"))
|
||||
.withArgument(new ImageFileArgument("filename"))
|
||||
.withArgument(new IntegerArgument("amount", 1, 64))
|
||||
.withArgument(new ImageDimensionArgument("width"))
|
||||
.withArgument(new ImageDimensionArgument("height"))
|
||||
.executes((sender, args) -> {
|
||||
ImageCommand.giveImageItems(sender, (Player) args[1], (ImageFile) args[2], (int) args[3],
|
||||
(int) args[4], (int) args[5], FakeImage.DEFAULT_GIVE_FLAGS);
|
||||
});
|
||||
root.addSubcommand("give")
|
||||
.withPermission("yamipa.command.give", "yamipa.give")
|
||||
.withArgument(new OnlinePlayerArgument("player"))
|
||||
.withArgument(new ImageFileArgument("filename"))
|
||||
.withArgument(new IntegerArgument("amount", 1, 64))
|
||||
.withArgument(new ImageDimensionArgument("width"))
|
||||
.executes((sender, args) -> {
|
||||
ImageCommand.giveImageItems(sender, (Player) args[1], (ImageFile) args[2], (int) args[3],
|
||||
(int) args[4], 0, FakeImage.DEFAULT_GIVE_FLAGS);
|
||||
});
|
||||
root.addSubcommand("give").withPermission("yamipa.command.give", "yamipa.give")
|
||||
.withArgument(new OnlinePlayerArgument("player")).withArgument(new ImageFileArgument("filename"))
|
||||
.withArgument(new IntegerArgument("amount", 1, 64))
|
||||
.withArgument(new IntegerArgument("width", 1, FakeImage.MAX_DIMENSION))
|
||||
.withArgument(new IntegerArgument("height", 1, FakeImage.MAX_DIMENSION))
|
||||
.withArgument(new ImageFlagsArgument("flags", FakeImage.DEFAULT_GIVE_FLAGS)).executes((sender, args) ->
|
||||
{
|
||||
|
||||
ImageCommand.giveImageItems(sender, (Player) args[1], (ImageFile) args[2], (int) args[3],
|
||||
(int) args[4], (int) args[5], (int) args[6]);
|
||||
|
||||
});
|
||||
root.addSubcommand("give").withPermission("yamipa.command.give", "yamipa.give")
|
||||
.withArgument(new OnlinePlayerArgument("player")).withArgument(new ImageFileArgument("filename"))
|
||||
.withArgument(new IntegerArgument("amount", 1, 64))
|
||||
.withArgument(new IntegerArgument("width", 1, FakeImage.MAX_DIMENSION))
|
||||
.withArgument(new IntegerArgument("height", 1, FakeImage.MAX_DIMENSION)).executes((sender, args) ->
|
||||
{
|
||||
|
||||
ImageCommand.giveImageItems(sender, (Player) args[1], (ImageFile) args[2], (int) args[3],
|
||||
(int) args[4], (int) args[5], FakeImage.DEFAULT_GIVE_FLAGS);
|
||||
|
||||
});
|
||||
root.addSubcommand("give").withPermission("yamipa.command.give", "yamipa.give")
|
||||
.withArgument(new OnlinePlayerArgument("player")).withArgument(new ImageFileArgument("filename"))
|
||||
.withArgument(new IntegerArgument("amount", 1, 64))
|
||||
.withArgument(new IntegerArgument("width", 1, FakeImage.MAX_DIMENSION)).executes((sender, args) ->
|
||||
{
|
||||
|
||||
ImageCommand.giveImageItems(sender, (Player) args[1], (ImageFile) args[2], (int) args[3],
|
||||
(int) args[4], 0, FakeImage.DEFAULT_GIVE_FLAGS);
|
||||
|
||||
});
|
||||
|
||||
// List subcommand
|
||||
root.addSubcommand("list")
|
||||
.withPermission("yamipa.command.list", "yamipa.list")
|
||||
.withArgument(new IntegerArgument("page", 1))
|
||||
.executes((sender, args) -> {
|
||||
ImageCommand.listImages(sender, (int) args[1]);
|
||||
});
|
||||
root.addSubcommand("list")
|
||||
.withPermission("yamipa.command.list", "yamipa.list")
|
||||
.executes((sender, __) -> {
|
||||
boolean isPlayer = (sender instanceof Player);
|
||||
ImageCommand.listImages(sender, isPlayer ? 1 : 0);
|
||||
});
|
||||
root.addSubcommand("list").withPermission("yamipa.command.list", "yamipa.list")
|
||||
.withArgument(new IntegerArgument("page", 1)).executes((sender, args) ->
|
||||
{
|
||||
|
||||
ImageCommand.listImages(sender, (int) args[1]);
|
||||
|
||||
});
|
||||
root.addSubcommand("list").withPermission("yamipa.command.list", "yamipa.list").executes((sender, __) -> {
|
||||
|
||||
boolean isPlayer = (sender instanceof Player);
|
||||
ImageCommand.listImages(sender, isPlayer ? 1 : 0);
|
||||
|
||||
});
|
||||
|
||||
// Place subcommand
|
||||
root.addSubcommand("place")
|
||||
.withPermission("yamipa.command.place", "yamipa.place")
|
||||
.withArgument(new ImageFileArgument("filename"))
|
||||
.withArgument(new ImageDimensionArgument("width"))
|
||||
.withArgument(new ImageDimensionArgument("height"))
|
||||
.withArgument(new ImageFlagsArgument("flags", FakeImage.DEFAULT_PLACE_FLAGS))
|
||||
.executesPlayer((player, args) -> {
|
||||
ImageCommand.placeImage(player, (ImageFile) args[1], (int) args[2], (int) args[3], (int) args[4]);
|
||||
});
|
||||
root.addSubcommand("place")
|
||||
.withPermission("yamipa.command.place", "yamipa.place")
|
||||
.withArgument(new ImageFileArgument("filename"))
|
||||
.withArgument(new ImageDimensionArgument("width"))
|
||||
.withArgument(new ImageDimensionArgument("height"))
|
||||
.executesPlayer((player, args) -> {
|
||||
ImageCommand.placeImage(player, (ImageFile) args[1], (int) args[2], (int) args[3],
|
||||
FakeImage.DEFAULT_PLACE_FLAGS);
|
||||
});
|
||||
root.addSubcommand("place")
|
||||
.withPermission("yamipa.command.place", "yamipa.place")
|
||||
.withArgument(new ImageFileArgument("filename"))
|
||||
.withArgument(new ImageDimensionArgument("width"))
|
||||
.executesPlayer((player, args) -> {
|
||||
ImageCommand.placeImage(player, (ImageFile) args[1], (int) args[2], 0,
|
||||
FakeImage.DEFAULT_PLACE_FLAGS);
|
||||
});
|
||||
root.addSubcommand("place").withPermission("yamipa.command.place", "yamipa.place")
|
||||
.withArgument(new ImageFileArgument("filename"))
|
||||
.withArgument(new IntegerArgument("width", 1, FakeImage.MAX_DIMENSION))
|
||||
.withArgument(new IntegerArgument("height", 1, FakeImage.MAX_DIMENSION))
|
||||
.withArgument(new ImageFlagsArgument("flags", FakeImage.DEFAULT_PLACE_FLAGS))
|
||||
.executesPlayer((player, args) ->
|
||||
{
|
||||
|
||||
ImageCommand.placeImage(player, (ImageFile) args[1], (int) args[2], (int) args[3], (int) args[4]);
|
||||
|
||||
});
|
||||
root.addSubcommand("place").withPermission("yamipa.command.place", "yamipa.place")
|
||||
.withArgument(new ImageFileArgument("filename"))
|
||||
.withArgument(new IntegerArgument("width", 1, FakeImage.MAX_DIMENSION))
|
||||
.withArgument(new IntegerArgument("height", 1, FakeImage.MAX_DIMENSION))
|
||||
.executesPlayer((player, args) ->
|
||||
{
|
||||
|
||||
ImageCommand.placeImage(player, (ImageFile) args[1], (int) args[2], (int) args[3],
|
||||
FakeImage.DEFAULT_PLACE_FLAGS);
|
||||
|
||||
});
|
||||
root.addSubcommand("place").withPermission("yamipa.command.place", "yamipa.place")
|
||||
.withArgument(new ImageFileArgument("filename"))
|
||||
.withArgument(new IntegerArgument("width", 1, FakeImage.MAX_DIMENSION)).executesPlayer((player, args) ->
|
||||
{
|
||||
|
||||
ImageCommand.placeImage(player, (ImageFile) args[1], (int) args[2], 0,
|
||||
FakeImage.DEFAULT_PLACE_FLAGS);
|
||||
|
||||
});
|
||||
|
||||
// Remove subcommand
|
||||
root.addSubcommand("remove")
|
||||
.withPermission("yamipa.command.remove.own", "yamipa.remove")
|
||||
.executesPlayer((player, __) -> {
|
||||
ImageCommand.removeImage(player);
|
||||
});
|
||||
root.addSubcommand("remove").withPermission("yamipa.command.remove.own", "yamipa.remove")
|
||||
.executesPlayer((player, __) ->
|
||||
{
|
||||
|
||||
ImageCommand.removeImage(player);
|
||||
|
||||
});
|
||||
|
||||
// Top subcommand
|
||||
root.addSubcommand("top")
|
||||
.withPermission("yamipa.command.top", "yamipa.top")
|
||||
.executes((sender, __) -> {
|
||||
ImageCommand.showTopPlayers(sender);
|
||||
});
|
||||
root.addSubcommand("top").withPermission("yamipa.command.top", "yamipa.top").executes((sender, __) -> {
|
||||
|
||||
ImageCommand.showTopPlayers(sender);
|
||||
|
||||
});
|
||||
|
||||
return root;
|
||||
|
||||
}
|
||||
|
||||
private static void fixPermissions(@NotNull String commandName) {
|
||||
|
||||
org.bukkit.command.Command command = Internals.getCommandMap().getCommand(commandName);
|
||||
if (command != null) { // Command may have been aliased to null in "commands.yml"
|
||||
|
||||
command.setPermission(null);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,63 +4,61 @@ import com.mojang.brigadier.LiteralMessage;
|
||||
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
|
||||
import com.mojang.brigadier.suggestion.Suggestions;
|
||||
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public abstract class Argument {
|
||||
|
||||
protected final String name;
|
||||
|
||||
/**
|
||||
* Argument constructor
|
||||
*
|
||||
* @param name Argument name
|
||||
*/
|
||||
public Argument(@NotNull String name) {
|
||||
|
||||
this.name = name;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get argument name
|
||||
*
|
||||
* @return Argument name
|
||||
*/
|
||||
public @NotNull String getName() {
|
||||
|
||||
return name;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Build argument
|
||||
* @return Argument builder instance
|
||||
*
|
||||
* @return Required Argument Builder instance
|
||||
*/
|
||||
public abstract @NotNull RequiredArgumentBuilder<?, ?> build();
|
||||
|
||||
/**
|
||||
* Suggest argument values
|
||||
* @param sender Command sender
|
||||
* @param builder Suggestions builder instance
|
||||
* @return Suggestions
|
||||
*/
|
||||
public @NotNull CompletableFuture<Suggestions> suggest(@NotNull CommandSender sender, @NotNull SuggestionsBuilder builder) {
|
||||
return builder.buildFuture();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse argument value
|
||||
* @param sender Command sender
|
||||
* @param rawValue Argument value provided by Brigadier
|
||||
* @return Parsed argument value
|
||||
*
|
||||
* @param sender Command sender
|
||||
* @param rawValue Argument value provided by Brigadier
|
||||
* @return Parsed argument value
|
||||
*/
|
||||
public @NotNull Object parse(@NotNull CommandSender sender, @NotNull Object rawValue) throws CommandSyntaxException {
|
||||
public @NotNull Object parse(@NotNull CommandSender sender, @NotNull Object rawValue)
|
||||
throws CommandSyntaxException
|
||||
{
|
||||
|
||||
return rawValue;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new syntax exception
|
||||
* @param message Message to show
|
||||
* @return Syntax exception
|
||||
*/
|
||||
protected @NotNull CommandSyntaxException newException(@NotNull String message) {
|
||||
|
||||
return new SimpleCommandExceptionType(new LiteralMessage(message)).create();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package io.josemmo.bukkit.plugin.commands.arguments;
|
||||
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import io.josemmo.bukkit.plugin.renderer.FakeImage;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class ImageDimensionArgument extends IntegerArgument {
|
||||
/**
|
||||
* Dimension Argument constructor
|
||||
* @param name Argument name
|
||||
*/
|
||||
public ImageDimensionArgument(@NotNull String name) {
|
||||
super(name, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Object parse(@NotNull CommandSender sender, @NotNull Object rawValue) throws CommandSyntaxException {
|
||||
int maxDimension = FakeImage.getMaxImageDimension(sender);
|
||||
int value = (int) rawValue;
|
||||
if (value > maxDimension) {
|
||||
throw newException("Image cannot be larger than " + maxDimension + "x" + maxDimension);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -1,41 +1,64 @@
|
||||
package io.josemmo.bukkit.plugin.commands.arguments;
|
||||
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import com.mojang.brigadier.suggestion.Suggestions;
|
||||
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
|
||||
import io.josemmo.bukkit.plugin.YamipaPlugin;
|
||||
import io.josemmo.bukkit.plugin.storage.ImageFile;
|
||||
import io.josemmo.bukkit.plugin.storage.ImageStorage;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class ImageFileArgument extends StringArgument {
|
||||
|
||||
/**
|
||||
* Image File Argument constructor
|
||||
*
|
||||
* @param name Argument name
|
||||
*/
|
||||
public ImageFileArgument(@NotNull String name) {
|
||||
|
||||
super(name);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull CompletableFuture<Suggestions> suggest(@NotNull CommandSender sender, @NotNull SuggestionsBuilder builder) {
|
||||
for (String filename : YamipaPlugin.getInstance().getStorage().getFilenames(sender)) {
|
||||
builder.suggest(StringArgumentType.escapeIfRequired(filename));
|
||||
}
|
||||
return builder.buildFuture();
|
||||
public @NotNull RequiredArgumentBuilder<?, ?> build() {
|
||||
|
||||
return super.build().suggests(this::getSuggestions);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Object parse(@NotNull CommandSender sender, @NotNull Object rawValue) throws CommandSyntaxException {
|
||||
String filename = (String) rawValue;
|
||||
ImageStorage storage = YamipaPlugin.getInstance().getStorage();
|
||||
ImageFile imageFile = storage.get(filename);
|
||||
if (imageFile == null || !storage.isPathAllowed(filename, sender)) {
|
||||
public @NotNull Object parse(@NotNull CommandSender sender, @NotNull Object rawValue)
|
||||
throws CommandSyntaxException
|
||||
{
|
||||
|
||||
ImageFile imageFile = YamipaPlugin.getInstance().getStorage().get((String) rawValue);
|
||||
if (imageFile == null) {
|
||||
|
||||
throw newException("Image file does not exist");
|
||||
|
||||
}
|
||||
|
||||
return imageFile;
|
||||
|
||||
}
|
||||
|
||||
private @NotNull CompletableFuture<Suggestions> getSuggestions(@NotNull CommandContext<?> ctx,
|
||||
@NotNull SuggestionsBuilder builder)
|
||||
{
|
||||
|
||||
for (String filename : YamipaPlugin.getInstance().getStorage().getAllFilenames()) {
|
||||
|
||||
builder.suggest(filename);
|
||||
|
||||
}
|
||||
|
||||
return builder.buildFuture();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package io.josemmo.bukkit.plugin.commands.arguments;
|
||||
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import com.mojang.brigadier.suggestion.Suggestions;
|
||||
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
|
||||
@@ -13,58 +14,75 @@ import java.util.Collections;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class ImageFlagsArgument extends Argument {
|
||||
|
||||
private final int defaultFlags;
|
||||
|
||||
/**
|
||||
* Image File Argument constructor
|
||||
*
|
||||
* @param name Argument name
|
||||
* @param defaultFlags Default flags
|
||||
*/
|
||||
public ImageFlagsArgument(@NotNull String name, int defaultFlags) {
|
||||
|
||||
super(name);
|
||||
this.defaultFlags = defaultFlags;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull RequiredArgumentBuilder<?, ?> build() {
|
||||
return RequiredArgumentBuilder.argument(name, StringArgumentType.greedyString());
|
||||
|
||||
return RequiredArgumentBuilder.argument(name, StringArgumentType.greedyString()).suggests(this::getSuggestions);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull CompletableFuture<Suggestions> suggest(@NotNull CommandSender sender, @NotNull SuggestionsBuilder builder) {
|
||||
private @NotNull CompletableFuture<Suggestions> getSuggestions(@NotNull CommandContext<?> ctx,
|
||||
@NotNull SuggestionsBuilder builder)
|
||||
{
|
||||
|
||||
String input = builder.getRemaining().replaceAll("[^A-Z+\\-,]", "");
|
||||
int lastIndex = Collections.max(
|
||||
Arrays.asList(input.lastIndexOf(","), input.lastIndexOf("+"), input.lastIndexOf("-"))
|
||||
);
|
||||
input = input.substring(0, lastIndex+1);
|
||||
int lastIndex = Collections
|
||||
.max(Arrays.asList(input.lastIndexOf(","), input.lastIndexOf("+"), input.lastIndexOf("-")));
|
||||
input = input.substring(0, lastIndex + 1);
|
||||
|
||||
// Add suggestions
|
||||
String[] values = new String[] {"ANIM", "REMO", "DROP", "GLOW"};
|
||||
String[] values = new String[] { "ANIM", "REMO", "DROP", "GLOW" };
|
||||
for (String value : values) {
|
||||
|
||||
builder.suggest(input + value);
|
||||
|
||||
}
|
||||
|
||||
return builder.buildFuture();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Object parse(@NotNull CommandSender sender, @NotNull Object rawValue) throws CommandSyntaxException {
|
||||
public @NotNull Object parse(@NotNull CommandSender sender, @NotNull Object rawValue)
|
||||
throws CommandSyntaxException
|
||||
{
|
||||
|
||||
String value = (String) rawValue;
|
||||
int parsedValue = defaultFlags;
|
||||
|
||||
// Load flags from parts
|
||||
String[] parts = value.split(",");
|
||||
for (String part : parts) {
|
||||
|
||||
// Parse modifier
|
||||
String modifier = "";
|
||||
if (part.startsWith("+") || part.startsWith("-")) {
|
||||
|
||||
modifier = part.substring(0, 1);
|
||||
part = part.substring(1);
|
||||
|
||||
}
|
||||
|
||||
// Parse flag name
|
||||
int flag;
|
||||
switch (part) {
|
||||
|
||||
case "ANIM":
|
||||
flag = FakeImage.FLAG_ANIMATABLE;
|
||||
break;
|
||||
@@ -79,18 +97,28 @@ public class ImageFlagsArgument extends Argument {
|
||||
break;
|
||||
default:
|
||||
throw newException("Unrecognized flag \"" + part + "\"");
|
||||
|
||||
}
|
||||
|
||||
// Apply flag to value
|
||||
if (modifier.equals("+")) {
|
||||
|
||||
parsedValue |= flag;
|
||||
|
||||
} else if (modifier.equals("-")) {
|
||||
|
||||
parsedValue &= ~flag;
|
||||
|
||||
} else {
|
||||
|
||||
parsedValue = flag;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return parsedValue;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,40 +5,53 @@ import com.mojang.brigadier.builder.RequiredArgumentBuilder;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class IntegerArgument extends Argument {
|
||||
|
||||
private final int min;
|
||||
private final int max;
|
||||
|
||||
/**
|
||||
* Integer Argument constructor
|
||||
*
|
||||
* @param name Argument name
|
||||
* @param min Minimum allowed value
|
||||
* @param max Maximum allowed value
|
||||
*/
|
||||
public IntegerArgument(@NotNull String name, int min, int max) {
|
||||
|
||||
super(name);
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Integer Argument constructor
|
||||
*
|
||||
* @param name Argument name
|
||||
* @param min Minimum allowed value
|
||||
*/
|
||||
public IntegerArgument(@NotNull String name, int min) {
|
||||
|
||||
this(name, min, Integer.MAX_VALUE);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Integer Argument constructor
|
||||
*
|
||||
* @param name Argument name
|
||||
*/
|
||||
public IntegerArgument(@NotNull String name) {
|
||||
|
||||
this(name, Integer.MIN_VALUE, Integer.MAX_VALUE);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull RequiredArgumentBuilder<?, ?> build() {
|
||||
|
||||
return RequiredArgumentBuilder.argument(name, IntegerArgumentType.integer(min, max));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+33
-4
@@ -1,5 +1,7 @@
|
||||
package io.josemmo.bukkit.plugin.commands.arguments;
|
||||
|
||||
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import com.mojang.brigadier.suggestion.Suggestions;
|
||||
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
|
||||
@@ -12,35 +14,62 @@ import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class OnlinePlayerArgument extends StringArgument {
|
||||
|
||||
/**
|
||||
* Online player argument constructor
|
||||
*
|
||||
* @param name Argument name
|
||||
*/
|
||||
public OnlinePlayerArgument(@NotNull String name) {
|
||||
|
||||
super(name);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull CompletableFuture<Suggestions> suggest(@NotNull CommandSender sender, @NotNull SuggestionsBuilder builder) {
|
||||
getAllowedValues().keySet().forEach(builder::suggest);
|
||||
return builder.buildFuture();
|
||||
public @NotNull RequiredArgumentBuilder<?, ?> build() {
|
||||
|
||||
return super.build().suggests(this::getSuggestions);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Object parse(@NotNull CommandSender sender, @NotNull Object rawValue) throws CommandSyntaxException {
|
||||
public @NotNull Object parse(@NotNull CommandSender sender, @NotNull Object rawValue)
|
||||
throws CommandSyntaxException
|
||||
{
|
||||
|
||||
Player player = getAllowedValues().get((String) rawValue);
|
||||
if (player == null) {
|
||||
|
||||
throw newException("Expected online player (name or UUID)");
|
||||
|
||||
}
|
||||
|
||||
return player;
|
||||
|
||||
}
|
||||
|
||||
private @NotNull CompletableFuture<Suggestions> getSuggestions(@NotNull CommandContext<?> ctx,
|
||||
@NotNull SuggestionsBuilder builder)
|
||||
{
|
||||
|
||||
getAllowedValues().keySet().forEach(builder::suggest);
|
||||
return builder.buildFuture();
|
||||
|
||||
}
|
||||
|
||||
private @NotNull Map<String, Player> getAllowedValues() {
|
||||
|
||||
Map<String, Player> values = new HashMap<>();
|
||||
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||
|
||||
values.put(player.getUniqueId().toString(), player);
|
||||
values.put(player.getName(), player);
|
||||
|
||||
}
|
||||
|
||||
return values;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package io.josemmo.bukkit.plugin.commands.arguments;
|
||||
|
||||
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import com.mojang.brigadier.suggestion.Suggestions;
|
||||
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
|
||||
@@ -13,38 +15,67 @@ import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class PlacedByArgument extends StringArgument {
|
||||
|
||||
/**
|
||||
* Placed by Argument constructor
|
||||
*
|
||||
* @param name Argument name
|
||||
*/
|
||||
public PlacedByArgument(@NotNull String name) {
|
||||
|
||||
super(name);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull CompletableFuture<Suggestions> suggest(@NotNull CommandSender sender, @NotNull SuggestionsBuilder builder) {
|
||||
getAllowedValues().keySet().forEach(builder::suggest);
|
||||
return builder.buildFuture();
|
||||
public @NotNull RequiredArgumentBuilder<?, ?> build() {
|
||||
|
||||
return super.build().suggests(this::getSuggestions);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Object parse(@NotNull CommandSender sender, @NotNull Object rawValue) throws CommandSyntaxException {
|
||||
public @NotNull Object parse(@NotNull CommandSender sender, @NotNull Object rawValue)
|
||||
throws CommandSyntaxException
|
||||
{
|
||||
|
||||
OfflinePlayer player = getAllowedValues().get((String) rawValue);
|
||||
if (player == null) {
|
||||
|
||||
throw newException("Expected player with placed images (name or UUID)");
|
||||
|
||||
}
|
||||
|
||||
return player;
|
||||
|
||||
}
|
||||
|
||||
private @NotNull CompletableFuture<Suggestions> getSuggestions(@NotNull CommandContext<?> ctx,
|
||||
@NotNull SuggestionsBuilder builder)
|
||||
{
|
||||
|
||||
getAllowedValues().keySet().forEach(builder::suggest);
|
||||
return builder.buildFuture();
|
||||
|
||||
}
|
||||
|
||||
private @NotNull Map<String, OfflinePlayer> getAllowedValues() {
|
||||
|
||||
Map<String, OfflinePlayer> values = new HashMap<>();
|
||||
ImageRenderer renderer = YamipaPlugin.getInstance().getRenderer();
|
||||
for (OfflinePlayer player : renderer.getPlayersWithPlacedImages()) {
|
||||
|
||||
values.put(player.getUniqueId().toString(), player);
|
||||
if (player.getName() != null) {
|
||||
|
||||
values.put(player.getName(), player);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return values;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,16 +5,23 @@ import com.mojang.brigadier.builder.RequiredArgumentBuilder;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class StringArgument extends Argument {
|
||||
|
||||
/**
|
||||
* String Argument constructor
|
||||
*
|
||||
* @param name Argument name
|
||||
*/
|
||||
public StringArgument(@NotNull String name) {
|
||||
|
||||
super(name);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull RequiredArgumentBuilder<?, ?> build() {
|
||||
|
||||
return RequiredArgumentBuilder.argument(name, StringArgumentType.string());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package io.josemmo.bukkit.plugin.commands.arguments;
|
||||
|
||||
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import com.mojang.brigadier.suggestion.Suggestions;
|
||||
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
|
||||
@@ -10,28 +12,53 @@ import org.jetbrains.annotations.NotNull;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class WorldArgument extends StringArgument {
|
||||
|
||||
/**
|
||||
* World Argument constructor
|
||||
*
|
||||
* @param name Argument name
|
||||
*/
|
||||
public WorldArgument(@NotNull String name) {
|
||||
|
||||
super(name);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull CompletableFuture<Suggestions> suggest(@NotNull CommandSender sender, @NotNull SuggestionsBuilder builder) {
|
||||
for (World world : Bukkit.getWorlds()) {
|
||||
builder.suggest(world.getName());
|
||||
}
|
||||
return builder.buildFuture();
|
||||
public @NotNull RequiredArgumentBuilder<?, ?> build() {
|
||||
|
||||
return super.build().suggests(this::getSuggestions);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Object parse(@NotNull CommandSender sender, @NotNull Object rawValue) throws CommandSyntaxException {
|
||||
public @NotNull Object parse(@NotNull CommandSender sender, @NotNull Object rawValue)
|
||||
throws CommandSyntaxException
|
||||
{
|
||||
|
||||
World world = Bukkit.getWorld((String) rawValue);
|
||||
if (world == null) {
|
||||
|
||||
throw newException("Expected world name");
|
||||
|
||||
}
|
||||
|
||||
return world;
|
||||
|
||||
}
|
||||
|
||||
private @NotNull CompletableFuture<Suggestions> getSuggestions(@NotNull CommandContext<?> ctx,
|
||||
@NotNull SuggestionsBuilder builder)
|
||||
{
|
||||
|
||||
for (World world : Bukkit.getWorlds()) {
|
||||
|
||||
builder.suggest(world.getName());
|
||||
|
||||
}
|
||||
|
||||
return builder.buildFuture();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,25 +1,39 @@
|
||||
package io.josemmo.bukkit.plugin.packets;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.wrappers.EnumWrappers;
|
||||
import com.comphenix.protocol.wrappers.WrappedChatComponent;
|
||||
|
||||
import io.josemmo.bukkit.plugin.utils.Internals;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||
import net.trueog.utilitiesog.UtilitiesOG;
|
||||
|
||||
public class ActionBarPacket extends PacketContainer {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final boolean USE_TITLE = (Internals.MINECRAFT_VERSION < 17);
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public ActionBarPacket() {
|
||||
|
||||
super(USE_TITLE ? PacketType.Play.Server.TITLE : PacketType.Play.Server.SET_ACTION_BAR_TEXT);
|
||||
if (USE_TITLE) {
|
||||
|
||||
getTitleActions().write(0, EnumWrappers.TitleAction.ACTIONBAR);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public @NotNull ActionBarPacket setText(@NotNull String text) {
|
||||
getChatComponents().write(0, WrappedChatComponent.fromText(text));
|
||||
|
||||
final String serialized = GsonComponentSerializer.gson().serialize(UtilitiesOG.trueogColorize(text));
|
||||
getChatComponents().write(0, WrappedChatComponent.fromJson(serialized));
|
||||
return this;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,24 +1,42 @@
|
||||
package io.josemmo.bukkit.plugin.packets;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
|
||||
import io.josemmo.bukkit.plugin.utils.Internals;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import java.util.Collections;
|
||||
|
||||
public class DestroyEntityPacket extends PacketContainer {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public DestroyEntityPacket() {
|
||||
|
||||
super(PacketType.Play.Server.ENTITY_DESTROY);
|
||||
|
||||
}
|
||||
|
||||
public @NotNull DestroyEntityPacket setId(int id) {
|
||||
|
||||
if (Internals.MINECRAFT_VERSION < 17) { // Minecraft 1.16.x
|
||||
getIntegerArrays().write(0, new int[]{id});
|
||||
|
||||
getIntegerArrays().write(0, new int[] { id });
|
||||
|
||||
} else if (Internals.MINECRAFT_VERSION < 17.1) { // Minecraft 1.17
|
||||
|
||||
getIntegers().write(0, id);
|
||||
|
||||
} else { // Minecraft 1.17.x
|
||||
|
||||
getIntLists().write(0, Collections.singletonList(id));
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,18 +1,23 @@
|
||||
package io.josemmo.bukkit.plugin.packets;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.bukkit.Rotation;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.wrappers.BukkitConverters;
|
||||
import com.comphenix.protocol.wrappers.WrappedDataValue;
|
||||
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
|
||||
|
||||
import io.josemmo.bukkit.plugin.utils.Internals;
|
||||
import org.bukkit.Rotation;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class EntityMetadataPacket extends PacketContainer {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final boolean USE_DATA_WATCHER;
|
||||
private static final int ITEM_INDEX;
|
||||
private static final int ROTATION_INDEX;
|
||||
@@ -20,68 +25,100 @@ public class EntityMetadataPacket extends PacketContainer {
|
||||
private final List<WrappedDataValue> values = new ArrayList<>(); // For >= 1.19.3
|
||||
|
||||
static {
|
||||
|
||||
USE_DATA_WATCHER = (Internals.MINECRAFT_VERSION < 19.3f);
|
||||
ITEM_INDEX = (Internals.MINECRAFT_VERSION < 17) ? 7 : 8;
|
||||
ROTATION_INDEX = ITEM_INDEX + 1;
|
||||
|
||||
}
|
||||
|
||||
public EntityMetadataPacket() {
|
||||
|
||||
super(PacketType.Play.Server.ENTITY_METADATA);
|
||||
|
||||
}
|
||||
|
||||
public @NotNull EntityMetadataPacket setId(int id) {
|
||||
|
||||
getIntegers().write(0, id);
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
public @NotNull EntityMetadataPacket setFlags(byte flags) {
|
||||
WrappedDataWatcher.Serializer serializer = WrappedDataWatcher.Registry.get(Byte.class);
|
||||
|
||||
final WrappedDataWatcher.Serializer serializer = WrappedDataWatcher.Registry.get(Byte.class);
|
||||
if (USE_DATA_WATCHER) {
|
||||
|
||||
dataWatcher.setObject(new WrappedDataWatcher.WrappedDataWatcherObject(0, serializer), flags);
|
||||
|
||||
} else {
|
||||
|
||||
values.add(new WrappedDataValue(0, serializer, flags));
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
public @NotNull EntityMetadataPacket setInvisible(boolean invisible) {
|
||||
int flags = invisible ? 0x20 : 0x00;
|
||||
|
||||
final int flags = invisible ? 0x20 : 0x00;
|
||||
return setFlags((byte) flags);
|
||||
|
||||
}
|
||||
|
||||
public @NotNull EntityMetadataPacket setItem(@NotNull ItemStack item) {
|
||||
WrappedDataWatcher.Serializer serializer = WrappedDataWatcher.Registry.getItemStackSerializer(false);
|
||||
|
||||
final WrappedDataWatcher.Serializer serializer = WrappedDataWatcher.Registry.getItemStackSerializer(false);
|
||||
if (USE_DATA_WATCHER) {
|
||||
|
||||
dataWatcher.setObject(new WrappedDataWatcher.WrappedDataWatcherObject(ITEM_INDEX, serializer), item);
|
||||
|
||||
} else {
|
||||
values.add(new WrappedDataValue(
|
||||
ITEM_INDEX,
|
||||
serializer,
|
||||
BukkitConverters.getItemStackConverter().getGeneric(item)
|
||||
));
|
||||
|
||||
values.add(new WrappedDataValue(ITEM_INDEX, serializer,
|
||||
BukkitConverters.getItemStackConverter().getGeneric(item)));
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
public @NotNull EntityMetadataPacket setRotation(@NotNull Rotation rotation) {
|
||||
WrappedDataWatcher.Serializer serializer = WrappedDataWatcher.Registry.get(Integer.class);
|
||||
|
||||
final WrappedDataWatcher.Serializer serializer = WrappedDataWatcher.Registry.get(Integer.class);
|
||||
if (USE_DATA_WATCHER) {
|
||||
dataWatcher.setObject(
|
||||
new WrappedDataWatcher.WrappedDataWatcherObject(ROTATION_INDEX, serializer),
|
||||
rotation.ordinal()
|
||||
);
|
||||
|
||||
dataWatcher.setObject(new WrappedDataWatcher.WrappedDataWatcherObject(ROTATION_INDEX, serializer),
|
||||
rotation.ordinal());
|
||||
|
||||
} else {
|
||||
|
||||
values.add(new WrappedDataValue(ROTATION_INDEX, serializer, rotation.ordinal()));
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
public @NotNull EntityMetadataPacket build() {
|
||||
|
||||
if (USE_DATA_WATCHER) {
|
||||
|
||||
getWatchableCollectionModifier().write(0, dataWatcher.getWatchableObjects());
|
||||
|
||||
} else {
|
||||
|
||||
getDataValueCollectionModifier().write(0, values);
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,73 +1,98 @@
|
||||
package io.josemmo.bukkit.plugin.packets;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
|
||||
import io.josemmo.bukkit.plugin.utils.Internals;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class MapDataPacket extends PacketContainer {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final int LOCKED_INDEX;
|
||||
private @Nullable StructureModifier<?> mapDataModifier;
|
||||
|
||||
static {
|
||||
|
||||
LOCKED_INDEX = (Internals.MINECRAFT_VERSION < 17) ? 1 : 0;
|
||||
|
||||
}
|
||||
|
||||
public MapDataPacket() {
|
||||
|
||||
super(PacketType.Play.Server.MAP);
|
||||
getModifier().writeDefaults();
|
||||
|
||||
if (Internals.MINECRAFT_VERSION < 17) {
|
||||
|
||||
// Disable tracking position
|
||||
getBooleans().write(0, false);
|
||||
|
||||
} else {
|
||||
|
||||
// Create modifier for map data instance
|
||||
Class<?> mapDataType = getModifier().getField(4).getType();
|
||||
Object mapDataInstance = getModifier().read(4);
|
||||
final Class<?> mapDataType = getModifier().getField(4).getType();
|
||||
final Object mapDataInstance = getModifier().read(4);
|
||||
mapDataModifier = new StructureModifier<>(mapDataType).withTarget(mapDataInstance);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public @NotNull MapDataPacket setId(int id) {
|
||||
|
||||
getIntegers().write(0, id);
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
public @NotNull MapDataPacket setArea(int columns, int rows, int x, int z) {
|
||||
|
||||
if (mapDataModifier == null) {
|
||||
getIntegers()
|
||||
.write(1, x)
|
||||
.write(2, z)
|
||||
.write(3, columns)
|
||||
.write (4, rows);
|
||||
|
||||
getIntegers().write(1, x).write(2, z).write(3, columns).write(4, rows);
|
||||
|
||||
} else {
|
||||
mapDataModifier.withType(Integer.TYPE)
|
||||
.write(0, x)
|
||||
.write(1, z)
|
||||
.write(2, columns)
|
||||
.write(3, rows);
|
||||
|
||||
mapDataModifier.withType(Integer.TYPE).write(0, x).write(1, z).write(2, columns).write(3, rows);
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
public @NotNull MapDataPacket setScale(int scale) {
|
||||
|
||||
getBytes().write(0, (byte) scale);
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
public @NotNull MapDataPacket setLocked(boolean locked) {
|
||||
|
||||
getBooleans().write(LOCKED_INDEX, locked);
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
public @NotNull MapDataPacket setPixels(byte[] pixels) {
|
||||
|
||||
if (mapDataModifier == null) {
|
||||
|
||||
getByteArrays().write(0, pixels);
|
||||
|
||||
} else {
|
||||
|
||||
mapDataModifier.withType(byte[].class).write(0, pixels);
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,69 +1,87 @@
|
||||
package io.josemmo.bukkit.plugin.packets;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
|
||||
import io.josemmo.bukkit.plugin.utils.Internals;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import java.util.UUID;
|
||||
|
||||
public class SpawnEntityPacket extends PacketContainer {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final boolean ROTATION_AS_BYTES;
|
||||
private static final int DATA_INDEX;
|
||||
|
||||
static {
|
||||
|
||||
if (Internals.MINECRAFT_VERSION < 19) {
|
||||
|
||||
ROTATION_AS_BYTES = false;
|
||||
DATA_INDEX = 6;
|
||||
|
||||
} else {
|
||||
|
||||
DATA_INDEX = 4;
|
||||
ROTATION_AS_BYTES = true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public SpawnEntityPacket() {
|
||||
|
||||
super(PacketType.Play.Server.SPAWN_ENTITY);
|
||||
getIntegers()
|
||||
.write(1, 0)
|
||||
.write(2, 0)
|
||||
.write(3, 0);
|
||||
getUUIDs()
|
||||
.write(0, UUID.randomUUID());
|
||||
getIntegers().write(1, 0).write(2, 0).write(3, 0);
|
||||
getUUIDs().write(0, UUID.randomUUID());
|
||||
|
||||
}
|
||||
|
||||
public @NotNull SpawnEntityPacket setEntityType(@NotNull EntityType type) {
|
||||
|
||||
getEntityTypeModifier().write(0, type);
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
public @NotNull SpawnEntityPacket setId(int id) {
|
||||
|
||||
getIntegers().write(0, id);
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
public @NotNull SpawnEntityPacket setRotation(int pitch, int yaw) {
|
||||
|
||||
if (ROTATION_AS_BYTES) {
|
||||
getBytes()
|
||||
.write(0, (byte) pitch)
|
||||
.write(1, (byte) yaw);
|
||||
|
||||
getBytes().write(0, (byte) pitch).write(1, (byte) yaw);
|
||||
|
||||
} else {
|
||||
getIntegers()
|
||||
.write(4, pitch)
|
||||
.write(5, yaw);
|
||||
|
||||
getIntegers().write(4, pitch).write(5, yaw);
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
public @NotNull SpawnEntityPacket setData(int data) {
|
||||
|
||||
getIntegers().write(DATA_INDEX, data);
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
public @NotNull SpawnEntityPacket setPosition(double x, double y, double z) {
|
||||
getDoubles()
|
||||
.write(0, x)
|
||||
.write(1, y)
|
||||
.write(2, z);
|
||||
|
||||
getDoubles().write(0, x).write(1, y).write(2, z);
|
||||
return this;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,33 +8,46 @@ import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
|
||||
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
|
||||
import io.josemmo.bukkit.plugin.YamipaPlugin;
|
||||
import io.josemmo.bukkit.plugin.utils.Internals;
|
||||
import io.josemmo.bukkit.plugin.utils.Logger;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public abstract class FakeEntity {
|
||||
private static final Logger LOGGER = Logger.getLogger("FakeEntity");
|
||||
private static final ProtocolManager CONNECTION = ProtocolLibrary.getProtocolManager();
|
||||
private static @Nullable PlayerInjectionHandler PLAYER_INJECTION_HANDLER;
|
||||
private static boolean READY = false;
|
||||
|
||||
protected static final YamipaPlugin plugin = YamipaPlugin.getInstance();
|
||||
private static final ProtocolManager connection = ProtocolLibrary.getProtocolManager();
|
||||
private static PlayerInjectionHandler playerInjectionHandler = null;
|
||||
private static boolean ready = false;
|
||||
|
||||
static {
|
||||
|
||||
try {
|
||||
for (Field field : CONNECTION.getClass().getDeclaredFields()) {
|
||||
|
||||
for (Field field : connection.getClass().getDeclaredFields()) {
|
||||
|
||||
if (field.getType().equals(PlayerInjectionHandler.class)) {
|
||||
|
||||
field.setAccessible(true);
|
||||
PLAYER_INJECTION_HANDLER = (PlayerInjectionHandler) field.get(CONNECTION);
|
||||
playerInjectionHandler = (PlayerInjectionHandler) field.get(connection);
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
if (PLAYER_INJECTION_HANDLER == null) {
|
||||
|
||||
if (playerInjectionHandler == null) {
|
||||
|
||||
throw new RuntimeException("No valid candidate field found in ProtocolManager");
|
||||
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
LOGGER.severe("Failed to get PlayerInjectionHandler from ProtocolLib", e);
|
||||
|
||||
plugin.log(Level.SEVERE, "Failed to get PlayerInjectionHandler from ProtocolLib", e);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -43,82 +56,126 @@ public abstract class FakeEntity {
|
||||
* NOTE: Will wait synchronously, blocking the invoker thread
|
||||
*/
|
||||
public static synchronized void waitForProtocolLib() {
|
||||
if (READY) {
|
||||
|
||||
if (ready) {
|
||||
|
||||
// ProtocolLib is ready
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
int retry = 0;
|
||||
while (true) {
|
||||
|
||||
try {
|
||||
|
||||
WrappedDataWatcher.Registry.get(Byte.class);
|
||||
READY = true;
|
||||
ready = true;
|
||||
break;
|
||||
|
||||
} catch (Exception e) {
|
||||
|
||||
if (++retry > 20) {
|
||||
|
||||
// Exhausted max. retries
|
||||
throw e;
|
||||
|
||||
}
|
||||
|
||||
tryToSleep(200);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to sleep
|
||||
* <p>
|
||||
* NOTE: Will wait synchronously, blocking the invoker thread
|
||||
*
|
||||
* @param ms Delay in milliseconds
|
||||
*/
|
||||
protected static void tryToSleep(long ms) {
|
||||
|
||||
try {
|
||||
|
||||
Thread.sleep(ms);
|
||||
|
||||
} catch (Exception __) {
|
||||
|
||||
// Fail silently
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to send packet
|
||||
*
|
||||
* @param player Player who will receive the packet
|
||||
* @param packet Packet to send
|
||||
*/
|
||||
protected static void tryToSendPacket(@NotNull Player player, @NotNull PacketContainer packet) {
|
||||
|
||||
try {
|
||||
if (PLAYER_INJECTION_HANDLER == null) { // Use single-threaded packet sending if reflection failed
|
||||
CONNECTION.sendServerPacket(player, packet);
|
||||
|
||||
if (playerInjectionHandler == null) { // Use single-threaded packet sending if reflection failed
|
||||
|
||||
connection.sendServerPacket(player, packet);
|
||||
|
||||
} else { // Use non-blocking packet sending if available (faster, the expected case)
|
||||
PLAYER_INJECTION_HANDLER.sendServerPacket(player, packet, null, false);
|
||||
|
||||
playerInjectionHandler.sendServerPacket(player, packet, null, false);
|
||||
|
||||
}
|
||||
|
||||
} catch (IllegalStateException e) {
|
||||
|
||||
// Server is shutting down and cannot send the packet, ignore
|
||||
} catch (Exception e) {
|
||||
LOGGER.severe("Failed to send FakeEntity packet", e);
|
||||
|
||||
plugin.log(Level.SEVERE, "Failed to send FakeEntity packet", e);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to send several packets
|
||||
*
|
||||
* @param player Player who will receive the packets
|
||||
* @param packets Packets to send
|
||||
*/
|
||||
protected static void tryToSendPackets(@NotNull Player player, @NotNull Iterable<PacketContainer> packets) {
|
||||
|
||||
if (Internals.MINECRAFT_VERSION < 19.4f) {
|
||||
|
||||
for (PacketContainer packet : packets) {
|
||||
|
||||
tryToSendPacket(player, packet);
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
PacketContainer container = new PacketContainer(PacketType.Play.Server.BUNDLE);
|
||||
container.getPacketBundles().write(0, packets);
|
||||
tryToSendPacket(player, container);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to run asynchronous task
|
||||
*
|
||||
* @param callback Callback to execute
|
||||
*/
|
||||
protected static void tryToRunAsyncTask(@NotNull Runnable callback) {
|
||||
YamipaPlugin.getInstance().getScheduler().execute(callback);
|
||||
|
||||
plugin.getScheduler().execute(callback);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,17 +1,12 @@
|
||||
package io.josemmo.bukkit.plugin.renderer;
|
||||
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import io.josemmo.bukkit.plugin.YamipaPlugin;
|
||||
import io.josemmo.bukkit.plugin.storage.CachedMapsFile;
|
||||
import io.josemmo.bukkit.plugin.storage.ImageFile;
|
||||
import io.josemmo.bukkit.plugin.utils.DirectionUtils;
|
||||
import io.josemmo.bukkit.plugin.utils.Logger;
|
||||
import io.josemmo.bukkit.plugin.utils.Permissions;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.Rotation;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.util.Vector;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -24,9 +19,8 @@ import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
public class FakeImage extends FakeEntity {
|
||||
private static final Logger LOGGER = Logger.getLogger("FakeImage");
|
||||
|
||||
// Image constants
|
||||
public static final int MAX_DIMENSION = 30; // In blocks
|
||||
public static final int MAX_STEPS = 500; // For animated images
|
||||
public static final int MIN_DELAY = 1; // Minimum step delay in 50ms intervals (50ms / 50ms)
|
||||
public static final int MAX_DELAY = 50; // Maximum step delay in 50ms intervals (5000ms / 50ms)
|
||||
@@ -52,33 +46,61 @@ public class FakeImage extends FakeEntity {
|
||||
private final int flags;
|
||||
private final BiFunction<Integer, Integer, Vector> getLocationVector;
|
||||
private final Set<Player> observingPlayers = new HashSet<>();
|
||||
private @Nullable Runnable onLoadedListener = null;
|
||||
private Runnable onLoadedListener = null;
|
||||
|
||||
// Generated values
|
||||
private boolean loading = false;
|
||||
private FakeItemFrame[] frames = null;
|
||||
private int delay = 0; // Delay between steps in 50ms intervals, "0" for N/A
|
||||
private int numOfSteps = -1; // Total number of animation steps
|
||||
private int numOfSteps = -1; // Total number of animation steps
|
||||
|
||||
// Animation task attributes
|
||||
private @Nullable ScheduledFuture<?> task;
|
||||
private static boolean animateImages = false;
|
||||
private ScheduledFuture<?> task;
|
||||
private int currentStep = -1; // Current animation step
|
||||
|
||||
/**
|
||||
* Configure class
|
||||
*
|
||||
* @param animImages Animate images
|
||||
*/
|
||||
public static void configure(boolean animImages) {
|
||||
|
||||
animateImages = animImages;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Is animation enabled
|
||||
*
|
||||
* @return Is animation enabled
|
||||
*/
|
||||
public static boolean isAnimationEnabled() {
|
||||
|
||||
return animateImages;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get image rotation from player eyesight
|
||||
* @param face Image block face
|
||||
* @param location Player eye location
|
||||
* @return Image rotation
|
||||
*
|
||||
* @param face Image block face
|
||||
* @param location Player eye location
|
||||
* @return Image rotation
|
||||
*/
|
||||
public static @NotNull Rotation getRotationFromPlayerEyesight(@NotNull BlockFace face, @NotNull Location location) {
|
||||
|
||||
// Images placed on N/S/E/W faces never have rotation
|
||||
if (face != BlockFace.UP && face != BlockFace.DOWN) {
|
||||
|
||||
return Rotation.NONE;
|
||||
|
||||
}
|
||||
|
||||
// Top and down images depend on where player is looking
|
||||
BlockFace eyeDirection = DirectionUtils.getCardinalDirection(location.getYaw());
|
||||
switch (eyeDirection) {
|
||||
|
||||
case EAST:
|
||||
return (face == BlockFace.DOWN) ? Rotation.CLOCKWISE_135 : Rotation.CLOCKWISE_45;
|
||||
case SOUTH:
|
||||
@@ -87,65 +109,45 @@ public class FakeImage extends FakeEntity {
|
||||
return (face == BlockFace.DOWN) ? Rotation.CLOCKWISE_45 : Rotation.CLOCKWISE_135;
|
||||
default:
|
||||
return Rotation.NONE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get maximum image dimension
|
||||
* @param sender Sender instance
|
||||
* @return Maximum image dimension in blocks
|
||||
*/
|
||||
public static int getMaxImageDimension(@NotNull CommandSender sender) {
|
||||
if (sender instanceof Player) {
|
||||
String rawValue = Permissions.getVariable("yamipa-max-image-dimension", (Player) sender);
|
||||
if (rawValue != null) {
|
||||
try {
|
||||
return Integer.parseInt(rawValue);
|
||||
} catch (NumberFormatException __) {
|
||||
LOGGER.warning("Max. image dimension for " + sender + " is not a valid integer: \"" + rawValue + "\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
return YamipaPlugin.getInstance().getRenderer().getMaxImageDimension();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get proportional height
|
||||
* @param sizeInPixels Image file dimension in pixels
|
||||
* @param sender Sender instance
|
||||
* @param width Desired width in blocks
|
||||
* @return Height in blocks (capped at maximum image dimension for sender)
|
||||
*
|
||||
* @param sizeInPixels Image file dimension in pixels
|
||||
* @param width Desired width in blocks
|
||||
* @return Height in blocks (capped at <code>FakeImage.MAX_DIMENSION</code>)
|
||||
*/
|
||||
public static int getProportionalHeight(@NotNull Dimension sizeInPixels, @NotNull CommandSender sender, int width) {
|
||||
public static int getProportionalHeight(@NotNull Dimension sizeInPixels, int width) {
|
||||
|
||||
float imageRatio = (float) sizeInPixels.height / sizeInPixels.width;
|
||||
int height = Math.round(width * imageRatio);
|
||||
height = Math.min(height, getMaxImageDimension(sender));
|
||||
height = Math.min(height, MAX_DIMENSION);
|
||||
return height;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
* @param filename Image filename
|
||||
* @param location Top-left corner where image will be placed
|
||||
* @param face Block face
|
||||
* @param rotation Image rotation
|
||||
* @param width Width in blocks
|
||||
* @param height Height in blocks
|
||||
* @param placedAt Placed at
|
||||
* @param placedBy Placed by
|
||||
* @param flags Flags
|
||||
*
|
||||
* @param filename Image filename
|
||||
* @param location Top-left corner where image will be placed
|
||||
* @param face Block face
|
||||
* @param rotation Image rotation
|
||||
* @param width Width in blocks
|
||||
* @param height Height in blocks
|
||||
* @param placedAt Placed at
|
||||
* @param placedBy Placed by
|
||||
* @param flags Flags
|
||||
*/
|
||||
public FakeImage(
|
||||
@NotNull String filename,
|
||||
@NotNull Location location,
|
||||
@NotNull BlockFace face,
|
||||
@NotNull Rotation rotation,
|
||||
int width,
|
||||
int height,
|
||||
@Nullable Date placedAt,
|
||||
@NotNull OfflinePlayer placedBy,
|
||||
int flags
|
||||
) {
|
||||
public FakeImage(@NotNull String filename, @NotNull Location location, @NotNull BlockFace face,
|
||||
@NotNull Rotation rotation, int width, int height, @Nullable Date placedAt, @NotNull OfflinePlayer placedBy,
|
||||
int flags)
|
||||
{
|
||||
|
||||
this.filename = filename;
|
||||
this.location = location;
|
||||
this.face = face;
|
||||
@@ -156,364 +158,503 @@ public class FakeImage extends FakeEntity {
|
||||
this.placedBy = placedBy;
|
||||
this.flags = flags;
|
||||
|
||||
// Define function for retrieving item frame positional vector from <row,column> pair
|
||||
// Define function for retrieving item frame positional vector from <row,column>
|
||||
// pair
|
||||
if (face == BlockFace.SOUTH) {
|
||||
|
||||
getLocationVector = (col, row) -> new Vector(col, -row, 0);
|
||||
|
||||
} else if (face == BlockFace.NORTH) {
|
||||
|
||||
getLocationVector = (col, row) -> new Vector(-col, -row, 0);
|
||||
|
||||
} else if (face == BlockFace.EAST) {
|
||||
|
||||
getLocationVector = (col, row) -> new Vector(0, -row, -col);
|
||||
|
||||
} else if (face == BlockFace.WEST) {
|
||||
|
||||
getLocationVector = (col, row) -> new Vector(0, -row, col);
|
||||
|
||||
} else if (face == BlockFace.UP) {
|
||||
|
||||
if (rotation == Rotation.CLOCKWISE_45) {
|
||||
|
||||
getLocationVector = (col, row) -> new Vector(-row, 0, col);
|
||||
|
||||
} else if (rotation == Rotation.CLOCKWISE_135) {
|
||||
|
||||
getLocationVector = (col, row) -> new Vector(row, 0, -col);
|
||||
|
||||
} else if (rotation == Rotation.CLOCKWISE) {
|
||||
|
||||
getLocationVector = (col, row) -> new Vector(-col, 0, -row);
|
||||
|
||||
} else { // Rotation.NONE
|
||||
|
||||
getLocationVector = (col, row) -> new Vector(col, 0, row);
|
||||
|
||||
}
|
||||
|
||||
} else { // BlockFace.DOWN
|
||||
|
||||
if (rotation == Rotation.CLOCKWISE_45) {
|
||||
|
||||
getLocationVector = (col, row) -> new Vector(-row, 0, -col);
|
||||
|
||||
} else if (rotation == Rotation.CLOCKWISE_135) {
|
||||
|
||||
getLocationVector = (col, row) -> new Vector(row, 0, col);
|
||||
|
||||
} else if (rotation == Rotation.CLOCKWISE) {
|
||||
|
||||
getLocationVector = (col, row) -> new Vector(-col, 0, row);
|
||||
|
||||
} else { // Rotation.NONE
|
||||
|
||||
getLocationVector = (col, row) -> new Vector(col, 0, -row);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
LOGGER.fine("Created FakeImage#(" + location + "," + face + ") from ImageFile#(" + filename + ")");
|
||||
plugin.fine("Created FakeImage#(" + location + "," + face + ") from ImageFile#(" + filename + ")");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get image filename
|
||||
*
|
||||
* @return Image filename
|
||||
*/
|
||||
public @NotNull String getFilename() {
|
||||
|
||||
return filename;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get image file instance
|
||||
*
|
||||
* @return Image file instance or NULL if not found
|
||||
*/
|
||||
public @Nullable ImageFile getFile() {
|
||||
return YamipaPlugin.getInstance().getStorage().get(filename);
|
||||
|
||||
return plugin.getStorage().get(filename);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get top-left corner of image
|
||||
*
|
||||
* @return Location instance
|
||||
*/
|
||||
public @NotNull Location getLocation() {
|
||||
|
||||
return location;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get image block face
|
||||
*
|
||||
* @return Image block face
|
||||
*/
|
||||
public @NotNull BlockFace getBlockFace() {
|
||||
|
||||
return face;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get image rotation
|
||||
*
|
||||
* @return Image rotation
|
||||
*/
|
||||
public @NotNull Rotation getRotation() {
|
||||
|
||||
return rotation;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get image width in blocks
|
||||
*
|
||||
* @return Image width
|
||||
*/
|
||||
public int getWidth() {
|
||||
|
||||
return width;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get image height in blocks
|
||||
*
|
||||
* @return Image height
|
||||
*/
|
||||
public int getHeight() {
|
||||
|
||||
return height;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get placed at date
|
||||
*
|
||||
* @return Placed at date
|
||||
*/
|
||||
public @Nullable Date getPlacedAt() {
|
||||
|
||||
return placedAt;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get placed by player
|
||||
*
|
||||
* @return Placed by player instance
|
||||
*/
|
||||
public @NotNull OfflinePlayer getPlacedBy() {
|
||||
|
||||
return placedBy;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get flags
|
||||
*
|
||||
* @return Flags
|
||||
*/
|
||||
public int getFlags() {
|
||||
|
||||
return flags;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Has flag
|
||||
* @param flag Flag to check
|
||||
* @return Whether instance has given flag or not
|
||||
*
|
||||
* @param flag Flag to check
|
||||
* @return Whether instance has given flag or not
|
||||
*/
|
||||
public boolean hasFlag(int flag) {
|
||||
|
||||
return ((flags & flag) == flag);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get image delay
|
||||
*
|
||||
* @return Image delay in 50ms intervals
|
||||
*/
|
||||
public int getDelay() {
|
||||
|
||||
return delay;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all block locations covered by the image
|
||||
*
|
||||
* @return Array of location instances
|
||||
*/
|
||||
public @NotNull Location[] getAllLocations() {
|
||||
Location[] locations = new Location[width*height];
|
||||
for (int row=0; row<height; row++) {
|
||||
for (int col=0; col<width; col++) {
|
||||
locations[row*width + col] = location.clone().add(getLocationVector.apply(col, row));
|
||||
|
||||
Location[] locations = new Location[width * height];
|
||||
for (int row = 0; row < height; row++) {
|
||||
|
||||
for (int col = 0; col < width; col++) {
|
||||
|
||||
locations[row * width + col] = location.clone().add(getLocationVector.apply(col, row));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return locations;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the world area IDs where this image is located
|
||||
*
|
||||
* @return Array of world area IDs
|
||||
*/
|
||||
public @NotNull WorldAreaId[] getWorldAreaIds() {
|
||||
|
||||
Set<WorldAreaId> corners = new HashSet<>();
|
||||
corners.add(WorldAreaId.fromLocation(location));
|
||||
corners.add(WorldAreaId.fromLocation(location.clone().add(getLocationVector.apply(width-1, 0))));
|
||||
corners.add(WorldAreaId.fromLocation(location.clone().add(getLocationVector.apply(0, height-1))));
|
||||
corners.add(WorldAreaId.fromLocation(location.clone().add(getLocationVector.apply(width-1, height-1))));
|
||||
corners.add(WorldAreaId.fromLocation(location.clone().add(getLocationVector.apply(width - 1, 0))));
|
||||
corners.add(WorldAreaId.fromLocation(location.clone().add(getLocationVector.apply(0, height - 1))));
|
||||
corners.add(WorldAreaId.fromLocation(location.clone().add(getLocationVector.apply(width - 1, height - 1))));
|
||||
return corners.toArray(new WorldAreaId[0]);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify whether a point is contained in the image plane
|
||||
* @param location Location instance (only for coordinates)
|
||||
* @param face Block face
|
||||
* @return TRUE for contained, FALSE otherwise
|
||||
*
|
||||
* @param location Location instance (only for coordinates)
|
||||
* @param face Block face
|
||||
* @return TRUE for contained, FALSE otherwise
|
||||
*/
|
||||
@SuppressWarnings("RedundantIfStatement")
|
||||
public boolean contains(@NotNull Location location, @NotNull BlockFace face) {
|
||||
|
||||
// Is point facing the same plane as the image?
|
||||
if (face != this.face) {
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
// Get sorted plane edges
|
||||
Location topLeft = this.location;
|
||||
Location bottomRight = this.location.clone().add(getLocationVector.apply(width-1, height-1));
|
||||
int[] x = new int[]{topLeft.getBlockX(), bottomRight.getBlockX()};
|
||||
int[] y = new int[]{topLeft.getBlockY(), bottomRight.getBlockY()};
|
||||
int[] z = new int[]{topLeft.getBlockZ(), bottomRight.getBlockZ()};
|
||||
Location bottomRight = this.location.clone().add(getLocationVector.apply(width - 1, height - 1));
|
||||
int[] x = new int[] { topLeft.getBlockX(), bottomRight.getBlockX() };
|
||||
int[] y = new int[] { topLeft.getBlockY(), bottomRight.getBlockY() };
|
||||
int[] z = new int[] { topLeft.getBlockZ(), bottomRight.getBlockZ() };
|
||||
Arrays.sort(x);
|
||||
Arrays.sort(y);
|
||||
Arrays.sort(z);
|
||||
|
||||
// Is point located inside the plane limits?
|
||||
int[] point = new int[] {location.getBlockX(), location.getBlockY(), location.getBlockZ()};
|
||||
if (point[0] < x[0] || point[0] > x[1]) return false;
|
||||
if (point[1] < y[0] || point[1] > y[1]) return false;
|
||||
if (point[2] < z[0] || point[2] > z[1]) return false;
|
||||
int[] point = new int[] { location.getBlockX(), location.getBlockY(), location.getBlockZ() };
|
||||
if (point[0] < x[0] || point[0] > x[1])
|
||||
return false;
|
||||
if (point[1] < y[0] || point[1] > y[1])
|
||||
return false;
|
||||
if (point[2] < z[0] || point[2] > z[1])
|
||||
return false;
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Set on loaded listener
|
||||
* <p>
|
||||
* Defines a single-use listener that gets called when the fake image finishes loading,
|
||||
* at which point the listener is automatically unregistered.
|
||||
* Defines a single-use listener that gets called when the fake image finishes
|
||||
* loading, at which point the listener is automatically unregistered.
|
||||
*
|
||||
* @param onLoadedListener Listener
|
||||
*/
|
||||
public void setOnLoadedListener(@NotNull Runnable onLoadedListener) {
|
||||
|
||||
this.onLoadedListener = onLoadedListener;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Load generated instance attributes
|
||||
*/
|
||||
private void load() {
|
||||
ImageFile file = getFile();
|
||||
|
||||
// Get maps to use
|
||||
FakeMap[][][] maps;
|
||||
ImageFile file = getFile();
|
||||
FakeMapsContainer container;
|
||||
if (file == null) {
|
||||
maps = FakeMap.getErrorMatrix(width, height);
|
||||
LOGGER.warning("File \"" + filename + "\" does not exist");
|
||||
|
||||
container = FakeMap.getErrorMatrix(width, height);
|
||||
plugin.warning("File \"" + filename + "\" does not exist");
|
||||
|
||||
} else {
|
||||
CachedMapsFile cachedMapsFile = file.getMapsAndSubscribe(this);
|
||||
maps = cachedMapsFile.getMaps();
|
||||
delay = cachedMapsFile.getDelay();
|
||||
|
||||
container = file.getMapsAndSubscribe(this);
|
||||
|
||||
}
|
||||
|
||||
// Extract data from container
|
||||
FakeMap[][][] maps = container.getFakeMaps();
|
||||
numOfSteps = maps[0][0].length;
|
||||
delay = container.getDelay();
|
||||
|
||||
// Generate frames
|
||||
FakeItemFrame[] newFrames = new FakeItemFrame[width*height];
|
||||
FakeItemFrame[] newFrames = new FakeItemFrame[width * height];
|
||||
boolean glowing = hasFlag(FLAG_GLOWING);
|
||||
for (int col=0; col<width; col++) {
|
||||
for (int row=0; row<height; row++) {
|
||||
for (int col = 0; col < width; col++) {
|
||||
|
||||
for (int row = 0; row < height; row++) {
|
||||
|
||||
Location frameLocation = location.clone().add(getLocationVector.apply(col, row));
|
||||
newFrames[height*col+row] = new FakeItemFrame(frameLocation, face, rotation, glowing, maps[col][row]);
|
||||
newFrames[height * col + row] = new FakeItemFrame(frameLocation, face, rotation, glowing,
|
||||
maps[col][row]);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
frames = newFrames;
|
||||
|
||||
// Start animation task (if needed)
|
||||
YamipaPlugin plugin = YamipaPlugin.getInstance();
|
||||
boolean isAnimationEnabled = plugin.getRenderer().isAnimationEnabled();
|
||||
if (isAnimationEnabled && task == null && hasFlag(FLAG_ANIMATABLE) && numOfSteps > 1) {
|
||||
task = plugin.getScheduler().scheduleAtFixedRate(
|
||||
this::nextStep,
|
||||
0,
|
||||
delay*50L,
|
||||
TimeUnit.MILLISECONDS
|
||||
);
|
||||
LOGGER.fine("Spawned animation task for FakeImage#(" + location + "," + face + ")");
|
||||
if (animateImages && task == null && hasFlag(FLAG_ANIMATABLE) && numOfSteps > 1) {
|
||||
|
||||
task = plugin.getScheduler().scheduleAtFixedRate(this::nextStep, 0, delay * 50L, TimeUnit.MILLISECONDS);
|
||||
plugin.fine("Spawned animation task for FakeImage#(" + location + "," + face + ")");
|
||||
|
||||
}
|
||||
|
||||
// Notify listener
|
||||
if (onLoadedListener != null) {
|
||||
|
||||
onLoadedListener.run();
|
||||
onLoadedListener = null;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Spawn image for a player
|
||||
*
|
||||
* @param player Player instance
|
||||
*/
|
||||
public void spawn(@NotNull Player player) {
|
||||
LOGGER.fine("Received request to spawn FakeImage#(" + location + "," + face + ") for Player#" + player.getName());
|
||||
|
||||
plugin.fine(
|
||||
"Received request to spawn FakeImage#(" + location + "," + face + ") for Player#" + player.getName());
|
||||
|
||||
// Send pixels if instance is already loaded
|
||||
if (frames != null) {
|
||||
|
||||
spawnOnceLoaded(player);
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
// Wait for instance to load if loading
|
||||
if (loading) {
|
||||
|
||||
tryToRunAsyncTask(() -> {
|
||||
|
||||
while (loading) {
|
||||
|
||||
tryToSleep(5);
|
||||
|
||||
}
|
||||
|
||||
spawnOnceLoaded(player);
|
||||
|
||||
});
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
// Instance needs to be loaded
|
||||
loading = true;
|
||||
tryToRunAsyncTask(() -> {
|
||||
|
||||
waitForProtocolLib();
|
||||
load();
|
||||
loading = false;
|
||||
spawnOnceLoaded(player);
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Spawn image for a player (once instance has been loaded)
|
||||
*
|
||||
* @param player Player instance
|
||||
*/
|
||||
private void spawnOnceLoaded(@NotNull Player player) {
|
||||
|
||||
String playerName = player.getName();
|
||||
observingPlayers.add(player);
|
||||
|
||||
// Prepare packets to send
|
||||
List<PacketContainer> packets = new ArrayList<>();
|
||||
for (FakeItemFrame frame : frames) {
|
||||
|
||||
packets.add(frame.getSpawnPacket());
|
||||
packets.addAll(frame.getRenderPackets(player, 0));
|
||||
LOGGER.fine("Spawned FakeItemFrame#" + frame.getId() + " for Player#" + playerName);
|
||||
plugin.fine("Spawned FakeItemFrame#" + frame.getId() + " for Player#" + playerName);
|
||||
|
||||
}
|
||||
|
||||
// Send packets
|
||||
tryToSendPackets(player, packets);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy image for all players
|
||||
*/
|
||||
public void destroy() {
|
||||
|
||||
if (frames != null) {
|
||||
|
||||
destroy(null);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy image a player
|
||||
*
|
||||
* @param player Player instance or NULL for all observing players
|
||||
*/
|
||||
public void destroy(@Nullable Player player) {
|
||||
LOGGER.fine(
|
||||
"Received request to destroy FakeImage#(" + location + "," + face + ") for " +
|
||||
(player == null ? "all players" : "Player#" + player.getName())
|
||||
);
|
||||
|
||||
plugin.fine("Received request to destroy FakeImage#(" + location + "," + face + ") for "
|
||||
+ (player == null ? "all players" : "Player#" + player.getName()));
|
||||
|
||||
// Send packets to destroy item frames
|
||||
if (frames != null) {
|
||||
|
||||
Set<Player> targets = (player == null) ? observingPlayers : Collections.singleton(player);
|
||||
for (Player target : targets) {
|
||||
|
||||
String targetName = target.getName();
|
||||
List<PacketContainer> packets = new ArrayList<>();
|
||||
for (FakeItemFrame frame : frames) {
|
||||
|
||||
packets.add(frame.getDestroyPacket());
|
||||
LOGGER.fine("Destroyed FakeItemFrame#" + frame.getId() + " for Player#" + targetName);
|
||||
plugin.fine("Destroyed FakeItemFrame#" + frame.getId() + " for Player#" + targetName);
|
||||
|
||||
}
|
||||
|
||||
tryToSendPackets(target, packets);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Remove player from observing players
|
||||
if (player == null) {
|
||||
|
||||
observingPlayers.clear();
|
||||
|
||||
} else {
|
||||
|
||||
observingPlayers.remove(player);
|
||||
|
||||
}
|
||||
|
||||
// Invalidate instance (if no more players)
|
||||
if (observingPlayers.isEmpty()) {
|
||||
|
||||
invalidate();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify player quit from server
|
||||
*
|
||||
* @param player Player instance
|
||||
*/
|
||||
public void notifyPlayerQuit(@NotNull Player player) {
|
||||
|
||||
observingPlayers.remove(player);
|
||||
if (observingPlayers.isEmpty()) {
|
||||
|
||||
invalidate();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -522,43 +663,59 @@ public class FakeImage extends FakeEntity {
|
||||
* Removes all item frames associated with this image, among other things.
|
||||
*/
|
||||
private void invalidate() {
|
||||
|
||||
// Destroy animation task
|
||||
if (task != null) {
|
||||
|
||||
task.cancel(true);
|
||||
task = null;
|
||||
currentStep = -1;
|
||||
LOGGER.fine("Destroyed animation task for FakeImage#(" + location + "," + face + ")");
|
||||
plugin.fine("Destroyed animation task for FakeImage#(" + location + "," + face + ")");
|
||||
|
||||
}
|
||||
|
||||
// Free array of fake item frames
|
||||
frames = null;
|
||||
loading = false;
|
||||
LOGGER.fine("Invalidated FakeImage#(" + location + "," + face + ")");
|
||||
plugin.fine("Invalidated FakeImage#(" + location + "," + face + ")");
|
||||
|
||||
// Notify invalidation to source ImageFile
|
||||
ImageFile file = getFile();
|
||||
if (file != null) {
|
||||
|
||||
file.unsubscribe(this);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Send next animation step to all registered players
|
||||
*/
|
||||
private void nextStep() {
|
||||
|
||||
currentStep = (currentStep + 1) % numOfSteps;
|
||||
try {
|
||||
|
||||
for (Player player : observingPlayers) {
|
||||
|
||||
List<PacketContainer> packets = new ArrayList<>();
|
||||
for (FakeItemFrame frame : frames) {
|
||||
|
||||
packets.addAll(frame.getRenderPackets(player, currentStep));
|
||||
|
||||
}
|
||||
|
||||
tryToSendPackets(player, packets);
|
||||
|
||||
}
|
||||
|
||||
} catch (ConcurrentModificationException e) {
|
||||
|
||||
// We can safely ignore this exception as it will just result
|
||||
// in a dropped step (all `observingPlayers` modifications are
|
||||
// called from the same thread).
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,14 +1,9 @@
|
||||
package io.josemmo.bukkit.plugin.renderer;
|
||||
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.wrappers.nbt.NbtCompound;
|
||||
import com.comphenix.protocol.wrappers.nbt.NbtFactory;
|
||||
import io.josemmo.bukkit.plugin.packets.DestroyEntityPacket;
|
||||
import io.josemmo.bukkit.plugin.packets.EntityMetadataPacket;
|
||||
import io.josemmo.bukkit.plugin.packets.SpawnEntityPacket;
|
||||
import io.josemmo.bukkit.plugin.utils.Internals;
|
||||
import io.josemmo.bukkit.plugin.utils.Logger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Rotation;
|
||||
@@ -17,16 +12,23 @@ import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.wrappers.nbt.NbtCompound;
|
||||
import com.comphenix.protocol.wrappers.nbt.NbtFactory;
|
||||
|
||||
import io.josemmo.bukkit.plugin.packets.DestroyEntityPacket;
|
||||
import io.josemmo.bukkit.plugin.packets.EntityMetadataPacket;
|
||||
import io.josemmo.bukkit.plugin.packets.SpawnEntityPacket;
|
||||
import io.josemmo.bukkit.plugin.utils.Internals;
|
||||
|
||||
public class FakeItemFrame extends FakeEntity {
|
||||
|
||||
public static final int MIN_FRAME_ID = Integer.MAX_VALUE / 4;
|
||||
public static final int MAX_FRAME_ID = Integer.MAX_VALUE;
|
||||
private static final boolean SUPPORTS_GLOWING = Internals.MINECRAFT_VERSION >= 17;
|
||||
private static final Logger LOGGER = Logger.getLogger("FakeItemFrame");
|
||||
private static final AtomicInteger LAST_FRAME_ID = new AtomicInteger(MAX_FRAME_ID);
|
||||
private static final AtomicInteger lastFrameId = new AtomicInteger(MAX_FRAME_ID);
|
||||
private final int id;
|
||||
private final Location location;
|
||||
private final BlockFace face;
|
||||
@@ -36,54 +38,66 @@ public class FakeItemFrame extends FakeEntity {
|
||||
|
||||
/**
|
||||
* Get next unused item frame ID
|
||||
*
|
||||
* @return Next unused item frame ID
|
||||
*/
|
||||
private static int getNextId() {
|
||||
return LAST_FRAME_ID.updateAndGet(lastId -> {
|
||||
if (lastId == MAX_FRAME_ID) {
|
||||
|
||||
return lastFrameId.updateAndGet(lastId -> {
|
||||
|
||||
if (lastId >= MAX_FRAME_ID) {
|
||||
|
||||
return MIN_FRAME_ID;
|
||||
|
||||
}
|
||||
|
||||
return lastId + 1;
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* @param location Frame location
|
||||
* @param face Block face
|
||||
* @param rotation Frame rotation
|
||||
* @param glowing Whether is glowing or regular frame
|
||||
* @param maps Fake maps to animate
|
||||
*/
|
||||
public FakeItemFrame(
|
||||
@NotNull Location location,
|
||||
@NotNull BlockFace face,
|
||||
@NotNull Rotation rotation,
|
||||
boolean glowing,
|
||||
@NotNull FakeMap[] maps
|
||||
) {
|
||||
public FakeItemFrame(@NotNull Location location, @NotNull BlockFace face, @NotNull Rotation rotation,
|
||||
boolean glowing, @NotNull FakeMap[] maps)
|
||||
{
|
||||
|
||||
this.id = getNextId();
|
||||
this.location = location;
|
||||
this.face = face;
|
||||
this.rotation = rotation;
|
||||
this.glowing = glowing;
|
||||
this.maps = maps;
|
||||
LOGGER.fine("Created FakeItemFrame#" + this.id + " using " + this.maps.length + " FakeMap(s)");
|
||||
plugin.fine("Created FakeItemFrame#" + this.id + " using " + this.maps.length + " FakeMap(s)");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get frame ID
|
||||
*
|
||||
* @return Frame ID
|
||||
*/
|
||||
public int getId() {
|
||||
|
||||
return id;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get entity spawn packet
|
||||
*
|
||||
* @return Spawn packet
|
||||
*/
|
||||
public @NotNull SpawnEntityPacket getSpawnPacket() {
|
||||
|
||||
// Calculate frame position in relation to target block
|
||||
double x = location.getBlockX();
|
||||
double y = location.getBlockY();
|
||||
@@ -92,6 +106,7 @@ public class FakeItemFrame extends FakeEntity {
|
||||
int yaw = 0;
|
||||
int orientation = 3;
|
||||
switch (face) {
|
||||
|
||||
case UP:
|
||||
++y;
|
||||
pitch = -64;
|
||||
@@ -119,58 +134,65 @@ public class FakeItemFrame extends FakeEntity {
|
||||
break;
|
||||
case SOUTH:
|
||||
++z;
|
||||
default:
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
// Create item frame entity
|
||||
SpawnEntityPacket framePacket = new SpawnEntityPacket();
|
||||
final SpawnEntityPacket framePacket = new SpawnEntityPacket();
|
||||
framePacket.setId(id)
|
||||
.setEntityType((glowing && SUPPORTS_GLOWING) ? EntityType.GLOW_ITEM_FRAME : EntityType.ITEM_FRAME)
|
||||
.setPosition(x, y, z)
|
||||
.setRotation(pitch, yaw)
|
||||
.setData(orientation);
|
||||
.setEntityType((glowing && SUPPORTS_GLOWING) ? EntityType.GLOW_ITEM_FRAME : EntityType.ITEM_FRAME)
|
||||
.setPosition(x, y, z).setRotation(pitch, yaw).setData(orientation);
|
||||
|
||||
return framePacket;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get frame of animation packets
|
||||
*
|
||||
* @param player Player who is expected to receive packets (for caching reasons)
|
||||
* @param step Map step
|
||||
*/
|
||||
public @NotNull List<PacketContainer> getRenderPackets(@NotNull Player player, int step) {
|
||||
List<PacketContainer> packets = new ArrayList<>(2);
|
||||
|
||||
final List<PacketContainer> packets = new ArrayList<>(2);
|
||||
|
||||
// Enqueue map pixels packet (if needed)
|
||||
boolean mustSendPixels = maps[step].requestResend(player);
|
||||
final boolean mustSendPixels = maps[step].requestResend(player);
|
||||
if (mustSendPixels) {
|
||||
|
||||
packets.add(maps[step].getPixelsPacket());
|
||||
|
||||
}
|
||||
|
||||
// Create and attach filled map
|
||||
ItemStack itemStack = MinecraftReflection.getBukkitItemStack(new ItemStack(Material.FILLED_MAP));
|
||||
NbtCompound itemStackNbt = NbtFactory.ofCompound("tag");
|
||||
final ItemStack itemStack = MinecraftReflection.getBukkitItemStack(new ItemStack(Material.FILLED_MAP));
|
||||
final NbtCompound itemStackNbt = NbtFactory.ofCompound("tag");
|
||||
itemStackNbt.put("map", maps[step].getId());
|
||||
NbtFactory.setItemTag(itemStack, itemStackNbt);
|
||||
|
||||
// Build entity metadata packet
|
||||
EntityMetadataPacket metadataPacket = new EntityMetadataPacket();
|
||||
metadataPacket.setId(id)
|
||||
.setInvisible(true)
|
||||
.setItem(itemStack)
|
||||
.setRotation(rotation)
|
||||
.build();
|
||||
final EntityMetadataPacket metadataPacket = new EntityMetadataPacket();
|
||||
metadataPacket.setId(id).setInvisible(true).setItem(itemStack).setRotation(rotation).build();
|
||||
packets.add(metadataPacket);
|
||||
|
||||
return packets;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get destroy item frame packet
|
||||
*
|
||||
* @return Destroy packet
|
||||
*/
|
||||
public @NotNull DestroyEntityPacket getDestroyPacket() {
|
||||
DestroyEntityPacket destroyPacket = new DestroyEntityPacket();
|
||||
|
||||
final DestroyEntityPacket destroyPacket = new DestroyEntityPacket();
|
||||
destroyPacket.setId(id);
|
||||
return destroyPacket;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,12 +1,6 @@
|
||||
package io.josemmo.bukkit.plugin.renderer;
|
||||
|
||||
import io.josemmo.bukkit.plugin.packets.MapDataPacket;
|
||||
import io.josemmo.bukkit.plugin.utils.Logger;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.map.MapPalette;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import java.awt.*;
|
||||
import java.awt.Color;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.UUID;
|
||||
@@ -14,146 +8,195 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.map.MapPalette;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import io.josemmo.bukkit.plugin.packets.MapDataPacket;
|
||||
|
||||
public class FakeMap extends FakeEntity {
|
||||
|
||||
public static final int DIMENSION = 128;
|
||||
private static final int MIN_MAP_ID = Integer.MAX_VALUE / 4;
|
||||
private static final int MAX_MAP_ID = Integer.MAX_VALUE;
|
||||
private static final int RESEND_THRESHOLD = 60*5; // Seconds after sending pixels when resending should be avoided
|
||||
private static final Logger LOGGER = Logger.getLogger("FakeMap");
|
||||
private static final AtomicInteger LAST_MAP_ID = new AtomicInteger(MIN_MAP_ID);
|
||||
private static @Nullable FakeMap ERROR_INSTANCE;
|
||||
public static final int MIN_MAP_ID = Integer.MAX_VALUE / 4;
|
||||
public static final int MAX_MAP_ID = Integer.MAX_VALUE;
|
||||
public static final int RESEND_THRESHOLD = 60 * 5; // Seconds after sending pixels when resending should be avoided
|
||||
private static final AtomicInteger lastMapId = new AtomicInteger(-1);
|
||||
private static FakeMap errorInstance;
|
||||
private final int id;
|
||||
private final byte[] pixels;
|
||||
private final ConcurrentMap<UUID, Long> lastPlayerSendTime = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Get next unused map ID
|
||||
*
|
||||
* @return Next unused map ID
|
||||
*/
|
||||
private static int getNextId() {
|
||||
return LAST_MAP_ID.updateAndGet(lastId -> {
|
||||
if (lastId == MIN_MAP_ID) {
|
||||
|
||||
return lastMapId.updateAndGet(lastId -> {
|
||||
|
||||
if (lastId <= MIN_MAP_ID) {
|
||||
|
||||
return MAX_MAP_ID;
|
||||
|
||||
}
|
||||
|
||||
return lastId - 1;
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Pixel to Minecraft color index
|
||||
* @param pixel RGBA pixel value
|
||||
* @return Closest Minecraft color index
|
||||
*
|
||||
* @param pixel RGBA pixel value
|
||||
* @return Closest Minecraft color index
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public static byte pixelToIndex(int pixel) {
|
||||
|
||||
return MapPalette.matchColor(new Color(pixel, true));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get map instance to show in case of error
|
||||
*
|
||||
* @return Error instance
|
||||
*/
|
||||
private static @NotNull FakeMap getErrorInstance() {
|
||||
if (ERROR_INSTANCE == null) {
|
||||
byte[] pixels = new byte[DIMENSION * DIMENSION];
|
||||
|
||||
if (errorInstance == null) {
|
||||
|
||||
final byte[] pixels = new byte[DIMENSION * DIMENSION];
|
||||
Arrays.fill(pixels, pixelToIndex(Color.RED.getRGB()));
|
||||
ERROR_INSTANCE = new FakeMap(pixels);
|
||||
errorInstance = new FakeMap(pixels);
|
||||
|
||||
}
|
||||
return ERROR_INSTANCE;
|
||||
|
||||
return errorInstance;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get matrix of error maps
|
||||
* @param width Width in blocks
|
||||
* @param height Height in blocks
|
||||
* @return Fake maps
|
||||
*
|
||||
* @param width Width in blocks
|
||||
* @param height Height in blocks
|
||||
* @return Fake maps container
|
||||
*/
|
||||
public static @NotNull FakeMap[][][] getErrorMatrix(int width, int height) {
|
||||
FakeMap[] errorMaps = new FakeMap[] {getErrorInstance()};
|
||||
FakeMap[][][] matrix = new FakeMap[width][height][1];
|
||||
public static @NotNull FakeMapsContainer getErrorMatrix(int width, int height) {
|
||||
|
||||
final FakeMap[] errorMaps = new FakeMap[] { getErrorInstance() };
|
||||
final FakeMap[][][] matrix = new FakeMap[width][height][1];
|
||||
for (FakeMap[][] column : matrix) {
|
||||
|
||||
Arrays.fill(column, errorMaps);
|
||||
|
||||
}
|
||||
return matrix;
|
||||
|
||||
return new FakeMapsContainer(matrix, 0);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* @param pixels Array of Minecraft color indexes
|
||||
* @param scanSize Original image width
|
||||
* @param startX Initial X pixel coordinate
|
||||
* @param startY Initial Y pixel coordinate
|
||||
*/
|
||||
public FakeMap(byte[] pixels, int scanSize, int startX, int startY) {
|
||||
|
||||
this.id = getNextId();
|
||||
|
||||
// Copy square of pixels to this instance
|
||||
this.pixels = new byte[DIMENSION*DIMENSION];
|
||||
for (int y=0; y<DIMENSION; y++) {
|
||||
System.arraycopy(pixels, startX+(startY+y)*scanSize, this.pixels, y*DIMENSION, DIMENSION);
|
||||
this.pixels = new byte[DIMENSION * DIMENSION];
|
||||
for (int y = 0; y < DIMENSION; y++) {
|
||||
|
||||
System.arraycopy(pixels, startX + (startY + y) * scanSize, this.pixels, y * DIMENSION, DIMENSION);
|
||||
|
||||
}
|
||||
|
||||
LOGGER.fine("Created FakeMap#" + this.id);
|
||||
plugin.fine("Created FakeMap#" + this.id);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* @param pixels Array of Minecraft color indexes
|
||||
*/
|
||||
public FakeMap(byte[] pixels) {
|
||||
|
||||
this.id = getNextId();
|
||||
this.pixels = pixels;
|
||||
LOGGER.fine("Created FakeMap#" + this.id);
|
||||
plugin.fine("Created FakeMap#" + this.id);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get map ID
|
||||
*
|
||||
* @return Map ID
|
||||
*/
|
||||
public int getId() {
|
||||
|
||||
return id;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get raw pixels
|
||||
*
|
||||
* @return Array of Minecraft color indexes
|
||||
*/
|
||||
public byte[] getPixels() {
|
||||
|
||||
return pixels;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Request re-send of map pixels
|
||||
* @param player Player who is expected to receive pixels
|
||||
* @return Whether re-send authorization was granted or not
|
||||
*
|
||||
* @param player Player who is expected to receive pixels
|
||||
* @return Whether re-send authorization was granted or not
|
||||
*/
|
||||
public boolean requestResend(@NotNull Player player) {
|
||||
UUID uuid = player.getUniqueId();
|
||||
long now = Instant.now().getEpochSecond();
|
||||
|
||||
final UUID uuid = player.getUniqueId();
|
||||
final long now = Instant.now().getEpochSecond();
|
||||
|
||||
// Has enough time passed since last re-send?
|
||||
long last = lastPlayerSendTime.getOrDefault(uuid, 0L);
|
||||
if ((now-last) <= RESEND_THRESHOLD && (player.getLastPlayed()/1000) < last) {
|
||||
final long last = lastPlayerSendTime.getOrDefault(uuid, 0L);
|
||||
if ((now - last) <= RESEND_THRESHOLD && (player.getLastSeen() / 1000) < last) {
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
// Authorize re-send and update latest timestamp
|
||||
lastPlayerSendTime.put(uuid, now);
|
||||
LOGGER.fine("Granted sending pixels for FakeMap#" + id + " to Player#" + player.getName());
|
||||
plugin.fine("Granted sending pixels for FakeMap#" + id + " to Player#" + player.getName());
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get map pixels packet
|
||||
*
|
||||
* @return Map pixels packet
|
||||
*/
|
||||
public @NotNull MapDataPacket getPixelsPacket() {
|
||||
MapDataPacket mapDataPacket = new MapDataPacket();
|
||||
mapDataPacket.setId(id)
|
||||
.setScale(0) // Fully zoomed-in
|
||||
.setLocked(true)
|
||||
.setArea(DIMENSION, DIMENSION, 0, 0)
|
||||
.setPixels(pixels);
|
||||
|
||||
final MapDataPacket mapDataPacket = new MapDataPacket();
|
||||
mapDataPacket.setId(id).setScale(0) // Fully zoomed-in
|
||||
.setLocked(true).setArea(DIMENSION, DIMENSION, 0, 0).setPixels(pixels);
|
||||
return mapDataPacket;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package io.josemmo.bukkit.plugin.renderer;
|
||||
|
||||
public class FakeMapsContainer {
|
||||
|
||||
private final FakeMap[][][] fakeMaps;
|
||||
private final int delay;
|
||||
|
||||
public FakeMapsContainer(FakeMap[][][] fakeMaps, int delay) {
|
||||
|
||||
this.fakeMaps = fakeMaps;
|
||||
this.delay = delay;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get fake maps
|
||||
*
|
||||
* @return Tri-dimensional array of maps (column, row, step)
|
||||
*/
|
||||
public FakeMap[][][] getFakeMaps() {
|
||||
|
||||
return fakeMaps;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get delay between steps
|
||||
*
|
||||
* @return Delay in 50ms intervals or <code>0</code> if not applicable
|
||||
*/
|
||||
public int getDelay() {
|
||||
|
||||
return delay;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package io.josemmo.bukkit.plugin.renderer;
|
||||
|
||||
import io.josemmo.bukkit.plugin.YamipaPlugin;
|
||||
import io.josemmo.bukkit.plugin.utils.CsvConfiguration;
|
||||
import io.josemmo.bukkit.plugin.utils.Logger;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.entity.Entity;
|
||||
@@ -18,19 +17,19 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ImageRenderer implements Listener {
|
||||
private static final long SAVE_INTERVAL = 20L * 90; // In server ticks
|
||||
private static final Logger LOGGER = Logger.getLogger("ImageRenderer");
|
||||
private final Path configPath;
|
||||
private final boolean animateImages;
|
||||
private final int maxImageDimension;
|
||||
|
||||
public static final long SAVE_INTERVAL = 20L * 90; // In server ticks
|
||||
private static final YamipaPlugin plugin = YamipaPlugin.getInstance();
|
||||
private final String configPath;
|
||||
private BukkitTask saveTask;
|
||||
private final AtomicBoolean hasConfigChanged = new AtomicBoolean(false);
|
||||
private final ConcurrentMap<WorldAreaId, Set<FakeImage>> images = new ConcurrentHashMap<>();
|
||||
@@ -39,255 +38,299 @@ public class ImageRenderer implements Listener {
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
* @param configPath Path to configuration file
|
||||
* @param animateImages Whether to animate images or not
|
||||
* @param maxImageDimension Maximum image dimension in blocks
|
||||
*
|
||||
* @param configPath Path to configuration file
|
||||
*/
|
||||
public ImageRenderer(@NotNull Path configPath, boolean animateImages, int maxImageDimension) {
|
||||
public ImageRenderer(@NotNull String configPath) {
|
||||
|
||||
this.configPath = configPath;
|
||||
this.animateImages = animateImages;
|
||||
this.maxImageDimension = maxImageDimension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is animation enabled
|
||||
* @return Is animation enabled
|
||||
*/
|
||||
public boolean isAnimationEnabled() {
|
||||
return animateImages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get maximum image dimension
|
||||
* @return Maximum image dimension in blocks
|
||||
*/
|
||||
public int getMaxImageDimension() {
|
||||
return maxImageDimension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start instance
|
||||
*/
|
||||
public void start() {
|
||||
|
||||
loadConfig();
|
||||
YamipaPlugin plugin = YamipaPlugin.getInstance();
|
||||
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
||||
saveTask = Bukkit.getScheduler().runTaskTimerAsynchronously(plugin, this::saveConfig, SAVE_INTERVAL, SAVE_INTERVAL);
|
||||
saveTask = Bukkit.getScheduler().runTaskTimerAsynchronously(plugin, this::saveConfig, SAVE_INTERVAL,
|
||||
SAVE_INTERVAL);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop instance
|
||||
*/
|
||||
public void stop() {
|
||||
|
||||
HandlerList.unregisterAll(this);
|
||||
|
||||
// Destroy images from remote clients
|
||||
for (Set<FakeImage> fakeImagesPart : images.values()) {
|
||||
|
||||
for (FakeImage fakeImage : fakeImagesPart) {
|
||||
|
||||
fakeImage.destroy();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Persist configuration
|
||||
if (saveTask != null) {
|
||||
|
||||
saveTask.cancel();
|
||||
|
||||
}
|
||||
|
||||
saveConfig();
|
||||
|
||||
// Clear dangling references
|
||||
images.clear();
|
||||
imagesCountByPlayer.clear();
|
||||
playersLocation.clear();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Load configuration from disk
|
||||
*/
|
||||
private void loadConfig() {
|
||||
if (!Files.isRegularFile(configPath)) {
|
||||
LOGGER.info("No placed fake images configuration file found");
|
||||
|
||||
if (!Files.isRegularFile(Paths.get(configPath))) {
|
||||
|
||||
plugin.info("No placed fake images configuration file found");
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
// Try to load configuration
|
||||
CsvConfiguration config = new CsvConfiguration();
|
||||
try {
|
||||
|
||||
config.load(configPath);
|
||||
|
||||
} catch (IOException e) {
|
||||
LOGGER.severe("Failed to load placed fake images from disk", e);
|
||||
|
||||
plugin.log(Level.SEVERE, "Failed to load placed fake images from disk", e);
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
// Parse each row
|
||||
for (String[] row : config.getRows()) {
|
||||
|
||||
try {
|
||||
|
||||
String filename = row[0];
|
||||
World world = Objects.requireNonNull(YamipaPlugin.getInstance().getServer().getWorld(row[1]));
|
||||
World world = Objects.requireNonNull(plugin.getServer().getWorld(row[1]));
|
||||
double x = Integer.parseInt(row[2]);
|
||||
double y = Integer.parseInt(row[3]);
|
||||
double z = Integer.parseInt(row[4]);
|
||||
Location location = new Location(world, x, y, z);
|
||||
BlockFace face = BlockFace.valueOf(row[5]);
|
||||
Rotation rotation = Rotation.valueOf(row[6]);
|
||||
int width = Math.abs(Integer.parseInt(row[7]));
|
||||
int height = Math.abs(Integer.parseInt(row[8]));
|
||||
Date placedAt = (row.length > 9 && !row[9].isEmpty()) ?
|
||||
new Date(Long.parseLong(row[9])*1000L) :
|
||||
null;
|
||||
UUID placedById = (row.length > 10 && !row[10].isEmpty()) ?
|
||||
UUID.fromString(row[10]) :
|
||||
FakeImage.UNKNOWN_PLAYER_ID;
|
||||
int width = Math.min(FakeImage.MAX_DIMENSION, Math.abs(Integer.parseInt(row[7])));
|
||||
int height = Math.min(FakeImage.MAX_DIMENSION, Math.abs(Integer.parseInt(row[8])));
|
||||
Date placedAt = (row.length > 9 && !row[9].equals("")) ? new Date(Long.parseLong(row[9]) * 1000L)
|
||||
: null;
|
||||
UUID placedById = (row.length > 10 && !row[10].equals("")) ? UUID.fromString(row[10])
|
||||
: FakeImage.UNKNOWN_PLAYER_ID;
|
||||
OfflinePlayer placedBy = Bukkit.getOfflinePlayer(placedById);
|
||||
int flags = (row.length > 11) ?
|
||||
Math.max(Integer.parseInt(row[11]), 0) :
|
||||
FakeImage.DEFAULT_PLACE_FLAGS;
|
||||
FakeImage fakeImage = new FakeImage(filename, location, face, rotation, width, height,
|
||||
placedAt, placedBy, flags);
|
||||
int flags = (row.length > 11) ? Math.max(Integer.parseInt(row[11]), 0) : FakeImage.DEFAULT_PLACE_FLAGS;
|
||||
FakeImage fakeImage = new FakeImage(filename, location, face, rotation, width, height, placedAt,
|
||||
placedBy, flags);
|
||||
addImage(fakeImage, true);
|
||||
|
||||
} catch (Exception e) {
|
||||
LOGGER.severe("Invalid fake image properties: " + String.join(";", row), e);
|
||||
|
||||
plugin.log(Level.SEVERE, "Invalid fake image properties: " + String.join(";", row), e);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Save configuration to disk
|
||||
*/
|
||||
@SuppressWarnings("ExtractMethodRecommender")
|
||||
private void saveConfig() {
|
||||
if (!hasConfigChanged.get()) return;
|
||||
|
||||
if (!hasConfigChanged.get())
|
||||
return;
|
||||
|
||||
// Get all fake images
|
||||
Set<FakeImage> fakeImages = new HashSet<>();
|
||||
for (Set<FakeImage> fakeImagesPart : images.values()) {
|
||||
|
||||
fakeImages.addAll(fakeImagesPart);
|
||||
|
||||
}
|
||||
|
||||
// Placed here so, if another change comes while saving, we don't lose those changes (will be saved later)
|
||||
// Placed here so, if another change comes while saving, we don't lose those
|
||||
// changes (will be saved later)
|
||||
hasConfigChanged.set(false);
|
||||
|
||||
// Export to configuration properties
|
||||
CsvConfiguration config = new CsvConfiguration();
|
||||
for (FakeImage fakeImage : fakeImages) {
|
||||
|
||||
Location location = fakeImage.getLocation();
|
||||
UUID placedById = fakeImage.getPlacedBy().getUniqueId();
|
||||
String[] row = new String[]{
|
||||
fakeImage.getFilename(),
|
||||
location.getChunk().getWorld().getName(),
|
||||
location.getBlockX() + "",
|
||||
location.getBlockY() + "",
|
||||
location.getBlockZ() + "",
|
||||
fakeImage.getBlockFace().name(),
|
||||
fakeImage.getRotation().name(),
|
||||
fakeImage.getWidth() + "",
|
||||
fakeImage.getHeight() + "",
|
||||
(fakeImage.getPlacedAt() == null) ? "" : (fakeImage.getPlacedAt().getTime() / 1000) + "",
|
||||
placedById.equals(FakeImage.UNKNOWN_PLAYER_ID) ? "" : placedById.toString(),
|
||||
fakeImage.getFlags() + ""
|
||||
};
|
||||
String[] row = new String[] { fakeImage.getFilename(), location.getChunk().getWorld().getName(),
|
||||
location.getBlockX() + "", location.getBlockY() + "", location.getBlockZ() + "",
|
||||
fakeImage.getBlockFace().name(), fakeImage.getRotation().name(), fakeImage.getWidth() + "",
|
||||
fakeImage.getHeight() + "",
|
||||
(fakeImage.getPlacedAt() == null) ? "" : (fakeImage.getPlacedAt().getTime() / 1000) + "",
|
||||
placedById.equals(FakeImage.UNKNOWN_PLAYER_ID) ? "" : placedById.toString(),
|
||||
fakeImage.getFlags() + "" };
|
||||
config.addRow(row);
|
||||
|
||||
}
|
||||
|
||||
// Write to disk
|
||||
try {
|
||||
|
||||
config.save(configPath);
|
||||
LOGGER.info("Saved placed fake images to disk");
|
||||
plugin.info("Saved placed fake images to disk");
|
||||
|
||||
} catch (IOException e) {
|
||||
LOGGER.severe("Failed to save placed fake images to disk", e);
|
||||
|
||||
plugin.log(Level.SEVERE, "Failed to save placed fake images to disk", e);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Add image to renderer
|
||||
*
|
||||
* @param image Fake image instance
|
||||
* @param isInit TRUE if called during renderer startup, FALSE otherwise
|
||||
*/
|
||||
public void addImage(@NotNull FakeImage image, boolean isInit) {
|
||||
|
||||
WorldAreaId[] imageWorldAreaIds = image.getWorldAreaIds();
|
||||
|
||||
// Add image to renderer
|
||||
for (WorldAreaId worldAreaId : imageWorldAreaIds) {
|
||||
|
||||
images.computeIfAbsent(worldAreaId, __ -> {
|
||||
LOGGER.fine("Created WorldArea#(" + worldAreaId + ")");
|
||||
|
||||
plugin.fine("Created WorldArea#(" + worldAreaId + ")");
|
||||
return ConcurrentHashMap.newKeySet();
|
||||
|
||||
}).add(image);
|
||||
|
||||
}
|
||||
|
||||
// Set configuration changed flag
|
||||
if (!isInit) {
|
||||
|
||||
hasConfigChanged.set(true);
|
||||
|
||||
}
|
||||
|
||||
// Increment count of placed images by player
|
||||
UUID placedById = image.getPlacedBy().getUniqueId();
|
||||
imagesCountByPlayer.compute(placedById, (__, prev) -> (prev == null) ? 1 : prev+1);
|
||||
imagesCountByPlayer.compute(placedById, (__, prev) -> (prev == null) ? 1 : prev + 1);
|
||||
|
||||
// Spawn image in players nearby
|
||||
for (Player player : getPlayersInViewDistance(imageWorldAreaIds)) {
|
||||
|
||||
image.spawn(player);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Add image to renderer
|
||||
*
|
||||
* @param image Fake image instance
|
||||
*/
|
||||
public void addImage(@NotNull FakeImage image) {
|
||||
|
||||
addImage(image, false);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get image from location
|
||||
* @param location Fake image location
|
||||
* @param face Fake image block face
|
||||
* @return Fake image instance or NULL if not found
|
||||
*
|
||||
* @param location Fake image location
|
||||
* @param face Fake image block face
|
||||
* @return Fake image instance or NULL if not found
|
||||
*/
|
||||
public @Nullable FakeImage getImage(@NotNull Location location, @NotNull BlockFace face) {
|
||||
|
||||
WorldAreaId worldAreaId = WorldAreaId.fromLocation(location);
|
||||
Set<FakeImage> candidateImages = images.get(worldAreaId);
|
||||
if (candidateImages == null) {
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
// Find first fake image containing the given location
|
||||
for (FakeImage image : candidateImages) {
|
||||
|
||||
if (image.contains(location, face)) {
|
||||
|
||||
return image;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// No match found
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get images from area
|
||||
* @param world World instance
|
||||
* @param minX Minimum X coordinate
|
||||
* @param maxX Maximum X coordinate
|
||||
* @param minZ Minimum Z coordinate
|
||||
* @param maxZ Maximum Z coordinate
|
||||
* @return Set of found images
|
||||
*
|
||||
* @param world World instance
|
||||
* @param minX Minimum X coordinate
|
||||
* @param maxX Maximum X coordinate
|
||||
* @param minZ Minimum Z coordinate
|
||||
* @param maxZ Maximum Z coordinate
|
||||
* @return List of found images
|
||||
*/
|
||||
public @NotNull Set<FakeImage> getImages(@NotNull World world, int minX, int maxX, int minZ, int maxZ) {
|
||||
|
||||
Set<FakeImage> response = new HashSet<>();
|
||||
for (Map.Entry<WorldAreaId, Set<FakeImage>> entry : images.entrySet()) {
|
||||
if (!entry.getKey().getWorld().getName().equals(world.getName())) continue;
|
||||
|
||||
if (!entry.getKey().getWorld().getName().equals(world.getName()))
|
||||
continue;
|
||||
for (FakeImage image : entry.getValue()) {
|
||||
|
||||
Location loc = image.getLocation();
|
||||
if (loc.getBlockX() < minX || loc.getBlockX() > maxX) continue;
|
||||
if (loc.getBlockZ() < minZ || loc.getBlockZ() > maxZ) continue;
|
||||
if (loc.getBlockX() < minX || loc.getBlockX() > maxX)
|
||||
continue;
|
||||
if (loc.getBlockZ() < minZ || loc.getBlockZ() > maxZ)
|
||||
continue;
|
||||
response.add(image);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return response;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove image from renderer
|
||||
*
|
||||
* @param image Fake image instance
|
||||
*/
|
||||
public void removeImage(@NotNull FakeImage image) {
|
||||
|
||||
WorldAreaId[] imageWorldAreaIds = image.getWorldAreaIds();
|
||||
|
||||
// Destroy image from all players nearby
|
||||
@@ -295,12 +338,16 @@ public class ImageRenderer implements Listener {
|
||||
|
||||
// Remove image from renderer
|
||||
for (WorldAreaId worldAreaId : imageWorldAreaIds) {
|
||||
|
||||
Set<FakeImage> worldAreaImages = images.get(worldAreaId);
|
||||
worldAreaImages.remove(image);
|
||||
if (worldAreaImages.isEmpty()) {
|
||||
LOGGER.fine("Destroyed WorldArea#(" + worldAreaId + ")");
|
||||
|
||||
plugin.fine("Destroyed WorldArea#(" + worldAreaId + ")");
|
||||
images.remove(worldAreaId);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Set configuration changed flag
|
||||
@@ -308,104 +355,141 @@ public class ImageRenderer implements Listener {
|
||||
|
||||
// Decrement count of placed images by player
|
||||
UUID placedById = image.getPlacedBy().getUniqueId();
|
||||
imagesCountByPlayer.compute(placedById, (__, prev) -> (prev != null && prev > 1) ? prev-1 : null);
|
||||
imagesCountByPlayer.compute(placedById, (__, prev) -> (prev != null && prev > 1) ? prev - 1 : null);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get set of players who have placed images
|
||||
*
|
||||
* @return Offline players
|
||||
*/
|
||||
public @NotNull Set<OfflinePlayer> getPlayersWithPlacedImages() {
|
||||
|
||||
return imagesCountByPlayer.keySet().stream().map(Bukkit::getOfflinePlayer).collect(Collectors.toSet());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get number of placed images
|
||||
*
|
||||
* @return Number of placed images
|
||||
*/
|
||||
public int size() {
|
||||
|
||||
return imagesCountByPlayer.values().stream().reduce(0, Integer::sum);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get number of placed images grouped by player
|
||||
* <p>
|
||||
* NOTE: Response is sorted by image count (descending)
|
||||
*
|
||||
* @return Images count by player
|
||||
*/
|
||||
public @NotNull Map<OfflinePlayer, Integer> getImagesCountByPlayer() {
|
||||
|
||||
List<Map.Entry<UUID, Integer>> sortedEntries = new ArrayList<>(imagesCountByPlayer.entrySet());
|
||||
sortedEntries.sort(Map.Entry.comparingByValue(Comparator.reverseOrder()));
|
||||
|
||||
Map<OfflinePlayer, Integer> sortedMap = new LinkedHashMap<>();
|
||||
for (Map.Entry<UUID, Integer> entry : sortedEntries) {
|
||||
|
||||
sortedMap.put(Bukkit.getOfflinePlayer(entry.getKey()), entry.getValue());
|
||||
|
||||
}
|
||||
|
||||
return sortedMap;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get players in view distance of the provided world area IDs
|
||||
* @param ids World area IDs
|
||||
* @return Players inside those world areas
|
||||
*
|
||||
* @param ids World area IDs
|
||||
* @return Players inside those world areas
|
||||
*/
|
||||
private @NotNull Set<Player> getPlayersInViewDistance(@NotNull WorldAreaId[] ids) {
|
||||
|
||||
Set<WorldAreaId> neighborhood = new HashSet<>();
|
||||
for (WorldAreaId worldAreaId : ids) {
|
||||
|
||||
Collections.addAll(neighborhood, worldAreaId.getNeighborhood());
|
||||
|
||||
}
|
||||
|
||||
Set<Player> players = new HashSet<>();
|
||||
for (Map.Entry<Player, WorldAreaId> entry : playersLocation.entrySet()) {
|
||||
|
||||
if (neighborhood.contains(entry.getValue())) {
|
||||
|
||||
players.add(entry.getKey());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return players;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get images in view distance from world area ID
|
||||
* @param worldAreaId World area ID
|
||||
* @return Set of fake images
|
||||
*
|
||||
* @param worldAreaId World area ID
|
||||
* @return Set of fake images
|
||||
*/
|
||||
private @NotNull Set<FakeImage> getImagesInViewDistance(@NotNull WorldAreaId worldAreaId) {
|
||||
|
||||
Set<FakeImage> response = new HashSet<>();
|
||||
for (WorldAreaId target : worldAreaId.getNeighborhood()) {
|
||||
|
||||
Set<FakeImage> targetImages = images.get(target);
|
||||
if (targetImages != null) {
|
||||
|
||||
response.addAll(targetImages);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return response;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* On player location change
|
||||
*
|
||||
* @param player Player instance
|
||||
* @param location New player location
|
||||
*/
|
||||
private void onPlayerLocationChange(@NotNull Player player, @NotNull Location location) {
|
||||
|
||||
// Ignore NPC events from other plugins
|
||||
if (player.hasMetadata("NPC")) {
|
||||
LOGGER.fine("Ignored NPC event from Player#" + player.getName());
|
||||
|
||||
plugin.fine("Ignored NPC event from Player#" + player.getName());
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
// Has player moved to another world area?
|
||||
WorldAreaId worldAreaId = WorldAreaId.fromLocation(location);
|
||||
WorldAreaId prevWorldAreaId = playersLocation.get(player);
|
||||
if (worldAreaId.equals(prevWorldAreaId)) {
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
playersLocation.put(player, worldAreaId);
|
||||
LOGGER.fine("Player#" + player.getName() + " moved to WorldArea#(" + worldAreaId + ")");
|
||||
plugin.fine("Player#" + player.getName() + " moved to WorldArea#(" + worldAreaId + ")");
|
||||
|
||||
// Get images that should be spawned/destroyed
|
||||
Set<FakeImage> desiredState = getImagesInViewDistance(worldAreaId);
|
||||
Set<FakeImage> currentState = (prevWorldAreaId == null) ? new HashSet<>() : getImagesInViewDistance(prevWorldAreaId);
|
||||
Set<FakeImage> currentState = (prevWorldAreaId == null) ? new HashSet<>()
|
||||
: getImagesInViewDistance(prevWorldAreaId);
|
||||
Set<FakeImage> imagesToLoad = new HashSet<>(desiredState);
|
||||
imagesToLoad.removeAll(currentState);
|
||||
Set<FakeImage> imagesToUnload = new HashSet<>(currentState);
|
||||
@@ -413,65 +497,101 @@ public class ImageRenderer implements Listener {
|
||||
|
||||
// Spawn/destroy images
|
||||
for (FakeImage image : imagesToUnload) {
|
||||
|
||||
image.destroy(player);
|
||||
|
||||
}
|
||||
|
||||
for (FakeImage image : imagesToLoad) {
|
||||
|
||||
image.spawn(player);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
|
||||
public void onPlayerJoin(@NotNull PlayerJoinEvent event) {
|
||||
|
||||
onPlayerLocationChange(event.getPlayer(), event.getPlayer().getLocation());
|
||||
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
|
||||
public void onPlayerQuit(@NotNull PlayerQuitEvent event) {
|
||||
|
||||
Player player = event.getPlayer();
|
||||
|
||||
// Get player's current world area ID
|
||||
WorldAreaId worldAreaId = playersLocation.get(player);
|
||||
if (worldAreaId == null) return;
|
||||
if (worldAreaId == null)
|
||||
return;
|
||||
playersLocation.remove(player);
|
||||
|
||||
// Notify world areas that player quit
|
||||
for (FakeImage image : getImagesInViewDistance(worldAreaId)) {
|
||||
|
||||
image.notifyPlayerQuit(player);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
|
||||
public void onPlayerRespawn(@NotNull PlayerRespawnEvent event) {
|
||||
|
||||
onPlayerLocationChange(event.getPlayer(), event.getPlayer().getLocation());
|
||||
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
|
||||
public void onPlayerTeleport(@NotNull PlayerTeleportEvent event) {
|
||||
if (event.getTo() == null) return;
|
||||
if (event.getFrom().getChunk().equals(event.getTo().getChunk())) return;
|
||||
|
||||
if (event.getTo() == null)
|
||||
return;
|
||||
if (event.getFrom().getChunk().equals(event.getTo().getChunk()))
|
||||
return;
|
||||
|
||||
// Wait until next server tick before handling location change
|
||||
// This is necessary as teleport events get fired *before* teleporting the player
|
||||
YamipaPlugin plugin = YamipaPlugin.getInstance();
|
||||
Bukkit.getScheduler().runTask(plugin, () -> onPlayerLocationChange(event.getPlayer(), event.getTo()));
|
||||
// This is necessary as teleport events get fired *before* teleporting the
|
||||
// player
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
|
||||
onPlayerLocationChange(event.getPlayer(), event.getTo());
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
|
||||
public void onPlayerMove(@NotNull PlayerMoveEvent event) {
|
||||
if (event.getTo() == null) return;
|
||||
if (event.getFrom().getChunk().equals(event.getTo().getChunk())) return;
|
||||
|
||||
if (event.getTo() == null)
|
||||
return;
|
||||
if (event.getFrom().getChunk().equals(event.getTo().getChunk()))
|
||||
return;
|
||||
onPlayerLocationChange(event.getPlayer(), event.getTo());
|
||||
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
|
||||
public void onVehicleMove(@NotNull VehicleMoveEvent event) {
|
||||
|
||||
List<Entity> passengers = event.getVehicle().getPassengers();
|
||||
if (passengers.isEmpty()) return;
|
||||
if (event.getFrom().getChunk().equals(event.getTo().getChunk())) return;
|
||||
if (passengers.isEmpty())
|
||||
return;
|
||||
if (event.getFrom().getChunk().equals(event.getTo().getChunk()))
|
||||
return;
|
||||
for (Entity passenger : passengers) {
|
||||
|
||||
if (passenger instanceof Player) {
|
||||
|
||||
onPlayerLocationChange((Player) passenger, event.getTo());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import io.josemmo.bukkit.plugin.commands.ImageCommand;
|
||||
import io.josemmo.bukkit.plugin.storage.ImageFile;
|
||||
import io.josemmo.bukkit.plugin.utils.ActionBar;
|
||||
import io.josemmo.bukkit.plugin.utils.InteractWithEntityListener;
|
||||
import io.josemmo.bukkit.plugin.utils.Logger;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockFace;
|
||||
@@ -26,87 +25,100 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import java.util.Collections;
|
||||
import java.util.Objects;
|
||||
import net.trueog.utilitiesog.UtilitiesOG;
|
||||
|
||||
public class ItemService extends InteractWithEntityListener implements Listener {
|
||||
private static final Logger LOGGER = Logger.getLogger("ItemService");
|
||||
private static final NamespacedKey NSK_FILENAME;
|
||||
private static final NamespacedKey NSK_WIDTH;
|
||||
private static final NamespacedKey NSK_HEIGHT;
|
||||
private static final NamespacedKey NSK_FLAGS;
|
||||
|
||||
static {
|
||||
YamipaPlugin plugin = YamipaPlugin.getInstance(); // Only used for getting namespace, reference will be freed
|
||||
NSK_FILENAME = new NamespacedKey(plugin, "filename");
|
||||
NSK_WIDTH = new NamespacedKey(plugin, "width");
|
||||
NSK_HEIGHT = new NamespacedKey(plugin, "height");
|
||||
NSK_FLAGS = new NamespacedKey(plugin, "flags");
|
||||
}
|
||||
private static final YamipaPlugin plugin = YamipaPlugin.getInstance();
|
||||
private static final NamespacedKey NSK_FILENAME = new NamespacedKey(plugin, "filename");
|
||||
private static final NamespacedKey NSK_WIDTH = new NamespacedKey(plugin, "width");
|
||||
private static final NamespacedKey NSK_HEIGHT = new NamespacedKey(plugin, "height");
|
||||
private static final NamespacedKey NSK_FLAGS = new NamespacedKey(plugin, "flags");
|
||||
|
||||
/**
|
||||
* Get image item
|
||||
* @param image Image file
|
||||
* @param amount Stack amount
|
||||
* @param width Image width in blocks
|
||||
* @param height Image height in blocks
|
||||
* @param flags Image flags
|
||||
* @return Image item
|
||||
*
|
||||
* @param image Image file
|
||||
* @param amount Stack amount
|
||||
* @param width Image width in blocks
|
||||
* @param height Image height in blocks
|
||||
* @param flags Image flags
|
||||
* @return Image item
|
||||
*/
|
||||
public static @NotNull ItemStack getImageItem(@NotNull ImageFile image, int amount, int width, int height, int flags) {
|
||||
public static @NotNull ItemStack getImageItem(@NotNull ImageFile image, int amount, int width, int height,
|
||||
int flags)
|
||||
{
|
||||
|
||||
ItemStack itemStack = new ItemStack(Material.ITEM_FRAME, amount);
|
||||
ItemMeta itemMeta = Objects.requireNonNull(itemStack.getItemMeta());
|
||||
|
||||
// Set metadata
|
||||
PersistentDataContainer itemData = itemMeta.getPersistentDataContainer();
|
||||
itemMeta.setDisplayName(image.getFilename() + ChatColor.AQUA + " (" + width + "x" + height + ")");
|
||||
itemMeta.setLore(Collections.singletonList("Yamipa image"));
|
||||
itemData.set(NSK_FILENAME, PersistentDataType.STRING, image.getFilename());
|
||||
itemMeta.displayName(UtilitiesOG.trueogColorize(image.getName() + "&b (" + width + "x" + height + ")"));
|
||||
itemMeta.lore(Collections.singletonList(UtilitiesOG.trueogColorize("Yamipa image")));
|
||||
itemData.set(NSK_FILENAME, PersistentDataType.STRING, image.getName());
|
||||
itemData.set(NSK_WIDTH, PersistentDataType.INTEGER, width);
|
||||
itemData.set(NSK_HEIGHT, PersistentDataType.INTEGER, height);
|
||||
itemData.set(NSK_FLAGS, PersistentDataType.INTEGER, flags);
|
||||
itemStack.setItemMeta(itemMeta);
|
||||
|
||||
return itemStack;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Start service
|
||||
*/
|
||||
public void start() {
|
||||
|
||||
register();
|
||||
YamipaPlugin plugin = YamipaPlugin.getInstance();
|
||||
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop service
|
||||
*/
|
||||
public void stop() {
|
||||
|
||||
unregister();
|
||||
HandlerList.unregisterAll(this);
|
||||
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onCraftItem(@NotNull PrepareItemCraftEvent event) {
|
||||
|
||||
CraftingInventory inventory = event.getInventory();
|
||||
for (@Nullable ItemStack item : inventory.getMatrix()) {
|
||||
if (item == null) continue;
|
||||
for (@Nullable
|
||||
ItemStack item : inventory.getMatrix()) {
|
||||
|
||||
if (item == null)
|
||||
continue;
|
||||
|
||||
// Get metadata from item
|
||||
ItemMeta itemMeta = item.getItemMeta();
|
||||
if (itemMeta == null) continue;
|
||||
if (itemMeta == null)
|
||||
continue;
|
||||
|
||||
// Prevent crafting recipes with image items
|
||||
if (itemMeta.getPersistentDataContainer().has(NSK_FILENAME, PersistentDataType.STRING)) {
|
||||
|
||||
inventory.setResult(new ItemStack(Material.AIR));
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
|
||||
public void onPlaceItem(@NotNull HangingPlaceEvent event) {
|
||||
|
||||
Player player = event.getPlayer();
|
||||
if (player == null) return;
|
||||
if (player == null)
|
||||
return;
|
||||
PlayerInventory inventory = player.getInventory();
|
||||
|
||||
// Prepare variables to hold item metadata
|
||||
@@ -118,40 +130,52 @@ public class ItemService extends InteractWithEntityListener implements Listener
|
||||
Integer flags = null;
|
||||
|
||||
// Find placed item stack in player's inventory
|
||||
// NOTE: we cannot use `event.getItemStack()` as it's only supported since 1.17.1
|
||||
for (int i=0; i<2; i++) {
|
||||
// NOTE: we cannot use `event.getItemStack()` as it's only supported since
|
||||
// 1.17.1
|
||||
for (int i = 0; i < 2; i++) {
|
||||
|
||||
isOffHand = (i == 1);
|
||||
item = isOffHand ? inventory.getItemInOffHand() : inventory.getItemInMainHand();
|
||||
|
||||
// Get item metadata
|
||||
ItemMeta itemMeta = item.getItemMeta();
|
||||
if (itemMeta == null) continue;
|
||||
if (itemMeta == null)
|
||||
continue;
|
||||
PersistentDataContainer itemData = itemMeta.getPersistentDataContainer();
|
||||
filename = itemData.get(NSK_FILENAME, PersistentDataType.STRING);
|
||||
if (filename == null) continue;
|
||||
if (filename == null)
|
||||
continue;
|
||||
|
||||
// Found item, parse metadata
|
||||
width = itemData.get(NSK_WIDTH, PersistentDataType.INTEGER);
|
||||
height = itemData.get(NSK_HEIGHT, PersistentDataType.INTEGER);
|
||||
flags = itemData.get(NSK_FLAGS, PersistentDataType.INTEGER);
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
// Validate item and metadata
|
||||
if (filename == null) {
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
if (width == null || height == null || flags == null) {
|
||||
LOGGER.warning(player + " tried to place corrupted image item (missing width/height/flags properties)");
|
||||
|
||||
plugin.warning(player + " tried to place corrupted image item (missing width/height/flags properties)");
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
// Validate filename
|
||||
ImageFile image = YamipaPlugin.getInstance().getStorage().get(filename);
|
||||
if (image == null) {
|
||||
LOGGER.warning(player + " tried to place corrupted image item (\"" + filename + "\" no longer exists)");
|
||||
ActionBar.send(player, ChatColor.RED + "Image file \"" + filename + "\" no longer exists");
|
||||
|
||||
plugin.warning(player + " tried to place corrupted image item (\"" + filename + "\" no longer exists)");
|
||||
ActionBar.send(player, "&cImage file \"" + filename + "\" no longer exists");
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
// Prevent item frame placing
|
||||
@@ -159,74 +183,106 @@ public class ItemService extends InteractWithEntityListener implements Listener
|
||||
|
||||
// Validate player permissions
|
||||
if (!player.hasPermission("yamipa.item.place")) {
|
||||
ActionBar.send(player, ChatColor.RED + "You're not allowed to place image items!");
|
||||
|
||||
ActionBar.send(player, "&cYou're not allowed to place image items!");
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
// Try to place image in world
|
||||
Location location = event.getBlock().getLocation();
|
||||
boolean success = ImageCommand.placeImage(player, image, width, height, flags, location, event.getBlockFace());
|
||||
if (!success) return;
|
||||
if (!success)
|
||||
return;
|
||||
|
||||
// Decrement item from player's inventory
|
||||
if (player.getGameMode() != GameMode.CREATIVE) {
|
||||
|
||||
int amount = item.getAmount();
|
||||
if (amount > 1) {
|
||||
|
||||
item.setAmount(amount - 1);
|
||||
|
||||
} else {
|
||||
|
||||
item = new ItemStack(Material.AIR);
|
||||
|
||||
}
|
||||
|
||||
if (isOffHand) {
|
||||
|
||||
inventory.setItemInOffHand(item);
|
||||
|
||||
} else {
|
||||
|
||||
inventory.setItemInMainHand(item);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onAttack(@NotNull Player player, @NotNull Block block, @NotNull BlockFace face) {
|
||||
ImageRenderer renderer = YamipaPlugin.getInstance().getRenderer();
|
||||
|
||||
ImageRenderer renderer = plugin.getRenderer();
|
||||
Location location = block.getLocation();
|
||||
|
||||
// Has the player clicked a removable placed image?
|
||||
FakeImage image = renderer.getImage(location, face);
|
||||
if (image == null || !image.hasFlag(FakeImage.FLAG_REMOVABLE)) return true;
|
||||
if (image == null || !image.hasFlag(FakeImage.FLAG_REMOVABLE))
|
||||
return true;
|
||||
|
||||
// Validate player permissions
|
||||
if (!player.hasPermission("yamipa.item.remove.own")) {
|
||||
ActionBar.send(player, ChatColor.RED + "You're not allowed to remove image items!");
|
||||
|
||||
ActionBar.send(player, "&cYou're not allowed to remove image items!");
|
||||
return true;
|
||||
|
||||
}
|
||||
if (
|
||||
!player.getUniqueId().equals(image.getPlacedBy().getUniqueId()) &&
|
||||
!player.hasPermission("yamipa.item.remove")
|
||||
) {
|
||||
ActionBar.send(player, ChatColor.RED + "You cannot remove image items from other players!");
|
||||
|
||||
if (!player.getUniqueId().equals(image.getPlacedBy().getUniqueId())
|
||||
&& !player.hasPermission("yamipa.item.remove"))
|
||||
{
|
||||
|
||||
ActionBar.send(player, "&cYou cannot remove image items from other players!");
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
// Attempt to remove image
|
||||
boolean success = ImageCommand.removeImage(player, image);
|
||||
if (!success) {
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
// Drop image item
|
||||
if (player.getGameMode() == GameMode.SURVIVAL && image.hasFlag(FakeImage.FLAG_DROPPABLE)) {
|
||||
|
||||
ImageFile imageFile = Objects.requireNonNull(image.getFile());
|
||||
ItemStack imageItem = getImageItem(imageFile, 1, image.getWidth(), image.getHeight(), image.getFlags());
|
||||
Location dropLocation = location.clone().add(0.5, -0.5, 0.5).add(face.getDirection());
|
||||
YamipaPlugin plugin = YamipaPlugin.getInstance();
|
||||
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, () -> block.getWorld().dropItem(dropLocation, imageItem));
|
||||
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, () -> {
|
||||
|
||||
block.getWorld().dropItem(dropLocation, imageItem);
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onInteract(@NotNull Player player, @NotNull Block block, @NotNull BlockFace face) {
|
||||
|
||||
// Intentionally left blank
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import java.util.Objects;
|
||||
* World Area IDs represent groups of 16 chunks arranged in a 4x4 square.
|
||||
*/
|
||||
public class WorldAreaId {
|
||||
|
||||
private static boolean USE_WORLD_VIEW_DISTANCE = true;
|
||||
private static final Map<String, Integer> SIZES_PER_WORLD = new HashMap<>();
|
||||
private final World world;
|
||||
@@ -22,395 +23,246 @@ public class WorldAreaId {
|
||||
private WorldAreaId[] neighborhood = null;
|
||||
|
||||
static {
|
||||
|
||||
try {
|
||||
|
||||
World.class.getMethod("getViewDistance");
|
||||
|
||||
} catch (Exception e) {
|
||||
|
||||
USE_WORLD_VIEW_DISTANCE = false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ID from location
|
||||
* @param location Location instance
|
||||
* @return World area ID
|
||||
*
|
||||
* @param location Location instance
|
||||
* @return World area ID
|
||||
*/
|
||||
public static @NotNull WorldAreaId fromLocation(@NotNull Location location) {
|
||||
|
||||
Chunk chunk = location.getChunk();
|
||||
return new WorldAreaId(chunk.getWorld(), chunk.getX() >> 2, chunk.getZ() >> 2);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* @param world World instance
|
||||
* @param x World area X coordinate
|
||||
* @param z World area Z coordinate
|
||||
*/
|
||||
public WorldAreaId(@NotNull World world, int x, int z) {
|
||||
|
||||
this.world = world;
|
||||
this.x = x;
|
||||
this.z = z;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get world instance
|
||||
*
|
||||
* @return World instance
|
||||
*/
|
||||
public @NotNull World getWorld() {
|
||||
|
||||
return world;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get nearby world area IDs in view distance (plus this one)
|
||||
* @return Array of neighbors
|
||||
*
|
||||
* @return List of neighbors
|
||||
*/
|
||||
public @NotNull WorldAreaId[] getNeighborhood() {
|
||||
|
||||
// Get value from cache (if available)
|
||||
if (neighborhood != null) {
|
||||
|
||||
return neighborhood;
|
||||
|
||||
}
|
||||
|
||||
// Calculate neighborhood size from world's view distance
|
||||
// NOTE: World size is cached to prevent issues with plugins that modify it at runtime
|
||||
// NOTE: World size is cached to prevent issues with plugins that modify it at
|
||||
// runtime
|
||||
int size = SIZES_PER_WORLD.computeIfAbsent(world.getName(), (__) -> {
|
||||
|
||||
int distance = USE_WORLD_VIEW_DISTANCE ? world.getViewDistance() : Bukkit.getServer().getViewDistance();
|
||||
return distance / 4;
|
||||
|
||||
});
|
||||
|
||||
// Size 0 (1+1+1=3)
|
||||
// ·|·
|
||||
// XOX
|
||||
// ·|·
|
||||
if (size == 0) {
|
||||
neighborhood = new WorldAreaId[]{
|
||||
new WorldAreaId(world, x, z-1),
|
||||
|
||||
new WorldAreaId(world, x-1, z),
|
||||
this,
|
||||
new WorldAreaId(world, x+1, z),
|
||||
|
||||
new WorldAreaId(world, x, z+1),
|
||||
};
|
||||
return neighborhood;
|
||||
}
|
||||
|
||||
// Size 1 (2+1+2=5)
|
||||
// ·X|X·
|
||||
// XX|XX
|
||||
// XXOXX
|
||||
// XX|XX
|
||||
// ·X|X·
|
||||
if (size == 1) {
|
||||
neighborhood = new WorldAreaId[]{
|
||||
new WorldAreaId(world, x-1, z-2),
|
||||
new WorldAreaId(world, x, z-2),
|
||||
new WorldAreaId(world, x+1, z-2),
|
||||
|
||||
new WorldAreaId(world, x-2, z-1),
|
||||
new WorldAreaId(world, x-1, z-1),
|
||||
new WorldAreaId(world, x, z-1),
|
||||
new WorldAreaId(world, x+1, z-1),
|
||||
new WorldAreaId(world, x+2, z-1),
|
||||
|
||||
new WorldAreaId(world, x-2, z),
|
||||
new WorldAreaId(world, x-1, z),
|
||||
this,
|
||||
new WorldAreaId(world, x+1, z),
|
||||
new WorldAreaId(world, x+2, z),
|
||||
|
||||
new WorldAreaId(world, x-2, z+1),
|
||||
new WorldAreaId(world, x-1, z+1),
|
||||
new WorldAreaId(world, x, z+1),
|
||||
new WorldAreaId(world, x+1, z+1),
|
||||
new WorldAreaId(world, x+2, z+1),
|
||||
|
||||
new WorldAreaId(world, x-1, z+2),
|
||||
new WorldAreaId(world, x, z+2),
|
||||
new WorldAreaId(world, x+1, z+2),
|
||||
};
|
||||
return neighborhood;
|
||||
}
|
||||
|
||||
// Size 2 (3+1+3=7)
|
||||
// ··X|X··
|
||||
// ·XX|XX·
|
||||
// XXX|XXX
|
||||
// XXXOXXX
|
||||
// XXX|XXX
|
||||
// ·XX|XX·
|
||||
// ··X|X··
|
||||
if (size == 2) {
|
||||
neighborhood = new WorldAreaId[]{
|
||||
new WorldAreaId(world, x-1, z-3),
|
||||
new WorldAreaId(world, x, z-3),
|
||||
new WorldAreaId(world, x+1, z-3),
|
||||
|
||||
new WorldAreaId(world, x-2, z-2),
|
||||
new WorldAreaId(world, x-1, z-2),
|
||||
new WorldAreaId(world, x, z-2),
|
||||
new WorldAreaId(world, x+1, z-2),
|
||||
new WorldAreaId(world, x+2, z-2),
|
||||
|
||||
new WorldAreaId(world, x-3, z-1),
|
||||
new WorldAreaId(world, x-2, z-1),
|
||||
new WorldAreaId(world, x-1, z-1),
|
||||
new WorldAreaId(world, x, z-1),
|
||||
new WorldAreaId(world, x+1, z-1),
|
||||
new WorldAreaId(world, x+2, z-1),
|
||||
new WorldAreaId(world, x+3, z-1),
|
||||
|
||||
new WorldAreaId(world, x-3, z),
|
||||
new WorldAreaId(world, x-2, z),
|
||||
new WorldAreaId(world, x-1, z),
|
||||
this,
|
||||
new WorldAreaId(world, x+1, z),
|
||||
new WorldAreaId(world, x+2, z),
|
||||
new WorldAreaId(world, x+3, z),
|
||||
|
||||
new WorldAreaId(world, x-3, z+1),
|
||||
new WorldAreaId(world, x-2, z+1),
|
||||
new WorldAreaId(world, x-1, z+1),
|
||||
new WorldAreaId(world, x, z+1),
|
||||
new WorldAreaId(world, x+1, z+1),
|
||||
new WorldAreaId(world, x+2, z+1),
|
||||
new WorldAreaId(world, x+3, z+1),
|
||||
|
||||
new WorldAreaId(world, x-2, z+2),
|
||||
new WorldAreaId(world, x-1, z+2),
|
||||
new WorldAreaId(world, x, z+2),
|
||||
new WorldAreaId(world, x+1, z+2),
|
||||
new WorldAreaId(world, x+2, z+2),
|
||||
|
||||
new WorldAreaId(world, x-1, z+3),
|
||||
new WorldAreaId(world, x, z+3),
|
||||
new WorldAreaId(world, x+1, z+3),
|
||||
};
|
||||
return neighborhood;
|
||||
}
|
||||
|
||||
// Size 3 (4+1+4=9)
|
||||
// ···X|X···
|
||||
// ·XXX|XXX·
|
||||
// ·XXX|XXX·
|
||||
// XXXX|XXXX
|
||||
// Size 3 (16-chunks radius)
|
||||
// ···XXX···
|
||||
// ·XXXXXXX·
|
||||
// ·XXXXXXX·
|
||||
// XXXXXXXXX
|
||||
// XXXXOXXXX
|
||||
// XXXX|XXXX
|
||||
// ·XXX|XXX·
|
||||
// ·XXX|XXX·
|
||||
// ···X|X···
|
||||
// XXXXXXXXX
|
||||
// ·XXXXXXX·
|
||||
// ·XXXXXXX·
|
||||
// ···XXX···
|
||||
if (size == 3) {
|
||||
neighborhood = new WorldAreaId[]{
|
||||
new WorldAreaId(world, x-1, z-4),
|
||||
new WorldAreaId(world, x, z-4),
|
||||
new WorldAreaId(world, x+1, z-4),
|
||||
|
||||
new WorldAreaId(world, x-3, z-3),
|
||||
new WorldAreaId(world, x-2, z-3),
|
||||
new WorldAreaId(world, x-1, z-3),
|
||||
new WorldAreaId(world, x, z-3),
|
||||
new WorldAreaId(world, x+1, z-3),
|
||||
new WorldAreaId(world, x+2, z-3),
|
||||
new WorldAreaId(world, x+3, z-3),
|
||||
neighborhood = new WorldAreaId[] { new WorldAreaId(world, x - 1, z - 4), new WorldAreaId(world, x, z - 4),
|
||||
new WorldAreaId(world, x + 1, z - 4),
|
||||
|
||||
new WorldAreaId(world, x-3, z-2),
|
||||
new WorldAreaId(world, x-2, z-2),
|
||||
new WorldAreaId(world, x-1, z-2),
|
||||
new WorldAreaId(world, x, z-2),
|
||||
new WorldAreaId(world, x+1, z-2),
|
||||
new WorldAreaId(world, x+2, z-2),
|
||||
new WorldAreaId(world, x+3, z-2),
|
||||
new WorldAreaId(world, x - 3, z - 3), new WorldAreaId(world, x - 2, z - 3),
|
||||
new WorldAreaId(world, x - 1, z - 3), new WorldAreaId(world, x, z - 3),
|
||||
new WorldAreaId(world, x + 1, z - 3), new WorldAreaId(world, x + 2, z - 3),
|
||||
new WorldAreaId(world, x + 3, z - 3),
|
||||
|
||||
new WorldAreaId(world, x-4, z-1),
|
||||
new WorldAreaId(world, x-3, z-1),
|
||||
new WorldAreaId(world, x-2, z-1),
|
||||
new WorldAreaId(world, x-1, z-1),
|
||||
new WorldAreaId(world, x, z-1),
|
||||
new WorldAreaId(world, x+1, z-1),
|
||||
new WorldAreaId(world, x+2, z-1),
|
||||
new WorldAreaId(world, x+3, z-1),
|
||||
new WorldAreaId(world, x+4, z-1),
|
||||
new WorldAreaId(world, x - 3, z - 2), new WorldAreaId(world, x - 2, z - 2),
|
||||
new WorldAreaId(world, x - 1, z - 2), new WorldAreaId(world, x, z - 2),
|
||||
new WorldAreaId(world, x + 1, z - 2), new WorldAreaId(world, x + 2, z - 2),
|
||||
new WorldAreaId(world, x + 3, z - 2),
|
||||
|
||||
new WorldAreaId(world, x-4, z),
|
||||
new WorldAreaId(world, x-3, z),
|
||||
new WorldAreaId(world, x-2, z),
|
||||
new WorldAreaId(world, x-1, z),
|
||||
this,
|
||||
new WorldAreaId(world, x+1, z),
|
||||
new WorldAreaId(world, x+2, z),
|
||||
new WorldAreaId(world, x+3, z),
|
||||
new WorldAreaId(world, x+4, z),
|
||||
new WorldAreaId(world, x - 4, z - 1), new WorldAreaId(world, x - 3, z - 1),
|
||||
new WorldAreaId(world, x - 2, z - 1), new WorldAreaId(world, x - 1, z - 1),
|
||||
new WorldAreaId(world, x, z - 1), new WorldAreaId(world, x + 1, z - 1),
|
||||
new WorldAreaId(world, x + 2, z - 1), new WorldAreaId(world, x + 3, z - 1),
|
||||
new WorldAreaId(world, x + 4, z - 1),
|
||||
|
||||
new WorldAreaId(world, x-4, z+1),
|
||||
new WorldAreaId(world, x-3, z+1),
|
||||
new WorldAreaId(world, x-2, z+1),
|
||||
new WorldAreaId(world, x-1, z+1),
|
||||
new WorldAreaId(world, x, z+1),
|
||||
new WorldAreaId(world, x+1, z+1),
|
||||
new WorldAreaId(world, x+2, z+1),
|
||||
new WorldAreaId(world, x+3, z+1),
|
||||
new WorldAreaId(world, x+4, z+1),
|
||||
new WorldAreaId(world, x - 4, z), new WorldAreaId(world, x - 3, z),
|
||||
new WorldAreaId(world, x - 2, z), new WorldAreaId(world, x - 1, z), this,
|
||||
new WorldAreaId(world, x + 1, z), new WorldAreaId(world, x + 2, z),
|
||||
new WorldAreaId(world, x + 3, z), new WorldAreaId(world, x + 4, z),
|
||||
|
||||
new WorldAreaId(world, x-3, z+2),
|
||||
new WorldAreaId(world, x-2, z+2),
|
||||
new WorldAreaId(world, x-1, z+2),
|
||||
new WorldAreaId(world, x, z+2),
|
||||
new WorldAreaId(world, x+1, z+2),
|
||||
new WorldAreaId(world, x+2, z+2),
|
||||
new WorldAreaId(world, x+3, z+2),
|
||||
new WorldAreaId(world, x - 4, z + 1), new WorldAreaId(world, x - 3, z + 1),
|
||||
new WorldAreaId(world, x - 2, z + 1), new WorldAreaId(world, x - 1, z + 1),
|
||||
new WorldAreaId(world, x, z + 1), new WorldAreaId(world, x + 1, z + 1),
|
||||
new WorldAreaId(world, x + 2, z + 1), new WorldAreaId(world, x + 3, z + 1),
|
||||
new WorldAreaId(world, x + 4, z + 1),
|
||||
|
||||
new WorldAreaId(world, x-3, z+3),
|
||||
new WorldAreaId(world, x-2, z+3),
|
||||
new WorldAreaId(world, x-1, z+3),
|
||||
new WorldAreaId(world, x, z+3),
|
||||
new WorldAreaId(world, x+1, z+3),
|
||||
new WorldAreaId(world, x+2, z+3),
|
||||
new WorldAreaId(world, x+3, z+3),
|
||||
new WorldAreaId(world, x - 3, z + 2), new WorldAreaId(world, x - 2, z + 2),
|
||||
new WorldAreaId(world, x - 1, z + 2), new WorldAreaId(world, x, z + 2),
|
||||
new WorldAreaId(world, x + 1, z + 2), new WorldAreaId(world, x + 2, z + 2),
|
||||
new WorldAreaId(world, x + 3, z + 2),
|
||||
|
||||
new WorldAreaId(world, x-1, z+4),
|
||||
new WorldAreaId(world, x, z+4),
|
||||
new WorldAreaId(world, x+1, z+4),
|
||||
};
|
||||
new WorldAreaId(world, x - 3, z + 3), new WorldAreaId(world, x - 2, z + 3),
|
||||
new WorldAreaId(world, x - 1, z + 3), new WorldAreaId(world, x, z + 3),
|
||||
new WorldAreaId(world, x + 1, z + 3), new WorldAreaId(world, x + 2, z + 3),
|
||||
new WorldAreaId(world, x + 3, z + 3),
|
||||
|
||||
new WorldAreaId(world, x - 1, z + 4), new WorldAreaId(world, x, z + 4),
|
||||
new WorldAreaId(world, x + 1, z + 4), };
|
||||
return neighborhood;
|
||||
|
||||
}
|
||||
|
||||
// Size ≥4 (5+1+5=11)
|
||||
// ···XX|XX···
|
||||
// ··XXX|XXX··
|
||||
// ·XXXX|XXXX·
|
||||
// XXXXX|XXXXX
|
||||
// XXXXX|XXXXX
|
||||
// XXXXXOXXXXX
|
||||
// XXXXX|XXXXX
|
||||
// XXXXX|XXXXX
|
||||
// ·XXXX|XXXX·
|
||||
// ··XXX|XXX··
|
||||
// ···XX|XX···
|
||||
neighborhood = new WorldAreaId[]{
|
||||
new WorldAreaId(world, x-2, z-5),
|
||||
new WorldAreaId(world, x-1, z-5),
|
||||
new WorldAreaId(world, x, z-5),
|
||||
new WorldAreaId(world, x+1, z-5),
|
||||
new WorldAreaId(world, x+2, z-5),
|
||||
// Size 2 (12-chunks radius)
|
||||
// ··XXX··
|
||||
// ·XXXXX·
|
||||
// XXXXXXX
|
||||
// XXXOXXX
|
||||
// XXXXXXX
|
||||
// ·XXXXX·
|
||||
// ··XXX··
|
||||
if (size == 2) {
|
||||
|
||||
new WorldAreaId(world, x-3, z-4),
|
||||
new WorldAreaId(world, x-2, z-4),
|
||||
new WorldAreaId(world, x-1, z-4),
|
||||
new WorldAreaId(world, x, z-4),
|
||||
new WorldAreaId(world, x+1, z-4),
|
||||
new WorldAreaId(world, x+2, z-4),
|
||||
new WorldAreaId(world, x+3, z-4),
|
||||
neighborhood = new WorldAreaId[] { new WorldAreaId(world, x - 1, z - 3), new WorldAreaId(world, x, z - 3),
|
||||
new WorldAreaId(world, x + 1, z - 3),
|
||||
|
||||
new WorldAreaId(world, x-4, z-3),
|
||||
new WorldAreaId(world, x-3, z-3),
|
||||
new WorldAreaId(world, x-2, z-3),
|
||||
new WorldAreaId(world, x-1, z-3),
|
||||
new WorldAreaId(world, x, z-3),
|
||||
new WorldAreaId(world, x+1, z-3),
|
||||
new WorldAreaId(world, x+2, z-3),
|
||||
new WorldAreaId(world, x+3, z-3),
|
||||
new WorldAreaId(world, x+4, z-3),
|
||||
new WorldAreaId(world, x - 2, z - 2), new WorldAreaId(world, x - 1, z - 2),
|
||||
new WorldAreaId(world, x, z - 2), new WorldAreaId(world, x + 1, z - 2),
|
||||
new WorldAreaId(world, x + 2, z - 2),
|
||||
|
||||
new WorldAreaId(world, x-5, z-2),
|
||||
new WorldAreaId(world, x-4, z-2),
|
||||
new WorldAreaId(world, x-3, z-2),
|
||||
new WorldAreaId(world, x-2, z-2),
|
||||
new WorldAreaId(world, x-1, z-2),
|
||||
new WorldAreaId(world, x, z-2),
|
||||
new WorldAreaId(world, x+1, z-2),
|
||||
new WorldAreaId(world, x+2, z-2),
|
||||
new WorldAreaId(world, x+3, z-2),
|
||||
new WorldAreaId(world, x+4, z-2),
|
||||
new WorldAreaId(world, x+5, z-2),
|
||||
new WorldAreaId(world, x - 3, z - 1), new WorldAreaId(world, x - 2, z - 1),
|
||||
new WorldAreaId(world, x - 1, z - 1), new WorldAreaId(world, x, z - 1),
|
||||
new WorldAreaId(world, x + 1, z - 1), new WorldAreaId(world, x + 2, z - 1),
|
||||
new WorldAreaId(world, x + 3, z - 1),
|
||||
|
||||
new WorldAreaId(world, x-5, z-1),
|
||||
new WorldAreaId(world, x-4, z-1),
|
||||
new WorldAreaId(world, x-3, z-1),
|
||||
new WorldAreaId(world, x-2, z-1),
|
||||
new WorldAreaId(world, x-1, z-1),
|
||||
new WorldAreaId(world, x, z-1),
|
||||
new WorldAreaId(world, x+1, z-1),
|
||||
new WorldAreaId(world, x+2, z-1),
|
||||
new WorldAreaId(world, x+3, z-1),
|
||||
new WorldAreaId(world, x+4, z-1),
|
||||
new WorldAreaId(world, x+5, z-1),
|
||||
new WorldAreaId(world, x - 3, z), new WorldAreaId(world, x - 2, z),
|
||||
new WorldAreaId(world, x - 1, z), this, new WorldAreaId(world, x + 1, z),
|
||||
new WorldAreaId(world, x + 2, z), new WorldAreaId(world, x + 3, z),
|
||||
|
||||
new WorldAreaId(world, x-5, z),
|
||||
new WorldAreaId(world, x-4, z),
|
||||
new WorldAreaId(world, x-3, z),
|
||||
new WorldAreaId(world, x-2, z),
|
||||
new WorldAreaId(world, x-1, z),
|
||||
this,
|
||||
new WorldAreaId(world, x+1, z),
|
||||
new WorldAreaId(world, x+2, z),
|
||||
new WorldAreaId(world, x+3, z),
|
||||
new WorldAreaId(world, x+4, z),
|
||||
new WorldAreaId(world, x+5, z),
|
||||
new WorldAreaId(world, x - 3, z + 1), new WorldAreaId(world, x - 2, z + 1),
|
||||
new WorldAreaId(world, x - 1, z + 1), new WorldAreaId(world, x, z + 1),
|
||||
new WorldAreaId(world, x + 1, z + 1), new WorldAreaId(world, x + 2, z + 1),
|
||||
new WorldAreaId(world, x + 3, z + 1),
|
||||
|
||||
new WorldAreaId(world, x-5, z+1),
|
||||
new WorldAreaId(world, x-4, z+1),
|
||||
new WorldAreaId(world, x-3, z+1),
|
||||
new WorldAreaId(world, x-2, z+1),
|
||||
new WorldAreaId(world, x-1, z+1),
|
||||
new WorldAreaId(world, x, z+1),
|
||||
new WorldAreaId(world, x+1, z+1),
|
||||
new WorldAreaId(world, x+2, z+1),
|
||||
new WorldAreaId(world, x+3, z+1),
|
||||
new WorldAreaId(world, x+4, z+1),
|
||||
new WorldAreaId(world, x+5, z+1),
|
||||
new WorldAreaId(world, x - 2, z + 2), new WorldAreaId(world, x - 1, z + 2),
|
||||
new WorldAreaId(world, x, z + 2), new WorldAreaId(world, x + 1, z + 2),
|
||||
new WorldAreaId(world, x + 2, z + 2),
|
||||
|
||||
new WorldAreaId(world, x-5, z+2),
|
||||
new WorldAreaId(world, x-4, z+2),
|
||||
new WorldAreaId(world, x-3, z+2),
|
||||
new WorldAreaId(world, x-2, z+2),
|
||||
new WorldAreaId(world, x-1, z+2),
|
||||
new WorldAreaId(world, x, z+2),
|
||||
new WorldAreaId(world, x+1, z+2),
|
||||
new WorldAreaId(world, x+2, z+2),
|
||||
new WorldAreaId(world, x+3, z+2),
|
||||
new WorldAreaId(world, x+4, z+2),
|
||||
new WorldAreaId(world, x+5, z+2),
|
||||
new WorldAreaId(world, x - 1, z + 3), new WorldAreaId(world, x, z + 3),
|
||||
new WorldAreaId(world, x + 1, z + 3), };
|
||||
return neighborhood;
|
||||
|
||||
new WorldAreaId(world, x-4, z+3),
|
||||
new WorldAreaId(world, x-3, z+3),
|
||||
new WorldAreaId(world, x-2, z+3),
|
||||
new WorldAreaId(world, x-1, z+3),
|
||||
new WorldAreaId(world, x, z+3),
|
||||
new WorldAreaId(world, x+1, z+3),
|
||||
new WorldAreaId(world, x+2, z+3),
|
||||
new WorldAreaId(world, x+3, z+3),
|
||||
new WorldAreaId(world, x+4, z+3),
|
||||
}
|
||||
|
||||
new WorldAreaId(world, x-3, z+4),
|
||||
new WorldAreaId(world, x-2, z+4),
|
||||
new WorldAreaId(world, x-1, z+4),
|
||||
new WorldAreaId(world, x, z+4),
|
||||
new WorldAreaId(world, x+1, z+4),
|
||||
new WorldAreaId(world, x+2, z+4),
|
||||
new WorldAreaId(world, x+3, z+4),
|
||||
//// Size 1 (8-chunks radius)
|
||||
// ·XXX·
|
||||
// XXXXX
|
||||
// XXOXX
|
||||
// XXXXX
|
||||
// ·XXX·
|
||||
if (size == 1) {
|
||||
|
||||
new WorldAreaId(world, x-2, z+5),
|
||||
new WorldAreaId(world, x-1, z+5),
|
||||
new WorldAreaId(world, x, z+5),
|
||||
new WorldAreaId(world, x+1, z+5),
|
||||
new WorldAreaId(world, x+2, z+5),
|
||||
};
|
||||
neighborhood = new WorldAreaId[] { new WorldAreaId(world, x - 1, z - 2), new WorldAreaId(world, x, z - 2),
|
||||
new WorldAreaId(world, x + 1, z - 2),
|
||||
|
||||
new WorldAreaId(world, x - 2, z - 1), new WorldAreaId(world, x - 1, z - 1),
|
||||
new WorldAreaId(world, x, z - 1), new WorldAreaId(world, x + 1, z - 1),
|
||||
new WorldAreaId(world, x + 2, z - 1),
|
||||
|
||||
new WorldAreaId(world, x - 2, z), new WorldAreaId(world, x - 1, z), this,
|
||||
new WorldAreaId(world, x + 1, z), new WorldAreaId(world, x + 2, z),
|
||||
|
||||
new WorldAreaId(world, x - 2, z + 1), new WorldAreaId(world, x - 1, z + 1),
|
||||
new WorldAreaId(world, x, z + 1), new WorldAreaId(world, x + 1, z + 1),
|
||||
new WorldAreaId(world, x + 2, z + 1),
|
||||
|
||||
new WorldAreaId(world, x - 1, z + 2), new WorldAreaId(world, x, z + 2),
|
||||
new WorldAreaId(world, x + 1, z + 2), };
|
||||
return neighborhood;
|
||||
|
||||
}
|
||||
|
||||
//// Size 0 (4x4 chunks)
|
||||
// ·X·
|
||||
// XOX
|
||||
// ·X·
|
||||
neighborhood = new WorldAreaId[] { new WorldAreaId(world, x, z - 1),
|
||||
|
||||
new WorldAreaId(world, x - 1, z), this, new WorldAreaId(world, x + 1, z),
|
||||
|
||||
new WorldAreaId(world, x, z + 1), };
|
||||
return neighborhood;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
if (this == o)
|
||||
return true;
|
||||
if (o == null || getClass() != o.getClass())
|
||||
return false;
|
||||
WorldAreaId that = (WorldAreaId) o;
|
||||
return x == that.x && z == that.z && world.getName().equals(that.world.getName());
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
|
||||
return Objects.hash(world, x, z);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String toString() {
|
||||
|
||||
return world.getName() + "," + x + "," + z;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,343 +0,0 @@
|
||||
package io.josemmo.bukkit.plugin.storage;
|
||||
|
||||
import io.josemmo.bukkit.plugin.YamipaPlugin;
|
||||
import io.josemmo.bukkit.plugin.renderer.FakeImage;
|
||||
import io.josemmo.bukkit.plugin.renderer.FakeMap;
|
||||
import io.josemmo.bukkit.plugin.utils.Logger;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.metadata.IIOMetadataNode;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.DataBufferInt;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
public class CachedMapsFile extends SynchronizedFile {
|
||||
private static final String CACHE_EXT = "cache";
|
||||
private static final byte[] CACHE_SIGNATURE = new byte[] {0x59, 0x4d, 0x50}; // "YMP"
|
||||
private static final int CACHE_VERSION = 1;
|
||||
private static final Logger LOGGER = Logger.getLogger("CachedMapsFile");
|
||||
private final ImageFile imageFile;
|
||||
private final int width;
|
||||
private final int height;
|
||||
private FakeMap[][][] maps;
|
||||
private int delay;
|
||||
|
||||
/**
|
||||
* Create instance from image file
|
||||
* @param imageFile Image file instance
|
||||
* @param width Width in blocks
|
||||
* @param height Height in blocks
|
||||
* @return Cached maps instance
|
||||
*/
|
||||
public static @NotNull CachedMapsFile from(@NotNull ImageFile imageFile, int width, int height) {
|
||||
Path cachePath = YamipaPlugin.getInstance().getStorage().getCachePath();
|
||||
Path path = cachePath.resolve(imageFile.getFilename() + "." + width + "-" + height + "." + CACHE_EXT);
|
||||
return new CachedMapsFile(path, imageFile, width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all cached maps files associated to an image file
|
||||
* @param imageFile Image file instance
|
||||
*/
|
||||
public static void deleteAll(@NotNull ImageFile imageFile) {
|
||||
String relativeFilename = imageFile.getFilename();
|
||||
Path cachePath = YamipaPlugin.getInstance().getStorage().getCachePath();
|
||||
File baseDirectory = cachePath.resolve(relativeFilename).getParent().toFile();
|
||||
String cachePattern = Pattern.quote(Paths.get(relativeFilename).getFileName().toString()) +
|
||||
"\\.[0-9]+-[0-9]+\\." + CACHE_EXT;
|
||||
|
||||
// Find cache files to delete
|
||||
if (!baseDirectory.exists()) {
|
||||
// Cache subdirectory does not exist, no need to delete files
|
||||
return;
|
||||
}
|
||||
File[] files = baseDirectory.listFiles((__, item) -> item.matches(cachePattern));
|
||||
if (files == null) {
|
||||
LOGGER.warning("An error occurred when listing cache files for image \"" + relativeFilename + "\"");
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete disk cache files
|
||||
for (File file : files) {
|
||||
if (!file.delete()) {
|
||||
LOGGER.warning("Failed to delete cache file \"" + file.getAbsolutePath() + "\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
* @param path Path to cached maps file in disk
|
||||
* @param imageFile Image file associated to these maps
|
||||
* @param width Width in blocks
|
||||
* @param height Height blocks
|
||||
*/
|
||||
private CachedMapsFile(@NotNull Path path, @NotNull ImageFile imageFile, int width, int height) {
|
||||
super(path);
|
||||
this.imageFile = imageFile;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
load();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get maps
|
||||
* @return Tri-dimensional array of maps (column, row, step)
|
||||
*/
|
||||
public @NotNull FakeMap[][][] getMaps() {
|
||||
return maps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get delay between steps
|
||||
* @return Delay in 50ms intervals or <code>0</code> if not applicable
|
||||
*/
|
||||
public int getDelay() {
|
||||
return delay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load maps
|
||||
*/
|
||||
private void load() {
|
||||
// Try to load maps from disk
|
||||
if (exists() && getLastModified() > imageFile.getLastModified()) {
|
||||
LOGGER.fine("Found warm cache file \"" + path + "\"");
|
||||
try {
|
||||
loadFromDisk();
|
||||
return;
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOGGER.info("Cache file \"" + path + "\" is outdated and will be overwritten");
|
||||
} catch (Exception e) {
|
||||
LOGGER.warning("Cache file \"" + path + "\" is corrupted", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate maps from image file
|
||||
LOGGER.fine("Missed cache file \"" + path + "\"");
|
||||
try {
|
||||
generateFromImage();
|
||||
tryToWriteToDisk();
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
LOGGER.severe("Failed to render image step(s) from file \"" + path + "\"", e);
|
||||
}
|
||||
|
||||
// Fallback to error matrix
|
||||
maps = FakeMap.getErrorMatrix(width, height);
|
||||
delay = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load data from disk
|
||||
* @throws IllegalArgumentException if cache file is outdated
|
||||
* @throws IOException if cache file is corrupted
|
||||
*/
|
||||
private void loadFromDisk() throws IllegalArgumentException, IOException {
|
||||
try (RandomAccessFile stream = read()) {
|
||||
// Validate file signature
|
||||
for (byte expectedByte : CACHE_SIGNATURE) {
|
||||
if ((byte) stream.read() != expectedByte) {
|
||||
throw new IllegalArgumentException("Invalid file signature");
|
||||
}
|
||||
}
|
||||
|
||||
// Validate version number
|
||||
if ((byte) stream.read() != CACHE_VERSION) {
|
||||
throw new IllegalArgumentException("Incompatible file format version");
|
||||
}
|
||||
|
||||
// Get number of animation steps
|
||||
int numOfSteps = stream.read() | (stream.read() << 8);
|
||||
if (numOfSteps < 1 || numOfSteps > FakeImage.MAX_STEPS) {
|
||||
throw new IOException("Invalid number of animation steps: " + numOfSteps);
|
||||
}
|
||||
|
||||
// Get delay between steps
|
||||
int delay = 0;
|
||||
if (numOfSteps > 1) {
|
||||
delay = stream.read();
|
||||
if (delay < FakeImage.MIN_DELAY || delay > FakeImage.MAX_DELAY) {
|
||||
throw new IOException("Invalid delay between steps: " + delay);
|
||||
}
|
||||
}
|
||||
|
||||
// Read pixels
|
||||
FakeMap[][][] maps = new FakeMap[width][height][numOfSteps];
|
||||
for (int col=0; col<width; ++col) {
|
||||
for (int row=0; row<height; ++row) {
|
||||
for (int step=0; step<numOfSteps; ++step) {
|
||||
byte[] buffer = new byte[FakeMap.DIMENSION*FakeMap.DIMENSION];
|
||||
stream.read(buffer);
|
||||
maps[col][row][step] = new FakeMap(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update instance state
|
||||
this.maps = maps;
|
||||
this.delay = delay;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate data from image
|
||||
* @throws IOException if an I/O error occurred
|
||||
* @throws RuntimeException if failed to render image steps
|
||||
*/
|
||||
private void generateFromImage() throws IOException, RuntimeException {
|
||||
int widthInPixels = width * FakeMap.DIMENSION;
|
||||
int heightInPixels = height * FakeMap.DIMENSION;
|
||||
|
||||
// Render image steps in Minecraft color palette
|
||||
List<byte[]> renderedImages = new ArrayList<>();
|
||||
Map<Integer, Integer> delays = new HashMap<>();
|
||||
try (ImageInputStream inputStream = ImageIO.createImageInputStream(imageFile.read())) {
|
||||
ImageReader reader = ImageIO.getImageReaders(inputStream).next();
|
||||
reader.setInput(inputStream);
|
||||
String format = reader.getFormatName().toLowerCase();
|
||||
|
||||
// Create temporary canvas
|
||||
int originalWidth = reader.getWidth(0);
|
||||
int originalHeight = reader.getHeight(0);
|
||||
BufferedImage tmpImage = new BufferedImage(originalWidth, originalHeight, BufferedImage.TYPE_4BYTE_ABGR);
|
||||
Graphics2D tmpGraphics = tmpImage.createGraphics();
|
||||
tmpGraphics.setBackground(new Color(0, 0, 0, 0));
|
||||
|
||||
// Create temporary scaled canvas (for resizing)
|
||||
BufferedImage tmpScaledImage = new BufferedImage(widthInPixels, heightInPixels, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D tmpScaledGraphics = tmpScaledImage.createGraphics();
|
||||
tmpScaledGraphics.setBackground(new Color(0, 0, 0, 0));
|
||||
|
||||
// Read images from file
|
||||
for (int step=0; step<FakeImage.MAX_STEPS; ++step) {
|
||||
try {
|
||||
// Extract step metadata
|
||||
int imageLeft = 0;
|
||||
int imageTop = 0;
|
||||
boolean disposePrevious = false;
|
||||
if (format.equals("gif")) {
|
||||
IIOMetadata metadata = reader.getImageMetadata(step);
|
||||
String formatName = metadata.getNativeMetadataFormatName();
|
||||
IIOMetadataNode metadataRoot = (IIOMetadataNode) metadata.getAsTree(formatName);
|
||||
for (int i=0; i<metadataRoot.getLength(); ++i) {
|
||||
String nodeName = metadataRoot.item(i).getNodeName();
|
||||
if (nodeName.equalsIgnoreCase("ImageDescriptor")) {
|
||||
IIOMetadataNode descriptorNode = (IIOMetadataNode) metadataRoot.item(i);
|
||||
imageLeft = Integer.parseInt(descriptorNode.getAttribute("imageLeftPosition"));
|
||||
imageTop = Integer.parseInt(descriptorNode.getAttribute("imageTopPosition"));
|
||||
} else if (nodeName.equalsIgnoreCase("GraphicControlExtension")) {
|
||||
IIOMetadataNode controlExtensionNode = (IIOMetadataNode) metadataRoot.item(i);
|
||||
int delay = Integer.parseInt(controlExtensionNode.getAttribute("delayTime"));
|
||||
delays.compute(delay, (__, count) -> (count == null) ? 1 : count + 1);
|
||||
disposePrevious = controlExtensionNode.getAttribute("disposalMethod").startsWith("restore");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear temporary canvases (if needed)
|
||||
if (disposePrevious) {
|
||||
tmpGraphics.clearRect(0, 0, originalWidth, originalHeight);
|
||||
tmpScaledGraphics.clearRect(0, 0, widthInPixels, heightInPixels);
|
||||
}
|
||||
|
||||
// Paint step image over temporary canvas
|
||||
BufferedImage image = reader.read(step);
|
||||
tmpGraphics.drawImage(image, imageLeft, imageTop, null);
|
||||
image.flush();
|
||||
|
||||
// Resize image and get pixels
|
||||
tmpScaledGraphics.drawImage(tmpImage, 0, 0, widthInPixels, heightInPixels, null);
|
||||
int[] rgbaPixels = ((DataBufferInt) tmpScaledImage.getRaster().getDataBuffer()).getData();
|
||||
|
||||
// Convert RGBA pixels to Minecraft color indexes
|
||||
byte[] renderedImage = new byte[widthInPixels * heightInPixels];
|
||||
IntStream.range(0, rgbaPixels.length)
|
||||
.parallel()
|
||||
.forEach(pixelIndex -> renderedImage[pixelIndex] = FakeMap.pixelToIndex(rgbaPixels[pixelIndex]));
|
||||
renderedImages.add(renderedImage);
|
||||
} catch (IndexOutOfBoundsException __) {
|
||||
// No more steps to read
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Free resources
|
||||
reader.dispose();
|
||||
tmpGraphics.dispose();
|
||||
tmpImage.flush();
|
||||
tmpScaledGraphics.dispose();
|
||||
tmpScaledImage.flush();
|
||||
}
|
||||
|
||||
// Get most occurring delay (mode)
|
||||
int delay = 0;
|
||||
if (renderedImages.size() > 1) {
|
||||
delay = Collections.max(delays.entrySet(), Map.Entry.comparingByValue()).getKey();
|
||||
delay = Math.round(delay * 0.2f); // (delay * 10) / 50
|
||||
delay = Math.min(Math.max(delay, FakeImage.MIN_DELAY), FakeImage.MAX_DELAY);
|
||||
}
|
||||
|
||||
// Instantiate fake maps from image steps
|
||||
FakeMap[][][] maps = new FakeMap[width][height][renderedImages.size()];
|
||||
IntStream.range(0, renderedImages.size()).forEach(i -> {
|
||||
for (int col=0; col<width; col++) {
|
||||
for (int row=0; row<height; row++) {
|
||||
maps[col][row][i] = new FakeMap(
|
||||
renderedImages.get(i),
|
||||
widthInPixels,
|
||||
col*FakeMap.DIMENSION,
|
||||
row*FakeMap.DIMENSION
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Update instance state
|
||||
this.maps = maps;
|
||||
this.delay = delay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to write data to disk
|
||||
*/
|
||||
private void tryToWriteToDisk() {
|
||||
mkdirs();
|
||||
try (RandomAccessFile stream = write()) {
|
||||
int numOfSteps = maps[0][0].length;
|
||||
|
||||
// Add file header
|
||||
stream.write(CACHE_SIGNATURE); // "YMP" signature
|
||||
stream.write(CACHE_VERSION); // Format version
|
||||
stream.write(numOfSteps & 0xff); // Number of animation steps (first byte)
|
||||
stream.write((numOfSteps >> 8) & 0xff); // Number of animation steps (second byte)
|
||||
if (numOfSteps > 1) {
|
||||
stream.write(delay);
|
||||
}
|
||||
|
||||
// Add pixels
|
||||
//noinspection ForLoopReplaceableByForEach
|
||||
for (int col=0; col<maps.length; ++col) {
|
||||
for (int row=0; row<maps[0].length; ++row) {
|
||||
for (int step=0; step<numOfSteps; ++step) {
|
||||
stream.write(maps[col][row][step].getPixels());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOGGER.severe("Failed to write to cache file \"" + path + "\"", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,326 +0,0 @@
|
||||
package io.josemmo.bukkit.plugin.storage;
|
||||
|
||||
import com.sun.nio.file.ExtendedWatchEventModifier;
|
||||
import io.josemmo.bukkit.plugin.utils.Logger;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.*;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Service for detecting file events inside a given directory.
|
||||
* <p>
|
||||
* It supports recursive storage (<i>e.g.,</i> nested directories) and watches for file system changes in realtime
|
||||
* when supported by the OS.
|
||||
*/
|
||||
public abstract class FileSystemWatcher {
|
||||
private static final int MAX_DEPTH = 32;
|
||||
private static final int POLLING_INTERVAL = 4000;
|
||||
private static final String PROBE_FILENAME = ".inotify_test";
|
||||
private static final Logger LOGGER = Logger.getLogger("FileSystemWatcher");
|
||||
protected final Path basePath;
|
||||
/** Map of existing directories with the files they contain and their last modification timestamps */
|
||||
private final SortedMap<Path, Map<Path, Long>> fileTree = new TreeMap<>();
|
||||
private @Nullable Thread watcherThread;
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
* @param basePath Base path
|
||||
*/
|
||||
public FileSystemWatcher(@NotNull Path basePath) {
|
||||
this.basePath = basePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start watcher
|
||||
* @throws RuntimeException if failed to start
|
||||
*/
|
||||
protected void start() throws RuntimeException {
|
||||
// Prevent initializing more than once
|
||||
if (watcherThread != null) {
|
||||
throw new RuntimeException("File system watcher is already running");
|
||||
}
|
||||
|
||||
// Perform initial scan
|
||||
scan();
|
||||
|
||||
// Start watching files
|
||||
watcherThread = new WatcherThread();
|
||||
watcherThread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop watcher
|
||||
*/
|
||||
protected void stop() {
|
||||
if (watcherThread != null) {
|
||||
watcherThread.interrupt();
|
||||
watcherThread = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On file created
|
||||
* @param path File path
|
||||
*/
|
||||
protected abstract void onFileCreated(@NotNull Path path);
|
||||
|
||||
/**
|
||||
* On file modified
|
||||
* @param path File path
|
||||
*/
|
||||
protected abstract void onFileModified(@NotNull Path path);
|
||||
|
||||
/**
|
||||
* On file deleted
|
||||
* @param path File path
|
||||
*/
|
||||
protected abstract void onFileDeleted(@NotNull Path path);
|
||||
|
||||
/**
|
||||
* Scan base directory recursively
|
||||
*/
|
||||
private void scan() {
|
||||
synchronized (fileTree) {
|
||||
// Assume all directories and files have been deleted, will discard existing items afterward
|
||||
Set<Path> deletedDirectories = new HashSet<>(fileTree.keySet());
|
||||
Set<Path> deletedFiles = fileTree.values().stream()
|
||||
.map(Map::keySet)
|
||||
.flatMap(Set::stream)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
// Traverse file tree
|
||||
Set<FileVisitOption> options = EnumSet.noneOf(FileVisitOption.class);
|
||||
try {
|
||||
Files.walkFileTree(basePath, options, MAX_DEPTH, new SimpleFileVisitor<Path>() {
|
||||
@Override
|
||||
public FileVisitResult preVisitDirectory(Path path, BasicFileAttributes attrs) {
|
||||
fileTree.putIfAbsent(path, new HashMap<>());
|
||||
deletedDirectories.remove(path);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) {
|
||||
Map<Path, Long> subtree = fileTree.get(path.getParent());
|
||||
Long oldModifiedAt = subtree.get(path);
|
||||
long newModifiedAt = attrs.lastModifiedTime().toMillis();
|
||||
if (oldModifiedAt == null) {
|
||||
subtree.put(path, newModifiedAt);
|
||||
onFileCreated(path);
|
||||
} else if (newModifiedAt > oldModifiedAt) {
|
||||
subtree.put(path, newModifiedAt);
|
||||
onFileModified(path);
|
||||
}
|
||||
deletedFiles.remove(path);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
} catch (IOException e) {
|
||||
LOGGER.severe("Failed to list files in directory", e);
|
||||
}
|
||||
|
||||
// Process deleted files and directories
|
||||
for (Path path : deletedFiles) {
|
||||
fileTree.get(path.getParent()).remove(path);
|
||||
onFileDeleted(path);
|
||||
}
|
||||
for (Path path : deletedDirectories) {
|
||||
fileTree.remove(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class WatcherThread extends Thread {
|
||||
private final boolean IS_WINDOWS = System.getProperty("os.name").toLowerCase(Locale.ROOT).contains("win");
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (isInotifySupported()) {
|
||||
runWithFileSystemEvents();
|
||||
} else {
|
||||
LOGGER.warning("Device does not support inotify, detection of file changes will be slower");
|
||||
runWithPolling();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Is inotify supported
|
||||
* @return Whether inotify is supported
|
||||
*/
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
private boolean isInotifySupported() {
|
||||
try (WatchService watchService = FileSystems.getDefault().newWatchService()) {
|
||||
// Start listening for events
|
||||
basePath.register(
|
||||
watchService,
|
||||
StandardWatchEventKinds.ENTRY_CREATE,
|
||||
StandardWatchEventKinds.ENTRY_DELETE,
|
||||
StandardWatchEventKinds.ENTRY_MODIFY
|
||||
);
|
||||
|
||||
// Create and delete probe directory
|
||||
File testFile = basePath.resolve(PROBE_FILENAME).toFile();
|
||||
testFile.mkdir();
|
||||
testFile.delete();
|
||||
|
||||
// Check that at least one event was emitted
|
||||
WatchKey watchKey = watchService.poll();
|
||||
return (watchKey != null && !watchKey.pollEvents().isEmpty());
|
||||
} catch (IOException __) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run with polling
|
||||
*/
|
||||
@SuppressWarnings({"InfiniteLoopStatement", "BusyWait"})
|
||||
private void runWithPolling() {
|
||||
try {
|
||||
while (true) {
|
||||
Thread.sleep(POLLING_INTERVAL);
|
||||
scan();
|
||||
}
|
||||
} catch (InterruptedException __) {
|
||||
// Silently ignore exception, this is expected when service shuts down
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run with file system events
|
||||
*/
|
||||
private void runWithFileSystemEvents() {
|
||||
try (WatchService watchService = FileSystems.getDefault().newWatchService()) {
|
||||
// Register initial directories
|
||||
synchronized (fileTree) {
|
||||
for (Path path : fileTree.keySet()) {
|
||||
registerDirectory(watchService, path);
|
||||
}
|
||||
}
|
||||
|
||||
// Listen for events
|
||||
WatchKey key;
|
||||
try {
|
||||
while ((key = watchService.take()) != null) {
|
||||
for (WatchEvent<?> event : key.pollEvents()) {
|
||||
WatchEvent.Kind<?> kind = event.kind();
|
||||
Path keyPath = (Path) key.watchable();
|
||||
Path path = keyPath.resolve((Path) event.context());
|
||||
handleWatchEvent(watchService, path, kind);
|
||||
}
|
||||
key.reset();
|
||||
}
|
||||
} catch (ClosedWatchServiceException | InterruptedException __) {
|
||||
// Silently ignore exception, this is expected when service shuts down
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOGGER.severe("Unexpected error at watch service", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle watch event
|
||||
* @param watchService Watch service
|
||||
* @param path File or directory path
|
||||
* @param kind Event kind
|
||||
*/
|
||||
private void handleWatchEvent(@NotNull WatchService watchService, @NotNull Path path, @NotNull WatchEvent.Kind<?> kind) {
|
||||
synchronized (fileTree) {
|
||||
Map<Path, Long> subtree = fileTree.computeIfAbsent(path.getParent(), k -> new HashMap<>());
|
||||
|
||||
// Handle deletion of files and directories
|
||||
if (kind == StandardWatchEventKinds.ENTRY_DELETE) {
|
||||
if (subtree.containsKey(path)) {
|
||||
subtree.remove(path);
|
||||
onFileDeleted(path);
|
||||
} else {
|
||||
unregisterDirectory(path);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle creation of directories
|
||||
if (path.toFile().isDirectory()) {
|
||||
if (kind == StandardWatchEventKinds.ENTRY_CREATE) {
|
||||
registerDirectory(watchService, path);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle creation and modification of files
|
||||
// NOTE: in Windows, some file creation events are reported as modifications
|
||||
Long oldModifiedAt = subtree.get(path);
|
||||
long newModifiedAt = path.toFile().lastModified();
|
||||
if (oldModifiedAt == null) {
|
||||
subtree.put(path, newModifiedAt);
|
||||
onFileCreated(path);
|
||||
} else if (newModifiedAt > oldModifiedAt) {
|
||||
subtree.put(path, newModifiedAt);
|
||||
onFileModified(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register directory
|
||||
* @param watchService Watch service
|
||||
* @param path Directory path
|
||||
*/
|
||||
private void registerDirectory(@NotNull WatchService watchService, @NotNull Path path) {
|
||||
// Windows supports listing to events in the entire file tree,
|
||||
// in that case only allow registering the listener on the base path
|
||||
if (IS_WINDOWS && !path.equals(basePath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Start watching directory for events
|
||||
try {
|
||||
WatchEvent.Kind<?>[] events = new WatchEvent.Kind[]{
|
||||
StandardWatchEventKinds.ENTRY_CREATE,
|
||||
StandardWatchEventKinds.ENTRY_DELETE,
|
||||
StandardWatchEventKinds.ENTRY_MODIFY
|
||||
};
|
||||
WatchEvent.Modifier[] modifiers = IS_WINDOWS ?
|
||||
new WatchEvent.Modifier[]{ExtendedWatchEventModifier.FILE_TREE} :
|
||||
new WatchEvent.Modifier[0];
|
||||
path.register(watchService, events, modifiers);
|
||||
LOGGER.fine("Started watching directory at \"" + path + "\"");
|
||||
} catch (IOException e) {
|
||||
LOGGER.severe("Failed to register directory", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister directory
|
||||
* @param path Directory path
|
||||
*/
|
||||
private void unregisterDirectory(@NotNull Path path) {
|
||||
synchronized (fileTree) {
|
||||
if (!fileTree.containsKey(path)) {
|
||||
// Already unregistered, can skip work
|
||||
return;
|
||||
}
|
||||
boolean foundFirst = false;
|
||||
Iterator<Map.Entry<Path, Map<Path, Long>>> iter = fileTree.entrySet().iterator();
|
||||
while (iter.hasNext()) {
|
||||
Map.Entry<Path, Map<Path, Long>> entry = iter.next();
|
||||
if (entry.getKey().startsWith(path)) {
|
||||
for (Path childPath : entry.getValue().keySet()) {
|
||||
onFileDeleted(childPath);
|
||||
}
|
||||
foundFirst = true;
|
||||
iter.remove();
|
||||
} else if (foundFirst) {
|
||||
// We can break early because set is alphabetically sorted by key
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,77 +1,255 @@
|
||||
package io.josemmo.bukkit.plugin.storage;
|
||||
|
||||
import com.comphenix.protocol.wrappers.Pair;
|
||||
import io.josemmo.bukkit.plugin.YamipaPlugin;
|
||||
import io.josemmo.bukkit.plugin.renderer.FakeImage;
|
||||
import io.josemmo.bukkit.plugin.renderer.FakeItemFrame;
|
||||
import io.josemmo.bukkit.plugin.utils.Logger;
|
||||
import org.jetbrains.annotations.Blocking;
|
||||
import io.josemmo.bukkit.plugin.renderer.FakeMap;
|
||||
import io.josemmo.bukkit.plugin.renderer.FakeMapsContainer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.metadata.IIOMetadataNode;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.DataBufferInt;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.logging.Level;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
public class ImageFile extends SynchronizedFile {
|
||||
private static final Logger LOGGER = Logger.getLogger("ImageFile");
|
||||
public class ImageFile {
|
||||
|
||||
public static final String CACHE_EXT = "cache";
|
||||
public static final byte[] CACHE_SIGNATURE = new byte[] { 0x59, 0x4d, 0x50 }; // "YMP"
|
||||
public static final int CACHE_VERSION = 1;
|
||||
private static final YamipaPlugin plugin = YamipaPlugin.getInstance();
|
||||
private final ConcurrentHashMap<String, Lock> locks = new ConcurrentHashMap<>();
|
||||
private final Map<String, CachedMapsFile> cache = new HashMap<>();
|
||||
private final Map<String, FakeMapsContainer> cache = new HashMap<>();
|
||||
private final Map<String, Set<FakeImage>> subscribers = new HashMap<>();
|
||||
private final String filename;
|
||||
private @Nullable Dimension size;
|
||||
private final String name;
|
||||
private final String path;
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
* @param filename Image filename
|
||||
* @param path Path to image file
|
||||
*
|
||||
* @param name Image file name
|
||||
* @param path Path to image file
|
||||
*/
|
||||
protected ImageFile(@NotNull String filename, @NotNull Path path) {
|
||||
super(path);
|
||||
this.filename = filename;
|
||||
protected ImageFile(@NotNull String name, @NotNull String path) {
|
||||
|
||||
this.name = name;
|
||||
this.path = path;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get image filename
|
||||
* @return Image filename
|
||||
* Get image file name
|
||||
*
|
||||
* @return Image file name
|
||||
*/
|
||||
public @NotNull String getFilename() {
|
||||
return filename;
|
||||
public @NotNull String getName() {
|
||||
|
||||
return name;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get image reader
|
||||
*
|
||||
* @return Image reader instance
|
||||
* @throws IOException if failed to get suitable image reader
|
||||
*/
|
||||
private @NotNull ImageReader getImageReader() throws IOException {
|
||||
|
||||
ImageInputStream inputStream = ImageIO.createImageInputStream(new File(path));
|
||||
ImageReader reader = ImageIO.getImageReaders(inputStream).next();
|
||||
reader.setInput(inputStream);
|
||||
return reader;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Render images using Minecraft palette
|
||||
*
|
||||
* @param width New width in pixels
|
||||
* @param height New height in pixels
|
||||
* @return Pair of bi-dimensional array of Minecraft images (step, pixel index)
|
||||
* and delay between steps
|
||||
* @throws IOException if failed to render images from file
|
||||
*/
|
||||
private @NotNull Pair<byte[][], Integer> renderImages(int width, int height) throws IOException {
|
||||
|
||||
ImageReader reader = getImageReader();
|
||||
String format = reader.getFormatName().toLowerCase();
|
||||
|
||||
// Create temporary canvas
|
||||
int originalWidth = reader.getWidth(0);
|
||||
int originalHeight = reader.getHeight(0);
|
||||
BufferedImage tmpImage = new BufferedImage(originalWidth, originalHeight, BufferedImage.TYPE_4BYTE_ABGR);
|
||||
Graphics2D tmpGraphics = tmpImage.createGraphics();
|
||||
tmpGraphics.setBackground(new Color(0, 0, 0, 0));
|
||||
|
||||
// Create temporary scaled canvas (for resizing)
|
||||
BufferedImage tmpScaledImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D tmpScaledGraphics = tmpScaledImage.createGraphics();
|
||||
tmpScaledGraphics.setBackground(new Color(0, 0, 0, 0));
|
||||
|
||||
// Read images from file
|
||||
List<byte[]> renderedImages = new ArrayList<>();
|
||||
Map<Integer, Integer> delays = new HashMap<>();
|
||||
for (int step = 0; step < FakeImage.MAX_STEPS; ++step) {
|
||||
|
||||
try {
|
||||
|
||||
// Extract step metadata
|
||||
int imageLeft = 0;
|
||||
int imageTop = 0;
|
||||
boolean disposePrevious = false;
|
||||
if (format.equals("gif")) {
|
||||
|
||||
IIOMetadata metadata = reader.getImageMetadata(step);
|
||||
IIOMetadataNode metadataRoot = (IIOMetadataNode) metadata
|
||||
.getAsTree(metadata.getNativeMetadataFormatName());
|
||||
for (int i = 0; i < metadataRoot.getLength(); ++i) {
|
||||
|
||||
String nodeName = metadataRoot.item(i).getNodeName();
|
||||
if (nodeName.equalsIgnoreCase("ImageDescriptor")) {
|
||||
|
||||
IIOMetadataNode descriptorNode = (IIOMetadataNode) metadataRoot.item(i);
|
||||
imageLeft = Integer.parseInt(descriptorNode.getAttribute("imageLeftPosition"));
|
||||
imageTop = Integer.parseInt(descriptorNode.getAttribute("imageTopPosition"));
|
||||
|
||||
} else if (nodeName.equalsIgnoreCase("GraphicControlExtension")) {
|
||||
|
||||
IIOMetadataNode controlExtensionNode = (IIOMetadataNode) metadataRoot.item(i);
|
||||
int delay = Integer.parseInt(controlExtensionNode.getAttribute("delayTime"));
|
||||
delays.compute(delay, (__, count) -> (count == null) ? 1 : count + 1);
|
||||
disposePrevious = controlExtensionNode.getAttribute("disposalMethod").startsWith("restore");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Clear temporary canvases (if needed)
|
||||
if (disposePrevious) {
|
||||
|
||||
tmpGraphics.clearRect(0, 0, originalWidth, originalHeight);
|
||||
tmpScaledGraphics.clearRect(0, 0, width, height);
|
||||
|
||||
}
|
||||
|
||||
// Paint step image over temporary canvas
|
||||
BufferedImage image = reader.read(step);
|
||||
tmpGraphics.drawImage(image, imageLeft, imageTop, null);
|
||||
image.flush();
|
||||
|
||||
// Resize image and get pixels
|
||||
tmpScaledGraphics.drawImage(tmpImage, 0, 0, width, height, null);
|
||||
int[] rgbaPixels = ((DataBufferInt) tmpScaledImage.getRaster().getDataBuffer()).getData();
|
||||
|
||||
// Convert RGBA pixels to Minecraft color indexes
|
||||
byte[] renderedImage = new byte[width * height];
|
||||
IntStream.range(0, rgbaPixels.length).parallel().forEach(pixelIndex -> {
|
||||
|
||||
renderedImage[pixelIndex] = FakeMap.pixelToIndex(rgbaPixels[pixelIndex]);
|
||||
|
||||
});
|
||||
renderedImages.add(renderedImage);
|
||||
|
||||
} catch (IndexOutOfBoundsException __) {
|
||||
|
||||
// No more steps to read
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Get most occurring delay (mode)
|
||||
int delay = 0;
|
||||
if (renderedImages.size() > 1) {
|
||||
|
||||
delay = Collections.max(delays.entrySet(), Map.Entry.comparingByValue()).getKey();
|
||||
delay = Math.round(delay * 0.2f); // (delay * 10) / 50
|
||||
delay = Math.min(Math.max(delay, FakeImage.MIN_DELAY), FakeImage.MAX_DELAY);
|
||||
|
||||
}
|
||||
|
||||
// Free resources
|
||||
reader.dispose();
|
||||
tmpGraphics.dispose();
|
||||
tmpImage.flush();
|
||||
tmpScaledGraphics.dispose();
|
||||
tmpScaledImage.flush();
|
||||
|
||||
return new Pair<>(renderedImages.toArray(new byte[0][0]), delay);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get original size in pixels
|
||||
*
|
||||
* @return Dimension instance or NULL if not a valid image file
|
||||
*/
|
||||
public synchronized @Nullable Dimension getSize() {
|
||||
if (size != null) {
|
||||
return size;
|
||||
}
|
||||
try (ImageInputStream inputStream = ImageIO.createImageInputStream(read())) {
|
||||
ImageReader reader = ImageIO.getImageReaders(inputStream).next();
|
||||
reader.setInput(inputStream);
|
||||
size = new Dimension(reader.getWidth(0), reader.getHeight(0));
|
||||
public @Nullable Dimension getSize() {
|
||||
|
||||
try {
|
||||
|
||||
ImageReader reader = getImageReader();
|
||||
Dimension dimension = new Dimension(reader.getWidth(0), reader.getHeight(0));
|
||||
reader.dispose();
|
||||
return size;
|
||||
} catch (IOException | RuntimeException e) {
|
||||
return dimension;
|
||||
|
||||
} catch (Exception e) {
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get maps and subscribe to them
|
||||
* @param subscriber Fake image instance requesting the maps
|
||||
* @return Cached maps
|
||||
* Get image last modified time
|
||||
*
|
||||
* @return Last modified time in milliseconds, zero in case of error
|
||||
*/
|
||||
@Blocking
|
||||
public @NotNull CachedMapsFile getMapsAndSubscribe(@NotNull FakeImage subscriber) {
|
||||
public long getLastModified() {
|
||||
|
||||
try {
|
||||
|
||||
return Files.getLastModifiedTime(Paths.get(path)).toMillis();
|
||||
|
||||
} catch (Exception __) {
|
||||
|
||||
return 0L;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get maps and subscribe to maps cache
|
||||
*
|
||||
* @param subscriber Fake image instance requesting the maps
|
||||
* @return Fake maps container
|
||||
*/
|
||||
public @NotNull FakeMapsContainer getMapsAndSubscribe(@NotNull FakeImage subscriber) {
|
||||
|
||||
int width = subscriber.getWidth();
|
||||
int height = subscriber.getHeight();
|
||||
String cacheKey = width + "-" + height;
|
||||
@@ -80,38 +258,257 @@ public class ImageFile extends SynchronizedFile {
|
||||
Lock lock = locks.computeIfAbsent(cacheKey, __ -> new ReentrantLock());
|
||||
lock.lock();
|
||||
|
||||
// Get cached maps without locking this instance
|
||||
CachedMapsFile maps = cache.get(cacheKey);
|
||||
if (maps == null) {
|
||||
maps = CachedMapsFile.from(this, width, height);
|
||||
// Execute code
|
||||
FakeMapsContainer container;
|
||||
try {
|
||||
|
||||
container = getMapsAndSubscribe(subscriber, cacheKey, width, height);
|
||||
|
||||
} finally {
|
||||
|
||||
lock.unlock();
|
||||
|
||||
}
|
||||
|
||||
// Update state of this instance
|
||||
synchronized (this) {
|
||||
if (cache.get(cacheKey) != maps) {
|
||||
cache.put(cacheKey, maps);
|
||||
}
|
||||
subscribers.computeIfAbsent(cacheKey, __ -> new HashSet<>()).add(subscriber);
|
||||
lock.unlock();
|
||||
locks.remove(cacheKey);
|
||||
return maps;
|
||||
return container;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get maps and subscribe to maps cache (internal)
|
||||
*
|
||||
* @param subscriber Fake image instance requesting the maps
|
||||
* @param cacheKey Maps cache key
|
||||
* @param width Desired image width in pixels
|
||||
* @param height Desired image height in pixels
|
||||
* @return Fake maps container
|
||||
*/
|
||||
private @NotNull FakeMapsContainer getMapsAndSubscribe(@NotNull FakeImage subscriber, @NotNull String cacheKey,
|
||||
int width, int height)
|
||||
{
|
||||
|
||||
// Update subscribers for given cached maps
|
||||
subscribers.computeIfAbsent(cacheKey, __ -> new HashSet<>()).add(subscriber);
|
||||
|
||||
// Try to get maps from memory cache
|
||||
if (cache.containsKey(cacheKey)) {
|
||||
|
||||
return cache.get(cacheKey);
|
||||
|
||||
}
|
||||
|
||||
// Try to get maps from disk cache
|
||||
String cacheFilename = name + "." + cacheKey + "." + CACHE_EXT;
|
||||
File cacheFile = Paths.get(plugin.getStorage().getCachePath(), cacheFilename).toFile();
|
||||
if (cacheFile.isFile() && cacheFile.lastModified() >= getLastModified()) {
|
||||
|
||||
try {
|
||||
|
||||
FakeMapsContainer container = readMapsFromCacheFile(cacheFile, width, height);
|
||||
cache.put(cacheKey, container);
|
||||
return container;
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
|
||||
plugin.info("Cache file \"" + cacheFile.getAbsolutePath() + "\" is outdated and will be overwritten");
|
||||
|
||||
} catch (Exception e) {
|
||||
|
||||
plugin.log(Level.WARNING, "Cache file \"" + cacheFile.getAbsolutePath() + "\" is corrupted", e);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Generate maps from original image
|
||||
FakeMapsContainer container;
|
||||
try {
|
||||
|
||||
int widthInPixels = width * FakeMap.DIMENSION;
|
||||
int heightInPixels = height * FakeMap.DIMENSION;
|
||||
Pair<byte[][], Integer> res = renderImages(widthInPixels, heightInPixels);
|
||||
byte[][] images = res.getFirst();
|
||||
int delay = res.getSecond();
|
||||
|
||||
// Instantiate fake maps
|
||||
FakeMap[][][] matrix = new FakeMap[width][height][images.length];
|
||||
IntStream.range(0, images.length).forEach(i -> {
|
||||
|
||||
for (int col = 0; col < width; col++) {
|
||||
|
||||
for (int row = 0; row < height; row++) {
|
||||
|
||||
matrix[col][row][i] = new FakeMap(images[i], widthInPixels, col * FakeMap.DIMENSION,
|
||||
row * FakeMap.DIMENSION);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// Persist in disk cache
|
||||
container = new FakeMapsContainer(matrix, delay);
|
||||
try {
|
||||
|
||||
writeMapsToCacheFile(container, cacheFile);
|
||||
|
||||
} catch (IOException e) {
|
||||
|
||||
plugin.log(Level.SEVERE, "Failed to write to cache file \"" + cacheFile.getAbsolutePath() + "\"", e);
|
||||
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
|
||||
container = FakeMap.getErrorMatrix(width, height);
|
||||
plugin.log(Level.SEVERE, "Failed to render image(s) from file \"" + path + "\"", e);
|
||||
|
||||
}
|
||||
|
||||
// Persist in memory cache and return
|
||||
cache.put(cacheKey, container);
|
||||
return container;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Read maps from cache file
|
||||
*
|
||||
* @param file Cache file
|
||||
* @param width Width in blocks
|
||||
* @param height Height in blocks
|
||||
* @return Fake maps container
|
||||
* @throws IllegalArgumentException if not a valid or outdated cache file
|
||||
* @throws IOException if failed to parse cache file
|
||||
*/
|
||||
private @NotNull FakeMapsContainer readMapsFromCacheFile(@NotNull File file, int width, int height)
|
||||
throws Exception
|
||||
{
|
||||
|
||||
try (FileInputStream stream = new FileInputStream(file)) {
|
||||
|
||||
// Validate file signature
|
||||
for (byte expectedByte : CACHE_SIGNATURE) {
|
||||
|
||||
if ((byte) stream.read() != expectedByte) {
|
||||
|
||||
throw new IllegalArgumentException("Invalid file signature");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Validate version number
|
||||
if ((byte) stream.read() != CACHE_VERSION) {
|
||||
|
||||
throw new IllegalArgumentException("Incompatible file format version");
|
||||
|
||||
}
|
||||
|
||||
// Get number of animation steps
|
||||
int numOfSteps = stream.read() | (stream.read() << 8);
|
||||
if (numOfSteps < 1 || numOfSteps > FakeImage.MAX_STEPS) {
|
||||
|
||||
throw new IOException("Invalid number of animation steps: " + numOfSteps);
|
||||
|
||||
}
|
||||
|
||||
// Get delay between steps
|
||||
int delay = 0;
|
||||
if (numOfSteps > 1) {
|
||||
|
||||
delay = stream.read();
|
||||
if (delay < FakeImage.MIN_DELAY || delay > FakeImage.MAX_DELAY) {
|
||||
|
||||
throw new IOException("Invalid delay between steps: " + delay);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Read pixels
|
||||
FakeMap[][][] maps = new FakeMap[width][height][numOfSteps];
|
||||
for (int col = 0; col < width; ++col) {
|
||||
|
||||
for (int row = 0; row < height; ++row) {
|
||||
|
||||
for (int step = 0; step < numOfSteps; ++step) {
|
||||
|
||||
byte[] buffer = new byte[FakeMap.DIMENSION * FakeMap.DIMENSION];
|
||||
stream.read(buffer);
|
||||
maps[col][row][step] = new FakeMap(buffer);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return new FakeMapsContainer(maps, delay);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Write maps to cache file
|
||||
*
|
||||
* @param container Fake maps container
|
||||
* @param file Cache file
|
||||
* @throws IOException if failed to write to cache file
|
||||
*/
|
||||
private void writeMapsToCacheFile(@NotNull FakeMapsContainer container, @NotNull File file) throws IOException {
|
||||
|
||||
try (FileOutputStream stream = new FileOutputStream(file)) {
|
||||
|
||||
FakeMap[][][] maps = container.getFakeMaps();
|
||||
int numOfSteps = maps[0][0].length;
|
||||
|
||||
// Add file header
|
||||
stream.write(CACHE_SIGNATURE); // "YMP" signature
|
||||
stream.write(CACHE_VERSION); // Format version
|
||||
stream.write(numOfSteps & 0xff); // Number of animation steps (first byte)
|
||||
stream.write((numOfSteps >> 8) & 0xff); // Number of animation steps (second byte)
|
||||
if (numOfSteps > 1) {
|
||||
|
||||
stream.write(container.getDelay());
|
||||
|
||||
}
|
||||
|
||||
// Add pixels
|
||||
for (int col = 0; col < maps.length; ++col) {
|
||||
|
||||
for (int row = 0; row < maps[0].length; ++row) {
|
||||
|
||||
for (int step = 0; step < numOfSteps; ++step) {
|
||||
|
||||
stream.write(maps[col][row][step].getPixels());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribe from memory cache
|
||||
* <p>
|
||||
* This method is called by {@link FakeImage} instances when they get invalidated by a world area change.
|
||||
* By notifying their respective source {@link ImageFile}, the latter can clear cached maps from memory when no
|
||||
* more {@link FakeItemFrame}s are using them.
|
||||
* This method is called by FakeImage instances when they get invalidated by a
|
||||
* WorldArea. By notifying their respective source ImageFile, the latter can
|
||||
* clear cached maps from memory when no more FakeItemFrames are using them.
|
||||
*
|
||||
* @param subscriber Fake image instance
|
||||
*/
|
||||
public synchronized void unsubscribe(@NotNull FakeImage subscriber) {
|
||||
|
||||
String cacheKey = subscriber.getWidth() + "-" + subscriber.getHeight();
|
||||
if (!subscribers.containsKey(cacheKey)) {
|
||||
// Not subscribed to this image file
|
||||
if (!subscribers.containsKey(cacheKey))
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove subscriber
|
||||
Set<FakeImage> currentSubscribers = subscribers.get(cacheKey);
|
||||
@@ -119,21 +516,48 @@ public class ImageFile extends SynchronizedFile {
|
||||
|
||||
// Can we clear cached maps?
|
||||
if (currentSubscribers.isEmpty()) {
|
||||
|
||||
subscribers.remove(cacheKey);
|
||||
cache.remove(cacheKey);
|
||||
LOGGER.fine("Invalidated cached maps \"" + cacheKey + "\" in ImageFile#(" + filename + ")");
|
||||
plugin.fine("Invalidated cached maps \"" + cacheKey + "\" in ImageFile#(" + name + ")");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate cache
|
||||
* <p>
|
||||
* Removes all references to cached map instances.
|
||||
* This way, next time an image is requested to be rendered, maps will be regenerated.
|
||||
* Removes all references to pre-generated map sets. This way, next time an
|
||||
* image is requested to be rendered, maps will be regenerated.
|
||||
*/
|
||||
public synchronized void invalidate() {
|
||||
size = null;
|
||||
|
||||
cache.clear();
|
||||
CachedMapsFile.deleteAll(this);
|
||||
|
||||
// Delete disk cache files
|
||||
File[] files = Paths.get(plugin.getStorage().getCachePath()).toFile().listFiles((__, filename) -> {
|
||||
|
||||
return filename.matches(Pattern.quote(name) + "\\.[0-9]+-[0-9]+\\." + CACHE_EXT);
|
||||
|
||||
});
|
||||
if (files == null) {
|
||||
|
||||
plugin.warning("An error occurred when listing cache files for image \"" + name + "\"");
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
for (File file : files) {
|
||||
|
||||
if (!file.delete()) {
|
||||
|
||||
plugin.warning("Failed to delete cache file \"" + file.getAbsolutePath() + "\"");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,210 +1,216 @@
|
||||
package io.josemmo.bukkit.plugin.storage;
|
||||
|
||||
import io.josemmo.bukkit.plugin.utils.Logger;
|
||||
import io.josemmo.bukkit.plugin.utils.Permissions;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import io.josemmo.bukkit.plugin.YamipaPlugin;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.*;
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.PatternSyntaxException;
|
||||
import java.util.Objects;
|
||||
import java.util.SortedMap;
|
||||
import java.util.TreeMap;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* Service for keeping track of image images.
|
||||
* <p>
|
||||
* All files are indexed based on their <b>filename</b>.
|
||||
* Due to recursion, filenames can contain forward slashes (<i>i.e.,</i> "/") and act as relative paths to the base
|
||||
* directory.
|
||||
*/
|
||||
public class ImageStorage extends FileSystemWatcher {
|
||||
private static final Logger LOGGER = Logger.getLogger("ImageStorage");
|
||||
/** Map of registered files indexed by filename */
|
||||
private final SortedMap<String, ImageFile> files = new TreeMap<>();
|
||||
private final Path cachePath;
|
||||
private final String allowedPaths;
|
||||
public class ImageStorage {
|
||||
|
||||
static public final long POLLING_INTERVAL = 20L * 5; // In server ticks
|
||||
static private final YamipaPlugin plugin = YamipaPlugin.getInstance();
|
||||
private final String basePath;
|
||||
private final String cachePath;
|
||||
private final SortedMap<String, ImageFile> cachedImages = new TreeMap<>();
|
||||
private BukkitTask task;
|
||||
private WatchService watchService;
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
* @param basePath Path to directory containing the images
|
||||
* @param cachePath Path to directory containing the cached image maps
|
||||
* @param allowedPaths Allowed paths pattern
|
||||
*
|
||||
* @param basePath Path to directory containing the images
|
||||
* @param cachePath Path to directory containing the cached image maps
|
||||
*/
|
||||
public ImageStorage(@NotNull Path basePath, @NotNull Path cachePath, @NotNull String allowedPaths) {
|
||||
super(basePath);
|
||||
public ImageStorage(@NotNull String basePath, @NotNull String cachePath) {
|
||||
|
||||
this.basePath = basePath;
|
||||
this.cachePath = cachePath;
|
||||
this.allowedPaths = allowedPaths;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get base path
|
||||
*
|
||||
* @return Base path
|
||||
*/
|
||||
public @NotNull Path getBasePath() {
|
||||
public @NotNull String getBasePath() {
|
||||
|
||||
return basePath;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache path
|
||||
*
|
||||
* @return Cache path
|
||||
*/
|
||||
public @NotNull Path getCachePath() {
|
||||
public @NotNull String getCachePath() {
|
||||
|
||||
return cachePath;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Start service
|
||||
* @throws RuntimeException if failed to start watch service
|
||||
* Start instance
|
||||
*
|
||||
* @throws SecurityException if failed to access filesystem
|
||||
* @throws Exception if failed to start watch service
|
||||
*/
|
||||
@Override
|
||||
public void start() throws RuntimeException {
|
||||
// Create base directories if necessary
|
||||
if (basePath.toFile().mkdirs()) {
|
||||
LOGGER.info("Created images directory as it did not exist");
|
||||
}
|
||||
if (cachePath.toFile().mkdirs()) {
|
||||
LOGGER.info("Created cache directory as it did not exist");
|
||||
public void start() throws Exception {
|
||||
|
||||
// Create directories if necessary
|
||||
File directory = new File(basePath);
|
||||
if (directory.mkdirs()) {
|
||||
|
||||
plugin.info("Created images directory as it did not exist");
|
||||
|
||||
}
|
||||
|
||||
// Start file system watcher
|
||||
super.start();
|
||||
LOGGER.fine("Found " + files.size() + " file(s) in images directory");
|
||||
if (new File(cachePath).mkdirs()) {
|
||||
|
||||
plugin.info("Created cache directory as it did not exist");
|
||||
|
||||
}
|
||||
|
||||
// Do initial directory listing
|
||||
for (File file : Objects.requireNonNull(directory.listFiles())) {
|
||||
|
||||
if (file.isDirectory())
|
||||
continue;
|
||||
String filename = file.getName();
|
||||
cachedImages.put(filename, new ImageFile(filename, file.getAbsolutePath()));
|
||||
|
||||
}
|
||||
|
||||
plugin.fine("Found " + cachedImages.size() + " file(s) in images directory");
|
||||
|
||||
// Prepare watch service
|
||||
watchService = FileSystems.getDefault().newWatchService();
|
||||
Path directoryPath = directory.toPath();
|
||||
directoryPath.register(watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE,
|
||||
StandardWatchEventKinds.ENTRY_MODIFY);
|
||||
|
||||
// Start watching for changes
|
||||
task = Bukkit.getScheduler().runTaskTimerAsynchronously(plugin, () -> {
|
||||
|
||||
WatchKey watchKey = watchService.poll();
|
||||
if (watchKey == null)
|
||||
return;
|
||||
|
||||
watchKey.pollEvents().forEach(event -> {
|
||||
|
||||
WatchEvent.Kind<?> kind = event.kind();
|
||||
File file = directoryPath.resolve((Path) event.context()).toFile();
|
||||
if (file.isDirectory())
|
||||
return;
|
||||
|
||||
String filename = file.getName();
|
||||
synchronized (this) {
|
||||
|
||||
if (kind == StandardWatchEventKinds.ENTRY_DELETE) {
|
||||
|
||||
ImageFile imageFile = cachedImages.get(filename);
|
||||
if (imageFile != null) {
|
||||
|
||||
imageFile.invalidate();
|
||||
cachedImages.remove(filename);
|
||||
|
||||
}
|
||||
|
||||
plugin.fine("Detected file deletion at " + filename);
|
||||
|
||||
} else if (cachedImages.containsKey(filename)) {
|
||||
|
||||
cachedImages.get(filename).invalidate();
|
||||
plugin.fine("Detected file update at " + filename);
|
||||
|
||||
} else {
|
||||
|
||||
cachedImages.put(filename, new ImageFile(filename, file.getAbsolutePath()));
|
||||
plugin.fine("Detected file creation at " + filename);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
watchKey.reset();
|
||||
|
||||
}, POLLING_INTERVAL, POLLING_INTERVAL);
|
||||
plugin.fine("Started watching for file changes in images directory");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop service
|
||||
* Stop instance
|
||||
*/
|
||||
@Override
|
||||
public void stop() {
|
||||
super.stop();
|
||||
|
||||
// Cancel async task
|
||||
if (task != null) {
|
||||
|
||||
task.cancel();
|
||||
|
||||
}
|
||||
|
||||
// Close watch service
|
||||
if (watchService != null) {
|
||||
|
||||
try {
|
||||
|
||||
watchService.close();
|
||||
|
||||
} catch (IOException e) {
|
||||
|
||||
plugin.log(Level.WARNING, "Failed to close watch service", e);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get number of images
|
||||
*
|
||||
* @return Number of images
|
||||
*/
|
||||
public synchronized int size() {
|
||||
return files.size();
|
||||
|
||||
return cachedImages.size();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get image filenames
|
||||
* @param sender Sender instance to filter only allowed images
|
||||
* @return Allowed images
|
||||
* Get all image filenames
|
||||
*
|
||||
* @return Sorted array of image filenames
|
||||
*/
|
||||
public synchronized @NotNull List<String> getFilenames(@NotNull CommandSender sender) {
|
||||
List<String> response = new ArrayList<>();
|
||||
for (String filename : files.keySet()) {
|
||||
if (isPathAllowed(filename, sender)) {
|
||||
response.add(filename);
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
public synchronized @NotNull String[] getAllFilenames() {
|
||||
|
||||
/**
|
||||
* Is path allowed
|
||||
* @param path Path instance
|
||||
* @param sender Sender instance
|
||||
* @return Whether sender is allowed to access path
|
||||
*/
|
||||
public boolean isPathAllowed(@NotNull Path path, @NotNull CommandSender sender) {
|
||||
return isPathAllowed(pathToFilename(path), sender);
|
||||
}
|
||||
return cachedImages.keySet().toArray(new String[0]);
|
||||
|
||||
/**
|
||||
* Is path allowed
|
||||
* @param path Path relative to {@link ImageStorage#basePath}
|
||||
* @param sender Sender instance
|
||||
* @return Whether sender is allowed to access path
|
||||
*/
|
||||
public boolean isPathAllowed(@NotNull String path, @NotNull CommandSender sender) {
|
||||
// Find allowed paths pattern
|
||||
String rawPattern = null;
|
||||
if (sender instanceof Player) {
|
||||
rawPattern = Permissions.getVariable("yamipa-allowed-paths", (Player) sender);
|
||||
}
|
||||
if (rawPattern == null) {
|
||||
rawPattern = allowedPaths;
|
||||
}
|
||||
if (rawPattern.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Replace special tokens in pattern
|
||||
if (sender instanceof Player) {
|
||||
Player player = (Player) sender;
|
||||
rawPattern = rawPattern.replaceAll("#player#", Matcher.quoteReplacement(Pattern.quote(player.getName())));
|
||||
rawPattern = rawPattern.replaceAll("#uuid#", player.getUniqueId().toString());
|
||||
} else {
|
||||
rawPattern = rawPattern.replaceAll("#player#", ".+");
|
||||
rawPattern = rawPattern.replaceAll("#uuid#", ".+");
|
||||
}
|
||||
|
||||
// Perform partial match against pattern
|
||||
try {
|
||||
return Pattern.compile(rawPattern).matcher(path).find();
|
||||
} catch (PatternSyntaxException __) {
|
||||
LOGGER.warning("Invalid allowed paths pattern: " + rawPattern);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get image by filename
|
||||
* @param filename Filename
|
||||
* @return Image instance or NULL if not found
|
||||
*
|
||||
* @param filename Filename
|
||||
* @return Image instance or NULL if not found
|
||||
*/
|
||||
public synchronized @Nullable ImageFile get(@NotNull String filename) {
|
||||
return files.get(filename);
|
||||
|
||||
return cachedImages.get(filename);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert path to filename
|
||||
* @param path File path
|
||||
* @return Relative path used for indexing
|
||||
*/
|
||||
private @NotNull String pathToFilename(@NotNull Path path) {
|
||||
return basePath.relativize(path).toString().replaceAll("\\\\", "/");
|
||||
}
|
||||
|
||||
/**
|
||||
* On file created
|
||||
* @param path File path
|
||||
*/
|
||||
protected synchronized void onFileCreated(@NotNull Path path) {
|
||||
String filename = pathToFilename(path);
|
||||
ImageFile imageFile = new ImageFile(filename, path);
|
||||
if (files.putIfAbsent(filename, imageFile) == null) {
|
||||
LOGGER.fine("Registered file \"" + filename + "\"");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On file modified
|
||||
* @param path File path
|
||||
*/
|
||||
protected synchronized void onFileModified(@NotNull Path path) {
|
||||
String filename = pathToFilename(path);
|
||||
ImageFile imageFile = files.get(filename);
|
||||
if (imageFile != null) {
|
||||
imageFile.invalidate();
|
||||
LOGGER.fine("Invalidated file \"" + filename + "\"");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On file deleted
|
||||
* @param path File path
|
||||
*/
|
||||
protected synchronized void onFileDeleted(@NotNull Path path) {
|
||||
String filename = pathToFilename(path);
|
||||
ImageFile imageFile = files.remove(filename);
|
||||
if (imageFile != null) {
|
||||
imageFile.invalidate();
|
||||
LOGGER.fine("Unregistered file \"" + filename + "\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
package io.josemmo.bukkit.plugin.storage;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* An instance of a file stored in a shared filesystem (<i>e.g.,</i> a NFS drive).
|
||||
* All operations are performed in a blocking way.
|
||||
* That is, the instance will acquire a read or write lock before reading or modifying the file.
|
||||
* If the file is already locked by another resource, it will wait until the lock is released
|
||||
* <b>while blocking the thread</b>.
|
||||
*/
|
||||
public class SynchronizedFile {
|
||||
protected final Path path;
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
* @param path Path to file
|
||||
*/
|
||||
public SynchronizedFile(@NotNull Path path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check file exists
|
||||
* @return Whether file exists or not
|
||||
*/
|
||||
public boolean exists() {
|
||||
return Files.exists(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get image last modified time
|
||||
* @return Last modified time in milliseconds or <code>0</code> in case of error
|
||||
*/
|
||||
public long getLastModified() {
|
||||
try {
|
||||
return Files.getLastModifiedTime(path).toMillis();
|
||||
} catch (Exception __) {
|
||||
return 0L;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make directories
|
||||
* <p>
|
||||
* Creates the directory containing this file and its parents if they don't exist.
|
||||
*/
|
||||
public void mkdirs() {
|
||||
try {
|
||||
Files.createDirectories(path.getParent());
|
||||
} catch (IOException __) {
|
||||
// Silently ignore exception
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file reader
|
||||
* @return Readable stream of the file
|
||||
* @throws IOException if failed to acquire lock
|
||||
*/
|
||||
public RandomAccessFile read() throws IOException {
|
||||
return new SynchronizedFileStream(path, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file writer
|
||||
* @return Writable stream of the file
|
||||
* @throws IOException if failed to acquire lock
|
||||
*/
|
||||
public RandomAccessFile write() throws IOException {
|
||||
return new SynchronizedFileStream(path, false);
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package io.josemmo.bukkit.plugin.storage;
|
||||
|
||||
import org.jetbrains.annotations.Blocking;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.FileLock;
|
||||
import java.nio.channels.OverlappingFileLockException;
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* Blocking file stream implementing {@link RandomAccessFile} for reading and writing data.
|
||||
* It will lock the file for reading/writing when opened, and release the lock when closed.
|
||||
*/
|
||||
public class SynchronizedFileStream extends RandomAccessFile {
|
||||
/**
|
||||
* Class constructor
|
||||
* @param path Path to file
|
||||
* @param readOnly Whether to open stream in read-only mode
|
||||
* @throws IOException if failed to acquire lock
|
||||
*/
|
||||
public SynchronizedFileStream(@NotNull Path path, boolean readOnly) throws IOException {
|
||||
super(path.toFile(), readOnly ? "r" : "rw");
|
||||
waitForLock(readOnly);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for lock to be released
|
||||
* @param readOnly Whether to acquire read-only (shared) lock
|
||||
*/
|
||||
@Blocking
|
||||
private void waitForLock(boolean readOnly) throws IOException {
|
||||
FileChannel channel = getChannel();
|
||||
FileLock lock = null;
|
||||
while (lock == null) {
|
||||
try {
|
||||
lock = channel.lock(0L, Long.MAX_VALUE, readOnly);
|
||||
} catch (OverlappingFileLockException e) {
|
||||
try {
|
||||
Thread.sleep(20);
|
||||
} catch (InterruptedException __) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,96 +7,132 @@ import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class ActionBar {
|
||||
private static final Logger LOGGER = Logger.getLogger("ActionBar");
|
||||
|
||||
private static final YamipaPlugin plugin = YamipaPlugin.getInstance();
|
||||
private final Player player;
|
||||
private String message;
|
||||
private BukkitTask task = null;
|
||||
|
||||
private ActionBar(@NotNull Player player, @NotNull String message) {
|
||||
|
||||
this.player = player;
|
||||
this.message = message;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Send action bar message
|
||||
*
|
||||
* @param player Player who will receive the message
|
||||
* @param message Message to send
|
||||
*/
|
||||
public static void send(@NotNull Player player, @NotNull String message) {
|
||||
|
||||
ActionBar instance = new ActionBar(player, message);
|
||||
instance.sendOnce();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep sending action bar message
|
||||
* @param player Player who will receive the message
|
||||
* @param message Message to send
|
||||
* @return New instance
|
||||
*
|
||||
* @param player Player who will receive the message
|
||||
* @param message Message to send
|
||||
* @return New instance
|
||||
*/
|
||||
public static @NotNull ActionBar repeat(@NotNull Player player, @NotNull String message) {
|
||||
|
||||
ActionBar instance = new ActionBar(player, message);
|
||||
instance.start();
|
||||
return instance;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Set message
|
||||
* @param message Message
|
||||
* @return This instance
|
||||
*
|
||||
* @param message Message
|
||||
* @return This instance
|
||||
*/
|
||||
public ActionBar setMessage(@NotNull String message) {
|
||||
|
||||
this.message = message;
|
||||
return sendOnce();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear message
|
||||
*
|
||||
* @return This instance
|
||||
*/
|
||||
public ActionBar clear() {
|
||||
|
||||
message = "";
|
||||
stop();
|
||||
return sendOnce();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Send message once
|
||||
*
|
||||
* @return This instance
|
||||
*/
|
||||
public ActionBar sendOnce() {
|
||||
|
||||
ActionBarPacket actionBarPacket = new ActionBarPacket();
|
||||
actionBarPacket.setText(message);
|
||||
try {
|
||||
|
||||
ProtocolLibrary.getProtocolManager().sendServerPacket(player, actionBarPacket);
|
||||
|
||||
} catch (Exception e) {
|
||||
LOGGER.severe("Failed to send ActionBar to " + player.getName(), e);
|
||||
|
||||
plugin.log(Level.SEVERE, "Failed to send ActionBar to " + player.getName(), e);
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Start sending message indefinitely
|
||||
*
|
||||
* @return This instance
|
||||
*/
|
||||
public ActionBar start() {
|
||||
|
||||
if (task == null) {
|
||||
|
||||
task = Bukkit.getScheduler().runTaskTimer(plugin, this::sendOnce, 0L, 40L);
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop sending message indefinitely
|
||||
*
|
||||
* @return This instance
|
||||
*/
|
||||
public ActionBar stop() {
|
||||
|
||||
if (task != null) {
|
||||
|
||||
task.cancel();
|
||||
task = null;
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,78 +1,90 @@
|
||||
package io.josemmo.bukkit.plugin.utils;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* A class for parsing simple CSV files.
|
||||
* It's used for loading and writing Yamipa's configuration file ("image.dat" by default) and does not
|
||||
* support escaping of special characters, as this is not currently needed.
|
||||
*/
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class CsvConfiguration {
|
||||
|
||||
public static final Charset CHARSET = StandardCharsets.UTF_8;
|
||||
public static final String COLUMN_DELIMITER = "\t";
|
||||
public static final String COLUMN_DELIMITER = "/";
|
||||
private final List<String[]> data = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Get rows
|
||||
*
|
||||
* @return List of rows
|
||||
*/
|
||||
public @NotNull List<String[]> getRows() {
|
||||
|
||||
return data;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Add row
|
||||
*
|
||||
* @param row Row
|
||||
*/
|
||||
public void addRow(@NotNull String[] row) {
|
||||
|
||||
data.add(row);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Load from file
|
||||
*
|
||||
* @param path File path
|
||||
* @throws IOException if failed to read file
|
||||
*/
|
||||
public void load(@NotNull Path path) throws IOException {
|
||||
try (Stream<String> stream = Files.lines(path, CHARSET)) {
|
||||
public void load(@NotNull String path) throws IOException {
|
||||
|
||||
try (Stream<String> stream = Files.lines(Paths.get(path), CHARSET)) {
|
||||
|
||||
stream.forEach(line -> {
|
||||
line = line.trim();
|
||||
|
||||
// Ignore empty lines
|
||||
if (line.isEmpty()) {
|
||||
return;
|
||||
line = StringUtils.trim(line);
|
||||
if (!StringUtils.isEmpty(line)) {
|
||||
|
||||
addRow(line.split(COLUMN_DELIMITER));
|
||||
|
||||
}
|
||||
|
||||
// Migrate legacy format
|
||||
if (!line.contains(COLUMN_DELIMITER)) {
|
||||
line = line.replaceAll("/", COLUMN_DELIMITER);
|
||||
}
|
||||
|
||||
// Parse line
|
||||
addRow(line.split(COLUMN_DELIMITER));
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Save to file
|
||||
*
|
||||
* @param path File path
|
||||
* @throws IOException if failed to write file
|
||||
*/
|
||||
public void save(@NotNull Path path) throws IOException {
|
||||
try (OutputStreamWriter writer = new OutputStreamWriter(Files.newOutputStream(path), CHARSET)) {
|
||||
public void save(@NotNull String path) throws IOException {
|
||||
|
||||
try (OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(path), CHARSET)) {
|
||||
|
||||
for (String[] row : getRows()) {
|
||||
|
||||
writer.write(String.join(COLUMN_DELIMITER, row) + "\n");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,24 +4,41 @@ import org.bukkit.block.BlockFace;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class DirectionUtils {
|
||||
|
||||
/**
|
||||
* Get cardinal direction
|
||||
* @param yaw Yaw angle
|
||||
* @return Cardinal direction
|
||||
*
|
||||
* @param yaw Yaw angle
|
||||
* @return Cardinal direction
|
||||
*/
|
||||
public static @NotNull BlockFace getCardinalDirection(float yaw) {
|
||||
|
||||
if (yaw < 0) {
|
||||
|
||||
yaw += 360;
|
||||
|
||||
}
|
||||
|
||||
if (yaw >= 315 || yaw < 45) {
|
||||
|
||||
return BlockFace.SOUTH;
|
||||
|
||||
} else if (yaw < 135) {
|
||||
|
||||
return BlockFace.WEST;
|
||||
|
||||
} else if (yaw < 225) {
|
||||
|
||||
return BlockFace.NORTH;
|
||||
|
||||
} else if (yaw < 315) {
|
||||
|
||||
return BlockFace.EAST;
|
||||
|
||||
}
|
||||
|
||||
return BlockFace.NORTH;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,112 +14,148 @@ import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public abstract class InteractWithEntityListener implements PacketListener {
|
||||
|
||||
public static final int MAX_BLOCK_DISTANCE = 5; // Server should only accept entities within a 4-block radius
|
||||
private static final Logger LOGGER = Logger.getLogger("InteractWithEntityListener");
|
||||
|
||||
/**
|
||||
* On player attack listener
|
||||
* @param player Initiating player
|
||||
* @param block Target block
|
||||
* @param face Target block face
|
||||
* @return Whether to allow original event (<code>true</code>) or not (<code>false</code>)
|
||||
*
|
||||
* @param player Initiating player
|
||||
* @param block Target block
|
||||
* @param face Target block face
|
||||
* @return Whether to allow original event (<code>true</code>) or not
|
||||
* (<code>false</code>)
|
||||
*/
|
||||
public abstract boolean onAttack(@NotNull Player player, @NotNull Block block, @NotNull BlockFace face);
|
||||
|
||||
/**
|
||||
* On player interact listener
|
||||
* @param player Initiating player
|
||||
* @param block Target block
|
||||
* @param face Target block face
|
||||
* @return Whether to allow original event (<code>true</code>) or not (<code>false</code>)
|
||||
*
|
||||
* @param player Initiating player
|
||||
* @param block Target block
|
||||
* @param face Target block face
|
||||
* @return Whether to allow original event (<code>true</code>) or not
|
||||
* (<code>false</code>)
|
||||
*/
|
||||
public abstract boolean onInteract(@NotNull Player player, @NotNull Block block, @NotNull BlockFace face);
|
||||
|
||||
/**
|
||||
* Get listener priority
|
||||
*
|
||||
* @return Listener priority
|
||||
*/
|
||||
public @NotNull ListenerPriority getPriority() {
|
||||
|
||||
return ListenerPriority.LOWEST;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Register listener
|
||||
*/
|
||||
public void register() {
|
||||
|
||||
ProtocolLibrary.getProtocolManager().addPacketListener(this);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister listener
|
||||
*/
|
||||
public void unregister() {
|
||||
|
||||
ProtocolLibrary.getProtocolManager().removePacketListener(this);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onPacketReceiving(@NotNull PacketEvent event) {
|
||||
|
||||
Player player = event.getPlayer();
|
||||
|
||||
// Discard out-of-range packets
|
||||
List<Block> lastTwoTargetBlocks = player.getLastTwoTargetBlocks(null, MAX_BLOCK_DISTANCE);
|
||||
if (lastTwoTargetBlocks.size() != 2) return;
|
||||
if (lastTwoTargetBlocks.size() != 2)
|
||||
return;
|
||||
Block targetBlock = lastTwoTargetBlocks.get(1);
|
||||
if (!targetBlock.getType().isSolid()) return;
|
||||
if (!targetBlock.getType().isSolid())
|
||||
return;
|
||||
|
||||
// Get target block face
|
||||
Block adjacentBlock = lastTwoTargetBlocks.get(0);
|
||||
BlockFace targetBlockFace = targetBlock.getFace(adjacentBlock);
|
||||
if (targetBlockFace == null) return;
|
||||
if (targetBlockFace == null)
|
||||
return;
|
||||
|
||||
// Get action
|
||||
EnumWrappers.EntityUseAction action;
|
||||
if (Internals.MINECRAFT_VERSION < 17) {
|
||||
|
||||
action = event.getPacket().getEntityUseActions().read(0);
|
||||
|
||||
} else {
|
||||
|
||||
action = event.getPacket().getEnumEntityUseActions().read(0).getAction();
|
||||
|
||||
}
|
||||
|
||||
// Notify handler synchronously
|
||||
boolean allowEvent = true;
|
||||
try {
|
||||
|
||||
if (action == EnumWrappers.EntityUseAction.ATTACK) {
|
||||
|
||||
allowEvent = onAttack(player, targetBlock, targetBlockFace);
|
||||
|
||||
} else if (action == EnumWrappers.EntityUseAction.INTERACT_AT) {
|
||||
|
||||
allowEvent = onInteract(player, targetBlock, targetBlockFace);
|
||||
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
LOGGER.severe("Failed to notify entity listener handler", e);
|
||||
|
||||
YamipaPlugin.getInstance().log(Level.SEVERE, "Failed to notify entity listener handler", e);
|
||||
|
||||
}
|
||||
|
||||
// Cancel event (if needed)
|
||||
if (!allowEvent) {
|
||||
|
||||
event.setCancelled(true);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onPacketSending(PacketEvent event) {
|
||||
|
||||
// Intentionally left blank
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ListeningWhitelist getReceivingWhitelist() {
|
||||
return ListeningWhitelist.newBuilder()
|
||||
.priority(getPriority())
|
||||
.types(PacketType.Play.Client.USE_ENTITY)
|
||||
.build();
|
||||
|
||||
return ListeningWhitelist.newBuilder().priority(getPriority()).types(PacketType.Play.Client.USE_ENTITY).build();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ListeningWhitelist getSendingWhitelist() {
|
||||
|
||||
return ListeningWhitelist.EMPTY_WHITELIST;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Plugin getPlugin() {
|
||||
|
||||
return YamipaPlugin.getInstance();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,21 +6,23 @@ import org.bukkit.Server;
|
||||
import org.bukkit.command.CommandMap;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class Internals {
|
||||
|
||||
public static final float MINECRAFT_VERSION;
|
||||
private static final CommandDispatcher<?> DISPATCHER;
|
||||
private static final CommandMap COMMAND_MAP;
|
||||
private static @Nullable Method GET_BUKKIT_SENDER_METHOD = null;
|
||||
private static final Method GET_BUKKIT_SENDER_METHOD;
|
||||
|
||||
static {
|
||||
|
||||
try {
|
||||
|
||||
// Get Minecraft version
|
||||
String version = Bukkit.getVersion();
|
||||
version = version.substring(version.lastIndexOf("(MC: 1.")+7, version.length()-1);
|
||||
version = version.substring(version.lastIndexOf("(MC: 1.") + 7, version.length() - 1);
|
||||
MINECRAFT_VERSION = Float.parseFloat(version);
|
||||
|
||||
// Get "org.bukkit.craftbukkit.CraftServer" references
|
||||
@@ -31,64 +33,92 @@ public class Internals {
|
||||
Object nmsInstance = obcClass.getDeclaredMethod("getServer").invoke(obcInstance);
|
||||
Class<?> nmsClass = nmsInstance.getClass().getSuperclass();
|
||||
|
||||
if (MINECRAFT_VERSION >= 20.6) {
|
||||
// Get "net.minecraft.commands.Commands" references
|
||||
Object nmsCommandsInstance = nmsClass.getDeclaredMethod("getCommands").invoke(nmsInstance);
|
||||
Class<?> nmsCommandsClass = nmsCommandsInstance.getClass();
|
||||
// Get "net.minecraft.server.CommandDispatcher" references
|
||||
Object nmsDispatcherInstance = nmsClass.getDeclaredField("vanillaCommandDispatcher").get(nmsInstance);
|
||||
Class<?> nmsDispatcherClass = nmsDispatcherInstance.getClass();
|
||||
|
||||
// Get "com.mojang.brigadier.CommandDispatcher" instance
|
||||
Field nmsDispatcherField = nmsCommandsClass.getDeclaredField("dispatcher");
|
||||
nmsDispatcherField.setAccessible(true);
|
||||
DISPATCHER = (CommandDispatcher<?>) nmsDispatcherField.get(nmsCommandsInstance);
|
||||
} else {
|
||||
// Get "net.minecraft.server.CommandDispatcher" references
|
||||
Object nmsDispatcherInstance = nmsClass.getDeclaredField("vanillaCommandDispatcher").get(nmsInstance);
|
||||
Class<?> nmsDispatcherClass = nmsDispatcherInstance.getClass();
|
||||
|
||||
// Get "com.mojang.brigadier.CommandDispatcher" instance
|
||||
Method getDispatcherMethod = nmsDispatcherClass.getDeclaredMethod("a");
|
||||
getDispatcherMethod.setAccessible(true);
|
||||
DISPATCHER = (CommandDispatcher<?>) getDispatcherMethod.invoke(nmsDispatcherInstance);
|
||||
}
|
||||
// Get Brigadier dispatcher instance
|
||||
Method getDispatcherMethod = nmsDispatcherClass.getDeclaredMethod("a");
|
||||
getDispatcherMethod.setAccessible(true);
|
||||
DISPATCHER = (CommandDispatcher<?>) getDispatcherMethod.invoke(nmsDispatcherInstance);
|
||||
|
||||
// Get command map instance
|
||||
Field commandMapField = obcClass.getDeclaredField("commandMap");
|
||||
commandMapField.setAccessible(true);
|
||||
COMMAND_MAP = (CommandMap) commandMapField.get(obcInstance);
|
||||
|
||||
// Get CommandListenerWrapper.getBukkitSender() method
|
||||
Class<?> clwClass = Class.forName(nmsDispatcherClass.getPackage().getName() + ".CommandListenerWrapper");
|
||||
GET_BUKKIT_SENDER_METHOD = clwClass.getDeclaredMethod("getBukkitSender");
|
||||
GET_BUKKIT_SENDER_METHOD.setAccessible(true);
|
||||
|
||||
} catch (Exception e) {
|
||||
|
||||
throw new RuntimeException("Failed to get internal classes due to incompatible Minecraft server", e);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Brigadier command dispatcher instance
|
||||
*
|
||||
* @return Command dispatcher instance
|
||||
*/
|
||||
public static @NotNull CommandDispatcher<?> getDispatcher() {
|
||||
|
||||
return DISPATCHER;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Bukkit command map instance
|
||||
*
|
||||
* @return Command map instance
|
||||
*/
|
||||
public static @NotNull CommandMap getCommandMap() {
|
||||
|
||||
return COMMAND_MAP;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronize Bukkit commands with Brigadier
|
||||
*/
|
||||
public static void syncCommands() {
|
||||
|
||||
try {
|
||||
|
||||
Method syncCommandsMethod = Bukkit.getServer().getClass().getDeclaredMethod("syncCommands");
|
||||
syncCommandsMethod.setAccessible(true);
|
||||
syncCommandsMethod.invoke(Bukkit.getServer());
|
||||
|
||||
} catch (Exception e) {
|
||||
|
||||
throw new RuntimeException("Failed to synchronize Bukkit commands", e);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Bukkit sender from Brigadier context source
|
||||
* @param source Brigadier command context source
|
||||
* @return Command sender instance
|
||||
*
|
||||
* @param source Brigadier command context source
|
||||
* @return Command sender instance
|
||||
*/
|
||||
public static @NotNull CommandSender getBukkitSender(@NotNull Object source) {
|
||||
|
||||
try {
|
||||
if (GET_BUKKIT_SENDER_METHOD == null) {
|
||||
GET_BUKKIT_SENDER_METHOD = source.getClass().getDeclaredMethod("getBukkitSender");
|
||||
}
|
||||
|
||||
return (CommandSender) GET_BUKKIT_SENDER_METHOD.invoke(source);
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to extract Bukkit sender from source", e);
|
||||
|
||||
throw new RuntimeException("Failed to extract bukkit sender from source", e);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
package io.josemmo.bukkit.plugin.utils;
|
||||
|
||||
import io.josemmo.bukkit.plugin.YamipaPlugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* Custom logging class used by components from the plugin.
|
||||
* It is <i>instance agnostic</i>, that is, the same logger instance will work regardless of whether the plugin
|
||||
* instance has changed (for example, because of the plugin being restarted by a plugin manager).
|
||||
*/
|
||||
public class Logger {
|
||||
private final @Nullable String name;
|
||||
|
||||
/**
|
||||
* Get logger instance
|
||||
* @param name Logger name
|
||||
* @return Logger instance
|
||||
*/
|
||||
public static @NotNull Logger getLogger(@NotNull String name) {
|
||||
return new Logger(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get logger instance
|
||||
* @return Logger instance
|
||||
*/
|
||||
public static @NotNull Logger getLogger() {
|
||||
return new Logger(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
* @param name Optional logger name
|
||||
*/
|
||||
private Logger(@Nullable String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log message
|
||||
* @param level Record level
|
||||
* @param message Message
|
||||
* @param e Optional throwable to log
|
||||
*/
|
||||
private void log(@NotNull Level level, @NotNull String message, @Nullable Throwable e) {
|
||||
YamipaPlugin plugin = YamipaPlugin.getInstance();
|
||||
|
||||
// Handle verbose logging levels
|
||||
if (level.intValue() < Level.INFO.intValue()) {
|
||||
if (!plugin.isVerbose()) return;
|
||||
level = Level.INFO;
|
||||
}
|
||||
|
||||
// Add logger name to message
|
||||
if (name != null) {
|
||||
message = "[" + name + "] " + message;
|
||||
}
|
||||
|
||||
// Proxy record to real logger
|
||||
if (e == null) {
|
||||
plugin.getLogger().log(level, message);
|
||||
} else {
|
||||
plugin.getLogger().log(level, message, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log severe message
|
||||
* @param message Message
|
||||
* @param e Throwable to log
|
||||
*/
|
||||
public void severe(@NotNull String message, @NotNull Throwable e) {
|
||||
log(Level.SEVERE, message, e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log warning message
|
||||
* @param message Message
|
||||
* @param e Throwable to log
|
||||
*/
|
||||
public void warning(@NotNull String message, @NotNull Throwable e) {
|
||||
log(Level.WARNING, message, e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log warning message
|
||||
* @param message Message
|
||||
*/
|
||||
public void warning(@NotNull String message) {
|
||||
log(Level.WARNING, message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log info message
|
||||
* @param message Message
|
||||
*/
|
||||
public void info(@NotNull String message) {
|
||||
log(Level.INFO, message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log fine message
|
||||
* @param message Message
|
||||
*/
|
||||
public void fine(@NotNull String message) {
|
||||
log(Level.FINE, message, null);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,16 @@
|
||||
package io.josemmo.bukkit.plugin.utils;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import com.palmergames.bukkit.towny.TownyAPI;
|
||||
import com.palmergames.bukkit.towny.object.TownyPermission;
|
||||
import com.palmergames.bukkit.towny.utils.PlayerCacheUtil;
|
||||
@@ -10,184 +21,184 @@ import com.sk89q.worldguard.bukkit.WorldGuardPlugin;
|
||||
import com.sk89q.worldguard.internal.platform.WorldGuardPlatform;
|
||||
import com.sk89q.worldguard.protection.flags.Flags;
|
||||
import com.sk89q.worldguard.protection.flags.StateFlag;
|
||||
|
||||
import io.josemmo.bukkit.plugin.YamipaPlugin;
|
||||
import me.angeschossen.lands.api.LandsIntegration;
|
||||
import me.angeschossen.lands.api.flags.type.RoleFlag;
|
||||
import me.angeschossen.lands.api.land.LandWorld;
|
||||
import me.angeschossen.lands.api.player.LandPlayer;
|
||||
import me.ryanhamshire.GriefPrevention.GriefPrevention;
|
||||
import net.luckperms.api.LuckPerms;
|
||||
import net.luckperms.api.LuckPermsProvider;
|
||||
import org.anjocaido.groupmanager.GroupManager;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
public class Permissions {
|
||||
private static final Logger LOGGER = Logger.getLogger();
|
||||
private static @Nullable LuckPerms luckPerms;
|
||||
private static @Nullable GroupManager groupManager;
|
||||
private static @Nullable WorldGuard worldGuard;
|
||||
private static @Nullable GriefPrevention griefPrevention;
|
||||
private static @Nullable TownyAPI townyApi;
|
||||
private static @Nullable LandsIntegration landsApi;
|
||||
|
||||
@Nullable
|
||||
private static WorldGuard worldGuard = null;
|
||||
@Nullable
|
||||
private static GriefPrevention griefPrevention = null;
|
||||
@Nullable
|
||||
private static TownyAPI townyApi = null;
|
||||
@Nullable
|
||||
private static LandsIntegration landsApi = null;
|
||||
|
||||
static {
|
||||
try {
|
||||
luckPerms = LuckPermsProvider.get();
|
||||
} catch (NoClassDefFoundError | IllegalStateException __) {
|
||||
// LuckPerms is not installed
|
||||
}
|
||||
|
||||
try {
|
||||
groupManager = (GroupManager) YamipaPlugin.getInstance().getServer().getPluginManager().getPlugin("GroupManager");
|
||||
} catch (NoClassDefFoundError __) {
|
||||
// GroupManager is not installed
|
||||
}
|
||||
|
||||
try {
|
||||
worldGuard = WorldGuard.getInstance();
|
||||
|
||||
} catch (NoClassDefFoundError __) {
|
||||
|
||||
// WorldGuard is not installed
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
griefPrevention = GriefPrevention.instance;
|
||||
|
||||
} catch (NoClassDefFoundError __) {
|
||||
|
||||
// GriefPrevention is not installed
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
townyApi = TownyAPI.getInstance();
|
||||
|
||||
} catch (NoClassDefFoundError __) {
|
||||
|
||||
// Towny is not installed
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
landsApi = LandsIntegration.of(YamipaPlugin.getInstance());
|
||||
|
||||
} catch (NoClassDefFoundError __) {
|
||||
|
||||
// Lands is not installed
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get player variable
|
||||
* @param variable Variable name (key)
|
||||
* @param player Player instance
|
||||
* @return Variable value or NULL if not found
|
||||
*/
|
||||
public static @Nullable String getVariable(@NotNull String variable, @NotNull Player player) {
|
||||
if (luckPerms != null) {
|
||||
return luckPerms.getPlayerAdapter(Player.class).getUser(player).getCachedData().getMetaData()
|
||||
.getMetaValue(variable);
|
||||
}
|
||||
|
||||
if (groupManager != null) {
|
||||
String rawValue = groupManager.getWorldsHolder().getWorldPermissions(player)
|
||||
.getPermissionString(player.getName(), variable);
|
||||
return rawValue.isEmpty() ? null : rawValue;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can build at this block
|
||||
* @param player Player instance
|
||||
* @param location Block location
|
||||
* @return Whether player can build or not
|
||||
*
|
||||
* @param player Player instance
|
||||
* @param location Block location
|
||||
* @return Whether player can build or not
|
||||
*/
|
||||
public static boolean canBuild(@NotNull Player player, @NotNull Location location) {
|
||||
return queryWorldGuard(player, location, true)
|
||||
&& queryGriefPrevention(player, location, true)
|
||||
&& queryTowny(player, location, true)
|
||||
&& queryLands(player, location, true);
|
||||
|
||||
return queryWorldGuard(player, location, true) && queryGriefPrevention(player, location, true)
|
||||
&& queryTowny(player, location, true) && queryLands(player, location, true);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Can destroy this block
|
||||
* @param player Player instance
|
||||
* @param location Block location
|
||||
* @return Whether player can destroy or not
|
||||
*
|
||||
* @param player Player instance
|
||||
* @param location Block location
|
||||
* @return Whether player can destroy or not
|
||||
*/
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
public static boolean canDestroy(@NotNull Player player, @NotNull Location location) {
|
||||
return queryWorldGuard(player, location, false)
|
||||
&& queryGriefPrevention(player, location, false)
|
||||
&& queryTowny(player, location, false)
|
||||
&& queryLands(player, location, false);
|
||||
|
||||
return queryWorldGuard(player, location, false) && queryGriefPrevention(player, location, false)
|
||||
&& queryTowny(player, location, false) && queryLands(player, location, false);
|
||||
|
||||
}
|
||||
|
||||
private static boolean queryWorldGuard(@NotNull Player player, @NotNull Location location, boolean isBuild) {
|
||||
|
||||
if (worldGuard == null) {
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
WorldGuardPlatform platform = worldGuard.getPlatform();
|
||||
LocalPlayer wrappedPlayer = WorldGuardPlugin.inst().wrapPlayer(player);
|
||||
|
||||
final WorldGuardPlatform platform = worldGuard.getPlatform();
|
||||
final LocalPlayer wrappedPlayer = WorldGuardPlugin.inst().wrapPlayer(player);
|
||||
|
||||
// Grant if bypass permission is enabled
|
||||
boolean hasBypass = platform.getSessionManager().hasBypass(
|
||||
wrappedPlayer,
|
||||
BukkitAdapter.adapt(Objects.requireNonNull(location.getWorld()))
|
||||
);
|
||||
final boolean hasBypass = platform.getSessionManager().hasBypass(wrappedPlayer,
|
||||
BukkitAdapter.adapt(Objects.requireNonNull(location.getWorld())));
|
||||
if (hasBypass) {
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
// Check permission (note "BUILD" flag must always be present)
|
||||
StateFlag flag = isBuild ? Flags.BLOCK_PLACE : Flags.BLOCK_BREAK;
|
||||
return platform.getRegionContainer().createQuery()
|
||||
.testState(BukkitAdapter.adapt(location), wrappedPlayer, Flags.BUILD, flag);
|
||||
final StateFlag flag = isBuild ? Flags.BLOCK_PLACE : Flags.BLOCK_BREAK;
|
||||
return platform.getRegionContainer().createQuery().testState(BukkitAdapter.adapt(location), wrappedPlayer,
|
||||
Flags.BUILD, flag);
|
||||
|
||||
}
|
||||
|
||||
private static boolean queryGriefPrevention(@NotNull Player player, @NotNull Location location, boolean isBuild) {
|
||||
|
||||
if (griefPrevention == null) {
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
YamipaPlugin plugin = YamipaPlugin.getInstance();
|
||||
|
||||
final YamipaPlugin plugin = YamipaPlugin.getInstance();
|
||||
|
||||
// Build callable depending on permission to check
|
||||
Callable<Boolean> canEditCallable = isBuild ?
|
||||
() -> griefPrevention.allowBuild(player, location) == null :
|
||||
() -> griefPrevention.allowBreak(player, location.getBlock(), location) == null;
|
||||
final Callable<Boolean> canEditCallable = isBuild ? () -> griefPrevention.allowBuild(player, location) == null
|
||||
: () -> griefPrevention.allowBreak(player, location.getBlock(), location) == null;
|
||||
|
||||
// Check permission from primary thread
|
||||
try {
|
||||
return Bukkit.isPrimaryThread() ?
|
||||
canEditCallable.call() :
|
||||
Bukkit.getScheduler().callSyncMethod(plugin, canEditCallable).get();
|
||||
|
||||
return Bukkit.isPrimaryThread() ? canEditCallable.call()
|
||||
: Bukkit.getScheduler().callSyncMethod(plugin, canEditCallable).get();
|
||||
|
||||
} catch (Exception e) {
|
||||
LOGGER.severe("Failed to get player permissions from GriefPrevention", e);
|
||||
|
||||
plugin.log(Level.SEVERE, "Failed to get player permissions from GriefPrevention", e);
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static boolean queryTowny(@NotNull Player player, @NotNull Location location, boolean isBuild) {
|
||||
|
||||
if (townyApi == null) {
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
Material material = location.getBlock().getType();
|
||||
TownyPermission.ActionType type = isBuild ? TownyPermission.ActionType.BUILD : TownyPermission.ActionType.DESTROY;
|
||||
|
||||
final Material material = location.getBlock().getType();
|
||||
final TownyPermission.ActionType type = isBuild ? TownyPermission.ActionType.BUILD
|
||||
: TownyPermission.ActionType.DESTROY;
|
||||
return PlayerCacheUtil.getCachePermission(player, location, material, type);
|
||||
|
||||
}
|
||||
|
||||
private static boolean queryLands(@NotNull Player player, @NotNull Location location, boolean isBuild) {
|
||||
|
||||
if (landsApi == null) {
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
LandWorld landWorld = landsApi.getWorld(Objects.requireNonNull(location.getWorld()));
|
||||
|
||||
final LandWorld landWorld = landsApi.getWorld(Objects.requireNonNull(location.getWorld()));
|
||||
if (landWorld == null) {
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
LandPlayer landPlayer = Objects.requireNonNull(landsApi.getLandPlayer(player.getUniqueId()));
|
||||
RoleFlag flag = isBuild ?
|
||||
me.angeschossen.lands.api.flags.type.Flags.BLOCK_PLACE :
|
||||
me.angeschossen.lands.api.flags.type.Flags.BLOCK_BREAK;
|
||||
|
||||
final LandPlayer landPlayer = Objects.requireNonNull(landsApi.getLandPlayer(player.getUniqueId()));
|
||||
final RoleFlag flag = isBuild ? me.angeschossen.lands.api.flags.type.Flags.BLOCK_PLACE
|
||||
: me.angeschossen.lands.api.flags.type.Flags.BLOCK_BREAK;
|
||||
return landWorld.hasRoleFlag(landPlayer, location, flag, null, false);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package io.josemmo.bukkit.plugin.utils;
|
||||
|
||||
import com.comphenix.protocol.events.ListenerPriority;
|
||||
import io.josemmo.bukkit.plugin.YamipaPlugin;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockFace;
|
||||
@@ -20,9 +19,11 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.function.BiConsumer;
|
||||
import net.trueog.utilitiesog.UtilitiesOG;
|
||||
|
||||
public class SelectBlockTask {
|
||||
private static final Logger LOGGER = Logger.getLogger("SelectBlockTask");
|
||||
|
||||
private static final YamipaPlugin plugin = YamipaPlugin.getInstance();
|
||||
private static final Map<UUID, SelectBlockTask> instances = new HashMap<>();
|
||||
private static SelectBlockTaskListener listener = null;
|
||||
private final Player player;
|
||||
@@ -32,157 +33,215 @@ public class SelectBlockTask {
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* @param player Target player instance
|
||||
*/
|
||||
public SelectBlockTask(@NotNull Player player) {
|
||||
|
||||
this.player = player;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Set on success callback
|
||||
*
|
||||
* @param callback Success callback
|
||||
*/
|
||||
public void onSuccess(@Nullable BiConsumer<@NotNull Location, @NotNull BlockFace> callback) {
|
||||
|
||||
this.success = callback;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Set on failure (e.g. canceled) callback
|
||||
*
|
||||
* @param callback Failure callback
|
||||
*/
|
||||
public void onFailure(@Nullable Runnable callback) {
|
||||
|
||||
this.failure = callback;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Run task
|
||||
*
|
||||
* @param helpMessage Help message for the player
|
||||
*/
|
||||
public void run(@NotNull String helpMessage) {
|
||||
|
||||
UUID uuid = player.getUniqueId();
|
||||
|
||||
// Has this player another active task?
|
||||
if (instances.containsKey(uuid)) {
|
||||
player.sendMessage(ChatColor.RED + "You already have a pending action!");
|
||||
|
||||
UtilitiesOG.trueogMessage(player, "&cYou already have a pending action!");
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
// Create listener singleton if needed
|
||||
if (listener == null) {
|
||||
|
||||
listener = new SelectBlockTaskListener();
|
||||
listener.register();
|
||||
LOGGER.fine("Created SelectBlockTaskListener singleton");
|
||||
plugin.fine("Created SelectBlockTaskListener singleton");
|
||||
|
||||
}
|
||||
|
||||
// Start task
|
||||
instances.put(uuid, this);
|
||||
actionBar = ActionBar.repeat(player, ChatColor.GREEN + helpMessage + ChatColor.RESET +
|
||||
" - " + ChatColor.RED + "Left click to cancel");
|
||||
actionBar = ActionBar.repeat(player, "&a" + helpMessage + "&r - &cLeft click to cancel");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel task
|
||||
*/
|
||||
public void cancel() {
|
||||
|
||||
if (actionBar != null) {
|
||||
|
||||
actionBar.clear();
|
||||
actionBar = null;
|
||||
|
||||
}
|
||||
|
||||
instances.remove(player.getUniqueId());
|
||||
|
||||
// Destroy listener singleton if no more active tasks
|
||||
if (instances.isEmpty()) {
|
||||
|
||||
listener.unregister();
|
||||
listener = null;
|
||||
LOGGER.fine("Destroyed SelectBlockTaskListener singleton");
|
||||
plugin.fine("Destroyed SelectBlockTaskListener singleton");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal listener for handling player events
|
||||
*/
|
||||
private static class SelectBlockTaskListener extends InteractWithEntityListener implements Listener {
|
||||
|
||||
@Override
|
||||
public void register() {
|
||||
|
||||
super.register();
|
||||
YamipaPlugin plugin = YamipaPlugin.getInstance();
|
||||
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregister() {
|
||||
|
||||
super.unregister();
|
||||
HandlerList.unregisterAll(this);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ListenerPriority getPriority() {
|
||||
|
||||
return ListenerPriority.LOW;
|
||||
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOW)
|
||||
public void onBlockInteraction(@NotNull PlayerInteractEvent event) {
|
||||
|
||||
Action action = event.getAction();
|
||||
Player player = event.getPlayer();
|
||||
Block block = event.getClickedBlock();
|
||||
if (block == null) return;
|
||||
if (block == null)
|
||||
return;
|
||||
BlockFace face = event.getBlockFace();
|
||||
|
||||
// Handle event
|
||||
boolean allowEvent = true;
|
||||
if (action == Action.LEFT_CLICK_AIR || action == Action.LEFT_CLICK_BLOCK) {
|
||||
|
||||
allowEvent = handle(player, null, null);
|
||||
|
||||
} else if (action == Action.RIGHT_CLICK_BLOCK) {
|
||||
|
||||
allowEvent = handle(player, block, face);
|
||||
|
||||
}
|
||||
|
||||
// Cancel event (if needed)
|
||||
if (!allowEvent) {
|
||||
|
||||
event.setCancelled(true);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
|
||||
public void onPlayerQuit(@NotNull PlayerQuitEvent event) {
|
||||
|
||||
UUID uuid = event.getPlayer().getUniqueId();
|
||||
SelectBlockTask task = instances.get(uuid);
|
||||
if (task != null) {
|
||||
|
||||
task.cancel();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onAttack(@NotNull Player player, @NotNull Block block, @NotNull BlockFace face) {
|
||||
|
||||
return handle(player, null, null);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onInteract(@NotNull Player player, @NotNull Block block, @NotNull BlockFace face) {
|
||||
|
||||
return handle(player, block, face);
|
||||
|
||||
}
|
||||
|
||||
private boolean handle(@NotNull Player player, @Nullable Block block, @Nullable BlockFace face) {
|
||||
|
||||
// Get task responsible for handling this event
|
||||
UUID uuid = player.getUniqueId();
|
||||
SelectBlockTask task = instances.get(uuid);
|
||||
if (task == null) return true;
|
||||
if (task == null)
|
||||
return true;
|
||||
|
||||
// Cancel task
|
||||
task.cancel();
|
||||
|
||||
// Notify failure listener
|
||||
if (block == null || face == null) {
|
||||
|
||||
if (task.failure != null) {
|
||||
|
||||
task.failure.run();
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
// Notify success listener
|
||||
if (task.success != null) {
|
||||
|
||||
task.success.accept(block.getLocation(), face);
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,20 +1,23 @@
|
||||
name: ${project.name}
|
||||
version: ${project.version}
|
||||
name: Yamipa-OG
|
||||
version: '${version}'
|
||||
main: io.josemmo.bukkit.plugin.YamipaPlugin
|
||||
api-version: 1.16
|
||||
api-version: '${apiVersion}'
|
||||
depend: [ProtocolLib]
|
||||
softdepend:
|
||||
- BentoBox
|
||||
- GriefPrevention
|
||||
- GroupManager
|
||||
- Hyperverse
|
||||
- LuckPerms
|
||||
- Multiverse-Core
|
||||
- My_Worlds
|
||||
- PhantomWorlds
|
||||
- Towny
|
||||
- WorldGuard
|
||||
|
||||
commands:
|
||||
yamipa:
|
||||
description: Manage Yamipa images
|
||||
usage: /yamipa [subcommand]
|
||||
aliases:
|
||||
- image
|
||||
|
||||
permissions:
|
||||
# All plugin permissions
|
||||
yamipa.*:
|
||||
|
||||
Reference in new issue
Block a user