diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..94f6f5c
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,8 @@
+# Shared TFMC XML formatting; see Docs/POM-CONVENTIONS.md.
+[*.xml]
+charset = utf-8
+end_of_line = lf
+indent_style = space
+indent_size = 4
+insert_final_newline = true
+trim_trailing_whitespace = true
diff --git a/.github/dependencies.sha256 b/.github/dependencies.sha256
new file mode 100644
index 0000000..b988d80
--- /dev/null
+++ b/.github/dependencies.sha256
@@ -0,0 +1,2 @@
+14850d7454374d7312305d3e84a391be720301e5964963f869b27ae97f6264eb libs/MMOCore-1.13.1.jar
+660ff2a6ec86bc8d7779948cf65c1637e271a16e1ef812d6a273f4a5c5eec73c libs/MythicLib-1.7.jar
diff --git a/.github/scripts/install-local-dependencies.sh b/.github/scripts/install-local-dependencies.sh
new file mode 100644
index 0000000..06358db
--- /dev/null
+++ b/.github/scripts/install-local-dependencies.sh
@@ -0,0 +1,12 @@
+#!/usr/bin/env bash
+set -euo pipefail
+# Run from the repository root after downloading the pinned JARs.
+# Hash-qualified versions prevent different private JARs sharing a Maven cache key.
+sha256sum --check .github/dependencies.sha256
+
+mvn -B --no-transfer-progress org.apache.maven.plugins:maven-install-plugin:3.1.4:install-file \
+ -Dfile="libs/MMOCore-1.13.1.jar" -DgroupId="local" -DartifactId="MMOCore" \
+ -Dversion="1.13.1-tfmc-14850d745437" -Dpackaging=jar -DgeneratePom=true "$@"
+mvn -B --no-transfer-progress org.apache.maven.plugins:maven-install-plugin:3.1.4:install-file \
+ -Dfile="libs/MythicLib-1.7.jar" -DgroupId="local" -DartifactId="MythicLib" \
+ -Dversion="1.7-tfmc-660ff2a6ec86" -Dpackaging=jar -DgeneratePom=true "$@"
diff --git a/.github/scripts/plugin-artifact.py b/.github/scripts/plugin-artifact.py
new file mode 100644
index 0000000..c730186
--- /dev/null
+++ b/.github/scripts/plugin-artifact.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python3
+"""Validate the Maven runtime JAR and optionally stage a release bundle."""
+import argparse
+import hashlib
+import json
+import os
+from pathlib import Path
+import re
+import shutil
+import zipfile
+
+
+def validate(source, version):
+ source = Path(source).resolve()
+ target = Path.cwd().resolve() / "target"
+ if not source.is_relative_to(target) or not source.is_file():
+ raise ValueError("Expected the Maven runtime JAR inside target/")
+ if not re.fullmatch(r"[a-z][a-z0-9]*-" + re.escape(version) + r"\.jar", source.name):
+ raise ValueError("Runtime JAR filename must use a lowercase alphanumeric name followed by the Maven version")
+ with zipfile.ZipFile(source) as jar:
+ descriptors = {"plugin.yml", "paper-plugin.yml"}.intersection(jar.namelist())
+ if not descriptors:
+ raise ValueError("Runtime JAR has no plugin descriptor")
+ if jar.testzip() is not None:
+ raise ValueError("Runtime JAR is corrupt")
+ for name in descriptors:
+ text = jar.read(name).decode("utf-8-sig")
+ versions = re.findall(
+ r'''^version:[ \t]*(?:"([^"\r\n]*)"|'([^'\r\n]*)'|([^\s#]+))(?:(?:[ \t]+\#[^\r\n]*)|[ \t]*)\r?$''',
+ text, re.MULTILINE,
+ )
+ if len(versions) != 1 or next((v for v in versions[0] if v), "") != version:
+ raise ValueError(f"{name} version must match Maven version {version}")
+ return source
+
+
+def main():
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument("--jar", required=True, type=Path)
+ parser.add_argument("--version", required=True)
+ parser.add_argument("--stage", type=Path)
+ parser.add_argument("--dependencies", type=Path)
+ args = parser.parse_args()
+ try:
+ source = validate(args.jar, args.version)
+ if args.stage:
+ if os.environ["TAG"] != f"v{args.version}":
+ raise ValueError("Release tag must match the Maven version")
+ dependencies = json.loads(args.dependencies.read_text()) if args.dependencies else []
+ digest = hashlib.sha256(source.read_bytes()).hexdigest()
+ metadata = {
+ "repository": os.environ["GITHUB_REPOSITORY"],
+ "commit": os.environ["GITHUB_SHA"],
+ "tag": os.environ["TAG"],
+ "run": f"{os.environ['GITHUB_SERVER_URL']}/{os.environ['GITHUB_REPOSITORY']}/actions/runs/{os.environ['GITHUB_RUN_ID']}",
+ "plugin_dependencies": dependencies,
+ "artifact": source.name,
+ "sha256": digest,
+ }
+ args.stage.mkdir()
+ shutil.copyfile(source, args.stage / source.name)
+ (args.stage / "SHA256SUMS").write_text(f"{digest} {source.name}\n")
+ (args.stage / "build.json").write_text(json.dumps(metadata, indent=2) + "\n")
+ print(f"Verified {source.name}: filename and embedded version match {args.version}")
+ except (ValueError, OSError, KeyError, zipfile.BadZipFile) as error:
+ parser.exit(1, f"Plugin artifact validation failed: {error}\n")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/.github/scripts/prepare-release.sh b/.github/scripts/prepare-release.sh
new file mode 100644
index 0000000..c06d651
--- /dev/null
+++ b/.github/scripts/prepare-release.sh
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+set -euo pipefail
+: "${GH_TOKEN:?Set DEPS_TOKEN with Contents read access to TF-Minecraft/ServerAssets}"
+ref=183a187cf6128371a31ad20f3b524f6f30d537b9
+mkdir -p libs
+curl --fail --location --silent --show-error --retry 3 -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github.raw+json" "https://api.github.com/repos/TF-Minecraft/ServerAssets/contents/jars/14850d745437/MMOCore-1.13.1.jar?ref=$ref" > "libs/MMOCore-1.13.1.jar"
+curl --fail --location --silent --show-error --retry 3 -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github.raw+json" "https://api.github.com/repos/TF-Minecraft/ServerAssets/contents/jars/660ff2a6ec86/MythicLib-1.7.jar?ref=$ref" > "libs/MythicLib-1.7.jar"
+bash .github/scripts/install-local-dependencies.sh "$@"
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..40b7935
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,77 @@
+name: Build
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+ branches: [main]
+
+permissions:
+ contents: read
+
+jobs:
+ build:
+ runs-on: ubuntu-24.04
+ timeout-minutes: 20
+ steps:
+ - uses: actions/checkout@v7
+ with:
+ persist-credentials: false
+ - uses: actions/setup-java@v6
+ with:
+ distribution: temurin
+ java-version: '21'
+ - name: Install shared plugin dependencies
+ uses: TF-Minecraft/TLibs/.github/actions/setup-plugins@4f7a3545063cb2e8d06a034687c817ab863f0835
+ with:
+ mode: pinned
+ private-token: ${{ secrets.DEPS_TOKEN }}
+
+ - name: Prepare pinned dependencies
+ env:
+ GH_TOKEN: ${{ secrets.DEPS_TOKEN }}
+ run: bash .github/scripts/prepare-release.sh
+ - name: Set build version
+ id: dev
+ shell: bash
+ run: |
+ dev_version="main-SNAPSHOT"
+ if [[ "$GITHUB_EVENT_NAME" == "pull_request" ]]; then
+ dev_version="DEV-$(date -u +%Y%m%d-%H%M)"
+ fi
+ mvn -B --no-transfer-progress -P'!deploy-live' org.codehaus.mojo:versions-maven-plugin:2.22.0:set \
+ -DnewVersion="$dev_version" -DgenerateBackupPoms=false
+ mvn -B --no-transfer-progress -P'!deploy-live' help:evaluate \
+ -Dexpression=project.build.finalName -Doutput="$RUNNER_TEMP/final-name"
+ final_name=$(cat "$RUNNER_TEMP/final-name")
+ [[ "$final_name" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]] || exit 1
+ echo "version=$dev_version" >> "$GITHUB_OUTPUT"
+ echo "name=$final_name" >> "$GITHUB_OUTPUT"
+ echo "jar=target/$final_name.jar" >> "$GITHUB_OUTPUT"
+
+ - name: Run unit tests and build
+ run: mvn -B --no-transfer-progress -P'!deploy-live' clean verify -DskipTests=false -Dmaven.test.skip=false
+
+ - name: Verify runtime JAR
+ env:
+ ARTIFACT_PATH: ${{ steps.dev.outputs.jar }}
+ BUILD_VERSION: ${{ steps.dev.outputs.version }}
+ run: python3 .github/scripts/plugin-artifact.py --jar "$ARTIFACT_PATH" --version "$BUILD_VERSION"
+
+ - if: github.event_name == 'pull_request'
+ uses: actions/upload-artifact@v7
+ with:
+ name: ${{ steps.dev.outputs.name }}-${{ github.run_id }}-${{ github.run_attempt }}
+ path: |
+ ${{ steps.dev.outputs.jar }}
+ .build/plugin-dependencies.json
+ include-hidden-files: true
+ if-no-files-found: error
+
+ - name: Upload unit test reports
+ if: ${{ !cancelled() && hashFiles('target/surefire-reports/*.xml') != '' }}
+ uses: actions/upload-artifact@v7
+ with:
+ name: unit-test-reports-${{ github.run_id }}-${{ github.run_attempt }}
+ path: target/surefire-reports/
+ if-no-files-found: error
diff --git a/.github/workflows/maven-release.yml b/.github/workflows/maven-release.yml
new file mode 100644
index 0000000..9d015e5
--- /dev/null
+++ b/.github/workflows/maven-release.yml
@@ -0,0 +1,138 @@
+name: Maven plugin release
+
+on:
+ workflow_call:
+ inputs:
+ java-version:
+ required: true
+ type: string
+ secrets:
+ DEPS_TOKEN:
+ required: false
+
+permissions:
+ contents: read
+
+concurrency:
+ group: plugin-release-${{ github.repository }}-${{ github.ref }}
+ cancel-in-progress: false
+
+defaults:
+ run:
+ shell: bash
+
+jobs:
+ build:
+ if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
+ runs-on: ubuntu-24.04
+ timeout-minutes: 20
+ steps:
+ - uses: actions/checkout@v7
+ with:
+ persist-credentials: false
+
+ - uses: actions/setup-java@v6
+ with:
+ distribution: temurin
+ java-version: ${{ inputs.java-version }}
+
+ - name: Validate release version
+ env:
+ TAG: ${{ github.ref_name }}
+ run: |
+ [[ "$TAG" =~ ^v[0-9]+\.[0-9]+(\.[0-9]+)?(-[0-9A-Za-z]+([.-][0-9A-Za-z]+)*)?$ ]] || {
+ echo '::error::Expected vMAJOR.MINOR or vMAJOR.MINOR.PATCH, with an optional prerelease suffix.'
+ exit 1
+ }
+ [[ "${TAG^^}" != *SNAPSHOT* ]] || exit 1
+
+ - name: Install shared plugin dependencies
+ uses: TF-Minecraft/TLibs/.github/actions/setup-plugins@4f7a3545063cb2e8d06a034687c817ab863f0835
+ with:
+ mode: pinned
+ private-token: ${{ secrets.DEPS_TOKEN }}
+
+ - name: Prepare pinned dependencies
+ env:
+ GH_TOKEN: ${{ secrets.DEPS_TOKEN }}
+ run: |
+ if [[ -f .github/scripts/prepare-release.sh ]]; then
+ bash .github/scripts/prepare-release.sh
+ fi
+
+ - name: Set version from tag and build
+ id: maven
+ env:
+ TAG: ${{ github.ref_name }}
+ run: |
+ mvn -B --no-transfer-progress -P'!deploy-live' org.codehaus.mojo:versions-maven-plugin:2.22.0:set \
+ -DnewVersion="${TAG#v}" -DgenerateBackupPoms=false
+ mvn -B --no-transfer-progress -P'!deploy-live' help:evaluate \
+ -Dexpression=project.build.finalName -Doutput="$RUNNER_TEMP/final-name"
+ final_name=$(cat "$RUNNER_TEMP/final-name")
+ [[ "$final_name" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]] || exit 1
+ echo "jar=target/$final_name.jar" >> "$GITHUB_OUTPUT"
+ mvn -B --no-transfer-progress -P'!deploy-live' clean verify -DskipTests=false -Dmaven.test.skip=false
+
+ - name: Stage only the release JAR
+ env:
+ ARTIFACT_PATH: ${{ steps.maven.outputs.jar }}
+ TAG: ${{ github.ref_name }}
+ run: |
+ python3 .github/scripts/plugin-artifact.py \
+ --jar "$ARTIFACT_PATH" --version "${TAG#v}" \
+ --stage "$RUNNER_TEMP/plugin-release" \
+ --dependencies .build/plugin-dependencies.json
+
+ - uses: actions/upload-artifact@v7
+ with:
+ name: plugin-release-${{ github.sha }}
+ path: ${{ runner.temp }}/plugin-release/
+ if-no-files-found: error
+ retention-days: 30
+
+ publish:
+ needs: build
+ runs-on: ubuntu-24.04
+ timeout-minutes: 5
+ permissions:
+ contents: write
+ steps:
+ - uses: actions/download-artifact@v8
+ with:
+ name: plugin-release-${{ github.sha }}
+ path: release
+
+ - name: Create draft release
+ working-directory: release
+ env:
+ GH_TOKEN: ${{ github.token }}
+ GH_REPO: ${{ github.repository }}
+ TAG: ${{ github.ref_name }}
+ run: |
+ sha256sum --check SHA256SUMS
+ flags=()
+ if [[ "$TAG" == *-* ]]; then flags+=(--prerelease); fi
+ gh api --method POST "repos/$GH_REPO/releases/generate-notes" \
+ -f tag_name="$TAG" --jq .body > "$RUNNER_TEMP/release-changes.md"
+ python3 - <<'PY'
+ import os, re
+ from pathlib import Path
+ from urllib.parse import quote
+ temp = Path(os.environ['RUNNER_TEMP'])
+ changes = (temp / 'release-changes.md').read_text().strip()
+ changes = re.sub(r"^## What's Changed\s*", '', changes)
+ changes = re.sub(r'^(#{1,5}) ', r'\1# ', changes, flags=re.MULTILINE)
+ lines = ['## Changes', '', changes or 'Initial release.', '', '## Downloads', '']
+ assets = sorted([*Path('.').glob('*.jar'), *Path('.').glob('*.tar.gz')])
+ assets += [Path('SHA256SUMS'), Path('build.json')]
+ base = f"https://github.com/{os.environ['GH_REPO']}/releases/download/{quote(os.environ['TAG'], safe='')}/"
+ for asset in assets:
+ if not asset.is_file():
+ raise SystemExit(f'Missing release asset: {asset}')
+ description = {'SHA256SUMS': ' — SHA-256 checksums.', 'build.json': ' — Build metadata.'}.get(asset.name, '')
+ lines.append(f'- [`{asset.name}`]({base}{quote(asset.name)}){description}')
+ (temp / 'release-notes.md').write_text('\n'.join(lines) + '\n')
+ PY
+ gh release create "$TAG" ./*.jar SHA256SUMS build.json \
+ --verify-tag --draft --title "$TAG" --notes-file "$RUNNER_TEMP/release-notes.md" "${flags[@]}"
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..241e773
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,18 @@
+name: Release
+
+on:
+ push:
+ tags: ['v*']
+
+permissions:
+ contents: read
+
+jobs:
+ release:
+ permissions:
+ contents: write
+ uses: ./.github/workflows/maven-release.yml
+ with:
+ java-version: '21'
+ secrets:
+ DEPS_TOKEN: ${{ secrets.DEPS_TOKEN }}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..fee0cd8
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,54 @@
+# Build output
+target/
+build/
+out/
+dist/
+nbbuild/
+nbdist/
+*.class
+dependency-reduced-pom.xml
+pom.xml.tag
+pom.xml.releaseBackup
+pom.xml.versionsBackup
+pom.xml.next
+release.properties
+
+# Build dependencies
+/.build/
+*.jar
+!.mvn/wrapper/maven-wrapper.jar
+
+# Python caches and environments
+__pycache__/
+*.py[cod]
+.venv/
+venv/
+
+# IDE files
+.idea/
+*.iml
+*.ipr
+*.iws
+.vscode/
+.project
+.classpath
+.settings/
+nbproject/private/
+.nb-gradle/
+
+# Logs and temporary files
+*.log
+hs_err_pid*
+replay_pid*
+*.bak
+*.swp
+*.tmp
+*~
+
+# Operating system files
+.DS_Store
+Thumbs.db
+Desktop.ini
+
+# Local investigation notes and test artifacts
+/.scratch/
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..9d00de5
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+ The Artistic License 2.0
+
+ Copyright (c) 2000-2006, The Perl Foundation.
+
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+Preamble
+
+This license establishes the terms under which a given free software
+Package may be copied, modified, distributed, and/or redistributed.
+The intent is that the Copyright Holder maintains some artistic
+control over the development of that Package while still keeping the
+Package available as open source and free software.
+
+You are always permitted to make arrangements wholly outside of this
+license directly with the Copyright Holder of a given Package. If the
+terms of this license do not permit the full use that you propose to
+make of the Package, you should contact the Copyright Holder and seek
+a different licensing arrangement.
+
+Definitions
+
+ "Copyright Holder" means the individual(s) or organization(s)
+ named in the copyright notice for the entire Package.
+
+ "Contributor" means any party that has contributed code or other
+ material to the Package, in accordance with the Copyright Holder's
+ procedures.
+
+ "You" and "your" means any person who would like to copy,
+ distribute, or modify the Package.
+
+ "Package" means the collection of files distributed by the
+ Copyright Holder, and derivatives of that collection and/or of
+ those files. A given Package may consist of either the Standard
+ Version, or a Modified Version.
+
+ "Distribute" means providing a copy of the Package or making it
+ accessible to anyone else, or in the case of a company or
+ organization, to others outside of your company or organization.
+
+ "Distributor Fee" means any fee that you charge for Distributing
+ this Package or providing support for this Package to another
+ party. It does not mean licensing fees.
+
+ "Standard Version" refers to the Package if it has not been
+ modified, or has been modified only in ways explicitly requested
+ by the Copyright Holder.
+
+ "Modified Version" means the Package, if it has been changed, and
+ such changes were not explicitly requested by the Copyright
+ Holder.
+
+ "Original License" means this Artistic License as Distributed with
+ the Standard Version of the Package, in its current version or as
+ it may be modified by The Perl Foundation in the future.
+
+ "Source" form means the source code, documentation source, and
+ configuration files for the Package.
+
+ "Compiled" form means the compiled bytecode, object code, binary,
+ or any other form resulting from mechanical transformation or
+ translation of the Source form.
+
+
+Permission for Use and Modification Without Distribution
+
+(1) You are permitted to use the Standard Version and create and use
+Modified Versions for any purpose without restriction, provided that
+you do not Distribute the Modified Version.
+
+
+Permissions for Redistribution of the Standard Version
+
+(2) You may Distribute verbatim copies of the Source form of the
+Standard Version of this Package in any medium without restriction,
+either gratis or for a Distributor Fee, provided that you duplicate
+all of the original copyright notices and associated disclaimers. At
+your discretion, such verbatim copies may or may not include a
+Compiled form of the Package.
+
+(3) You may apply any bug fixes, portability changes, and other
+modifications made available from the Copyright Holder. The resulting
+Package will still be considered the Standard Version, and as such
+will be subject to the Original License.
+
+
+Distribution of Modified Versions of the Package as Source
+
+(4) You may Distribute your Modified Version as Source (either gratis
+or for a Distributor Fee, and with or without a Compiled form of the
+Modified Version) provided that you clearly document how it differs
+from the Standard Version, including, but not limited to, documenting
+any non-standard features, executables, or modules, and provided that
+you do at least ONE of the following:
+
+ (a) make the Modified Version available to the Copyright Holder
+ of the Standard Version, under the Original License, so that the
+ Copyright Holder may include your modifications in the Standard
+ Version.
+
+ (b) ensure that installation of your Modified Version does not
+ prevent the user installing or running the Standard Version. In
+ addition, the Modified Version must bear a name that is different
+ from the name of the Standard Version.
+
+ (c) allow anyone who receives a copy of the Modified Version to
+ make the Source form of the Modified Version available to others
+ under
+
+ (i) the Original License or
+
+ (ii) a license that permits the licensee to freely copy,
+ modify and redistribute the Modified Version using the same
+ licensing terms that apply to the copy that the licensee
+ received, and requires that the Source form of the Modified
+ Version, and of any works derived from it, be made freely
+ available in that license fees are prohibited but Distributor
+ Fees are allowed.
+
+
+Distribution of Compiled Forms of the Standard Version
+or Modified Versions without the Source
+
+(5) You may Distribute Compiled forms of the Standard Version without
+the Source, provided that you include complete instructions on how to
+get the Source of the Standard Version. Such instructions must be
+valid at the time of your distribution. If these instructions, at any
+time while you are carrying out such distribution, become invalid, you
+must provide new instructions on demand or cease further distribution.
+If you provide valid instructions or cease distribution within thirty
+days after you become aware that the instructions are invalid, then
+you do not forfeit any of your rights under this license.
+
+(6) You may Distribute a Modified Version in Compiled form without
+the Source, provided that you comply with Section 4 with respect to
+the Source of the Modified Version.
+
+
+Aggregating or Linking the Package
+
+(7) You may aggregate the Package (either the Standard Version or
+Modified Version) with other packages and Distribute the resulting
+aggregation provided that you do not charge a licensing fee for the
+Package. Distributor Fees are permitted, and licensing fees for other
+components in the aggregation are permitted. The terms of this license
+apply to the use and Distribution of the Standard or Modified Versions
+as included in the aggregation.
+
+(8) You are permitted to link Modified and Standard Versions with
+other works, to embed the Package in a larger work of your own, or to
+build stand-alone binary or bytecode versions of applications that
+include the Package, and Distribute the result without restriction,
+provided the result does not expose a direct interface to the Package.
+
+
+Items That are Not Considered Part of a Modified Version
+
+(9) Works (including, but not limited to, modules and scripts) that
+merely extend or make use of the Package, do not, by themselves, cause
+the Package to be a Modified Version. In addition, such works are not
+considered parts of the Package itself, and are not subject to the
+terms of this license.
+
+
+General Provisions
+
+(10) Any use, modification, and distribution of the Standard or
+Modified Versions is governed by this Artistic License. By using,
+modifying or distributing the Package, you accept this license. Do not
+use, modify, or distribute the Package, if you do not accept this
+license.
+
+(11) If your Modified Version has been derived from a Modified
+Version made by someone other than you, you are nevertheless required
+to ensure that your Modified Version complies with the requirements of
+this license.
+
+(12) This license does not grant you the right to use any trademark,
+service mark, tradename, or logo of the Copyright Holder.
+
+(13) This license includes the non-exclusive, worldwide,
+free-of-charge patent license to make, have made, use, offer to sell,
+sell, import and otherwise transfer the Package with respect to any
+patent claims licensable by the Copyright Holder that are necessarily
+infringed by the Package. If you institute patent litigation
+(including a cross-claim or counterclaim) against any party alleging
+that the Package constitutes direct or contributory patent
+infringement, then this Artistic License to you shall terminate on the
+date that such litigation is filed.
+
+(14) Disclaimer of Warranty:
+THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS
+IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
+NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL
+LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF
+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..5f5ae91
--- /dev/null
+++ b/README.md
@@ -0,0 +1,41 @@
+# Gathering
+
+> Scatter hidden gathering spots across the world for TF-Minecraft characters to discover and harvest.
+
+Gathering spawns hidden resource spots across configured regions of the world.
+Roleplay characters notice nearby spots over time, guided by their attributes and
+profession, and can then harvest them for configurable, weighted drops.
+
+## Features
+
+- **World spawning** — spots appear periodically in random chunks within configured
+ world bounds, filtered by biome, altitude, and surface block.
+- **Character discovery** — each RPCharacters character discovers spots separately,
+ with a chance improved by wisdom, intelligence, and an optional MMOCore profession
+ level.
+- **Visible only once found** — discovered spots are marked by a particle ring that
+ only that character can see.
+- **Weighted drops** — spot types roll weighted drop categories, and each category
+ rolls weighted TLibs item paths with configurable amounts.
+- **Chunk cooldowns** — a harvested chunk rests before it can host another spot, and
+ chunks with no valid surface are excluded from future spawning.
+- **Administrator tools** — reload configuration, inspect status, force a spawn,
+ clear excluded chunks, and use an admin mode that reveals every spot.
+- **Persistent state** — active spots, character discoveries, chunk cooldowns, and
+ excluded chunks survive server restarts.
+
+Originally created by [Drefvelin](https://github.com/Drefvelin).
+
+## Documentation
+
+[Project documentation](https://github.com/TF-Minecraft/Docs/blob/main/projects/Gathering/README.md)
+
+Technical documentation is maintained in [TF-Minecraft/Docs](https://github.com/TF-Minecraft/Docs).
+
+## License
+
+Copyright (c) 2026 TF-Minecraft contributors.
+
+TF-Minecraft-authored material in this repository is licensed under the
+[Artistic License 2.0](LICENSE). Third-party dependencies and pre-existing
+material retain their own licenses.
diff --git a/pom.xml b/pom.xml
index e0806e3..1334ba8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,82 +1,99 @@
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
net.tfminecraft
gathering
- 0.1.0-ALPHA
+ main-SNAPSHOT
+
+ Gathering
+ https://github.com/TF-Minecraft/Gathering
- 17
- 17
+ 21
+ UTF-8
+ full
+ 2.0.2
+ 2.0.1
- local
- gson
- 2.10.1
- system
- D:/Dokumenter/My Plugins/Reference Libs/gson-2.10.1.jar
+ io.papermc.paper
+ paper-api
+ 1.21.10-R0.1-SNAPSHOT
+ provided
local
- spigot-api
- 1.21.8
- system
- D:/Dokumenter/My Plugins/Reference Libs/spigot-api.jar
+ MMOCore
+ 1.13.1-tfmc-14850d745437
+ provided
local
- TLibs
- 1.0
- system
- D:/Dokumenter/My Plugins/TFMC/TLibs/TLibs.jar
+ MythicLib
+ 1.7-tfmc-660ff2a6ec86
+ provided
- local
- RPCharacters
- 1.1.5
- system
- D:/Dokumenter/My Plugins/TFMC/RPCharacters/rpcharacters-1.1.5.jar
+ me.plugins
+ tlibs
+ ${tlibs.version}
+ provided
+
+
+ *
+ *
+
+
- local
- MMOCore
- 1.13.1
- system
- D:/Dokumenter/My Plugins/Reference Libs/MMOCore-1.13.1.jar
-
-
- local
- MythicLib
- 1.7
- system
- D:/Dokumenter/My Plugins/Reference Libs/MythicLib-1.7.jar
+ net.tfminecraft
+ rpcharacters
+ ${rpcharacters.version}
+ provided
+
+
+ *
+ *
+
+
+
+
+ papermc
+ https://repo.papermc.io/repository/maven-public/
+
+
+
+
+
+ src/main/resources
+ true
+
+ plugin.yml
+
+
+
+ src/main/resources
+ false
+
+ plugin.yml
+
+
+
org.apache.maven.plugins
- maven-antrun-plugin
-
-
- package
- run
-
-
-
-
-
-
-
+ maven-compiler-plugin
+ 3.14.1
-
diff --git a/src/main/java/net/tfminecraft/gathering/discovery/SpotDiscoveryService.java b/src/main/java/net/tfminecraft/gathering/discovery/SpotDiscoveryService.java
index 808063b..5718d15 100644
--- a/src/main/java/net/tfminecraft/gathering/discovery/SpotDiscoveryService.java
+++ b/src/main/java/net/tfminecraft/gathering/discovery/SpotDiscoveryService.java
@@ -6,9 +6,9 @@
import org.bukkit.entity.Player;
-import me.Plugins.TLibs.Objects.API.SubAPI.StringFormatter;
-import net.tfminecraft.RPCharacters.Objects.Attributes.AttributeModifier;
-import net.tfminecraft.RPCharacters.Objects.RPCharacter;
+import net.tfminecraft.tlibs.objects.api.subapi.StringFormatter;
+import net.tfminecraft.rpcharacters.objects.attributes.AttributeModifier;
+import net.tfminecraft.rpcharacters.objects.RPCharacter;
import net.tfminecraft.gathering.cache.Cache;
import net.tfminecraft.gathering.loader.SpotTypeLoader;
import net.tfminecraft.gathering.manager.SpotManager;
diff --git a/src/main/java/net/tfminecraft/gathering/loader/SpotTypeLoader.java b/src/main/java/net/tfminecraft/gathering/loader/SpotTypeLoader.java
index 2df14d0..4095f30 100644
--- a/src/main/java/net/tfminecraft/gathering/loader/SpotTypeLoader.java
+++ b/src/main/java/net/tfminecraft/gathering/loader/SpotTypeLoader.java
@@ -12,7 +12,10 @@
import java.util.concurrent.ThreadLocalRandom;
+import io.papermc.paper.registry.RegistryAccess;
+import io.papermc.paper.registry.RegistryKey;
import org.bukkit.Material;
+import org.bukkit.NamespacedKey;
import org.bukkit.block.Biome;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.InvalidConfigurationException;
@@ -186,10 +189,12 @@ private Set parseBiomes(List names) {
Set out = new HashSet<>();
for (String name : names) {
if (name == null || name.isBlank()) continue;
- try {
- out.add(Biome.valueOf(name.trim().toUpperCase(Locale.ROOT)));
- } catch (IllegalArgumentException ex) {
+ NamespacedKey key = NamespacedKey.fromString(name.trim().toLowerCase(Locale.ROOT));
+ Biome biome = key == null ? null : RegistryAccess.registryAccess().getRegistry(RegistryKey.BIOME).get(key);
+ if (biome == null) {
Gathering.plugin.getLogger().warning("Unknown biome in spot-types: " + name);
+ } else {
+ out.add(biome);
}
}
return out;
diff --git a/src/main/java/net/tfminecraft/gathering/utils/CharacterBridge.java b/src/main/java/net/tfminecraft/gathering/utils/CharacterBridge.java
index d5e0317..e0f7174 100644
--- a/src/main/java/net/tfminecraft/gathering/utils/CharacterBridge.java
+++ b/src/main/java/net/tfminecraft/gathering/utils/CharacterBridge.java
@@ -4,9 +4,9 @@
import org.bukkit.entity.Player;
-import net.tfminecraft.RPCharacters.Managers.PlayerManager;
-import net.tfminecraft.RPCharacters.Objects.PlayerData;
-import net.tfminecraft.RPCharacters.Objects.RPCharacter;
+import net.tfminecraft.rpcharacters.managers.PlayerManager;
+import net.tfminecraft.rpcharacters.objects.PlayerData;
+import net.tfminecraft.rpcharacters.objects.RPCharacter;
public final class CharacterBridge {
diff --git a/src/main/java/net/tfminecraft/gathering/utils/ItemResolver.java b/src/main/java/net/tfminecraft/gathering/utils/ItemResolver.java
index 791d0e8..cf53278 100644
--- a/src/main/java/net/tfminecraft/gathering/utils/ItemResolver.java
+++ b/src/main/java/net/tfminecraft/gathering/utils/ItemResolver.java
@@ -3,7 +3,7 @@
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
-import me.Plugins.TLibs.TLibs;
+import net.tfminecraft.tlibs.TLibs;
public final class ItemResolver {
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index c6a380b..a627132 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -1,7 +1,7 @@
main: net.tfminecraft.gathering.Gathering
-api-version: 1.20
+api-version: '1.21.10'
name: Gathering
-version: 0.1.0-ALPHA
+version: '${project.version}'
author: Drefvelin
depend: [TLibs, RPCharacters]