66 Commits

Author SHA1 Message Date
xSquishyLiam
b7f78c500b removing my debugger 2025-12-15 04:19:16 +00:00
xSquishyLiam
9d64d02f3d just sorting my build to match the main build 2025-12-14 03:10:14 +00:00
xSquishyLiam
a109d21b76 updated PacketEvents andd addeddd zims stuff 2025-12-10 05:10:33 +00:00
xSquishyLiam
be7b067889 updated BetterModel Support 2025-11-25 03:25:17 +00:00
xSquishyLiam
76b857e8a4 whoops forgot to remove the pack part for one of them smh 2025-11-20 14:09:39 +00:00
xSquishyLiam
1b759c78d6 Update extension.yml 2025-11-20 14:09:09 +00:00
xSquishyLiam
223e4885b2 just a rename to GeyserModelEnginePackGenerator 2025-11-20 14:09:02 +00:00
xSquishyLiam
0b06963c1a never touch things when tired ;-; 2025-11-16 20:11:48 +00:00
xSquishyLiam
d0c42c4e06 merged GeyserModelEnginePackGenerator to here 2025-11-15 19:26:01 +00:00
xSquishyLiam
742351ec66 removed a debugger i had 2025-11-09 22:44:10 +00:00
xSquishyLiam
f82dd2b7f8 removed an old import 2025-11-06 01:29:44 +00:00
xSquishyLiam
f93f49980c Merge branch 'bettermodel-support-dev' 2025-11-06 01:26:04 +00:00
xSquishyLiam
0c90fe4809 updated PE to 2.10.1 2025-11-06 00:46:49 +00:00
xSquishyLiam
6892ffb519 removed so unused config options 2025-11-06 00:41:04 +00:00
xSquishyLiam
af1984bef8 Update config.yml 2025-11-05 18:58:25 +00:00
xSquishyLiam
9be6efdc84 reflecting on a cleaner change i didd inside bettermodel support dev 2025-11-05 18:56:21 +00:00
xSquishyLiam
84f50af049 seeing something 2025-11-05 18:52:21 +00:00
xSquishyLiam
f93559c822 just testing 2025-11-04 13:57:39 +00:00
xSquishyLiam
468757762f updated BetterModel API? 2025-11-02 16:46:32 +00:00
xSquishyLiam
e7114faf84 updated CommandAPI 2025-10-30 20:04:46 +00:00
xSquishyLiam
63bf26d7a9 updated BetterModel API 2025-10-30 19:51:00 +00:00
xSquishyLiam
bdc75512d8 pushed my side for PE to lastest 2025-10-22 11:11:42 +01:00
xSquishyLiam
c5a34f1690 testing out renovatebot 2025-10-16 18:33:53 +01:00
xSquishyLiam
74432c2800 updated GeyserUtils 2025-10-16 01:21:28 +01:00
xSquishyLiam
7f8b648685 updated PE to 2.10.0 SNAPSHOT 2025-10-10 15:25:00 +01:00
xSquishyLiam
f5f424ace2 updated to 1.21.9/1.21.10 2025-10-04 16:46:35 +01:00
xSquishyLiam
a4fba7715d Update config.yml 2025-09-26 04:06:21 +01:00
xSquishyLiam
0176d86233 cleaner config.yml (guess i never pushed this) 2025-09-26 04:05:48 +01:00
xSquishyLiam
79f99ffebe added Damage Tint and comments to interfaces 2025-09-10 22:35:44 +01:00
xSquishyLiam
ae3b67a733 pushing beta to github 2025-09-09 23:41:26 +01:00
xSquishyLiam
8e36bf2829 work in progress 2025-09-07 00:26:16 +01:00
xSquishyLiam
804d2aac04 working on it 2025-09-06 23:24:53 +01:00
xSquishyLiam
b6425ffa51 addedd namespace support 2025-09-01 14:56:01 +01:00
zimzaza4
1c47fe83d2 Merge pull request #53 from xSquishyLiam/main
Updated PE to 2.9.5 & added bstats & fixed build.yml
2025-08-31 04:07:22 +08:00
xSquishyLiam
7bbe66928c added an option to disable bStats 2025-08-30 20:56:17 +01:00
xSquishyLiam
a26ea6e148 updated actions/checkout to v4 2025-08-30 12:59:55 +01:00
xSquishyLiam
d1ef89088c learning workflows 2025-08-30 12:55:22 +01:00
xSquishyLiam
c98e807827 bumped shadow 2025-08-30 12:54:08 +01:00
xSquishyLiam
35131c2d58 i wonder 2025-08-30 12:46:37 +01:00
xSquishyLiam
6651ae99f7 addedd a comment for an idea 2025-08-30 00:49:27 +01:00
xSquishyLiam
ca9a54ae71 added `` to true for floodgate 2025-08-30 00:04:19 +01:00
xSquishyLiam
865f5f567c changed readme a bit, removed the old method 2025-08-30 00:03:33 +01:00
xSquishyLiam
b5d9d9b9ca added info about key.pem 2025-08-29 18:03:14 +01:00
xSquishyLiam
31dac482bd made the info more cleaner 2025-08-29 18:01:59 +01:00
xSquishyLiam
b2bfa2977e addedd floodgate send data to set to true info 2025-08-29 17:52:22 +01:00
xSquishyLiam
84012a23b7 bumped PE to 2.9.5 2025-08-26 01:22:14 +01:00
xSquishyLiam
3943381b2c added bstats 2025-08-21 00:12:29 +01:00
TheLividaProject
42d7ea506f Update build.gradle.kts 2025-07-23 21:36:53 +01:00
zimzaza4
e177c8af74 Merge pull request #49 from TheLividaProject/main
Bumped PacketEvents to 2.9.4
2025-07-23 04:33:42 +08:00
TheLividaProject
2c5dba8097 bumped PacketEvents to 2.9.4 2025-07-22 20:45:31 +01:00
TheLividaProject
2a9fdec8b6 Update build.gradle.kts 2025-07-22 20:17:35 +01:00
TheLividaProject
94df76b9a6 Now renames the jar to the project name and it's version 2025-07-22 18:07:31 +01:00
TheLividaProject
03f27514ef added a comment ModelListener issue in regards of pig spawning (such a thing issue) - mainly for those who want to know why it has a delay on the player join, i didn't know this when forked it and took it off but added it back now and should hopefully fix the issue 2025-07-22 17:23:06 +01:00
TheLividaProject
4679fa6f2b changed a comment about the pig issue 2025-07-22 12:23:07 +01:00
TheLividaProject
db99b2fe00 we love big spawning issue, should be fixed till better solution 2025-07-22 11:03:23 +01:00
TheLividaProject
ffd72b0a56 Merge branch 'GeyserExtensionists:main' into main 2025-07-21 22:40:21 +01:00
TheLividaProject
e83caacd2f nvm 90% sure it works i'm just dumb 2025-07-21 22:39:58 +01:00
TheLividaProject
170e67e059 just testing something 2025-07-21 22:32:45 +01:00
zimzaza4
d150d7e324 Merge pull request #48 from TheLividaProject/main
Updates to 1.21.8
2025-07-22 05:11:03 +08:00
TheLividaProject
db0367537e disables default jar hopefully 2025-07-21 22:05:39 +01:00
TheLividaProject
36ea0e5fa0 Merge branch 'GeyserExtensionists:main' into main 2025-07-21 21:59:54 +01:00
zimzaza4
f4c5167ef5 Rename .github/workflows/maven.yml to build.yml 2025-07-10 22:04:08 +08:00
zimzaza4
dc4e39506d gradle build 2025-07-10 20:35:06 +08:00
zimzaza4
5fdf93db1e Merge branch 'main' of https://github.com/TheLividaProject/mc-GeyserModelEngine-plugin
# Conflicts:
#	pom.xml
2025-07-10 20:29:36 +08:00
zimzaza4
4d75f0615c Fix maven.yml 2025-06-21 15:50:56 +08:00
zimzaza4
97e5bdefcd Update PacketEvents 2025-06-21 15:47:42 +08:00
78 changed files with 3757 additions and 797 deletions

View File

@@ -8,10 +8,10 @@ on:
jobs: jobs:
build: build:
name: Build name: Build
runs-on: ubuntu-20.04 runs-on: ubuntu-22.04
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v4
# Setup JDK # Setup JDK
- name: Setup Java JDK - name: Setup Java JDK
@@ -20,24 +20,19 @@ jobs:
java-version: 17 java-version: 17
distribution: 'zulu' distribution: 'zulu'
- name: Restore Maven cache
uses: skjolber/maven-cache-github-action@v1
with:
step: restore
# Build # Build
- name: Build with Maven - name: Make gradlew executable
run: mvn package run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew build
- name: Save Maven cache
uses: skjolber/maven-cache-github-action@v1
with:
step: save
- name: Auto release - name: Auto release
uses: "marvinpinto/action-automatic-releases@latest" uses: "marvinpinto/action-automatic-releases@latest"
with: with:
repo_token: "${{ secrets.GITHUB_TOKEN }}" repo_token: "${{secrets.GITHUB_TOKEN}}"
automatic_release_tag: latest automatic_release_tag: latest
prerelease: false prerelease: false
files: | files: |
target/GeyserModelEngine*.jar paper/build/libs/GeyserModelEngine*.jar
geyser/build/libs/GeyserModelEngine*.jar

3
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

4
.idea/encodings.xml generated
View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="Encoding"> <component name="Encoding">
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" /> <file url="file://$PROJECT_DIR$/paper/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" /> <file url="file://$PROJECT_DIR$/paper/src/main/resources" charset="UTF-8" />
</component> </component>
</project> </project>

2
.idea/gradle.xml generated
View File

@@ -8,6 +8,8 @@
<option name="modules"> <option name="modules">
<set> <set>
<option value="$PROJECT_DIR$" /> <option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/geyser" />
<option value="$PROJECT_DIR$/paper" />
</set> </set>
</option> </option>
</GradleProjectSettings> </GradleProjectSettings>

120
.idea/workspace.xml generated
View File

@@ -4,15 +4,15 @@
<option name="autoReloadType" value="SELECTIVE" /> <option name="autoReloadType" value="SELECTIVE" />
</component> </component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="ff2e9770-ec88-4715-adeb-b9dbda130e1a" name="Changes" comment=""> <list default="true" id="ff2e9770-ec88-4715-adeb-b9dbda130e1a" name="Changes" comment="" />
<change beforePath="$PROJECT_DIR$/src/main/java/re/imc/geysermodelengine/managers/model/data/ModelEntityData.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/re/imc/geysermodelengine/managers/model/data/ModelEntityData.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/re/imc/geysermodelengine/runnables/EntityTaskRunnable.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/re/imc/geysermodelengine/runnables/EntityTaskRunnable.java" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" /> <option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" /> <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" /> <option name="LAST_RESOLUTION" value="IGNORE" />
</component> </component>
<component name="DarkyenusTimeTracker">
<option name="totalTimeSeconds" value="32226" />
</component>
<component name="ExternalProjectsData"> <component name="ExternalProjectsData">
<projectState path="$PROJECT_DIR$"> <projectState path="$PROJECT_DIR$">
<ProjectState /> <ProjectState />
@@ -60,6 +60,7 @@
<component name="Git.Settings"> <component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" /> <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component> </component>
<component name="GradleScriptDefinitionsStorage" workingDir="$PROJECT_DIR$" gradleHome="C:\Users\xsqui\.gradle\wrapper\dists\gradle-9.2.0-bin\11i5gvueggl8a5cioxuftxrik\gradle-9.2.0" javaHome="C:\Program Files\Java\jdk-21" gradleVersion="9.2.0" />
<component name="MavenRunner"> <component name="MavenRunner">
<option name="delegateBuildToMaven" value="true" /> <option name="delegateBuildToMaven" value="true" />
</component> </component>
@@ -79,29 +80,130 @@
</component> </component>
<component name="PropertiesComponent">{ <component name="PropertiesComponent">{
&quot;keyToString&quot;: { &quot;keyToString&quot;: {
&quot;Gradle.Build GeyserModelEngine.executor&quot;: &quot;Run&quot;,
&quot;Gradle.Download Sources.executor&quot;: &quot;Run&quot;,
&quot;Gradle.GeyserModelEngine [buildDependents].executor&quot;: &quot;Run&quot;,
&quot;Gradle.GeyserModelEngine [buildNeeded].executor&quot;: &quot;Run&quot;,
&quot;Gradle.GeyserModelEngine [build].executor&quot;: &quot;Run&quot;,
&quot;Gradle.GeyserModelEngine [clean].executor&quot;: &quot;Run&quot;,
&quot;Gradle.GeyserModelEngine [jar].executor&quot;: &quot;Run&quot;, &quot;Gradle.GeyserModelEngine [jar].executor&quot;: &quot;Run&quot;,
&quot;Maven.GeyserModelEngine [install...].executor&quot;: &quot;Run&quot;, &quot;Maven.GeyserModelEngine [install...].executor&quot;: &quot;Run&quot;,
&quot;Maven.GeyserModelEngine [install].executor&quot;: &quot;Run&quot;, &quot;Maven.GeyserModelEngine [install].executor&quot;: &quot;Run&quot;,
&quot;ModuleVcsDetector.initialDetectionPerformed&quot;: &quot;true&quot;, &quot;ModuleVcsDetector.initialDetectionPerformed&quot;: &quot;true&quot;,
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;, &quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252&quot;: &quot;true&quot;,
&quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;, &quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;,
&quot;git-widget-placeholder&quot;: &quot;main&quot;, &quot;git-widget-placeholder&quot;: &quot;main&quot;,
&quot;last_opened_file_path&quot;: &quot;D:/Coding/Forks/Minecraft/GeyserModelEngine&quot;, &quot;ignore.virus.scanning.warn.message&quot;: &quot;true&quot;,
&quot;kotlin-language-version-configured&quot;: &quot;true&quot;,
&quot;last_opened_file_path&quot;: &quot;D:/Coding/Minecraft/GeyserExtensionists/GeyserModelEngine&quot;,
&quot;project.structure.last.edited&quot;: &quot;Project&quot;, &quot;project.structure.last.edited&quot;: &quot;Project&quot;,
&quot;project.structure.proportion&quot;: &quot;0.0&quot;, &quot;project.structure.proportion&quot;: &quot;0.0&quot;,
&quot;project.structure.side.proportion&quot;: &quot;0.2&quot;, &quot;project.structure.side.proportion&quot;: &quot;0.2&quot;,
&quot;settings.editor.selected.configurable&quot;: &quot;reference.settings.project.maven.runner&quot; &quot;settings.editor.selected.configurable&quot;: &quot;reference.settingsdialog.project.gradle&quot;
} }
}</component> }</component>
<component name="RecentsManager"> <component name="RecentsManager">
<key name="CopyFile.RECENT_KEYS"> <key name="CopyFile.RECENT_KEYS">
<recent name="D:\Coding\Forks\Minecraft\GeyserModelEngine\geyser" />
<recent name="D:\Coding\Forks\Minecraft\GeyserModelEngine\paper" />
<recent name="D:\Coding\Forks\Minecraft\GeyserModelEngine" /> <recent name="D:\Coding\Forks\Minecraft\GeyserModelEngine" />
</key> </key>
<key name="MoveFile.RECENT_KEYS"> <key name="MoveFile.RECENT_KEYS">
<recent name="D:\Coding\Forks\Minecraft\GeyserModelEngine\paper" />
<recent name="D:\Coding\Forks\Minecraft\GeyserModelEngine\.github\workflows" />
<recent name="D:\Coding\Forks\Minecraft\GeyserModelEngine\libs" /> <recent name="D:\Coding\Forks\Minecraft\GeyserModelEngine\libs" />
</key> </key>
</component> </component>
<component name="RunManager"> <component name="RunManager" selected="Gradle.GeyserModelEngine [build]">
<configuration name="GeyserModelEngine [buildDependents]" type="GradleRunConfiguration" factoryName="Gradle" temporary="true">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="buildDependents" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
<configuration name="GeyserModelEngine [buildNeeded]" type="GradleRunConfiguration" factoryName="Gradle" temporary="true">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="buildNeeded" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
<configuration name="GeyserModelEngine [build]" type="GradleRunConfiguration" factoryName="Gradle" temporary="true">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="build" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
<configuration name="GeyserModelEngine [clean]" type="GradleRunConfiguration" factoryName="Gradle" temporary="true">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="clean" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
<configuration name="GeyserModelEngine [jar]" type="GradleRunConfiguration" factoryName="Gradle" temporary="true"> <configuration name="GeyserModelEngine [jar]" type="GradleRunConfiguration" factoryName="Gradle" temporary="true">
<ExternalSystemSettings> <ExternalSystemSettings>
<option name="executionName" /> <option name="executionName" />
@@ -126,7 +228,11 @@
</configuration> </configuration>
<recent_temporary> <recent_temporary>
<list> <list>
<item itemvalue="Gradle.GeyserModelEngine [build]" />
<item itemvalue="Gradle.GeyserModelEngine [jar]" /> <item itemvalue="Gradle.GeyserModelEngine [jar]" />
<item itemvalue="Gradle.GeyserModelEngine [clean]" />
<item itemvalue="Gradle.GeyserModelEngine [buildNeeded]" />
<item itemvalue="Gradle.GeyserModelEngine [buildDependents]" />
</list> </list>
</recent_temporary> </recent_temporary>
</component> </component>

View File

@@ -20,27 +20,22 @@ Download the following plugins according to what server software you use.
- Put `GeyserModelEngine` in the plugins folder (only Spigot or forks of Spigot supported) - Put `GeyserModelEngine` in the plugins folder (only Spigot or forks of Spigot supported)
- Put either `geyserutils-spigot` in your plugins folder aswell (`geyserutils-velocity`/`geyserutils-bungeecord` in your Velocity/Bungeecord plugins folder if you use it) - Put either `geyserutils-spigot` in your plugins folder aswell (`geyserutils-velocity`/`geyserutils-bungeecord` in your Velocity/Bungeecord plugins folder if you use it)
- Put `GeyserModelEnginePackGenerator` and `geyserutils-geyser` into `plugins/[Geyser-Folder]/extensions` - Put `GeyserModelEnginePackGenerator` and `geyserutils-geyser` into `plugins/[Geyser-Folder]/extensions`
- Inside `floodgate` set `send-floodgate-data` to `true` in your Velocity/Bungeecord folder and copy over the key.pem into your backend `floodgate` folders
Start the server to generate the relevant configuration files, and then shut down the server to convert any models. Start the server to generate the relevant configuration files, and then shut down the server to convert any models.
# Convert Models # Convert Models
This is old method to convert model:
`GeyserModelEnginePackGenerator` is capable of generating models all by itself. After generating it will also apply this pack automatically. `GeyserModelEnginePackGenerator` is capable of generating models all by itself. After generating it will also apply this pack automatically.
- First go to `plugins/[Geyser-Folder]/extensions/geysermodelenginepackgenerator/input/` - Firstly, install [packer plugin](https://github.com/GeyserExtensionists/GeyserModelEngineBlockbenchPacker) for your blockbench.
- Create a folder in this directory with the ID of the model. (this is the same name as your model within ModelEngine 4.)
- Then, open your bbmodel, go `File -> Export -> Export GeyserModelEngine Model`, you will get a zip, just unzip it to `input` folder.
<img src="docsimg/example.jpg" width="500"> <img src="docsimg/example.jpg" width="500">
> Each model should have a separate model folder > Each model should have a separate model folder
> Subfolders are supported if you want to categorize them > Subfolders are supported if you want to categorize them
- Now use BlockBench and convert your model to a Bedrock Entity, this will allow you to export the Bedrock Geometry and Animations.
- Put the geometry, animations and texture file in this folder you've made.
<img src="docsimg/example1.jpg" width="500">
- Restart the server or reload geyser to start generating the resource pack. - Restart the server or reload geyser to start generating the resource pack.
- Go to `plugins/[Geyser-Folder]/extensions/geysermodelenginepackgenerator`, and you should see your pack generated! - Go to `plugins/[Geyser-Folder]/extensions/geysermodelenginepackgenerator`, and you should see your pack generated!
@@ -49,12 +44,6 @@ This is old method to convert model:
- Final step, reload Geyser or restart the server to load the resource pack. - Final step, reload Geyser or restart the server to load the resource pack.
- Congratulations, you've completed this tutorial! - Congratulations, you've completed this tutorial!
# Model Packer
This is new way to convert model
- Firstly, install [packer plugin](https://github.com/GeyserExtensionists/GeyserModelEngineBlockbenchPacker) for your blockbench.
- Then, open your bbmodel, go `File -> Export -> Export GeyserModelEngine Model`, you will get a zip, just unzip it to `input` folder.
# Tips # Tips
* Pay attention! The pack only regenerates when the number of models changes, you can technically speaking remove the generated_pack folder to force a reload aswell. * Pay attention! The pack only regenerates when the number of models changes, you can technically speaking remove the generated_pack folder to force a reload aswell.

View File

@@ -1,6 +1,6 @@
plugins { plugins {
id("java") id("java")
id("io.github.goooler.shadow") version "8.1.7" id("com.gradleup.shadow") version "9.2.2"
} }
group = "re.imc" group = "re.imc"
@@ -8,30 +8,10 @@ version = "1.0.0"
repositories { repositories {
mavenCentral() mavenCentral()
maven("https://repo.papermc.io/repository/maven-public/")
maven("https://central.sonatype.com/repository/maven-snapshots/")
maven("https://mvn.lumine.io/repository/maven-public/")
maven("https://repo.opencollab.dev/main/")
maven("https://repo.codemc.io/repository/maven-public/")
maven("https://repo.codemc.io/repository/maven-releases/")
} }
dependencies { dependencies {
compileOnly("io.papermc.paper:paper-api:1.21.8-R0.1-SNAPSHOT")
implementation("dev.jorel:commandapi-bukkit-shade-mojang-mapped:10.1.2")
compileOnly("com.ticxo.modelengine:ModelEngine:R4.0.9")
compileOnly(files("libs/geyserutils-spigot-1.0-SNAPSHOT.jar"))
compileOnly("org.geysermc.floodgate:api:2.2.4-SNAPSHOT")
implementation("com.github.retrooper:packetevents-spigot:2.9.3")
implementation("org.reflections:reflections:0.10.2")
} }
java { java {
@@ -41,16 +21,3 @@ java {
tasks.compileJava { tasks.compileJava {
options.encoding = "UTF-8" options.encoding = "UTF-8"
} }
tasks.shadowJar {
relocate("dev.jorel.commandapi", "re.imc.geysermodelengine.libs.commandapi")
relocate("com.github.retrooper", "re.imc.geysermodelengine.libs.com.github.retrooper.packetevents")
relocate("io.github.retrooper", "re.imc.geysermodelengine.libs.io.github.retrooper.packetevents")
relocate("org.reflections", "re.imc.geysermodelengine.libs.reflections")
}
tasks.build {
dependsOn("shadowJar")
}

35
geyser/build.gradle.kts Normal file
View File

@@ -0,0 +1,35 @@
plugins {
id("java")
id("com.gradleup.shadow") version "9.2.2"
}
group = "me.zimzaza4"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
maven("https://repo.opencollab.dev/main/")
maven("https://maven.tomalbrc.de")
}
dependencies {
compileOnly("org.geysermc.geyser:api:2.9.0-SNAPSHOT")
compileOnly(files("libs/geyserutils-geyser-1.0-SNAPSHOT.jar"))
implementation("org.spongepowered:configurate-yaml:4.2.0-GeyserMC-SNAPSHOT")
implementation("com.google.code.gson:gson:2.13.1")
implementation("de.tomalbrc:blockbench-import-library:1.7.0+1.21.9")
}
tasks.shadowJar {
archiveFileName.set("${rootProject.name}Extension-${version}.jar")
relocate("org.spongepowered.configurate", "me.zimzaza4.geysermodelenginepackgenerator.libs.configurate")
}
tasks.build {
dependsOn("shadowJar")
}

Binary file not shown.

View File

@@ -0,0 +1,71 @@
package re.imc.geysermodelengineextension;
import org.geysermc.event.subscribe.Subscribe;
import org.geysermc.geyser.api.command.Command;
import org.geysermc.geyser.api.command.CommandSource;
import org.geysermc.geyser.api.event.lifecycle.GeyserDefineCommandsEvent;
import org.geysermc.geyser.api.event.lifecycle.GeyserDefineResourcePacksEvent;
import org.geysermc.geyser.api.event.lifecycle.GeyserPreInitializeEvent;
import org.geysermc.geyser.api.extension.Extension;
import org.geysermc.geyser.api.pack.PackCodec;
import org.geysermc.geyser.api.pack.ResourcePack;
import re.imc.geysermodelengineextension.managers.ConfigManager;
import re.imc.geysermodelengineextension.managers.resourcepack.ResourcePackManager;
public class GeyserModelEngineExtension implements Extension {
private static GeyserModelEngineExtension extension;
private ConfigManager configManager;
private ResourcePackManager resourcePackManager;
@Subscribe
public void onLoad(GeyserPreInitializeEvent event) {
extension = this;
loadManagers();
resourcePackManager.loadPack();
}
@Subscribe
public void onDefineCommand(GeyserDefineCommandsEvent event) {
event.register(Command.builder(this)
.name("reload")
.source(CommandSource.class)
.playerOnly(false)
.description("GeyserModelPackGenerator Reload Command")
.permission("geysermodelenginepackgenerator.commands.reload")
.executor((source, command, args) -> {
resourcePackManager.loadPack();
source.sendMessage(configManager.getLang().getString("commands.geysermodelenginepackgenerator.reload.successfully-reloaded"));
})
.build());
}
@Subscribe
public void onPackLoad(GeyserDefineResourcePacksEvent event) {
if (!configManager.getConfig().getBoolean("options.resource-pack.auto-load")) return;
ResourcePack resourcePack = ResourcePack.create(PackCodec.path(resourcePackManager.getGeneratedPackZipPath()));
event.register(resourcePack);
}
private void loadManagers() {
this.configManager = new ConfigManager();
this.resourcePackManager = new ResourcePackManager(this);
}
public static GeyserModelEngineExtension getExtension() {
return extension;
}
public ConfigManager getConfigManager() {
return configManager;
}
public ResourcePackManager getResourcePackManager() {
return resourcePackManager;
}
}

View File

@@ -0,0 +1,25 @@
package re.imc.geysermodelengineextension.managers;
import re.imc.geysermodelengineextension.util.FileConfiguration;
public class ConfigManager {
private FileConfiguration config, lang;
public ConfigManager() {
load();
}
public void load() {
this.config = new FileConfiguration("config.yml");
this.lang = new FileConfiguration("Lang/messages.yml");
}
public FileConfiguration getConfig() {
return config;
}
public FileConfiguration getLang() {
return lang;
}
}

View File

@@ -0,0 +1,446 @@
package re.imc.geysermodelengineextension.managers.resourcepack;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonParser;
import re.imc.geysermodelengineextension.GeyserModelEngineExtension;
import re.imc.geysermodelengineextension.managers.resourcepack.generator.*;
import re.imc.geysermodelengineextension.managers.resourcepack.generator.data.TextureData;
import re.imc.geysermodelengineextension.util.ZipUtil;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
public class ResourcePackManager {
private final GeyserModelEngineExtension extension;
private final File inputFolder;
private final File generatedPack;
private Path generatedPackZipPath;
private final HashMap<String, Entity> entityCache = new HashMap<>();
private final HashMap<String, Animation> animationCache = new HashMap<>();
private final HashMap<String, Geometry> geometryCache = new HashMap<>();
private final HashMap<String, Map<String, TextureData>> textureCache = new HashMap<>();
private final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
public ResourcePackManager(GeyserModelEngineExtension extension) {
this.extension = extension;
this.inputFolder = extension.dataFolder().resolve("input").toFile();
this.inputFolder.mkdirs();
this.generatedPack = extension.dataFolder().resolve("generated_pack").toFile();
}
public void loadPack() {
generateResourcePack(inputFolder, generatedPack);
generatedPackZipPath = extension.dataFolder().resolve("generated_pack.zip");
try (ZipOutputStream zipOutputStream = new ZipOutputStream(Files.newOutputStream(generatedPackZipPath))) {
ZipUtil.compressFolder(generatedPack, null, zipOutputStream);
} catch (IOException err) {
throw new RuntimeException(err);
}
for (Entity entity : entityCache.values()) {
entity.register(extension.getConfigManager().getConfig().getString("models.namespace"));
}
}
private void generateResourcePack(File inputFolder, File output) {
generateFromFolder("", inputFolder, true);
File animationsFolder = new File(output, "animations");
File entityFolder = new File(output, "entity");
File modelsFolder = new File(output, "models/entity");
File texturesFolder = new File(output, "textures/entity");
File animationControllersFolder = new File(output, "animation_controllers");
File renderControllersFolder = new File(output, "render_controllers");
File materialsFolder = new File(output, "materials");
File manifestFile = new File(output, "manifest.json");
output.mkdirs();
if (!manifestFile.exists()) {
try {
Files.writeString(manifestFile.toPath(), PackManifest.generate(), StandardCharsets.UTF_8);
} catch (IOException err) {
throw new RuntimeException(err);
}
}
animationsFolder.mkdirs();
entityFolder.mkdirs();
modelsFolder.mkdirs();
texturesFolder.mkdirs();
animationControllersFolder.mkdirs();
renderControllersFolder.mkdirs();
materialsFolder.mkdirs();
File materialFile = new File(materialsFolder, "entity.material");
if (!materialFile.exists()) {
try {
Files.writeString(materialFile.toPath(), Material.TEMPLATE, StandardCharsets.UTF_8);
} catch (IOException err) {
throw new RuntimeException(err);
}
}
for (Map.Entry<String, Animation> entry : animationCache.entrySet()) {
Entity entity = entityCache.get(entry.getKey());
Geometry geo = geometryCache.get(entry.getKey());
if (geo != null) entry.getValue().addHeadBind(geo);
Path path = animationsFolder.toPath().resolve(entry.getValue().getPath() + entry.getKey() + ".animation.json");
Path pathController = animationControllersFolder.toPath().resolve(entry.getValue().getPath() + entry.getKey() + ".animation_controllers.json");
pathController.toFile().getParentFile().mkdirs();
path.toFile().getParentFile().mkdirs();
if (path.toFile().exists()) continue;
AnimationController controller = new AnimationController();
controller.load(extension, entry.getValue(), entity);
try {
Files.writeString(path, GSON.toJson(entry.getValue().getJson()), StandardCharsets.UTF_8);
Files.writeString(pathController, controller.getJson().toString(), StandardCharsets.UTF_8);
} catch (IOException err) {
throw new RuntimeException(err);
}
}
for (Map.Entry<String, Geometry> entry : geometryCache.entrySet()) {
entry.getValue().modify();
Path path = modelsFolder.toPath().resolve(entry.getValue().getPath() + entry.getKey() + ".geo.json");
path.toFile().getParentFile().mkdirs();
String id = entry.getValue().getGeometryId();
Entity entity = entityCache.get(entry.getKey());
if (entity != null) {
ModelConfig modelConfig = entity.getModelConfig();
if (!modelConfig.getPerTextureUvSize().isEmpty()) {
for (Map.Entry<String, TextureData> textureEntry : entity.getTextureMap().entrySet()) {
String name = textureEntry.getKey();
Integer[] size = modelConfig.getPerTextureUvSize().getOrDefault(name, new Integer[]{16, 16});
String suffix = size[0] + "_" + size[1];
entry.getValue().setTextureWidth(size[0]);
entry.getValue().setTextureHeight(size[1]);
path = modelsFolder.toPath().resolve(entry.getValue().getPath() + entry.getKey() + "_" + suffix + ".geo.json");
entry.getValue().setId(id + "_" + suffix);
if (path.toFile().exists()) continue;
try {
Files.writeString(path, GSON.toJson(entry.getValue().getJson()), StandardCharsets.UTF_8);
} catch (IOException err) {
throw new RuntimeException(err);
}
}
}
}
if (path.toFile().exists()) continue;
try {
Files.writeString(path, GSON.toJson(entry.getValue().getJson()), StandardCharsets.UTF_8);
} catch (IOException err) {
throw new RuntimeException(err);
}
}
for (Map.Entry<String, Map<String, TextureData>> textures : textureCache.entrySet()) {
for (Map.Entry<String, TextureData> entry : textures.getValue().entrySet()) {
Path path = texturesFolder.toPath().resolve(entry.getValue().getPath() + textures.getKey() + "/" + entry.getKey() + ".png");
path.toFile().getParentFile().mkdirs();
if (path.toFile().exists()) continue;
try {
if (entry.getValue().getImage() != null) Files.write(path, entry.getValue().getImage());
} catch (IOException err) {
throw new RuntimeException(err);
}
}
}
for (Map.Entry<String, Entity> entry : entityCache.entrySet()) {
Entity entity = entry.getValue();
entity.modify(extension.getConfigManager().getConfig().getString("models.namespace"));
Path entityPath = entityFolder.toPath().resolve(entity.getPath() + entry.getKey() + ".entity.json");
entityPath.toFile().getParentFile().mkdirs();
if (entityPath.toFile().exists()) continue;
try {
Files.writeString(entityPath, entity.getJson().toString(), StandardCharsets.UTF_8);
} catch (IOException err) {
throw new RuntimeException(err);
}
// render controller part
String id = entity.getModelId();
if (!geometryCache.containsKey(id)) continue;
RenderController controller = new RenderController(id, geometryCache.get(id).getBones(), entity);
entity.setRenderController(controller);
Path renderPath = new File(renderControllersFolder, "controller.render." + id + ".json").toPath();
if (renderPath.toFile().exists()) continue;
try {
Files.writeString(renderPath, controller.generate(extension.getConfigManager().getConfig().getString("models.namespace")), StandardCharsets.UTF_8);
} catch (IOException err) {
throw new RuntimeException(err);
}
}
}
public void generateFromFolder(String currentPath, File folder, boolean root) {
if (folder.listFiles() == null) return;
String modelId = root ? "" : folder.getName().toLowerCase();
Entity entity = new Entity(modelId);
ModelConfig modelConfig = new ModelConfig();
boolean shouldOverrideConfig = false;
File textureConfigFile = new File(folder, "config.json");
if (textureConfigFile.exists()) {
try {
modelConfig = GSON.fromJson(Files.readString(textureConfigFile.toPath()), ModelConfig.class);
} catch (IOException err) {
throw new RuntimeException(err);
}
}
boolean canAdd = false;
for (File file : folder.listFiles()) {
if (file.isDirectory()) generateFromFolder(currentPath + (root ? "" : folder.getName() + "/"), file, false);
if (file.getName().endsWith(".zip")) {
try {
generateFromZip(currentPath, file.getName().replace(".zip", "").toLowerCase(Locale.ROOT), new ZipFile(file));
} catch (IOException err) {
throw new RuntimeException(err);
}
}
if (entityCache.containsKey(modelId)) continue;
if (file.getName().endsWith(".png")) {
String textureName = file.getName().replace(".png", "");
Set<String> bindingBones = new HashSet<>();
bindingBones.add("*");
if (modelConfig.getBingingBones().containsKey(textureName)) bindingBones = modelConfig.getBingingBones().get(textureName);
Map<String, TextureData> map = textureCache.computeIfAbsent(modelId, s -> new HashMap<>());
try {
map.put(textureName, new TextureData(modelId, currentPath, bindingBones, Files.readAllBytes(file.toPath())));
} catch (IOException err) {
throw new RuntimeException(err);
}
entity.setTextureMap(map);
if (modelConfig.getBingingBones().isEmpty()) {
modelConfig.getBingingBones().put(textureName, Set.of("*"));
shouldOverrideConfig = true;
}
}
if (file.getName().endsWith(".json")) {
try {
String json = Files.readString(file.toPath());
if (isAnimationFile(json)) {
Animation animation = new Animation();
animation.setPath(currentPath);
animation.setModelId(modelId);
animation.load(json);
animationCache.put(modelId, animation);
entity.setAnimation(animation);
}
if (isGeometryFile(json)) {
Geometry geometry = new Geometry();
geometry.load(json);
geometry.setPath(currentPath);
geometry.setModelId(modelId);
geometryCache.put(modelId, geometry);
entity.setGeometry(geometry);
canAdd = true;
}
} catch (IOException err) {
throw new RuntimeException(err);
}
}
}
if (canAdd) {
// old config
File oldConfig = new File(folder, "config.properties");
Properties old = new Properties();
try {
if (oldConfig.exists()) {
old.load(new FileReader(oldConfig));
modelConfig.setMaterial(old.getProperty("material", "entity_alphatest_change_color"));
modelConfig.setEnableBlendTransition(Boolean.parseBoolean(old.getProperty("blend-transition", "true")));
modelConfig.setEnableHeadRotation(Boolean.parseBoolean(old.getProperty("head-rotation", "true")));
shouldOverrideConfig = true;
oldConfig.delete();
}
} catch (IOException err) {
throw new RuntimeException(err);
}
if (shouldOverrideConfig) {
try {
Files.writeString(textureConfigFile.toPath(), GSON.toJson(modelConfig));
} catch (IOException err) {
throw new RuntimeException(err);
}
}
entity.setModelConfig(modelConfig);
entity.setPath(currentPath);
entityCache.put(modelId, entity);
}
}
public void generateFromZip(String currentPath, String modelId, ZipFile zip) {
Entity entity = new Entity(modelId);
if (entityCache.containsKey(modelId)) return;
ModelConfig modelConfig = new ModelConfig();
ZipEntry textureConfigFile = null;
for (Iterator<? extends ZipEntry> it = zip.entries().asIterator(); it.hasNext(); ) {
ZipEntry entry = it.next();
if (entry.getName().endsWith("config.json")) {
textureConfigFile = entry;
}
}
if (textureConfigFile != null) {
try {
modelConfig = GSON.fromJson(new InputStreamReader(zip.getInputStream(textureConfigFile)), ModelConfig.class);
} catch (IOException err) {
throw new RuntimeException(err);
}
}
boolean canAdd = false;
for (Iterator<? extends ZipEntry> it = zip.entries().asIterator(); it.hasNext(); ) {
ZipEntry e = it.next();
if (e.getName().endsWith(".png")) {
String[] path = e.getName().split("/");
String textureName = path[path.length - 1].replace(".png", "");
Set<String> bindingBones = new HashSet<>();
bindingBones.add("*");
if (modelConfig.getBingingBones().containsKey(textureName)) {
bindingBones = modelConfig.getBingingBones().get(textureName);
}
Map<String, TextureData> map = textureCache.computeIfAbsent(modelId, s -> new HashMap<>());
try {
map.put(textureName, new TextureData(modelId, currentPath, bindingBones, zip.getInputStream(e).readAllBytes()));
} catch (IOException err) {
throw new RuntimeException(err);
}
entity.setTextureMap(map);
if (modelConfig.getBingingBones().isEmpty()) modelConfig.getBingingBones().put(textureName, Set.of("*"));
}
if (e.getName().endsWith(".json")) {
try {
InputStream stream = zip.getInputStream(e);
String json = new String(stream.readAllBytes());
if (isAnimationFile(json)) {
Animation animation = new Animation();
animation.setPath(currentPath);
animation.setModelId(modelId);
animation.load(json);
animationCache.put(modelId, animation);
entity.setAnimation(animation);
}
if (isGeometryFile(json)) {
Geometry geometry = new Geometry();
geometry.load(json);
geometry.setPath(currentPath);
geometry.setModelId(modelId);
geometryCache.put(modelId, geometry);
entity.setGeometry(geometry);
canAdd = true;
}
} catch (IOException err) {
throw new RuntimeException(err);
}
}
}
if (canAdd) {
entity.setModelConfig(modelConfig);
entity.setPath(currentPath);
entityCache.put(modelId, entity);
}
}
private boolean isGeometryFile(String json) {
try {
return JsonParser.parseString(json).getAsJsonObject().has("minecraft:geometry");
} catch (Throwable ignored) {
return false;
}
}
private boolean isAnimationFile(String json) {
try {
return JsonParser.parseString(json).getAsJsonObject().has("animations");
} catch (Throwable ignored) {
return false;
}
}
public File getInputFolder() {
return inputFolder;
}
public Path getGeneratedPackZipPath() {
return generatedPackZipPath;
}
public HashMap<String, Entity> getEntityCache() {
return entityCache;
}
public HashMap<String, Animation> getAnimationCache() {
return animationCache;
}
public HashMap<String, Geometry> getGeometryCache() {
return geometryCache;
}
public HashMap<String, Map<String, TextureData>> getTextureCache() {
return textureCache;
}
}

View File

@@ -0,0 +1,151 @@
package re.imc.geysermodelengineextension.managers.resourcepack.generator;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import re.imc.geysermodelengineextension.GeyserModelEngineExtension;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class Animation {
private String modelId;
private JsonObject json;
private Set<String> animationIds = new HashSet<>();
private String path;
public static final String HEAD_TEMPLATE = """
{
"relative_to" : {
"rotation" : "entity"
},
"rotation" : [ "query.target_x_rotation - this", "query.target_y_rotation - this", 0.0 ]
}
""";
public void load(String string) {
this.json = JsonParser.parseString(string).getAsJsonObject();
JsonObject newAnimations = new JsonObject();
for (Map.Entry<String, JsonElement> element : json.get("animations").getAsJsonObject().entrySet()) {
animationIds.add(element.getKey());
JsonObject animation = element.getValue().getAsJsonObject();
if (animation.has("override_previous_animation")) {
if (animation.get("override_previous_animation").getAsBoolean()) {
if (!animation.has("loop")) {
animation.addProperty("loop", "hold_on_last_frame");
// play once but override must use this to avoid strange anim
}
}
animation.remove("override_previous_animation");
}
if (animation.has("loop")) {
if (animation.get("loop").getAsJsonPrimitive().isString()) {
if (animation.get("loop").getAsString().equals("hold_on_last_frame")) {
if (!animation.has("bones")) {
continue;
}
for (Map.Entry<String, JsonElement> bone : animation.get("bones").getAsJsonObject().entrySet()) {
for (Map.Entry<String, JsonElement> anim : bone.getValue().getAsJsonObject().entrySet()) {
float max = -1;
JsonObject end = null;
if (!anim.getValue().isJsonObject()) {
continue;
}
try {
for (Map.Entry<String, JsonElement> timeline : anim.getValue().getAsJsonObject().entrySet()) {
float time = Float.parseFloat(timeline.getKey());
if (time > max) {
max = time;
if (timeline.getValue().isJsonObject()) {
end = timeline.getValue().getAsJsonObject();
}
}
}
} catch (Throwable ignored) {}
if (end != null && end.has("lerp_mode") && end.get("lerp_mode").getAsString().equals("catmullrom")) {
end.addProperty("lerp_mode", "linear");
}
}
}
}
}
}
newAnimations.add("animation." + modelId + "." + element.getKey().replace(" ", "_"), element.getValue());
}
json.add("animations", newAnimations);
}
public void addHeadBind(Geometry geometry) {
JsonObject object = new JsonObject();
object.addProperty("loop", true);
JsonObject bones = new JsonObject();
JsonArray array = geometry.getInternal().get("bones").getAsJsonArray();
int i = 0;
for (JsonElement element : array) {
if (element.isJsonObject()) {
String name = element.getAsJsonObject().get("name").getAsString();
String parent = "";
if (element.getAsJsonObject().has("parent")) parent = element.getAsJsonObject().get("parent").getAsString();
if (parent.startsWith("h_") || parent.startsWith("hi_")) continue;
if (name.startsWith("h_") || name.startsWith("hi_")) {
bones.add(name, JsonParser.parseString(HEAD_TEMPLATE));
i++;
}
}
}
if (i == 0) return;
GeyserModelEngineExtension.getExtension().getResourcePackManager().getEntityCache().get(modelId).setHasHeadAnimation(true);
object.add("bones", bones);
json.get("animations").getAsJsonObject().add("animation." + modelId + ".look_at_target", object);
}
public void setModelId(String modelId) {
this.modelId = modelId;
}
public void setJson(JsonObject json) {
this.json = json;
}
public void setAnimationIds(Set<String> animationIds) {
this.animationIds = animationIds;
}
public String getModelId() {
return modelId;
}
public void setPath(String path) {
this.path = path;
}
public JsonObject getJson() {
return json;
}
public Set<String> getAnimationIds() {
return animationIds;
}
public String getPath() {
return path;
}
}

View File

@@ -0,0 +1,84 @@
package re.imc.geysermodelengineextension.managers.resourcepack.generator;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import re.imc.geysermodelengineextension.GeyserModelEngineExtension;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class AnimationController {
private JsonObject json;
private Entity entity;
public static final String CONTROLLER_TEMPLATE =
"""
{
"initial_state": "stop",
"states": {
"play": {
"animations": [
"%anim%"
],
"blend_transition": 0.1,
"transitions": [{ "stop": "%query% == 0"}]
},
"stop": {
"blend_transition": 0.1,
"transitions": [{ "play": "%query% != 0"}]
}
}
}
""";
public void load(GeyserModelEngineExtension extension, Animation animation, Entity entity) {
JsonObject root = new JsonObject();
json = root;
root.addProperty("format_version", "1.10.0");
JsonObject animationControllers = new JsonObject();
root.add("animation_controllers", animationControllers);
List<String> sorted = new ArrayList<>(animation.getAnimationIds());
int i = 0;
Collections.sort(sorted);
for (String id : sorted) {
id = id.replace(" ", "_");
int n = (int) Math.pow(2, (i % 24));
JsonObject controller = JsonParser.parseString(CONTROLLER_TEMPLATE.replace("%anim%", id).replace("%query%", "math.mod(math.floor(query.property('" + extension.getConfigManager().getConfig().getString("models.namespace") + ":anim" + i / 24 + "') / " + n + "), 2)")).getAsJsonObject();
animationControllers.add("controller.animation." + animation.getModelId() + "." + id, controller);
i++;
if (entity != null) {
boolean blend = entity.getModelConfig().isEnableBlendTransition();
if (!blend) {
for (Map.Entry<String, JsonElement> states : controller.get("states").getAsJsonObject().entrySet()) {
states.getValue().getAsJsonObject().remove("blend_transition");
}
}
}
}
}
public void setJson(JsonObject json) {
this.json = json;
}
public void setEntity(Entity entity) {
this.entity = entity;
}
public JsonObject getJson() {
return json;
}
public Entity getEntity() {
return entity;
}
}

View File

@@ -0,0 +1,207 @@
package re.imc.geysermodelengineextension.managers.resourcepack.generator;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import me.zimzaza4.geyserutils.geyser.GeyserUtils;
import re.imc.geysermodelengineextension.managers.resourcepack.generator.data.TextureData;
import java.util.*;
public class Entity {
public static final Set<String> REGISTERED_ENTITIES = new HashSet<>();
private String modelId;
private JsonObject json;
private boolean hasHeadAnimation = false;
private Animation animation;
private Geometry geometry;
private RenderController renderController;
private String path;
private Map<String, TextureData> textureMap = new HashMap<>();
private ModelConfig modelConfig;
public static final String TEMPLATE = """
{
"format_version": "1.10.0",
"minecraft:client_entity": {
"description": {
"identifier": "%namespace%:%entity_id%",
"materials": {
"default": "%material%",
"anim": "entity_alphatest_anim_change_color_one_sided"
},
"textures": {
},
"geometry": {
},
"animations": {
"look_at_target": "%look_at_target%"
},
"scripts": {
"animate": [
"look_at_target"
]
},
"render_controllers": [
]
}
}
}
""";
public Entity(String modelId) {
this.modelId = modelId;
}
public void modify(String namespace) {
this.json = JsonParser.parseString(TEMPLATE.replace("%namespace%", namespace)
.replace("%entity_id%", modelId)
.replace("%geometry%", "geometry.meg_" + modelId)
.replace("%texture%", "textures/entity/" + path + modelId)
.replace("%look_at_target%", modelConfig.isEnableHeadRotation() ? "animation." + modelId + ".look_at_target" : "animation.none")
.replace("%material%", modelConfig.getMaterial())).getAsJsonObject();
JsonObject description = json.get("minecraft:client_entity").getAsJsonObject().get("description").getAsJsonObject();
JsonObject jsonAnimations = description.get("animations").getAsJsonObject();
JsonObject jsonTextures = description.get("textures").getAsJsonObject();
JsonObject jsonGeometry = description.get("geometry").getAsJsonObject();
JsonObject jsonMaterials = description.get("materials").getAsJsonObject();
JsonArray jsonRenderControllers = description.get("render_controllers").getAsJsonArray();
Map<String, String> materials = modelConfig.getTextureMaterials();
materials.forEach(jsonMaterials::addProperty);
if (modelConfig.getPerTextureUvSize().isEmpty()) {
jsonGeometry.addProperty("default", "geometry.meg_" + modelId);
jsonTextures.addProperty("default", "textures/entity/" + path + modelId + "/" + textureMap.keySet().stream().findFirst().orElse("def"));
}
for (String name : textureMap.keySet()) {
if (name.endsWith("_e")) continue;
if (modelConfig.getPerTextureUvSize().containsKey(name)) {
Integer[] size = modelConfig.getPerTextureUvSize().getOrDefault(name, new Integer[]{16, 16});
String suffix = size[0] + "_" + size[1];
jsonGeometry.addProperty("t_" + suffix, "geometry.meg_" + modelId + "_" + suffix);
jsonTextures.addProperty(name, "textures/entity/" + path + modelId + "/" + name);
}
jsonRenderControllers.add("controller.render." + modelId + "_" + name);
}
JsonArray animate = description.get("scripts").getAsJsonObject().get("animate").getAsJsonArray();
if (animation != null) {
for (String animation : animation.getAnimationIds()) {
animation = animation.replace(" ", "_");
String controller = "controller.animation." + modelId + "." + animation;
animate.add(animation + "_control");
jsonAnimations.addProperty(animation, "animation." + modelId + "." + animation);
jsonAnimations.addProperty(animation + "_control", controller);
}
}
}
public void register(String namespace) {
String id = namespace + ":" + modelId;
boolean registered = REGISTERED_ENTITIES.contains(id);
if (registered) return;
REGISTERED_ENTITIES.add(id);
GeyserUtils.addCustomEntity(id);
if (geometry == null) return;
if (!modelConfig.isDisablePartVisibility()) {
for (int i = 0; i < Math.ceil(geometry.getBones().size() / 24f); i++) {
GeyserUtils.addProperty(id, namespace + ":" + "bone" + i, Integer.class);
}
}
if (animation != null) {
for (int i = 0; i < Math.ceil(animation.getAnimationIds().size() / 24f); i++) {
GeyserUtils.addProperty(id, namespace + ":" + "anim" + i, Integer.class);
}
}
GeyserUtils.registerProperties(id);
}
public void setModelId(String modelId) {
this.modelId = modelId;
}
public void setJson(JsonObject json) {
this.json = json;
}
public void setHasHeadAnimation(boolean hasHeadAnimation) {
this.hasHeadAnimation = hasHeadAnimation;
}
public void setAnimation(Animation animation) {
this.animation = animation;
}
public void setGeometry(Geometry geometry) {
this.geometry = geometry;
}
public void setRenderController(RenderController renderController) {
this.renderController = renderController;
}
public void setPath(String path) {
this.path = path;
}
public void setTextureMap(Map<String, TextureData> textureMap) {
this.textureMap = textureMap;
}
public void setModelConfig(ModelConfig modelConfig) {
this.modelConfig = modelConfig;
}
public String getModelId() {
return modelId;
}
public JsonObject getJson() {
return json;
}
public boolean isHasHeadAnimation() {
return hasHeadAnimation;
}
public Animation getAnimation() {
return animation;
}
public Geometry getGeometry() {
return geometry;
}
public RenderController getRenderController() {
return renderController;
}
public String getPath() {
return path;
}
public Map<String, TextureData> getTextureMap() {
return textureMap;
}
public ModelConfig getModelConfig() {
return modelConfig;
}
}

View File

@@ -0,0 +1,113 @@
package re.imc.geysermodelengineextension.managers.resourcepack.generator;
import com.google.gson.*;
import re.imc.geysermodelengineextension.managers.resourcepack.generator.data.BoneData;
import java.util.*;
public class Geometry {
private String modelId;
private String geometryId;
private JsonObject json;
private final Map<String, BoneData> bones = new HashMap<>();
private String path;
public void load(String json) {
this.json = JsonParser.parseString(json).getAsJsonObject();
}
public void setId(String id) {
geometryId = id;
getInternal().get("description").getAsJsonObject().addProperty("identifier", id);
}
public void setTextureWidth(int w) {
getInternal().get("description").getAsJsonObject().addProperty("texture_width", w);
}
public void setTextureHeight(int h) {
getInternal().get("description").getAsJsonObject().addProperty("texture_height", h);
}
public JsonObject getInternal() {
return json.get("minecraft:geometry").getAsJsonArray().get(0).getAsJsonObject();
}
public void modify() {
JsonArray array = getInternal().get("bones").getAsJsonArray();
Iterator<JsonElement> iterator = array.iterator();
while (iterator.hasNext()) {
JsonElement element = iterator.next();
if (element.isJsonObject()) {
String name = element.getAsJsonObject().get("name").getAsString().toLowerCase(Locale.ROOT);
String parent = element.getAsJsonObject().has("parent") ? element.getAsJsonObject().get("parent").getAsString().toLowerCase() : null;
element.getAsJsonObject().remove("name");
element.getAsJsonObject().addProperty("name", name);
if (name.equals("hitbox") || name.equals("shadow") || name.equals("mount") || name.startsWith("b_") || name.startsWith("ob_")) {
iterator.remove();
} else {
bones.put(name, new BoneData(name, parent, new HashSet<>(), new HashSet<>()));
}
}
for (BoneData bone : bones.values()) {
if (bone.getParent() != null) {
BoneData parent = bones.get(bone.getParent());
if (parent != null) {
parent.getChildren().add(bone);
addAllChildren(parent, bone);
}
}
}
}
setId("geometry.meg_" + modelId);
}
public void addAllChildren(BoneData p, BoneData c) {
p.getAllChildren().add(c);
BoneData parent = bones.get(p.getParent());
if (parent != null) {
addAllChildren(parent, c);
}
}
public void setModelId(String modelId) {
this.modelId = modelId;
}
public void setGeometryId(String geometryId) {
this.geometryId = geometryId;
}
public void setJson(JsonObject json) {
this.json = json;
}
public void setPath(String path) {
this.path = path;
}
public String getModelId() {
return modelId;
}
public String getGeometryId() {
return geometryId;
}
public JsonObject getJson() {
return json;
}
public String getPath() {
return path;
}
public Map<String, BoneData> getBones() {
return bones;
}
}

View File

@@ -0,0 +1,38 @@
package re.imc.geysermodelengineextension.managers.resourcepack.generator;
public class Material {
public static final String TEMPLATE = """
{
"materials":{
"version":"1.0.0",
"entity_alphatest_anim_change_color:entity_alphatest_change_color":{
"+defines":[
"USE_UV_ANIM"
]
},
"entity_change_color_one_sided:entity": {
"+defines": [
"USE_OVERLAY",
"USE_COLOR_MASK"
]
},
"entity_alphatest_change_color_one_sided:entity_change_color_one_sided": {
"+defines": [ "ALPHA_TEST" ],
"+samplerStates": [
{
"samplerIndex": 1,
"textureWrap": "Repeat"
}
],
"msaaSupport": "Both"
},
"entity_alphatest_anim_change_color_one_sided:entity_alphatest_change_color_one_sided":{
"+defines":[
"USE_UV_ANIM"
]
}
}
}
""";
}

View File

@@ -0,0 +1,104 @@
package re.imc.geysermodelengineextension.managers.resourcepack.generator;
import com.google.gson.annotations.SerializedName;
import lombok.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class ModelConfig {
@SerializedName("head_rotation")
boolean enableHeadRotation = true;
@SerializedName("material")
String material = "entity_alphatest_change_color_one_sided";
@SerializedName("blend_transition")
boolean enableBlendTransition = true;
@SerializedName("binding_bones")
Map<String, Set<String>> bingingBones = new HashMap<>();
@SerializedName("anim_textures")
Map<String, AnimTextureOptions> animTextures = new HashMap<>();
@SerializedName("texture_materials")
Map<String, String> textureMaterials = new HashMap<>();
@SerializedName("per_texture_uv_size")
Map<String, Integer[]> perTextureUvSize;
@SerializedName("disable_part_visibility")
boolean disablePartVisibility = true;
public void setEnableHeadRotation(boolean enableHeadRotation) {
this.enableHeadRotation = enableHeadRotation;
}
public void setMaterial(String material) {
this.material = material;
}
public void setEnableBlendTransition(boolean enableBlendTransition) {
this.enableBlendTransition = enableBlendTransition;
}
public void setBingingBones(Map<String, Set<String>> bingingBones) {
this.bingingBones = bingingBones;
}
public void setAnimTextures(Map<String, AnimTextureOptions> animTextures) {
this.animTextures = animTextures;
}
public void setTextureMaterials(Map<String, String> textureMaterials) {
this.textureMaterials = textureMaterials;
}
public void setPerTextureUvSize(Map<String, Integer[]> perTextureUvSize) {
this.perTextureUvSize = perTextureUvSize;
}
public void setDisablePartVisibility(boolean disablePartVisibility) {
this.disablePartVisibility = disablePartVisibility;
}
public Map<String, String> getTextureMaterials() {
return textureMaterials != null ? textureMaterials : Map.of();
}
public Map<String, Integer[]> getPerTextureUvSize() {
return perTextureUvSize != null ? perTextureUvSize : Map.of();
}
public boolean isEnableHeadRotation() {
return enableHeadRotation;
}
public String getMaterial() {
return material;
}
public boolean isEnableBlendTransition() {
return enableBlendTransition;
}
public Map<String, Set<String>> getBingingBones() {
return bingingBones;
}
public Map<String, AnimTextureOptions> getAnimTextures() {
return animTextures;
}
public boolean isDisablePartVisibility() {
return disablePartVisibility;
}
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
public static class AnimTextureOptions {
float fps;
int frames;
}
}

View File

@@ -0,0 +1,32 @@
package re.imc.geysermodelengineextension.managers.resourcepack.generator;
import java.util.UUID;
public class PackManifest {
public static final String TEMPLATE = """
{
"format_version": 2,
"header": {
"name": "GeyserModelEngine",
"description": "GeyserModelEngine For Geyser",
"uuid": "%uuid-1%",
"version": [0, 0, 1],
"min_engine_version": [1, 21, 100]
},
"modules": [
{
"type": "resources",
"description": "GeyserModelEngine",
"uuid": "%uuid-2%",
"version": [0, 0, 1]
}
]
}
""";
public static String generate() {
return TEMPLATE.replace("%uuid-1%", UUID.randomUUID().toString())
.replace("%uuid-2%", UUID.randomUUID().toString());
}
}

View File

@@ -0,0 +1,178 @@
package re.imc.geysermodelengineextension.managers.resourcepack.generator;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import re.imc.geysermodelengineextension.managers.resourcepack.generator.data.BoneData;
import java.util.*;
public class RenderController {
public static final Set<String> NEED_REMOVE_WHEN_SORT = Set.of("pbody_", "plarm_", "prarm_", "plleg_", "prleg_", "phead_", "p_");
private final String modelId;
private final Map<String, BoneData> bones;
private final Entity entity;
public RenderController(String modelId, Map<String, BoneData> bones, Entity entity) {
this.modelId = modelId;
this.bones = bones;
this.entity = entity;
}
// look, I'm fine with your other code and stuff, but I ain't using templates for JSON lmao
public String generate(String namespace) {
List<String> se = new ArrayList<>(bones.keySet());
Collections.sort(se);
JsonObject root = new JsonObject();
root.addProperty("format_version", "1.8.0");
JsonObject renderControllers = new JsonObject();
root.add("render_controllers", renderControllers);
Set<BoneData> processedBones = new HashSet<>();
boolean singleTexture = entity.getTextureMap().size() == 1 && entity.getModelConfig().getPerTextureUvSize().isEmpty();
for (String key : entity.getTextureMap().keySet()) {
if (key.endsWith("_e")) continue;
// Texture texture = entity.textureMap.get(key);
Set<String> uvBonesId = entity.getModelConfig().getBingingBones().get(key);
if (uvBonesId == null) {
if (!singleTexture) {
continue;
} else {
uvBonesId = new HashSet<>();
uvBonesId.add("*");
}
}
ModelConfig.AnimTextureOptions anim = entity.getModelConfig().getAnimTextures().get(key);
JsonObject controller = new JsonObject();
renderControllers.add("controller.render." + modelId + "_" + key, controller);
if (!entity.getModelConfig().getPerTextureUvSize().isEmpty()) {
Integer[] size = entity.getModelConfig().getPerTextureUvSize().getOrDefault(key, new Integer[]{16, 16});
String suffix = "t_" + size[0] + "_" + size[1];
controller.addProperty("geometry", "Geometry." + suffix);
} else {
controller.addProperty("geometry", "Geometry.default");
}
JsonArray materials = new JsonArray();
String material = entity.getModelConfig().getTextureMaterials().get(key);
JsonObject materialItem = new JsonObject();
if (material != null) {
materialItem.addProperty("*", "Material." + material);
} else if (anim != null) {
materialItem.addProperty("*", "Material.anim");
JsonObject uvAnim = new JsonObject();
controller.add("uv_anim", uvAnim);
JsonArray offset = new JsonArray();
offset.add(0.0);
offset.add("math.mod(math.floor(q.life_time * " + anim.fps + ")," + anim.frames + ") / " + anim.frames);
uvAnim.add("offset", offset);
JsonArray scale = new JsonArray();
scale.add(1.0);
scale.add("1 / " + anim.frames);
uvAnim.add("scale", scale);
} else {
materialItem.addProperty("*", "Material.default");
}
materials.add(materialItem);
controller.add("materials", materials);
JsonArray textures = new JsonArray();
if (singleTexture) {
textures.add("Texture.default");
} else {
textures.add("Texture." + key);
}
controller.add("textures", textures);
// if (enable) {
JsonArray partVisibility = new JsonArray();
JsonObject visibilityDefault = new JsonObject();
visibilityDefault.addProperty("*", false);
partVisibility.add(visibilityDefault);
int i = 0;
List<String> sorted = new ArrayList<>(bones.keySet());
Map<String, String> originalId = new HashMap<>();
ListIterator<String> iterator = sorted.listIterator();
while (iterator.hasNext()) {
String s = iterator.next();
String o = s;
for (String r : NEED_REMOVE_WHEN_SORT) {
s = s.replace(r, "");
}
iterator.set(s);
originalId.put(s, o);
}
Collections.sort(sorted);
Set<String> uvAllBones = new HashSet<>();
for (String uvBone : uvBonesId) {
if (uvBone.equals("*")) {
uvAllBones.addAll(bones.keySet());
}
if (!bones.containsKey(uvBone.toLowerCase())) continue;
uvAllBones.add(uvBone.toLowerCase());
}
for (String boneName : sorted) {
boneName = originalId.get(boneName);
JsonObject visibilityItem = new JsonObject();
BoneData bone = bones.get(boneName);
boolean uvParent = false;
for (BoneData child : bone.getAllChildren()) {
if (child.getName().startsWith("uv_")) {
if (uvAllBones.contains(child.getName())) {
uvParent = true;
}
}
}
for (Map.Entry<String, Set<String>> entry : entity.getModelConfig().getBingingBones().entrySet()) {
if (entry.getKey().equals(key)) continue;
if (entry.getValue().stream().anyMatch(boneName::equalsIgnoreCase)) {
uvParent = false;
break;
}
}
if (!processedBones.contains(bone) && (uvParent || uvAllBones.contains(boneName) || uvBonesId.contains("*"))) {
int index = i;
if (boneName.startsWith("uv_")) {
index = sorted.indexOf(bone.getParent());
}
int n = (int) Math.pow(2, (index % 24));
if (entity.getModelConfig().isDisablePartVisibility()) {
visibilityItem.addProperty(boneName, true);
} else {
visibilityItem.addProperty(boneName, "math.mod(math.floor(query.property('" + namespace + ":bone" + index / 24 + "') / " + n + "), 2) == 1");
}
partVisibility.add(visibilityItem);
if (!uvBonesId.contains("*")) {
processedBones.add(bone);
}
}
if (!boneName.startsWith("uv_")) {
i++;
}
}
controller.add("part_visibility", partVisibility);
//}
}
return root.toString();
}
}

View File

@@ -0,0 +1,34 @@
package re.imc.geysermodelengineextension.managers.resourcepack.generator.data;
import java.util.Set;
public class BoneData {
private final String name;
private final String parent;
private final Set<BoneData> children;
private final Set<BoneData> allChildren;
public BoneData(String name, String parent, Set<BoneData> children, Set<BoneData> allChildren) {
this.name = name;
this.parent = parent;
this.children = children;
this.allChildren = allChildren;
}
public String getName() {
return name;
}
public String getParent() {
return parent;
}
public Set<BoneData> getChildren() {
return children;
}
public Set<BoneData> getAllChildren() {
return allChildren;
}
}

View File

@@ -0,0 +1,34 @@
package re.imc.geysermodelengineextension.managers.resourcepack.generator.data;
import java.util.Set;
public class TextureData {
private final String modelId;
private final String path;
private final Set<String> bindingBones;
private final byte[] image;
public TextureData(String modelId, String path, Set<String> bindingBones, byte[] image) {
this.modelId = modelId;
this.path = path;
this.bindingBones = bindingBones;
this.image = image;
}
public String getModelId() {
return modelId;
}
public String getPath() {
return path;
}
public Set<String> getBindingBones() {
return bindingBones;
}
public byte[] getImage() {
return image;
}
}

View File

@@ -0,0 +1,68 @@
package re.imc.geysermodelengineextension.util;
import java.util.*;
public class BooleanPacker {
public static final int MAX_BOOLEANS = 24;
public static int booleansToInt(List<Boolean> booleans) {
int result = 0;
int i = 1;
for (boolean b : booleans) {
if (b) result += i;
i *= 2;
}
return result;
}
public static int mapBooleansToInt(Map<String, Boolean> booleanMap) {
int result = 0;
int i = 1;
List<String> keys = new ArrayList<>(booleanMap.keySet());
Collections.sort(keys);
for (String key : keys) {
if (booleanMap.get(key)) result += i;
i *= 2;
}
return result;
}
public static List<Integer> booleansToInts(List<Boolean> booleans) {
List<Integer> results = new ArrayList<>();
int result = 0;
int i = 1;
int i1 = 1;
for (boolean b : booleans) {
if (b) {
result += i;
}
if (i1 % MAX_BOOLEANS == 0 || i1 == booleans.size()) {
results.add(result);
result = 0;
i = 1;
} else {
i *= 2;
}
i1++;
}
return results;
}
public static List<Integer> mapBooleansToInts(Map<String, Boolean> booleanMap) {
List<String> keys = new ArrayList<>(booleanMap.keySet());
List<Boolean> booleans = new ArrayList<>();
Collections.sort(keys);
for (String key : keys) {
booleans.add(booleanMap.get(key));
}
return booleansToInts(booleans);
}
}

View File

@@ -0,0 +1,146 @@
package re.imc.geysermodelengineextension.util;
import org.spongepowered.configurate.CommentedConfigurationNode;
import org.spongepowered.configurate.serialize.SerializationException;
import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
import re.imc.geysermodelengineextension.GeyserModelEngineExtension;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
public class FileConfiguration {
private final GeyserModelEngineExtension extension = GeyserModelEngineExtension.getExtension();
private final Path dataDirectory = extension.dataFolder();
protected final String configFile;
private final CommentedConfigurationNode configurationNode;
public FileConfiguration(String configFile) {
this.configFile = configFile;
this.configurationNode = load(configFile);
}
public FileConfiguration(CommentedConfigurationNode configurationNode, String configFile) {
this.configFile = configFile;
this.configurationNode = configurationNode;
}
private CommentedConfigurationNode load(String fileName) {
try {
if (!Files.exists(this.dataDirectory)) Files.createDirectories(this.dataDirectory);
Path config = this.dataDirectory.resolve(fileName);
FileUtils.createFiles(extension, fileName);
YamlConfigurationLoader loader = YamlConfigurationLoader.builder().path(config).build();
return loader.load();
} catch (IOException err) {
throw new RuntimeException(err);
}
}
public FileConfiguration getConfigurationSection(String path) {
CommentedConfigurationNode sectionNode = getConfigurationSectionNode(path);
if (sectionNode == null || sectionNode.virtual()) return null;
return new FileConfiguration(sectionNode, this.configFile);
}
public String getString(String path) {
CommentedConfigurationNode node = getInternal(path);
if (node == null) return null;
return node.getString();
}
public List<String> getStringList(String path) {
CommentedConfigurationNode node = getInternal(path);
if (node == null || node.virtual()) return List.of();
try {
return node.getList(String.class, List.of());
} catch (SerializationException err) {
throw new RuntimeException(err);
}
}
public int getInt(String path) {
CommentedConfigurationNode node = getInternal(path);
if (node == null) return 0;
return node.getInt();
}
public List<Integer> getIntegerList(String path) {
CommentedConfigurationNode node = getInternal(path);
if (node == null || node.virtual()) return List.of();
try {
return node.getList(Integer.class, List.of());
} catch (SerializationException err) {
throw new RuntimeException(err);
}
}
public double getDouble(String path) {
CommentedConfigurationNode node = getInternal(path);
if (node == null) return 0;
return node.getDouble();
}
public double getLong(String path) {
CommentedConfigurationNode node = getInternal(path);
if (node == null) return 0;
return node.getLong();
}
public List<Long> getLongList(String path) {
CommentedConfigurationNode node = getInternal(path);
if (node == null || node.virtual()) return List.of();
try {
return node.getList(Long.class, List.of());
} catch (SerializationException err) {
throw new RuntimeException(err);
}
}
public boolean getBoolean(String path) {
CommentedConfigurationNode node = getInternal(path);
if (node == null) return false;
return node.getBoolean();
}
public boolean isBoolean(String path) {
CommentedConfigurationNode node = getInternal(path);
return node != null && node.raw() instanceof Boolean;
}
public File toFile() {
return this.dataDirectory.resolve(configFile).toFile();
}
private CommentedConfigurationNode getInternal(String path) {
CommentedConfigurationNode node = toSplitRoot(path, this.configurationNode);
if (node.virtual()) return null;
return node;
}
private CommentedConfigurationNode toSplitRoot(String path, CommentedConfigurationNode node) {
if (path == null) return node;
path = path.startsWith(".") ? path.substring(1) : path;
return node.node(path.contains(".") ? path.split("\\.") : new Object[]{path});
}
private CommentedConfigurationNode getConfigurationSectionNode(String path) {
return getInternal(path);
}
public CommentedConfigurationNode getRootNode() {
return configurationNode;
}
}

View File

@@ -0,0 +1,52 @@
package re.imc.geysermodelengineextension.util;
import re.imc.geysermodelengineextension.GeyserModelEngineExtension;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
public class FileUtils {
public static List<File> getAllFiles(File folder, String fileType) {
List<File> files = new ArrayList<>();
if (folder == null || !folder.exists()) return files;
for (File file : folder.listFiles()) {
if (file.isDirectory()) {
files.addAll(getAllFiles(file, fileType));
} else if (file.getName().endsWith(fileType)) {
files.add(file);
}
}
return files;
}
public static void createFiles(GeyserModelEngineExtension extension, String fileName) {
Path config = extension.dataFolder().resolve(fileName);
if (Files.exists(config)) return;
try {
Path parentDirectory = config.getParent();
if (parentDirectory != null && !Files.exists(parentDirectory)) Files.createDirectories(parentDirectory);
try (InputStream resourceAsStream = extension.getClass().getClassLoader().getResourceAsStream("Extension/" + fileName)) {
if (resourceAsStream == null) {
extension.logger().warning(fileName + " is invalid!");
return;
}
Files.copy(resourceAsStream, config);
} catch (IOException err) {
throw new RuntimeException(err);
}
} catch (IOException err) {
throw new RuntimeException(err);
}
}
}

View File

@@ -0,0 +1,49 @@
package re.imc.geysermodelengineextension.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class ZipUtil {
public static void compressFolder(File folder, String folderName, ZipOutputStream zipOutputStream) throws IOException {
File[] files = folder.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
if (folderName == null) {
compressFolder(file, file.getName(), zipOutputStream);
continue;
}
compressFolder(file, folderName + "/" + file.getName(), zipOutputStream);
} else {
if (folderName == null) {
addToZipFile(file.getName(), file, zipOutputStream);
continue;
}
addToZipFile(folderName + "/" + file.getName(), file, zipOutputStream);
}
}
}
}
private static void addToZipFile(String fileName, File file, ZipOutputStream zipOutputStream) throws IOException {
ZipEntry entry = new ZipEntry(fileName);
zipOutputStream.putNextEntry(entry);
try (FileInputStream fileInputStream = new FileInputStream(file)) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = fileInputStream.read(buffer)) != -1) {
zipOutputStream.write(buffer, 0, bytesRead);
}
}
zipOutputStream.closeEntry();
}
}

View File

@@ -0,0 +1,4 @@
commands:
geysermodelenginepackgenerator:
reload:
successfully-reloaded: "GeyserModelEnginePackGenerator reloaded!"

View File

@@ -0,0 +1,6 @@
models:
namespace: "modelengine"
options:
resource-pack:
auto-load: true

View File

@@ -0,0 +1,8 @@
name: GeyserModelEngineExtension
id: geysermodelengineextension
main: re.imc.geysermodelengineextension.GeyserModelEngineExtension
api: 2.7.0
version: 1.0.0
authors:
- zimzaza4
- xSquishyLiam

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.0-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

70
paper/build.gradle.kts Normal file
View File

@@ -0,0 +1,70 @@
plugins {
id("java")
id("com.gradleup.shadow") version "9.2.2"
}
group = "re.imc"
version = "1.0.1"
repositories {
mavenCentral()
maven("https://repo.papermc.io/repository/maven-public/")
maven("https://central.sonatype.com/repository/maven-snapshots/")
maven("https://mvn.lumine.io/repository/maven-public/")
maven("https://repo.opencollab.dev/main/")
maven("https://repo.codemc.io/repository/maven-public/")
maven("https://repo.codemc.io/repository/maven-releases/")
}
dependencies {
compileOnly("io.papermc.paper:paper-api:1.21.10-R0.1-SNAPSHOT")
// implementation("dev.jorel:commandapi-paper-shade:11.0.0")
compileOnly("com.ticxo.modelengine:ModelEngine:R4.0.9")
compileOnly("io.github.toxicity188:bettermodel:1.14.0")
compileOnly(files("libs/geyserutils-spigot-1.0-SNAPSHOT.jar"))
compileOnly("org.geysermc.floodgate:api:2.2.4-SNAPSHOT")
implementation("com.github.retrooper:packetevents-spigot:2.11.0")
implementation("org.bstats:bstats-bukkit:3.0.2")
implementation("org.reflections:reflections:0.10.2")
}
java {
toolchain.languageVersion.set(JavaLanguageVersion.of(21))
}
tasks.compileJava {
options.encoding = "UTF-8"
}
tasks.shadowJar {
archiveFileName.set("${rootProject.name}-${version}.jar")
relocate("dev.jorel.commandapi", "re.imc.geysermodelengine.libs.commandapi")
relocate("com.github.retrooper", "re.imc.geysermodelengine.libs.com.github.retrooper.packetevents")
relocate("io.github.retrooper", "re.imc.geysermodelengine.libs.io.github.retrooper.packetevents")
relocate("org.bstats", "re.imc.geysermodelengine.libs.bstats")
relocate("org.reflections", "re.imc.geysermodelengine.libs.reflections")
}
tasks.build {
dependsOn("shadowJar")
}
tasks.processResources {
val props = mapOf("version" to version)
inputs.properties(props)
filteringCharset = "UTF-8"
filesMatching("paper-plugin.yml") {
expand(props)
}
}

View File

@@ -2,23 +2,20 @@ package re.imc.geysermodelengine;
import com.github.retrooper.packetevents.PacketEvents; import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.event.PacketListenerPriority; import com.github.retrooper.packetevents.event.PacketListenerPriority;
import com.ticxo.modelengine.api.model.ActiveModel;
import dev.jorel.commandapi.CommandAPI;
import dev.jorel.commandapi.CommandAPIBukkitConfig;
import io.github.retrooper.packetevents.factory.spigot.SpigotPacketEventsBuilder; import io.github.retrooper.packetevents.factory.spigot.SpigotPacketEventsBuilder;
import org.bstats.bukkit.Metrics;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import re.imc.geysermodelengine.hooks.FloodgateAPIHook;
import re.imc.geysermodelengine.listener.ModelListener; import re.imc.geysermodelengine.listener.ModelListener;
import re.imc.geysermodelengine.listener.MountPacketListener; import re.imc.geysermodelengine.listener.MountPacketListener;
import re.imc.geysermodelengine.managers.ConfigManager; import re.imc.geysermodelengine.managers.ConfigManager;
import re.imc.geysermodelengine.managers.commands.CommandManager; import re.imc.geysermodelengine.managers.commands.CommandManager;
import re.imc.geysermodelengine.managers.model.EntityTaskManager; import re.imc.geysermodelengine.managers.model.EntityTaskManager;
import re.imc.geysermodelengine.managers.model.ModelManager; import re.imc.geysermodelengine.managers.model.ModelManager;
import re.imc.geysermodelengine.managers.model.data.ModelEntityData;
import re.imc.geysermodelengine.runnables.BedrockMountControlRunnable; import re.imc.geysermodelengine.runnables.BedrockMountControlRunnable;
import re.imc.geysermodelengine.runnables.UpdateTaskRunnable; import re.imc.geysermodelengine.runnables.UpdateTaskRunnable;
import java.util.*;
import java.util.concurrent.*; import java.util.concurrent.*;
public class GeyserModelEngine extends JavaPlugin { public class GeyserModelEngine extends JavaPlugin {
@@ -37,7 +34,8 @@ public class GeyserModelEngine extends JavaPlugin {
PacketEvents.setAPI(SpigotPacketEventsBuilder.build(this)); PacketEvents.setAPI(SpigotPacketEventsBuilder.build(this));
PacketEvents.getAPI().load(); PacketEvents.getAPI().load();
CommandAPI.onLoad(new CommandAPIBukkitConfig(this)); // CommandAPI.onLoad(new CommandAPIPaperConfig(this));
preLoadManagers();
} }
@Override @Override
@@ -46,6 +44,8 @@ public class GeyserModelEngine extends JavaPlugin {
loadManagers(); loadManagers();
loadRunnables(); loadRunnables();
loadBStats();
PacketEvents.getAPI().getEventManager().registerListener(new MountPacketListener(this), PacketListenerPriority.NORMAL); PacketEvents.getAPI().getEventManager().registerListener(new MountPacketListener(this), PacketListenerPriority.NORMAL);
Bukkit.getPluginManager().registerEvents(new ModelListener(this), this); Bukkit.getPluginManager().registerEvents(new ModelListener(this), this);
@@ -53,25 +53,27 @@ public class GeyserModelEngine extends JavaPlugin {
@Override @Override
public void onDisable() { public void onDisable() {
this.modelManager.removeEntities();
PacketEvents.getAPI().terminate(); PacketEvents.getAPI().terminate();
// CommandAPI.onDisable();
for (Map<ActiveModel, ModelEntityData> entities : modelManager.getEntitiesCache().values()) {
entities.forEach((model, modelEntity) -> {
modelEntity.getEntity().remove();
});
}
CommandAPI.onDisable();
} }
private void loadHooks() { private void loadHooks() {
PacketEvents.getAPI().init(); PacketEvents.getAPI().init();
CommandAPI.onEnable(); FloodgateAPIHook.loadHook(this);
// CommandAPI.onEnable();
}
private void loadBStats() {
if (this.configManager.getConfig().getBoolean("metrics.bstats", true)) new Metrics(this, 26981);
}
private void preLoadManagers() {
this.configManager = new ConfigManager(this);
} }
private void loadManagers() { private void loadManagers() {
this.configManager = new ConfigManager(this);
this.commandManager = new CommandManager(this); this.commandManager = new CommandManager(this);
this.modelManager = new ModelManager(this); this.modelManager = new ModelManager(this);
@@ -79,10 +81,9 @@ public class GeyserModelEngine extends JavaPlugin {
} }
private void loadRunnables() { private void loadRunnables() {
this.schedulerPool = Executors.newScheduledThreadPool(configManager.getConfig().getInt("thread-pool-size", 4)); this.schedulerPool = Executors.newScheduledThreadPool(configManager.getConfig().getInt("models.thread-pool-size", 4));
this.schedulerPool.scheduleAtFixedRate(new UpdateTaskRunnable(this), 10, configManager.getConfig().getLong("models.entity-position-update-period", 35), TimeUnit.MILLISECONDS);
Bukkit.getAsyncScheduler().runAtFixedRate(this, new UpdateTaskRunnable(this), 10, configManager.getConfig().getLong("entity-position-update-period", 35), TimeUnit.MILLISECONDS); this.schedulerPool.scheduleAtFixedRate(new BedrockMountControlRunnable(this), 1, 1, TimeUnit.MILLISECONDS);
Bukkit.getAsyncScheduler().runAtFixedRate(this, new BedrockMountControlRunnable(this), 1, 1, TimeUnit.MILLISECONDS);
} }
public ConfigManager getConfigManager() { public ConfigManager getConfigManager() {

View File

@@ -0,0 +1,26 @@
package re.imc.geysermodelengine.commands.geysermodelenginecommands;
import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.managers.commands.subcommands.SubCommands;
import re.imc.geysermodelengine.util.ColourUtils;
public class GeyserModelEngineReloadCommand implements SubCommands {
private final GeyserModelEngine plugin;
private final ColourUtils colourUtils = new ColourUtils();
public GeyserModelEngineReloadCommand(GeyserModelEngine plugin) {
this.plugin = plugin;
}
// @Override
// public CommandAPICommand onCommand() {
// return new CommandAPICommand("reload")
// .withPermission("geysermodelengine.commands.reload")
// .executes((sender, args) -> {
// Bukkit.getAsyncScheduler().runNow(plugin, scheduledTask -> plugin.getConfigManager().load());
// sender.sendMessage(colourUtils.miniFormat(plugin.getConfigManager().getLang().getString("commands.reload.successfully-reloaded")));
// });
// }
}

View File

@@ -0,0 +1,24 @@
package re.imc.geysermodelengine.hooks;
import org.bukkit.Bukkit;
import org.geysermc.floodgate.api.FloodgateApi;
import re.imc.geysermodelengine.GeyserModelEngine;
public class FloodgateAPIHook {
private static FloodgateApi floodgateAPI;
public static void loadHook(GeyserModelEngine plugin) {
if (Bukkit.getPluginManager().getPlugin("floodgate") == null || !plugin.getConfigManager().getConfig().getBoolean("options.hooks.floodgate", true)) {
plugin.getLogger().info("Floodgate hook disabled!");
return;
}
floodgateAPI = FloodgateApi.getInstance();
plugin.getLogger().info("Floodgate hook enabled!");
}
public static FloodgateApi getAPI() {
return floodgateAPI;
}
}

View File

@@ -0,0 +1,38 @@
package re.imc.geysermodelengine.listener;
import kr.toxicity.model.api.event.CreateEntityTrackerEvent;
import org.bukkit.entity.Entity;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.managers.model.entity.BetterModelEntityData;
import re.imc.geysermodelengine.managers.model.model.Model;
public class BetterModelListener implements Listener {
private final GeyserModelEngine plugin;
public BetterModelListener(GeyserModelEngine plugin) {
this.plugin = plugin;
}
@EventHandler(priority = EventPriority.MONITOR)
public void onModelSpawn(CreateEntityTrackerEvent event) {
plugin.getModelManager().getModelHandler().createModel(event.sourceEntity(), event.getTracker(), event.tracker());
}
@EventHandler
public void onModelDamage(EntityDamageByEntityEvent event) {
Entity entity = event.getEntity();
Model model = plugin.getModelManager().getModelEntitiesCache().get(entity.getEntityId());
if (model == null) return;
BetterModelEntityData entityData = (BetterModelEntityData) model.getEntityData();
entityData.setHurt(true);
}
}

View File

@@ -0,0 +1,58 @@
package re.imc.geysermodelengine.listener;
import com.ticxo.modelengine.api.events.*;
import com.ticxo.modelengine.api.model.ActiveModel;
import org.apache.commons.lang3.tuple.Pair;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.managers.model.entity.EntityData;
import re.imc.geysermodelengine.managers.model.model.Model;
import java.util.Map;
public class ModelEngineListener implements Listener {
private final GeyserModelEngine plugin;
public ModelEngineListener(GeyserModelEngine plugin) {
this.plugin = plugin;
}
@EventHandler(priority = EventPriority.MONITOR)
public void onAddModel(AddModelEvent event) {
if (event.isCancelled()) return;
plugin.getModelManager().getModelHandler().createModel(event.getTarget(), event.getModel());
}
// Needs Testing
@EventHandler(priority = EventPriority.MONITOR)
public void onModelMount(ModelMountEvent event) {
if (!event.isDriver()) return;
ActiveModel activeModel = event.getVehicle();
if (activeModel == null) return;
int entityID = activeModel.getModeledEntity().getBase().getEntityId();
Map<Model, EntityData> entityDataCache = plugin.getModelManager().getEntitiesCache().get(entityID);
if (entityDataCache == null) return;
Model model = plugin.getModelManager().getModelEntitiesCache().get(entityID);
EntityData entityData = entityDataCache.get(model);
if (entityData != null && event.getPassenger() instanceof Player player) {
plugin.getModelManager().getDriversCache().put(player.getUniqueId(), Pair.of(event.getVehicle(), event.getSeat()));
}
}
@EventHandler(priority = EventPriority.MONITOR)
public void onModelDismount(ModelDismountEvent event) {
if (event.getPassenger() instanceof Player player) {
plugin.getModelManager().getDriversCache().remove(player.getUniqueId());
}
}
}

View File

@@ -0,0 +1,50 @@
package re.imc.geysermodelengine.listener;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.world.WorldInitEvent;
import org.geysermc.floodgate.api.FloodgateApi;
import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.util.BedrockUtils;
public class ModelListener implements Listener {
private final GeyserModelEngine plugin;
public ModelListener(GeyserModelEngine plugin) {
this.plugin = plugin;
}
/*
/ xSquishyLiam:
/ May change this into a better system?
*/
@EventHandler
public void onWorldInit(WorldInitEvent event) {
World world = event.getWorld();
world.getEntities().forEach(entity -> plugin.getModelManager().getModelHandler().processEntities(entity));
}
/*
/ xSquishyLiam:
/ A runDelay makes sure the client doesn't see pigs on login due to the client resyncing themselves back to normal
*/
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
if (!BedrockUtils.isBedrockPlayer(player)) return;
Bukkit.getGlobalRegionScheduler().runDelayed(plugin, scheduledTask -> plugin.getModelManager().getPlayerJoinedCache().add(player.getUniqueId()), 10);
}
@EventHandler
public void onPlayerQuit(PlayerQuitEvent event) {
Player player = event.getPlayer();
if (!BedrockUtils.isBedrockPlayer(player)) return;
plugin.getModelManager().getPlayerJoinedCache().remove(player.getUniqueId());
}
}

View File

@@ -11,6 +11,7 @@ import org.apache.commons.lang3.tuple.Pair;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.geysermc.floodgate.api.FloodgateApi; import org.geysermc.floodgate.api.FloodgateApi;
import re.imc.geysermodelengine.GeyserModelEngine; import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.util.BedrockUtils;
public class MountPacketListener implements PacketListener { public class MountPacketListener implements PacketListener {
@@ -23,9 +24,9 @@ public class MountPacketListener implements PacketListener {
@Override @Override
public void onPacketReceive(PacketReceiveEvent event) { public void onPacketReceive(PacketReceiveEvent event) {
if (event.getPacketType() != PacketType.Play.Client.ENTITY_ACTION) return; if (event.getPacketType() != PacketType.Play.Client.ENTITY_ACTION) return;
if (!FloodgateApi.getInstance().isFloodgatePlayer(event.getUser().getUUID())) return;
Player player = event.getPlayer(); Player player = event.getPlayer();
if (!BedrockUtils.isBedrockPlayer(player)) return;
WrapperPlayClientEntityAction action = new WrapperPlayClientEntityAction(event); WrapperPlayClientEntityAction action = new WrapperPlayClientEntityAction(event);
Pair<ActiveModel, Mount> seat = plugin.getModelManager().getDriversCache().get(player.getUniqueId()); Pair<ActiveModel, Mount> seat = plugin.getModelManager().getDriversCache().get(player.getUniqueId());
@@ -33,6 +34,6 @@ public class MountPacketListener implements PacketListener {
if (seat == null) return; if (seat == null) return;
if (action.getAction() != WrapperPlayClientEntityAction.Action.START_SNEAKING) return; if (action.getAction() != WrapperPlayClientEntityAction.Action.START_SNEAKING) return;
ModelEngineAPI.getMountPairManager().tryDismount(event.getPlayer()); ModelEngineAPI.getMountPairManager().tryDismount(player);
} }
} }

View File

@@ -21,8 +21,8 @@ public class CommandManager {
for (Class<?> clazz : new Reflections(path).getSubTypesOf(CommandManagers.class)) { for (Class<?> clazz : new Reflections(path).getSubTypesOf(CommandManagers.class)) {
try { try {
CommandManagers commandManager = (CommandManagers) clazz.getDeclaredConstructor(GeyserModelEngine.class).newInstance(plugin); CommandManagers commandManager = (CommandManagers) clazz.getDeclaredConstructor(GeyserModelEngine.class).newInstance(plugin);
plugin.getLogger().warning("Loading Command Manager - " + commandManager.name()); plugin.getLogger().info("Loading Command Manager - " + commandManager.getName());
commandManagersCache.put(commandManager.name(), commandManager); commandManagersCache.put(commandManager.getName(), commandManager);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException err) { } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException err) {
plugin.getLogger().severe("Failed to load Command Manager " + clazz.getName()); plugin.getLogger().severe("Failed to load Command Manager " + clazz.getName());
throw new RuntimeException(err); throw new RuntimeException(err);

View File

@@ -6,7 +6,13 @@ import java.util.ArrayList;
public interface CommandManagers { public interface CommandManagers {
String name(); /**
* Gets the name of the command manager
*/
String getName();
/**
* Gets the command manager subcommands
*/
ArrayList<SubCommands> getCommands(); ArrayList<SubCommands> getCommands();
} }

View File

@@ -1,6 +1,5 @@
package re.imc.geysermodelengine.managers.commands.managers.geysermodelengine; package re.imc.geysermodelengine.managers.commands.managers.geysermodelengine;
import dev.jorel.commandapi.CommandAPICommand;
import re.imc.geysermodelengine.GeyserModelEngine; import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.commands.geysermodelenginecommands.GeyserModelEngineReloadCommand; import re.imc.geysermodelengine.commands.geysermodelenginecommands.GeyserModelEngineReloadCommand;
import re.imc.geysermodelengine.managers.commands.CommandManagers; import re.imc.geysermodelengine.managers.commands.CommandManagers;
@@ -19,15 +18,15 @@ public class GeyserModelEngineCommandManager implements CommandManagers {
} }
private void registerCommand() { private void registerCommand() {
CommandAPICommand geyserModelEngineCommand = new CommandAPICommand(name()); // CommandAPICommand geyserModelEngineCommand = new CommandAPICommand(getName());
//
commands.forEach(subCommands -> geyserModelEngineCommand.withSubcommand(subCommands.onCommand())); // commands.forEach(subCommands -> geyserModelEngineCommand.withSubcommand(subCommands.onCommand()));
//
geyserModelEngineCommand.register(); // geyserModelEngineCommand.register();
} }
@Override @Override
public String name() { public String getName() {
return "geysermodelengine"; return "geysermodelengine";
} }

View File

@@ -1,7 +1,9 @@
package re.imc.geysermodelengine.managers.commands.subcommands; package re.imc.geysermodelengine.managers.commands.subcommands;
import dev.jorel.commandapi.CommandAPICommand;
public interface SubCommands { public interface SubCommands {
CommandAPICommand onCommand();
/**
* Subcommand setup
*/
// CommandAPICommand onCommand();
} }

View File

@@ -0,0 +1,102 @@
package re.imc.geysermodelengine.managers.model;
import com.ticxo.modelengine.api.animation.BlueprintAnimation;
import com.ticxo.modelengine.api.model.ActiveModel;
import me.zimzaza4.geyserutils.spigot.api.EntityUtils;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.managers.model.entity.EntityData;
import re.imc.geysermodelengine.managers.model.propertyhandler.BetterModelPropertyHandler;
import re.imc.geysermodelengine.managers.model.propertyhandler.ModelEnginePropertyHandler;
import re.imc.geysermodelengine.managers.model.propertyhandler.PropertyHandler;
import re.imc.geysermodelengine.managers.model.entity.ModelEngineEntityData;
import re.imc.geysermodelengine.managers.model.taskshandler.TaskHandler;
import re.imc.geysermodelengine.packet.entity.PacketEntity;
import re.imc.geysermodelengine.util.BedrockUtils;
import java.util.*;
public class EntityTaskManager {
private final GeyserModelEngine plugin;
private PropertyHandler propertyHandler;
public EntityTaskManager(GeyserModelEngine plugin) {
this.plugin = plugin;
if (Bukkit.getPluginManager().getPlugin("ModelEngine") != null) {
this.propertyHandler = new ModelEnginePropertyHandler(plugin);
plugin.getLogger().info("Using ModelEngine property handler!");
} else if (Bukkit.getPluginManager().getPlugin("BetterModel") != null) {
this.propertyHandler = new BetterModelPropertyHandler(plugin);
plugin.getLogger().info("Using BetterModel property handler!");
} else {
plugin.getLogger().severe("No supported model engine found!");
plugin.getServer().getPluginManager().disablePlugin(plugin);
}
}
public void checkViewers(EntityData model, Set<Player> viewers) {
for (Player onlinePlayer : Bukkit.getOnlinePlayers()) {
if (!BedrockUtils.isBedrockPlayer(onlinePlayer)) continue;
if (canSee(onlinePlayer, model.getEntity())) {
if (!viewers.contains(onlinePlayer)) {
sendSpawnPacket(model, onlinePlayer);
viewers.add(onlinePlayer);
}
} else {
if (viewers.contains(onlinePlayer)) {
model.getEntity().sendEntityDestroyPacket(Collections.singletonList(onlinePlayer));
viewers.remove(onlinePlayer);
}
}
}
}
private void sendSpawnPacket(EntityData model, Player onlinePlayer) {
TaskHandler task = model.getEntityTask();
boolean firstJoined = !plugin.getModelManager().getPlayerJoinedCache().contains(onlinePlayer.getUniqueId());
if (firstJoined) {
task.sendEntityData(model, onlinePlayer, plugin.getConfigManager().getConfig().getInt("models.join-send-delay") / 50);
} else {
task.sendEntityData(model, onlinePlayer, 5);
}
}
public boolean canSee(Player player, PacketEntity entity) {
if (!player.isOnline()) return false;
if (!plugin.getModelManager().getPlayerJoinedCache().contains(player.getUniqueId())) return false;
Location playerLocation = player.getLocation().clone();
Location entityLocation = entity.getLocation().clone();
playerLocation.setY(0);
entityLocation.setY(0);
if (playerLocation.getWorld() != entityLocation.getWorld()) return false;
if (playerLocation.distanceSquared(entityLocation) > player.getSendViewDistance() * player.getSendViewDistance() * 48) return false;
return true;
}
public void sendHitBoxToAll(EntityData model) {
for (Player viewer : model.getViewers()) {
EntityUtils.sendCustomHitBox(viewer, model.getEntity().getEntityId(), 0.01f, 0.01f);
}
}
//TODO move this
public boolean hasAnimation(ModelEngineEntityData model, String animation) {
ActiveModel activeModel = model.getActiveModel();
BlueprintAnimation animationProperty = activeModel.getBlueprint().getAnimations().get(animation);
return !(animationProperty == null);
}
public PropertyHandler getPropertyHandler() {
return propertyHandler;
}
}

View File

@@ -0,0 +1,74 @@
package re.imc.geysermodelengine.managers.model;
import com.ticxo.modelengine.api.model.ActiveModel;
import com.ticxo.modelengine.api.model.bone.type.Mount;
import org.apache.commons.lang3.tuple.Pair;
import org.bukkit.Bukkit;
import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.managers.model.entity.EntityData;
import re.imc.geysermodelengine.managers.model.model.Model;
import re.imc.geysermodelengine.managers.model.modelhandler.BetterModelHandler;
import re.imc.geysermodelengine.managers.model.modelhandler.ModelEngineHandler;
import re.imc.geysermodelengine.managers.model.modelhandler.ModelHandler;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class ModelManager {
private final GeyserModelEngine plugin;
private ModelHandler modelHandler;
private final HashSet<UUID> playerJoinedCache = new HashSet<>();
private final ConcurrentHashMap<Integer, Model> modelEntitiesCache = new ConcurrentHashMap<>();
private final ConcurrentHashMap<Integer, Map<Model, EntityData>> entitiesCache = new ConcurrentHashMap<>();
// MEG ONLY
private final ConcurrentHashMap<UUID, Pair<ActiveModel, Mount>> driversCache = new ConcurrentHashMap<>();
public ModelManager(GeyserModelEngine plugin) {
this.plugin = plugin;
if (Bukkit.getPluginManager().getPlugin("ModelEngine") != null) {
this.modelHandler = new ModelEngineHandler(plugin);
plugin.getLogger().info("Using ModelEngine handler!");
} else if (Bukkit.getPluginManager().getPlugin("BetterModel") != null) {
this.modelHandler = new BetterModelHandler(plugin);
plugin.getLogger().info("Using BetterModel handler!");
} else {
plugin.getLogger().severe("No supported model engine found!");
plugin.getServer().getPluginManager().disablePlugin(plugin);
return;
}
modelHandler.loadListeners();
}
public void removeEntities() {
for (Map<Model, EntityData> entities : entitiesCache.values()) {
entities.forEach((model, modelEntity) -> modelEntity.getEntity().remove());
}
}
public ModelHandler getModelHandler() {
return modelHandler;
}
public HashSet<UUID> getPlayerJoinedCache() {
return playerJoinedCache;
}
public ConcurrentHashMap<Integer, Map<Model, EntityData>> getEntitiesCache() {
return entitiesCache;
}
public ConcurrentHashMap<Integer, Model> getModelEntitiesCache() {
return modelEntitiesCache;
}
public ConcurrentHashMap<UUID, Pair<ActiveModel, Mount>> getDriversCache() {
return driversCache;
}
}

View File

@@ -0,0 +1,86 @@
package re.imc.geysermodelengine.managers.model.entity;
import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes;
import com.google.common.collect.Sets;
import kr.toxicity.model.api.entity.BaseEntity;
import kr.toxicity.model.api.tracker.EntityTracker;
import kr.toxicity.model.api.tracker.Tracker;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.managers.model.taskshandler.BetterModelTaskHandler;
import re.imc.geysermodelengine.packet.entity.PacketEntity;
import java.util.Set;
public class BetterModelEntityData implements EntityData {
private final GeyserModelEngine plugin;
private final PacketEntity entity;
private final Set<Player> viewers = Sets.newConcurrentHashSet();
private final BaseEntity entitySource;
private final Tracker tracker;
private final EntityTracker entityTracker;
private BetterModelTaskHandler entityTask;
private boolean hurt;
public BetterModelEntityData(GeyserModelEngine plugin, BaseEntity entitySource, Tracker tracker, EntityTracker entityTracker) {
this.plugin = plugin;
this.entitySource = entitySource;
this.tracker = tracker;
this.entityTracker = entityTracker;
this.entity = new PacketEntity(EntityTypes.PIG, viewers, entitySource.location());
runEntityTask();
}
@Override
public void teleportToModel() {
Location location = entitySource.location();
entity.teleport(location);
}
public void runEntityTask() {
entityTask = new BetterModelTaskHandler(plugin, this);
}
@Override
public PacketEntity getEntity() {
return entity;
}
@Override
public Set<Player> getViewers() {
return viewers;
}
@Override
public BetterModelTaskHandler getEntityTask() {
return entityTask;
}
public void setHurt(boolean hurt) {
this.hurt = hurt;
}
public BaseEntity getEntitySource() {
return entitySource;
}
public Tracker getTracker() {
return tracker;
}
public EntityTracker getEntityTracker() {
return entityTracker;
}
public boolean isHurt() {
return hurt;
}
}

View File

@@ -0,0 +1,30 @@
package re.imc.geysermodelengine.managers.model.entity;
import org.bukkit.entity.Player;
import re.imc.geysermodelengine.managers.model.taskshandler.TaskHandler;
import re.imc.geysermodelengine.packet.entity.PacketEntity;
import java.util.Set;
public interface EntityData {
/**
* Teleports the packet entity to the model
*/
void teleportToModel();
/**
* Gets the packet Entity
*/
PacketEntity getEntity();
/**
* Gets the entity view of players
*/
Set<Player> getViewers();
/**
* Get the entity task handler
*/
TaskHandler getEntityTask();
}

View File

@@ -1,4 +1,4 @@
package re.imc.geysermodelengine.managers.model.data; package re.imc.geysermodelengine.managers.model.entity;
import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes; import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
@@ -7,57 +7,58 @@ import com.ticxo.modelengine.api.model.ModeledEntity;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import re.imc.geysermodelengine.GeyserModelEngine; import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.managers.model.taskshandler.ModelEngineTaskHandler;
import re.imc.geysermodelengine.packet.entity.PacketEntity; import re.imc.geysermodelengine.packet.entity.PacketEntity;
import re.imc.geysermodelengine.runnables.EntityTaskRunnable;
import java.util.Set; import java.util.Set;
public class ModelEntityData { public class ModelEngineEntityData implements EntityData {
private final GeyserModelEngine plugin; private final GeyserModelEngine plugin;
private PacketEntity entity; private final PacketEntity entity;
private final Set<Player> viewers = Sets.newConcurrentHashSet(); private final Set<Player> viewers = Sets.newConcurrentHashSet();
private final ModeledEntity modeledEntity; private final ModeledEntity modeledEntity;
private final ActiveModel activeModel; private final ActiveModel activeModel;
private EntityTaskRunnable entityTask; private ModelEngineTaskHandler entityTask;
public ModelEntityData(GeyserModelEngine plugin, ModeledEntity modeledEntity, ActiveModel model) { public ModelEngineEntityData(GeyserModelEngine plugin, ModeledEntity modeledEntity, ActiveModel activeModel) {
this.plugin = plugin; this.plugin = plugin;
this.modeledEntity = modeledEntity; this.modeledEntity = modeledEntity;
this.activeModel = model; this.activeModel = activeModel;
this.entity = spawnEntity(); this.entity = new PacketEntity(EntityTypes.PIG, viewers, modeledEntity.getBase().getLocation());
runEntityTask(); runEntityTask();
} }
@Override
public void teleportToModel() { public void teleportToModel() {
Location location = modeledEntity.getBase().getLocation(); Location location = modeledEntity.getBase().getLocation();
entity.teleport(location); entity.teleport(location);
} }
public PacketEntity spawnEntity() {
entity = new PacketEntity(EntityTypes.PIG, viewers, modeledEntity.getBase().getLocation());
return entity;
}
public void runEntityTask() { public void runEntityTask() {
entityTask = new EntityTaskRunnable(plugin, this); entityTask = new ModelEngineTaskHandler(plugin, this);
} }
@Override
public PacketEntity getEntity() { public PacketEntity getEntity() {
return entity; return entity;
} }
@Override
public Set<Player> getViewers() { public Set<Player> getViewers() {
return viewers; return viewers;
} }
@Override
public ModelEngineTaskHandler getEntityTask() {
return entityTask;
}
public ModeledEntity getModeledEntity() { public ModeledEntity getModeledEntity() {
return modeledEntity; return modeledEntity;
} }
@@ -65,8 +66,4 @@ public class ModelEntityData {
public ActiveModel getActiveModel() { public ActiveModel getActiveModel() {
return activeModel; return activeModel;
} }
public EntityTaskRunnable getEntityTask() {
return entityTask;
}
} }

View File

@@ -0,0 +1,41 @@
package re.imc.geysermodelengine.managers.model.model;
import kr.toxicity.model.api.tracker.Tracker;
import re.imc.geysermodelengine.managers.model.entity.EntityData;
import re.imc.geysermodelengine.managers.model.modelhandler.ModelHandler;
import re.imc.geysermodelengine.managers.model.propertyhandler.PropertyHandler;
public class BetterModelModel implements Model {
private final Tracker tracker;
private final ModelHandler modelHandler;
private final EntityData entityData;
private final PropertyHandler propertyHandler;
public BetterModelModel(Tracker tracker, ModelHandler modelHandler, EntityData entityData, PropertyHandler propertyHandler) {
this.tracker = tracker;
this.modelHandler = modelHandler;
this.entityData = entityData;
this.propertyHandler = propertyHandler;
}
@Override
public String getName() {
return tracker.name();
}
@Override
public ModelHandler getModelHandler() {
return modelHandler;
}
@Override
public EntityData getEntityData() {
return entityData;
}
@Override
public PropertyHandler getPropertyHandler() {
return propertyHandler;
}
}

View File

@@ -0,0 +1,28 @@
package re.imc.geysermodelengine.managers.model.model;
import re.imc.geysermodelengine.managers.model.entity.EntityData;
import re.imc.geysermodelengine.managers.model.modelhandler.ModelHandler;
import re.imc.geysermodelengine.managers.model.propertyhandler.PropertyHandler;
public interface Model {
/**
* Gets the model's name
*/
String getName();
/**
* Gets the model's entity data
*/
EntityData getEntityData();
/**
* Gets the model's model handler
*/
ModelHandler getModelHandler();
/**
* Gets the model's property handler
*/
PropertyHandler getPropertyHandler();
}

View File

@@ -0,0 +1,45 @@
package re.imc.geysermodelengine.managers.model.model;
import com.ticxo.modelengine.api.model.ActiveModel;
import re.imc.geysermodelengine.managers.model.entity.EntityData;
import re.imc.geysermodelengine.managers.model.modelhandler.ModelHandler;
import re.imc.geysermodelengine.managers.model.propertyhandler.PropertyHandler;
public class ModelEngineModel implements Model {
private final ActiveModel activeModel;
private final ModelHandler modelHandler;
private final EntityData entityData;
private final PropertyHandler propertyHandler;
public ModelEngineModel(ActiveModel activeModel, ModelHandler modelHandler, EntityData entityData, PropertyHandler propertyHandler) {
this.activeModel = activeModel;
this.modelHandler = modelHandler;
this.entityData = entityData;
this.propertyHandler = propertyHandler;
}
@Override
public String getName() {
return activeModel.getBlueprint().getName();
}
@Override
public ModelHandler getModelHandler() {
return modelHandler;
}
@Override
public EntityData getEntityData() {
return entityData;
}
@Override
public PropertyHandler getPropertyHandler() {
return propertyHandler;
}
public ActiveModel getActiveModel() {
return activeModel;
}
}

View File

@@ -0,0 +1,66 @@
package re.imc.geysermodelengine.managers.model.modelhandler;
import kr.toxicity.model.api.entity.BaseEntity;
import kr.toxicity.model.api.tracker.EntityTracker;
import kr.toxicity.model.api.tracker.Tracker;
import org.bukkit.Bukkit;
import org.bukkit.entity.Entity;
import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.listener.BetterModelListener;
import re.imc.geysermodelengine.managers.model.propertyhandler.PropertyHandler;
import re.imc.geysermodelengine.managers.model.entity.BetterModelEntityData;
import re.imc.geysermodelengine.managers.model.entity.EntityData;
import re.imc.geysermodelengine.managers.model.model.BetterModelModel;
import re.imc.geysermodelengine.managers.model.model.Model;
import java.util.HashMap;
import java.util.Map;
public class BetterModelHandler implements ModelHandler {
private final GeyserModelEngine plugin;
public BetterModelHandler(GeyserModelEngine plugin) {
this.plugin = plugin;
}
//TODO fix dupe issue - dupe happens when server restart
@Override
public void createModel(Object... objects) {
BaseEntity entitySource = (BaseEntity) objects[0];
Tracker tracker = (Tracker) objects[1];
EntityTracker entityTracker = (EntityTracker) objects[2];
int entityID = entitySource.id();
PropertyHandler propertyHandler = plugin.getEntityTaskManager().getPropertyHandler();
EntityData entityData = new BetterModelEntityData(plugin, entitySource, tracker, entityTracker);
Model model = new BetterModelModel(tracker, this, entityData, propertyHandler);
Map<Model, EntityData> entityDataCache = plugin.getModelManager().getEntitiesCache().computeIfAbsent(entityID, k -> new HashMap<>());
for (Map.Entry<Model, EntityData> entry : entityDataCache.entrySet()) {
if (entry.getKey() != model && entry.getKey().getName().equals(tracker.name())) {
return;
}
}
plugin.getModelManager().getModelEntitiesCache().put(entityID, model);
entityDataCache.put(model, entityData);
}
@Override
public void processEntities(Entity entity) {
// if (plugin.getModelManager().getEntitiesCache().containsKey(entity.getEntityId())) return;
//
// @NotNull Optional<EntityTrackerRegistry> modeledEntity = BetterModel.registry(entity);
//
// modeledEntity.ifPresent(m -> createModel(modeledEntity.get().entity(), m.));
}
@Override
public void loadListeners() {
Bukkit.getPluginManager().registerEvents(new BetterModelListener(plugin), plugin);
}
}

View File

@@ -0,0 +1,69 @@
package re.imc.geysermodelengine.managers.model.modelhandler;
import com.ticxo.modelengine.api.ModelEngineAPI;
import com.ticxo.modelengine.api.model.ActiveModel;
import com.ticxo.modelengine.api.model.ModeledEntity;
import org.bukkit.Bukkit;
import org.bukkit.entity.Entity;
import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.listener.ModelEngineListener;
import re.imc.geysermodelengine.managers.model.entity.EntityData;
import re.imc.geysermodelengine.managers.model.entity.ModelEngineEntityData;
import re.imc.geysermodelengine.managers.model.model.Model;
import re.imc.geysermodelengine.managers.model.model.ModelEngineModel;
import re.imc.geysermodelengine.managers.model.propertyhandler.PropertyHandler;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
public class ModelEngineHandler implements ModelHandler {
//TODO move driver hashmap here
private final GeyserModelEngine plugin;
public ModelEngineHandler(GeyserModelEngine plugin) {
this.plugin = plugin;
}
@Override
public void createModel(Object... objects) {
ModeledEntity megEntity = (ModeledEntity) objects[0];
ActiveModel megActiveModel = (ActiveModel) objects[1];
int entityID = megEntity.getBase().getEntityId();
PropertyHandler propertyHandler = plugin.getEntityTaskManager().getPropertyHandler();
EntityData entityData = new ModelEngineEntityData(plugin, megEntity, megActiveModel);
Model model = new ModelEngineModel(megActiveModel, this, entityData, propertyHandler);
Map<Model, EntityData> entityDataCache = plugin.getModelManager().getEntitiesCache().computeIfAbsent(entityID, k -> new HashMap<>());
for (Map.Entry<Model, EntityData> entry : entityDataCache.entrySet()) {
if (entry.getKey() != model && entry.getKey().getName().equals(megActiveModel.getBlueprint().getName())) {
return;
}
}
plugin.getModelManager().getModelEntitiesCache().put(entityID, model);
entityDataCache.put(model, entityData);
}
@Override
public void processEntities(Entity entity) {
if (plugin.getModelManager().getEntitiesCache().containsKey(entity.getEntityId())) return;
ModeledEntity modeledEntity = ModelEngineAPI.getModeledEntity(entity);
if (modeledEntity == null) return;
Optional<ActiveModel> model = modeledEntity.getModels().values().stream().findFirst();
model.ifPresent(m -> createModel(modeledEntity, m));
}
@Override
public void loadListeners() {
Bukkit.getPluginManager().registerEvents(new ModelEngineListener(plugin), plugin);
}
}

View File

@@ -0,0 +1,23 @@
package re.imc.geysermodelengine.managers.model.modelhandler;
import org.bukkit.entity.Entity;
public interface ModelHandler {
/**
* Creates the model from the required Model Engine
* @param objects Processes the required objects
*/
void createModel(Object... objects);
/**
* Processes entities into createModel()
* @param entity Registers bukkit entities
*/
void processEntities(Entity entity);
/**
* Loads the required listeners
*/
void loadListeners();
}

View File

@@ -0,0 +1,167 @@
package re.imc.geysermodelengine.managers.model.propertyhandler;
import kr.toxicity.model.api.animation.AnimationIterator;
import kr.toxicity.model.api.bone.RenderedBone;
import kr.toxicity.model.api.data.blueprint.BlueprintAnimation;
import kr.toxicity.model.api.data.renderer.RenderPipeline;
import kr.toxicity.model.api.nms.ModelDisplay;
import me.zimzaza4.geyserutils.spigot.api.EntityUtils;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.managers.model.entity.BetterModelEntityData;
import re.imc.geysermodelengine.managers.model.entity.EntityData;
import re.imc.geysermodelengine.util.BooleanPacker;
import java.awt.*;
import java.util.*;
import java.util.List;
public class BetterModelPropertyHandler implements PropertyHandler {
private final GeyserModelEngine plugin;
public BetterModelPropertyHandler(GeyserModelEngine plugin) {
this.plugin = plugin;
}
// Figure out on how to get the scale from BetterModel
@Override
public void sendScale(EntityData entityData, Collection<Player> players, float lastScale, boolean firstSend) {
BetterModelEntityData betterModelEntityData = (BetterModelEntityData) entityData;
}
@Override
public void sendColor(EntityData entityData, Collection<Player> players, Color lastColor, boolean firstSend) {
if (players.isEmpty()) return;
BetterModelEntityData betterModelEntityData = (BetterModelEntityData) entityData;
Color color = new Color(0xFFFFFF);
if (betterModelEntityData.isHurt()) color = new Color(betterModelEntityData.getEntityTracker().damageTintValue());
if (firstSend) {
if (color.equals(lastColor)) return;
}
for (Player player : players) {
EntityUtils.sendCustomColor(player, betterModelEntityData.getEntity().getEntityId(), color);
}
betterModelEntityData.setHurt(false);
}
@Override
public void sendHitBox(EntityData entityData, Player player) {
BetterModelEntityData betterModelEntityData = (BetterModelEntityData) entityData;
float w = 0;
EntityUtils.sendCustomHitBox(player, betterModelEntityData.getEntity().getEntityId(), 0.02f, w);
}
@Override
public void updateEntityProperties(EntityData entityData, Collection<Player> players, boolean firstSend, String... forceAnims) {
BetterModelEntityData model = (BetterModelEntityData) entityData;
int entity = model.getEntity().getEntityId();
Set<String> forceAnimSet = Set.of(forceAnims);
Map<String, Boolean> boneUpdates = new HashMap<>();
Map<String, Boolean> animUpdates = new HashMap<>();
Set<String> anims = new HashSet<>();
model.getTracker().bones().forEach(bone -> processBone(model, bone, boneUpdates));
RenderPipeline handler = model.getTracker().getPipeline();
for (RenderedBone renderedBone : handler.bones()) {
if (model.getTracker().bone(renderedBone.name()).runningAnimation() != null) {
BlueprintAnimation anim = model.getTracker().renderer().animations().get(renderedBone.runningAnimation().name());
anims.add(renderedBone.runningAnimation().name());
if (anim.override() && anim.loop() == AnimationIterator.Type.PLAY_ONCE) {
break;
}
}
}
for (String id : handler.getParent().animations().keySet()) {
if (anims.contains(id)) {
animUpdates.put(id, true);
} else {
animUpdates.put(id, false);
}
}
Set<String> lastPlayed = new HashSet<>(model.getEntityTask().getLastPlayedAnim().asMap().keySet());
for (Map.Entry<String, Boolean> anim : animUpdates.entrySet()) {
if (anim.getValue()) {
model.getEntityTask().getLastPlayedAnim().put(anim.getKey(), true);
}
}
for (String anim : lastPlayed) animUpdates.put(anim, true);
if (boneUpdates.isEmpty() && animUpdates.isEmpty()) return;
Map<String, Integer> intUpdates = new HashMap<>();
int i = 0;
for (Integer integer : BooleanPacker.mapBooleansToInts(boneUpdates)) {
intUpdates.put(plugin.getConfigManager().getConfig().getString("models.namespace") + ":bone" + i, integer);
i++;
}
i = 0;
for (Integer integer : BooleanPacker.mapBooleansToInts(animUpdates)) {
intUpdates.put(plugin.getConfigManager().getConfig().getString("models.namespace") + ":anim" + i, integer);
i++;
}
if (!firstSend) {
if (intUpdates.equals(model.getEntityTask().getLastIntSet())) {
return;
} else {
model.getEntityTask().getLastIntSet().clear();
model.getEntityTask().getLastIntSet().putAll(intUpdates);
}
}
if (plugin.getConfigManager().getConfig().getBoolean("options.debug")) plugin.getLogger().info(animUpdates.toString());
List<String> list = new ArrayList<>(boneUpdates.keySet());
Collections.sort(list);
players.forEach(player -> EntityUtils.sendIntProperties(player, entity, intUpdates));
}
public String unstripName(RenderedBone bone) {
@NotNull String name = bone.name().rawName();
if (name.equals("head")) {
if (!bone.getChildren().isEmpty()) return "hi_" + name;
return "h_" + name;
}
return name;
}
private void processBone(BetterModelEntityData entityData, RenderedBone bone, Map<String, Boolean> map) {
String name = unstripName(bone).toLowerCase();
if (name.equals("hitbox") || name.equals("shadow") || name.equals("mount") || name.startsWith("p_") || name.startsWith("b_") || name.startsWith("ob_")) return;
for (RenderedBone renderedBone : bone.getChildren().values()) {
processBone(entityData, renderedBone, map);
}
RenderedBone activeBone = entityData.getTracker().bone(bone.name());
ModelDisplay modelDisplay = activeBone.getDisplay();
if (modelDisplay == null) return;
boolean visible = activeBone.getDisplay().invisible();
map.put(name, visible);
}
}

View File

@@ -0,0 +1,179 @@
package re.imc.geysermodelengine.managers.model.propertyhandler;
import com.ticxo.modelengine.api.animation.BlueprintAnimation;
import com.ticxo.modelengine.api.animation.handler.AnimationHandler;
import com.ticxo.modelengine.api.generator.blueprint.BlueprintBone;
import com.ticxo.modelengine.api.model.bone.ModelBone;
import com.ticxo.modelengine.api.model.render.DisplayRenderer;
import me.zimzaza4.geyserutils.spigot.api.EntityUtils;
import org.bukkit.entity.Player;
import org.joml.Vector3fc;
import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.managers.model.entity.EntityData;
import re.imc.geysermodelengine.managers.model.entity.ModelEngineEntityData;
import re.imc.geysermodelengine.util.BooleanPacker;
import java.awt.*;
import java.util.*;
public class ModelEnginePropertyHandler implements PropertyHandler {
private final GeyserModelEngine plugin;
public ModelEnginePropertyHandler(GeyserModelEngine plugin) {
this.plugin = plugin;
}
@Override
public void sendScale(EntityData modelData, Collection<Player> players, float lastScale, boolean firstSend) {
try {
if (players.isEmpty()) return;
ModelEngineEntityData modelEngineEntityData = (ModelEngineEntityData) modelData;
Vector3fc scale = modelEngineEntityData.getActiveModel().getScale();
float average = (scale.x() + scale.y() + scale.z()) / 3;
if (!firstSend) {
if (average == lastScale) return;
}
players.forEach(player -> EntityUtils.sendCustomScale(player, modelEngineEntityData.getEntity().getEntityId(), average));
} catch (Exception err) {
throw new RuntimeException(err);
}
}
@Override
public void sendColor(EntityData entityData, Collection<Player> players, Color lastColor, boolean firstSend) {
if (players.isEmpty()) return;
ModelEngineEntityData data = (ModelEngineEntityData) entityData;
Color color = calculateCurrentColor(data);
if (!firstSend && color.equals(lastColor)) return;
players.forEach(player -> EntityUtils.sendCustomColor(player, data.getEntity().getEntityId(), color));
}
@Override
public void sendHitBox(EntityData entityData, Player player) {
ModelEngineEntityData modelEngineEntityData = (ModelEngineEntityData) entityData;
float w = 0;
if (modelEngineEntityData.getActiveModel().isShadowVisible()) {
if (modelEngineEntityData.getActiveModel().getModelRenderer() instanceof DisplayRenderer displayRenderer) {
// w = displayRenderer.getHitbox().getShadowRadius().get();
}
}
EntityUtils.sendCustomHitBox(player, modelEngineEntityData.getEntity().getEntityId(), 0.02f, w);
}
@Override
public void updateEntityProperties(EntityData entityData, Collection<Player> players, boolean firstSend, String... forceAnims) {
ModelEngineEntityData model = (ModelEngineEntityData) entityData;
int entity = model.getEntity().getEntityId();
Set<String> forceAnimSet = Set.of(forceAnims);
Map<String, Boolean> boneUpdates = new LinkedHashMap<>();
Map<String, Boolean> animUpdates = new HashMap<>();
Set<String> anims = new HashSet<>();
model.getActiveModel().getBlueprint().getBones().forEach((s, bone) -> processBone(model, bone, boneUpdates));
AnimationHandler handler = model.getActiveModel().getAnimationHandler();
Set<String> priority = model.getActiveModel().getBlueprint().getAnimationDescendingPriority();
for (String animId : priority) {
if (handler.isPlayingAnimation(animId)) {
BlueprintAnimation anim = model.getActiveModel().getBlueprint().getAnimations().get(animId);
anims.add(animId);
if (anim.isOverride() && anim.getLoopMode() == BlueprintAnimation.LoopMode.ONCE) {
break;
}
}
}
for (String id : priority) {
if (anims.contains(id)) {
animUpdates.put(id, true);
} else {
animUpdates.put(id, false);
}
}
Set<String> lastPlayed = new HashSet<>(model.getEntityTask().getLastPlayedAnim().asMap().keySet());
for (Map.Entry<String, Boolean> anim : animUpdates.entrySet()) {
if (anim.getValue()) {
model.getEntityTask().getLastPlayedAnim().put(anim.getKey(), true);
}
}
for (String anim : lastPlayed) {
animUpdates.put(anim, true);
}
if (boneUpdates.isEmpty() && animUpdates.isEmpty()) return;
Map<String, Integer> intUpdates = new HashMap<>();
int i = 0;
for (Integer integer : BooleanPacker.mapBooleansToInts(boneUpdates)) {
intUpdates.put(plugin.getConfigManager().getConfig().getString("models.namespace") + ":bone" + i, integer);
i++;
}
i = 0;
for (Integer integer : BooleanPacker.mapBooleansToInts(animUpdates)) {
intUpdates.put(plugin.getConfigManager().getConfig().getString("models.namespace") + ":anim" + i, integer);
i++;
}
if (!firstSend) {
if (intUpdates.equals(model.getEntityTask().getLastIntSet())) {
return;
} else {
model.getEntityTask().getLastIntSet().clear();
model.getEntityTask().getLastIntSet().putAll(intUpdates);
}
}
if (plugin.getConfigManager().getConfig().getBoolean("options.debug")) plugin.getLogger().info(animUpdates.toString());
players.forEach(player -> EntityUtils.sendIntProperties(player, entity, intUpdates));
}
private void processBone(ModelEngineEntityData model, BlueprintBone bone, Map<String, Boolean> map) {
String name = unstripName(bone).toLowerCase();
if (name.equals("hitbox") || name.equals("shadow") || name.equals("mount") || name.startsWith("p_") || name.startsWith("b_") || name.startsWith("ob_")) return;
bone.getChildren().values().forEach(child -> processBone(model, child, map));
ModelBone activeBone = model.getActiveModel().getBones().get(bone.getName());
boolean visible = false;
if (activeBone != null) visible = activeBone.isVisible();
map.put(name, visible);
}
public String unstripName(BlueprintBone bone) {
String name = bone.getName();
if (bone.getBehaviors().get("head") != null) {
if (!bone.getBehaviors().get("head").isEmpty()) return "hi_" + name;
return "h_" + name;
}
return name;
}
private Color calculateCurrentColor(ModelEngineEntityData modelEngineEntityData) {
if (modelEngineEntityData.getActiveModel().isMarkedHurt()) return new Color(modelEngineEntityData.getActiveModel().getDamageTint().asARGB());
return new Color(modelEngineEntityData.getActiveModel().getDefaultTint().asARGB());
}
}

View File

@@ -0,0 +1,44 @@
package re.imc.geysermodelengine.managers.model.propertyhandler;
import org.bukkit.entity.Player;
import re.imc.geysermodelengine.managers.model.entity.EntityData;
import java.awt.*;
import java.util.Collection;
public interface PropertyHandler {
/**
* Sends scale of the entity to the player
* @param entityData The data of the entity
* @param players Collection of players from the entity view
* @param lastScale Sends the last scale to the player
* @param firstSend Checks if it's the first time to send scale to the player
*/
void sendScale(EntityData entityData, Collection<Player> players, float lastScale, boolean firstSend);
/**
* Sends a colour tint to the player
* @param entityData The data of the entity
* @param players Collection of players from the entity view
* @param lastColor Sends the last colour to the player
* @param firstSend Checks if it's the first time to send colour to the player
*/
void sendColor(EntityData entityData, Collection<Player> players, Color lastColor, boolean firstSend);
/**
* Sends a hitbox to the player
* @param entityData The data of the entity
* @param player Sends the player the entity hitbox
*/
void sendHitBox(EntityData entityData, Player player);
/**
* Updates the entity to all viewable players
* @param entityData The data of the entity
* @param players Collection of players from the entity view
* @param firstSend Checks if it's the first time to send the entity to the player
* @param forceAnims Forces the entity to do an animation
*/
void updateEntityProperties(EntityData entityData, Collection<Player> players, boolean firstSend, String... forceAnims);
}

View File

@@ -0,0 +1,171 @@
package re.imc.geysermodelengine.managers.model.taskshandler;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import kr.toxicity.model.api.entity.BaseEntity;
import kr.toxicity.model.api.tracker.Tracker;
import me.zimzaza4.geyserutils.spigot.api.EntityUtils;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.managers.model.entity.BetterModelEntityData;
import re.imc.geysermodelengine.managers.model.entity.EntityData;
import re.imc.geysermodelengine.packet.entity.PacketEntity;
import java.awt.*;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
public class BetterModelTaskHandler implements TaskHandler {
private final GeyserModelEngine plugin;
private final BetterModelEntityData entityData;
private int tick = 0;
private int syncTick = 0;
private float lastScale = -1.0f;
private Color lastColor = null;
private boolean removed = false;
private final ConcurrentHashMap<String, Integer> lastIntSet = new ConcurrentHashMap<>();
private final Cache<String, Boolean> lastPlayedAnim = CacheBuilder.newBuilder().expireAfterWrite(30, TimeUnit.MILLISECONDS).build();
private ScheduledFuture scheduledFuture;
public BetterModelTaskHandler(GeyserModelEngine plugin, BetterModelEntityData entityData) {
this.plugin = plugin;
this.entityData = entityData;
plugin.getEntityTaskManager().sendHitBoxToAll(entityData);
scheduledFuture = plugin.getSchedulerPool().scheduleAtFixedRate(this::runAsync, 0, 20, TimeUnit.MILLISECONDS);
}
@Override
public void runAsync() {
plugin.getEntityTaskManager().checkViewers(entityData, entityData.getViewers());
PacketEntity entity = entityData.getEntity();
if (entity.isDead()) return;
Set<Player> viewers = entityData.getViewers();
BaseEntity entitySource = entityData.getEntitySource();
Tracker tracker = entityData.getTracker();
entityData.teleportToModel();
if (entitySource.dead() || tracker.forRemoval()) {
removed = true;
entity.remove();
plugin.getModelManager().getEntitiesCache().remove(entitySource.id());
plugin.getModelManager().getModelEntitiesCache().remove(entitySource.id());
cancel();
return;
}
if (tick % 5 == 0) {
if (tick % 40 == 0) {
for (Player viewer : Set.copyOf(viewers)) {
if (!plugin.getEntityTaskManager().canSee(viewer, entityData.getEntity())) {
viewers.remove(viewer);
}
}
}
}
tick++;
if (tick > 400) {
tick = 0;
plugin.getEntityTaskManager().sendHitBoxToAll(entityData);
}
if (viewers.isEmpty()) return;
plugin.getEntityTaskManager().getPropertyHandler().sendScale(entityData, viewers, lastScale, false);
plugin.getEntityTaskManager().getPropertyHandler().sendColor(entityData, viewers, lastColor, false);
}
@Override
public void sendEntityData(EntityData entityData, Player player, int delay) {
BetterModelEntityData betterModelEntityData = (BetterModelEntityData) entityData;
EntityUtils.setCustomEntity(player, betterModelEntityData.getEntity().getEntityId(), plugin.getConfigManager().getConfig().getString("models.namespace") + ":" + betterModelEntityData.getTracker().name().toLowerCase());
plugin.getSchedulerPool().schedule(() -> {
entityData.getEntity().sendSpawnPacket(Collections.singletonList(player));
plugin.getSchedulerPool().schedule(() -> {
plugin.getEntityTaskManager().getPropertyHandler().sendHitBox(entityData, player);
plugin.getEntityTaskManager().getPropertyHandler().sendScale(entityData, Collections.singleton(player), lastScale, true);
plugin.getEntityTaskManager().getPropertyHandler().sendColor(entityData, Collections.singleton(player), lastColor, true);
plugin.getEntityTaskManager().getPropertyHandler().updateEntityProperties(entityData, Collections.singleton(player), true);
}, 500, TimeUnit.MILLISECONDS);
}, delay * 50L, TimeUnit.MILLISECONDS);
}
@Override
public void cancel() {
scheduledFuture.cancel(true);
}
public void setTick(int tick) {
this.tick = tick;
}
public void setSyncTick(int syncTick) {
this.syncTick = syncTick;
}
public void setRemoved(boolean removed) {
this.removed = removed;
}
public void setLastScale(float lastScale) {
this.lastScale = lastScale;
}
public int getTick() {
return tick;
}
public int getSyncTick() {
return syncTick;
}
public void setLastColor(Color lastColor) {
this.lastColor = lastColor;
}
public float getLastScale() {
return lastScale;
}
public Color getLastColor() {
return lastColor;
}
public boolean isRemoved() {
return removed;
}
public ConcurrentHashMap<String, Integer> getLastIntSet() {
return lastIntSet;
}
public Cache<String, Boolean> getLastPlayedAnim() {
return lastPlayedAnim;
}
public ScheduledFuture getScheduledFuture() {
return scheduledFuture;
}
}

View File

@@ -0,0 +1,168 @@
package re.imc.geysermodelengine.managers.model.taskshandler;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.ticxo.modelengine.api.model.ActiveModel;
import com.ticxo.modelengine.api.model.ModeledEntity;
import me.zimzaza4.geyserutils.spigot.api.EntityUtils;
import org.bukkit.entity.Player;
import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.managers.model.entity.EntityData;
import re.imc.geysermodelengine.managers.model.entity.ModelEngineEntityData;
import re.imc.geysermodelengine.packet.entity.PacketEntity;
import java.awt.*;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
public class ModelEngineTaskHandler implements TaskHandler {
private final GeyserModelEngine plugin;
private final ModelEngineEntityData entityData;
private int tick = 0;
private int syncTick = 0;
private float lastScale = -1.0f;
private Color lastColor = null;
private boolean removed = false;
private final ConcurrentHashMap<String, Integer> lastIntSet = new ConcurrentHashMap<>();
private final Cache<String, Boolean> lastPlayedAnim = CacheBuilder.newBuilder().expireAfterWrite(30, TimeUnit.MILLISECONDS).build();
private ScheduledFuture scheduledFuture;
public ModelEngineTaskHandler(GeyserModelEngine plugin, ModelEngineEntityData entityData) {
this.plugin = plugin;
this.entityData = entityData;
plugin.getEntityTaskManager().sendHitBoxToAll(entityData);
scheduledFuture = plugin.getSchedulerPool().scheduleAtFixedRate(this::runAsync, 0, 20, TimeUnit.MILLISECONDS);
}
@Override
public void runAsync() {
if (removed || entityData == null) return;
PacketEntity entity = entityData.getEntity();
if (entity == null || entity.isDead()) return;
plugin.getEntityTaskManager().checkViewers(entityData, entityData.getViewers());
entityData.teleportToModel();
Set<Player> viewers = entityData.getViewers();
ActiveModel activeModel = entityData.getActiveModel();
ModeledEntity modeledEntity = entityData.getModeledEntity();
if (activeModel.isDestroyed() || activeModel.isRemoved()) {
removed = true;
entity.remove();
plugin.getModelManager().getEntitiesCache().remove(modeledEntity.getBase().getEntityId());
plugin.getModelManager().getModelEntitiesCache().remove(modeledEntity.getBase().getEntityId());
cancel();
return;
}
if (tick % 5 == 0) {
if (tick % 40 == 0) {
viewers.removeIf(viewer -> !plugin.getEntityTaskManager().canSee(viewer, entityData.getEntity()));
}
}
tick ++;
if (tick > 400) {
tick = 0;
plugin.getEntityTaskManager().sendHitBoxToAll(entityData);
}
if (viewers.isEmpty()) return;
plugin.getEntityTaskManager().getPropertyHandler().sendScale(entityData, viewers, lastScale, false);
plugin.getEntityTaskManager().getPropertyHandler().sendColor(entityData, viewers, lastColor, false);
}
@Override
public void sendEntityData(EntityData entityData, Player player, int delay) {
ModelEngineEntityData modelEngineEntityData = (ModelEngineEntityData) entityData;
EntityUtils.setCustomEntity(player, modelEngineEntityData.getEntity().getEntityId(), plugin.getConfigManager().getConfig().getString("models.namespace") + ":" + modelEngineEntityData.getActiveModel().getBlueprint().getName().toLowerCase());
plugin.getSchedulerPool().schedule(() -> {
entityData.getEntity().sendSpawnPacket(Collections.singletonList(player));
plugin.getSchedulerPool().schedule(() -> {
plugin.getEntityTaskManager().getPropertyHandler().sendHitBox(entityData, player);
plugin.getEntityTaskManager().getPropertyHandler().sendScale(entityData, Collections.singleton(player), lastScale, true);
plugin.getEntityTaskManager().getPropertyHandler().sendColor(entityData, Collections.singleton(player), lastColor, true);
plugin.getEntityTaskManager().getPropertyHandler().updateEntityProperties(entityData, Collections.singleton(player), true);
}, 500, TimeUnit.MILLISECONDS);
}, delay * 50L, TimeUnit.MILLISECONDS);
}
@Override
public void cancel() {
scheduledFuture.cancel(true);
}
public void setTick(int tick) {
this.tick = tick;
}
public void setSyncTick(int syncTick) {
this.syncTick = syncTick;
}
public void setRemoved(boolean removed) {
this.removed = removed;
}
public void setLastScale(float lastScale) {
this.lastScale = lastScale;
}
public int getTick() {
return tick;
}
public int getSyncTick() {
return syncTick;
}
public void setLastColor(Color lastColor) {
this.lastColor = lastColor;
}
public float getLastScale() {
return lastScale;
}
public Color getLastColor() {
return lastColor;
}
public boolean isRemoved() {
return removed;
}
public ConcurrentHashMap<String, Integer> getLastIntSet() {
return lastIntSet;
}
public Cache<String, Boolean> getLastPlayedAnim() {
return lastPlayedAnim;
}
public ScheduledFuture getScheduledFuture() {
return scheduledFuture;
}
}

View File

@@ -0,0 +1,25 @@
package re.imc.geysermodelengine.managers.model.taskshandler;
import org.bukkit.entity.Player;
import re.imc.geysermodelengine.managers.model.entity.EntityData;
public interface TaskHandler {
/**
* Runs the entity scheduler
*/
void runAsync();
/**
* Spawns the entity to the player
* @param entityData The data of the entity
* @param player Sends the entity to the player
* @param delay Delays sending the entity to the player
*/
void sendEntityData(EntityData entityData, Player player, int delay);
/**
* Cancels the entity scheduler
*/
void cancel();
}

View File

@@ -3,9 +3,7 @@ package re.imc.geysermodelengine.packet.entity;
import com.github.retrooper.packetevents.PacketEvents; import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.manager.server.ServerVersion; import com.github.retrooper.packetevents.manager.server.ServerVersion;
import com.github.retrooper.packetevents.protocol.entity.EntityPositionData; import com.github.retrooper.packetevents.protocol.entity.EntityPositionData;
import com.github.retrooper.packetevents.protocol.entity.data.EntityMetadataProvider;
import com.github.retrooper.packetevents.protocol.entity.type.EntityType; import com.github.retrooper.packetevents.protocol.entity.type.EntityType;
import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes;
import com.github.retrooper.packetevents.protocol.teleport.RelativeFlag; import com.github.retrooper.packetevents.protocol.teleport.RelativeFlag;
import com.github.retrooper.packetevents.util.Vector3d; import com.github.retrooper.packetevents.util.Vector3d;
import com.github.retrooper.packetevents.wrapper.PacketWrapper; import com.github.retrooper.packetevents.wrapper.PacketWrapper;
@@ -16,7 +14,6 @@ import lombok.Setter;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import re.imc.geysermodelengine.GeyserModelEngine;
import java.util.Collection; import java.util.Collection;
import java.util.Set; import java.util.Set;
@@ -50,7 +47,7 @@ public class PacketEntity {
} }
public boolean teleport(@NotNull Location location) { public boolean teleport(@NotNull Location location) {
boolean sent = this.location.getWorld() != location.getWorld() || this.location.distanceSquared(location) > 0.000001; boolean sent = this.location.getWorld() != location.getWorld() || this.location.distanceSquared(location) > 0.000001 || this.location.getYaw() != location.getYaw() || this.location.getPitch() != location.getPitch();
this.location = location.clone(); this.location = location.clone();
if (sent) sendLocationPacket(viewers); if (sent) sendLocationPacket(viewers);
@@ -58,7 +55,6 @@ public class PacketEntity {
return true; return true;
} }
public void remove() { public void remove() {
removed = true; removed = true;
sendEntityDestroyPacket(viewers); sendEntityDestroyPacket(viewers);
@@ -78,7 +74,6 @@ public class PacketEntity {
} }
public void sendLocationPacket(Collection<Player> players) { public void sendLocationPacket(Collection<Player> players) {
PacketWrapper<?> packet; PacketWrapper<?> packet;
EntityPositionData data = new EntityPositionData(SpigotConversionUtil.fromBukkitLocation(location).getPosition(), Vector3d.zero(), location.getYaw(), location.getPitch()); EntityPositionData data = new EntityPositionData(SpigotConversionUtil.fromBukkitLocation(location).getPosition(), Vector3d.zero(), location.getYaw(), location.getPitch());

View File

@@ -5,16 +5,14 @@ import com.ticxo.modelengine.api.entity.BukkitEntity;
import com.ticxo.modelengine.api.model.ActiveModel; import com.ticxo.modelengine.api.model.ActiveModel;
import com.ticxo.modelengine.api.model.bone.type.Mount; import com.ticxo.modelengine.api.model.bone.type.Mount;
import com.ticxo.modelengine.api.mount.controller.MountController; import com.ticxo.modelengine.api.mount.controller.MountController;
import io.papermc.paper.threadedregions.scheduler.ScheduledTask;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.geysermc.floodgate.api.FloodgateApi;
import re.imc.geysermodelengine.GeyserModelEngine; import re.imc.geysermodelengine.GeyserModelEngine;
import java.util.function.Consumer; import java.util.UUID;
public class BedrockMountControlRunnable implements Consumer<ScheduledTask> { public class BedrockMountControlRunnable implements Runnable {
private final GeyserModelEngine plugin; private final GeyserModelEngine plugin;
@@ -23,13 +21,12 @@ public class BedrockMountControlRunnable implements Consumer<ScheduledTask> {
} }
@Override @Override
public void accept(ScheduledTask scheduledTask) { public void run() {
for (Player player : Bukkit.getOnlinePlayers()) { for (UUID playerUUID : plugin.getModelManager().getPlayerJoinedCache()) {
if (!FloodgateApi.getInstance().isFloodgatePlayer(player.getUniqueId())) continue; Player player = Bukkit.getPlayer(playerUUID);
float pitch = player.getLocation().getPitch(); float pitch = player.getLocation().getPitch();
Pair<ActiveModel, Mount> seat = plugin.getModelManager().getDriversCache().get(player.getUniqueId()); Pair<ActiveModel, Mount> seat = plugin.getModelManager().getDriversCache().get(player.getUniqueId());
if (seat == null) continue; if (seat == null) continue;
if (pitch < -30) { if (pitch < -30) {
@@ -45,13 +42,10 @@ public class BedrockMountControlRunnable implements Consumer<ScheduledTask> {
if (pitch > 80) { if (pitch > 80) {
if (seat.getKey().getModeledEntity().getBase() instanceof BukkitEntity bukkitEntity) { if (seat.getKey().getModeledEntity().getBase() instanceof BukkitEntity bukkitEntity) {
if (bukkitEntity.getOriginal().isOnGround()) { if (bukkitEntity.getOriginal().isOnGround()) continue;
return;
}
} }
MountController controller = ModelEngineAPI.getMountPairManager().getController(player.getUniqueId()); MountController controller = ModelEngineAPI.getMountPairManager().getController(player.getUniqueId());
if (controller != null) { if (controller != null) {
MountController.MountInput input = controller.getInput(); MountController.MountInput input = controller.getInput();
if (input != null) { if (input != null) {

View File

@@ -0,0 +1,34 @@
package re.imc.geysermodelengine.runnables;
import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.managers.model.entity.EntityData;
import re.imc.geysermodelengine.managers.model.model.Model;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class UpdateTaskRunnable implements Runnable {
private final GeyserModelEngine plugin;
public UpdateTaskRunnable(GeyserModelEngine plugin) {
this.plugin = plugin;
}
@Override
public void run() {
ConcurrentHashMap<Integer, Map<Model, EntityData>> entitiesCache = plugin.getModelManager().getEntitiesCache();
if (entitiesCache.isEmpty()) return;
try {
for (Map<Model, EntityData> models : entitiesCache.values()) {
models.values().forEach(entityData -> {
if (entityData.getViewers().isEmpty()) return;
plugin.getEntityTaskManager().getPropertyHandler().updateEntityProperties(entityData, entityData.getViewers(), false);
});
}
} catch (Throwable err) {
throw new RuntimeException(err);
}
}
}

View File

@@ -0,0 +1,17 @@
package re.imc.geysermodelengine.util;
import org.bukkit.entity.Player;
import org.geysermc.floodgate.api.FloodgateApi;
import re.imc.geysermodelengine.hooks.FloodgateAPIHook;
public class BedrockUtils {
private static final FloodgateApi floodgateAPIHook = FloodgateAPIHook.getAPI();
public static boolean isBedrockPlayer(Player player) {
if (floodgateAPIHook != null) return floodgateAPIHook.isFloodgatePlayer(player.getUniqueId());
String clientBrand = player.getClientBrandName();
if (clientBrand == null) return false;
return clientBrand.contains("Geyser");
}
}

View File

@@ -7,9 +7,9 @@ import java.util.Map;
public class BooleanPacker { public class BooleanPacker {
private final int MAX_BOOLEANS = 24; private static final int MAX_BOOLEANS = 24;
public int booleansToInt(List<Boolean> booleans) { public static int booleansToInt(List<Boolean> booleans) {
int result = 0; int result = 0;
int i = 1; int i = 1;
@@ -23,7 +23,7 @@ public class BooleanPacker {
return result; return result;
} }
public int mapBooleansToInt(Map<String, Boolean> booleanMap) { public static int mapBooleansToInt(Map<String, Boolean> booleanMap) {
int result = 0; int result = 0;
int i = 1; int i = 1;
@@ -39,7 +39,7 @@ public class BooleanPacker {
return result; return result;
} }
public List<Integer> booleansToInts(List<Boolean> booleans) { public static List<Integer> booleansToInts(List<Boolean> booleans) {
List<Integer> results = new ArrayList<>(); List<Integer> results = new ArrayList<>();
int result = 0; int result = 0;
int i = 1; int i = 1;
@@ -62,7 +62,7 @@ public class BooleanPacker {
return results; return results;
} }
public List<Integer> mapBooleansToInts(Map<String, Boolean> booleanMap) { public static List<Integer> mapBooleansToInts(Map<String, Boolean> booleanMap) {
List<String> keys = new ArrayList<>(booleanMap.keySet()); List<String> keys = new ArrayList<>(booleanMap.keySet());
List<Boolean> booleans = new ArrayList<>(); List<Boolean> booleans = new ArrayList<>();

View File

@@ -0,0 +1,15 @@
metrics:
bstats: true
models:
namespace: "modelengine"
data-send-delay: 5
entity-view-distance: 50
join-send-delay: 20
entity-position-update-period: 35
thread-pool-size: 4
options:
debug: false
hooks:
floodgate: true # Recommended method

View File

@@ -1,12 +1,12 @@
main: re.imc.geysermodelengine.GeyserModelEngine main: re.imc.geysermodelengine.GeyserModelEngine
name: GeyserModelEngine name: GeyserModelEngine
version: '1.0.0' version: '${version}'
api-version: '1.21' api-version: '1.21'
authors: authors:
- zimzaza4 - zimzaza4
- willem.dev - willem.dev
- TheLividaProject - xSquishyLiam
load: STARTUP load: STARTUP
@@ -16,7 +16,9 @@ dependencies:
required: true required: true
packetevents: packetevents:
required: true required: true
ModelEngine:
required: true
floodgate: floodgate:
required: true required: false
ModelEngine:
required: false
BetterModel:
required: false

View File

@@ -1,2 +1,5 @@
rootProject.name = "GeyserModelEngine" rootProject.name = "GeyserModelEngine"
include("paper")
include("geyser")

View File

@@ -1,28 +0,0 @@
package re.imc.geysermodelengine.commands.geysermodelenginecommands;
import dev.jorel.commandapi.CommandAPICommand;
import org.bukkit.Bukkit;
import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.managers.commands.subcommands.SubCommands;
import re.imc.geysermodelengine.util.ColourUtils;
public class GeyserModelEngineReloadCommand implements SubCommands {
private final GeyserModelEngine plugin;
private final ColourUtils colourUtils = new ColourUtils();
public GeyserModelEngineReloadCommand(GeyserModelEngine plugin) {
this.plugin = plugin;
}
@Override
public CommandAPICommand onCommand() {
return new CommandAPICommand("reload")
.withPermission("geysermodelengine.commands.reload")
.executes((sender, args) -> {
Bukkit.getAsyncScheduler().runNow(plugin, scheduledTask -> plugin.getConfigManager().load());
sender.sendMessage(colourUtils.miniFormat(plugin.getConfigManager().getLang().getString("commands.reload.successfully-reloaded")));
});
}
}

View File

@@ -1,79 +0,0 @@
package re.imc.geysermodelengine.listener;
import com.ticxo.modelengine.api.events.AddModelEvent;
import com.ticxo.modelengine.api.events.ModelDismountEvent;
import com.ticxo.modelengine.api.events.ModelMountEvent;
import com.ticxo.modelengine.api.model.ActiveModel;
import org.apache.commons.lang3.tuple.Pair;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.world.WorldInitEvent;
import org.bukkit.event.world.WorldLoadEvent;
import org.geysermc.floodgate.api.FloodgateApi;
import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.managers.model.data.ModelEntityData;
import java.util.Map;
import java.util.concurrent.TimeUnit;
public class ModelListener implements Listener {
private final GeyserModelEngine plugin;
public ModelListener(GeyserModelEngine plugin) {
this.plugin = plugin;
}
@EventHandler(priority = EventPriority.MONITOR)
public void onAddModel(AddModelEvent event) {
if (event.isCancelled()) return;
plugin.getModelManager().create(event.getTarget(), event.getModel());
}
@EventHandler(priority = EventPriority.MONITOR)
public void onModelMount(ModelMountEvent event) {
Map<ActiveModel, ModelEntityData> map = plugin.getModelManager().getEntitiesCache().get(event.getVehicle().getModeledEntity().getBase().getEntityId());
if (!event.isDriver()) return;
ModelEntityData model = map.get(event.getVehicle());
if (model != null && event.getPassenger() instanceof Player player) {
plugin.getModelManager().getDriversCache().put(player.getUniqueId(), Pair.of(event.getVehicle(), event.getSeat()));
}
}
@EventHandler(priority = EventPriority.MONITOR)
public void onModelDismount(ModelDismountEvent event) {
if (event.getPassenger() instanceof Player player) {
plugin.getModelManager().getDriversCache().remove(player.getUniqueId());
}
}
@EventHandler
public void onWorldInit(WorldInitEvent event) {
World world = event.getWorld();
world.getEntities().forEach(entity -> plugin.getModelManager().processEntities(entity));
}
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
if (!FloodgateApi.getInstance().isFloodgatePlayer(player.getUniqueId())) return;
//TODO temp fix bc like why? - the issue is when a player logs out and the mob is there, the player logs back in sometimes it can display as a pig only
Bukkit.getAsyncScheduler().runDelayed(plugin, scheduledTask -> plugin.getModelManager().getPlayerJoinedCache().add(player.getUniqueId()), 10, TimeUnit.MILLISECONDS);
}
@EventHandler
public void onPlayerQuit(PlayerQuitEvent event) {
Player player = event.getPlayer();
if (!FloodgateApi.getInstance().isFloodgatePlayer(player.getUniqueId())) return;
plugin.getModelManager().getPlayerJoinedCache().remove(player.getUniqueId());
}
}

View File

@@ -1,154 +0,0 @@
package re.imc.geysermodelengine.managers.model;
import com.ticxo.modelengine.api.animation.BlueprintAnimation;
import com.ticxo.modelengine.api.generator.blueprint.BlueprintBone;
import com.ticxo.modelengine.api.model.ActiveModel;
import com.ticxo.modelengine.api.model.render.DisplayRenderer;
import me.zimzaza4.geyserutils.spigot.api.EntityUtils;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.geysermc.floodgate.api.FloodgateApi;
import org.joml.Vector3fc;
import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.managers.model.data.ModelEntityData;
import re.imc.geysermodelengine.packet.entity.PacketEntity;
import re.imc.geysermodelengine.runnables.EntityTaskRunnable;
import java.awt.*;
import java.lang.reflect.Method;
import java.util.*;
public class EntityTaskManager {
private final GeyserModelEngine plugin;
private final Method scaleMethod;
public EntityTaskManager(GeyserModelEngine plugin) {
this.plugin = plugin;
try {
this.scaleMethod = ActiveModel.class.getMethod("getScale");
} catch (NoSuchMethodException err) {
throw new RuntimeException(err);
}
}
public String unstripName(BlueprintBone bone) {
String name = bone.getName();
if (bone.getBehaviors().get("head") != null) {
if (!bone.getBehaviors().get("head").isEmpty()) return "hi_" + name;
return "h_" + name;
}
return name;
}
public void sendScale(ModelEntityData model, Collection<Player> players, float lastScale, boolean firstSend) {
try {
if (players.isEmpty()) return;
Vector3fc scale = (Vector3fc) scaleMethod.invoke(model.getActiveModel());
float average = (scale.x() + scale.y() + scale.z()) / 3;
if (!firstSend) {
if (average == lastScale) return;
}
for (Player player : players) {
EntityUtils.sendCustomScale(player, model.getEntity().getEntityId(), average);
}
} catch (Throwable ignored) {}
}
public void sendColor(ModelEntityData model, Collection<Player> players, Color lastColor, boolean firstSend) {
if (players.isEmpty()) return;
Color color = new Color(model.getActiveModel().getDefaultTint().asARGB());
if (model.getActiveModel().isMarkedHurt()) color = new Color(model.getActiveModel().getDamageTint().asARGB());
if (firstSend) {
if (color.equals(lastColor)) return;
}
for (Player player : players) {
EntityUtils.sendCustomColor(player, model.getEntity().getEntityId(), color);
}
}
public void checkViewers(ModelEntityData model, Set<Player> viewers) {
for (Player onlinePlayer : Bukkit.getOnlinePlayers()) {
if (!FloodgateApi.getInstance().isFloodgatePlayer(onlinePlayer.getUniqueId())) continue;
if (canSee(onlinePlayer, model.getEntity())) {
if (!viewers.contains(onlinePlayer)) {
sendSpawnPacket(model, onlinePlayer);
viewers.add(onlinePlayer);
}
} else {
if (viewers.contains(onlinePlayer)) {
model.getEntity().sendEntityDestroyPacket(Collections.singletonList(onlinePlayer));
viewers.remove(onlinePlayer);
}
}
}
}
// Issue here - start: See ModelListener.class and look at function onPlayerJoin
private void sendSpawnPacket(ModelEntityData model, Player onlinePlayer) {
EntityTaskRunnable task = model.getEntityTask();
boolean firstJoined = !plugin.getModelManager().getPlayerJoinedCache().contains(onlinePlayer.getUniqueId());
if (firstJoined) {
task.sendEntityData(model, onlinePlayer, plugin.getConfigManager().getConfig().getInt("join-send-delay") / 50);
} else {
task.sendEntityData(model, onlinePlayer, 5);
}
}
public boolean canSee(Player player, PacketEntity entity) {
if (!player.isOnline()) return false;
if (!plugin.getModelManager().getPlayerJoinedCache().contains(player.getUniqueId())) return false;
Location playerLocation = player.getLocation().clone();
Location entityLocation = entity.getLocation().clone();
playerLocation.setY(0);
entityLocation.setY(0);
if (playerLocation.getWorld() != entityLocation.getWorld()) return false;
if (playerLocation.distanceSquared(entityLocation) > player.getSendViewDistance() * player.getSendViewDistance() * 48) return false;
return true;
}
// Issue here - end
public void sendHitBoxToAll(ModelEntityData model) {
for (Player viewer : model.getViewers()) {
EntityUtils.sendCustomHitBox(viewer, model.getEntity().getEntityId(), 0.01f, 0.01f);
}
}
public void sendHitBox(ModelEntityData model, Player viewer) {
float w = 0;
if (model.getActiveModel().isShadowVisible()) {
if (model.getActiveModel().getModelRenderer() instanceof DisplayRenderer displayRenderer) {
// w = displayRenderer.getHitbox().getShadowRadius().get();
}
}
EntityUtils.sendCustomHitBox(viewer, model.getEntity().getEntityId(), 0.02f, w);
}
public boolean hasAnimation(ModelEntityData model, String animation) {
ActiveModel activeModel = model.getActiveModel();
BlueprintAnimation animationProperty = activeModel.getBlueprint().getAnimations().get(animation);
return !(animationProperty == null);
}
public Method getScaleMethod() {
return scaleMethod;
}
}

View File

@@ -1,70 +0,0 @@
package re.imc.geysermodelengine.managers.model;
import com.ticxo.modelengine.api.ModelEngineAPI;
import com.ticxo.modelengine.api.model.ActiveModel;
import com.ticxo.modelengine.api.model.ModeledEntity;
import com.ticxo.modelengine.api.model.bone.type.Mount;
import org.apache.commons.lang3.tuple.Pair;
import org.bukkit.entity.Entity;
import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.managers.model.data.ModelEntityData;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class ModelManager {
private final GeyserModelEngine plugin;
private final HashSet<UUID> playerJoinedCache = new HashSet<>();
private final ConcurrentHashMap<Integer, Map<ActiveModel, ModelEntityData>> entitiesCache = new ConcurrentHashMap<>();
private final ConcurrentHashMap<Integer, ModelEntityData> modelEntitiesCache = new ConcurrentHashMap<>();
private final ConcurrentHashMap<UUID, Pair<ActiveModel, Mount>> driversCache = new ConcurrentHashMap<>();
public ModelManager(GeyserModelEngine plugin) {
this.plugin = plugin;
}
public void create(ModeledEntity entity, ActiveModel model) {
ModelEntityData modelEntity = new ModelEntityData(plugin, entity, model);
int id = entity.getBase().getEntityId();
Map<ActiveModel, ModelEntityData> map = entitiesCache.computeIfAbsent(id, k -> new HashMap<>());
for (Map.Entry<ActiveModel, ModelEntityData> entry : map.entrySet()) {
if (entry.getKey() != model && entry.getKey().getBlueprint().getName().equals(model.getBlueprint().getName())) {
return;
}
}
map.put(model, modelEntity);
}
public void processEntities(Entity entity) {
if (entitiesCache.containsKey(entity.getEntityId())) return;
ModeledEntity modeledEntity = ModelEngineAPI.getModeledEntity(entity);
if (modeledEntity == null) return;
Optional<ActiveModel> model = modeledEntity.getModels().values().stream().findFirst();
model.ifPresent(m -> create(modeledEntity, m));
}
public HashSet<UUID> getPlayerJoinedCache() {
return playerJoinedCache;
}
public ConcurrentHashMap<Integer, Map<ActiveModel, ModelEntityData>> getEntitiesCache() {
return entitiesCache;
}
public ConcurrentHashMap<Integer, ModelEntityData> getModelEntitiesCache() {
return modelEntitiesCache;
}
public ConcurrentHashMap<UUID, Pair<ActiveModel, Mount>> getDriversCache() {
return driversCache;
}
}

View File

@@ -1,268 +0,0 @@
package re.imc.geysermodelengine.runnables;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.ticxo.modelengine.api.animation.BlueprintAnimation;
import com.ticxo.modelengine.api.animation.handler.AnimationHandler;
import com.ticxo.modelengine.api.generator.blueprint.BlueprintBone;
import com.ticxo.modelengine.api.model.ActiveModel;
import com.ticxo.modelengine.api.model.ModeledEntity;
import com.ticxo.modelengine.api.model.bone.ModelBone;
import me.zimzaza4.geyserutils.spigot.api.EntityUtils;
import org.bukkit.entity.Player;
import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.managers.model.data.ModelEntityData;
import re.imc.geysermodelengine.packet.entity.PacketEntity;
import re.imc.geysermodelengine.util.BooleanPacker;
import java.awt.*;
import java.util.*;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
public class EntityTaskRunnable {
private final GeyserModelEngine plugin;
private final ModelEntityData model;
private int tick = 0;
private int syncTick = 0;
private float lastScale = -1.0f;
private Color lastColor = null;
private boolean removed = false;
private final ConcurrentHashMap<String, Integer> lastIntSet = new ConcurrentHashMap<>();
private final Cache<String, Boolean> lastPlayedAnim = CacheBuilder.newBuilder().expireAfterWrite(30, TimeUnit.MILLISECONDS).build();
private final BooleanPacker booleanPacker = new BooleanPacker();
private final ScheduledFuture scheduledFuture;
public EntityTaskRunnable(GeyserModelEngine plugin, ModelEntityData model) {
this.plugin = plugin;
this.model = model;
plugin.getEntityTaskManager().sendHitBoxToAll(model);
scheduledFuture = plugin.getSchedulerPool().scheduleAtFixedRate(this::runAsync, 0, 20, TimeUnit.MILLISECONDS);
}
public void runAsync() {
plugin.getEntityTaskManager().checkViewers(model, model.getViewers());
PacketEntity entity = model.getEntity();
if (entity.isDead()) return;
model.teleportToModel();
Set<Player> viewers = model.getViewers();
ActiveModel activeModel = model.getActiveModel();
ModeledEntity modeledEntity = model.getModeledEntity();
if (activeModel.isDestroyed() || activeModel.isRemoved()) {
removed = true;
entity.remove();
plugin.getModelManager().getEntitiesCache().remove(modeledEntity.getBase().getEntityId());
plugin.getModelManager().getModelEntitiesCache().remove(entity.getEntityId());
cancel();
return;
}
if (tick % 5 == 0) {
if (tick % 40 == 0) {
for (Player viewer : Set.copyOf(viewers)) {
if (!plugin.getEntityTaskManager().canSee(viewer, model.getEntity())) {
viewers.remove(viewer);
}
}
}
}
tick ++;
if (tick > 400) {
tick = 0;
plugin.getEntityTaskManager().sendHitBoxToAll(model);
}
if (viewers.isEmpty()) return;
plugin.getEntityTaskManager().sendScale(model, viewers, lastScale, false);
plugin.getEntityTaskManager().sendColor(model, viewers, lastColor, false);
}
public void cancel() {
scheduledFuture.cancel(true);
}
public void sendEntityData(ModelEntityData model, Player player, int delay) {
//TODO with ModelEngine, you can define the namespace inside the config, make an option to change it here as well? if i'm right about this
EntityUtils.setCustomEntity(player, model.getEntity().getEntityId(), "modelengine:" + model.getActiveModel().getBlueprint().getName().toLowerCase());
plugin.getSchedulerPool().schedule(() -> {
model.getEntity().sendSpawnPacket(Collections.singletonList(player));
plugin.getSchedulerPool().schedule(() -> {
plugin.getEntityTaskManager().sendHitBox(model, player);
plugin.getEntityTaskManager().sendScale(model, Collections.singleton(player), lastScale, true);
plugin.getEntityTaskManager().sendColor(model, Collections.singleton(player), lastColor, true);
updateEntityProperties(model, Collections.singleton(player), true);
}, delay * 50L, TimeUnit.MILLISECONDS);
}, 500, TimeUnit.MILLISECONDS);
}
public void updateEntityProperties(ModelEntityData model, Collection<Player> players, boolean firstSend, String... forceAnims) {
int entity = model.getEntity().getEntityId();
Set<String> forceAnimSet = Set.of(forceAnims);
Map<String, Boolean> boneUpdates = new HashMap<>();
Map<String, Boolean> animUpdates = new HashMap<>();
Set<String> anims = new HashSet<>();
model.getActiveModel().getBlueprint().getBones().forEach((s, bone) -> processBone(model, bone, boneUpdates));
AnimationHandler handler = model.getActiveModel().getAnimationHandler();
Set<String> priority = model.getActiveModel().getBlueprint().getAnimationDescendingPriority();
for (String animId : priority) {
if (handler.isPlayingAnimation(animId)) {
BlueprintAnimation anim = model.getActiveModel().getBlueprint().getAnimations().get(animId);
anims.add(animId);
if (anim.isOverride() && anim.getLoopMode() == BlueprintAnimation.LoopMode.ONCE) {
break;
}
}
}
for (String id : priority) {
if (anims.contains(id)) {
animUpdates.put(id, true);
} else {
animUpdates.put(id, false);
}
}
Set<String> lastPlayed = new HashSet<>(lastPlayedAnim.asMap().keySet());
for (Map.Entry<String, Boolean> anim : animUpdates.entrySet()) {
if (anim.getValue()) {
lastPlayedAnim.put(anim.getKey(), true);
}
}
for (String anim : lastPlayed) animUpdates.put(anim, true);
if (boneUpdates.isEmpty() && animUpdates.isEmpty()) return;
Map<String, Integer> intUpdates = new HashMap<>();
int i = 0;
for (Integer integer : booleanPacker.mapBooleansToInts(boneUpdates)) {
intUpdates.put("modelengine:bone" + i, integer);
i++;
}
i = 0;
for (Integer integer : booleanPacker.mapBooleansToInts(animUpdates)) {
intUpdates.put("modelengine:anim" + i, integer);
i++;
}
if (!firstSend) {
if (intUpdates.equals(lastIntSet)) {
return;
} else {
lastIntSet.clear();
lastIntSet.putAll(intUpdates);
}
}
if (plugin.getConfigManager().getConfig().getBoolean("debug")) plugin.getLogger().info(animUpdates.toString());
List<String> list = new ArrayList<>(boneUpdates.keySet());
Collections.sort(list);
for (Player player : players) {
EntityUtils.sendIntProperties(player, entity, intUpdates);
}
}
private void processBone(ModelEntityData model, BlueprintBone bone, Map<String, Boolean> map) {
String name = plugin.getEntityTaskManager().unstripName(bone).toLowerCase();
if (name.equals("hitbox") ||
name.equals("shadow") ||
name.equals("mount") ||
name.startsWith("p_") ||
name.startsWith("b_") ||
name.startsWith("ob_")) {
return;
}
for (BlueprintBone blueprintBone : bone.getChildren().values()) processBone(model, blueprintBone, map);
ModelBone activeBone = model.getActiveModel().getBones().get(bone.getName());
boolean visible = false;
if (activeBone != null) visible = activeBone.isVisible();
map.put(name, visible);
}
public void setTick(int tick) {
this.tick = tick;
}
public void setSyncTick(int syncTick) {
this.syncTick = syncTick;
}
public void setRemoved(boolean removed) {
this.removed = removed;
}
public void setLastScale(float lastScale) {
this.lastScale = lastScale;
}
public int getTick() {
return tick;
}
public int getSyncTick() {
return syncTick;
}
public void setLastColor(Color lastColor) {
this.lastColor = lastColor;
}
public float getLastScale() {
return lastScale;
}
public Color getLastColor() {
return lastColor;
}
public boolean isRemoved() {
return removed;
}
public ConcurrentHashMap<String, Integer> getLastIntSet() {
return lastIntSet;
}
public Cache<String, Boolean> getLastPlayedAnim() {
return lastPlayedAnim;
}
public ScheduledFuture getScheduledFuture() {
return scheduledFuture;
}
}

View File

@@ -1,29 +0,0 @@
package re.imc.geysermodelengine.runnables;
import com.ticxo.modelengine.api.model.ActiveModel;
import io.papermc.paper.threadedregions.scheduler.ScheduledTask;
import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.managers.model.data.ModelEntityData;
import java.util.Map;
import java.util.function.Consumer;
public class UpdateTaskRunnable implements Consumer<ScheduledTask> {
private final GeyserModelEngine plugin;
public UpdateTaskRunnable(GeyserModelEngine plugin) {
this.plugin = plugin;
}
@Override
public void accept(ScheduledTask scheduledTask) {
try {
for (Map<ActiveModel, ModelEntityData> models : plugin.getModelManager().getEntitiesCache().values()) {
models.values().forEach(model -> model.getEntityTask().updateEntityProperties(model, model.getViewers(), false));
}
} catch (Throwable err) {
throw new RuntimeException(err);
}
}
}

View File

@@ -1,9 +0,0 @@
data-send-delay: 5
entity-view-distance: 50
join-send-delay: 20
entity-position-update-period: 35
thread-pool-size: 4
model-entity-type: BAT # must be a living entity
enable-part-visibility-models:
- example
debug: false