diff --git a/.gradle/9.6.1/checksums/checksums.lock b/.gradle/9.6.1/checksums/checksums.lock
new file mode 100644
index 0000000..65aff3f
Binary files /dev/null and b/.gradle/9.6.1/checksums/checksums.lock differ
diff --git a/.gradle/9.6.1/checksums/md5-checksums.bin b/.gradle/9.6.1/checksums/md5-checksums.bin
new file mode 100644
index 0000000..7c299e7
Binary files /dev/null and b/.gradle/9.6.1/checksums/md5-checksums.bin differ
diff --git a/.gradle/9.6.1/checksums/sha1-checksums.bin b/.gradle/9.6.1/checksums/sha1-checksums.bin
new file mode 100644
index 0000000..c387a39
Binary files /dev/null and b/.gradle/9.6.1/checksums/sha1-checksums.bin differ
diff --git a/.gradle/9.6.1/executionHistory/executionHistory.bin b/.gradle/9.6.1/executionHistory/executionHistory.bin
new file mode 100644
index 0000000..04941a5
Binary files /dev/null and b/.gradle/9.6.1/executionHistory/executionHistory.bin differ
diff --git a/.gradle/9.6.1/executionHistory/executionHistory.lock b/.gradle/9.6.1/executionHistory/executionHistory.lock
new file mode 100644
index 0000000..beb0bb8
Binary files /dev/null and b/.gradle/9.6.1/executionHistory/executionHistory.lock differ
diff --git a/.gradle/9.6.1/fileChanges/last-build.bin b/.gradle/9.6.1/fileChanges/last-build.bin
new file mode 100644
index 0000000..f76dd23
Binary files /dev/null and b/.gradle/9.6.1/fileChanges/last-build.bin differ
diff --git a/.gradle/9.6.1/fileHashes/fileHashes.bin b/.gradle/9.6.1/fileHashes/fileHashes.bin
new file mode 100644
index 0000000..8831819
Binary files /dev/null and b/.gradle/9.6.1/fileHashes/fileHashes.bin differ
diff --git a/.gradle/9.6.1/fileHashes/fileHashes.lock b/.gradle/9.6.1/fileHashes/fileHashes.lock
new file mode 100644
index 0000000..25c9e21
Binary files /dev/null and b/.gradle/9.6.1/fileHashes/fileHashes.lock differ
diff --git a/.gradle/9.6.1/fileHashes/resourceHashesCache.bin b/.gradle/9.6.1/fileHashes/resourceHashesCache.bin
new file mode 100644
index 0000000..480b0de
Binary files /dev/null and b/.gradle/9.6.1/fileHashes/resourceHashesCache.bin differ
diff --git a/.gradle/9.6.1/gc.properties b/.gradle/9.6.1/gc.properties
new file mode 100644
index 0000000..e69de29
diff --git a/.gradle/9.6.1/kotlin-dsl-plugin-entries/kotlin-dsl-plugin-entries.lock b/.gradle/9.6.1/kotlin-dsl-plugin-entries/kotlin-dsl-plugin-entries.lock
new file mode 100644
index 0000000..d682852
Binary files /dev/null and b/.gradle/9.6.1/kotlin-dsl-plugin-entries/kotlin-dsl-plugin-entries.lock differ
diff --git a/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/.gradle/buildOutputCleanup/buildOutputCleanup.lock
new file mode 100644
index 0000000..3312b10
Binary files /dev/null and b/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ
diff --git a/.gradle/buildOutputCleanup/cache.properties b/.gradle/buildOutputCleanup/cache.properties
new file mode 100644
index 0000000..3d76b0d
--- /dev/null
+++ b/.gradle/buildOutputCleanup/cache.properties
@@ -0,0 +1,2 @@
+#Fri Sep 04 09:55:07 CEST 2026
+gradle.version=9.6.1
diff --git a/.gradle/buildOutputCleanup/outputFiles.bin b/.gradle/buildOutputCleanup/outputFiles.bin
new file mode 100644
index 0000000..28da4bf
Binary files /dev/null and b/.gradle/buildOutputCleanup/outputFiles.bin differ
diff --git a/.gradle/configuration-cache/04874f85-d75a-4b9f-91ec-9f227f2a0929/.globals.work.bin b/.gradle/configuration-cache/04874f85-d75a-4b9f-91ec-9f227f2a0929/.globals.work.bin
new file mode 100644
index 0000000..fb8246c
--- /dev/null
+++ b/.gradle/configuration-cache/04874f85-d75a-4b9f-91ec-9f227f2a0929/.globals.work.bin
@@ -0,0 +1 @@
+ µøD©×OX"�ñs鿚
\ No newline at end of file
diff --git a/.gradle/configuration-cache/04874f85-d75a-4b9f-91ec-9f227f2a0929/.strings.work.bin b/.gradle/configuration-cache/04874f85-d75a-4b9f-91ec-9f227f2a0929/.strings.work.bin
new file mode 100644
index 0000000..a829d91
Binary files /dev/null and b/.gradle/configuration-cache/04874f85-d75a-4b9f-91ec-9f227f2a0929/.strings.work.bin differ
diff --git a/.gradle/configuration-cache/04874f85-d75a-4b9f-91ec-9f227f2a0929/_.work.bin b/.gradle/configuration-cache/04874f85-d75a-4b9f-91ec-9f227f2a0929/_.work.bin
new file mode 100644
index 0000000..c57505f
Binary files /dev/null and b/.gradle/configuration-cache/04874f85-d75a-4b9f-91ec-9f227f2a0929/_.work.bin differ
diff --git a/.gradle/configuration-cache/04874f85-d75a-4b9f-91ec-9f227f2a0929/buildfingerprint.bin b/.gradle/configuration-cache/04874f85-d75a-4b9f-91ec-9f227f2a0929/buildfingerprint.bin
new file mode 100644
index 0000000..eb41021
Binary files /dev/null and b/.gradle/configuration-cache/04874f85-d75a-4b9f-91ec-9f227f2a0929/buildfingerprint.bin differ
diff --git a/.gradle/configuration-cache/04874f85-d75a-4b9f-91ec-9f227f2a0929/classloaderscopes.bin b/.gradle/configuration-cache/04874f85-d75a-4b9f-91ec-9f227f2a0929/classloaderscopes.bin
new file mode 100644
index 0000000..f76dd23
Binary files /dev/null and b/.gradle/configuration-cache/04874f85-d75a-4b9f-91ec-9f227f2a0929/classloaderscopes.bin differ
diff --git a/.gradle/configuration-cache/04874f85-d75a-4b9f-91ec-9f227f2a0929/entry.bin b/.gradle/configuration-cache/04874f85-d75a-4b9f-91ec-9f227f2a0929/entry.bin
new file mode 100644
index 0000000..51a99dd
Binary files /dev/null and b/.gradle/configuration-cache/04874f85-d75a-4b9f-91ec-9f227f2a0929/entry.bin differ
diff --git a/.gradle/configuration-cache/04874f85-d75a-4b9f-91ec-9f227f2a0929/projectfingerprint.bin b/.gradle/configuration-cache/04874f85-d75a-4b9f-91ec-9f227f2a0929/projectfingerprint.bin
new file mode 100644
index 0000000..de8cae4
Binary files /dev/null and b/.gradle/configuration-cache/04874f85-d75a-4b9f-91ec-9f227f2a0929/projectfingerprint.bin differ
diff --git a/.gradle/configuration-cache/04874f85-d75a-4b9f-91ec-9f227f2a0929/work.bin b/.gradle/configuration-cache/04874f85-d75a-4b9f-91ec-9f227f2a0929/work.bin
new file mode 100644
index 0000000..976414d
Binary files /dev/null and b/.gradle/configuration-cache/04874f85-d75a-4b9f-91ec-9f227f2a0929/work.bin differ
diff --git a/.gradle/configuration-cache/0a6fcc7f-203b-41cd-9bee-4149060db226/classloaderscopes13361634749120215098.tmp b/.gradle/configuration-cache/0a6fcc7f-203b-41cd-9bee-4149060db226/classloaderscopes13361634749120215098.tmp
new file mode 100644
index 0000000..f76dd23
Binary files /dev/null and b/.gradle/configuration-cache/0a6fcc7f-203b-41cd-9bee-4149060db226/classloaderscopes13361634749120215098.tmp differ
diff --git a/.gradle/configuration-cache/1dae3fb7-7c9b-4252-9987-7d0bf4c8a39c/classloaderscopes11303081905500863799.tmp b/.gradle/configuration-cache/1dae3fb7-7c9b-4252-9987-7d0bf4c8a39c/classloaderscopes11303081905500863799.tmp
new file mode 100644
index 0000000..f76dd23
Binary files /dev/null and b/.gradle/configuration-cache/1dae3fb7-7c9b-4252-9987-7d0bf4c8a39c/classloaderscopes11303081905500863799.tmp differ
diff --git a/.gradle/configuration-cache/341fbea8-17c8-4ff9-abb1-8cf0a8d07b36/.globals.work.bin b/.gradle/configuration-cache/341fbea8-17c8-4ff9-abb1-8cf0a8d07b36/.globals.work.bin
new file mode 100644
index 0000000..bee2f5f
--- /dev/null
+++ b/.gradle/configuration-cache/341fbea8-17c8-4ff9-abb1-8cf0a8d07b36/.globals.work.bin
@@ -0,0 +1 @@
+ÑuWiPº¢Ïnì7a¶—àut
\ No newline at end of file
diff --git a/.gradle/configuration-cache/341fbea8-17c8-4ff9-abb1-8cf0a8d07b36/.strings.work.bin b/.gradle/configuration-cache/341fbea8-17c8-4ff9-abb1-8cf0a8d07b36/.strings.work.bin
new file mode 100644
index 0000000..37e7c85
Binary files /dev/null and b/.gradle/configuration-cache/341fbea8-17c8-4ff9-abb1-8cf0a8d07b36/.strings.work.bin differ
diff --git a/.gradle/configuration-cache/341fbea8-17c8-4ff9-abb1-8cf0a8d07b36/_.work.bin b/.gradle/configuration-cache/341fbea8-17c8-4ff9-abb1-8cf0a8d07b36/_.work.bin
new file mode 100644
index 0000000..30c7309
Binary files /dev/null and b/.gradle/configuration-cache/341fbea8-17c8-4ff9-abb1-8cf0a8d07b36/_.work.bin differ
diff --git a/.gradle/configuration-cache/341fbea8-17c8-4ff9-abb1-8cf0a8d07b36/build.work.bin b/.gradle/configuration-cache/341fbea8-17c8-4ff9-abb1-8cf0a8d07b36/build.work.bin
new file mode 100644
index 0000000..ff3988c
--- /dev/null
+++ b/.gradle/configuration-cache/341fbea8-17c8-4ff9-abb1-8cf0a8d07b36/build.work.bin
@@ -0,0 +1 @@
+X&¿IÞä�±@áî¦gKŸÁÙÅQ©L%
\ No newline at end of file
diff --git a/.gradle/configuration-cache/341fbea8-17c8-4ff9-abb1-8cf0a8d07b36/buildfingerprint.bin b/.gradle/configuration-cache/341fbea8-17c8-4ff9-abb1-8cf0a8d07b36/buildfingerprint.bin
new file mode 100644
index 0000000..2abcb27
Binary files /dev/null and b/.gradle/configuration-cache/341fbea8-17c8-4ff9-abb1-8cf0a8d07b36/buildfingerprint.bin differ
diff --git a/.gradle/configuration-cache/341fbea8-17c8-4ff9-abb1-8cf0a8d07b36/classloaderscopes.bin b/.gradle/configuration-cache/341fbea8-17c8-4ff9-abb1-8cf0a8d07b36/classloaderscopes.bin
new file mode 100644
index 0000000..ba9450a
Binary files /dev/null and b/.gradle/configuration-cache/341fbea8-17c8-4ff9-abb1-8cf0a8d07b36/classloaderscopes.bin differ
diff --git a/.gradle/configuration-cache/341fbea8-17c8-4ff9-abb1-8cf0a8d07b36/entry.bin b/.gradle/configuration-cache/341fbea8-17c8-4ff9-abb1-8cf0a8d07b36/entry.bin
new file mode 100644
index 0000000..580a410
Binary files /dev/null and b/.gradle/configuration-cache/341fbea8-17c8-4ff9-abb1-8cf0a8d07b36/entry.bin differ
diff --git a/.gradle/configuration-cache/341fbea8-17c8-4ff9-abb1-8cf0a8d07b36/projectfingerprint.bin b/.gradle/configuration-cache/341fbea8-17c8-4ff9-abb1-8cf0a8d07b36/projectfingerprint.bin
new file mode 100644
index 0000000..434380a
Binary files /dev/null and b/.gradle/configuration-cache/341fbea8-17c8-4ff9-abb1-8cf0a8d07b36/projectfingerprint.bin differ
diff --git a/.gradle/configuration-cache/341fbea8-17c8-4ff9-abb1-8cf0a8d07b36/work.bin b/.gradle/configuration-cache/341fbea8-17c8-4ff9-abb1-8cf0a8d07b36/work.bin
new file mode 100644
index 0000000..6642e13
Binary files /dev/null and b/.gradle/configuration-cache/341fbea8-17c8-4ff9-abb1-8cf0a8d07b36/work.bin differ
diff --git a/.gradle/configuration-cache/4ad0b03c-7d47-4056-990b-3a93c3db11fa/.globals.work.bin b/.gradle/configuration-cache/4ad0b03c-7d47-4056-990b-3a93c3db11fa/.globals.work.bin
new file mode 100644
index 0000000..fdafe58
--- /dev/null
+++ b/.gradle/configuration-cache/4ad0b03c-7d47-4056-990b-3a93c3db11fa/.globals.work.bin
@@ -0,0 +1 @@
+®=Ãps_ïćì²;m«KRáò
\ No newline at end of file
diff --git a/.gradle/configuration-cache/4ad0b03c-7d47-4056-990b-3a93c3db11fa/.strings.work.bin b/.gradle/configuration-cache/4ad0b03c-7d47-4056-990b-3a93c3db11fa/.strings.work.bin
new file mode 100644
index 0000000..115b2b3
Binary files /dev/null and b/.gradle/configuration-cache/4ad0b03c-7d47-4056-990b-3a93c3db11fa/.strings.work.bin differ
diff --git a/.gradle/configuration-cache/4ad0b03c-7d47-4056-990b-3a93c3db11fa/_.work.bin b/.gradle/configuration-cache/4ad0b03c-7d47-4056-990b-3a93c3db11fa/_.work.bin
new file mode 100644
index 0000000..afeda6a
Binary files /dev/null and b/.gradle/configuration-cache/4ad0b03c-7d47-4056-990b-3a93c3db11fa/_.work.bin differ
diff --git a/.gradle/configuration-cache/4ad0b03c-7d47-4056-990b-3a93c3db11fa/buildfingerprint.bin b/.gradle/configuration-cache/4ad0b03c-7d47-4056-990b-3a93c3db11fa/buildfingerprint.bin
new file mode 100644
index 0000000..875a32d
Binary files /dev/null and b/.gradle/configuration-cache/4ad0b03c-7d47-4056-990b-3a93c3db11fa/buildfingerprint.bin differ
diff --git a/.gradle/configuration-cache/4ad0b03c-7d47-4056-990b-3a93c3db11fa/classloaderscopes.bin b/.gradle/configuration-cache/4ad0b03c-7d47-4056-990b-3a93c3db11fa/classloaderscopes.bin
new file mode 100644
index 0000000..ba9450a
Binary files /dev/null and b/.gradle/configuration-cache/4ad0b03c-7d47-4056-990b-3a93c3db11fa/classloaderscopes.bin differ
diff --git a/.gradle/configuration-cache/4ad0b03c-7d47-4056-990b-3a93c3db11fa/entry.bin b/.gradle/configuration-cache/4ad0b03c-7d47-4056-990b-3a93c3db11fa/entry.bin
new file mode 100644
index 0000000..df96cfa
Binary files /dev/null and b/.gradle/configuration-cache/4ad0b03c-7d47-4056-990b-3a93c3db11fa/entry.bin differ
diff --git a/.gradle/configuration-cache/4ad0b03c-7d47-4056-990b-3a93c3db11fa/projectfingerprint.bin b/.gradle/configuration-cache/4ad0b03c-7d47-4056-990b-3a93c3db11fa/projectfingerprint.bin
new file mode 100644
index 0000000..25a6e4c
Binary files /dev/null and b/.gradle/configuration-cache/4ad0b03c-7d47-4056-990b-3a93c3db11fa/projectfingerprint.bin differ
diff --git a/.gradle/configuration-cache/4ad0b03c-7d47-4056-990b-3a93c3db11fa/work.bin b/.gradle/configuration-cache/4ad0b03c-7d47-4056-990b-3a93c3db11fa/work.bin
new file mode 100644
index 0000000..a203861
Binary files /dev/null and b/.gradle/configuration-cache/4ad0b03c-7d47-4056-990b-3a93c3db11fa/work.bin differ
diff --git a/.gradle/configuration-cache/7014d45d-1419-4a4c-8a3a-ef9442bc9fb4/classloaderscopes16220690481779945732.tmp b/.gradle/configuration-cache/7014d45d-1419-4a4c-8a3a-ef9442bc9fb4/classloaderscopes16220690481779945732.tmp
new file mode 100644
index 0000000..f76dd23
Binary files /dev/null and b/.gradle/configuration-cache/7014d45d-1419-4a4c-8a3a-ef9442bc9fb4/classloaderscopes16220690481779945732.tmp differ
diff --git a/.gradle/configuration-cache/9291a06f-3306-48d6-9bf2-f3df3a9ed90d/classloaderscopes17273679322066117525.tmp b/.gradle/configuration-cache/9291a06f-3306-48d6-9bf2-f3df3a9ed90d/classloaderscopes17273679322066117525.tmp
new file mode 100644
index 0000000..f76dd23
Binary files /dev/null and b/.gradle/configuration-cache/9291a06f-3306-48d6-9bf2-f3df3a9ed90d/classloaderscopes17273679322066117525.tmp differ
diff --git a/.gradle/configuration-cache/ai52j6yn7dodllvdf53pkfm4n/candidates.bin b/.gradle/configuration-cache/ai52j6yn7dodllvdf53pkfm4n/candidates.bin
new file mode 100644
index 0000000..9c96d90
--- /dev/null
+++ b/.gradle/configuration-cache/ai52j6yn7dodllvdf53pkfm4n/candidates.bin
@@ -0,0 +1 @@
+04874f85-d75a-4b9f-91ec-9f227f2a092¹
\ No newline at end of file
diff --git a/.gradle/configuration-cache/ar1d6jr1dzfkx6v3l38d47pb9/candidates.bin b/.gradle/configuration-cache/ar1d6jr1dzfkx6v3l38d47pb9/candidates.bin
new file mode 100644
index 0000000..c62b9bb
--- /dev/null
+++ b/.gradle/configuration-cache/ar1d6jr1dzfkx6v3l38d47pb9/candidates.bin
@@ -0,0 +1 @@
+341fbea8-17c8-4ff9-abb1-8cf0a8d07b3¶
\ No newline at end of file
diff --git a/.gradle/configuration-cache/bd41d510-ed22-45ce-940f-f06d19f20a88/classloaderscopes16335041072390187000.tmp b/.gradle/configuration-cache/bd41d510-ed22-45ce-940f-f06d19f20a88/classloaderscopes16335041072390187000.tmp
new file mode 100644
index 0000000..f76dd23
Binary files /dev/null and b/.gradle/configuration-cache/bd41d510-ed22-45ce-940f-f06d19f20a88/classloaderscopes16335041072390187000.tmp differ
diff --git a/.gradle/configuration-cache/c98bcd8d-0cc5-4259-9c8e-1d8f73264445/classloaderscopes16498410897654978536.tmp b/.gradle/configuration-cache/c98bcd8d-0cc5-4259-9c8e-1d8f73264445/classloaderscopes16498410897654978536.tmp
new file mode 100644
index 0000000..f76dd23
Binary files /dev/null and b/.gradle/configuration-cache/c98bcd8d-0cc5-4259-9c8e-1d8f73264445/classloaderscopes16498410897654978536.tmp differ
diff --git a/.gradle/configuration-cache/configuration-cache.lock b/.gradle/configuration-cache/configuration-cache.lock
new file mode 100644
index 0000000..7d34d4f
Binary files /dev/null and b/.gradle/configuration-cache/configuration-cache.lock differ
diff --git a/.gradle/configuration-cache/dm175kxwzsruj0sogx306vr1b/candidates.bin b/.gradle/configuration-cache/dm175kxwzsruj0sogx306vr1b/candidates.bin
new file mode 100644
index 0000000..03c6cb1
--- /dev/null
+++ b/.gradle/configuration-cache/dm175kxwzsruj0sogx306vr1b/candidates.bin
@@ -0,0 +1 @@
+4ad0b03c-7d47-4056-990b-3a93c3db11fá
\ No newline at end of file
diff --git a/.gradle/configuration-cache/fd7352ca-cea3-478e-947e-acba6f17b48b/classloaderscopes13362262299681345635.tmp b/.gradle/configuration-cache/fd7352ca-cea3-478e-947e-acba6f17b48b/classloaderscopes13362262299681345635.tmp
new file mode 100644
index 0000000..f76dd23
Binary files /dev/null and b/.gradle/configuration-cache/fd7352ca-cea3-478e-947e-acba6f17b48b/classloaderscopes13362262299681345635.tmp differ
diff --git a/.gradle/configuration-cache/gc.properties b/.gradle/configuration-cache/gc.properties
new file mode 100644
index 0000000..e69de29
diff --git a/.gradle/file-system.probe b/.gradle/file-system.probe
new file mode 100644
index 0000000..462509e
Binary files /dev/null and b/.gradle/file-system.probe differ
diff --git a/.gradle/vcs-1/gc.properties b/.gradle/vcs-1/gc.properties
new file mode 100644
index 0000000..e69de29
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..f3f4f83
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index dece6c5..3c73da5 100644
--- a/README.md
+++ b/README.md
@@ -6,6 +6,7 @@ A quality-of-life SMP plugin for Paper/Folia.
- **Join messages** — friendly welcome messages, including a first-join message that lists the server's plugins, and a special announcement when the owner logs in.
- **`/nopvp`** — toggle no-PvP mode. While active you cannot damage other players and other players cannot damage you (direct hits and projectiles). While enabled you also glow so everyone can see you're protected. Turning no-PvP *off* starts a **1-hour cooldown** before you can enable it again, and the state persists across restarts so relogging can't bypass it.
+- **Anti-combat-logging** — whenever you hit another player or get hit by one, you're tagged as being in combat for **15 seconds** with a chat warning. Logging off while tagged kills you on the spot and announces it to the server.
## Commands
@@ -27,9 +28,11 @@ Player no-PvP state and cooldown timestamps are stored in `plugins/qol-tweaks/no
## Building
```bash
-./gradlew build
+./build.sh
```
+This just runs `./gradlew build`, but if the pinned Java toolchain isn't installed it will automatically fall back to whatever JDK you do have (keeping the jar's bytecode compatible). You can also pass extra Gradle args, e.g. `./build.sh clean build`.
+
The plugin jar is output to `build/libs/`.
## License
diff --git a/build.sh b/build.sh
new file mode 100755
index 0000000..ac51b7e
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,101 @@
+#!/usr/bin/env bash
+#
+# Builds the qol-tweaks plugin jar.
+#
+# The build pins a Java toolchain version in build.gradle.kts. This script
+# finds that version on your machine, and if it isn't installed it transparently
+# falls back to the highest JDK that is (so `./build.sh` just works).
+#
+# Usage: ./build.sh [extra gradle args...]
+set -euo pipefail
+
+cd "$(dirname "$0")"
+
+# --- 1. which Java version does the build pin? --------------------------------
+pinned="$(grep -oE 'JavaLanguageVersion\.of\([0-9]+\)' build.gradle.kts | grep -oE '[0-9]+' | head -1 || true)"
+pinned="${pinned:-25}"
+
+# --- 2. find an installed JDK for a given version ------------------------------
+java_major() { # $1 = path to java binary; echoes its major version (e.g. 26)
+ "$1" -version 2>&1 | grep -oE '"([0-9]+)' | head -1 | tr -d '"'
+}
+
+find_java_home() { # $1 = version (e.g. 25); echoes a home whose java matches, or fails
+ local -a candidates=()
+ if [[ -x /usr/libexec/java_home ]]; then
+ local mac_home
+ if mac_home="$(/usr/libexec/java_home -v "$1" 2>/dev/null)"; then
+ candidates+=("$mac_home")
+ fi
+ fi
+ [[ -n "${JAVA_HOME:-}" ]] && candidates+=("$JAVA_HOME")
+
+ local home bin ver
+ for home in "${candidates[@]}"; do
+ bin="$home/bin/java"
+ [[ -x "$bin" ]] || continue
+ ver="$(java_major "$bin")"
+ if [[ -n "$ver" && "$ver" == "$1" ]]; then
+ echo "$home"
+ return 0
+ fi
+ done
+ return 1
+}
+
+# --- 3. pick the JDK to build with --------------------------------------------
+selected=""
+selected_home=""
+for v in "$pinned" 26 25 24 23 22 21 20 17; do
+ if h="$(find_java_home "$v")"; then
+ selected="$v"
+ selected_home="$h"
+ break
+ fi
+done
+
+if [[ -z "$selected" ]]; then
+ echo "error: no JDK found. Install any JDK 17+ (e.g. Java 25) and try again." >&2
+ exit 1
+fi
+
+export JAVA_HOME="$selected_home"
+export PATH="$JAVA_HOME/bin:$PATH"
+
+gradle_args=()
+override_init=""
+
+if [[ "$selected" != "$pinned" ]]; then
+ echo "note: pinned JDK $pinned not found; building with installed JDK $selected."
+ override_init="${TMPDIR:-/tmp}/qol-tweaks-toolchain-$pinned-$selected.gradle"
+ cat > "$override_init" <
+ if (p.plugins.hasPlugin('java')) {
+ p.extensions.findByName('java').toolchain.languageVersion.set(JavaLanguageVersion.of($selected))
+ p.tasks.withType(JavaCompile).configureEach {
+ options.release.set($pinned)
+ }
+ }
+}
+EOF
+ gradle_args+=(-I "$override_init")
+ # the init script is external input, so a cached configuration would be stale
+ gradle_args+=(--no-configuration-cache)
+fi
+
+trap '[[ -n "$override_init" ]] && rm -f "$override_init"' EXIT
+
+if [[ $# -eq 0 ]]; then
+ gradle_args+=(build)
+else
+ gradle_args+=("$@")
+fi
+
+echo "Using JDK $selected at $JAVA_HOME"
+./gradlew "${gradle_args[@]}"
+
+jar="$(ls -1 build/libs/*.jar 2>/dev/null | grep -Ev '(-sources|-javadoc)\.jar$' | head -1 || true)"
+if [[ -n "$jar" ]]; then
+ echo
+ echo "Done. Plugin jar: $(pwd)/$jar"
+fi
diff --git a/src/main/java/com/dvorakmortonxd/qoltweaks/QoLtweaks.java b/src/main/java/com/dvorakmortonxd/qoltweaks/QoLtweaks.java
index bc3c3e6..0209929 100644
--- a/src/main/java/com/dvorakmortonxd/qoltweaks/QoLtweaks.java
+++ b/src/main/java/com/dvorakmortonxd/qoltweaks/QoLtweaks.java
@@ -1,5 +1,7 @@
package com.dvorakmortonxd.qoltweaks;
+import com.dvorakmortonxd.qoltweaks.combatlog.CombatLogListener;
+import com.dvorakmortonxd.qoltweaks.combatlog.CombatLogManager;
import com.dvorakmortonxd.qoltweaks.listeners.JoinListener;
import com.dvorakmortonxd.qoltweaks.nopvp.NoPvPCommand;
import com.dvorakmortonxd.qoltweaks.nopvp.NoPvPListener;
@@ -10,14 +12,17 @@ import org.bukkit.plugin.java.JavaPlugin;
public final class QoLtweaks extends JavaPlugin {
private NoPvPManager noPvPManager;
+ private CombatLogManager combatLogManager;
@Override
public void onEnable() {
// Plugin startup logic
noPvPManager = new NoPvPManager(this);
+ combatLogManager = new CombatLogManager(this);
getServer().getPluginManager().registerEvents(new JoinListener(), this);
getServer().getPluginManager().registerEvents(new NoPvPListener(noPvPManager), this);
+ getServer().getPluginManager().registerEvents(new CombatLogListener(this, combatLogManager), this);
PluginCommand noPvPCommand = getCommand("nopvp");
if (noPvPCommand != null) {
@@ -31,5 +36,8 @@ public final class QoLtweaks extends JavaPlugin {
if (noPvPManager != null) {
noPvPManager.save();
}
+ if (combatLogManager != null) {
+ combatLogManager.cancelNotices();
+ }
}
}
diff --git a/src/main/java/com/dvorakmortonxd/qoltweaks/combatlog/CombatLogListener.java b/src/main/java/com/dvorakmortonxd/qoltweaks/combatlog/CombatLogListener.java
new file mode 100644
index 0000000..347cb7b
--- /dev/null
+++ b/src/main/java/com/dvorakmortonxd/qoltweaks/combatlog/CombatLogListener.java
@@ -0,0 +1,63 @@
+package com.dvorakmortonxd.qoltweaks.combatlog;
+
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.Player;
+import org.bukkit.entity.Projectile;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.event.entity.EntityDamageByEntityEvent;
+import org.bukkit.event.player.PlayerQuitEvent;
+import org.bukkit.plugin.java.JavaPlugin;
+
+public final class CombatLogListener implements Listener {
+
+ private final JavaPlugin plugin;
+ private final CombatLogManager manager;
+
+ public CombatLogListener(JavaPlugin plugin, CombatLogManager manager) {
+ this.plugin = plugin;
+ this.manager = manager;
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onEntityDamageByEntity(EntityDamageByEntityEvent event) {
+ if (!(event.getEntity() instanceof Player victim)) {
+ return;
+ }
+
+ Player attacker = resolveAttacker(event.getDamager());
+ if (attacker == null) {
+ return;
+ }
+
+ if (attacker.getUniqueId().equals(victim.getUniqueId())) {
+ return;
+ }
+
+ manager.tag(victim, true);
+ manager.tag(attacker, false);
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR)
+ public void onPlayerQuit(PlayerQuitEvent event) {
+ Player player = event.getPlayer();
+ if (!plugin.isEnabled() || player.isDead() || !manager.isTagged(player)) {
+ return;
+ }
+
+ player.setHealth(0.0);
+ plugin.getServer().getOnlinePlayers().forEach(other ->
+ other.sendMessage("§c" + player.getName() + " combat-logged and died!"));
+ }
+
+ private Player resolveAttacker(Entity damager) {
+ if (damager instanceof Player player) {
+ return player;
+ }
+ if (damager instanceof Projectile projectile && projectile.getShooter() instanceof Player player) {
+ return player;
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/com/dvorakmortonxd/qoltweaks/combatlog/CombatLogManager.java b/src/main/java/com/dvorakmortonxd/qoltweaks/combatlog/CombatLogManager.java
new file mode 100644
index 0000000..79710ea
--- /dev/null
+++ b/src/main/java/com/dvorakmortonxd/qoltweaks/combatlog/CombatLogManager.java
@@ -0,0 +1,70 @@
+package com.dvorakmortonxd.qoltweaks.combatlog;
+
+import io.papermc.paper.threadedregions.scheduler.ScheduledTask;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.java.JavaPlugin;
+
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+public final class CombatLogManager {
+
+ public static final long TAG_MILLIS = 15 * 1000L; // 15 seconds
+
+ private static final String VICTIM_MESSAGE = "§cYou got hit! Don't log off for 15 seconds or you'll insta-die.";
+ private static final String ATTACKER_MESSAGE = "§cYou hit a player! Don't log off for 15 seconds or you'll insta-die.";
+ private static final String SAFE_MESSAGE = "§aYou are no longer in combat.";
+
+ private final JavaPlugin plugin;
+ private final Map taggedUntil = new ConcurrentHashMap<>();
+ private final Map notices = new ConcurrentHashMap<>();
+
+ public CombatLogManager(JavaPlugin plugin) {
+ this.plugin = plugin;
+ }
+
+ public void tag(Player player, boolean victim) {
+ UUID uuid = player.getUniqueId();
+ boolean firstTag = !isTagged(player);
+ taggedUntil.put(uuid, System.currentTimeMillis() + TAG_MILLIS);
+
+ if (firstTag) {
+ player.sendMessage(victim ? VICTIM_MESSAGE : ATTACKER_MESSAGE);
+ }
+ scheduleNotice(player);
+ }
+
+ public boolean isTagged(Player player) {
+ return getRemainingMillis(player) > 0;
+ }
+
+ public long getRemainingMillis(Player player) {
+ long until = taggedUntil.getOrDefault(player.getUniqueId(), 0L);
+ return Math.max(0L, until - System.currentTimeMillis());
+ }
+
+ private void scheduleNotice(Player player) {
+ UUID uuid = player.getUniqueId();
+ ScheduledTask previous = notices.remove(uuid);
+ if (previous != null) {
+ previous.cancel();
+ }
+ long delayTicks = (TAG_MILLIS / 1000L) * 20L;
+ ScheduledTask task = plugin.getServer().getGlobalRegionScheduler()
+ .runDelayed(plugin, scheduled -> {
+ notices.remove(uuid);
+ if (player.isOnline() && !isTagged(player)) {
+ player.sendMessage(SAFE_MESSAGE);
+ }
+ }, delayTicks);
+ notices.put(uuid, task);
+ }
+
+ public void cancelNotices() {
+ for (ScheduledTask task : notices.values()) {
+ task.cancel();
+ }
+ notices.clear();
+ }
+}
diff --git a/src/main/java/com/dvorakmortonxd/qoltweaks/listeners/JoinListener.java b/src/main/java/com/dvorakmortonxd/qoltweaks/listeners/JoinListener.java
index 4e60057..21b140a 100644
--- a/src/main/java/com/dvorakmortonxd/qoltweaks/listeners/JoinListener.java
+++ b/src/main/java/com/dvorakmortonxd/qoltweaks/listeners/JoinListener.java
@@ -10,8 +10,20 @@ public class JoinListener implements Listener {
public void onPlayerJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
+ player.sendTitle("§aWelcome to the SMP", "§7on a new world since 4th September 2026", 10, 70, 20);
+
if (!player.hasPlayedBefore()) {
- player.sendMessage("§aWelcome to the SMP, " + player.getName() + "!\nThis is your first time here, so let me tell you whats on here, theres a 3d map of the server at https://bluemap.dvorakmortonxd.dev , theres a voicechat you can optionally install as a mod (Simple Voice Chat), theres a tree chopper plugin you can toggle with /atc, theres veinminer which you can also toggle with /veinminer toggle, theres tradecycle plugin which allows you to cycle thru villagers trades by clicking [F] in any inventory slot while trading and there is ClickVillagers which allows you to pick up villagers into your inventory when shifting and right clicking,\n thats about it, enjoy your game! :3");
+ player.sendMessage("§aWelcome to the SMP, " + player.getName() + "!");
+ player.sendMessage("This is your first time here, so let me tell you whats on here:");
+ player.sendMessage("§7- a 3d map of the server at https://bluemap.dvorakmortonxd.dev");
+ player.sendMessage("§7- a voicechat you can optionally install as a mod (Simple Voice Chat)");
+ player.sendMessage("§7- a tree chopper plugin you can toggle with /atc");
+ player.sendMessage("§7- veinminer, which you can also toggle with /veinminer toggle");
+ player.sendMessage("§7- tradecycle, which lets you cycle thru villagers trades by pressing [F] in any inventory slot while trading");
+ player.sendMessage("§7- ClickVillagers, which lets you pick up villagers into your inventory by shift-right-clicking");
+ player.sendMessage("§7- a /nopvp command to toggle no-pvp mode if you want to build in peace");
+ player.sendMessage("§cAbuse of /nopvp is bannable. Also, combat logging is NOT a good idea.");
+ player.sendMessage("§aThats about it, enjoy your game! :3");
event.setJoinMessage("§e" + player.getName() + " has joined the SMP for the first time!");
} else {
if (player.getName().equals("dvorakmortonxd")) {