diff --git a/src/main/java/re/imc/geysermodelenginepackgenerator/ExtensionMain.java b/src/main/java/re/imc/geysermodelenginepackgenerator/ExtensionMain.java index 591d0e5..ca1a43e 100644 --- a/src/main/java/re/imc/geysermodelenginepackgenerator/ExtensionMain.java +++ b/src/main/java/re/imc/geysermodelenginepackgenerator/ExtensionMain.java @@ -35,28 +35,14 @@ public class ExtensionMain implements Extension { generatedPackZip = dataFolder().resolve("generated_pack.zip"); try (ZipOutputStream zipOutputStream = new ZipOutputStream(Files.newOutputStream(generatedPackZip))) { - // 压缩文件夹 ZipUtil.compressFolder(generatedPack, null, zipOutputStream); } catch (IOException e) { e.printStackTrace(); } - for (String entity : GeneratorMain.entityMap.keySet()) { - String id = "modelengine:" + entity; - GeyserUtils.addCustomEntity(id); - - Geometry geometry = GeneratorMain.geometryMap.get(entity); - if (geometry == null) { - continue; - } - geometry.getBones().forEach(bone -> { - GeyserUtils.addProperty(id, entity + ":" + bone, Boolean.class); - }); - - GeyserUtils.addProperty(id, "modelengine:anim", Integer.class); - - GeyserUtils.registerProperties(id); + for (Entity entity : GeneratorMain.entityMap.values()) { + entity.register(); } } diff --git a/src/main/java/re/imc/geysermodelenginepackgenerator/GeneratorMain.java b/src/main/java/re/imc/geysermodelenginepackgenerator/GeneratorMain.java index 3b8f212..718723d 100644 --- a/src/main/java/re/imc/geysermodelenginepackgenerator/GeneratorMain.java +++ b/src/main/java/re/imc/geysermodelenginepackgenerator/GeneratorMain.java @@ -2,7 +2,6 @@ package re.imc.geysermodelenginepackgenerator; import com.google.gson.Gson; import com.google.gson.GsonBuilder; -import com.google.gson.JsonObject; import com.google.gson.JsonParser; import re.imc.geysermodelenginepackgenerator.generator.*; @@ -16,7 +15,6 @@ import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.HashMap; import java.util.Map; -import java.util.UUID; public class GeneratorMain { public static final Map entityMap = new HashMap<>(); @@ -56,9 +54,11 @@ public class GeneratorMain { if (isAnimationFile(json)) { Animation animation = new Animation(); animation.setPath(currentPath); - animation.load(json); animation.setModelId(modelId); + + animation.load(json); animationMap.put(modelId, animation); + entity.setAnimation(animation); } if (isGeometryFile(json)) { @@ -67,6 +67,7 @@ public class GeneratorMain { geometry.setPath(currentPath); geometry.setModelId(modelId); geometryMap.put(modelId, geometry); + entity.setGeometry(geometry); canAdd = true; } } catch (IOException ex) { @@ -78,10 +79,10 @@ public class GeneratorMain { File config = new File(folder, "config.properties"); try { if (config.exists()) { - entity.getProperties().load(new FileReader(config)); + entity.getConfig().load(new FileReader(config)); } else { - entity.getProperties().setProperty("enable-part-visibility", "false"); - entity.getProperties().store(new FileWriter(config), "For some reasons, the part visibility render controller may cause client crash"); + entity.getConfig().setProperty("enable-part-visibility", "false"); + entity.getConfig().store(new FileWriter(config), "For some reasons, the part visibility render controller may cause client crash"); } } catch (IOException ex) { ex.printStackTrace(); @@ -132,19 +133,24 @@ public class GeneratorMain { renderControllersFolder.mkdirs(); for (Map.Entry entry : animationMap.entrySet()) { - entry.getValue().modify(); Geometry geo = geometryMap.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"); + path.toFile().getParentFile().mkdirs(); if (path.toFile().exists()) { continue; } + + AnimationController controller = new AnimationController(); + controller.load(entry.getValue()); try { - Files.writeString(path, GSON.toJson(entry.getValue().getJson()), StandardCharsets.UTF_8); + Files.writeString(path, entry.getValue().getJson().toString(), StandardCharsets.UTF_8); + Files.writeString(pathController, controller.getJson().toString(), StandardCharsets.UTF_8); } catch (IOException e) { e.printStackTrace(); } @@ -159,7 +165,7 @@ public class GeneratorMain { continue; } try { - Files.writeString(path, GSON.toJson(entry.getValue().getJson()), StandardCharsets.UTF_8); + Files.writeString(path, entry.getValue().getJson().toString(), StandardCharsets.UTF_8); } catch (IOException e) { e.printStackTrace(); } @@ -181,7 +187,7 @@ public class GeneratorMain { for (Map.Entry entry : entityMap.entrySet()) { Entity entity = entry.getValue(); - entity.getProperties().setProperty("render_controller", "controller.render." + entry.getKey()); + entity.getConfig().setProperty("render_controller", "controller.render." + entry.getKey()); entity.modify(); Path entityPath = entityFolder.toPath().resolve(entity.getPath() + entry.getKey() + ".entity.json"); @@ -190,7 +196,7 @@ public class GeneratorMain { continue; } try { - Files.writeString(entityPath, entity.getJson(), StandardCharsets.UTF_8); + Files.writeString(entityPath, entity.getJson().toString(), StandardCharsets.UTF_8); } catch (IOException e) { e.printStackTrace(); } @@ -200,7 +206,7 @@ public class GeneratorMain { String id = entity.getModelId(); if (!geometryMap.containsKey(id)) continue; RenderController controller = new RenderController(id, geometryMap.get(id).getBones()); - + entity.setRenderController(controller); Path renderPath = new File(renderControllersFolder, "controller.render." + id + ".json").toPath(); if (renderPath.toFile().exists()) { continue; @@ -212,6 +218,7 @@ public class GeneratorMain { } } + /* File controller = new File(animationControllersFolder, "modelengine.animation_controller.json"); if (!controller.exists()) { try { @@ -221,6 +228,7 @@ public class GeneratorMain { } } + */ } private static boolean isGeometryFile(String json) { diff --git a/src/main/java/re/imc/geysermodelenginepackgenerator/generator/Animation.java b/src/main/java/re/imc/geysermodelenginepackgenerator/generator/Animation.java index 91f0246..367c1c9 100644 --- a/src/main/java/re/imc/geysermodelenginepackgenerator/generator/Animation.java +++ b/src/main/java/re/imc/geysermodelenginepackgenerator/generator/Animation.java @@ -13,8 +13,10 @@ import re.imc.geysermodelenginepackgenerator.GeneratorMain; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; +import java.util.HashSet; import java.util.Iterator; import java.util.Map; +import java.util.Set; @Getter @Setter @@ -33,23 +35,44 @@ public class Animation { String modelId; JsonObject json; + @Getter + Set animationIds = new HashSet<>(); String path; - public void load(String json) { - this.json = new JsonParser().parse(json).getAsJsonObject(); - } - - public void modify() { + public void load(String string) { + this.json = new JsonParser().parse(string).getAsJsonObject(); JsonObject newAnimations = new JsonObject(); for (Map.Entry element : json.get("animations").getAsJsonObject().entrySet()) { - if (element.getKey().equals("spawn")) { - GeneratorMain.entityMap - .get(modelId).setHasSpawnAnimation(true); - } - if (element.getKey().equals("walk")) { - GeneratorMain.entityMap - .get(modelId).setHasWalkAnimation(true); + animationIds.add(element.getKey()); + JsonObject animation = element.getValue().getAsJsonObject(); + if (animation.has("loop")) { + if (animation.get("loop").getAsJsonPrimitive().isString()) { + if (animation.get("loop").getAsString().equals("hold_on_last_frame")) { + 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; + } + 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(); + } + } + } + if (end != null && end.get("lerp_mode").getAsString().equals("catmullrom")) { + end.addProperty("lerp_mode", "linear"); + } + } + } + } + } } newAnimations.add("animation." + modelId + "." + element.getKey(), element.getValue()); } diff --git a/src/main/java/re/imc/geysermodelenginepackgenerator/generator/AnimationController.java b/src/main/java/re/imc/geysermodelenginepackgenerator/generator/AnimationController.java index 0e7b71d..68f2a71 100644 --- a/src/main/java/re/imc/geysermodelenginepackgenerator/generator/AnimationController.java +++ b/src/main/java/re/imc/geysermodelenginepackgenerator/generator/AnimationController.java @@ -1,7 +1,33 @@ package re.imc.geysermodelenginepackgenerator.generator; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import lombok.Getter; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + public class AnimationController { + 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 static final String TEMPLATE = """ { @@ -73,6 +99,31 @@ public class AnimationController { } } }"""; + + @Getter + JsonObject json; + + public void load(Animation animation) { + 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.animationIds); + int i = 0; + + Collections.sort(sorted); + for (String id : sorted) { + + int n = (int) Math.pow(2, (i % 24)); + JsonObject controller = new JsonParser().parse(CONTROLLER_TEMPLATE.replace("%anim%", id).replace("%query%", "math.mod(math.floor(query.property('modelengine:anim" + i / 24 + "') / " + n + "), 2)")).getAsJsonObject(); + animationControllers.add("controller.animation." + animation.modelId + "." + id, controller); + i++; + } + } + /* public static final String TEMPLATE = """ diff --git a/src/main/java/re/imc/geysermodelenginepackgenerator/generator/Entity.java b/src/main/java/re/imc/geysermodelenginepackgenerator/generator/Entity.java index 72fc3bf..e19b044 100644 --- a/src/main/java/re/imc/geysermodelenginepackgenerator/generator/Entity.java +++ b/src/main/java/re/imc/geysermodelenginepackgenerator/generator/Entity.java @@ -1,11 +1,18 @@ package re.imc.geysermodelenginepackgenerator.generator; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import me.zimzaza4.geyserutils.geyser.GeyserUtils; +import re.imc.geysermodelenginepackgenerator.GeneratorMain; +import java.util.HashSet; import java.util.Properties; +import java.util.Set; @Getter @Setter @@ -28,16 +35,10 @@ public class Entity { "default": "%geometry%" }, "animations": { - - "idle": "animation.%entity_id%.idle", - "spawn": "animation.%entity_id%.%spawn%", - "walk": "animation.%entity_id%.%walk%", - "look_at_target": "%look_at_target%", - "modelengine_controller": "controller.animation.modelengine" + "look_at_target": "%look_at_target%" }, "scripts": { "animate": [ - "modelengine_controller", "look_at_target" ] }, @@ -51,13 +52,25 @@ public class Entity { String modelId; - String json; + JsonObject json; boolean hasHeadAnimation = false; - boolean hasWalkAnimation = false; - boolean hasSpawnAnimation = false; + @Setter + @Getter + Animation animation; + + @Setter + @Getter + Geometry geometry; + + @Setter + @Getter + RenderController renderController; + String path; - Properties properties = new Properties(); + Properties config = new Properties(); + + public Entity(String modelId) { this.modelId = modelId; @@ -65,27 +78,43 @@ public class Entity { public void modify() { - String walk; - String spawn; - walk = spawn = "idle"; - if (hasWalkAnimation) { - walk = "walk"; - } - if (hasSpawnAnimation) { - spawn = "spawn"; - } - json = TEMPLATE.replace("%entity_id%", modelId) + json = new JsonParser().parse(TEMPLATE.replace("%entity_id%", modelId) .replace("%geometry%", "geometry.modelengine_" + modelId) .replace("%texture%", "textures/entity/" + path + modelId) .replace("%look_at_target%", "animation." + modelId + ".look_at_target") - .replace("%walk%", walk) - .replace("%spawn%", spawn) - .replace("%material%", properties.getProperty("material", "entity_alphatest_change_color")) - .replace("%render_controller%", properties.getProperty("render_controller", "controller.render.default")); + .replace("%material%", config.getProperty("material", "entity_alphatest_change_color")) + .replace("%render_controller%", config.getProperty("render_controller", "controller.render.default"))).getAsJsonObject(); + JsonObject description = json.get("minecraft:client_entity").getAsJsonObject().get("description").getAsJsonObject(); + JsonObject jsonAnimations = description.get("animations").getAsJsonObject(); + JsonArray animate = description.get("scripts").getAsJsonObject().get("animate").getAsJsonArray(); + if (animation != null) { + for (String animation : animation.animationIds) { + 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 id = "modelengine:" + modelId; + GeyserUtils.addCustomEntity(id); + if (geometry == null) { + return; + } + for (int i = 0; i < Math.ceil(geometry.getBones().size() / 24f); i++) { + GeyserUtils.addProperty(id, "modelengine:bone" + i, Integer.class); + } + if (animation != null) { + for (int i = 0; i < Math.ceil(animation.animationIds.size() / 24f); i++) { + GeyserUtils.addProperty(id, "modelengine:anim" + i, Integer.class); + } + } + GeyserUtils.registerProperties(id); + } } diff --git a/src/main/java/re/imc/geysermodelenginepackgenerator/generator/Geometry.java b/src/main/java/re/imc/geysermodelenginepackgenerator/generator/Geometry.java index aac919a..b00bcf3 100644 --- a/src/main/java/re/imc/geysermodelenginepackgenerator/generator/Geometry.java +++ b/src/main/java/re/imc/geysermodelenginepackgenerator/generator/Geometry.java @@ -6,10 +6,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; +import java.util.*; @Getter @Setter @@ -17,9 +14,10 @@ import java.util.Locale; @NoArgsConstructor public class Geometry { + String modelId; JsonObject json; - List bones = new ArrayList<>(); + Set bones = new HashSet<>(); String path; public void load(String json) { @@ -44,11 +42,12 @@ public class Geometry { String name = element.getAsJsonObject().get("name").getAsString().toLowerCase(Locale.ROOT); element.getAsJsonObject().remove("name"); + element.getAsJsonObject().addProperty("name", name); if (name.equals("hitbox") || + name.equals("shadow") || name.equals("mount") || - name.startsWith("p_") || name.startsWith("b_") || name.startsWith("ob_")) { iterator.remove(); diff --git a/src/main/java/re/imc/geysermodelenginepackgenerator/generator/RenderController.java b/src/main/java/re/imc/geysermodelenginepackgenerator/generator/RenderController.java index 9f0462f..0f7ecab 100644 --- a/src/main/java/re/imc/geysermodelenginepackgenerator/generator/RenderController.java +++ b/src/main/java/re/imc/geysermodelenginepackgenerator/generator/RenderController.java @@ -4,14 +4,15 @@ import com.google.gson.JsonArray; import com.google.gson.JsonObject; import re.imc.geysermodelenginepackgenerator.GeneratorMain; -import java.util.List; +import java.util.*; public class RenderController { + public static final Set NEED_REMOVE_WHEN_SORT = Set.of("pbody_", "plarm_", "prarm_", "plleg_", "prleg_", "phead_", "p_"); String modelId; - List bones; + Set bones; - public RenderController(String modelId, List bones) { + public RenderController(String modelId, Set bones) { this.modelId = modelId; this.bones = bones; } @@ -40,21 +41,37 @@ public class RenderController { controller.add("textures", textures); Entity entity = GeneratorMain.entityMap .get(modelId); - boolean enable = Boolean.parseBoolean(entity.getProperties().getProperty("enable-part-visibility", "false")); + // boolean enable = Boolean.parseBoolean(entity.getConfig().getProperty("enable-part-visibility", "true")); - if (enable) { - JsonArray partVisibility = new JsonArray(); - JsonObject visibilityDefault = new JsonObject(); - visibilityDefault.addProperty("*", true); - partVisibility.add(visibilityDefault); - - for (String bone : bones) { - JsonObject visibilityItem = new JsonObject(); - visibilityItem.addProperty(bone, "query.property('" + modelId + ":" + bone + "')"); - partVisibility.add(visibilityItem); + // if (enable) { + JsonArray partVisibility = new JsonArray(); + JsonObject visibilityDefault = new JsonObject(); + visibilityDefault.addProperty("*", true); + partVisibility.add(visibilityDefault); + int i = 0; + List sorted = new ArrayList<>(bones); + 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, ""); } - controller.add("part_visibility", partVisibility); + iterator.set(s); + originalId.put(s, o); } + Collections.sort(sorted); + for (String bone : sorted) { + bone = originalId.get(bone); + JsonObject visibilityItem = new JsonObject(); + int n = (int) Math.pow(2, (i % 24)); + visibilityItem.addProperty(bone, "math.mod(math.floor(query.property('modelengine:bone" + i / 24 + "') / " + n + "), 2) == 1"); + partVisibility.add(visibilityItem); + i++; + } + controller.add("part_visibility", partVisibility); + //} return root.toString(); } diff --git a/src/main/java/re/imc/geysermodelenginepackgenerator/util/BooleanPacker.java b/src/main/java/re/imc/geysermodelenginepackgenerator/util/BooleanPacker.java new file mode 100644 index 0000000..221a120 --- /dev/null +++ b/src/main/java/re/imc/geysermodelenginepackgenerator/util/BooleanPacker.java @@ -0,0 +1,69 @@ +package re.imc.geysermodelenginepackgenerator.util; + +import java.util.*; +import java.util.concurrent.ThreadLocalRandom; + +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); + } + + + +}