diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml
new file mode 100644
index 0000000..4701c19
--- /dev/null
+++ b/.github/workflows/gradle.yml
@@ -0,0 +1,43 @@
+name: Test Gradle build script
+
+on:
+ push:
+ branches: [ "ver/*", "dev/*", "feat/**/*" ]
+ paths:
+ - "*.kt"
+ - "*.kts"
+ - "*.toml"
+ - "gradle-wrapper.*"
+ workflow_dispatch:
+
+jobs:
+ check:
+ name: Test Gradle build script
+ strategy:
+ matrix:
+ jdk: [21]
+ java: ['zulu']
+ os: [ubuntu-22.04]
+
+ if: "!contains(github.event.commits[0].message, '[CheckSkip]')"
+ runs-on: ${{ matrix.os }}
+ steps:
+ - name: Checkout action
+ uses: actions/checkout@v3
+
+ - name: Validate Gradle Wrapper
+ uses: gradle/wrapper-validation-action@v1
+
+ - name: Set up JDK ${{ matrix.java }} ${{ matrix.jdk }}
+ uses: actions/setup-java@v3
+ with:
+ distribution: ${{ matrix.java }}
+ java-version: ${{ matrix.jdk }}
+ cache: 'gradle'
+
+ - name: Configure Git
+ run: |
+ git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" && git config --global user.name "github-actions[bot]"
+
+ - name: Run Gradle
+ run: ./gradlew --stacktrace
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..e340f0f
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,102 @@
+name: Release Volt
+
+on:
+ push:
+ branches: [ "ver/*", "dev/*", "feat/**/*" ]
+ paths:
+ - "**.patch"
+ workflow_dispatch:
+
+env:
+ ORG_NAME: PlazmaMC
+ MC_VERSION: 1.20.4
+
+jobs:
+ release:
+ name: Release Volt
+ strategy:
+ matrix:
+ base_jdk: [17]
+ os: [ubuntu-22.04]
+
+ if: "!startsWith(github.event.commits[0].message, '[CI-Skip]')"
+ runs-on: ${{ matrix.os }}
+ steps:
+ - name: Setup Variables
+ id: setup
+ env:
+ BRANCH: ${{ github.ref_name }}
+ run: echo "branch=${BRANCH##*/}" >> $GITHUB_OUTPUT
+
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Validate Gradle Wrapper
+ uses: gradle/wrapper-validation-action@v1
+
+ - name: Set up GraalVM ${{ matrix.base_jdk }}
+ uses: graalvm/setup-graalvm@v1
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ java-version: ${{ matrix.base_jdk }}
+ version: latest
+ cache: 'gradle'
+
+ - name: Configure Git
+ run: git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" && git config --global user.name "github-actions[bot]"
+
+ - name: Apply Patches
+ run: ./gradlew applyPatches --stacktrace
+
+ - name: Build
+ run: ./gradlew build --stacktrace
+
+ - name: Create Reobf Jar
+ run: ./gradlew createReobfPaperclipJar --stacktrace
+
+ - name: Create Mojmap Jar
+ run: ./gradlew createMojmapPaperclipJar --stacktrace
+
+ - name: Publish Packages
+ if: startsWith(github.ref_name, 'ver/') || startsWith(github.ref_name, 'dev/')
+ run: |
+ export GITHUB_USERNAME=${{ env.ORG_NAME }}
+ export GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}
+ ./gradlew publish --stacktrace
+
+ - name: Get Release Number
+ if: startsWith(github.ref_name, 'ver/')
+ run: echo "RELEASE=$(git ls-remote --tags origin | grep "build/${{ env.MC_VERSION }}" | wc -l)" >> $GITHUB_ENV
+
+ - name: Release Artifacts
+ if: startsWith(github.ref_name, 'ver/')
+ uses: softprops/action-gh-release@v0.1.15
+ with:
+ name: "Build #${{ env.RELEASE }} for ${{ env.MC_VERSION }}"
+ tag_name: build/${{ env.MC_VERSION }}/${{ env.RELEASE }}
+ target_commitish: ${{ github.ref_name }}
+ generate_release_notes: true
+ fail_on_unmatched_files: true
+ files: build/libs/*.jar
+
+ - name: Release Artifacts (Latest/Stable)
+ if: startsWith(github.ref_name, 'ver/')
+ uses: softprops/action-gh-release@v0.1.15
+ with:
+ name: "Build #${{ env.RELEASE }} for ${{ env.MC_VERSION }}"
+ tag_name: build/${{ env.MC_VERSION }}/latest
+ target_commitish: ${{ github.ref_name }}
+ generate_release_notes: true
+ fail_on_unmatched_files: true
+ files: build/libs/*.jar
+
+ - name: Release Artifacts (Latest/Development)
+ if: startsWith(github.ref_name, 'dev/')
+ uses: softprops/action-gh-release@v0.1.15
+ with:
+ name: "Development Build for ${{ env.MC_VERSION }}"
+ tag_name: build/${{ env.MC_VERSION }}/latest
+ target_commitish: ${{ github.ref_name }}
+ generate_release_notes: true
+ fail_on_unmatched_files: true
+ files: build/libs/*.jar
diff --git a/.github/workflows/upstream.yml b/.github/workflows/upstream.yml
new file mode 100644
index 0000000..796507f
--- /dev/null
+++ b/.github/workflows/upstream.yml
@@ -0,0 +1,43 @@
+name: Always Up To Date
+on:
+ workflow_dispatch:
+ schedule:
+ - cron: "*/20 * * * *"
+jobs:
+ upstream:
+ name: Update Upstream
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Validate Gradle Wrapper
+ uses: gradle/wrapper-validation-action@v1
+
+ - name: Setup JDK
+ uses: actions/setup-java@v4
+ with:
+ distribution: zulu
+ java-version: 21
+
+ - name: Determine if update is required
+ id: check
+ run: echo "needs=$(./gradlew -q checkNeedsUpdate)" >> $GITHUB_OUTPUT
+
+ - name: Configure Git
+ if: steps.check.outputs.needs == 'true'
+ run: git config --global user.email "action@github.com" && git config --global user.name "Github Action"
+
+ - name: Update upstream
+ if: steps.check.outputs.needs == 'true'
+ run: ./gradlew updateUpstream
+
+ - name: Commit changes
+ if: steps.check.outputs.needs == 'true'
+ run: git add . && printf "Updated Upstream (Plazma)\n\nUpstream has released updates that appear to apply and compile correctly\n\n$(cat compare.txt)" | git commit -F -
+
+ - name: Push changes
+ if: steps.check.outputs.needs == 'true'
+ uses: ad-m/github-push-action@master
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..086664a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,61 @@
+# JVM crash related
+core.*
+hs_err_pid*
+
+# Intellij
+.idea/
+*.iml
+*.ipr
+*.iws
+out/
+
+# Eclipse
+.classpath
+.project
+.settings/
+
+# netbeans
+nbproject/
+nbactions.xml
+
+# Gradle
+!gradle-wrapper.jar
+.gradle/
+build/
+*/build/
+run/
+javadoc/
+
+# we use maven!
+build.xml
+
+# Maven
+log/
+target/
+dependency-reduced-pom.xml
+
+# various other potential build files
+bin/
+dist/
+manifest.mf
+
+# Mac
+.DS_Store/
+.DS_Store
+
+# vim
+.*.sw[a-p]
+
+# Linux temp files
+*~
+
+# Paperweight
+libs/
+build-data/
+*-API
+*-MojangAPI
+*-Server
+paper-api-generator
+compare.txt
+*.patch
+!patches/**/*
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000..6c40a95
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,23 @@
+The MIT License (MIT)
+=====================
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the “Software”), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..423c266
--- /dev/null
+++ b/README.md
@@ -0,0 +1,43 @@
+
+
+# Thunderbolt
+
+### A Server platform with features too experimental to implement into Plazma
+
+[![Discord](https://img.shields.io/discord/1083716853928558652?style=for-the-badge&logo=discord&logoColor=ffffff&label=DISCORD&color=5865F2)](https://discord.gg/MmfC52K8A8)
+[![License](https://img.shields.io/github/license/PlazmaMC/Thunderbolt?style=for-the-badge&logo=github&logoColor=ffffff)](LICENSE.md)
+[![Stargazers](https://img.shields.io/github/stars/PlazmaMC/Thunderbolt?label=stars&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABhWlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw0AcxV9bxSIVh1YQcYhQnSyIiuimVShChVArtOpgcukXNGlIUlwcBdeCgx+LVQcXZ10dXAVB8APE0clJ0UVK/F9SaBHjwXE/3t173L0D/PUyU82OMUDVLCOViAuZ7KrQ9YogwujDEGYkZupzopiE5/i6h4+vdzGe5X3uz9Gj5EwG+ATiWaYbFvEG8dSmpXPeJ46woqQQnxOPGnRB4keuyy6/cS447OeZESOdmieOEAuFNpbbmBUNlXiSOKqoGuX7My4rnLc4q+Uqa96TvzCU01aWuU5zEAksYgkiBMioooQyLMRo1UgxkaL9uId/wPGL5JLJVQIjxwIqUCE5fvA/+N2tmZ8Yd5NCcaDzxbY/hoGuXaBRs+3vY9tunACBZ+BKa/krdWD6k/RaS4seAb3bwMV1S5P3gMsdoP9JlwzJkQI0/fk88H5G35QFwrdA95rbW3Mfpw9AmrpK3gAHh8BIgbLXPd4dbO/t3zPN/n4Ax9dyyerighsAAAAGYktHRAAAAAAAAPlDu38AAAAJcEhZcwAADdcAAA3XAUIom3gAAAAHdElNRQfmCBMVNjtc7/hFAAABIElEQVQ4y62SzS5DURSFv6smXkAUCRU0UdKYGNTPyCsYYOYFGGi8Ao9QM0PxCh6CgQ4qfiLpBFEjdKCfySaXtDch1uScs9Ze62TvcyAD6o66zV+gjqpvalsd61XXl5GxBySx3/3t7UPqi1pTD9VXdaRbbZIyDQLTwBSwBqzGGaABnAInwCXQSJLk/tO4orb8jra6nwo/CC6NlrqMOq421Y5aVSfUXJe2cqFVo7b5NdwIuVaf1IWM2cyrD+qdOvlTLERIS53pYi6FdqMWet2wGP1tdNE2Q1vK+gfDsdbDlFfzwV3Ems8KmAXegcd4hSvgVq0Bz6GV0ob+HgF1YAA4Cn4LWA9tLusHnscTHavFFF8MrqOeZQVU1HKGXlYr/Cc+AKuOI2h/Jrf7AAAAAElFTkSuQmCC&style=for-the-badge&color=green)](https://github.com/PlazmaMC/Thunderbolt/stargazers)
+
+[![Upstream Status](https://img.shields.io/github/actions/workflow/status/PlazmaMC/Thunderbolt/upstream.yml?label=upstream&logo=data%3Aimage%2Fpng%3Bbase64%2CiVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAYAAADimHc4AAAACXBIWXMAAAsTAAALEwEAmpwYAAAGD0lEQVR4nO2dW4hVVRiAl5OjCFoMlpYx2WXMXjShi5FFTxFhhmFlQxlRU0kEXQiJorKIMoIiIoJegiiK8pKWZBTUg0ZpaHYhslBL8TKVTWWO00x98XP%2BicNhzt5rnb3P2WvtWd%2FjzDl7r%2FX%2F6%2FrfjjGRSCQSiUQikUgkEolEIpGIJwDHAHOA24CngTXA18Bu4BDwN3AUOAh8D3wOrAOeBLqBWUB70f0ICuBE4E7gXaCP7PwBrARuAqYU3T8vAdqB64EPgSGaxz%2F6joUyu8xoBzgOWAbsofXs0ndPNKMNoA24EThA8ezTPWZ0zAjgIt1IfWMbMM%2BUfJ1f3uQ1Po894rnSnZyAU3WEhcInwGmmDMi0BnozCGMA2KT3ADlKXgycAnQAY4HxwAnA6fouWc%2BfBzYCgxne%2BwtwgQkZ4Fq9KLlyBHgDWARMyPD%2BDr2QvamXNlf%2BAuabENGOu47AvcD9wOQmtOck4FG9NbsgfbjBhARwneNm26eCn9CCtk0EHtdZ5qKE%2BSEdM12WndVifiignbKPvOfQzsPA%2BSaA006vw%2Fq6pOD2jgHucRgw0rfpxkeAccBWBzPAHOMJMrIdBs4q4yNqArbhG2Ca8QxgBrDTov0yW8YaD9d9m013O3C88RRgmoUS%2Fm3FYcHVafKVhfB%2FAk42ngOcCfyc0I9txieApZYb7mwTCMBc4M8R%2BiGz%2FHLjC8CxlhebHhMYVDZmcXEO8wOwwPgEcJ%2BF8FebgAGmAp3GU%2FPyjxZLj5%2Fn5tBRH24aDxTdztICfGBhzp1UdDtLiYR3WJz7Hyy6naUFuCNF%2BGJ7n1p0O0uLRp8l8bYpIcAkYAXwKfAl8EzLg700pETCApPoNiUD6KpjppCTYFczXjimzt%2FPThH%2BULVHS00VfhmvGhO%2BeOvqsTc3JWgw7AagXx3i7wBnVf2%2FJ0UBn%2BnnZgLr9RniUfoYOM%2BUT%2Fj5KUGFP5IN5Dexj%2BhnniKZF9WWIt%2BppT8kJWAv%2FHyUoCM%2FyW8rgl2V0oiXU6KaPzLlFH42JejmKiM0CRHsfrIx6HscJo0LP7MSXCIFGkWU3GbKK%2FzGlaAZKM1mnSm%2F8BtTAnBGnc0zL%2BTZM8zoEH7DSpibU2rQiJu48RAqUXPNTBLZ4xT71AQleCt8AXid5vOacSFHJfgu%2FLYWHT4O17MuNFMJXgu%2FKqBMEjOy8Kve%2BNNMNO4mmQxK8F74wwBbEvoxoCmzafGtC1KUsMU0SgNKCEb4AnBZnVkwoIJ9KaW%2FK%2FQ59ZQgz77UZMFBCUEJfxjgSg09qY7iu1D%2FlxbzenPVc%2BapryD%2FcBYLJQQp%2FJoNuVOOpTXu17Q9YnadMMfO3G%2F9CZbO%2Fy2mZQJYYhF80FZE%2FORbmuS8T3OwvLzhZkX9IUmsyfySyMhIMLFF9MfSOl%2BPZAV4zCL6w9tw%2B6ChEnicFnywvuh2lhbgEdJZXHQ7SwkwXW03aXluQUd7eIlmTko0Rxp3G5%2BQ4hZVR9M%2B7cQsExhUyqWlccCrQk%2BaH3ywTr22YC5nwDkWgQnCLcYngFcTGisJbzON51AZRDJ709jqXVCBhTtvp4%2B5wTWVGr%2B1DKnxb0YDOywav8vHmUDFYPYddiw3PgK8YtmBXp9GEHCu5i%2FbsNnbUmZ6bk5Kbq5N9b%2FX2T%2Baf5t7LDdcNBrQv0zJaqS8l2ZF2vJ%2BERmUamCzOecPc8SnWZsIcIVjlax%2B4IlWJPNpoaaH9WhsixjbrjIBOjBcS5X1quUx95OSerQeaqBIrAj%2FahMiOhNclqPqTq%2FUHOTJGUf7Yr2Z267z1ch3FpmQ0T1B3HWNMqT1O18AbtdylV06osdrpn6Hlqu8RG6nwLOSoZOxXOX%2B4MtV1tiHRIihsNn7044rWlzV95LFg5p%2BOs6UFY2X8bF88RfBHDNziru5xqLKSiuQi%2BNdvqdLNQU9pSzT34JpNbu1VKU%2F9vyioJLEvVArr2SNUE5b4zdoRd%2FoRqyjjClaHV3O7r%2FnIHQJGV%2BrNe1i6IjjzGjXn6DqVlPFWq3ftkM9b0f10nZIfQ3bNX9ZkshvlVhN75wmkUgkEolEIpFIJBKJRCIRM5r5D9WH9bFOGhrhAAAAAElFTkSuQmCC&style=flat-square)](https://github.com/PlazmaMC/Thunderbolt/actions/workflows/upstream.yml)
+[![Forks](https://img.shields.io/github/forks/PlazmaMC/Thunderbolt?label=forks&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABhWlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw0AcxV9bxSIVh1YQcYhQnSyIiuimVShChVArtOpgcukXNGlIUlwcBdeCgx+LVQcXZ10dXAVB8APE0clJ0UVK/F9SaBHjwXE/3t173L0D/PUyU82OMUDVLCOViAuZ7KrQ9YogwujDEGYkZupzopiE5/i6h4+vdzGe5X3uz9Gj5EwG+ATiWaYbFvEG8dSmpXPeJ46woqQQnxOPGnRB4keuyy6/cS447OeZESOdmieOEAuFNpbbmBUNlXiSOKqoGuX7My4rnLc4q+Uqa96TvzCU01aWuU5zEAksYgkiBMioooQyLMRo1UgxkaL9uId/wPGL5JLJVQIjxwIqUCE5fvA/+N2tmZ8Yd5NCcaDzxbY/hoGuXaBRs+3vY9tunACBZ+BKa/krdWD6k/RaS4seAb3bwMV1S5P3gMsdoP9JlwzJkQI0/fk88H5G35QFwrdA95rbW3Mfpw9AmrpK3gAHh8BIgbLXPd4dbO/t3zPN/n4Ax9dyyerighsAAAAGYktHRAAAAAAAAPlDu38AAAAJcEhZcwAADdcAAA3XAUIom3gAAAAHdElNRQfmCBMVNCYN3/YeAAAA/UlEQVQ4y7WTQUoDQRBFf01czlJcxUyOINGjjAvFHMFzZGdygOwDwTtk6UZcqLlAxCAuMigug89FamIzdAIN+qGhq/6v6qrqbumvAJwBj8AHMAQs4DJgBHy65jSW4Bl4AaZsUAbcufumrnmquSzIcSzpTtLA7XbA1fuBa9qxCob8YgUUAdcFqoC/iSXIgLELOhG+49w4nM+2BTP7ljR3M4/MufbNzYxdN1E0Sm2ialZnsVIllZKOJF24eyLpXdKtmS1S3sYMmO3THOwJziUdbrbkZvaVcnILeAh6vweylAQ9D7z2BXCS0sJS0lrSpdtrSW+pn6sPLIFX4Er/hR9C0wl1FTBzNwAAAABJRU5ErkJggg==&style=flat-square&color=green)](https://github.com/PlazmaMC/Volt/forks)
+[![Watchers](https://img.shields.io/github/watchers/PlazmaMC/Thunderbolt?label=watchers&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABhWlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw0AcxV9bxSIVh1YQcYhQnSyIiuimVShChVArtOpgcukXNGlIUlwcBdeCgx+LVQcXZ10dXAVB8APE0clJ0UVK/F9SaBHjwXE/3t173L0D/PUyU82OMUDVLCOViAuZ7KrQ9YogwujDEGYkZupzopiE5/i6h4+vdzGe5X3uz9Gj5EwG+ATiWaYbFvEG8dSmpXPeJ46woqQQnxOPGnRB4keuyy6/cS447OeZESOdmieOEAuFNpbbmBUNlXiSOKqoGuX7My4rnLc4q+Uqa96TvzCU01aWuU5zEAksYgkiBMioooQyLMRo1UgxkaL9uId/wPGL5JLJVQIjxwIqUCE5fvA/+N2tmZ8Yd5NCcaDzxbY/hoGuXaBRs+3vY9tunACBZ+BKa/krdWD6k/RaS4seAb3bwMV1S5P3gMsdoP9JlwzJkQI0/fk88H5G35QFwrdA95rbW3Mfpw9AmrpK3gAHh8BIgbLXPd4dbO/t3zPN/n4Ax9dyyerighsAAAAGYktHRAAAAAAAAPlDu38AAAAJcEhZcwAADdcAAA3XAUIom3gAAAAHdElNRQfmCBMVNw4TRw0nAAAA3UlEQVQ4y83SP04CURAG8I0lewHOwAFUaiwkdmAlp8CL4FHopfIvtOIJWE3opIBK489mQPKy6xYWOskkL9/MN/PNzMuyf2fIcYkZVuGzwPI68gle8Yl7jMIfAntBp4o8wAeecFgSP8I8cgZp8DwC12j8oLCBCd7R34ItbHCzT8ZZSC7QTYrcYo1WhjGWaCbdCt+2SGLN4IwPfnu07QjrkhG6oWKB0+TMd7sRAuzHYuqWmO8tsVd1xjmOS8htPEfORVWHTmweHnEVPg2sqPxIicxhFFjhLd7D2q/8J/YFHSJt9VSqQ08AAAAASUVORK5CYII=&style=flat-square&color=green)](https://github.com/PlazmaMC/Thunderbolt/watchers)
+
+
+
+[main]: https://github.com/PlazmaMC/Thunderbolt
+
+> [!WARNING]
+Thunderbolt는 Plazma에 추가하기에 매우 불안정하고 게임 플레이에 크게 영향을 미치는 패치가 포함된 프로젝트입니다!
+기존 월드가 손상되거나, 일부 플러그인이 작동하지 않을 수 있으며, 매 업데이트마다 서버의 안정 상태가 크게 달라질 수 있습니다!
+Thunberbolt는 공개 서버에서 사용하기 매우 부적합하고, Plazma에 패치를 추가하기 전 실험용으로 사용되는 프로젝트입니다! 사용에 주의해주세요!
+Thunderbolt is a project that includes patches that are very unstable to add to Plazma and greatly affect gameplay!
+Existing worlds may be damaged, some plugins may not work, and the server's stable status can vary greatly with each update!
+Thunderbolt is very unfit for public servers, and it is an experimental project before adding patches to Plazma! Please be careful!
+
+> [!IMPORTANT]
+This is the FO experimental branch of Thunderbolt 1.20.6. If you are curious about other versions of Thunderbolt, please check **[main branch][main]**.
+이곳은 Thunderbolt 1.20.6의 FO 실험 분기입니다. Thunderbolt의 다른 버전이 궁금하다면 **[main 분기][main]** 를 확인해 주세요.
+
+## ⬇️ Downloads
+> [!NOTE]
+If you don't know about Mojmap or Bundler, download **Mojmap Paperclip**
+Mojmap 또는 Bundler에 대해 잘 알지 못한다면, **Mojmap Paperclip**을 사용하세요
+
+[paperReobf]: https://github.com/PlazmaMC/Thunderbolt/releases/download/build/fo/latest/thunderbolt-paperclip-1.20.6-R0.1-SNAPSHOT-reobf.jar
+[paperMojmap]: https://github.com/PlazmaMC/Thunderbolt/releases/download/build/fo/latest/thunderbolt-paperclip-1.20.6-R0.1-SNAPSHOT-mojmap.jar
+[bundlerReobf]: https://github.com/PlazmaMC/Thunderbolt/releases/download/build/fo/latest/thunderbolt-bundler-1.20.6-R0.1-SNAPSHOT-reobf.jar
+[bundlerMojmap]: https://github.com/PlazmaMC/Thunderbolt/releases/download/build/fo/latest/thunderbolt-bundler-1.20.6-R0.1-SNAPSHOT-mojmap.jar
+
+| **Reobf Paperclip (Default)** | Mojmap Paperclip | Reobf Bundler | Mojmap Bundler |
+|:-----------------------------:|:-----------------------:|:------------------------:|:-------------------------:|
+| [Download][paperReobf] | [Download][paperMojmap] | [Download][bundlerReobf] | [Download][bundlerMojmap] |
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 0000000..f861843
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,233 @@
+import io.papermc.paperweight.util.*
+import org.gradle.api.tasks.testing.logging.TestExceptionFormat
+import org.gradle.api.tasks.testing.logging.TestLogEvent
+
+plugins {
+ java
+ `kotlin-dsl`
+ `maven-publish`
+ id("com.github.johnrengelman.shadow") version "8.1.1" apply false
+ id("io.papermc.paperweight.patcher") version "1.6.3"
+}
+
+repositories {
+ mavenCentral()
+ maven("https://papermc.io/repo/repository/maven-public/") {
+ content {
+ onlyForConfigurations(configurations.paperclip.name)
+ }
+ }
+}
+
+val jdkVersion = property("jdkVersion").toString().toInt()
+val projectName = property("projectName").toString()
+val projectRepo = property("projectRepo").toString()
+val upstreamRef = property("plazmaRef").toString()
+val upstreamCommitValue = property("plazmaCommit").toString()
+
+kotlin.jvmToolchain(jdkVersion)
+
+dependencies {
+ remapper("net.fabricmc:tiny-remapper:0.8.10:fat")
+ decompiler("org.vineflower:vineflower:1.10.1")
+ paperclip("io.papermc:paperclip:3.0.3")
+}
+
+allprojects {
+ apply(plugin = "java")
+ apply(plugin = "maven-publish")
+
+ java.toolchain.languageVersion.set(JavaLanguageVersion.of(jdkVersion))
+
+ publishing {
+ repositories {
+ maven {
+ name = "githubPackage"
+ url = uri("https://maven.pkg.github.com/$projectRepo") // Volt - Change this
+
+ credentials {
+ username = System.getenv("GITHUB_USERNAME")
+ password = System.getenv("GITHUB_TOKEN")
+ }
+ }
+
+ publications.register("gpr") {
+ from(components["java"])
+ }
+ }
+ }
+}
+
+subprojects {
+ tasks {
+ withType().configureEach {
+ options.compilerArgs.addAll(listOf("--add-modules=jdk.incubator.vector", "-Xmaxwarns", "1"))
+ options.encoding = Charsets.UTF_8.name()
+ options.release = jdkVersion
+ }
+
+ withType {
+ options.encoding = Charsets.UTF_8.name()
+ }
+
+ withType {
+ filteringCharset = Charsets.UTF_8.name()
+ }
+
+ withType {
+ testLogging {
+ showStackTraces = true
+ exceptionFormat = TestExceptionFormat.FULL
+ events(TestLogEvent.STANDARD_OUT)
+ }
+ }
+ }
+
+ repositories {
+ mavenCentral()
+ maven("https://s01.oss.sonatype.org/content/repositories/snapshots/")
+ maven("https://papermc.io/repo/repository/maven-public/")
+ maven("https://jitpack.io")
+ }
+}
+
+paperweight {
+ serverProject = project(":${projectName.lowercase()}-server")
+
+ remapRepo = "https://papermc.io/repo/repository/maven-public/"
+ decompileRepo = "https://papermc.io/repo/repository/maven-public/"
+
+ useStandardUpstream("plazma") {
+ url = github("PlazmaMC", "PlazmaBukkit")
+ ref = providers.gradleProperty("plazmaCommit")
+
+ withStandardPatcher {
+ baseName("Plazma")
+
+ apiPatchDir = layout.projectDirectory.dir("patches/api")
+ apiOutputDir = layout.projectDirectory.dir("$projectName-API")
+
+ serverPatchDir = layout.projectDirectory.dir("patches/server")
+ serverOutputDir = layout.projectDirectory.dir("$projectName-Server")
+ }
+
+ patchTasks.register("mojangApi") {
+ isBareDirectory = true
+ upstreamDirPath = "Plazma-MojangAPI"
+ patchDir = layout.projectDirectory.dir("patches/mojang-api")
+ outputDir = layout.projectDirectory.dir("$projectName-MojangAPI")
+ }
+
+ patchTasks.register("generatedApi") {
+ isBareDirectory = true
+ upstreamDirPath = "paper-api-generator/generated"
+ patchDir = layout.projectDirectory.dir("patches/generated-api")
+ outputDir = layout.projectDirectory.dir("paper-api-generator/generated")
+ }
+
+ patchTasks.register("versionCatalogs") {
+ isBareDirectory = true
+ upstreamDirPath = "libs"
+ patchDir = layout.projectDirectory.dir("patches/version-catalog")
+ outputDir = layout.projectDirectory.dir("libs")
+ }
+ }
+}
+
+tasks {
+ applyPatches {
+ dependsOn("applyVersionCatalogsPatches")
+ dependsOn("applyGeneratedApiPatches")
+ }
+
+ rebuildPatches {
+ dependsOn("rebuildVersionCatalogsPatches")
+ dependsOn("rebuildGeneratedApiPatches")
+ }
+
+ generateDevelopmentBundle {
+ apiCoordinates = "$group:$projectName-api"
+ mojangApiCoordinates = "$group:$projectName-mojangapi"
+ libraryRepositories.set(
+ listOf(
+ "https://repo.maven.apache.org/maven2/",
+ "https://maven.pkg.github.com/$projectRepo",
+ "https://papermc.io/repo/repository/maven-public/"
+ )
+ )
+ }
+
+ register("checkNeedsUpdate") {
+ var latest: String = ""
+
+ doFirst {
+ val commit = layout.cache.resolve("commit.json")
+ download.get().download("https://api.github.com/repos/PlazmaMC/PlazmaBukkit/commits/$upstreamRef", commit)
+ latest = gson.fromJson(commit)["sha"].asString
+ }
+
+ doLast {
+ println(latest != property("plazmaCommit"))
+ }
+ }
+
+ register("updateUpstream") {
+ val tempDir = layout.cacheDir("updateUpstream")
+ val file = "gradle.properties"
+ val builder = StringBuilder()
+
+ doFirst {
+ val commit = layout.cache.resolve("commit.json")
+ download.get().download("https://api.github.com/repos/PlazmaMC/PlazmaBukkit/commits/$upstreamRef", commit)
+ val latestCommit = gson.fromJson(commit)["sha"].asString
+
+ val compare = layout.cache.resolve("compare.json")
+ download.get().download("https://api.github.com/repos/PlazmaMC/PlazmaBukkit/compare/$upstreamCommitValue...$upstreamRef", compare)
+ gson.fromJson(compare)["commits"].asJsonArray.forEach {
+ builder.append("PlazmaMC/PlazmaBukkit@${it.asJsonObject["sha"].asString.subSequence(0, 7)}: ${it.asJsonObject["commit"].asJsonObject["message"].asString.split("\n")[0]}\n")
+ }
+
+ copy {
+ from(file)
+ into(tempDir)
+ filter {
+ it.replace("plazmaCommit = .*".toRegex(), "plazmaCommit = $latestCommit")
+ }
+ }
+
+ project.setProperty("plazmaCommit", latestCommit)
+ }
+
+ doLast {
+ copy {
+ from(tempDir.file("gradle.properties"))
+ into(project.file(file).parent)
+ }
+ project.file("compare.txt").writeText(builder.toString())
+ }
+
+ finalizedBy(applyPatches)
+ }
+
+ clean {
+ doLast {
+ projectDir.resolve(".gradle/caches").deleteRecursively()
+ listOf("$projectName-API", "$projectName-MojangAPI", "$projectName-Server", "paper-api-generator", "run").forEach {
+ projectDir.resolve(it).deleteRecursively()
+ }
+
+ // remove dev environment files
+ listOf("0001-fixup.patch", "compare.txt").forEach {
+ projectDir.resolve(it).delete()
+ }
+ }
+ }
+}
+
+publishing {
+ publications.create("devBundle") {
+ artifact(tasks.generateDevelopmentBundle) {
+ artifactId = "dev-bundle"
+ }
+ }
+}
diff --git a/fxapi b/fxapi
new file mode 100755
index 0000000..d25667b
--- /dev/null
+++ b/fxapi
@@ -0,0 +1,14 @@
+#!/usr/bin/env bash
+
+# FixupAPI
+
+PS1="$"
+
+cd *-API
+
+git add .
+git commit -m "fixup"
+git format-patch -1
+mv 0001-fixup.patch ../
+
+cd ../
diff --git a/fxsrv b/fxsrv
new file mode 100755
index 0000000..85f8772
--- /dev/null
+++ b/fxsrv
@@ -0,0 +1,14 @@
+#!/usr/bin/env bash
+
+# FixupServer
+
+PS1="$"
+
+cd *-Server
+
+git add .
+git commit -m "fixup"
+git format-patch -1
+mv 0001-fixup.patch ../
+
+cd ../
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..b194212
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,23 @@
+org.gradle.deamon = true
+org.gradle.caching = true
+org.gradle.parallel = true
+org.gradle.vfs.watch = false
+org.gradle.jvmargs = -Xmx4G -Dfile.encoding=UTF-8 -Dgraal.CompilerConfiguration=community -Dgraal.UsePriorityInlining=true -Dgraal.Vectorization=true -Dgraal.OptDuplication=true -Dgraal.SpeculativeGuardMovement=true -Dgraal.WriteableCodeCache=true
+
+# Volt - for Debugging
+paper.runMemoryGb = 8
+paper.runDisableWatchdog = true
+
+# Volt - Change below values
+group = org.plazmamc.thunderbolt
+projectName = Thunderbolt
+projectRepo = PlazmaMC/Thunderbolt
+
+# Volt - You must change below values when you upgrade the Minecraft.
+version = 1.20.6-R0.1-SNAPSHOT
+mcVersion = 1.20.6
+plazmaRef = dev/1.20.6
+jdkVersion = 21
+
+# Volt - Use ./gradlew updateUpstream to update this value
+plazmaCommit = 069250da5981752ace0db45a789ccd38b64475cc
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..e644113
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..b82aa23
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,7 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..1aa94a4
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,249 @@
+#!/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.
+#
+
+##############################################################################
+#
+# 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/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# 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 "${APP_HOME:-./}" > /dev/null && pwd -P ) || 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=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# 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, 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" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# 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" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..7101f8e
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,92 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/patches/api/0001-Configuration-API.patch b/patches/api/0001-Configuration-API.patch
new file mode 100644
index 0000000..df295ad
--- /dev/null
+++ b/patches/api/0001-Configuration-API.patch
@@ -0,0 +1,24 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: AlphaKR93
+Date: Sun, 12 Jun 2022 10:57:51 -0500
+Subject: [PATCH] Configuration API
+
+
+diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java
+index f1b39968788f90ffc785b9dd434b2a3671553a14..90eb3884f5283fa6c52ff2267570c466b2fdd463 100644
+--- a/src/main/java/org/bukkit/Server.java
++++ b/src/main/java/org/bukkit/Server.java
+@@ -2276,6 +2276,13 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
+ }
+ // Plazma end
+
++ // Thunderbolt - Configuration API
++ @NotNull
++ public org.bukkit.configuration.file.YamlConfiguration getThunderboltConfig() {
++ throw new UnsupportedOperationException("Not implemented yet.");
++ }
++ // Thunderbolt end
++
+ /**
+ * Sends the component to the player
+ *
diff --git a/patches/server/0001-Rebranding.patch b/patches/server/0001-Rebranding.patch
new file mode 100644
index 0000000..ba74e03
--- /dev/null
+++ b/patches/server/0001-Rebranding.patch
@@ -0,0 +1,65 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: AlphaKR93
+Date: Thu, 9 May 2024 12:47:04 +0900
+Subject: [PATCH] Rebranding
+
+
+diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java
+index d2215f5293c568b4ae525f4a63996bac6edf787b..fcd474949f1a5b4108d635e659bfe0a0ba551583 100644
+--- a/src/main/java/net/minecraft/server/Main.java
++++ b/src/main/java/net/minecraft/server/Main.java
+@@ -117,15 +117,19 @@ public class Main {
+ // \033[38;2;236;61;151m┃\033[38;2;236;61;157m \033[38;2;237;62;163m┃\033[38;2;237;62;169m \033[38;2;238;62;175m \033[38;2;238;63;181m \033[38;2;239;63;187m \033[38;2;239;63;193m \033[38;2;239;64;200m┃\033[38;2;240;64;206m \033[38;2;240;64;212m┗\033[38;2;241;65;218m━\033[38;2;241;65;224m━\033[38;2;242;65;230m━\033[38;2;242;66;236m━\033[38;2;242;66;242m┓\033[38;2;237;66;243m┃\033[38;2;232;67;243m \033[38;2;227;67;244m┃\033[38;2;221;67;244m \033[38;2;216;68;244m \033[38;2;211;68;245m┃\033[38;2;205;69;245m \033[38;2;200;69;246m┃\033[38;2;195;69;246m┏\033[38;2;189;70;246m┛\033[38;2;184;70;247m \033[38;2;179;70;247m \033[38;2;173;71;247m┗\033[38;2;168;71;248m━\033[38;2;163;72;248m━\033[38;2;157;72;249m┓\033[38;2;152;72;249m┃\033[38;2;147;73;249m \033[38;2;141;73;250m┃\033[38;2;136;74;250m \033[38;2;131;74;250m┗\033[38;2;125;74;251m━\033[38;2;120;75;251m┛\033[38;2;115;75;251m \033[38;2;109;76;252m┃\033[38;2;104;76;252m \033[38;2;99;77;252m┃\033[38;2;94;77;253m┃\033[38;2;88;77;253m \033[38;2;83;78;253m┃\033[38;2;78;79;254m \033[38;2;79;85;254m \033[38;2;79;91;254m┃\033[38;2;80;97;255m \033[38;2;80;103;255m┃
+ // \033[38;2;236;61;151m┗\033[38;2;236;61;157m━\033[38;2;237;62;163m┛\033[38;2;237;62;169m \033[38;2;238;62;175m \033[38;2;238;63;181m \033[38;2;239;63;187m \033[38;2;239;63;193m \033[38;2;239;64;200m┗\033[38;2;240;64;206m━\033[38;2;240;64;212m━\033[38;2;241;65;218m━\033[38;2;241;65;224m━\033[38;2;242;65;230m━\033[38;2;242;66;236m━\033[38;2;242;66;242m┛\033[38;2;237;66;243m┗\033[38;2;232;67;243m━\033[38;2;227;67;244m┛\033[38;2;221;67;244m \033[38;2;216;68;244m \033[38;2;211;68;245m┗\033[38;2;205;69;245m━\033[38;2;200;69;246m┛\033[38;2;195;69;246m┗\033[38;2;189;70;246m━\033[38;2;184;70;247m━\033[38;2;179;70;247m━\033[38;2;173;71;247m━\033[38;2;168;71;248m━\033[38;2;163;72;248m━\033[38;2;157;72;249m┛\033[38;2;152;72;249m┗\033[38;2;147;73;249m━\033[38;2;141;73;250m┛\033[38;2;136;74;250m \033[38;2;131;74;250m \033[38;2;125;74;251m \033[38;2;120;75;251m \033[38;2;115;75;251m \033[38;2;109;76;252m┗\033[38;2;104;76;252m━\033[38;2;99;77;252m┛\033[38;2;94;77;253m┗\033[38;2;88;77;253m━\033[38;2;83;78;253m┛\033[38;2;78;79;254m \033[38;2;79;85;254m \033[38;2;79;91;254m┗\033[38;2;80;97;255m━\033[38;2;80;103;255m┛\033[0m
+ // """);
++ // Thunderbolt start - Branding
+ System.out.println("""
+-
+- \033[38;2;215;23;133m█\033[38;2;212;24;134m█\033[38;2;209;26;134m█\033[38;2;205;27;135m█\033[38;2;202;28;136m█\033[38;2;199;30;136m█\033[38;2;196;31;137m╗\033[38;2;193;33;138m \033[38;2;190;34;138m█\033[38;2;186;35;139m█\033[38;2;183;37;140m╗\033[38;2;180;38;140m \033[38;2;177;39;141m \033[38;2;174;41;142m \033[38;2;170;42;142m \033[38;2;167;43;143m \033[38;2;164;45;144m \033[38;2;161;46;144m█\033[38;2;158;47;145m█\033[38;2;155;49;146m█\033[38;2;151;50;146m█\033[38;2;148;52;147m█\033[38;2;145;53;148m╗\033[38;2;142;54;148m \033[38;2;139;56;149m█\033[38;2;135;57;149m█\033[38;2;132;58;150m█\033[38;2;129;60;151m█\033[38;2;126;61;151m█\033[38;2;123;62;152m█\033[38;2;120;64;153m█\033[38;2;116;65;153m╗ \033[38;2;113;67;154m█\033[38;2;110;68;155m█\033[38;2;107;69;155m█\033[38;2;104;71;156m╗\033[38;2;101;72;157m \033[38;2;97;73;157m \033[38;2;94;75;158m \033[38;2;91;76;159m█\033[38;2;88;77;159m█\033[38;2;85;79;160m█\033[38;2;81;80;161m╗ \033[38;2;78;81;161m \033[38;2;75;83;162m█\033[38;2;72;84;163m█\033[38;2;69;86;163m█\033[38;2;66;87;164m█\033[38;2;62;88;165m█\033[38;2;59;90;165m╗\033[38;2;56;91;166m\s
+- \033[38;2;215;23;133m█\033[38;2;212;24;134m█\033[38;2;209;26;134m╔\033[38;2;205;27;135m═\033[38;2;202;28;136m═\033[38;2;199;30;136m█\033[38;2;196;31;137m█\033[38;2;193;33;138m╗ \033[38;2;190;34;138m█\033[38;2;186;35;139m█\033[38;2;183;37;140m║\033[38;2;180;38;140m \033[38;2;177;39;141m \033[38;2;174;41;142m \033[38;2;170;42;142m \033[38;2;167;43;143m \033[38;2;164;45;144m█\033[38;2;161;46;144m█\033[38;2;158;47;145m╔\033[38;2;155;49;146m═\033[38;2;151;50;146m═\033[38;2;148;52;147m█\033[38;2;145;53;148m█\033[38;2;142;54;148m╗ \033[38;2;139;56;149m╚\033[38;2;135;57;149m═\033[38;2;132;58;150m═\033[38;2;129;60;151m█\033[38;2;126;61;151m█\033[38;2;123;62;152m█\033[38;2;120;64;153m╔\033[38;2;116;65;153m╝ \033[38;2;113;67;154m█\033[38;2;110;68;155m█\033[38;2;107;69;155m█\033[38;2;104;71;156m█\033[38;2;101;72;157m╗\033[38;2;97;73;157m \033[38;2;94;75;158m█\033[38;2;91;76;159m█\033[38;2;88;77;159m█\033[38;2;85;79;160m█\033[38;2;81;80;161m║ \033[38;2;78;81;161m█\033[38;2;75;83;162m█\033[38;2;72;84;163m╔\033[38;2;69;86;163m═\033[38;2;66;87;164m═\033[38;2;62;88;165m█\033[38;2;59;90;165m█\033[38;2;56;91;166m╗
+- \033[38;2;215;23;133m█\033[38;2;212;24;134m█\033[38;2;209;26;134m█\033[38;2;205;27;135m█\033[38;2;202;28;136m█\033[38;2;199;30;136m█\033[38;2;196;31;137m╔\033[38;2;193;33;138m╝ \033[38;2;190;34;138m█\033[38;2;186;35;139m█\033[38;2;183;37;140m║\033[38;2;180;38;140m \033[38;2;177;39;141m \033[38;2;174;41;142m \033[38;2;170;42;142m \033[38;2;167;43;143m \033[38;2;164;45;144m█\033[38;2;161;46;144m█\033[38;2;158;47;145m█\033[38;2;155;49;146m█\033[38;2;151;50;146m█\033[38;2;148;52;147m█\033[38;2;145;53;148m█\033[38;2;142;54;148m║ \033[38;2;139;56;149m \033[38;2;135;57;149m \033[38;2;132;58;150m█\033[38;2;129;60;151m█\033[38;2;126;61;151m█\033[38;2;123;62;152m╔\033[38;2;120;64;153m╝\033[38;2;116;65;153m \033[38;2;113;67;154m█\033[38;2;110;68;155m█\033[38;2;107;69;155m╔\033[38;2;104;71;156m█\033[38;2;101;72;157m█\033[38;2;97;73;157m█\033[38;2;94;75;158m█\033[38;2;91;76;159m╔\033[38;2;88;77;159m█\033[38;2;85;79;160m█\033[38;2;81;80;161m║ \033[38;2;78;81;161m█\033[38;2;75;83;162m█\033[38;2;72;84;163m█\033[38;2;69;86;163m█\033[38;2;66;87;164m█\033[38;2;62;88;165m█\033[38;2;59;90;165m█\033[38;2;56;91;166m║
+- \033[38;2;215;23;133m█\033[38;2;212;24;134m█\033[38;2;209;26;134m╔\033[38;2;205;27;135m═\033[38;2;202;28;136m═\033[38;2;199;30;136m═\033[38;2;196;31;137m╝\033[38;2;193;33;138m \033[38;2;190;34;138m█\033[38;2;186;35;139m█\033[38;2;183;37;140m║\033[38;2;180;38;140m \033[38;2;177;39;141m \033[38;2;174;41;142m \033[38;2;170;42;142m \033[38;2;167;43;143m \033[38;2;164;45;144m█\033[38;2;161;46;144m█\033[38;2;158;47;145m╔\033[38;2;155;49;146m═\033[38;2;151;50;146m═\033[38;2;148;52;147m█\033[38;2;145;53;148m█\033[38;2;142;54;148m║ \033[38;2;139;56;149m \033[38;2;135;57;149m█\033[38;2;132;58;150m█\033[38;2;129;60;151m█\033[38;2;126;61;151m╔\033[38;2;123;62;152m╝\033[38;2;120;64;153m \033[38;2;116;65;153m \033[38;2;113;67;154m█\033[38;2;110;68;155m█\033[38;2;107;69;155m║\033[38;2;104;71;156m╚\033[38;2;101;72;157m█\033[38;2;97;73;157m█\033[38;2;94;75;158m╔\033[38;2;91;76;159m╝\033[38;2;88;77;159m█\033[38;2;85;79;160m█\033[38;2;81;80;161m║ \033[38;2;78;81;161m█\033[38;2;75;83;162m█\033[38;2;72;84;163m╔\033[38;2;69;86;163m═\033[38;2;66;87;164m═\033[38;2;62;88;165m█\033[38;2;59;90;165m█\033[38;2;56;91;166m║
+- \033[38;2;215;23;133m█\033[38;2;212;24;134m█\033[38;2;209;26;134m║\033[38;2;205;27;135m \033[38;2;202;28;136m \033[38;2;199;30;136m \033[38;2;196;31;137m \033[38;2;193;33;138m \033[38;2;190;34;138m█\033[38;2;186;35;139m█\033[38;2;183;37;140m█\033[38;2;180;38;140m█\033[38;2;177;39;141m█\033[38;2;174;41;142m█\033[38;2;170;42;142m█\033[38;2;167;43;143m╗ \033[38;2;164;45;144m█\033[38;2;161;46;144m█\033[38;2;158;47;145m║\033[38;2;155;49;146m \033[38;2;151;50;146m \033[38;2;148;52;147m█\033[38;2;145;53;148m█\033[38;2;142;54;148m║ \033[38;2;139;56;149m█\033[38;2;135;57;149m█\033[38;2;132;58;150m█\033[38;2;129;60;151m█\033[38;2;126;61;151m█\033[38;2;123;62;152m█\033[38;2;120;64;153m█\033[38;2;116;65;153m╗ \033[38;2;113;67;154m█\033[38;2;110;68;155m█\033[38;2;107;69;155m║\033[38;2;104;71;156m \033[38;2;101;72;157m╚\033[38;2;97;73;157m═\033[38;2;94;75;158m╝\033[38;2;91;76;159m \033[38;2;88;77;159m█\033[38;2;85;79;160m█\033[38;2;81;80;161m║ \033[38;2;78;81;161m█\033[38;2;75;83;162m█\033[38;2;72;84;163m║\033[38;2;69;86;163m \033[38;2;66;87;164m \033[38;2;62;88;165m█\033[38;2;59;90;165m█\033[38;2;56;91;166m║
+- \033[38;2;215;23;133m╚\033[38;2;212;24;134m═\033[38;2;209;26;134m╝\033[38;2;205;27;135m \033[38;2;202;28;136m \033[38;2;199;30;136m \033[38;2;196;31;137m \033[38;2;193;33;138m \033[38;2;190;34;138m╚\033[38;2;186;35;139m═\033[38;2;183;37;140m═\033[38;2;180;38;140m═\033[38;2;177;39;141m═\033[38;2;174;41;142m═\033[38;2;170;42;142m═\033[38;2;167;43;143m╝ \033[38;2;164;45;144m╚\033[38;2;161;46;144m═\033[38;2;158;47;145m╝\033[38;2;155;49;146m \033[38;2;151;50;146m \033[38;2;148;52;147m╚\033[38;2;145;53;148m═\033[38;2;142;54;148m╝ \033[38;2;139;56;149m╚\033[38;2;135;57;149m═\033[38;2;132;58;150m═\033[38;2;129;60;151m═\033[38;2;126;61;151m═\033[38;2;123;62;152m═\033[38;2;120;64;153m═\033[38;2;116;65;153m╝ \033[38;2;113;67;154m╚\033[38;2;110;68;155m═\033[38;2;107;69;155m╝\033[38;2;104;71;156m \033[38;2;101;72;157m \033[38;2;97;73;157m \033[38;2;94;75;158m \033[38;2;91;76;159m \033[38;2;88;77;159m╚\033[38;2;85;79;160m═\033[38;2;81;80;161m╝ \033[38;2;78;81;161m╚\033[38;2;75;83;162m═\033[38;2;72;84;163m╝\033[38;2;69;86;163m \033[38;2;66;87;164m \033[38;2;62;88;165m╚\033[38;2;59;90;165m═\033[38;2;56;91;166m╝\033[0m
+- """);
++ \033[95m
++ ********** ** ** ** ** ** \s
++ /////**/// /** /** /** /** /** \s
++ /** /** ** ** ******* /** ***** ****** /** ****** /** ******
++ /** /****** /** /** //**///** ****** **///** //**//* /****** **////** /** ///**/\s
++ /** /**///** /** /** /** /** **///** /******* /** / /**///** /** /** /** /** \s
++ /** /** /** /** /** /** /** /** /** /**//// /** /** /** /** /** /** /** \s
++ /** /** /** //****** *** /** //****** //****** /*** /****** //****** *** //**\s
++ // // // ////// /// // ////// ////// /// ///// ////// /// // \s\033[0m
++ """);
++ // Thunderbolt end - Branding
+ // Plazma end
+ // Plazma start - Warn on startup
+ if (!org.plazmamc.plazma.Options.NO_WARN) {
+diff --git a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java
+index 2612df0680800e8c4644599b1885230778c812b7..c87390df0aa77d7d1399096a62c109aab6736d82 100644
+--- a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java
++++ b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java
+@@ -11,16 +11,18 @@ import org.bukkit.Bukkit;
+ public final class Versioning {
+ // Plazma start - Rebrand; Branding utils
+ private static String version = "Unknown-Version";
+- public static final String BRANDING = "Plazma";
++ // Thunderbolt start - Rebranding
++ public static final String BRANDING = "Thunderbolt";
+ public static final String BRAND_L = BRANDING.toLowerCase(Locale.ROOT);
+ public static final String BRAND_U = BRANDING.toUpperCase(Locale.ROOT);
+- public static final String REPOSITORY = "PlazmaMC/PlazmaBukkit";
+- public static final String DOWNLOAD = "https://plazmamc.org/downloads";
++ public static final String REPOSITORY = "PlazmaMC/Thunderbolt";
++ public static final String DOWNLOAD = "https://github.com/PlazmaMC/Thunderbolt/releases"; // TODO: Move to docs
++ // Thunderbolt end - Rebranding
+ public static final String DOCUMENT = "https://docs.plazmamc.org/plazma/administration/getting-started";
+ public static final String STEP_UP = "https://docs.plazmamc.org/plazma/administration/getting-started/next-step";
+ public static final boolean DEVELOPMENT = true; // Plazma - Development build
+ static {
+- InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/org.plazmamc.plazma/plazma-api/pom.properties");
++ InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/org.plazmamc.thunderbolt/thunderbolt-api/pom.properties"); // Thunderbolt - Rebranding
+ Properties properties = new Properties();
+
+ if (stream != null) {
diff --git a/patches/server/0002-Configurable-Thunderbolt.patch b/patches/server/0002-Configurable-Thunderbolt.patch
new file mode 100644
index 0000000..bb6a8ae
--- /dev/null
+++ b/patches/server/0002-Configurable-Thunderbolt.patch
@@ -0,0 +1,749 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Alpha
+Date: Sun, 25 Feb 2024 19:13:15 +0900
+Subject: [PATCH] Configurable Thunderbolt
+
+
+diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
+index 4635d67ed906886f533b6d5911d99a163810ea11..b179aaad657396771213c8b02e29ee6f61faedb0 100644
+--- a/src/main/java/net/minecraft/server/MinecraftServer.java
++++ b/src/main/java/net/minecraft/server/MinecraftServer.java
+@@ -318,6 +318,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop minecraftserver.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(convertable_conversionsession.levelDirectory.path(), iworlddataserver.getLevelName(), resourcekey.location(), spigotConfig, minecraftserver.registryAccess(), iworlddataserver.getGameRules())),
+ spigotConfig -> minecraftserver.plazmaConfigurations.createWorldConfig(org.plazmamc.plazma.configurations.PlazmaConfigurations.createWorldContextMap(convertable_conversionsession.levelDirectory.path(), iworlddataserver.getLevelName(), resourcekey.location(), spigotConfig, minecraftserver.registryAccess(), iworlddataserver.getGameRules())),
++ spigotConfig -> minecraftserver.thunderboltConfigurations.createWorldConfig(org.plazmamc.thunderbolt.configurations.ThunderboltConfigurations.createWorldContextMap(convertable_conversionsession.levelDirectory.path(), iworlddataserver.getLevelName(), resourcekey.location(), spigotConfig, minecraftserver.registryAccess(), iworlddataserver.getGameRules())), // Thunderbolt - Configurable Thunderbolt
+ executor
+ );
+ // Plazma end - Configurable Plazma
+diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
+index 47fcbff8a0b7feedbc700b6c3cf44d3a02a60246..b7e58a6b11e648999a97add335cc13e27dcf6861 100644
+--- a/src/main/java/net/minecraft/world/level/Level.java
++++ b/src/main/java/net/minecraft/world/level/Level.java
+@@ -174,6 +174,12 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+ return this.plazmaConfig;
+ }
+ // Plazma end - Configurable Plazma
++ // Thunderbolt start - Configurable Thunderbolt
++ private final org.plazmamc.thunderbolt.configurations.WorldConfigurations thunderboltConfig;
++ public org.plazmamc.thunderbolt.configurations.WorldConfigurations thunderboltConfig() {
++ return this.thunderboltConfig;
++ }
++ // Thunderbolt end - Configurable Thunderbolt
+
+ public final com.destroystokyo.paper.antixray.ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray
+ public final org.purpurmc.purpur.PurpurWorldConfig purpurConfig; // Purpur
+@@ -279,12 +285,14 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+ org.bukkit.World.Environment env,
+ java.util.function.Function paperWorldConfigCreator,
+ java.util.function.Function plazmaWorldConfigurationCreator,
++ java.util.function.Function thunderboltWorldConfigurationCreator, // Thunderbolt - Configurable Thunderbolt
+ java.util.concurrent.Executor executor
+ ) {
+ // Plazma end - Configurable Plazma
+ this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot
+ this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper - create paper world config
+ this.plazmaConfig = plazmaWorldConfigurationCreator.apply(this.spigotConfig); // Plazma - Configurable Plazma
++ this.thunderboltConfig = thunderboltWorldConfigurationCreator.apply(this.spigotConfig); // Thunderbolt - Configurable Thunderbolt
+ this.purpurConfig = new org.purpurmc.purpur.PurpurWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName(), env); // Purpur
+ this.playerBreedingCooldowns = this.getNewBreedingCooldownCache(); // Purpur
+ this.generator = gen;
+diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+index d53be3e57e97cf9806b04f6d6bdd6a3a9fdb299b..4f5290801f9bdd3f9c300bd445f089a63e1ad998 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+@@ -1069,6 +1069,7 @@ public final class CraftServer implements Server {
+ org.spigotmc.SpigotConfig.init((File) this.console.options.valueOf("spigot-settings")); // Spigot
+ this.console.paperConfigurations.reloadConfigs(this.console);
+ this.console.plazmaConfigurations.reloadConfigs(this.console); // Plazma - Configurable Plazma
++ this.console.thunderboltConfigurations.reloadConfigs(this.console); // Thunderbolt - Configurable Thunderbolt
+ org.purpurmc.purpur.PurpurConfig.init((File) console.options.valueOf("purpur-settings")); // Purpur
+ for (ServerLevel world : this.console.getAllLevels()) {
+ // world.serverLevelData.setDifficulty(config.difficulty); // Paper - per level difficulty
+@@ -3134,6 +3135,13 @@ public final class CraftServer implements Server {
+ }
+ // Plazma end - Configurable Plazma
+
++ // Thunderbolt start - Configurable Thunderbolt
++ @Override @org.jetbrains.annotations.NotNull
++ public YamlConfiguration getThunderboltConfig() {
++ return CraftServer.this.console.thunderboltConfigurations.createLegacyObject(CraftServer.this.console);
++ }
++ // Thunderbolt end - Configurable Thunderbolt
++
+ @Override
+ public void restart() {
+ org.spigotmc.RestartCommand.restart();
+diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java
+index 40fd40da9a7fc2b12035b99960e7f38c78eb2a5e..c6f35002b8ef7bbd0e490b7bcaae4c6e5d3832aa 100644
+--- a/src/main/java/org/bukkit/craftbukkit/Main.java
++++ b/src/main/java/org/bukkit/craftbukkit/Main.java
+@@ -198,6 +198,14 @@ public class Main {
+ .defaultsTo(new File(org.plazmamc.plazma.configurations.PlazmaConfigurations.CONFIG_DIR))
+ .describedAs("Configuration Directory");
+ // Plazma end - Configurable Plazma
++
++ // Thunderbolt start - Configurable Thunderbolt
++ acceptsAll(asList("thunderbolt-dir", "thunderbolt-settings-directory"), "Directory for Thunderbolt settings")
++ .withRequiredArg()
++ .ofType(File.class)
++ .defaultsTo(new File(org.plazmamc.thunderbolt.configurations.ThunderboltConfigurations.CONFIG_DIR))
++ .describedAs("Configuration Directory");
++ // Thunderbolt end - Configurable Thunderbolt
+ }
+ };
+
+diff --git a/src/main/java/org/plazmamc/thunderbolt/commands/Commands.java b/src/main/java/org/plazmamc/thunderbolt/commands/Commands.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..a943c5ffd470f7919855a434900504cce477160b
+--- /dev/null
++++ b/src/main/java/org/plazmamc/thunderbolt/commands/Commands.java
+@@ -0,0 +1,22 @@
++package org.plazmamc.thunderbolt.commands;
++
++import net.minecraft.server.MinecraftServer;
++import org.bukkit.command.Command;
++import org.checkerframework.framework.qual.DefaultQualifier;
++import org.jetbrains.annotations.NotNull;
++
++import java.util.HashMap;
++import java.util.Map;
++
++@DefaultQualifier(NotNull.class)
++public class Commands {
++
++ private static final Map COMMANDS = new HashMap<>() {{
++ put("thunderbolt", new org.plazmamc.thunderbolt.commands.thunderbolt.ThunderboltCommand("thunderbolt"));
++ }};
++
++ public static void register(final MinecraftServer server) {
++ COMMANDS.forEach((name, command) -> server.server.getCommandMap().register(name, "Thunderbolt", command));
++ }
++
++}
+diff --git a/src/main/java/org/plazmamc/thunderbolt/commands/ThunderboltSubCommand.java b/src/main/java/org/plazmamc/thunderbolt/commands/ThunderboltSubCommand.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..bf5fa777a4d1187e7f86708d892a24f2a4f738ef
+--- /dev/null
++++ b/src/main/java/org/plazmamc/thunderbolt/commands/ThunderboltSubCommand.java
+@@ -0,0 +1,19 @@
++package org.plazmamc.thunderbolt.commands;
++
++import org.bukkit.command.CommandSender;
++import org.checkerframework.framework.qual.DefaultQualifier;
++import org.jetbrains.annotations.NotNull;
++
++import java.util.Collections;
++import java.util.List;
++
++@DefaultQualifier(NotNull.class)
++public interface ThunderboltSubCommand {
++
++ boolean execute(final CommandSender sender, final String subCommand, final String[] args);
++
++ default List tabComplete(final CommandSender sender, final String subCommand, final String[] args) {
++ return Collections.emptyList();
++ }
++
++}
+diff --git a/src/main/java/org/plazmamc/thunderbolt/commands/thunderbolt/ThunderboltCommand.java b/src/main/java/org/plazmamc/thunderbolt/commands/thunderbolt/ThunderboltCommand.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..d366faa50f12ecd0cd9167c6a746ef715ddcb303
+--- /dev/null
++++ b/src/main/java/org/plazmamc/thunderbolt/commands/thunderbolt/ThunderboltCommand.java
+@@ -0,0 +1,118 @@
++package org.plazmamc.thunderbolt.commands.thunderbolt;
++
++import io.papermc.paper.command.CommandUtil;
++import net.kyori.adventure.text.format.NamedTextColor;
++import it.unimi.dsi.fastutil.Pair;
++import net.minecraft.Util;
++import org.bukkit.Bukkit;
++import org.bukkit.command.Command;
++import org.bukkit.command.CommandSender;
++import org.bukkit.permissions.Permission;
++import org.bukkit.permissions.PermissionDefault;
++import org.checkerframework.framework.qual.DefaultQualifier;
++import org.jetbrains.annotations.NotNull;
++import org.jetbrains.annotations.Nullable;
++import org.plazmamc.thunderbolt.commands.thunderbolt.subcommand.ReloadCommand;
++import org.plazmamc.thunderbolt.commands.thunderbolt.subcommand.VersionCommand;
++
++import java.util.*;
++import java.util.stream.Collectors;
++
++import static net.kyori.adventure.text.Component.text;
++
++@DefaultQualifier(NotNull.class)
++public class ThunderboltCommand extends Command {
++
++ private static final String PERMISSION = "bukkit.command.thunderbolt";
++
++ private static final Map SUB_COMMANDS = Util.make(() -> {
++ final Map, org.plazmamc.thunderbolt.commands.ThunderboltSubCommand> commands = new HashMap<>() {{
++ put(Set.of("reload"), new ReloadCommand());
++ put(Set.of("version"), new VersionCommand());
++ }};
++
++ return commands.entrySet().stream()
++ .flatMap(entry -> entry.getKey().stream().map(key -> Map.entry(key, entry.getValue())))
++ .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
++ });
++
++ private static final Map ALIASES = Util.make(() -> {
++ final Map> aliases = new HashMap<>() {{
++ put("reload", Set.of("rl"));
++ put("version", Set.of("ver"));
++ }};
++
++ return aliases.entrySet().stream()
++ .flatMap(entry -> entry.getValue().stream().map(val -> Map.entry(val, entry.getKey())))
++ .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
++ });
++
++ public ThunderboltCommand(final String name) {
++ super(name);
++
++ final List permissions = new ArrayList<>() {{
++ add(PERMISSION);
++ addAll(SUB_COMMANDS.keySet().stream().map(key -> PERMISSION + "." + key).toList());
++ }};
++
++ this.description = "Thunderbolt related commands";
++ this.usageMessage = String.format("/thunderbolt [%s]", String.join("|", SUB_COMMANDS.keySet()));
++ this.setPermission(String.join(";", permissions));
++
++ permissions.forEach(perm -> Bukkit.getServer().getPluginManager().addPermission(new Permission(perm, PermissionDefault.OP)));
++ }
++
++ @Override
++ public boolean execute(final CommandSender sender, final String commandLabel, final String[] args) {
++ if (!testPermission(sender)) return true;
++
++ if (args.length == 0) {
++ sender.sendMessage(text("Usage: " + this.usageMessage, NamedTextColor.RED));
++ return false;
++ }
++
++ final @Nullable Pair subCommand = resolveSubCommand(args[0]);
++
++ if (subCommand == null) {
++ sender.sendMessage(text("Usage: " + this.usageMessage, NamedTextColor.RED));
++ return false;
++ }
++
++ if (!testPermission(sender, subCommand.first())) return true;
++
++ return subCommand.second().execute(sender, subCommand.first(), Arrays.copyOfRange(args, 1, args.length));
++ }
++
++ @Override
++ public List tabComplete(final CommandSender sender, final String aliases, final String[] args) throws IllegalArgumentException {
++ if (args.length <= 1) return CommandUtil.getListMatchingLast(sender, args, SUB_COMMANDS.keySet());
++
++ final @Nullable Pair subCommand = resolveSubCommand(args[0]);
++
++ if (subCommand != null) return subCommand.second().tabComplete(sender, subCommand.first(), Arrays.copyOfRange(args, 1, args.length));
++ return Collections.emptyList();
++ }
++
++ private static boolean testPermission(final CommandSender sender, final String permission) {
++ if (sender.hasPermission(PERMISSION + "." + permission) || sender.hasPermission(PERMISSION)) return true;
++ sender.sendMessage(Bukkit.permissionMessage());
++ return false;
++ }
++
++ private static @Nullable Pair resolveSubCommand(String label) {
++ label = label.toLowerCase();
++ @Nullable org.plazmamc.thunderbolt.commands.ThunderboltSubCommand subCommand = SUB_COMMANDS.get(label);
++
++ if (subCommand == null) {
++ final @Nullable String command = ALIASES.get(label);
++ if (command != null) {
++ label = command;
++ subCommand = SUB_COMMANDS.get(label);
++ }
++ }
++
++ if (subCommand != null) return Pair.of(label, subCommand);
++ return null;
++ }
++
++}
+diff --git a/src/main/java/org/plazmamc/thunderbolt/commands/thunderbolt/subcommand/ReloadCommand.java b/src/main/java/org/plazmamc/thunderbolt/commands/thunderbolt/subcommand/ReloadCommand.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..9afee425a7cf37bc0a61097b3f9833ffe0c881c9
+--- /dev/null
++++ b/src/main/java/org/plazmamc/thunderbolt/commands/thunderbolt/subcommand/ReloadCommand.java
+@@ -0,0 +1,34 @@
++package org.plazmamc.thunderbolt.commands.thunderbolt.subcommand;
++
++import net.kyori.adventure.text.format.NamedTextColor;
++import net.minecraft.server.MinecraftServer;
++import org.bukkit.command.Command;
++import org.bukkit.command.CommandSender;
++import org.bukkit.craftbukkit.CraftServer;
++import org.checkerframework.framework.qual.DefaultQualifier;
++import org.jetbrains.annotations.NotNull;
++import org.plazmamc.thunderbolt.commands.ThunderboltSubCommand;
++
++import static net.kyori.adventure.text.Component.text;
++
++@DefaultQualifier(NotNull.class)
++public class ReloadCommand implements ThunderboltSubCommand {
++
++ @Override
++ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) {
++ reload(sender);
++ return true;
++ }
++
++ private static void reload(final CommandSender sender) {
++ Command.broadcastCommandMessage(sender, text("Please note that this command is not supported and may cause issues.", NamedTextColor.RED));
++ Command.broadcastCommandMessage(sender, text("If you encounter any issues please use the /stop command to restart your server.", NamedTextColor.RED));
++
++ MinecraftServer server = ((CraftServer) sender.getServer()).getServer();
++ server.thunderboltConfigurations.reloadConfigs(server);
++ server.server.reloadCount++;
++
++ Command.broadcastCommandMessage(sender, text("Successfully reloaded Thunderbolt configuration files.", NamedTextColor.GREEN));
++ }
++
++}
+diff --git a/src/main/java/org/plazmamc/thunderbolt/commands/thunderbolt/subcommand/VersionCommand.java b/src/main/java/org/plazmamc/thunderbolt/commands/thunderbolt/subcommand/VersionCommand.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..b859430a39546cded05032110163d67671cd346e
+--- /dev/null
++++ b/src/main/java/org/plazmamc/thunderbolt/commands/thunderbolt/subcommand/VersionCommand.java
+@@ -0,0 +1,21 @@
++package org.plazmamc.thunderbolt.commands.thunderbolt.subcommand;
++
++import net.minecraft.server.MinecraftServer;
++import org.bukkit.command.Command;
++import org.bukkit.command.CommandSender;
++import org.checkerframework.framework.qual.DefaultQualifier;
++import org.jetbrains.annotations.NotNull;
++import org.jetbrains.annotations.Nullable;
++import org.plazmamc.thunderbolt.commands.ThunderboltSubCommand;
++
++@DefaultQualifier(NotNull.class)
++public class VersionCommand implements ThunderboltSubCommand {
++
++ @Override
++ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) {
++ final @Nullable Command ver = MinecraftServer.getServer().server.getCommandMap().getCommand("version");
++ if (ver != null) return ver.execute(sender, "thunderbolt", new String[0]);
++ return false;
++ }
++
++}
+diff --git a/src/main/java/org/plazmamc/thunderbolt/configurations/GlobalConfiguration.java b/src/main/java/org/plazmamc/thunderbolt/configurations/GlobalConfiguration.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..fbac06d6c70fd6250f16a6bdbea21873ff5e1d2a
+--- /dev/null
++++ b/src/main/java/org/plazmamc/thunderbolt/configurations/GlobalConfiguration.java
+@@ -0,0 +1,28 @@
++package org.plazmamc.thunderbolt.configurations;
++
++import io.papermc.paper.configuration.Configuration;
++import io.papermc.paper.configuration.ConfigurationPart;
++import org.jetbrains.annotations.NotNull;
++import org.spongepowered.configurate.objectmapping.meta.Setting;
++import org.spongepowered.configurate.objectmapping.meta.PostProcess;
++
++import static org.plazmamc.plazma.configurations.PlazmaConfigurations.optimize;
++
++@SuppressWarnings({"CanBeFinal", "FieldCanBeLocal", "FieldMayBeFinal", "InnerClassMayBeStatic"})
++public class GlobalConfiguration extends ConfigurationPart {
++
++ private static GlobalConfiguration INSTANCE;
++ static final int VERSION = 1;
++
++ public static GlobalConfiguration get() {
++ return INSTANCE;
++ }
++
++ static void set(final @NotNull GlobalConfiguration instance) {
++ GlobalConfiguration.INSTANCE = instance;
++ }
++
++ @Setting(Configuration.VERSION_FIELD)
++ int version = VERSION;
++
++}
+diff --git a/src/main/java/org/plazmamc/thunderbolt/configurations/RemovedConfigurations.java b/src/main/java/org/plazmamc/thunderbolt/configurations/RemovedConfigurations.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..1d4afff273484b08d340e7b8d80e031325a76945
+--- /dev/null
++++ b/src/main/java/org/plazmamc/thunderbolt/configurations/RemovedConfigurations.java
+@@ -0,0 +1,13 @@
++package org.plazmamc.thunderbolt.configurations;
++
++import org.spongepowered.configurate.NodePath;
++
++interface RemovedConfigurations {
++
++ NodePath[] WORLD_PATHS = {
++ };
++
++ NodePath[] GLOBAL_PATHS = {
++ };
++
++}
+diff --git a/src/main/java/org/plazmamc/thunderbolt/configurations/ThunderboltConfigurations.java b/src/main/java/org/plazmamc/thunderbolt/configurations/ThunderboltConfigurations.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..2b6653148b6a3586373efffa7a74caf2dd5e560c
+--- /dev/null
++++ b/src/main/java/org/plazmamc/thunderbolt/configurations/ThunderboltConfigurations.java
+@@ -0,0 +1,209 @@
++package org.plazmamc.thunderbolt.configurations;
++
++import com.mojang.logging.LogUtils;
++import io.papermc.paper.configuration.ConfigurationPart;
++import io.papermc.paper.configuration.Configurations;
++import joptsimple.OptionSet;
++import net.minecraft.core.RegistryAccess;
++import net.minecraft.server.level.ServerLevel;
++import org.checkerframework.framework.qual.DefaultQualifier;
++import org.jetbrains.annotations.NotNull;
++import org.jetbrains.annotations.VisibleForTesting;
++import org.slf4j.Logger;
++import org.spongepowered.configurate.ConfigurationOptions;
++import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
++import org.spongepowered.configurate.ConfigurateException;
++import org.spongepowered.configurate.BasicConfigurationNode;
++import org.spongepowered.configurate.ConfigurationNode;
++import org.spongepowered.configurate.objectmapping.ObjectMapper;
++import org.spongepowered.configurate.NodePath;
++
++import java.io.File;
++import java.util.function.Function;
++
++import static io.leangen.geantyref.GenericTypeReflector.erase;
++
++@DefaultQualifier(NotNull.class)
++public class ThunderboltConfigurations extends Configurations {
++
++ public static final String CONFIG_DIR = "config";
++ static final Logger LOGGER = LogUtils.getClassLogger();
++ static final String GLOBAL_CONFIG_FILE_NAME = "thudnerbolt_global.yaml";
++ static final String WORLD_DEFAULT_CONFIG_FILE_NAME = "thunderbolt_world_defaults.yaml";
++ static final String WORLD_CONFIG_FILE_NAME = "thunderbolt.yaml";
++ static boolean GLOBAL_LOADED = false;
++
++ private static final String HEADER_START = """
++ ### ENGLISH ###
++ Volt is a template project that helps you fork your Plazma easily.
++ This software is not suitable for use as a regular server. If you
++ are not a fork developer, and you just want to run a server,
++ please use Plazma.
++
++ %s
++
++ ### KOREAN ###
++ Volt는 Plazma를 쉽게 포크할 수 있도록 도와주는 템플릿 프로젝트입니다.
++ 이 소프트웨어는 일반 서버로 사용하기 적합하지 않습니다. 당신이 포크
++ 개발자가 아니고, 단지 서버를 운영하고 싶다면, Plazma를 사용해 주십시오.
++
++ %s
++
++ GitHub: https://github.com/PlazmaMC/Volt
++ Development Guide: https://github.com/PlazmaMC/Volt/wiki
++
++ Download Plazma: https://github.com/PlazmaMC/PlazmaBukkit?tab=readme-ov-file#️-downloads
++ Discord: https://plazmamc.org/discord
++ """;
++
++ private static final String GLOBAL_HEADER = HEADER_START.formatted("""
++ Below are several template values set in GlobalConfiguration.java.
++ Refer to the class for more information on how the configuration applies.
++ These are values that apply to the **entire server** regardless of
++ the world. However, you must take advantage of the system properties
++ in order for the server to be able to modify the values before
++ loading the configuration. See org.plazmamc.thunderbolt.Options class for
++ development of system properties.
++ """, """
++ 아래에는 GlobalConfiguration.java에 설정된 여러 템플릿 값들이 있습니다.
++ 구성이 적용되는 여러 방법은 해당 클래스를 참고하세요.
++ 이 값들은 월드에 관계 없이 **서버 전체**에 적용되는 값들입니다. 다만, 서버가 구성을
++ 로드하기 전의 값을 수정할 수 있게 만드려면, 시스템 속성을 이용해야 합니다.
++ 시스템 속성에 대한 개발은 org.plazmamc.thunderbolt.Options 클래스를 참고하세요.
++ """
++ );
++
++ private static final String WORLD_DEFAULT_HEADER = HEADER_START.formatted("""
++ Below are several template values that are set in WorldConfigurations.java.
++ Refer to the class for more information on how the configuration applies.
++ These are values that apply to the **entire game (world)** regardless
++ of the world. However, these world configurations can be set to
++ override values that apply only to that world through the
++ %WORLD_CONFIG_FILE_NAME% file in the world folder.
++ """, """
++ 아래에는 WorldConfigurations.java에 설정된 여러 템플릿 값들이 있습니다.
++ 구성이 적용되는 여러 방법은 해당 클래스를 참고하세요.
++ 이 값들은 월드에 관계 없이 **게임(월드) 전체**에 적용되는 값들입니다. 다만,
++ 이러한 월드 구성은 월드 폴더 내에 있는 %WORLD_CONFIG_FILE_NAME% 파일을 통해
++ 해당 월드에만 적용되는 덮어쓰기 값을 설정할 수 있습니다.
++ """
++ ).replace("%WORLD_CONFIG_FILE_NAME%", WORLD_CONFIG_FILE_NAME);
++
++ private static final Function WORLD_HEADER = map -> """
++ ### ENGLISH ###
++ This file is a Volt configuration file for %s (%s) world only.
++ This file is empty in its initial state, but you can populate the
++ values set in the %s file to override them.
++
++ ### KOREAN ###
++ 본 파일은 %s (%s) 월드 전용 Volt 구성 파일입니다.
++ 이 파일은 초기 상태에는 비어있지만, %s 파일에 설정된 값을 덮어쓰기 위해 값을 채워
++ 넣을 수 있습니다.
++ """.formatted(
++ map.require(WORLD_NAME), map.require(WORLD_KEY), WORLD_CONFIG_FILE_NAME,
++ map.require(WORLD_NAME), map.require(WORLD_KEY), WORLD_CONFIG_FILE_NAME
++ );
++
++ public ThunderboltConfigurations(final OptionSet optionSet) {
++ super(((File) optionSet.valueOf("thunderbolt-settings-directory")).toPath());
++ }
++
++ @Override
++ protected Class globalConfigClass() {
++ return GlobalConfiguration.class;
++ }
++
++ @Override
++ protected String globalConfigFileName() {
++ return GLOBAL_CONFIG_FILE_NAME;
++ }
++
++ @Override
++ protected NodePath[] removedGlobalPaths() {
++ return RemovedConfigurations.GLOBAL_PATHS;
++ }
++
++ @Override
++ protected GlobalConfiguration getGlobalConfiguration() {
++ return GlobalConfiguration.get();
++ }
++
++ @Override
++ protected int globalConfigVersion() {
++ return GlobalConfiguration.VERSION;
++ }
++
++ @Override
++ protected Class worldConfigClass() {
++ return WorldConfigurations.class;
++ }
++
++ @Override
++ protected String defaultWorldConfigFileName() {
++ return WORLD_DEFAULT_CONFIG_FILE_NAME;
++ }
++
++ @Override
++ protected String worldConfigFileName() {
++ return WORLD_CONFIG_FILE_NAME;
++ }
++
++ @Override
++ protected String worldDefaultHeader() {
++ return WORLD_DEFAULT_HEADER;
++ }
++
++ @Override
++ protected NodePath[] removedWorldPaths() {
++ return RemovedConfigurations.WORLD_PATHS;
++ }
++
++ @Override
++ protected WorldConfigurations getWorldConfiguration(ServerLevel level) {
++ throw new UnsupportedOperationException("TODO");
++ }
++
++ @Override
++ protected int worldConfigVersion() {
++ return WorldConfigurations.VERSION;
++ }
++
++ @Override
++ protected String buildWorldHeader(ContextMap contextMap) {
++ return WORLD_HEADER.apply(contextMap);
++ }
++
++ @Override
++ protected YamlConfigurationLoader.Builder createGlobalLoaderBuilder() {
++ return super.createGlobalLoaderBuilder()
++ .defaultOptions(ThunderboltConfigurations::defaultGlobalOptions);
++ }
++
++ @Override
++ public GlobalConfiguration initializeGlobalConfiguration(final RegistryAccess registryAccess) throws ConfigurateException {
++ GlobalConfiguration configuration = super.initializeGlobalConfiguration(registryAccess);
++ GlobalConfiguration.set(configuration);
++ GLOBAL_LOADED = true;
++ return configuration;
++ }
++
++ @Override
++ protected WorldConfigurations createWorldConfigInstance(ContextMap contextMap) {
++ return new WorldConfigurations(contextMap.require(WORLD_KEY));
++ }
++
++ private static ConfigurationOptions defaultGlobalOptions(
++ final ConfigurationOptions options
++ ) {
++ return options.header(GLOBAL_HEADER);
++ }
++
++ @VisibleForTesting
++ static ConfigurationNode createForTesting() {
++ ObjectMapper.Factory factory = defaultGlobalFactoryBuilder(ObjectMapper.factoryBuilder()).build();
++ ConfigurationOptions options = defaultGlobalOptions(defaultOptions(ConfigurationOptions.defaults()))
++ .serializers(builder -> builder.register(type -> ConfigurationPart.class.isAssignableFrom(erase(type)), factory.asTypeSerializer()));
++ return BasicConfigurationNode.root(options);
++ }
++
++}
+diff --git a/src/main/java/org/plazmamc/thunderbolt/configurations/WorldConfigurations.java b/src/main/java/org/plazmamc/thunderbolt/configurations/WorldConfigurations.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..4a8bff4b6e42c3fa49758520e743dc65fe79e2cc
+--- /dev/null
++++ b/src/main/java/org/plazmamc/thunderbolt/configurations/WorldConfigurations.java
+@@ -0,0 +1,23 @@
++package org.plazmamc.thunderbolt.configurations;
++
++import io.papermc.paper.configuration.Configuration;
++import io.papermc.paper.configuration.ConfigurationPart;
++import org.jetbrains.annotations.NotNull;
++import net.minecraft.resources.ResourceLocation;
++import org.spongepowered.configurate.objectmapping.meta.PostProcess;
++import org.spongepowered.configurate.objectmapping.meta.Setting;
++
++@SuppressWarnings({"CanBeFinal", "FieldCanBeLocal", "FieldMayBeFinal", "InnerClassMayBeStatic"})
++public class WorldConfigurations extends ConfigurationPart {
++
++ static final int VERSION = 1;
++
++ private transient final ResourceLocation worldKey;
++ public WorldConfigurations(final @NotNull ResourceLocation worldKey) {
++ this.worldKey = worldKey;
++ }
++
++ @Setting(Configuration.VERSION_FIELD)
++ int version = VERSION;
++
++}
+diff --git a/src/test/java/org/bukkit/support/AbstractTestingBase.java b/src/test/java/org/bukkit/support/AbstractTestingBase.java
+index 74af3501397bfd89d637ad5ce72bb8f2c2bbdc8a..93767e76b6ea52da8587f282dfe18774e9dad22f 100644
+--- a/src/test/java/org/bukkit/support/AbstractTestingBase.java
++++ b/src/test/java/org/bukkit/support/AbstractTestingBase.java
+@@ -65,6 +65,7 @@ public abstract class AbstractTestingBase {
+ DummyServer.setup();
+ io.papermc.paper.configuration.GlobalConfigTestingBase.setupGlobalConfigForTest(); // Paper
+ org.plazmamc.plazma.configurations.GlobalConfigurationTestingBase.setupGlobalConfigForTest(); // Plazma - Configurable Plazma
++ org.plazmamc.thunderbolt.configurations.GlobalConfigurationTestingBase.setupGlobalConfigurationForTesting(); // Thunderbolt - Configurable Thunderbolt
+
+ CraftRegistry.setMinecraftRegistry(REGISTRY_CUSTOM);
+
+diff --git a/src/test/java/org/plazmamc/thunderbolt/configurations/GlobalConfigurationTestingBase.java b/src/test/java/org/plazmamc/thunderbolt/configurations/GlobalConfigurationTestingBase.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..682089f155c75b0c106c3516f053fde7787b400c
+--- /dev/null
++++ b/src/test/java/org/plazmamc/thunderbolt/configurations/GlobalConfigurationTestingBase.java
+@@ -0,0 +1,17 @@
++package org.plazmamc.thunderbolt.configurations;
++
++import org.spongepowered.configurate.ConfigurationNode;
++
++public class GlobalConfigurationTestingBase {
++
++ public static void setupGlobalConfigurationForTesting() {
++ if (GlobalConfiguration.get() != null) return;
++ ConfigurationNode node = ThunderboltConfigurations.createForTesting();
++ try {
++ GlobalConfiguration.set(node.require(GlobalConfiguration.class));
++ } catch (Exception e) {
++ throw new RuntimeException(e);
++ }
++ }
++
++}
diff --git a/patches/server/0003-Add-basic-configuration-parts.patch b/patches/server/0003-Add-basic-configuration-parts.patch
new file mode 100644
index 0000000..3310950
--- /dev/null
+++ b/patches/server/0003-Add-basic-configuration-parts.patch
@@ -0,0 +1,31 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: AlphaKR93
+Date: Fri, 10 May 2024 12:50:20 +0900
+Subject: [PATCH] Add basic configuration parts
+
+
+diff --git a/src/main/java/org/plazmamc/thunderbolt/configurations/GlobalConfiguration.java b/src/main/java/org/plazmamc/thunderbolt/configurations/GlobalConfiguration.java
+index fbac06d6c70fd6250f16a6bdbea21873ff5e1d2a..0b16bbf87a6c5e658e8182e423800acd16dfc7ca 100644
+--- a/src/main/java/org/plazmamc/thunderbolt/configurations/GlobalConfiguration.java
++++ b/src/main/java/org/plazmamc/thunderbolt/configurations/GlobalConfiguration.java
+@@ -25,4 +25,20 @@ public class GlobalConfiguration extends ConfigurationPart {
+ @Setting(Configuration.VERSION_FIELD)
+ int version = VERSION;
+
++ public NeedsReload needsReload;
++ public class NeedsReload extends ConfigurationPart {
++
++
++ @PostProcess
++ public void post() {
++ if (ThunderboltConfigurations.GLOBAL_LOADED) {
++ ThunderboltConfigurations.LOGGER.warn("Configuration settings that require a restart have been changed."
++ + "The settings will take effect at the next restart.");
++ return;
++ }
++
++ }
++
++ }
++
+ }
diff --git a/patches/server/0004-Implement-Noisium.patch b/patches/server/0004-Implement-Noisium.patch
new file mode 100644
index 0000000..f580810
--- /dev/null
+++ b/patches/server/0004-Implement-Noisium.patch
@@ -0,0 +1,243 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: AlphaKR93
+Date: Fri, 10 May 2024 11:31:37 +0900
+Subject: [PATCH] Implement Noisium
+
+
+diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
+index a2a5aef769ee8bb638a5a9f3da9812fa4a85dda5..39960b9d4099cd22bd695a06355f0045a35049a1 100644
+--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
+@@ -19,8 +19,8 @@ public class LevelChunkSection {
+ public static final int SECTION_HEIGHT = 16;
+ public static final int SECTION_SIZE = 4096;
+ public static final int BIOME_CONTAINER_BITS = 2;
+- short nonEmptyBlockCount; // Paper - package private
+- private short tickingBlockCount;
++ public short nonEmptyBlockCount; // Paper - package private // Thunderbolt - Implement noisium; why?
++ public short tickingBlockCount; // Thunderbolt - AT
+ private short tickingFluidCount;
+ public final PalettedContainer states;
+ // CraftBukkit start - read/write
+@@ -226,17 +226,13 @@ public class LevelChunkSection {
+ // CraftBukkit end
+
+ public void fillBiomesFromNoise(BiomeResolver biomeSupplier, Climate.Sampler sampler, int x, int y, int z) {
+- PalettedContainer> datapaletteblock = this.biomes.recreate();
+- boolean flag = true;
++ // Thunderbolt start - Implement noisium; micro optimization
++ PalettedContainer> palette = this.biomes.recreate();
+
+- for (int l = 0; l < 4; ++l) {
+- for (int i1 = 0; i1 < 4; ++i1) {
+- for (int j1 = 0; j1 < 4; ++j1) {
+- datapaletteblock.getAndSetUnchecked(l, i1, j1, biomeSupplier.getNoiseBiome(x + l, y + i1, z + j1, sampler));
+- }
+- }
+- }
++ for (int y1 = 0; y1 < 4; ++y1) for (int z1 = 0; z1 < 4; ++z1) for (int x1 = 0; x1 < 4; ++x1)
++ palette.getAndSetUnchecked(x1, y1, z1, biomeSupplier.getNoiseBiome(x + x1, y + y1, z + z1, sampler));
+
+- this.biomes = datapaletteblock;
++ this.biomes = palette;
++ // Thunderbolt end - Implement noisium; micro optimization
+ }
+ }
+diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
+index 81368bf186365878db2e1ed305bb7bf36c26f61f..c1d31b9f37472364d08b07958787410a013619cd 100644
+--- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
++++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
+@@ -29,8 +29,8 @@ public class PalettedContainer implements PaletteResize, PalettedContainer
+ private final PaletteResize dummyPaletteResize = (newSize, added) -> 0;
+ public final IdMap registry;
+ private final T @org.jetbrains.annotations.Nullable [] presetValues; // Paper - Anti-Xray - Add preset values
+- private volatile PalettedContainer.Data data;
+- private final PalettedContainer.Strategy strategy;
++ public volatile PalettedContainer.Data data; // Thunderbolt - AT (private -> public)
++ public final PalettedContainer.Strategy strategy; // Thunderbolt - AT (private -> public)
+ // private final ThreadingDetector threadingDetector = new ThreadingDetector("PalettedContainer"); // Paper - unused
+
+ public void acquire() {
+@@ -394,7 +394,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer
+ void accept(T object, int count);
+ }
+
+- static record Data(PalettedContainer.Configuration configuration, BitStorage storage, Palette palette) {
++ public static record Data(PalettedContainer.Configuration configuration, BitStorage storage, Palette palette) { // Thunderbolt - AT (package-private -> public)
+ public void copyFrom(Palette palette, BitStorage storage) {
+ for (int i = 0; i < storage.getSize(); i++) {
+ T object = palette.valueFor(storage.get(i));
+diff --git a/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java b/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java
+index e3990d5f5a49aca501289e92f7462a5f2709db9b..03cf6ea4091d40cb65d5ecba139e2c0275074cc1 100644
+--- a/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java
++++ b/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java
+@@ -52,6 +52,7 @@ import org.apache.commons.lang3.mutable.MutableObject;
+
+ public final class NoiseBasedChunkGenerator extends ChunkGenerator {
+
++ public static boolean NOISIUM = true; // Thunderbolt - Implement noisium
+ public static final MapCodec CODEC = RecordCodecBuilder.mapCodec((instance) -> {
+ return instance.group(BiomeSource.CODEC.fieldOf("biome_source").forGetter((chunkgeneratorabstract) -> {
+ return chunkgeneratorabstract.biomeSource;
+@@ -274,38 +275,45 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator {
+
+ @Override
+ public CompletableFuture fillFromNoise(Executor executor, Blender blender, RandomState noiseConfig, StructureManager structureAccessor, ChunkAccess chunk) {
+- NoiseSettings noisesettings = ((NoiseGeneratorSettings) this.settings.value()).noiseSettings().clampToHeightAccessor(chunk.getHeightAccessorForGeneration());
+- int i = noisesettings.minY();
+- int j = Mth.floorDiv(i, noisesettings.getCellHeight());
+- int k = Mth.floorDiv(noisesettings.height(), noisesettings.getCellHeight());
+-
+- if (k <= 0) {
+- return CompletableFuture.completedFuture(chunk);
+- } else {
+- int l = chunk.getSectionIndex(k * noisesettings.getCellHeight() - 1 + i);
+- int i1 = chunk.getSectionIndex(i);
+- Set set = Sets.newHashSet();
+-
+- for (int j1 = l; j1 >= i1; --j1) {
+- LevelChunkSection chunksection = chunk.getSection(j1);
+-
+- chunksection.acquire();
+- set.add(chunksection);
+- }
+-
+- return CompletableFuture.supplyAsync(Util.wrapThreadWithTaskName("wgen_fill_noise", () -> {
+- return this.doFill(blender, structureAccessor, noiseConfig, chunk, j, k);
+- }), executor).whenCompleteAsync((ichunkaccess1, throwable) -> { // Paper - run with supplied executor
+- Iterator iterator = set.iterator();
++ // Thunderbolt start - Implement noisium
++ NoiseSettings settings = this.settings.value().noiseSettings().clampToHeightAccessor(chunk.getHeightAccessorForGeneration());
++ int minY = settings.minY();
++ int minYDiv = Mth.floorDiv(minY, settings.getCellHeight());
++ int cellHeightDiv = Mth.floorDiv(settings.height(), settings.getCellHeight());
++
++ if (cellHeightDiv <= 0) return CompletableFuture.completedFuture(chunk);
++
++ int startIndex = chunk.getSectionIndex(cellHeightDiv * settings.getCellHeight() - 1 + minY);
++ int minYIndex = chunk.getSectionIndex(minY);
++
++ if (NOISIUM) {
++ LevelChunkSection[] sections = chunk.getSections();
++ for (int i = startIndex; i >= minYIndex; --i) sections[i].acquire();
++
++ return CompletableFuture.supplyAsync(Util.wrapThreadWithTaskName(
++ "wgen_fill_noise",
++ () -> this.doFill(blender, structureAccessor, noiseConfig, chunk, minYDiv, cellHeightDiv)
++ ), Util.backgroundExecutor()).whenCompleteAsync((result, ignored) -> {
++ for (int i = startIndex; i >= minYIndex; --i) sections[i].release();
++ }, executor);
++ }
+
+- while (iterator.hasNext()) {
+- LevelChunkSection chunksection1 = (LevelChunkSection) iterator.next();
++ Set set = Sets.newHashSet();
+
+- chunksection1.release();
+- }
++ for (int i = startIndex; i >= minYIndex; --i) {
++ LevelChunkSection section = chunk.getSection(i);
+
+- }, executor);
++ section.acquire();
++ set.add(section);
+ }
++
++ return CompletableFuture.supplyAsync(Util.wrapThreadWithTaskName(
++ "wgen_fill_noise",
++ () -> this.doFill(blender, structureAccessor, noiseConfig, chunk, minYDiv, cellHeightDiv)
++ ), executor).whenCompleteAsync((ignored1, ignored2) -> {
++ for (LevelChunkSection result : set) result.release();
++ }, executor);
++ // Thunderbolt end
+ }
+
+ private ChunkAccess doFill(Blender blender, StructureManager structureAccessor, RandomState noiseConfig, ChunkAccess chunk, int minimumCellY, int cellHeight) {
+@@ -371,6 +379,20 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator {
+
+ iblockdata = this.debugPreliminarySurfaceLevel(noisechunk, j4, j3, i5, iblockdata);
+ if (iblockdata != NoiseBasedChunkGenerator.AIR && !SharedConstants.debugVoidTerrain(chunk.getPos())) {
++ if (NOISIUM) {
++ // Thunderbolt start - Implement noisium
++ chunksection.nonEmptyBlockCount++;
++
++ if (!iblockdata.getFluidState().isEmpty()) chunksection.nonEmptyBlockCount++;
++ if (iblockdata.isRandomlyTicking()) chunksection.tickingBlockCount++;
++
++ chunksection.states.data.storage().set(
++ chunksection.states.strategy.getIndex(k4, k3, j5),
++ chunksection.states.data.palette().idFor(iblockdata)
++ );
++ // Thunderbolt end - Implement noisium
++ }
++
+ chunksection.setBlockState(k4, k3, j5, iblockdata, false);
+ heightmap.update(k4, j3, j5, iblockdata);
+ heightmap1.update(k4, j3, j5, iblockdata);
+diff --git a/src/main/java/net/minecraft/world/level/levelgen/NoiseSettings.java b/src/main/java/net/minecraft/world/level/levelgen/NoiseSettings.java
+index 52fcf1b92854e5c67c51a83d31b4a136413b54e0..e2b07363d4c902739e88927daefbefe24f40891b 100644
+--- a/src/main/java/net/minecraft/world/level/levelgen/NoiseSettings.java
++++ b/src/main/java/net/minecraft/world/level/levelgen/NoiseSettings.java
+@@ -8,7 +8,12 @@ import net.minecraft.core.QuartPos;
+ import net.minecraft.world.level.LevelHeightAccessor;
+ import net.minecraft.world.level.dimension.DimensionType;
+
+-public record NoiseSettings(int minY, int height, int noiseSizeHorizontal, int noiseSizeVertical) {
++// Thunderbolt start - Implement noisium
++public record NoiseSettings(int minY, int height, int noiseSizeHorizontal, int noiseSizeVertical, int cellHeight, int cellWidth) {
++ public NoiseSettings(int minY, int height, int noiseSizeHorizontal, int noiseSizeVertical) {
++ this(minY, height, noiseSizeHorizontal, noiseSizeVertical, QuartPos.toBlock(noiseSizeHorizontal), QuartPos.toBlock(noiseSizeVertical));
++ }
++// Thunderbolt end - Implement noisium
+ public static final Codec CODEC = RecordCodecBuilder.create(
+ instance -> instance.group(
+ Codec.intRange(DimensionType.MIN_Y, DimensionType.MAX_Y).fieldOf("min_y").forGetter(NoiseSettings::minY),
+@@ -44,11 +49,11 @@ public record NoiseSettings(int minY, int height, int noiseSizeHorizontal, int n
+ }
+
+ public int getCellHeight() {
+- return QuartPos.toBlock(this.noiseSizeVertical());
++ return this.cellHeight; // Thunderbolt - Implement noisium
+ }
+
+ public int getCellWidth() {
+- return QuartPos.toBlock(this.noiseSizeHorizontal());
++ return this.cellWidth; // Thunderbolt - Implement noisium
+ }
+
+ public NoiseSettings clampToHeightAccessor(LevelHeightAccessor world) {
+diff --git a/src/main/java/net/minecraft/world/level/levelgen/material/MaterialRuleList.java b/src/main/java/net/minecraft/world/level/levelgen/material/MaterialRuleList.java
+index afdbc74a3012fa717f59ecef613567338d285b7b..23fb7d811772804c7256981bbaca4ad7673a5e31 100644
+--- a/src/main/java/net/minecraft/world/level/levelgen/material/MaterialRuleList.java
++++ b/src/main/java/net/minecraft/world/level/levelgen/material/MaterialRuleList.java
+@@ -12,9 +12,7 @@ public record MaterialRuleList(List materialRuleLis
+ public BlockState calculate(DensityFunction.FunctionContext pos) {
+ for (NoiseChunk.BlockStateFiller blockStateFiller : this.materialRuleList) {
+ BlockState blockState = blockStateFiller.calculate(pos);
+- if (blockState != null) {
+- return blockState;
+- }
++ if (blockState == null) continue; return blockState; // Thunderbolt - Implement noisium
+ }
+
+ return null;
+diff --git a/src/main/java/org/plazmamc/thunderbolt/configurations/GlobalConfiguration.java b/src/main/java/org/plazmamc/thunderbolt/configurations/GlobalConfiguration.java
+index 0b16bbf87a6c5e658e8182e423800acd16dfc7ca..f1cc8de931ff068a387a91d1ae8e2e6cf91036ff 100644
+--- a/src/main/java/org/plazmamc/thunderbolt/configurations/GlobalConfiguration.java
++++ b/src/main/java/org/plazmamc/thunderbolt/configurations/GlobalConfiguration.java
+@@ -28,6 +28,7 @@ public class GlobalConfiguration extends ConfigurationPart {
+ public NeedsReload needsReload;
+ public class NeedsReload extends ConfigurationPart {
+
++ boolean enableNoisium = optimize();
+
+ @PostProcess
+ public void post() {
+@@ -37,6 +38,7 @@ public class GlobalConfiguration extends ConfigurationPart {
+ return;
+ }
+
++ net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator.NOISIUM = this.enableNoisium;
+ }
+
+ }
diff --git a/patches/unapplied/api/dropped/.gitkeep b/patches/unapplied/api/dropped/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/patches/unapplied/generated-api/dropped/.gitkeep b/patches/unapplied/generated-api/dropped/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/patches/unapplied/mojang-api/dropped/.gitkeep b/patches/unapplied/mojang-api/dropped/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/patches/unapplied/server/dropped/.gitkeep b/patches/unapplied/server/dropped/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/settings.gradle.kts b/settings.gradle.kts
new file mode 100644
index 0000000..537f3bb
--- /dev/null
+++ b/settings.gradle.kts
@@ -0,0 +1,53 @@
+import java.util.Locale
+
+val projectName = "Thunderbolt"
+
+pluginManagement {
+ repositories {
+ gradlePluginPortal()
+ maven("https://repo.papermc.io/repository/maven-public/")
+ }
+}
+
+plugins {
+ id("org.gradle.toolchains.foojay-resolver-convention") version "0.4.0"
+}
+
+if (!file(".git").exists()) {
+ val errorText = """
+
+ =================================[ ERROR ]=================================
+ The Thunderbolt project directory is not a properly cloned Git repository.
+
+ In order to build Volt from source you must clone
+ the repository using Git, not download a code zip from GitHub.
+
+ See https://github.com/PaperMC/Paper/blob/master/CONTRIBUTING.md
+ for further information on building and modifying Thunderbolt.
+ ===========================================================================
+ """.trimIndent()
+ error(errorText)
+}
+
+if (file("libs").exists()) {
+ dependencyResolutionManagement {
+ versionCatalogs {
+ create("api") {
+ from(files("libs/api.versions.toml"))
+ }
+ create("server") {
+ from(files("libs/server.versions.toml"))
+ }
+ create("common") {
+ from(files("libs/common.versions.toml"))
+ }
+ }
+ }
+}
+
+rootProject.name = projectName.lowercase()
+for (name in listOf("$projectName-API", "$projectName-Server", "$projectName-MojangAPI")) {
+ val projName = name.lowercase(Locale.ENGLISH)
+ include(projName)
+ findProject(":$projName")!!.projectDir = file(name)
+}