diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 5c4480e..b27e9d1 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -4,6 +4,7 @@ on:
push:
branches:
- main
+ - bettermodel-support-dev
jobs:
build:
@@ -30,8 +31,9 @@ jobs:
- name: Auto release
uses: "marvinpinto/action-automatic-releases@latest"
with:
- repo_token: "${{ secrets.GITHUB_TOKEN }}"
+ repo_token: "${{secrets.GITHUB_TOKEN}}"
automatic_release_tag: latest
prerelease: false
files: |
- build/libs/GeyserModelEngine*.jar
+ paper/build/libs/GeyserModelEngine*.jar
+ geyser/build/libs/GeyserModelEngine*.jar
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..26d3352
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
index aa00ffa..da33708 100644
--- a/.idea/encodings.xml
+++ b/.idea/encodings.xml
@@ -1,7 +1,7 @@
-
-
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index ce1c62c..79647b7 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -8,6 +8,8 @@
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index 58d74b2..0f855d4 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -4,14 +4,15 @@
-
-
-
+
+
+
+
@@ -59,7 +60,7 @@
-
+
@@ -77,35 +78,88 @@
- {
+ "keyToString": {
+ "Gradle.Build GeyserModelEngine.executor": "Run",
+ "Gradle.Download Sources.executor": "Run",
+ "Gradle.GeyserModelEngine [buildDependents].executor": "Run",
+ "Gradle.GeyserModelEngine [buildNeeded].executor": "Run",
+ "Gradle.GeyserModelEngine [build].executor": "Run",
+ "Gradle.GeyserModelEngine [clean].executor": "Run",
+ "Gradle.GeyserModelEngine [jar].executor": "Run",
+ "Maven.GeyserModelEngine [install...].executor": "Run",
+ "Maven.GeyserModelEngine [install].executor": "Run",
+ "ModuleVcsDetector.initialDetectionPerformed": "true",
+ "RunOnceActivity.ShowReadmeOnStart": "true",
+ "RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
+ "RunOnceActivity.git.unshallow": "true",
+ "git-widget-placeholder": "main",
+ "ignore.virus.scanning.warn.message": "true",
+ "kotlin-language-version-configured": "true",
+ "last_opened_file_path": "D:/Coding/Forks/Minecraft/GeyserModelEngine/geyser",
+ "project.structure.last.edited": "Project",
+ "project.structure.proportion": "0.0",
+ "project.structure.side.proportion": "0.2",
+ "settings.editor.selected.configurable": "reference.settingsdialog.project.gradle"
}
-}]]>
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ true
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ true
+ false
+ false
+
+
@@ -128,6 +182,28 @@
false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ true
+ false
+ false
+
+
@@ -154,6 +230,9 @@
+
+
+
diff --git a/build.gradle.kts b/build.gradle.kts
index 287f7a5..19d3d7a 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,6 +1,6 @@
plugins {
id("java")
- id("io.github.goooler.shadow") version "8.1.8"
+ id("com.gradleup.shadow") version "9.2.2"
}
group = "re.imc"
@@ -8,30 +8,10 @@ version = "1.0.0"
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(files("libs/geyserutils-spigot-1.0-SNAPSHOT.jar"))
- compileOnly("org.geysermc.floodgate:api:2.2.4-SNAPSHOT")
-
- implementation("com.github.retrooper:packetevents-spigot:2.10.0")
- implementation("org.bstats:bstats-bukkit:3.0.2")
-
- implementation("org.reflections:reflections:0.10.2")
}
java {
@@ -40,21 +20,4 @@ java {
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")
}
\ No newline at end of file
diff --git a/geyser/build.gradle.kts b/geyser/build.gradle.kts
new file mode 100644
index 0000000..17073d5
--- /dev/null
+++ b/geyser/build.gradle.kts
@@ -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")
+}
diff --git a/geyser/libs/geyserutils-geyser-1.0-SNAPSHOT.jar b/geyser/libs/geyserutils-geyser-1.0-SNAPSHOT.jar
new file mode 100644
index 0000000..e4579a4
Binary files /dev/null and b/geyser/libs/geyserutils-geyser-1.0-SNAPSHOT.jar differ
diff --git a/geyser/src/main/java/re/imc/geysermodelengineextension/GeyserModelEngineExtension.java b/geyser/src/main/java/re/imc/geysermodelengineextension/GeyserModelEngineExtension.java
new file mode 100644
index 0000000..f5a5b35
--- /dev/null
+++ b/geyser/src/main/java/re/imc/geysermodelengineextension/GeyserModelEngineExtension.java
@@ -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;
+ }
+}
diff --git a/geyser/src/main/java/re/imc/geysermodelengineextension/managers/ConfigManager.java b/geyser/src/main/java/re/imc/geysermodelengineextension/managers/ConfigManager.java
new file mode 100644
index 0000000..4220a02
--- /dev/null
+++ b/geyser/src/main/java/re/imc/geysermodelengineextension/managers/ConfigManager.java
@@ -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;
+ }
+}
diff --git a/geyser/src/main/java/re/imc/geysermodelengineextension/managers/resourcepack/ResourcePackManager.java b/geyser/src/main/java/re/imc/geysermodelengineextension/managers/resourcepack/ResourcePackManager.java
new file mode 100644
index 0000000..2f5135d
--- /dev/null
+++ b/geyser/src/main/java/re/imc/geysermodelengineextension/managers/resourcepack/ResourcePackManager.java
@@ -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 entityCache = new HashMap<>();
+ private final HashMap animationCache = new HashMap<>();
+ private final HashMap geometryCache = new HashMap<>();
+ private final HashMap> 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 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 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 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> textures : textureCache.entrySet()) {
+ for (Map.Entry 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 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 bindingBones = new HashSet<>();
+ bindingBones.add("*");
+ if (modelConfig.getBingingBones().containsKey(textureName)) bindingBones = modelConfig.getBingingBones().get(textureName);
+
+ Map 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 bindingBones = new HashSet<>();
+ bindingBones.add("*");
+
+ if (modelConfig.getBingingBones().containsKey(textureName)) {
+ bindingBones = modelConfig.getBingingBones().get(textureName);
+ }
+
+ Map 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 getEntityCache() {
+ return entityCache;
+ }
+
+ public HashMap getAnimationCache() {
+ return animationCache;
+ }
+
+ public HashMap getGeometryCache() {
+ return geometryCache;
+ }
+
+ public HashMap> getTextureCache() {
+ return textureCache;
+ }
+}
diff --git a/geyser/src/main/java/re/imc/geysermodelengineextension/managers/resourcepack/generator/Animation.java b/geyser/src/main/java/re/imc/geysermodelengineextension/managers/resourcepack/generator/Animation.java
new file mode 100644
index 0000000..ff0945c
--- /dev/null
+++ b/geyser/src/main/java/re/imc/geysermodelengineextension/managers/resourcepack/generator/Animation.java
@@ -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 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 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 bone : animation.get("bones").getAsJsonObject().entrySet()) {
+
+ for (Map.Entry anim : bone.getValue().getAsJsonObject().entrySet()) {
+ float max = -1;
+ JsonObject end = null;
+ if (!anim.getValue().isJsonObject()) {
+ continue;
+ }
+ try {
+ for (Map.Entry 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 animationIds) {
+ this.animationIds = animationIds;
+ }
+
+ public String getModelId() {
+ return modelId;
+ }
+
+ public void setPath(String path) {
+ this.path = path;
+ }
+
+ public JsonObject getJson() {
+ return json;
+ }
+
+ public Set getAnimationIds() {
+ return animationIds;
+ }
+
+ public String getPath() {
+ return path;
+ }
+}
diff --git a/geyser/src/main/java/re/imc/geysermodelengineextension/managers/resourcepack/generator/AnimationController.java b/geyser/src/main/java/re/imc/geysermodelengineextension/managers/resourcepack/generator/AnimationController.java
new file mode 100644
index 0000000..420a66f
--- /dev/null
+++ b/geyser/src/main/java/re/imc/geysermodelengineextension/managers/resourcepack/generator/AnimationController.java
@@ -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 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 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;
+ }
+}
diff --git a/geyser/src/main/java/re/imc/geysermodelengineextension/managers/resourcepack/generator/Entity.java b/geyser/src/main/java/re/imc/geysermodelengineextension/managers/resourcepack/generator/Entity.java
new file mode 100644
index 0000000..39d98d4
--- /dev/null
+++ b/geyser/src/main/java/re/imc/geysermodelengineextension/managers/resourcepack/generator/Entity.java
@@ -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 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 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 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 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 getTextureMap() {
+ return textureMap;
+ }
+
+ public ModelConfig getModelConfig() {
+ return modelConfig;
+ }
+}
diff --git a/geyser/src/main/java/re/imc/geysermodelengineextension/managers/resourcepack/generator/Geometry.java b/geyser/src/main/java/re/imc/geysermodelengineextension/managers/resourcepack/generator/Geometry.java
new file mode 100644
index 0000000..90ecd30
--- /dev/null
+++ b/geyser/src/main/java/re/imc/geysermodelengineextension/managers/resourcepack/generator/Geometry.java
@@ -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 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 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 getBones() {
+ return bones;
+ }
+}
diff --git a/geyser/src/main/java/re/imc/geysermodelengineextension/managers/resourcepack/generator/Material.java b/geyser/src/main/java/re/imc/geysermodelengineextension/managers/resourcepack/generator/Material.java
new file mode 100644
index 0000000..8f56889
--- /dev/null
+++ b/geyser/src/main/java/re/imc/geysermodelengineextension/managers/resourcepack/generator/Material.java
@@ -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"
+ ]
+ }
+ }
+ }
+ """;
+}
diff --git a/geyser/src/main/java/re/imc/geysermodelengineextension/managers/resourcepack/generator/ModelConfig.java b/geyser/src/main/java/re/imc/geysermodelengineextension/managers/resourcepack/generator/ModelConfig.java
new file mode 100644
index 0000000..4fe60a1
--- /dev/null
+++ b/geyser/src/main/java/re/imc/geysermodelengineextension/managers/resourcepack/generator/ModelConfig.java
@@ -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> bingingBones = new HashMap<>();
+ @SerializedName("anim_textures")
+ Map animTextures = new HashMap<>();
+ @SerializedName("texture_materials")
+ Map textureMaterials = new HashMap<>();
+ @SerializedName("per_texture_uv_size")
+ Map 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> bingingBones) {
+ this.bingingBones = bingingBones;
+ }
+
+ public void setAnimTextures(Map animTextures) {
+ this.animTextures = animTextures;
+ }
+
+ public void setTextureMaterials(Map textureMaterials) {
+ this.textureMaterials = textureMaterials;
+ }
+
+ public void setPerTextureUvSize(Map perTextureUvSize) {
+ this.perTextureUvSize = perTextureUvSize;
+ }
+
+ public void setDisablePartVisibility(boolean disablePartVisibility) {
+ this.disablePartVisibility = disablePartVisibility;
+ }
+
+ public Map getTextureMaterials() {
+ return textureMaterials != null ? textureMaterials : Map.of();
+ }
+
+ public Map getPerTextureUvSize() {
+ return perTextureUvSize != null ? perTextureUvSize : Map.of();
+ }
+
+ public boolean isEnableHeadRotation() {
+ return enableHeadRotation;
+ }
+
+ public String getMaterial() {
+ return material;
+ }
+
+ public boolean isEnableBlendTransition() {
+ return enableBlendTransition;
+ }
+
+ public Map> getBingingBones() {
+ return bingingBones;
+ }
+
+ public Map getAnimTextures() {
+ return animTextures;
+ }
+
+ public boolean isDisablePartVisibility() {
+ return disablePartVisibility;
+ }
+
+ @NoArgsConstructor
+ @AllArgsConstructor
+ @Getter
+ @Setter
+ public static class AnimTextureOptions {
+ float fps;
+ int frames;
+ }
+}
diff --git a/geyser/src/main/java/re/imc/geysermodelengineextension/managers/resourcepack/generator/PackManifest.java b/geyser/src/main/java/re/imc/geysermodelengineextension/managers/resourcepack/generator/PackManifest.java
new file mode 100644
index 0000000..544c7af
--- /dev/null
+++ b/geyser/src/main/java/re/imc/geysermodelengineextension/managers/resourcepack/generator/PackManifest.java
@@ -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());
+ }
+}
diff --git a/geyser/src/main/java/re/imc/geysermodelengineextension/managers/resourcepack/generator/RenderController.java b/geyser/src/main/java/re/imc/geysermodelengineextension/managers/resourcepack/generator/RenderController.java
new file mode 100644
index 0000000..92724fa
--- /dev/null
+++ b/geyser/src/main/java/re/imc/geysermodelengineextension/managers/resourcepack/generator/RenderController.java
@@ -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 NEED_REMOVE_WHEN_SORT = Set.of("pbody_", "plarm_", "prarm_", "plleg_", "prleg_", "phead_", "p_");
+
+ private final String modelId;
+ private final Map bones;
+ private final Entity entity;
+
+ public RenderController(String modelId, Map 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 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 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 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 sorted = new ArrayList<>(bones.keySet());
+ Map originalId = new HashMap<>();
+ ListIterator 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 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> 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();
+ }
+
+}
diff --git a/geyser/src/main/java/re/imc/geysermodelengineextension/managers/resourcepack/generator/data/BoneData.java b/geyser/src/main/java/re/imc/geysermodelengineextension/managers/resourcepack/generator/data/BoneData.java
new file mode 100644
index 0000000..a8b1320
--- /dev/null
+++ b/geyser/src/main/java/re/imc/geysermodelengineextension/managers/resourcepack/generator/data/BoneData.java
@@ -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 children;
+ private final Set allChildren;
+
+ public BoneData(String name, String parent, Set children, Set 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 getChildren() {
+ return children;
+ }
+
+ public Set getAllChildren() {
+ return allChildren;
+ }
+}
diff --git a/geyser/src/main/java/re/imc/geysermodelengineextension/managers/resourcepack/generator/data/TextureData.java b/geyser/src/main/java/re/imc/geysermodelengineextension/managers/resourcepack/generator/data/TextureData.java
new file mode 100644
index 0000000..f4e1b62
--- /dev/null
+++ b/geyser/src/main/java/re/imc/geysermodelengineextension/managers/resourcepack/generator/data/TextureData.java
@@ -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 bindingBones;
+ private final byte[] image;
+
+ public TextureData(String modelId, String path, Set 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 getBindingBones() {
+ return bindingBones;
+ }
+
+ public byte[] getImage() {
+ return image;
+ }
+}
diff --git a/geyser/src/main/java/re/imc/geysermodelengineextension/util/BooleanPacker.java b/geyser/src/main/java/re/imc/geysermodelengineextension/util/BooleanPacker.java
new file mode 100644
index 0000000..8461d6f
--- /dev/null
+++ b/geyser/src/main/java/re/imc/geysermodelengineextension/util/BooleanPacker.java
@@ -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 booleans) {
+ int result = 0;
+ int i = 1;
+
+ for (boolean b : booleans) {
+ if (b) result += i;
+ i *= 2;
+ }
+
+ return result;
+ }
+
+ public static int mapBooleansToInt(Map booleanMap) {
+ int result = 0;
+ int i = 1;
+ List 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 booleansToInts(List booleans) {
+ List 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 mapBooleansToInts(Map booleanMap) {
+ List keys = new ArrayList<>(booleanMap.keySet());
+ List booleans = new ArrayList<>();
+ Collections.sort(keys);
+
+ for (String key : keys) {
+ booleans.add(booleanMap.get(key));
+ }
+
+ return booleansToInts(booleans);
+ }
+}
diff --git a/geyser/src/main/java/re/imc/geysermodelengineextension/util/FileConfiguration.java b/geyser/src/main/java/re/imc/geysermodelengineextension/util/FileConfiguration.java
new file mode 100644
index 0000000..5bbb0d0
--- /dev/null
+++ b/geyser/src/main/java/re/imc/geysermodelengineextension/util/FileConfiguration.java
@@ -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 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 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 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;
+ }
+}
diff --git a/geyser/src/main/java/re/imc/geysermodelengineextension/util/FileUtils.java b/geyser/src/main/java/re/imc/geysermodelengineextension/util/FileUtils.java
new file mode 100644
index 0000000..c94bb5b
--- /dev/null
+++ b/geyser/src/main/java/re/imc/geysermodelengineextension/util/FileUtils.java
@@ -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 getAllFiles(File folder, String fileType) {
+ List 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);
+ }
+ }
+}
diff --git a/geyser/src/main/java/re/imc/geysermodelengineextension/util/ZipUtil.java b/geyser/src/main/java/re/imc/geysermodelengineextension/util/ZipUtil.java
new file mode 100644
index 0000000..3be1cec
--- /dev/null
+++ b/geyser/src/main/java/re/imc/geysermodelengineextension/util/ZipUtil.java
@@ -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();
+ }
+}
diff --git a/geyser/src/main/resources/Extension/Lang/messages.yml b/geyser/src/main/resources/Extension/Lang/messages.yml
new file mode 100644
index 0000000..e5ff741
--- /dev/null
+++ b/geyser/src/main/resources/Extension/Lang/messages.yml
@@ -0,0 +1,4 @@
+commands:
+ geysermodelenginepackgenerator:
+ reload:
+ successfully-reloaded: "GeyserModelEnginePackGenerator reloaded!"
\ No newline at end of file
diff --git a/geyser/src/main/resources/Extension/config.yml b/geyser/src/main/resources/Extension/config.yml
new file mode 100644
index 0000000..f176c8b
--- /dev/null
+++ b/geyser/src/main/resources/Extension/config.yml
@@ -0,0 +1,6 @@
+models:
+ namespace: "modelengine"
+
+options:
+ resource-pack:
+ auto-load: true
\ No newline at end of file
diff --git a/geyser/src/main/resources/extension.yml b/geyser/src/main/resources/extension.yml
new file mode 100644
index 0000000..f7e3455
--- /dev/null
+++ b/geyser/src/main/resources/extension.yml
@@ -0,0 +1,8 @@
+name: GeyserModelEngineExtension
+id: geysermodelengineextension
+main: re.imc.geysermodelengineextension.GeyserModelEngineExtension
+api: 2.7.0
+version: 1.0.0
+authors:
+ - zimzaza4
+ - xSquishyLiam
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index cea7a79..bad7c24 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
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
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
diff --git a/paper/build.gradle.kts b/paper/build.gradle.kts
new file mode 100644
index 0000000..73239ab
--- /dev/null
+++ b/paper/build.gradle.kts
@@ -0,0 +1,61 @@
+plugins {
+ id("java")
+ id("com.gradleup.shadow") version "9.2.2"
+}
+
+group = "re.imc"
+version = "1.0.0"
+
+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")
+}
\ No newline at end of file
diff --git a/libs/geyserutils-spigot-1.0-SNAPSHOT.jar b/paper/libs/geyserutils-spigot-1.0-SNAPSHOT.jar
similarity index 82%
rename from libs/geyserutils-spigot-1.0-SNAPSHOT.jar
rename to paper/libs/geyserutils-spigot-1.0-SNAPSHOT.jar
index f5cefdb..3eec7cf 100644
Binary files a/libs/geyserutils-spigot-1.0-SNAPSHOT.jar and b/paper/libs/geyserutils-spigot-1.0-SNAPSHOT.jar differ
diff --git a/src/main/java/re/imc/geysermodelengine/GeyserModelEngine.java b/paper/src/main/java/re/imc/geysermodelengine/GeyserModelEngine.java
similarity index 79%
rename from src/main/java/re/imc/geysermodelengine/GeyserModelEngine.java
rename to paper/src/main/java/re/imc/geysermodelengine/GeyserModelEngine.java
index c347832..608fa8f 100644
--- a/src/main/java/re/imc/geysermodelengine/GeyserModelEngine.java
+++ b/paper/src/main/java/re/imc/geysermodelengine/GeyserModelEngine.java
@@ -2,9 +2,6 @@ package re.imc.geysermodelengine;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.event.PacketListenerPriority;
-import dev.jorel.commandapi.CommandAPI;
-import dev.jorel.commandapi.CommandAPIBukkitConfig;
-import dev.jorel.commandapi.CommandAPIPaperConfig;
import io.github.retrooper.packetevents.factory.spigot.SpigotPacketEventsBuilder;
import org.bstats.bukkit.Metrics;
import org.bukkit.Bukkit;
@@ -36,7 +33,7 @@ public class GeyserModelEngine extends JavaPlugin {
PacketEvents.setAPI(SpigotPacketEventsBuilder.build(this));
PacketEvents.getAPI().load();
- CommandAPI.onLoad(new CommandAPIPaperConfig(this));
+// CommandAPI.onLoad(new CommandAPIPaperConfig(this));
}
@Override
@@ -54,20 +51,19 @@ public class GeyserModelEngine extends JavaPlugin {
@Override
public void onDisable() {
- PacketEvents.getAPI().terminate();
-
this.modelManager.removeEntities();
- CommandAPI.onDisable();
+ PacketEvents.getAPI().terminate();
+// CommandAPI.onDisable();
}
private void loadHooks() {
PacketEvents.getAPI().init();
- CommandAPI.onEnable();
+// CommandAPI.onEnable();
}
private void loadBStats() {
- if (configManager.getConfig().getBoolean("bstats", true)) new Metrics(this, 26981);
+ if (configManager.getConfig().getBoolean("metrics.bstats", true)) new Metrics(this, 26981);
}
private void loadManagers() {
@@ -80,10 +76,10 @@ public class GeyserModelEngine extends JavaPlugin {
}
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));
- Bukkit.getAsyncScheduler().runAtFixedRate(this, new UpdateTaskRunnable(this), 10, configManager.getConfig().getLong("entity-position-update-period", 35), TimeUnit.MILLISECONDS);
- Bukkit.getAsyncScheduler().runAtFixedRate(this, new BedrockMountControlRunnable(this), 1, 1, TimeUnit.MILLISECONDS);
+ schedulerPool.scheduleAtFixedRate(new UpdateTaskRunnable(this), 10, configManager.getConfig().getLong("models.entity-position-update-period", 35), TimeUnit.MILLISECONDS);
+ schedulerPool.scheduleAtFixedRate(new BedrockMountControlRunnable(this), 1, 1, TimeUnit.MILLISECONDS);
}
public ConfigManager getConfigManager() {
diff --git a/paper/src/main/java/re/imc/geysermodelengine/commands/geysermodelenginecommands/GeyserModelEngineReloadCommand.java b/paper/src/main/java/re/imc/geysermodelengine/commands/geysermodelenginecommands/GeyserModelEngineReloadCommand.java
new file mode 100644
index 0000000..5ecd25f
--- /dev/null
+++ b/paper/src/main/java/re/imc/geysermodelengine/commands/geysermodelenginecommands/GeyserModelEngineReloadCommand.java
@@ -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")));
+// });
+// }
+}
\ No newline at end of file
diff --git a/paper/src/main/java/re/imc/geysermodelengine/listener/BetterModelListener.java b/paper/src/main/java/re/imc/geysermodelengine/listener/BetterModelListener.java
new file mode 100644
index 0000000..fb7046e
--- /dev/null
+++ b/paper/src/main/java/re/imc/geysermodelengine/listener/BetterModelListener.java
@@ -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);
+ }
+}
diff --git a/paper/src/main/java/re/imc/geysermodelengine/listener/ModelEngineListener.java b/paper/src/main/java/re/imc/geysermodelengine/listener/ModelEngineListener.java
new file mode 100644
index 0000000..c0c35af
--- /dev/null
+++ b/paper/src/main/java/re/imc/geysermodelengine/listener/ModelEngineListener.java
@@ -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 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());
+ }
+ }
+}
diff --git a/paper/src/main/java/re/imc/geysermodelengine/listener/ModelListener.java b/paper/src/main/java/re/imc/geysermodelengine/listener/ModelListener.java
new file mode 100644
index 0000000..28eeac0
--- /dev/null
+++ b/paper/src/main/java/re/imc/geysermodelengine/listener/ModelListener.java
@@ -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());
+ }
+}
diff --git a/src/main/java/re/imc/geysermodelengine/listener/MountPacketListener.java b/paper/src/main/java/re/imc/geysermodelengine/listener/MountPacketListener.java
similarity index 93%
rename from src/main/java/re/imc/geysermodelengine/listener/MountPacketListener.java
rename to paper/src/main/java/re/imc/geysermodelengine/listener/MountPacketListener.java
index cdda581..ed89048 100644
--- a/src/main/java/re/imc/geysermodelengine/listener/MountPacketListener.java
+++ b/paper/src/main/java/re/imc/geysermodelengine/listener/MountPacketListener.java
@@ -11,6 +11,7 @@ import org.apache.commons.lang3.tuple.Pair;
import org.bukkit.entity.Player;
import org.geysermc.floodgate.api.FloodgateApi;
import re.imc.geysermodelengine.GeyserModelEngine;
+import re.imc.geysermodelengine.util.BedrockUtils;
public class MountPacketListener implements PacketListener {
@@ -23,9 +24,9 @@ public class MountPacketListener implements PacketListener {
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (event.getPacketType() != PacketType.Play.Client.ENTITY_ACTION) return;
- if (!FloodgateApi.getInstance().isFloodgatePlayer(event.getUser().getUUID())) return;
Player player = event.getPlayer();
+ if (!BedrockUtils.isBedrockPlayer(player)) return;
WrapperPlayClientEntityAction action = new WrapperPlayClientEntityAction(event);
Pair seat = plugin.getModelManager().getDriversCache().get(player.getUniqueId());
diff --git a/src/main/java/re/imc/geysermodelengine/managers/ConfigManager.java b/paper/src/main/java/re/imc/geysermodelengine/managers/ConfigManager.java
similarity index 100%
rename from src/main/java/re/imc/geysermodelengine/managers/ConfigManager.java
rename to paper/src/main/java/re/imc/geysermodelengine/managers/ConfigManager.java
diff --git a/src/main/java/re/imc/geysermodelengine/managers/commands/CommandManager.java b/paper/src/main/java/re/imc/geysermodelengine/managers/commands/CommandManager.java
similarity index 87%
rename from src/main/java/re/imc/geysermodelengine/managers/commands/CommandManager.java
rename to paper/src/main/java/re/imc/geysermodelengine/managers/commands/CommandManager.java
index fe46a7a..a1f76d7 100644
--- a/src/main/java/re/imc/geysermodelengine/managers/commands/CommandManager.java
+++ b/paper/src/main/java/re/imc/geysermodelengine/managers/commands/CommandManager.java
@@ -21,8 +21,8 @@ public class CommandManager {
for (Class> clazz : new Reflections(path).getSubTypesOf(CommandManagers.class)) {
try {
CommandManagers commandManager = (CommandManagers) clazz.getDeclaredConstructor(GeyserModelEngine.class).newInstance(plugin);
- plugin.getLogger().warning("Loading Command Manager - " + commandManager.name());
- commandManagersCache.put(commandManager.name(), commandManager);
+ plugin.getLogger().info("Loading Command Manager - " + commandManager.getName());
+ commandManagersCache.put(commandManager.getName(), commandManager);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException err) {
plugin.getLogger().severe("Failed to load Command Manager " + clazz.getName());
throw new RuntimeException(err);
diff --git a/src/main/java/re/imc/geysermodelengine/managers/commands/CommandManagers.java b/paper/src/main/java/re/imc/geysermodelengine/managers/commands/CommandManagers.java
similarity index 62%
rename from src/main/java/re/imc/geysermodelengine/managers/commands/CommandManagers.java
rename to paper/src/main/java/re/imc/geysermodelengine/managers/commands/CommandManagers.java
index d757bbc..89e2ee2 100644
--- a/src/main/java/re/imc/geysermodelengine/managers/commands/CommandManagers.java
+++ b/paper/src/main/java/re/imc/geysermodelengine/managers/commands/CommandManagers.java
@@ -6,7 +6,13 @@ import java.util.ArrayList;
public interface CommandManagers {
- String name();
+ /**
+ * Gets the name of the command manager
+ */
+ String getName();
+ /**
+ * Gets the command manager subcommands
+ */
ArrayList getCommands();
}
diff --git a/src/main/java/re/imc/geysermodelengine/managers/commands/managers/geysermodelengine/GeyserModelEngineCommandManager.java b/paper/src/main/java/re/imc/geysermodelengine/managers/commands/managers/geysermodelengine/GeyserModelEngineCommandManager.java
similarity index 74%
rename from src/main/java/re/imc/geysermodelengine/managers/commands/managers/geysermodelengine/GeyserModelEngineCommandManager.java
rename to paper/src/main/java/re/imc/geysermodelengine/managers/commands/managers/geysermodelengine/GeyserModelEngineCommandManager.java
index 3d13b29..c821956 100644
--- a/src/main/java/re/imc/geysermodelengine/managers/commands/managers/geysermodelengine/GeyserModelEngineCommandManager.java
+++ b/paper/src/main/java/re/imc/geysermodelengine/managers/commands/managers/geysermodelengine/GeyserModelEngineCommandManager.java
@@ -1,6 +1,5 @@
package re.imc.geysermodelengine.managers.commands.managers.geysermodelengine;
-import dev.jorel.commandapi.CommandAPICommand;
import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.commands.geysermodelenginecommands.GeyserModelEngineReloadCommand;
import re.imc.geysermodelengine.managers.commands.CommandManagers;
@@ -19,15 +18,15 @@ public class GeyserModelEngineCommandManager implements CommandManagers {
}
private void registerCommand() {
- CommandAPICommand geyserModelEngineCommand = new CommandAPICommand(name());
-
- commands.forEach(subCommands -> geyserModelEngineCommand.withSubcommand(subCommands.onCommand()));
-
- geyserModelEngineCommand.register();
+// CommandAPICommand geyserModelEngineCommand = new CommandAPICommand(getName());
+//
+// commands.forEach(subCommands -> geyserModelEngineCommand.withSubcommand(subCommands.onCommand()));
+//
+// geyserModelEngineCommand.register();
}
@Override
- public String name() {
+ public String getName() {
return "geysermodelengine";
}
diff --git a/src/main/java/re/imc/geysermodelengine/managers/commands/subcommands/SubCommands.java b/paper/src/main/java/re/imc/geysermodelengine/managers/commands/subcommands/SubCommands.java
similarity index 54%
rename from src/main/java/re/imc/geysermodelengine/managers/commands/subcommands/SubCommands.java
rename to paper/src/main/java/re/imc/geysermodelengine/managers/commands/subcommands/SubCommands.java
index ebdc2a0..7391e64 100644
--- a/src/main/java/re/imc/geysermodelengine/managers/commands/subcommands/SubCommands.java
+++ b/paper/src/main/java/re/imc/geysermodelengine/managers/commands/subcommands/SubCommands.java
@@ -1,7 +1,9 @@
package re.imc.geysermodelengine.managers.commands.subcommands;
-import dev.jorel.commandapi.CommandAPICommand;
-
public interface SubCommands {
- CommandAPICommand onCommand();
+
+ /**
+ * Subcommand setup
+ */
+// CommandAPICommand onCommand();
}
diff --git a/paper/src/main/java/re/imc/geysermodelengine/managers/model/EntityTaskManager.java b/paper/src/main/java/re/imc/geysermodelengine/managers/model/EntityTaskManager.java
new file mode 100644
index 0000000..68580fb
--- /dev/null
+++ b/paper/src/main/java/re/imc/geysermodelengine/managers/model/EntityTaskManager.java
@@ -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 viewers) {
+ for (Player onlinePlayer : Bukkit.getOnlinePlayers()) {
+ if (!BedrockUtils.isBedrockPlayer(onlinePlayer)) return;
+
+ 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;
+ }
+}
diff --git a/paper/src/main/java/re/imc/geysermodelengine/managers/model/ModelManager.java b/paper/src/main/java/re/imc/geysermodelengine/managers/model/ModelManager.java
new file mode 100644
index 0000000..1f21d23
--- /dev/null
+++ b/paper/src/main/java/re/imc/geysermodelengine/managers/model/ModelManager.java
@@ -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 playerJoinedCache = new HashSet<>();
+
+ private final ConcurrentHashMap modelEntitiesCache = new ConcurrentHashMap<>();
+ private final ConcurrentHashMap> entitiesCache = new ConcurrentHashMap<>();
+
+ // MEG ONLY
+ private final ConcurrentHashMap> 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 entities : entitiesCache.values()) {
+ entities.forEach((model, modelEntity) -> modelEntity.getEntity().remove());
+ }
+ }
+
+ public ModelHandler getModelHandler() {
+ return modelHandler;
+ }
+
+ public HashSet getPlayerJoinedCache() {
+ return playerJoinedCache;
+ }
+
+ public ConcurrentHashMap> getEntitiesCache() {
+ return entitiesCache;
+ }
+
+ public ConcurrentHashMap getModelEntitiesCache() {
+ return modelEntitiesCache;
+ }
+
+ public ConcurrentHashMap> getDriversCache() {
+ return driversCache;
+ }
+}
diff --git a/paper/src/main/java/re/imc/geysermodelengine/managers/model/entity/BetterModelEntityData.java b/paper/src/main/java/re/imc/geysermodelengine/managers/model/entity/BetterModelEntityData.java
new file mode 100644
index 0000000..f76adee
--- /dev/null
+++ b/paper/src/main/java/re/imc/geysermodelengine/managers/model/entity/BetterModelEntityData.java
@@ -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 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 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;
+ }
+}
diff --git a/paper/src/main/java/re/imc/geysermodelengine/managers/model/entity/EntityData.java b/paper/src/main/java/re/imc/geysermodelengine/managers/model/entity/EntityData.java
new file mode 100644
index 0000000..780dd41
--- /dev/null
+++ b/paper/src/main/java/re/imc/geysermodelengine/managers/model/entity/EntityData.java
@@ -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 getViewers();
+
+ /**
+ * Get the entity task handler
+ */
+ TaskHandler getEntityTask();
+}
diff --git a/src/main/java/re/imc/geysermodelengine/managers/model/data/ModelEntityData.java b/paper/src/main/java/re/imc/geysermodelengine/managers/model/entity/ModelEngineEntityData.java
similarity index 63%
rename from src/main/java/re/imc/geysermodelengine/managers/model/data/ModelEntityData.java
rename to paper/src/main/java/re/imc/geysermodelengine/managers/model/entity/ModelEngineEntityData.java
index eadfd1b..4454775 100644
--- a/src/main/java/re/imc/geysermodelengine/managers/model/data/ModelEntityData.java
+++ b/paper/src/main/java/re/imc/geysermodelengine/managers/model/entity/ModelEngineEntityData.java
@@ -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.google.common.collect.Sets;
@@ -7,57 +7,58 @@ import com.ticxo.modelengine.api.model.ModeledEntity;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import re.imc.geysermodelengine.GeyserModelEngine;
+import re.imc.geysermodelengine.managers.model.taskshandler.ModelEngineTaskHandler;
import re.imc.geysermodelengine.packet.entity.PacketEntity;
-import re.imc.geysermodelengine.runnables.EntityTaskRunnable;
import java.util.Set;
-public class ModelEntityData {
+public class ModelEngineEntityData implements EntityData {
private final GeyserModelEngine plugin;
- private PacketEntity entity;
-
+ private final PacketEntity entity;
private final Set viewers = Sets.newConcurrentHashSet();
private final ModeledEntity modeledEntity;
-
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.modeledEntity = modeledEntity;
- this.activeModel = model;
- this.entity = spawnEntity();
+ this.activeModel = activeModel;
+ this.entity = new PacketEntity(EntityTypes.PIG, viewers, modeledEntity.getBase().getLocation());
runEntityTask();
}
+ @Override
public void teleportToModel() {
Location location = modeledEntity.getBase().getLocation();
entity.teleport(location);
}
- public PacketEntity spawnEntity() {
- entity = new PacketEntity(EntityTypes.PIG, viewers, modeledEntity.getBase().getLocation());
- return entity;
- }
-
public void runEntityTask() {
- entityTask = new EntityTaskRunnable(plugin, this);
+ entityTask = new ModelEngineTaskHandler(plugin, this);
}
+ @Override
public PacketEntity getEntity() {
return entity;
}
+ @Override
public Set getViewers() {
return viewers;
}
+ @Override
+ public ModelEngineTaskHandler getEntityTask() {
+ return entityTask;
+ }
+
public ModeledEntity getModeledEntity() {
return modeledEntity;
}
@@ -65,8 +66,4 @@ public class ModelEntityData {
public ActiveModel getActiveModel() {
return activeModel;
}
-
- public EntityTaskRunnable getEntityTask() {
- return entityTask;
- }
}
diff --git a/paper/src/main/java/re/imc/geysermodelengine/managers/model/model/BetterModelModel.java b/paper/src/main/java/re/imc/geysermodelengine/managers/model/model/BetterModelModel.java
new file mode 100644
index 0000000..b8f83c5
--- /dev/null
+++ b/paper/src/main/java/re/imc/geysermodelengine/managers/model/model/BetterModelModel.java
@@ -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;
+ }
+}
diff --git a/paper/src/main/java/re/imc/geysermodelengine/managers/model/model/Model.java b/paper/src/main/java/re/imc/geysermodelengine/managers/model/model/Model.java
new file mode 100644
index 0000000..44710a9
--- /dev/null
+++ b/paper/src/main/java/re/imc/geysermodelengine/managers/model/model/Model.java
@@ -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();
+}
diff --git a/paper/src/main/java/re/imc/geysermodelengine/managers/model/model/ModelEngineModel.java b/paper/src/main/java/re/imc/geysermodelengine/managers/model/model/ModelEngineModel.java
new file mode 100644
index 0000000..edf32ee
--- /dev/null
+++ b/paper/src/main/java/re/imc/geysermodelengine/managers/model/model/ModelEngineModel.java
@@ -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;
+ }
+}
diff --git a/paper/src/main/java/re/imc/geysermodelengine/managers/model/modelhandler/BetterModelHandler.java b/paper/src/main/java/re/imc/geysermodelengine/managers/model/modelhandler/BetterModelHandler.java
new file mode 100644
index 0000000..4fd70a5
--- /dev/null
+++ b/paper/src/main/java/re/imc/geysermodelengine/managers/model/modelhandler/BetterModelHandler.java
@@ -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 entityDataCache = plugin.getModelManager().getEntitiesCache().computeIfAbsent(entityID, k -> new HashMap<>());
+
+ for (Map.Entry 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 modeledEntity = BetterModel.registry(entity);
+//
+// modeledEntity.ifPresent(m -> createModel(modeledEntity.get().entity(), m.));
+ }
+
+ @Override
+ public void loadListeners() {
+ Bukkit.getPluginManager().registerEvents(new BetterModelListener(plugin), plugin);
+ }
+}
diff --git a/paper/src/main/java/re/imc/geysermodelengine/managers/model/modelhandler/ModelEngineHandler.java b/paper/src/main/java/re/imc/geysermodelengine/managers/model/modelhandler/ModelEngineHandler.java
new file mode 100644
index 0000000..e2aea3a
--- /dev/null
+++ b/paper/src/main/java/re/imc/geysermodelengine/managers/model/modelhandler/ModelEngineHandler.java
@@ -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 entityDataCache = plugin.getModelManager().getEntitiesCache().computeIfAbsent(entityID, k -> new HashMap<>());
+
+ for (Map.Entry 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 model = modeledEntity.getModels().values().stream().findFirst();
+ model.ifPresent(m -> createModel(modeledEntity, m));
+ }
+
+ @Override
+ public void loadListeners() {
+ Bukkit.getPluginManager().registerEvents(new ModelEngineListener(plugin), plugin);
+ }
+}
diff --git a/paper/src/main/java/re/imc/geysermodelengine/managers/model/modelhandler/ModelHandler.java b/paper/src/main/java/re/imc/geysermodelengine/managers/model/modelhandler/ModelHandler.java
new file mode 100644
index 0000000..30801af
--- /dev/null
+++ b/paper/src/main/java/re/imc/geysermodelengine/managers/model/modelhandler/ModelHandler.java
@@ -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();
+}
diff --git a/paper/src/main/java/re/imc/geysermodelengine/managers/model/propertyhandler/BetterModelPropertyHandler.java b/paper/src/main/java/re/imc/geysermodelengine/managers/model/propertyhandler/BetterModelPropertyHandler.java
new file mode 100644
index 0000000..993533f
--- /dev/null
+++ b/paper/src/main/java/re/imc/geysermodelengine/managers/model/propertyhandler/BetterModelPropertyHandler.java
@@ -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 players, float lastScale, boolean firstSend) {
+ BetterModelEntityData betterModelEntityData = (BetterModelEntityData) entityData;
+ }
+
+ @Override
+ public void sendColor(EntityData entityData, Collection 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 players, boolean firstSend, String... forceAnims) {
+ BetterModelEntityData model = (BetterModelEntityData) entityData;
+
+ int entity = model.getEntity().getEntityId();
+ Set forceAnimSet = Set.of(forceAnims);
+
+ Map boneUpdates = new HashMap<>();
+ Map animUpdates = new HashMap<>();
+ Set 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 lastPlayed = new HashSet<>(model.getEntityTask().getLastPlayedAnim().asMap().keySet());
+
+ for (Map.Entry 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 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 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 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);
+ }
+}
diff --git a/paper/src/main/java/re/imc/geysermodelengine/managers/model/propertyhandler/ModelEnginePropertyHandler.java b/paper/src/main/java/re/imc/geysermodelengine/managers/model/propertyhandler/ModelEnginePropertyHandler.java
new file mode 100644
index 0000000..6a98566
--- /dev/null
+++ b/paper/src/main/java/re/imc/geysermodelengine/managers/model/propertyhandler/ModelEnginePropertyHandler.java
@@ -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 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 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 players, boolean firstSend, String... forceAnims) {
+ ModelEngineEntityData model = (ModelEngineEntityData) entityData;
+
+ int entity = model.getEntity().getEntityId();
+ Set forceAnimSet = Set.of(forceAnims);
+
+ Map boneUpdates = new LinkedHashMap<>();
+ Map animUpdates = new HashMap<>();
+ Set anims = new HashSet<>();
+
+ model.getActiveModel().getBlueprint().getBones().forEach((s, bone) -> processBone(model, bone, boneUpdates));
+
+ AnimationHandler handler = model.getActiveModel().getAnimationHandler();
+ Set 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 lastPlayed = new HashSet<>(model.getEntityTask().getLastPlayedAnim().asMap().keySet());
+
+ for (Map.Entry 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 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 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());
+ }
+}
diff --git a/paper/src/main/java/re/imc/geysermodelengine/managers/model/propertyhandler/PropertyHandler.java b/paper/src/main/java/re/imc/geysermodelengine/managers/model/propertyhandler/PropertyHandler.java
new file mode 100644
index 0000000..793afcf
--- /dev/null
+++ b/paper/src/main/java/re/imc/geysermodelengine/managers/model/propertyhandler/PropertyHandler.java
@@ -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 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 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 players, boolean firstSend, String... forceAnims);
+}
diff --git a/paper/src/main/java/re/imc/geysermodelengine/managers/model/taskshandler/BetterModelTaskHandler.java b/paper/src/main/java/re/imc/geysermodelengine/managers/model/taskshandler/BetterModelTaskHandler.java
new file mode 100644
index 0000000..e64dd36
--- /dev/null
+++ b/paper/src/main/java/re/imc/geysermodelengine/managers/model/taskshandler/BetterModelTaskHandler.java
@@ -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 lastIntSet = new ConcurrentHashMap<>();
+ private final Cache 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 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 getLastIntSet() {
+ return lastIntSet;
+ }
+
+ public Cache getLastPlayedAnim() {
+ return lastPlayedAnim;
+ }
+
+ public ScheduledFuture getScheduledFuture() {
+ return scheduledFuture;
+ }
+}
diff --git a/paper/src/main/java/re/imc/geysermodelengine/managers/model/taskshandler/ModelEngineTaskHandler.java b/paper/src/main/java/re/imc/geysermodelengine/managers/model/taskshandler/ModelEngineTaskHandler.java
new file mode 100644
index 0000000..7488a8c
--- /dev/null
+++ b/paper/src/main/java/re/imc/geysermodelengine/managers/model/taskshandler/ModelEngineTaskHandler.java
@@ -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 lastIntSet = new ConcurrentHashMap<>();
+ private final Cache 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 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 getLastIntSet() {
+ return lastIntSet;
+ }
+
+ public Cache getLastPlayedAnim() {
+ return lastPlayedAnim;
+ }
+
+ public ScheduledFuture getScheduledFuture() {
+ return scheduledFuture;
+ }
+}
diff --git a/paper/src/main/java/re/imc/geysermodelengine/managers/model/taskshandler/TaskHandler.java b/paper/src/main/java/re/imc/geysermodelengine/managers/model/taskshandler/TaskHandler.java
new file mode 100644
index 0000000..f2af8a2
--- /dev/null
+++ b/paper/src/main/java/re/imc/geysermodelengine/managers/model/taskshandler/TaskHandler.java
@@ -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();
+}
diff --git a/src/main/java/re/imc/geysermodelengine/packet/entity/PacketEntity.java b/paper/src/main/java/re/imc/geysermodelengine/packet/entity/PacketEntity.java
similarity index 93%
rename from src/main/java/re/imc/geysermodelengine/packet/entity/PacketEntity.java
rename to paper/src/main/java/re/imc/geysermodelengine/packet/entity/PacketEntity.java
index f3cf64e..a151c09 100644
--- a/src/main/java/re/imc/geysermodelengine/packet/entity/PacketEntity.java
+++ b/paper/src/main/java/re/imc/geysermodelengine/packet/entity/PacketEntity.java
@@ -3,9 +3,7 @@ package re.imc.geysermodelengine.packet.entity;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
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.EntityTypes;
import com.github.retrooper.packetevents.protocol.teleport.RelativeFlag;
import com.github.retrooper.packetevents.util.Vector3d;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
@@ -16,7 +14,6 @@ import lombok.Setter;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
-import re.imc.geysermodelengine.GeyserModelEngine;
import java.util.Collection;
import java.util.Set;
@@ -50,7 +47,7 @@ public class PacketEntity {
}
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();
if (sent) sendLocationPacket(viewers);
@@ -58,7 +55,6 @@ public class PacketEntity {
return true;
}
-
public void remove() {
removed = true;
sendEntityDestroyPacket(viewers);
@@ -78,7 +74,6 @@ public class PacketEntity {
}
public void sendLocationPacket(Collection players) {
-
PacketWrapper> packet;
EntityPositionData data = new EntityPositionData(SpigotConversionUtil.fromBukkitLocation(location).getPosition(), Vector3d.zero(), location.getYaw(), location.getPitch());
diff --git a/src/main/java/re/imc/geysermodelengine/runnables/BedrockMountControlRunnable.java b/paper/src/main/java/re/imc/geysermodelengine/runnables/BedrockMountControlRunnable.java
similarity index 80%
rename from src/main/java/re/imc/geysermodelengine/runnables/BedrockMountControlRunnable.java
rename to paper/src/main/java/re/imc/geysermodelengine/runnables/BedrockMountControlRunnable.java
index 7693197..3f55d56 100644
--- a/src/main/java/re/imc/geysermodelengine/runnables/BedrockMountControlRunnable.java
+++ b/paper/src/main/java/re/imc/geysermodelengine/runnables/BedrockMountControlRunnable.java
@@ -5,16 +5,14 @@ import com.ticxo.modelengine.api.entity.BukkitEntity;
import com.ticxo.modelengine.api.model.ActiveModel;
import com.ticxo.modelengine.api.model.bone.type.Mount;
import com.ticxo.modelengine.api.mount.controller.MountController;
-import io.papermc.paper.threadedregions.scheduler.ScheduledTask;
import org.apache.commons.lang3.tuple.Pair;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
-import org.geysermc.floodgate.api.FloodgateApi;
import re.imc.geysermodelengine.GeyserModelEngine;
-import java.util.function.Consumer;
+import java.util.UUID;
-public class BedrockMountControlRunnable implements Consumer {
+public class BedrockMountControlRunnable implements Runnable {
private final GeyserModelEngine plugin;
@@ -23,13 +21,12 @@ public class BedrockMountControlRunnable implements Consumer {
}
@Override
- public void accept(ScheduledTask scheduledTask) {
- for (Player player : Bukkit.getOnlinePlayers()) {
- if (!FloodgateApi.getInstance().isFloodgatePlayer(player.getUniqueId())) continue;
+ public void run() {
+ for (UUID playerUUID : plugin.getModelManager().getPlayerJoinedCache()) {
+ Player player = Bukkit.getPlayer(playerUUID);
float pitch = player.getLocation().getPitch();
Pair seat = plugin.getModelManager().getDriversCache().get(player.getUniqueId());
-
if (seat == null) continue;
if (pitch < -30) {
@@ -45,13 +42,10 @@ public class BedrockMountControlRunnable implements Consumer {
if (pitch > 80) {
if (seat.getKey().getModeledEntity().getBase() instanceof BukkitEntity bukkitEntity) {
- if (bukkitEntity.getOriginal().isOnGround()) {
- return;
- }
+ if (bukkitEntity.getOriginal().isOnGround()) continue;
}
MountController controller = ModelEngineAPI.getMountPairManager().getController(player.getUniqueId());
-
if (controller != null) {
MountController.MountInput input = controller.getInput();
if (input != null) {
diff --git a/paper/src/main/java/re/imc/geysermodelengine/runnables/UpdateTaskRunnable.java b/paper/src/main/java/re/imc/geysermodelengine/runnables/UpdateTaskRunnable.java
new file mode 100644
index 0000000..a61e1dc
--- /dev/null
+++ b/paper/src/main/java/re/imc/geysermodelengine/runnables/UpdateTaskRunnable.java
@@ -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> entitiesCache = plugin.getModelManager().getEntitiesCache();
+ if (entitiesCache.isEmpty()) return;
+
+ try {
+ for (Map 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);
+ }
+ }
+}
diff --git a/paper/src/main/java/re/imc/geysermodelengine/util/BedrockUtils.java b/paper/src/main/java/re/imc/geysermodelengine/util/BedrockUtils.java
new file mode 100644
index 0000000..5ba3e2d
--- /dev/null
+++ b/paper/src/main/java/re/imc/geysermodelengine/util/BedrockUtils.java
@@ -0,0 +1,18 @@
+package re.imc.geysermodelengine.util;
+
+import org.bukkit.entity.Player;
+import org.geysermc.floodgate.api.FloodgateApi;
+
+public class BedrockUtils {
+
+ private static FloodgateApi FLOODGATE_API;
+
+ public static boolean isBedrockPlayer(Player player) {
+ if (FLOODGATE_API != null) return FLOODGATE_API.isFloodgatePlayer(player.getUniqueId());
+ return player.getClientBrandName().contains("Geyser");
+ }
+
+ public static FloodgateApi getFloodgateApi() {
+ return FLOODGATE_API;
+ }
+}
diff --git a/src/main/java/re/imc/geysermodelengine/util/BooleanPacker.java b/paper/src/main/java/re/imc/geysermodelengine/util/BooleanPacker.java
similarity index 81%
rename from src/main/java/re/imc/geysermodelengine/util/BooleanPacker.java
rename to paper/src/main/java/re/imc/geysermodelengine/util/BooleanPacker.java
index 5692a9d..ee2fe85 100644
--- a/src/main/java/re/imc/geysermodelengine/util/BooleanPacker.java
+++ b/paper/src/main/java/re/imc/geysermodelengine/util/BooleanPacker.java
@@ -7,9 +7,9 @@ import java.util.Map;
public class BooleanPacker {
- private final int MAX_BOOLEANS = 24;
+ private static final int MAX_BOOLEANS = 24;
- public int booleansToInt(List booleans) {
+ public static int booleansToInt(List booleans) {
int result = 0;
int i = 1;
@@ -23,7 +23,7 @@ public class BooleanPacker {
return result;
}
- public int mapBooleansToInt(Map booleanMap) {
+ public static int mapBooleansToInt(Map booleanMap) {
int result = 0;
int i = 1;
@@ -39,7 +39,7 @@ public class BooleanPacker {
return result;
}
- public List booleansToInts(List booleans) {
+ public static List booleansToInts(List booleans) {
List results = new ArrayList<>();
int result = 0;
int i = 1;
@@ -62,7 +62,7 @@ public class BooleanPacker {
return results;
}
- public List mapBooleansToInts(Map booleanMap) {
+ public static List mapBooleansToInts(Map booleanMap) {
List keys = new ArrayList<>(booleanMap.keySet());
List booleans = new ArrayList<>();
diff --git a/src/main/java/re/imc/geysermodelengine/util/ColourUtils.java b/paper/src/main/java/re/imc/geysermodelengine/util/ColourUtils.java
similarity index 100%
rename from src/main/java/re/imc/geysermodelengine/util/ColourUtils.java
rename to paper/src/main/java/re/imc/geysermodelengine/util/ColourUtils.java
diff --git a/src/main/resources/Lang/messages.yml b/paper/src/main/resources/Lang/messages.yml
similarity index 100%
rename from src/main/resources/Lang/messages.yml
rename to paper/src/main/resources/Lang/messages.yml
diff --git a/paper/src/main/resources/config.yml b/paper/src/main/resources/config.yml
new file mode 100644
index 0000000..32d3d21
--- /dev/null
+++ b/paper/src/main/resources/config.yml
@@ -0,0 +1,13 @@
+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
\ No newline at end of file
diff --git a/src/main/resources/paper-plugin.yml b/paper/src/main/resources/paper-plugin.yml
similarity index 75%
rename from src/main/resources/paper-plugin.yml
rename to paper/src/main/resources/paper-plugin.yml
index 015a6c6..61af711 100644
--- a/src/main/resources/paper-plugin.yml
+++ b/paper/src/main/resources/paper-plugin.yml
@@ -6,6 +6,7 @@ api-version: '1.21'
authors:
- zimzaza4
- willem.dev
+ - xSquishyLiam
load: STARTUP
@@ -15,7 +16,9 @@ dependencies:
required: true
packetevents:
required: true
- ModelEngine:
- required: true
floodgate:
- required: true
\ No newline at end of file
+ required: false
+ ModelEngine:
+ required: false
+ BetterModel:
+ required: false
\ No newline at end of file
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 8e9efd7..dd9810f 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -1,2 +1,5 @@
rootProject.name = "GeyserModelEngine"
+include("paper")
+include("geyser")
+
diff --git a/src/main/java/re/imc/geysermodelengine/commands/geysermodelenginecommands/GeyserModelEngineReloadCommand.java b/src/main/java/re/imc/geysermodelengine/commands/geysermodelenginecommands/GeyserModelEngineReloadCommand.java
deleted file mode 100644
index 2e4bfa7..0000000
--- a/src/main/java/re/imc/geysermodelengine/commands/geysermodelenginecommands/GeyserModelEngineReloadCommand.java
+++ /dev/null
@@ -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")));
- });
- }
-}
\ No newline at end of file
diff --git a/src/main/java/re/imc/geysermodelengine/listener/ModelListener.java b/src/main/java/re/imc/geysermodelengine/listener/ModelListener.java
deleted file mode 100644
index bf15a2b..0000000
--- a/src/main/java/re/imc/geysermodelengine/listener/ModelListener.java
+++ /dev/null
@@ -1,77 +0,0 @@
-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.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.geysermc.floodgate.api.FloodgateApi;
-import re.imc.geysermodelengine.GeyserModelEngine;
-import re.imc.geysermodelengine.managers.model.data.ModelEntityData;
-
-import java.util.Map;
-
-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 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));
- }
-
- /*
- / xSquishyLiam:
- / Delay is required due to when a player joins the server the packet for mob spawning is instant so the client resyncs itself
- */
- @EventHandler
- public void onPlayerJoin(PlayerJoinEvent event) {
- Player player = event.getPlayer();
- if (!FloodgateApi.getInstance().isFloodgatePlayer(player.getUniqueId())) return;
- Bukkit.getGlobalRegionScheduler().runDelayed(plugin, scheduledTask -> plugin.getModelManager().getPlayerJoinedCache().add(player.getUniqueId()), 10);
- }
-
- @EventHandler
- public void onPlayerQuit(PlayerQuitEvent event) {
- Player player = event.getPlayer();
- if (!FloodgateApi.getInstance().isFloodgatePlayer(player.getUniqueId())) return;
- plugin.getModelManager().getPlayerJoinedCache().remove(player.getUniqueId());
- }
-}
diff --git a/src/main/java/re/imc/geysermodelengine/managers/model/EntityTaskManager.java b/src/main/java/re/imc/geysermodelengine/managers/model/EntityTaskManager.java
deleted file mode 100644
index 26d5eed..0000000
--- a/src/main/java/re/imc/geysermodelengine/managers/model/EntityTaskManager.java
+++ /dev/null
@@ -1,152 +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 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 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 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);
- }
- }
- }
- }
-
- 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;
- }
-
- 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;
- }
-}
diff --git a/src/main/java/re/imc/geysermodelengine/managers/model/ModelManager.java b/src/main/java/re/imc/geysermodelengine/managers/model/ModelManager.java
deleted file mode 100644
index 37eaa14..0000000
--- a/src/main/java/re/imc/geysermodelengine/managers/model/ModelManager.java
+++ /dev/null
@@ -1,76 +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 playerJoinedCache = new HashSet<>();
-
- private final ConcurrentHashMap> entitiesCache = new ConcurrentHashMap<>();
- private final ConcurrentHashMap modelEntitiesCache = new ConcurrentHashMap<>();
-
- private final ConcurrentHashMap> 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 map = entitiesCache.computeIfAbsent(id, k -> new HashMap<>());
-
- for (Map.Entry 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 model = modeledEntity.getModels().values().stream().findFirst();
- model.ifPresent(m -> create(modeledEntity, m));
- }
-
- public void removeEntities() {
- for (Map entities : entitiesCache.values()) {
- entities.forEach((model, modelEntity) -> modelEntity.getEntity().remove());
- }
- }
-
- public HashSet getPlayerJoinedCache() {
- return playerJoinedCache;
- }
-
- public ConcurrentHashMap> getEntitiesCache() {
- return entitiesCache;
- }
-
- public ConcurrentHashMap getModelEntitiesCache() {
- return modelEntitiesCache;
- }
-
- public ConcurrentHashMap> getDriversCache() {
- return driversCache;
- }
-}
diff --git a/src/main/java/re/imc/geysermodelengine/runnables/EntityTaskRunnable.java b/src/main/java/re/imc/geysermodelengine/runnables/EntityTaskRunnable.java
deleted file mode 100644
index e9121a8..0000000
--- a/src/main/java/re/imc/geysermodelengine/runnables/EntityTaskRunnable.java
+++ /dev/null
@@ -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 lastIntSet = new ConcurrentHashMap<>();
- private final Cache 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 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);
- }, 500, TimeUnit.MILLISECONDS);
- }, delay * 50L, TimeUnit.MILLISECONDS);
- }
-
- public void updateEntityProperties(ModelEntityData model, Collection players, boolean firstSend, String... forceAnims) {
- int entity = model.getEntity().getEntityId();
- Set forceAnimSet = Set.of(forceAnims);
-
- Map boneUpdates = new HashMap<>();
- Map animUpdates = new HashMap<>();
- Set anims = new HashSet<>();
-
- model.getActiveModel().getBlueprint().getBones().forEach((s, bone) -> processBone(model, bone, boneUpdates));
-
- AnimationHandler handler = model.getActiveModel().getAnimationHandler();
- Set 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 lastPlayed = new HashSet<>(lastPlayedAnim.asMap().keySet());
-
- for (Map.Entry 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 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 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 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 getLastIntSet() {
- return lastIntSet;
- }
-
- public Cache getLastPlayedAnim() {
- return lastPlayedAnim;
- }
-
- public ScheduledFuture getScheduledFuture() {
- return scheduledFuture;
- }
-}
diff --git a/src/main/java/re/imc/geysermodelengine/runnables/UpdateTaskRunnable.java b/src/main/java/re/imc/geysermodelengine/runnables/UpdateTaskRunnable.java
deleted file mode 100644
index 015682b..0000000
--- a/src/main/java/re/imc/geysermodelengine/runnables/UpdateTaskRunnable.java
+++ /dev/null
@@ -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 {
-
- private final GeyserModelEngine plugin;
-
- public UpdateTaskRunnable(GeyserModelEngine plugin) {
- this.plugin = plugin;
- }
-
- @Override
- public void accept(ScheduledTask scheduledTask) {
- try {
- for (Map models : plugin.getModelManager().getEntitiesCache().values()) {
- models.values().forEach(model -> model.getEntityTask().updateEntityProperties(model, model.getViewers(), false));
- }
- } catch (Throwable err) {
- throw new RuntimeException(err);
- }
- }
-}
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
deleted file mode 100644
index 53c5312..0000000
--- a/src/main/resources/config.yml
+++ /dev/null
@@ -1,11 +0,0 @@
-bstats: true
-
-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
\ No newline at end of file