From ba12039d3770b6dcb07a0574dfeebd34d67bdf67 Mon Sep 17 00:00:00 2001
From: miraculixx
Date: Tue, 6 Jan 2026 16:45:11 +0100
Subject: [PATCH 01/34] Update supported entities list
---
README.md | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/README.md b/README.md
index 124226d..c6c9814 100644
--- a/README.md
+++ b/README.md
@@ -32,8 +32,10 @@ bluemap-configs. You will need to purge/re-render the map to see the entities ap
- Glow Squid
- Dolphin
- Guardian
+ - Axolotl
- Fox
- Iron Golem
+ - Snow Golem
- Llama
- Trader Llama
- Zombie
@@ -51,3 +53,10 @@ bluemap-configs. You will need to purge/re-render the map to see the entities ap
- Zombie Horse
- Vex
- Allay
+- Copper Golem
+- Camel
+ - Camel Husk
+- Bat
+- Frog
+ - Tadpole
+- Armadillo
From 9da6740be19f0f4d5528d10d96cf0c39760e9ed8 Mon Sep 17 00:00:00 2001
From: miraculixx
Date: Mon, 10 Aug 2026 21:56:02 +0200
Subject: [PATCH 02/34] Support ghasts (old commit)
---
.../bluecolored/bluemap/entities/Addon.java | 5 +
.../bluemap/entities/entity/HappyGhast.java | 35 ++++
.../entities/renderer/GhastRenderer.java | 81 ++++++++
.../assets/minecraft/entitystates/ghast.json | 5 +
.../minecraft/entitystates/happy_ghast.json | 5 +
.../minecraft/models/entity/ghast/ghast.json | 163 ++++++++++++++++
.../entity/ghast/happy_ghast_adult.json | 178 ++++++++++++++++++
.../models/entity/ghast/happy_ghast_baby.json | 6 +
8 files changed, 478 insertions(+)
create mode 100644 src/main/java/de/bluecolored/bluemap/entities/entity/HappyGhast.java
create mode 100644 src/main/java/de/bluecolored/bluemap/entities/renderer/GhastRenderer.java
create mode 100644 src/main/resources/assets/minecraft/entitystates/ghast.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/happy_ghast.json
create mode 100644 src/main/resources/assets/minecraft/models/entity/ghast/ghast.json
create mode 100644 src/main/resources/assets/minecraft/models/entity/ghast/happy_ghast_adult.json
create mode 100644 src/main/resources/assets/minecraft/models/entity/ghast/happy_ghast_baby.json
diff --git a/src/main/java/de/bluecolored/bluemap/entities/Addon.java b/src/main/java/de/bluecolored/bluemap/entities/Addon.java
index 24174b2..45fc3d8 100644
--- a/src/main/java/de/bluecolored/bluemap/entities/Addon.java
+++ b/src/main/java/de/bluecolored/bluemap/entities/Addon.java
@@ -3,6 +3,7 @@
import de.bluecolored.bluemap.core.map.hires.entity.EntityRendererType;
import de.bluecolored.bluemap.core.util.Key;
import de.bluecolored.bluemap.core.world.mca.entity.EntityType;
+import de.bluecolored.bluemap.core.world.mca.entity.MCAEntity;
import de.bluecolored.bluemap.entities.entity.*;
import de.bluecolored.bluemap.entities.renderer.*;
@@ -49,6 +50,8 @@ public void run() {
EntityType.REGISTRY.register(new EntityType.Impl(Key.minecraft("camel_husk"), CamelHusk.class));
EntityType.REGISTRY.register(new EntityType.Impl(Key.minecraft("copper_golem"), CopperGolem.class));
EntityType.REGISTRY.register(new EntityType.Impl(Key.minecraft("frog"), AgeVariantEntity.class));
+ EntityType.REGISTRY.register(new EntityType.Impl(Key.minecraft("ghast"), MCAEntity.class));
+ EntityType.REGISTRY.register(new EntityType.Impl(Key.minecraft("happy_ghast"), HappyGhast.class));
EntityRendererType.REGISTRY.register(new EntityRendererType.Impl(Key.minecraft("llama"), LlamaRenderer::new));
@@ -85,6 +88,8 @@ public void run() {
EntityRendererType.REGISTRY.register(new EntityRendererType.Impl(Key.minecraft("camel_husk"), CamelRenderer::new));
EntityRendererType.REGISTRY.register(new EntityRendererType.Impl(Key.minecraft("copper_golem"), CopperGolemRenderer::new));
EntityRendererType.REGISTRY.register(new EntityRendererType.Impl(Key.minecraft("frog"), FrogRenderer::new));
+ EntityRendererType.REGISTRY.register(new EntityRendererType.Impl(Key.minecraft("ghast"), GhastRenderer::new));
+ EntityRendererType.REGISTRY.register(new EntityRendererType.Impl(Key.minecraft("happy_ghast"), GhastRenderer::new));
}
}
diff --git a/src/main/java/de/bluecolored/bluemap/entities/entity/HappyGhast.java b/src/main/java/de/bluecolored/bluemap/entities/entity/HappyGhast.java
new file mode 100644
index 0000000..f0259b4
--- /dev/null
+++ b/src/main/java/de/bluecolored/bluemap/entities/entity/HappyGhast.java
@@ -0,0 +1,35 @@
+/*
+ * This file is part of BlueMap, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) Blue (Lukas Rieger)
+ * Copyright (c) contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package de.bluecolored.bluemap.entities.entity;
+
+import lombok.Getter;
+import lombok.ToString;
+
+@Getter
+@ToString
+@SuppressWarnings("FieldMayBeFinal")
+public class HappyGhast extends AgeEntity {
+
+}
diff --git a/src/main/java/de/bluecolored/bluemap/entities/renderer/GhastRenderer.java b/src/main/java/de/bluecolored/bluemap/entities/renderer/GhastRenderer.java
new file mode 100644
index 0000000..ac87500
--- /dev/null
+++ b/src/main/java/de/bluecolored/bluemap/entities/renderer/GhastRenderer.java
@@ -0,0 +1,81 @@
+/*
+ * This file is part of BlueMap, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) Blue (Lukas Rieger)
+ * Copyright (c) contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package de.bluecolored.bluemap.entities.renderer;
+
+import com.flowpowered.math.vector.Vector3f;
+import de.bluecolored.bluemap.core.map.TextureGallery;
+import de.bluecolored.bluemap.core.map.hires.RenderSettings;
+import de.bluecolored.bluemap.core.map.hires.TileModelView;
+import de.bluecolored.bluemap.core.resources.ResourcePath;
+import de.bluecolored.bluemap.core.resources.pack.resourcepack.ResourcePack;
+import de.bluecolored.bluemap.core.resources.pack.resourcepack.entitystate.Part;
+import de.bluecolored.bluemap.core.resources.pack.resourcepack.model.Model;
+import de.bluecolored.bluemap.core.util.Key;
+import de.bluecolored.bluemap.core.world.Entity;
+import de.bluecolored.bluemap.core.world.block.BlockNeighborhood;
+import de.bluecolored.bluemap.core.world.mca.entity.MCAEntity;
+import de.bluecolored.bluemap.entities.entity.HappyGhast;
+
+public class GhastRenderer extends CustomResourceModelRenderer {
+
+ private final ResourcePath
+ GHAST = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/ghast/ghast"),
+ HAPPY_GHAST = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/ghast/happy_ghast_adult"),
+ GHASTLING = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/ghast/happy_ghast_baby");
+
+ public GhastRenderer(ResourcePack resourcePack, TextureGallery textureGallery, RenderSettings renderSettings) {
+ super(resourcePack, textureGallery, renderSettings);
+ }
+
+ @Override
+ public void render(Entity entity, BlockNeighborhood block, Part part, TileModelView tileModel) {
+ if (!(entity instanceof MCAEntity ghast)) return;
+
+ // choose correct model & scale
+ ResourcePath model;
+ Vector3f scale;
+ if (ghast instanceof HappyGhast) {
+ if (((HappyGhast) ghast).getAge() < 0) {
+ model = GHASTLING;
+ scale = new Vector3f(0.9f, 0.9f, 0.9f);
+ } else {
+ model = HAPPY_GHAST;
+ scale = new Vector3f(4f, 4f, 4f);
+ }
+ } else {
+ model = GHAST;
+ scale = new Vector3f(4.5, 4.5, 4.5);
+ }
+
+ // render chosen model
+ super.render(entity, block, model.getResource(getModelProvider()), TintColorProvider.NO_TINT, tileModel);
+ tileModel.scale(scale.getX(), scale.getY(), scale.getZ());
+
+ // apply part transform
+ if (part.isTransformed())
+ tileModel.transform(part.getTransformMatrix());
+ }
+
+}
diff --git a/src/main/resources/assets/minecraft/entitystates/ghast.json b/src/main/resources/assets/minecraft/entitystates/ghast.json
new file mode 100644
index 0000000..de2bac5
--- /dev/null
+++ b/src/main/resources/assets/minecraft/entitystates/ghast.json
@@ -0,0 +1,5 @@
+{
+ "parts": [
+ { "renderer": "minecraft:ghast" }
+ ]
+}
\ No newline at end of file
diff --git a/src/main/resources/assets/minecraft/entitystates/happy_ghast.json b/src/main/resources/assets/minecraft/entitystates/happy_ghast.json
new file mode 100644
index 0000000..a39ed8f
--- /dev/null
+++ b/src/main/resources/assets/minecraft/entitystates/happy_ghast.json
@@ -0,0 +1,5 @@
+{
+ "parts": [
+ { "renderer": "minecraft:happy_ghast" }
+ ]
+}
\ No newline at end of file
diff --git a/src/main/resources/assets/minecraft/models/entity/ghast/ghast.json b/src/main/resources/assets/minecraft/models/entity/ghast/ghast.json
new file mode 100644
index 0000000..beea921
--- /dev/null
+++ b/src/main/resources/assets/minecraft/models/entity/ghast/ghast.json
@@ -0,0 +1,163 @@
+{
+ "format_version": "1.21.11",
+ "credit": "Made by Miraculixx",
+ "texture_size": [64, 32],
+ "textures": {
+ "0": "entity/ghast/ghast"
+ },
+ "elements": [
+ {
+ "name": "body",
+ "from": [-8, 0, -8],
+ "to": [8, 16, 8],
+ "faces": {
+ "north": {"uv": [4, 8, 8, 16], "texture": "#0"},
+ "east": {"uv": [0, 8, 4, 16], "texture": "#0"},
+ "south": {"uv": [12, 8, 16, 16], "texture": "#0"},
+ "west": {"uv": [8, 8, 12, 16], "texture": "#0"},
+ "up": {"uv": [8, 8, 4, 0], "texture": "#0"},
+ "down": {"uv": [12, 0, 8, 8], "texture": "#0"}
+ }
+ },
+ {
+ "name": "tentacle1",
+ "from": [2.7, -7, -6],
+ "to": [4.7, 1, -4],
+ "rotation": {"angle": -10, "axis": "x", "origin": [3.7, 1, -5]},
+ "faces": {
+ "north": {"uv": [0.5, 1, 1, 5], "texture": "#0"},
+ "east": {"uv": [0, 1, 0.5, 5], "texture": "#0"},
+ "south": {"uv": [1.5, 1, 2, 5], "texture": "#0"},
+ "west": {"uv": [1, 1, 1.5, 5], "texture": "#0"},
+ "up": {"uv": [1, 1, 0.5, 0], "texture": "#0"},
+ "down": {"uv": [1.5, 0, 1, 1], "texture": "#0"}
+ }
+ },
+ {
+ "name": "tentacle2",
+ "from": [-7.3, -8, -6],
+ "to": [-5.3, 1, -4],
+ "rotation": {"angle": -15, "axis": "x", "origin": [-6.3, 1, -5]},
+ "faces": {
+ "north": {"uv": [0.5, 1, 1, 5.5], "texture": "#0"},
+ "east": {"uv": [0, 1, 0.5, 5.5], "texture": "#0"},
+ "south": {"uv": [1.5, 1, 2, 5.5], "texture": "#0"},
+ "west": {"uv": [1, 1, 1.5, 5.5], "texture": "#0"},
+ "up": {"uv": [1, 1, 0.5, 0], "texture": "#0"},
+ "down": {"uv": [1.5, 0, 1, 1], "texture": "#0"}
+ }
+ },
+ {
+ "name": "tentacle3",
+ "from": [-2.3, -12, -6],
+ "to": [-0.3, 1, -4],
+ "rotation": {"angle": -25, "axis": "x", "origin": [-1.3, 1, -5]},
+ "faces": {
+ "north": {"uv": [0.5, 1, 1, 7.5], "texture": "#0"},
+ "east": {"uv": [0, 1, 0.5, 7.5], "texture": "#0"},
+ "south": {"uv": [1.5, 1, 2, 7.5], "texture": "#0"},
+ "west": {"uv": [1, 1, 1.5, 7.5], "texture": "#0"},
+ "up": {"uv": [1, 1, 0.5, 0], "texture": "#0"},
+ "down": {"uv": [1.5, 0, 1, 1], "texture": "#0"}
+ }
+ },
+ {
+ "name": "tentacle4",
+ "from": [5.3, -10, -1],
+ "to": [7.3, 1, 1],
+ "rotation": {"angle": -12.5, "axis": "x", "origin": [6.3, 1, 0]},
+ "faces": {
+ "north": {"uv": [0.5, 1, 1, 6.5], "texture": "#0"},
+ "east": {"uv": [0, 1, 0.5, 6.5], "texture": "#0"},
+ "south": {"uv": [1.5, 1, 2, 6.5], "texture": "#0"},
+ "west": {"uv": [1, 1, 1.5, 6.5], "texture": "#0"},
+ "up": {"uv": [1, 1, 0.5, 0], "texture": "#0"},
+ "down": {"uv": [1.5, 0, 1, 1], "texture": "#0"}
+ }
+ },
+ {
+ "name": "tentacle5",
+ "from": [0.3, -10, -1],
+ "to": [2.3, 1, 1],
+ "rotation": {"angle": -25, "axis": "x", "origin": [1.3, 1, 0]},
+ "faces": {
+ "north": {"uv": [0.5, 1, 1, 6.5], "texture": "#0"},
+ "east": {"uv": [0, 1, 0.5, 6.5], "texture": "#0"},
+ "south": {"uv": [1.5, 1, 2, 6.5], "texture": "#0"},
+ "west": {"uv": [1, 1, 1.5, 6.5], "texture": "#0"},
+ "up": {"uv": [1, 1, 0.5, 0], "texture": "#0"},
+ "down": {"uv": [1.5, 0, 1, 1], "texture": "#0"}
+ }
+ },
+ {
+ "name": "tentacle6",
+ "from": [-4.7, -9, -1],
+ "to": [-2.7, 1, 1],
+ "rotation": {"angle": -15, "axis": "x", "origin": [-3.7, 1, 0]},
+ "faces": {
+ "north": {"uv": [0.5, 1, 1, 6], "texture": "#0"},
+ "east": {"uv": [0, 1, 0.5, 6], "texture": "#0"},
+ "south": {"uv": [1.5, 1, 2, 6], "texture": "#0"},
+ "west": {"uv": [1, 1, 1.5, 6], "texture": "#0"},
+ "up": {"uv": [1, 1, 0.5, 0], "texture": "#0"},
+ "down": {"uv": [1.5, 0, 1, 1], "texture": "#0"}
+ }
+ },
+ {
+ "name": "tentacle7",
+ "from": [2.7, -11, 4],
+ "to": [4.7, 1, 6],
+ "rotation": {"angle": -32.5, "axis": "x", "origin": [3.7, 1, 5]},
+ "faces": {
+ "north": {"uv": [0.5, 1, 1, 7], "texture": "#0"},
+ "east": {"uv": [0, 1, 0.5, 7], "texture": "#0"},
+ "south": {"uv": [1.5, 1, 2, 7], "texture": "#0"},
+ "west": {"uv": [1, 1, 1.5, 7], "texture": "#0"},
+ "up": {"uv": [1, 1, 0.5, 0], "texture": "#0"},
+ "down": {"uv": [1.5, 0, 1, 1], "texture": "#0"}
+ }
+ },
+ {
+ "name": "tentacle8",
+ "from": [-7.3, -11, 4],
+ "to": [-5.3, 1, 6],
+ "rotation": {"angle": -22.5, "axis": "x", "origin": [-6.3, 1, 5]},
+ "faces": {
+ "north": {"uv": [0.5, 1, 1, 7], "texture": "#0"},
+ "east": {"uv": [0, 1, 0.5, 7], "texture": "#0"},
+ "south": {"uv": [1.5, 1, 2, 7], "texture": "#0"},
+ "west": {"uv": [1, 1, 1.5, 7], "texture": "#0"},
+ "up": {"uv": [1, 1, 0.5, 0], "texture": "#0"},
+ "down": {"uv": [1.5, 0, 1, 1], "texture": "#0"}
+ }
+ },
+ {
+ "name": "tentacle9",
+ "from": [-2.3, -8, 4],
+ "to": [-0.3, 1, 6],
+ "rotation": {"angle": -17.5, "axis": "x", "origin": [-1.3, 1, 5]},
+ "faces": {
+ "north": {"uv": [0.5, 1, 1, 5.5], "texture": "#0"},
+ "east": {"uv": [0, 1, 0.5, 5.5], "texture": "#0"},
+ "south": {"uv": [1.5, 1, 2, 5.5], "texture": "#0"},
+ "west": {"uv": [1, 1, 1.5, 5.5], "texture": "#0"},
+ "up": {"uv": [1, 1, 0.5, 0], "texture": "#0"},
+ "down": {"uv": [1.5, 0, 1, 1], "texture": "#0"}
+ }
+ }
+ ],
+ "groups": [
+ {
+ "name": "body",
+ "origin": [0, 20, 0],
+ "color": 0,
+ "children": [0]
+ },
+ {
+ "name": "tentacle",
+ "origin": [3.7, 13, -5],
+ "color": 0,
+ "children": [1, 2, 3, 4, 5, 6, 7, 8, 9]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/main/resources/assets/minecraft/models/entity/ghast/happy_ghast_adult.json b/src/main/resources/assets/minecraft/models/entity/ghast/happy_ghast_adult.json
new file mode 100644
index 0000000..9c530af
--- /dev/null
+++ b/src/main/resources/assets/minecraft/models/entity/ghast/happy_ghast_adult.json
@@ -0,0 +1,178 @@
+{
+ "format_version": "1.21.11",
+ "credit": "Made by Miraculixx",
+ "texture_size": [64, 64],
+ "textures": {
+ "0": "entity/ghast/happy_ghast"
+ },
+ "elements": [
+ {
+ "name": "body",
+ "from": [-8, 0, -8],
+ "to": [8, 16, 8],
+ "rotation": {"angle": 0, "axis": "y", "origin": [0, -12, 0]},
+ "faces": {
+ "north": {"uv": [4, 4, 8, 8], "texture": "#0"},
+ "east": {"uv": [0, 4, 4, 8], "texture": "#0"},
+ "south": {"uv": [12, 4, 16, 8], "texture": "#0"},
+ "west": {"uv": [8, 4, 12, 8], "texture": "#0"},
+ "up": {"uv": [8, 4, 4, 0], "texture": "#0"},
+ "down": {"uv": [12, 0, 8, 4], "texture": "#0"}
+ }
+ },
+ {
+ "name": "body",
+ "from": [-7.5, 0.5, -7.5],
+ "to": [7.5, 15.5, 7.5],
+ "rotation": {"angle": 0, "axis": "y", "origin": [0, -12, 0]},
+ "faces": {
+ "north": {"uv": [4, 12, 8, 16], "texture": "#0"},
+ "east": {"uv": [0, 12, 4, 16], "texture": "#0"},
+ "south": {"uv": [12, 12, 16, 16], "texture": "#0"},
+ "west": {"uv": [8, 12, 12, 16], "texture": "#0"},
+ "up": {"uv": [8, 12, 4, 8], "texture": "#0"},
+ "down": {"uv": [12, 8, 8, 12], "texture": "#0"}
+ }
+ },
+ {
+ "name": "tentacle1",
+ "from": [2.7, -7, -6],
+ "to": [4.7, 1, -4],
+ "rotation": {"angle": -10, "axis": "x", "origin": [3.7, 1, -5]},
+ "faces": {
+ "north": {"uv": [0.5, 0.5, 1, 2.5], "texture": "#0"},
+ "east": {"uv": [0, 0.5, 0.5, 2.5], "texture": "#0"},
+ "south": {"uv": [1.5, 0.5, 2, 2.5], "texture": "#0"},
+ "west": {"uv": [1, 0.5, 1.5, 2.5], "texture": "#0"},
+ "up": {"uv": [1, 0.5, 0.5, 0], "texture": "#0"},
+ "down": {"uv": [1.5, 0, 1, 0.5], "texture": "#0"}
+ }
+ },
+ {
+ "name": "tentacle2",
+ "from": [-7.3, -8, -6],
+ "to": [-5.3, 1, -4],
+ "rotation": {"angle": -15, "axis": "x", "origin": [-6.3, 1, -5]},
+ "faces": {
+ "north": {"uv": [0.5, 0.5, 1, 2.75], "texture": "#0"},
+ "east": {"uv": [0, 0.5, 0.5, 2.75], "texture": "#0"},
+ "south": {"uv": [1.5, 0.5, 2, 2.75], "texture": "#0"},
+ "west": {"uv": [1, 0.5, 1.5, 2.75], "texture": "#0"},
+ "up": {"uv": [1, 0.5, 0.5, 0], "texture": "#0"},
+ "down": {"uv": [1.5, 0, 1, 0.5], "texture": "#0"}
+ }
+ },
+ {
+ "name": "tentacle3",
+ "from": [-2.3, -12, -6],
+ "to": [-0.3, 1, -4],
+ "rotation": {"angle": -25, "axis": "x", "origin": [-1.3, 1, -5]},
+ "faces": {
+ "north": {"uv": [0.5, 0.5, 1, 3.75], "texture": "#0"},
+ "east": {"uv": [0, 0.5, 0.5, 3.75], "texture": "#0"},
+ "south": {"uv": [1.5, 0.5, 2, 3.75], "texture": "#0"},
+ "west": {"uv": [1, 0.5, 1.5, 3.75], "texture": "#0"},
+ "up": {"uv": [1, 0.5, 0.5, 0], "texture": "#0"},
+ "down": {"uv": [1.5, 0, 1, 0.5], "texture": "#0"}
+ }
+ },
+ {
+ "name": "tentacle4",
+ "from": [5.3, -10, -1],
+ "to": [7.3, 1, 1],
+ "rotation": {"angle": -12.5, "axis": "x", "origin": [6.3, 1, 0]},
+ "faces": {
+ "north": {"uv": [0.5, 0.5, 1, 3.25], "texture": "#0"},
+ "east": {"uv": [0, 0.5, 0.5, 3.25], "texture": "#0"},
+ "south": {"uv": [1.5, 0.5, 2, 3.25], "texture": "#0"},
+ "west": {"uv": [1, 0.5, 1.5, 3.25], "texture": "#0"},
+ "up": {"uv": [1, 0.5, 0.5, 0], "texture": "#0"},
+ "down": {"uv": [1.5, 0, 1, 0.5], "texture": "#0"}
+ }
+ },
+ {
+ "name": "tentacle5",
+ "from": [0.3, -10, -1],
+ "to": [2.3, 1, 1],
+ "rotation": {"angle": -25, "axis": "x", "origin": [1.3, 1, 0]},
+ "faces": {
+ "north": {"uv": [0.5, 0.5, 1, 3.25], "texture": "#0"},
+ "east": {"uv": [0, 0.5, 0.5, 3.25], "texture": "#0"},
+ "south": {"uv": [1.5, 0.5, 2, 3.25], "texture": "#0"},
+ "west": {"uv": [1, 0.5, 1.5, 3.25], "texture": "#0"},
+ "up": {"uv": [1, 0.5, 0.5, 0], "texture": "#0"},
+ "down": {"uv": [1.5, 0, 1, 0.5], "texture": "#0"}
+ }
+ },
+ {
+ "name": "tentacle6",
+ "from": [-4.7, -9, -1],
+ "to": [-2.7, 1, 1],
+ "rotation": {"angle": -15, "axis": "x", "origin": [-3.7, 1, 0]},
+ "faces": {
+ "north": {"uv": [0.5, 0.5, 1, 3], "texture": "#0"},
+ "east": {"uv": [0, 0.5, 0.5, 3], "texture": "#0"},
+ "south": {"uv": [1.5, 0.5, 2, 3], "texture": "#0"},
+ "west": {"uv": [1, 0.5, 1.5, 3], "texture": "#0"},
+ "up": {"uv": [1, 0.5, 0.5, 0], "texture": "#0"},
+ "down": {"uv": [1.5, 0, 1, 0.5], "texture": "#0"}
+ }
+ },
+ {
+ "name": "tentacle7",
+ "from": [2.7, -11, 4],
+ "to": [4.7, 1, 6],
+ "rotation": {"angle": -32.5, "axis": "x", "origin": [3.7, 1, 5]},
+ "faces": {
+ "north": {"uv": [0.5, 0.5, 1, 3.5], "texture": "#0"},
+ "east": {"uv": [0, 0.5, 0.5, 3.5], "texture": "#0"},
+ "south": {"uv": [1.5, 0.5, 2, 3.5], "texture": "#0"},
+ "west": {"uv": [1, 0.5, 1.5, 3.5], "texture": "#0"},
+ "up": {"uv": [1, 0.5, 0.5, 0], "texture": "#0"},
+ "down": {"uv": [1.5, 0, 1, 0.5], "texture": "#0"}
+ }
+ },
+ {
+ "name": "tentacle8",
+ "from": [-7.3, -11, 4],
+ "to": [-5.3, 1, 6],
+ "rotation": {"angle": -22.5, "axis": "x", "origin": [-6.3, 1, 5]},
+ "faces": {
+ "north": {"uv": [0.5, 0.5, 1, 3.5], "texture": "#0"},
+ "east": {"uv": [0, 0.5, 0.5, 3.5], "texture": "#0"},
+ "south": {"uv": [1.5, 0.5, 2, 3.5], "texture": "#0"},
+ "west": {"uv": [1, 0.5, 1.5, 3.5], "texture": "#0"},
+ "up": {"uv": [1, 0.5, 0.5, 0], "texture": "#0"},
+ "down": {"uv": [1.5, 0, 1, 0.5], "texture": "#0"}
+ }
+ },
+ {
+ "name": "tentacle9",
+ "from": [-2.3, -8, 4],
+ "to": [-0.3, 1, 6],
+ "rotation": {"angle": -17.5, "axis": "x", "origin": [-1.3, 1, 5]},
+ "faces": {
+ "north": {"uv": [0.5, 0.5, 1, 2.75], "texture": "#0"},
+ "east": {"uv": [0, 0.5, 0.5, 2.75], "texture": "#0"},
+ "south": {"uv": [1.5, 0.5, 2, 2.75], "texture": "#0"},
+ "west": {"uv": [1, 0.5, 1.5, 2.75], "texture": "#0"},
+ "up": {"uv": [1, 0.5, 0.5, 0], "texture": "#0"},
+ "down": {"uv": [1.5, 0, 1, 0.5], "texture": "#0"}
+ }
+ }
+ ],
+ "groups": [
+ {
+ "name": "body",
+ "origin": [0, 20, 0],
+ "color": 0,
+ "children": [0, 1]
+ },
+ {
+ "name": "tentacle",
+ "origin": [3.7, 13, -5],
+ "color": 0,
+ "children": [2, 3, 4, 5, 6, 7, 8, 9, 10]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/main/resources/assets/minecraft/models/entity/ghast/happy_ghast_baby.json b/src/main/resources/assets/minecraft/models/entity/ghast/happy_ghast_baby.json
new file mode 100644
index 0000000..31c590c
--- /dev/null
+++ b/src/main/resources/assets/minecraft/models/entity/ghast/happy_ghast_baby.json
@@ -0,0 +1,6 @@
+{
+ "parent": "entity/ghast/happy_ghast_adult",
+ "textures": {
+ "0": "entity/ghast/happy_ghast_baby"
+ }
+}
\ No newline at end of file
From 70cf82bb928c40eefcaa3a40b4766dd8fc6d44dd Mon Sep 17 00:00:00 2001
From: miraculixx
Date: Mon, 10 Aug 2026 23:48:18 +0200
Subject: [PATCH 03/34] start of gen modul, still needs many overrides
---
.gitignore | 1 +
generator/README.md | 66 ++++
generator/build.gradle.kts | 100 ++++++
generator/config/equipment.json | 8 +
generator/config/textures.json | 55 +++
generator/config/variant-models.json | 5 +
.../bluemap/entities/generator/Geometry.java | 48 +++
.../entities/generator/LayerConverter.java | 309 +++++++++++++++++
.../bluemap/entities/generator/Main.java | 238 +++++++++++++
.../bluemap/entities/generator/McAssets.java | 192 +++++++++++
.../bluemap/entities/generator/ModelJson.java | 98 ++++++
.../entities/generator/ModelWriter.java | 120 +++++++
.../bluemap/entities/generator/Report.java | 91 +++++
.../entities/generator/TextureResolver.java | 316 ++++++++++++++++++
settings.gradle.kts | 2 +
15 files changed, 1649 insertions(+)
create mode 100644 generator/README.md
create mode 100644 generator/build.gradle.kts
create mode 100644 generator/config/equipment.json
create mode 100644 generator/config/textures.json
create mode 100644 generator/config/variant-models.json
create mode 100644 generator/src/main/java/de/bluecolored/bluemap/entities/generator/Geometry.java
create mode 100644 generator/src/main/java/de/bluecolored/bluemap/entities/generator/LayerConverter.java
create mode 100644 generator/src/main/java/de/bluecolored/bluemap/entities/generator/Main.java
create mode 100644 generator/src/main/java/de/bluecolored/bluemap/entities/generator/McAssets.java
create mode 100644 generator/src/main/java/de/bluecolored/bluemap/entities/generator/ModelJson.java
create mode 100644 generator/src/main/java/de/bluecolored/bluemap/entities/generator/ModelWriter.java
create mode 100644 generator/src/main/java/de/bluecolored/bluemap/entities/generator/Report.java
create mode 100644 generator/src/main/java/de/bluecolored/bluemap/entities/generator/TextureResolver.java
diff --git a/.gitignore b/.gitignore
index 4445a5e..0bb893c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@
.idea
build
gradle.properties
+logs
diff --git a/generator/README.md b/generator/README.md
new file mode 100644
index 0000000..134bb7b
--- /dev/null
+++ b/generator/README.md
@@ -0,0 +1,66 @@
+# Entity-model generator
+
+Generates the bluemap entity-models in `src/main/resources/assets/minecraft/models/entity` from the model-layers that
+are compiled into the vanilla minecraft client-jar, so they do not have to be rebuilt by hand for every
+minecraft-version.
+
+```bash
+./gradlew :generator:generateEntityModels # current version (see mcVersion below)
+./gradlew :generator:generateEntityModels -PmcVersion=26.3 # a different minecraft-version
+./gradlew :generator:generateEntityModels -Players='sheep|cow' # only some layers
+./gradlew :generator:generateEntityModels -PoutDir=/tmp/models # write somewhere else (dry-run)
+```
+
+The client-jar and its libraries are downloaded from mojangs launcher-metadata into `generator/build/minecraft/`
+(~100 MB, once per version). Nothing of it is shipped with the addon, only the generated models are.
+
+Gradle itself has to run on a JDK **21-24** (its kotlin-dsl compiler does not run on 25 yet), the generator is compiled
+and run with a java-25 toolchain, since that is what the minecraft client-jar is built for.
+
+## How it works
+
+`LayerDefinitions.createRoots()` returns every entity-model-layer minecraft knows (358 in 26.2). Each layer is baked
+(`LayerDefinition.bakeRoot()`) and walked with `ModelPart.visit(...)`, which yields every cube together with the
+accumulated part-transform. Minecraft builds entity-models mirrored and upside-down (the entity-renderer applies a
+`scale(-1, -1, 1)`) with the origin 1.5 blocks above the feet, so every coordinate is converted with
+`x' = -x`, `y' = 24 - y`, `z' = z`, and the part-hierarchy is flattened into the single rotation a bluemap-element
+supports. Uv-coordinates are taken from the baked polygon-vertices, which also covers mirrored and deformed cubes.
+
+## Output
+
+One geometry-model per layer, plus a thin model per texture-variant that only overrides the texture:
+
+```
+entity/sheep/main.json minecraft:sheep#main
+entity/sheep/wool.json minecraft:sheep#wool (overlay-layer)
+entity/sheep_baby/main.json minecraft:sheep_baby#main (baby-model)
+entity/cat/main_tabby.json variant, parent: entity/cat/main
+entity/zombie/helmet_iron.json equipment-material of minecraft:zombie#helmet
+```
+
+Textures use the vanilla paths (`entity/sheep/sheep`) and are resolved, in this order, from
+`config/textures.json`, the data-driven variant-registries of the client-jar (cat, cow, pig, chicken, frog, wolf, ...),
+the equipment-textures (`entity/equipment/...`), the vanilla naming-convention, the texture-family of the entity
+(axolotl, horse, mooshroom, ...) and finally the texture-paths referenced by the compiled renderer-classes.
+
+`generator/build/generation-report.txt` lists which models were added, changed and removed, where every texture came
+from, and which layers could not be resolved.
+
+## Configuration
+
+| file | purpose |
+|---|---|
+| `config/textures.json` | pins a texture (string) or a set of variant-textures (array) for `#` |
+| `config/variant-models.json` | maps the `model` field of a variant-registry-entry to its model-id |
+| `config/equipment.json` | maps `#` to a folder in `entity/equipment` |
+| `overrides/.json` | replaces a generated model entirely, e.g. for hand-fixed geometry |
+| `generated-index.txt` | the files of the last run, used to delete models that vanished from minecraft |
+
+## Limits
+
+- Layers are generated in their base pose. Animations (`setupAnim`) and renderer-side tints are not part of the
+ model-data and stay the job of the renderers in `de.bluecolored.bluemap.entities.renderer`.
+- Which layers an entity stacks (wool over sheep, saddle over pig, armor over zombie) is renderer-logic in minecraft,
+ so it stays renderer-logic here too - the generator only provides the models.
+- Elements that need a rotation around more than one axis (60 models in 26.2) require **bluemap-core 5.14 or newer**,
+ everything else also works with older versions.
diff --git a/generator/build.gradle.kts b/generator/build.gradle.kts
new file mode 100644
index 0000000..5818057
--- /dev/null
+++ b/generator/build.gradle.kts
@@ -0,0 +1,100 @@
+import groovy.json.JsonSlurper
+import java.net.URI
+
+plugins {
+ java
+}
+
+val mcVersion = (findProperty("mcVersion") as String?) ?: "26.2"
+val mcDir = layout.buildDirectory.dir("minecraft/$mcVersion")
+
+// the generator is a tool, not part of the addon
+val requested = gradle.startParameter.taskNames.any { it.startsWith(":generator") || it == "generateEntityModels" }
+val available = { requested || mcDir.get().file("client.jar").asFile.isFile }
+
+java {
+ toolchain.languageVersion = JavaLanguageVersion.of(25)
+}
+
+@Suppress("UNCHECKED_CAST")
+val downloadMinecraft by tasks.registering {
+ description = "Downloads the vanilla minecraft client-jar and its libraries for version $mcVersion"
+ val target = mcDir
+ outputs.dir(target)
+ onlyIf { available() }
+ doLast {
+ val dir = target.get().asFile
+ val libs = dir.resolve("libs").apply { mkdirs() }
+
+ fun fetch(url: String, file: File, size: Long) {
+ if (file.isFile && file.length() == size) return
+ logger.lifecycle("downloading ${file.name}")
+ URI(url).toURL().openStream().use { input -> file.outputStream().use { input.copyTo(it) } }
+ }
+
+ val manifest = JsonSlurper()
+ .parse(URI("https://launchermeta.mojang.com/mc/game/version_manifest_v2.json").toURL()) as Map
+ val entry = (manifest["versions"] as List
+ */
+final class LayerConverter {
+
+ private static final float[] FLIP = { -1, -1, 1 };
+ private static final float Y_OFFSET = 24;
+ private static final float POSITION_EPSILON = 1e-3f;
+ private static final float ANGLE_EPSILON = 1e-4f;
+ private static final String[] DIRECTIONS = { "down", "up", "north", "south", "west", "east" };
+
+ private LayerConverter() {}
+
+ static Geometry convert(LayerDefinition layer) {
+ int[] textureSize = textureSize(layer);
+ List elements = new ArrayList<>();
+ List warnings = new ArrayList<>();
+
+ layer.bakeRoot().visit(new PoseStack(), (pose, path, index, cube) -> {
+ String name = name(path, index);
+ Geometry.Element element = convertCube(pose.pose(), name, cube, warnings);
+ if (finite(element)) elements.add(element);
+ else warnings.add(name + ": dropped, contains a non-finite coordinate");
+ });
+
+ elements.sort(Comparator.comparing(Geometry.Element::name));
+ return new Geometry(textureSize[0], textureSize[1], elements, warnings);
+ }
+
+ private static boolean finite(Geometry.Element element) {
+ List values = new ArrayList<>(element.faces().values());
+ values.add(element.from());
+ values.add(element.to());
+ if (element.rotation() != null) {
+ values.add(element.rotation());
+ values.add(element.rotationOrigin());
+ }
+
+ for (float[] value : values)
+ for (float number : value)
+ if (!Float.isFinite(number)) return false;
+ return true;
+ }
+
+ private static String name(String path, int index) {
+ String name = path.startsWith("/") ? path.substring(1) : path;
+ return index == 0 ? name : name + "_" + index;
+ }
+
+ private static Geometry.Element convertCube(Matrix4f matrix, String name, ModelPart.Cube cube, List warnings) {
+
+ // linear part of the accumulated part-transform, columns are the local axes
+ float[][] linear = {
+ { matrix.m00(), matrix.m10(), matrix.m20() },
+ { matrix.m01(), matrix.m11(), matrix.m21() },
+ { matrix.m02(), matrix.m12(), matrix.m22() }
+ };
+
+ float[] scale = new float[3];
+ for (int col = 0; col < 3; col++) {
+ scale[col] = (float) Math.sqrt(
+ linear[0][col] * linear[0][col] +
+ linear[1][col] * linear[1][col] +
+ linear[2][col] * linear[2][col]
+ );
+ if (scale[col] < ANGLE_EPSILON) {
+ warnings.add(name + ": degenerate part-scale, using 1");
+ scale[col] = 1;
+ }
+ for (int row = 0; row < 3; row++) linear[row][col] /= scale[col];
+ }
+
+ // conjugate the rotation with the coordinate-flip, so an un-rotated part stays un-rotated
+ float[][] rotation = new float[3][3];
+ for (int row = 0; row < 3; row++)
+ for (int col = 0; col < 3; col++)
+ rotation[row][col] = FLIP[row] * FLIP[col] * linear[row][col];
+
+ // the parts pivot becomes the rotation-origin of the element
+ float[] origin = {
+ FLIP[0] * matrix.m30() * 16,
+ FLIP[1] * matrix.m31() * 16 + Y_OFFSET,
+ FLIP[2] * matrix.m32() * 16
+ };
+
+ // the vertices, not minX/maxX, carry the cube-deformation ("grow") minecraft applies to a cube
+ float[][] bounds = bounds(cube);
+ float[] min = { bounds[0][0] * scale[0], bounds[0][1] * scale[1], bounds[0][2] * scale[2] };
+ float[] max = { bounds[1][0] * scale[0], bounds[1][1] * scale[1], bounds[1][2] * scale[2] };
+ float[] localMin = { -max[0], -max[1], min[2] };
+ float[] localMax = { -min[0], -min[1], max[2] };
+
+ float[] from = new float[3], to = new float[3];
+ for (int i = 0; i < 3; i++) {
+ from[i] = localMin[i] + origin[i];
+ to[i] = localMax[i] + origin[i];
+ }
+
+ float[] euler = euler(rotation, name, warnings);
+ Map faces = faces(cube, localMin, localMax, scale, name, warnings);
+
+ return new Geometry.Element(name, from, to, euler, euler == null ? null : origin, faces);
+ }
+
+ private static float[][] bounds(ModelPart.Cube cube) {
+ float[] min = { Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE };
+ float[] max = { -Float.MAX_VALUE, -Float.MAX_VALUE, -Float.MAX_VALUE };
+
+ for (ModelPart.Polygon polygon : cube.polygons) {
+ for (ModelPart.Vertex vertex : polygon.vertices()) {
+ float[] position = { vertex.x(), vertex.y(), vertex.z() };
+ for (int i = 0; i < 3; i++) {
+ min[i] = Math.min(min[i], position[i]);
+ max[i] = Math.max(max[i], position[i]);
+ }
+ }
+ }
+
+ if (min[0] > max[0]) return new float[][] {
+ { cube.minX, cube.minY, cube.minZ },
+ { cube.maxX, cube.maxY, cube.maxZ }
+ };
+ return new float[][] { min, max };
+ }
+
+ private static Map faces(
+ ModelPart.Cube cube, float[] localMin, float[] localMax, float[] scale,
+ String name, List warnings
+ ) {
+ Map faces = new LinkedHashMap<>();
+ for (ModelPart.Polygon polygon : cube.polygons) {
+ int direction = direction(polygon.normal());
+ if (direction < 0) {
+ warnings.add(name + ": skipped face with non-axis-aligned normal " + polygon.normal());
+ continue;
+ }
+
+ float[][] corners = corners(direction, localMin, localMax);
+ float[][] uvs = new float[4][];
+ for (int i = 0; i < 4; i++) {
+ uvs[i] = uv(polygon, corners[i], scale);
+ if (uvs[i] == null) {
+ warnings.add(name + ": no matching vertex for the " + DIRECTIONS[direction] + "-face");
+ break;
+ }
+ }
+ if (uvs[3] == null) continue;
+
+ // bluemap maps the uv-corners (x1,y2) (x2,y2) (x2,y1) (x1,y1) onto the face-corners in that order
+ faces.put(DIRECTIONS[direction], new float[] { uvs[0][0], uvs[2][1], uvs[1][0], uvs[0][1] });
+ }
+ return faces;
+ }
+
+ private static float[] uv(ModelPart.Polygon polygon, float[] corner, float[] scale) {
+ float x = FLIP[0] * corner[0] / scale[0];
+ float y = FLIP[1] * corner[1] / scale[1];
+ float z = FLIP[2] * corner[2] / scale[2];
+
+ for (ModelPart.Vertex vertex : polygon.vertices()) {
+ if (
+ Math.abs(vertex.x() - x) < POSITION_EPSILON &&
+ Math.abs(vertex.y() - y) < POSITION_EPSILON &&
+ Math.abs(vertex.z() - z) < POSITION_EPSILON
+ ) return new float[] { vertex.u() * 16, vertex.v() * 16 };
+ }
+ return null;
+ }
+
+ private static int direction(Vector3fc normal) {
+ float x = FLIP[0] * normal.x(), y = FLIP[1] * normal.y(), z = FLIP[2] * normal.z();
+ if (y < -0.9f) return 0;
+ if (y > 0.9f) return 1;
+ if (z < -0.9f) return 2;
+ if (z > 0.9f) return 3;
+ if (x < -0.9f) return 4;
+ if (x > 0.9f) return 5;
+ return -1;
+ }
+
+ private static float[][] corners(int direction, float[] min, float[] max) {
+ return switch (direction) {
+ case 0 -> new float[][] {
+ { min[0], min[1], min[2] }, { max[0], min[1], min[2] },
+ { max[0], min[1], max[2] }, { min[0], min[1], max[2] } };
+ case 1 -> new float[][] {
+ { min[0], max[1], max[2] }, { max[0], max[1], max[2] },
+ { max[0], max[1], min[2] }, { min[0], max[1], min[2] } };
+ case 2 -> new float[][] {
+ { max[0], min[1], min[2] }, { min[0], min[1], min[2] },
+ { min[0], max[1], min[2] }, { max[0], max[1], min[2] } };
+ case 3 -> new float[][] {
+ { min[0], min[1], max[2] }, { max[0], min[1], max[2] },
+ { max[0], max[1], max[2] }, { min[0], max[1], max[2] } };
+ case 4 -> new float[][] {
+ { min[0], min[1], min[2] }, { min[0], min[1], max[2] },
+ { min[0], max[1], max[2] }, { min[0], max[1], min[2] } };
+ default -> new float[][] {
+ { max[0], min[1], max[2] }, { max[0], min[1], min[2] },
+ { max[0], max[1], min[2] }, { max[0], max[1], max[2] } };
+ };
+ }
+
+ /**
+ * Decomposes a rotation-matrix into the y-x-z euler-angles (in degrees)
+ */
+ private static float[] euler(float[][] r, String name, List warnings) {
+ double x, y, z;
+ if (Math.abs(r[1][2]) < 0.99999) {
+ x = Math.asin(-r[1][2]);
+ y = Math.atan2(r[0][2], r[2][2]);
+ z = Math.atan2(r[1][0], r[1][1]);
+ } else {
+ x = r[1][2] < 0 ? Math.PI / 2 : -Math.PI / 2;
+ y = Math.atan2(-r[2][0], r[0][0]);
+ z = 0;
+ }
+
+ float[] euler = {
+ (float) Math.toDegrees(x),
+ (float) Math.toDegrees(y),
+ (float) Math.toDegrees(z)
+ };
+
+ float error = error(r, euler);
+ if (error > 1e-3f) warnings.add(name + ": rotation could not be decomposed exactly (error " + error + ")");
+
+ if (Math.abs(euler[0]) < ANGLE_EPSILON && Math.abs(euler[1]) < ANGLE_EPSILON && Math.abs(euler[2]) < ANGLE_EPSILON)
+ return null;
+ return euler;
+ }
+
+ private static float error(float[][] r, float[] euler) {
+ double x = Math.toRadians(euler[0]), y = Math.toRadians(euler[1]), z = Math.toRadians(euler[2]);
+ double sx = Math.sin(x), cx = Math.cos(x);
+ double sy = Math.sin(y), cy = Math.cos(y);
+ double sz = Math.sin(z), cz = Math.cos(z);
+
+ double[][] check = {
+ { cy * cz + sy * sx * sz, -cy * sz + sy * sx * cz, sy * cx },
+ { cx * sz, cx * cz, -sx },
+ { -sy * cz + cy * sx * sz, sy * sz + cy * sx * cz, cy * cx }
+ };
+
+ float error = 0;
+ for (int row = 0; row < 3; row++)
+ for (int col = 0; col < 3; col++)
+ error = Math.max(error, (float) Math.abs(check[row][col] - r[row][col]));
+ return error;
+ }
+
+ private static int[] textureSize(LayerDefinition layer) {
+ try {
+ Field materialField = LayerDefinition.class.getDeclaredField("material");
+ materialField.setAccessible(true);
+ Object material = materialField.get(layer);
+
+ Field width = material.getClass().getDeclaredField("xTexSize");
+ Field height = material.getClass().getDeclaredField("yTexSize");
+ width.setAccessible(true);
+ height.setAccessible(true);
+ return new int[] { width.getInt(material), height.getInt(material) };
+ } catch (ReflectiveOperationException e) {
+ return new int[] { 64, 64 };
+ }
+ }
+
+}
diff --git a/generator/src/main/java/de/bluecolored/bluemap/entities/generator/Main.java b/generator/src/main/java/de/bluecolored/bluemap/entities/generator/Main.java
new file mode 100644
index 0000000..e43229b
--- /dev/null
+++ b/generator/src/main/java/de/bluecolored/bluemap/entities/generator/Main.java
@@ -0,0 +1,238 @@
+/*
+ * This file is part of BlueMap, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) Blue (Lukas Rieger)
+ * Copyright (c) contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package de.bluecolored.bluemap.entities.generator;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+import net.minecraft.SharedConstants;
+import net.minecraft.client.model.geom.LayerDefinitions;
+import net.minecraft.client.model.geom.ModelLayerLocation;
+import net.minecraft.client.model.geom.builders.LayerDefinition;
+import net.minecraft.server.Bootstrap;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.regex.Pattern;
+
+/**
+ * One model per model-layer {@code entity//.json}.
+ * One model per data-driven variant {@code entity//_.json}.
+ * Baby-models & overlay-layers separate layers.
+ */
+public final class Main {
+
+ private static final String CONFIG_TEXTURES = "textures.json";
+ private static final String CONFIG_VARIANT_MODELS = "variant-models.json";
+ private static final String CONFIG_EQUIPMENT = "equipment.json";
+
+ private final Arguments arguments;
+ private final Report report = new Report();
+
+ private Main(Arguments arguments) {
+ this.arguments = arguments;
+ }
+
+ public static void main(String[] args) throws Exception {
+ new Main(Arguments.parse(args)).run();
+ }
+
+ private void run() throws IOException {
+ SharedConstants.tryDetectVersion();
+ Bootstrap.bootStrap();
+
+ Map layers = new TreeMap<>(Main::compare);
+ layers.putAll(LayerDefinitions.createRoots());
+
+ try (McAssets assets = new McAssets(arguments.clientJar)) {
+ Set models = new LinkedHashSet<>();
+ layers.keySet().forEach(layer -> models.add(layer.model().getPath()));
+
+ TextureResolver textures = new TextureResolver(
+ assets,
+ readConfig(CONFIG_TEXTURES, new TypeToken>() {}),
+ readConfig(CONFIG_VARIANT_MODELS, new TypeToken>>() {}),
+ readConfig(CONFIG_EQUIPMENT, new TypeToken>() {}),
+ models
+ );
+ textures.warnings().forEach(report::warning);
+
+ Set written = new TreeSet<>();
+ for (Map.Entry entry : layers.entrySet())
+ generate(entry.getKey(), entry.getValue(), textures, written);
+
+ removeStale(written);
+ writeIndex(written);
+ }
+
+ report.write(arguments.report);
+ System.out.println(report.summary());
+ }
+
+ private void generate(
+ ModelLayerLocation location, LayerDefinition definition,
+ TextureResolver textures, Set written
+ ) throws IOException {
+ String model = location.model().getPath();
+ String layer = location.layer();
+ String key = location.model() + "#" + layer;
+
+ if (!arguments.include.matcher(key).find()) return;
+ if (arguments.exclude != null && arguments.exclude.matcher(key).find()) return;
+
+ Geometry geometry = LayerConverter.convert(definition);
+ geometry.warnings().forEach(warning -> report.warning(key + ": " + warning));
+
+ if (geometry.elements().isEmpty()) {
+ report.empty(key);
+ return;
+ }
+
+ TextureResolver.Resolution resolution = textures.resolve(model, layer);
+ if (resolution.isEmpty()) report.unresolved(key);
+ else report.resolved(key, resolution.source(), resolution.variants().size());
+
+ String base = model + "/" + layer;
+ write(base, ModelWriter.write(geometry, resolution.texture(), arguments.minecraftVersion, key), written);
+
+ for (TextureResolver.Variant variant : resolution.variants())
+ write(base + "_" + variant.suffix(), ModelWriter.writeVariant(base, variant.texture()), written);
+ }
+
+ private void write(String name, String generated, Set written) throws IOException {
+ String path = name + ".json";
+ written.add(path);
+
+ Path override = arguments.overrides.resolve(path);
+ boolean isOverridden = Files.isRegularFile(override);
+ String content = isOverridden ? Files.readString(override, StandardCharsets.UTF_8) : generated;
+ if (isOverridden) report.overridden(path);
+
+ Path file = arguments.out.resolve(path);
+ if (Files.isRegularFile(file)) {
+ if (Files.readString(file, StandardCharsets.UTF_8).equals(content)) {
+ report.unchanged(path);
+ return;
+ }
+ report.changed(path);
+ } else {
+ report.added(path);
+ }
+
+ Files.createDirectories(file.getParent());
+ Files.writeString(file, content, StandardCharsets.UTF_8);
+ }
+
+ /** Deletes models that were generated by a previous run but do not exist in minecraft anymore. */
+ private void removeStale(Set written) throws IOException {
+ for (String path : readIndex()) {
+ if (written.contains(path)) continue;
+
+ Path file = arguments.out.resolve(path);
+ if (Files.deleteIfExists(file)) report.removed(path);
+
+ Path directory = file.getParent();
+ while (directory != null && directory.startsWith(arguments.out) && !directory.equals(arguments.out)) {
+ try (var entries = Files.list(directory)) {
+ if (entries.findAny().isPresent()) break;
+ }
+ Files.delete(directory);
+ directory = directory.getParent();
+ }
+ }
+ }
+
+ private List readIndex() throws IOException {
+ if (!Files.isRegularFile(arguments.index)) return List.of();
+ return Files.readAllLines(arguments.index, StandardCharsets.UTF_8).stream()
+ .map(String::trim)
+ .filter(line -> !line.isEmpty() && !line.startsWith("#"))
+ .toList();
+ }
+
+ private void writeIndex(Set written) throws IOException {
+ List lines = new ArrayList<>();
+ lines.add("# generated by the :generator module - do not edit");
+ lines.addAll(written);
+ Files.createDirectories(arguments.index.getParent());
+ Files.write(arguments.index, lines, StandardCharsets.UTF_8);
+ }
+
+ private T readConfig(String name, TypeToken type) throws IOException {
+ Path file = arguments.config.resolve(name);
+ if (!Files.isRegularFile(file)) return new Gson().fromJson("{}", type);
+ try (var reader = Files.newBufferedReader(file, StandardCharsets.UTF_8)) {
+ T config = new Gson().fromJson(reader, type);
+ return config != null ? config : new Gson().fromJson("{}", type);
+ }
+ }
+
+ private static int compare(ModelLayerLocation a, ModelLayerLocation b) {
+ int model = a.model().toString().compareTo(b.model().toString());
+ return model != 0 ? model : a.layer().compareTo(b.layer());
+ }
+
+ private record Arguments(
+ Path clientJar, Path out, Path config, Path overrides, Path report, Path index,
+ String minecraftVersion, Pattern include, Pattern exclude
+ ) {
+
+ static Arguments parse(String[] args) {
+ Map values = new HashMap<>();
+ for (int i = 0; i < args.length - 1; i += 2) values.put(args[i], args[i + 1]);
+
+ Path config = Path.of(required(values, "--config"));
+ return new Arguments(
+ Path.of(required(values, "--client")),
+ Path.of(required(values, "--out")),
+ config,
+ Path.of(values.getOrDefault("--overrides", config.resolveSibling("overrides").toString())),
+ Path.of(values.getOrDefault("--report", "generation-report.txt")),
+ Path.of(values.getOrDefault("--index", config.resolveSibling("generated-index.txt").toString())),
+ values.getOrDefault("--mc-version", "unknown"),
+ Pattern.compile(values.getOrDefault("--include", ".*")),
+ values.containsKey("--exclude") ? Pattern.compile(values.get("--exclude")) : null
+ );
+ }
+
+ private static String required(Map values, String key) {
+ String value = values.get(key);
+ if (value == null) throw new IllegalArgumentException("missing argument: " + key);
+ return value;
+ }
+
+ }
+
+}
diff --git a/generator/src/main/java/de/bluecolored/bluemap/entities/generator/McAssets.java b/generator/src/main/java/de/bluecolored/bluemap/entities/generator/McAssets.java
new file mode 100644
index 0000000..60c8c31
--- /dev/null
+++ b/generator/src/main/java/de/bluecolored/bluemap/entities/generator/McAssets.java
@@ -0,0 +1,192 @@
+/*
+ * This file is part of BlueMap, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) Blue (Lukas Rieger)
+ * Copyright (c) contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package de.bluecolored.bluemap.entities.generator;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+
+final class McAssets implements AutoCloseable {
+
+ private static final String TEXTURE_PREFIX = "assets/minecraft/textures/";
+ private static final String VARIANT_PREFIX = "data/minecraft/";
+ private static final String RENDERER_PREFIX = "net/minecraft/client/renderer/entity/";
+ private static final Pattern TEXTURE_REFERENCE = Pattern.compile("textures/entity/[a-z0-9_/]+");
+
+ private final ZipFile jar;
+ private final Set textures = new HashSet<>();
+ private final Map> rendererTextures = new HashMap<>();
+ private final Map> variants = new TreeMap<>();
+
+ /**
+ * A single data-driven entity-variant, with its textures keyed by sub-type
+ */
+ record Variant(String entity, String name, String model, Map adult, Map baby) {}
+
+ McAssets(Path clientJar) throws IOException {
+ this.jar = new ZipFile(clientJar.toFile());
+ index();
+ }
+
+ boolean hasTexture(String texture) {
+ return textures.contains(texture);
+ }
+
+ List texturesNamed(String name) {
+ String suffix = "/" + name;
+ return textures.stream()
+ .filter(texture -> texture.startsWith("entity/") && texture.endsWith(suffix))
+ .sorted()
+ .toList();
+ }
+
+ List texturesStartingWith(String prefix) {
+ return textures.stream()
+ .filter(texture -> texture.startsWith("entity/")
+ && texture.substring(texture.lastIndexOf('/') + 1).startsWith(prefix))
+ .sorted()
+ .toList();
+ }
+
+ List texturesIn(String folder) {
+ String prefix = folder + "/";
+ return textures.stream()
+ .filter(texture -> texture.startsWith(prefix) && texture.indexOf('/', prefix.length()) < 0)
+ .sorted()
+ .toList();
+ }
+
+ /** All variants of the given entity-name, in registry-order. */
+ List variants(String entity) {
+ return variants.getOrDefault(entity, List.of());
+ }
+
+ Map> variants() {
+ return Collections.unmodifiableMap(variants);
+ }
+
+ /** Texture-paths referenced by the compiled renderer- and render-layer-classes, keyed by simple class-name. */
+ Map> rendererTextures() {
+ return Collections.unmodifiableMap(rendererTextures);
+ }
+
+ private void index() throws IOException {
+ var entries = jar.entries();
+ while (entries.hasMoreElements()) {
+ ZipEntry entry = entries.nextElement();
+ String path = entry.getName();
+
+ if (path.startsWith(TEXTURE_PREFIX) && path.endsWith(".png")) {
+ textures.add(path.substring(TEXTURE_PREFIX.length(), path.length() - ".png".length()));
+ continue;
+ }
+
+ if (path.startsWith(RENDERER_PREFIX) && path.endsWith(".class")) {
+ indexRendererTextures(entry);
+ continue;
+ }
+
+ if (path.startsWith(VARIANT_PREFIX) && path.endsWith(".json")) indexVariant(entry);
+ }
+ }
+
+ private void indexRendererTextures(ZipEntry entry) throws IOException {
+ String name = entry.getName();
+ name = name.substring(name.lastIndexOf('/') + 1, name.length() - ".class".length());
+
+ String content;
+ try (var in = jar.getInputStream(entry)) {
+ content = new String(in.readAllBytes(), StandardCharsets.ISO_8859_1);
+ }
+
+ List found = new ArrayList<>();
+ Matcher matcher = TEXTURE_REFERENCE.matcher(content);
+ while (matcher.find()) found.add(matcher.group());
+ if (!found.isEmpty()) rendererTextures.put(name, found);
+ }
+
+ private void indexVariant(ZipEntry entry) throws IOException {
+ String path = entry.getName().substring(VARIANT_PREFIX.length());
+ int slash = path.indexOf('/');
+ if (slash < 0) return;
+
+ String registry = path.substring(0, slash);
+ if (!registry.endsWith("_variant")) return;
+ String entity = registry.substring(0, registry.length() - "_variant".length());
+ String name = path.substring(slash + 1, path.length() - ".json".length());
+
+ JsonObject json;
+ try (Reader reader = new InputStreamReader(jar.getInputStream(entry), StandardCharsets.UTF_8)) {
+ json = new Gson().fromJson(reader, JsonObject.class);
+ }
+
+ Map adult = assets(json, "asset_id", "assets");
+ if (adult.isEmpty()) return;
+ Map baby = assets(json, "baby_asset_id", "baby_assets");
+
+ String model = json.has("model") ? json.get("model").getAsString() : null;
+ variants.computeIfAbsent(entity, key -> new ArrayList<>()).add(new Variant(entity, name, model, adult, baby));
+ }
+
+ private static Map assets(JsonObject json, String singleKey, String mapKey) {
+ Map assets = new LinkedHashMap<>();
+ if (json.has(singleKey)) assets.put("", texture(json.get(singleKey).getAsString()));
+ if (json.has(mapKey)) {
+ for (Map.Entry asset : json.getAsJsonObject(mapKey).entrySet())
+ assets.put(asset.getKey(), texture(asset.getValue().getAsString()));
+ }
+ return assets;
+ }
+
+ private static String texture(String assetId) {
+ return assetId.startsWith("minecraft:") ? assetId.substring("minecraft:".length()) : assetId;
+ }
+
+ @Override
+ public void close() throws IOException {
+ jar.close();
+ }
+
+}
diff --git a/generator/src/main/java/de/bluecolored/bluemap/entities/generator/ModelJson.java b/generator/src/main/java/de/bluecolored/bluemap/entities/generator/ModelJson.java
new file mode 100644
index 0000000..5126d50
--- /dev/null
+++ b/generator/src/main/java/de/bluecolored/bluemap/entities/generator/ModelJson.java
@@ -0,0 +1,98 @@
+/*
+ * This file is part of BlueMap, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) Blue (Lukas Rieger)
+ * Copyright (c) contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package de.bluecolored.bluemap.entities.generator;
+
+import com.google.gson.annotations.SerializedName;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * The bluemap entity-model json. Null fields are left out when serialized, so the same records describe both a
+ * geometry-model and a variant-model that only inherits from it.
+ */
+record ModelJson(
+ @SerializedName("format_version") String formatVersion,
+ String credit,
+ @SerializedName("texture_size") int[] textureSize,
+ String parent,
+ Map textures,
+ List elements
+) {
+
+ private static final float ROTATION_EPSILON = 1e-4f;
+ private static final String[] AXES = { "x", "y", "z" };
+
+ record Element(String name, float[] from, float[] to, Rotation rotation, Map faces) {}
+
+ /** Either axis/angle (single-axis, understood by older bluemap-versions too) or the x/y/z euler-angles. */
+ record Rotation(Float angle, String axis, Float x, Float y, Float z, float[] origin) {}
+
+ record Face(float[] uv, String texture) {}
+
+ static ModelJson geometry(Geometry geometry, String texture, String minecraftVersion, String layer) {
+ List elements = geometry.elements().stream().map(ModelJson::element).toList();
+ return new ModelJson(
+ minecraftVersion,
+ "Generated from minecraft " + minecraftVersion + " (" + layer + ")",
+ new int[] { geometry.textureWidth(), geometry.textureHeight() },
+ null,
+ texture == null ? Map.of() : Map.of("0", texture),
+ elements
+ );
+ }
+
+ static ModelJson variant(String parent, String texture) {
+ return new ModelJson(null, null, null, "minecraft:entity/" + parent, Map.of("0", texture), null);
+ }
+
+ private static Element element(Geometry.Element element) {
+ Map faces = new LinkedHashMap<>();
+ element.faces().forEach((direction, uv) -> faces.put(direction, new Face(uv, "#0")));
+
+ return new Element(
+ element.name(),
+ element.from(),
+ element.to(),
+ rotation(element.rotation(), element.rotationOrigin()),
+ faces
+ );
+ }
+
+ private static Rotation rotation(float[] rotation, float[] origin) {
+ if (rotation == null) return null;
+
+ for (int axis = 0; axis < 3; axis++) {
+ if (Math.abs(rotation[(axis + 1) % 3]) > ROTATION_EPSILON) continue;
+ if (Math.abs(rotation[(axis + 2) % 3]) > ROTATION_EPSILON) continue;
+
+ return new Rotation(rotation[axis], AXES[axis], null, null, null, origin);
+ }
+
+ return new Rotation(null, null, rotation[0], rotation[1], rotation[2], origin);
+ }
+
+}
diff --git a/generator/src/main/java/de/bluecolored/bluemap/entities/generator/ModelWriter.java b/generator/src/main/java/de/bluecolored/bluemap/entities/generator/ModelWriter.java
new file mode 100644
index 0000000..d891908
--- /dev/null
+++ b/generator/src/main/java/de/bluecolored/bluemap/entities/generator/ModelWriter.java
@@ -0,0 +1,120 @@
+/*
+ * This file is part of BlueMap, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) Blue (Lukas Rieger)
+ * Copyright (c) contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package de.bluecolored.bluemap.entities.generator;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.TypeAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.UncheckedIOException;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.StringJoiner;
+import java.util.function.Function;
+
+/**
+ * Serializes a {@link ModelJson}. Coordinates, uvs and faces are written inline. Numbers are rounded
+ */
+final class ModelWriter {
+
+ private static final int DECIMALS = 5;
+
+ private static final Gson GSON = gson();
+
+ private static Gson gson() {
+ GsonBuilder builder = new GsonBuilder();
+ register(builder, float[].class, ModelWriter::inline);
+ register(builder, int[].class, ModelWriter::inline);
+ register(builder, Float.class, ModelWriter::number);
+ register(builder, ModelJson.Face.class, ModelWriter::inline);
+ return builder.create();
+ }
+
+ private ModelWriter() {}
+
+ static String write(Geometry geometry, String texture, String minecraftVersion, String layer) {
+ return write(ModelJson.geometry(geometry, texture, minecraftVersion, layer));
+ }
+
+ /** A texture-variant of an already written model, which only overrides its texture. */
+ static String writeVariant(String parent, String texture) {
+ return write(ModelJson.variant(parent, texture));
+ }
+
+ private static String write(ModelJson model) {
+ StringWriter target = new StringWriter();
+ try (JsonWriter writer = new JsonWriter(target)) {
+ writer.setIndent("\t");
+ GSON.toJson(model, ModelJson.class, writer);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ return target + "\n";
+ }
+
+ private static String inline(ModelJson.Face face) {
+ return "{\"uv\": " + inline(face.uv()) + ", \"texture\": \"" + face.texture() + "\"}";
+ }
+
+ private static String inline(float[] values) {
+ StringJoiner joiner = new StringJoiner(", ", "[", "]");
+ for (float value : values) joiner.add(number(value));
+ return joiner.toString();
+ }
+
+ private static String inline(int[] values) {
+ StringJoiner joiner = new StringJoiner(", ", "[", "]");
+ for (int value : values) joiner.add(Integer.toString(value));
+ return joiner.toString();
+ }
+
+ private static String number(float value) {
+ BigDecimal rounded = BigDecimal.valueOf(value)
+ .setScale(DECIMALS, RoundingMode.HALF_UP)
+ .stripTrailingZeros();
+ return rounded.signum() == 0 ? "0" : rounded.toPlainString();
+ }
+
+ private static void register(GsonBuilder builder, Class type, Function format) {
+ builder.registerTypeAdapter(type, new TypeAdapter() {
+
+ @Override
+ public void write(JsonWriter out, T value) throws IOException {
+ out.jsonValue(format.apply(value));
+ }
+
+ @Override
+ public T read(JsonReader in) {
+ throw new UnsupportedOperationException();
+ }
+
+ }.nullSafe());
+ }
+
+}
diff --git a/generator/src/main/java/de/bluecolored/bluemap/entities/generator/Report.java b/generator/src/main/java/de/bluecolored/bluemap/entities/generator/Report.java
new file mode 100644
index 0000000..af2c64d
--- /dev/null
+++ b/generator/src/main/java/de/bluecolored/bluemap/entities/generator/Report.java
@@ -0,0 +1,91 @@
+/*
+ * This file is part of BlueMap, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) Blue (Lukas Rieger)
+ * Copyright (c) contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package de.bluecolored.bluemap.entities.generator;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.TreeSet;
+
+/**
+ * Collects what a generator-run did, so changes between minecraft-versions are visible without reading the whole diff
+ */
+final class Report {
+
+ private final TreeSet added = new TreeSet<>();
+ private final TreeSet changed = new TreeSet<>();
+ private final TreeSet removed = new TreeSet<>();
+ private final TreeSet overridden = new TreeSet<>();
+ private final TreeSet unresolved = new TreeSet<>();
+ private final TreeSet resolved = new TreeSet<>();
+ private final TreeSet empty = new TreeSet<>();
+ private final TreeSet warnings = new TreeSet<>();
+ private int unchanged;
+
+ void added(String path) { added.add(path); }
+ void changed(String path) { changed.add(path); }
+ void removed(String path) { removed.add(path); }
+ void overridden(String path) { overridden.add(path); }
+ void unresolved(String layer) { unresolved.add(layer); }
+ void resolved(String layer, String source, int variants) {
+ resolved.add(layer + " -> " + source + (variants > 0 ? " (" + variants + " variants)" : ""));
+ }
+ void empty(String layer) { empty.add(layer); }
+ void warning(String warning) { warnings.add(warning); }
+ void unchanged(String path) { unchanged++; }
+
+ String summary() {
+ return "models: %d added, %d changed, %d removed, %d unchanged | %d layers without texture, %d warnings"
+ .formatted(added.size(), changed.size(), removed.size(), unchanged, unresolved.size(), warnings.size());
+ }
+
+ void write(Path file) throws IOException {
+ List lines = new ArrayList<>();
+ lines.add(summary());
+
+ section(lines, "added", added);
+ section(lines, "changed", changed);
+ section(lines, "removed", removed);
+ section(lines, "overridden by generator/overrides", overridden);
+ section(lines, "no texture found (pin one in config/textures.json)", unresolved);
+ section(lines, "layers without any cube", empty);
+ section(lines, "warnings", warnings);
+ section(lines, "resolved textures", resolved);
+
+ if (file.getParent() != null) Files.createDirectories(file.getParent());
+ Files.write(file, lines, StandardCharsets.UTF_8);
+ }
+
+ private static void section(List lines, String title, TreeSet entries) {
+ if (entries.isEmpty()) return;
+ lines.add("");
+ lines.add("### " + title + " (" + entries.size() + ")");
+ lines.addAll(entries);
+ }
+
+}
diff --git a/generator/src/main/java/de/bluecolored/bluemap/entities/generator/TextureResolver.java b/generator/src/main/java/de/bluecolored/bluemap/entities/generator/TextureResolver.java
new file mode 100644
index 0000000..812bf2c
--- /dev/null
+++ b/generator/src/main/java/de/bluecolored/bluemap/entities/generator/TextureResolver.java
@@ -0,0 +1,316 @@
+/*
+ * This file is part of BlueMap, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) Blue (Lukas Rieger)
+ * Copyright (c) contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package de.bluecolored.bluemap.entities.generator;
+
+import com.google.gson.JsonElement;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+/**
+ * Finds the vanilla textures for a model-layer.
+ */
+final class TextureResolver {
+
+ private static final String BABY_SUFFIX = "_baby";
+ private static final String EQUIPMENT = "entity/equipment/";
+ private static final Set ARMOR_LAYERS = Set.of("helmet", "chestplate", "boots", "leggings");
+ private static final List ALIAS_SUFFIXES = List.of("_small", "_medium", "_big", "_large", "_no_hat");
+
+ private final McAssets assets;
+ private final Map textureOverrides;
+ private final Map> variantModelOverrides;
+ private final Map equipmentOverrides;
+ private final Set knownModels;
+ private final Map> dataVariants = new TreeMap<>();
+ private final List warnings = new ArrayList<>();
+
+ /** A texture-only variation of a model-layer, written as an additional model with the given name-suffix. */
+ record Variant(String suffix, String texture) {}
+
+ record Resolution(String texture, List variants, String source) {
+
+ boolean isEmpty() {
+ return texture == null && variants.isEmpty();
+ }
+
+ }
+
+ TextureResolver(
+ McAssets assets,
+ Map textureOverrides,
+ Map> variantModelOverrides,
+ Map equipmentOverrides,
+ Set knownModels
+ ) {
+ this.assets = assets;
+ this.textureOverrides = textureOverrides;
+ this.variantModelOverrides = variantModelOverrides;
+ this.equipmentOverrides = equipmentOverrides;
+ this.knownModels = knownModels;
+ indexDataVariants();
+ }
+
+ List warnings() {
+ return Collections.unmodifiableList(warnings);
+ }
+
+ Resolution resolve(String model, String layer) {
+ JsonElement override = textureOverrides.get(model + "#" + layer);
+ if (override != null) return fromOverride(override);
+
+ if (layer.equals("main")) {
+ List variants = dataVariants.get(model);
+ if (variants != null) return new Resolution(null, variants, "variant-registry");
+ }
+
+ List equipment = equipment(model, layer);
+ if (!equipment.isEmpty()) return new Resolution(null, equipment, "equipment");
+
+ for (String candidate : conventions(model, layer))
+ if (assets.hasTexture(candidate)) return new Resolution(candidate, List.of(), "convention");
+
+ String byName = byName(model, layer);
+ if (byName != null) return new Resolution(byName, List.of(), "texture-name");
+
+ List family = family(model, layer);
+ if (family.size() == 1) return new Resolution(family.getFirst().texture(), List.of(), "texture-family");
+ if (!family.isEmpty()) return new Resolution(null, family, "texture-family");
+
+ List fromClasses = fromRendererClasses(model, layer);
+ for (String candidate : fromClasses)
+ if (names(model, layer).contains(candidate.substring(candidate.lastIndexOf('/') + 1)))
+ return new Resolution(candidate, List.of(), "renderer-class");
+ if (fromClasses.size() == 1) return new Resolution(fromClasses.getFirst(), List.of(), "renderer-class");
+ if (!fromClasses.isEmpty()) {
+ List variants = new ArrayList<>();
+ fromClasses.forEach(texture -> variants.add(variant(texture, null)));
+ return new Resolution(null, variants, "renderer-class");
+ }
+
+ return new Resolution(null, List.of(), "unresolved");
+ }
+
+ private Resolution fromOverride(JsonElement override) {
+ if (!override.isJsonArray()) return new Resolution(override.getAsString(), List.of(), "config");
+
+ List variants = new ArrayList<>();
+ for (JsonElement texture : override.getAsJsonArray()) variants.add(variant(texture.getAsString(), null));
+ return new Resolution(null, variants, "config");
+ }
+
+ /** Armor, saddles and other equipment: one variant per material-texture of the matching equipment-folder. */
+ private List equipment(String model, String layer) {
+ String folder = equipmentFolder(model, layer);
+ if (folder == null) return List.of();
+
+ List variants = new ArrayList<>();
+ for (String texture : assets.texturesIn(EQUIPMENT + folder)) variants.add(variant(texture, null));
+ return variants;
+ }
+
+ private String equipmentFolder(String model, String layer) {
+ String override = equipmentOverrides.get(model + "#" + layer);
+ if (override != null) return override;
+
+ String folder = null;
+ if (ARMOR_LAYERS.contains(layer)) {
+ folder = layer.equals("leggings") ? "humanoid_leggings" : "humanoid";
+ if (model.endsWith(BABY_SUFFIX) && exists(folder + BABY_SUFFIX)) folder += BABY_SUFFIX;
+ } else if (layer.equals("saddle")) {
+ folder = model + "_saddle";
+ } else if (layer.equals("decor")) {
+ folder = base(model) + "_body";
+ } else if (layer.equals("main") && (model.endsWith("_armor") || model.endsWith("_harness"))) {
+ folder = model.substring(0, model.lastIndexOf('_')) + "_body";
+ }
+
+ return folder != null && exists(folder) ? folder : null;
+ }
+
+ private boolean exists(String folder) {
+ return !assets.texturesIn(EQUIPMENT + folder).isEmpty();
+ }
+
+ private List conventions(String model, String layer) {
+ String base = base(model);
+ List candidates = new ArrayList<>();
+
+ if (layer.equals("main")) {
+ candidates.add("entity/" + base + "/" + model);
+ candidates.add("entity/" + model + "/" + model);
+ candidates.add("entity/" + model);
+ candidates.add("entity/" + base + "/" + base);
+ candidates.add("entity/" + base);
+ } else {
+ candidates.add("entity/" + base + "/" + model + "_" + layer);
+ candidates.add("entity/" + base + "/" + base + "_" + layer);
+ candidates.add("entity/" + model + "/" + model + "_" + layer);
+ candidates.add("entity/" + base + "/" + layer);
+ candidates.add("entity/" + model + "/" + layer);
+ candidates.add("entity/" + layer);
+ }
+
+ return candidates;
+ }
+
+ /** Vanilla often keeps a texture in the folder of its entity-family ({@code entity/zombie/husk}), so search by name. */
+ private String byName(String model, String layer) {
+ for (String name : names(model, layer)) {
+ List textures = assets.texturesNamed(name);
+ if (textures.size() == 1) return textures.getFirst();
+ }
+ return null;
+ }
+
+ /** The texture file-names an entity-model may use, most specific first. */
+ private List names(String model, String layer) {
+ List names = new ArrayList<>();
+ for (String alias : aliases(model)) {
+ if (layer.equals("main")) {
+ names.add(alias);
+ } else {
+ names.add(alias + "_" + layer);
+ names.add(alias + "_" + layer + "_layer");
+ }
+ }
+ return names;
+ }
+
+ /** The model-id plus the shortened forms minecraft uses */
+ private static List aliases(String model) {
+ Set aliases = new LinkedHashSet<>();
+ aliases.add(model);
+ aliases.add(base(model));
+
+ for (String alias : List.copyOf(aliases)) {
+ for (String suffix : ALIAS_SUFFIXES)
+ if (alias.endsWith(suffix)) aliases.add(alias.substring(0, alias.length() - suffix.length()));
+ }
+ return List.copyOf(aliases);
+ }
+
+ /**
+ * Variants that are hard-coded in minecraft instead of being data-driven (axolotl, mooshroom, horse, ...)
+ */
+ private List family(String model, String layer) {
+ boolean baby = model.endsWith(BABY_SUFFIX);
+
+ for (String name : names(model, layer)) {
+ List variants = new ArrayList<>();
+ for (String texture : assets.texturesStartingWith(name + "_")) {
+ if (texture.endsWith(BABY_SUFFIX) != baby) continue;
+
+ String suffix = texture.substring(texture.lastIndexOf('/') + 1 + name.length() + 1);
+ if (baby) suffix = suffix.substring(0, suffix.length() - BABY_SUFFIX.length());
+ variants.add(new Variant(suffix, texture));
+ }
+ if (!variants.isEmpty()) return variants;
+ }
+ return List.of();
+ }
+
+ private List fromRendererClasses(String model, String layer) {
+ List names = aliases(model).stream().map(TextureResolver::camel).toList();
+ String layerName = camel(layer);
+
+ Set candidates = new LinkedHashSet<>();
+ assets.rendererTextures().forEach((className, textures) -> {
+ if (names.stream().noneMatch(className::startsWith)) return;
+
+ boolean matchesLayer = layer.equals("main")
+ ? names.stream().anyMatch(name -> className.equals(name + "Renderer"))
+ : className.contains(layerName);
+ if (!matchesLayer) return;
+
+ textures.forEach(texture -> {
+ String id = texture.substring("textures/".length());
+ if (assets.hasTexture(id)) candidates.add(id);
+ });
+ });
+
+ return List.copyOf(candidates);
+ }
+
+ private void indexDataVariants() {
+ assets.variants().forEach((entity, variants) -> {
+ for (McAssets.Variant variant : variants) {
+ String adultModel = targetModel(entity, variant.model());
+ if (adultModel == null) {
+ if (knownModels.contains(entity))
+ warnings.add("variant " + entity + "/" + variant.name() + ": no model '" + variant.model() + "'");
+ continue;
+ }
+
+ register(adultModel, variant, variant.adult());
+ register(adultModel + BABY_SUFFIX, variant, variant.baby().isEmpty() ? variant.adult() : variant.baby());
+ }
+ });
+ }
+
+ private void register(String model, McAssets.Variant variant, Map textures) {
+ if (!knownModels.contains(model)) return;
+
+ List list = dataVariants.computeIfAbsent(model, key -> new ArrayList<>());
+ textures.forEach((sub, texture) ->
+ list.add(new Variant(sub.isEmpty() ? variant.name() : variant.name() + "_" + sub, texture)));
+ }
+
+ private String targetModel(String entity, String model) {
+ if (model == null) return knownModels.contains(entity) ? entity : null;
+
+ String override = variantModelOverrides.getOrDefault(entity, Map.of()).get(model);
+ if (override != null) return override;
+
+ for (String candidate : List.of(model + "_" + entity, entity + "_" + model))
+ if (knownModels.contains(candidate)) return candidate;
+
+ return null;
+ }
+
+ private static Variant variant(String texture, String suffix) {
+ if (suffix != null) return new Variant(suffix, texture);
+ return new Variant(texture.substring(texture.lastIndexOf('/') + 1), texture);
+ }
+
+ private static String base(String model) {
+ return model.endsWith(BABY_SUFFIX) ? model.substring(0, model.length() - BABY_SUFFIX.length()) : model;
+ }
+
+ private static String camel(String name) {
+ StringBuilder camel = new StringBuilder();
+ for (String part : name.split("[_/]")) {
+ if (part.isEmpty()) continue;
+ camel.append(Character.toUpperCase(part.charAt(0))).append(part.substring(1));
+ }
+ return camel.toString();
+ }
+
+}
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 059ff09..2f1e384 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -4,3 +4,5 @@ pluginManagement {
mavenCentral()
}
}
+
+include("generator")
From 7080e1681717812189ff96c97b583fb44f480a86 Mon Sep 17 00:00:00 2001
From: miraculixx
Date: Tue, 11 Aug 2026 01:28:56 +0200
Subject: [PATCH 04/34] add demo datapack, slightly faster than manually
placing each mob
---
.../function/group/aquatic.mcfunction | 14 ++++++++
.../function/group/armadillo.mcfunction | 5 +++
.../function/group/axolotl.mcfunction | 7 ++++
.../bme_test/function/group/bee.mcfunction | 5 +++
.../bme_test/function/group/camel.mcfunction | 5 +++
.../bme_test/function/group/cat.mcfunction | 15 ++++++++
.../function/group/chested_horse.mcfunction | 7 ++++
.../function/group/chicken.mcfunction | 7 ++++
.../function/group/copper_golem.mcfunction | 5 +++
.../bme_test/function/group/cow.mcfunction | 7 ++++
.../bme_test/function/group/fox.mcfunction | 7 ++++
.../bme_test/function/group/frog.mcfunction | 4 +++
.../bme_test/function/group/ghast.mcfunction | 4 +++
.../bme_test/function/group/golem.mcfunction | 4 +++
.../function/group/horse_baby.mcfunction | 8 +++++
.../group/horse_markings_0.mcfunction | 8 +++++
.../group/horse_markings_1.mcfunction | 8 +++++
.../group/horse_markings_2.mcfunction | 8 +++++
.../group/horse_markings_3.mcfunction | 8 +++++
.../group/horse_markings_4.mcfunction | 8 +++++
.../bme_test/function/group/llama.mcfunction | 24 +++++++++++++
.../bme_test/function/group/misc.mcfunction | 5 +++
.../bme_test/function/group/ocelot.mcfunction | 4 +++
.../bme_test/function/group/pig.mcfunction | 8 +++++
.../bme_test/function/group/sheep.mcfunction | 17 +++++++++
.../function/group/sheep_states.mcfunction | 8 +++++
.../function/group/skeleton.mcfunction | 6 ++++
.../function/group/trader_llama.mcfunction | 7 ++++
.../function/group/tropical_fish.mcfunction | 10 ++++++
.../function/group/undead_horse.mcfunction | 5 +++
.../bme_test/function/group/zombie.mcfunction | 7 ++++
.../data/bme_test/function/kill.mcfunction | 2 ++
.../bme_test/function/platform.mcfunction | 3 ++
.../data/bme_test/function/spawn.mcfunction | 36 +++++++++++++++++++
.../datapack/bluemap-entity-test/pack.mcmeta | 9 +++++
35 files changed, 295 insertions(+)
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/aquatic.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/armadillo.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/axolotl.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/bee.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/camel.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/cat.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/chested_horse.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/chicken.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/copper_golem.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/cow.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/fox.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/frog.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/ghast.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/golem.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/horse_baby.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/horse_markings_0.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/horse_markings_1.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/horse_markings_2.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/horse_markings_3.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/horse_markings_4.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/llama.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/misc.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/ocelot.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/pig.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/sheep.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/sheep_states.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/skeleton.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/trader_llama.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/tropical_fish.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/undead_horse.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/zombie.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/kill.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/platform.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/spawn.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/pack.mcmeta
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/aquatic.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/aquatic.mcfunction
new file mode 100644
index 0000000..d78f24e
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/aquatic.mcfunction
@@ -0,0 +1,14 @@
+# aquatic (13 entities)
+summon minecraft:squid ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:squid ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Age:-24000}
+summon minecraft:glow_squid ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:glow_squid ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Age:-24000}
+summon minecraft:dolphin ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:dolphin ~15 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Age:-24000}
+summon minecraft:cod ~18 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:salmon ~21 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:pufferfish ~24 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],PuffState:0}
+summon minecraft:pufferfish ~27 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],PuffState:1}
+summon minecraft:pufferfish ~30 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],PuffState:2}
+summon minecraft:guardian ~33 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:tadpole ~36 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/armadillo.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/armadillo.mcfunction
new file mode 100644
index 0000000..aa79a11
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/armadillo.mcfunction
@@ -0,0 +1,5 @@
+# armadillo (4 entities)
+summon minecraft:armadillo ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],state:"idle"}
+summon minecraft:armadillo ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],state:"scared"}
+summon minecraft:armadillo ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],state:"idle",Age:-24000}
+summon minecraft:armadillo ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],state:"scared",Age:-24000}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/axolotl.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/axolotl.mcfunction
new file mode 100644
index 0000000..9e216b8
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/axolotl.mcfunction
@@ -0,0 +1,7 @@
+# axolotl (6 entities)
+summon minecraft:axolotl ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0}
+summon minecraft:axolotl ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:1}
+summon minecraft:axolotl ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:2}
+summon minecraft:axolotl ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:3}
+summon minecraft:axolotl ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:4}
+summon minecraft:axolotl ~15 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,Age:-24000}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/bee.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/bee.mcfunction
new file mode 100644
index 0000000..e973df6
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/bee.mcfunction
@@ -0,0 +1,5 @@
+# bee (4 entities)
+summon minecraft:bee ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:bee ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],HasNectar:1}
+summon minecraft:bee ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],HasStung:1b}
+summon minecraft:bee ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Age:-24000}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/camel.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/camel.mcfunction
new file mode 100644
index 0000000..82c588d
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/camel.mcfunction
@@ -0,0 +1,5 @@
+# camel (4 entities)
+summon minecraft:camel ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:camel ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Age:-24000}
+summon minecraft:camel_husk ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:camel_husk ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Age:-24000}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/cat.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/cat.mcfunction
new file mode 100644
index 0000000..eda3c59
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/cat.mcfunction
@@ -0,0 +1,15 @@
+# cat (14 entities)
+summon minecraft:cat ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:tabby"}
+summon minecraft:cat ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:black"}
+summon minecraft:cat ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:red"}
+summon minecraft:cat ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:siamese"}
+summon minecraft:cat ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:british_shorthair"}
+summon minecraft:cat ~15 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:calico"}
+summon minecraft:cat ~18 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:persian"}
+summon minecraft:cat ~21 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:ragdoll"}
+summon minecraft:cat ~24 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:white"}
+summon minecraft:cat ~27 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:jellie"}
+summon minecraft:cat ~30 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:all_black"}
+summon minecraft:cat ~33 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:tabby",Sitting:1b}
+summon minecraft:cat ~36 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:tabby",Age:-24000}
+summon minecraft:cat ~39 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:calico",Age:-24000}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/chested_horse.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/chested_horse.mcfunction
new file mode 100644
index 0000000..3f5b855
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/chested_horse.mcfunction
@@ -0,0 +1,7 @@
+# chested_horse (6 entities)
+summon minecraft:donkey ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:donkey ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],ChestedHorse:1b}
+summon minecraft:donkey ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Age:-24000}
+summon minecraft:mule ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:mule ~16 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],ChestedHorse:1b}
+summon minecraft:mule ~20 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Age:-24000}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/chicken.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/chicken.mcfunction
new file mode 100644
index 0000000..298fdc8
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/chicken.mcfunction
@@ -0,0 +1,7 @@
+# chicken (6 entities)
+summon minecraft:chicken ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:temperate"}
+summon minecraft:chicken ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:warm"}
+summon minecraft:chicken ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:cold"}
+summon minecraft:chicken ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:temperate",Age:-24000}
+summon minecraft:chicken ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:warm",Age:-24000}
+summon minecraft:chicken ~15 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:cold",Age:-24000}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/copper_golem.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/copper_golem.mcfunction
new file mode 100644
index 0000000..3c1fcc2
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/copper_golem.mcfunction
@@ -0,0 +1,5 @@
+# copper_golem (4 entities)
+summon minecraft:copper_golem ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],weather_state:"unaffected"}
+summon minecraft:copper_golem ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],weather_state:"exposed"}
+summon minecraft:copper_golem ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],weather_state:"weathered"}
+summon minecraft:copper_golem ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],weather_state:"oxidized"}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/cow.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/cow.mcfunction
new file mode 100644
index 0000000..f14f47d
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/cow.mcfunction
@@ -0,0 +1,7 @@
+# cow (6 entities)
+summon minecraft:cow ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:temperate"}
+summon minecraft:cow ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:warm"}
+summon minecraft:cow ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:cold"}
+summon minecraft:cow ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:temperate",Age:-24000}
+summon minecraft:cow ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:warm",Age:-24000}
+summon minecraft:cow ~15 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:cold",Age:-24000}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/fox.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/fox.mcfunction
new file mode 100644
index 0000000..2c98219
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/fox.mcfunction
@@ -0,0 +1,7 @@
+# fox (6 entities)
+summon minecraft:fox ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Type:"red"}
+summon minecraft:fox ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Type:"snow"}
+summon minecraft:fox ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Type:"red",Sitting:1b}
+summon minecraft:fox ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Type:"red",Sleeping:1b}
+summon minecraft:fox ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Type:"red",Age:-24000}
+summon minecraft:fox ~15 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Type:"snow",Age:-24000}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/frog.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/frog.mcfunction
new file mode 100644
index 0000000..70d006e
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/frog.mcfunction
@@ -0,0 +1,4 @@
+# frog (3 entities)
+summon minecraft:frog ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:temperate"}
+summon minecraft:frog ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:warm"}
+summon minecraft:frog ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:cold"}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/ghast.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/ghast.mcfunction
new file mode 100644
index 0000000..6a6a2b5
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/ghast.mcfunction
@@ -0,0 +1,4 @@
+# ghast (3 entities)
+summon minecraft:ghast ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:happy_ghast ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:happy_ghast ~24 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Age:-24000}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/golem.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/golem.mcfunction
new file mode 100644
index 0000000..9ffbc50
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/golem.mcfunction
@@ -0,0 +1,4 @@
+# golem (3 entities)
+summon minecraft:snow_golem ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Pumpkin:1b}
+summon minecraft:snow_golem ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Pumpkin:0b}
+summon minecraft:iron_golem ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/horse_baby.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/horse_baby.mcfunction
new file mode 100644
index 0000000..78e2df0
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/horse_baby.mcfunction
@@ -0,0 +1,8 @@
+# horse_baby (7 entities)
+summon minecraft:horse ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,Age:-24000}
+summon minecraft:horse ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:1,Age:-24000}
+summon minecraft:horse ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:2,Age:-24000}
+summon minecraft:horse ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:3,Age:-24000}
+summon minecraft:horse ~16 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:4,Age:-24000}
+summon minecraft:horse ~20 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:5,Age:-24000}
+summon minecraft:horse ~24 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:6,Age:-24000}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/horse_markings_0.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/horse_markings_0.mcfunction
new file mode 100644
index 0000000..ad41d39
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/horse_markings_0.mcfunction
@@ -0,0 +1,8 @@
+# horse_markings_0 (7 entities)
+summon minecraft:horse ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0}
+summon minecraft:horse ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:1}
+summon minecraft:horse ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:2}
+summon minecraft:horse ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:3}
+summon minecraft:horse ~16 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:4}
+summon minecraft:horse ~20 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:5}
+summon minecraft:horse ~24 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:6}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/horse_markings_1.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/horse_markings_1.mcfunction
new file mode 100644
index 0000000..09200dc
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/horse_markings_1.mcfunction
@@ -0,0 +1,8 @@
+# horse_markings_1 (7 entities)
+summon minecraft:horse ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:256}
+summon minecraft:horse ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:257}
+summon minecraft:horse ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:258}
+summon minecraft:horse ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:259}
+summon minecraft:horse ~16 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:260}
+summon minecraft:horse ~20 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:261}
+summon minecraft:horse ~24 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:262}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/horse_markings_2.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/horse_markings_2.mcfunction
new file mode 100644
index 0000000..5558aa1
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/horse_markings_2.mcfunction
@@ -0,0 +1,8 @@
+# horse_markings_2 (7 entities)
+summon minecraft:horse ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:512}
+summon minecraft:horse ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:513}
+summon minecraft:horse ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:514}
+summon minecraft:horse ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:515}
+summon minecraft:horse ~16 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:516}
+summon minecraft:horse ~20 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:517}
+summon minecraft:horse ~24 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:518}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/horse_markings_3.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/horse_markings_3.mcfunction
new file mode 100644
index 0000000..a1698f9
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/horse_markings_3.mcfunction
@@ -0,0 +1,8 @@
+# horse_markings_3 (7 entities)
+summon minecraft:horse ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:768}
+summon minecraft:horse ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:769}
+summon minecraft:horse ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:770}
+summon minecraft:horse ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:771}
+summon minecraft:horse ~16 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:772}
+summon minecraft:horse ~20 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:773}
+summon minecraft:horse ~24 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:774}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/horse_markings_4.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/horse_markings_4.mcfunction
new file mode 100644
index 0000000..a0acca6
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/horse_markings_4.mcfunction
@@ -0,0 +1,8 @@
+# horse_markings_4 (7 entities)
+summon minecraft:horse ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:1024}
+summon minecraft:horse ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:1025}
+summon minecraft:horse ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:1026}
+summon minecraft:horse ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:1027}
+summon minecraft:horse ~16 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:1028}
+summon minecraft:horse ~20 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:1029}
+summon minecraft:horse ~24 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:1030}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/llama.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/llama.mcfunction
new file mode 100644
index 0000000..f49ccde
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/llama.mcfunction
@@ -0,0 +1,24 @@
+# llama (23 entities)
+summon minecraft:llama ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0}
+summon minecraft:llama ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:1}
+summon minecraft:llama ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:2}
+summon minecraft:llama ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:3}
+summon minecraft:llama ~16 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,ChestedHorse:1b}
+summon minecraft:llama ~20 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,Age:-24000}
+summon minecraft:llama ~24 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:2,Age:-24000}
+summon minecraft:llama ~28 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,equipment:{body:{id:"minecraft:white_carpet",count:1}}}
+summon minecraft:llama ~32 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,equipment:{body:{id:"minecraft:orange_carpet",count:1}}}
+summon minecraft:llama ~36 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,equipment:{body:{id:"minecraft:magenta_carpet",count:1}}}
+summon minecraft:llama ~40 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,equipment:{body:{id:"minecraft:light_blue_carpet",count:1}}}
+summon minecraft:llama ~44 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,equipment:{body:{id:"minecraft:yellow_carpet",count:1}}}
+summon minecraft:llama ~48 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,equipment:{body:{id:"minecraft:lime_carpet",count:1}}}
+summon minecraft:llama ~52 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,equipment:{body:{id:"minecraft:pink_carpet",count:1}}}
+summon minecraft:llama ~56 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,equipment:{body:{id:"minecraft:gray_carpet",count:1}}}
+summon minecraft:llama ~60 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,equipment:{body:{id:"minecraft:light_gray_carpet",count:1}}}
+summon minecraft:llama ~64 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,equipment:{body:{id:"minecraft:cyan_carpet",count:1}}}
+summon minecraft:llama ~68 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,equipment:{body:{id:"minecraft:purple_carpet",count:1}}}
+summon minecraft:llama ~72 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,equipment:{body:{id:"minecraft:blue_carpet",count:1}}}
+summon minecraft:llama ~76 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,equipment:{body:{id:"minecraft:brown_carpet",count:1}}}
+summon minecraft:llama ~80 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,equipment:{body:{id:"minecraft:green_carpet",count:1}}}
+summon minecraft:llama ~84 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,equipment:{body:{id:"minecraft:red_carpet",count:1}}}
+summon minecraft:llama ~88 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,equipment:{body:{id:"minecraft:black_carpet",count:1}}}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/misc.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/misc.mcfunction
new file mode 100644
index 0000000..399eec5
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/misc.mcfunction
@@ -0,0 +1,5 @@
+# misc (4 entities)
+summon minecraft:bat ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:allay ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:vex ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:breeze ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/ocelot.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/ocelot.mcfunction
new file mode 100644
index 0000000..36ba7d1
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/ocelot.mcfunction
@@ -0,0 +1,4 @@
+# ocelot (3 entities)
+summon minecraft:ocelot ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:ocelot ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Sitting:1}
+summon minecraft:ocelot ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Age:-24000}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/pig.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/pig.mcfunction
new file mode 100644
index 0000000..ffcf950
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/pig.mcfunction
@@ -0,0 +1,8 @@
+# pig (7 entities)
+summon minecraft:pig ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:temperate"}
+summon minecraft:pig ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:warm"}
+summon minecraft:pig ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:cold"}
+summon minecraft:pig ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:temperate",Age:-24000}
+summon minecraft:pig ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:warm",Age:-24000}
+summon minecraft:pig ~15 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:cold",Age:-24000}
+summon minecraft:pig ~18 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:temperate",Saddle:1b}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/sheep.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/sheep.mcfunction
new file mode 100644
index 0000000..de7ee98
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/sheep.mcfunction
@@ -0,0 +1,17 @@
+# sheep (16 entities)
+summon minecraft:sheep ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:0b}
+summon minecraft:sheep ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:1b}
+summon minecraft:sheep ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:2b}
+summon minecraft:sheep ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:3b}
+summon minecraft:sheep ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:4b}
+summon minecraft:sheep ~15 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:5b}
+summon minecraft:sheep ~18 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:6b}
+summon minecraft:sheep ~21 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:7b}
+summon minecraft:sheep ~24 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:8b}
+summon minecraft:sheep ~27 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:9b}
+summon minecraft:sheep ~30 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:10b}
+summon minecraft:sheep ~33 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:11b}
+summon minecraft:sheep ~36 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:12b}
+summon minecraft:sheep ~39 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:13b}
+summon minecraft:sheep ~42 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:14b}
+summon minecraft:sheep ~45 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:15b}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/sheep_states.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/sheep_states.mcfunction
new file mode 100644
index 0000000..a221624
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/sheep_states.mcfunction
@@ -0,0 +1,8 @@
+# sheep_states (7 entities)
+summon minecraft:sheep ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:0b,Sheared:1b}
+summon minecraft:sheep ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:15b,Sheared:1b}
+summon minecraft:sheep ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:14b,Sheared:1b}
+summon minecraft:sheep ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:0b,Age:-24000}
+summon minecraft:sheep ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:4b,Age:-24000}
+summon minecraft:sheep ~15 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:14b,Age:-24000}
+summon minecraft:sheep ~18 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:0b,Sheared:1b,Age:-24000}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/skeleton.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/skeleton.mcfunction
new file mode 100644
index 0000000..56fc6bb
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/skeleton.mcfunction
@@ -0,0 +1,6 @@
+# skeleton (5 entities)
+summon minecraft:skeleton ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:wither_skeleton ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:stray ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:bogged ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:parched ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/trader_llama.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/trader_llama.mcfunction
new file mode 100644
index 0000000..4ee7db9
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/trader_llama.mcfunction
@@ -0,0 +1,7 @@
+# trader_llama (6 entities)
+summon minecraft:trader_llama ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0}
+summon minecraft:trader_llama ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:1}
+summon minecraft:trader_llama ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:2}
+summon minecraft:trader_llama ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:3}
+summon minecraft:trader_llama ~16 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,ChestedHorse:1b}
+summon minecraft:trader_llama ~20 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,Age:-24000}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/tropical_fish.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/tropical_fish.mcfunction
new file mode 100644
index 0000000..0e7905f
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/tropical_fish.mcfunction
@@ -0,0 +1,10 @@
+# tropical_fish (9 entities)
+summon minecraft:tropical_fish ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0}
+summon minecraft:tropical_fish ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:1}
+summon minecraft:tropical_fish ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:65536}
+summon minecraft:tropical_fish ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:65537}
+summon minecraft:tropical_fish ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:16777216}
+summon minecraft:tropical_fish ~15 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:16777217}
+summon minecraft:tropical_fish ~18 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:50790656}
+summon minecraft:tropical_fish ~21 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:67108865}
+summon minecraft:tropical_fish ~24 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:117506305}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/undead_horse.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/undead_horse.mcfunction
new file mode 100644
index 0000000..134e25d
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/undead_horse.mcfunction
@@ -0,0 +1,5 @@
+# undead_horse (4 entities)
+summon minecraft:skeleton_horse ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:skeleton_horse ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Age:-24000}
+summon minecraft:zombie_horse ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:zombie_horse ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Age:-24000}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/zombie.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/zombie.mcfunction
new file mode 100644
index 0000000..b085d1e
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/zombie.mcfunction
@@ -0,0 +1,7 @@
+# zombie (6 entities)
+summon minecraft:zombie ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:zombie ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],IsBaby:1b}
+summon minecraft:husk ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:husk ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],IsBaby:1b}
+summon minecraft:drowned ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:drowned ~15 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],IsBaby:1b}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/kill.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/kill.mcfunction
new file mode 100644
index 0000000..4672431
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/kill.mcfunction
@@ -0,0 +1,2 @@
+
+kill @e[tag=bme_test]
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/platform.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/platform.mcfunction
new file mode 100644
index 0000000..2e80c79
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/platform.mcfunction
@@ -0,0 +1,3 @@
+
+fill ~-2 ~-1 ~-2 ~90 ~-1 ~147 minecraft:white_concrete
+fill ~-2 ~ ~-2 ~90 ~8 ~147 minecraft:air
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/spawn.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/spawn.mcfunction
new file mode 100644
index 0000000..8c86dac
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/spawn.mcfunction
@@ -0,0 +1,36 @@
+
+function bme_test:kill
+
+execute positioned ~ ~ ~0 run function bme_test:group/sheep
+execute positioned ~ ~ ~4 run function bme_test:group/sheep_states
+execute positioned ~ ~ ~8 run function bme_test:group/cow
+execute positioned ~ ~ ~12 run function bme_test:group/pig
+execute positioned ~ ~ ~16 run function bme_test:group/chicken
+execute positioned ~ ~ ~20 run function bme_test:group/cat
+execute positioned ~ ~ ~24 run function bme_test:group/ocelot
+execute positioned ~ ~ ~28 run function bme_test:group/fox
+execute positioned ~ ~ ~32 run function bme_test:group/frog
+execute positioned ~ ~ ~36 run function bme_test:group/axolotl
+execute positioned ~ ~ ~40 run function bme_test:group/bee
+execute positioned ~ ~ ~44 run function bme_test:group/llama
+execute positioned ~ ~ ~49 run function bme_test:group/trader_llama
+execute positioned ~ ~ ~54 run function bme_test:group/horse_markings_0
+execute positioned ~ ~ ~59 run function bme_test:group/horse_markings_1
+execute positioned ~ ~ ~64 run function bme_test:group/horse_markings_2
+execute positioned ~ ~ ~69 run function bme_test:group/horse_markings_3
+execute positioned ~ ~ ~74 run function bme_test:group/horse_markings_4
+execute positioned ~ ~ ~79 run function bme_test:group/horse_baby
+execute positioned ~ ~ ~84 run function bme_test:group/chested_horse
+execute positioned ~ ~ ~89 run function bme_test:group/undead_horse
+execute positioned ~ ~ ~94 run function bme_test:group/zombie
+execute positioned ~ ~ ~98 run function bme_test:group/skeleton
+execute positioned ~ ~ ~102 run function bme_test:group/armadillo
+execute positioned ~ ~ ~106 run function bme_test:group/camel
+execute positioned ~ ~ ~111 run function bme_test:group/copper_golem
+execute positioned ~ ~ ~115 run function bme_test:group/golem
+execute positioned ~ ~ ~120 run function bme_test:group/aquatic
+execute positioned ~ ~ ~124 run function bme_test:group/tropical_fish
+execute positioned ~ ~ ~128 run function bme_test:group/ghast
+execute positioned ~ ~ ~142 run function bme_test:group/misc
+
+tellraw @s {"text":"[bme] spawned 214 test-entities in 31 groups","color":"green"}
diff --git a/generator/datapack/bluemap-entity-test/pack.mcmeta b/generator/datapack/bluemap-entity-test/pack.mcmeta
new file mode 100644
index 0000000..58e5006
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/pack.mcmeta
@@ -0,0 +1,9 @@
+{
+ "pack": {
+ "description": "BlueMap entity test-scene: spawns every supported entity-variant on a grid",
+ "pack_format": 100,
+ "min_format": 48,
+ "max_format": 9999,
+ "supported_formats": { "min_inclusive": 48, "max_inclusive": 9999 }
+ }
+}
\ No newline at end of file
From 73ef79a2da9e8b2bb95d6d519214cbca440dd300 Mon Sep 17 00:00:00 2001
From: miraculixx
Date: Tue, 11 Aug 2026 20:18:55 +0200
Subject: [PATCH 05/34] Reflect changed path structure into renderer
---
generator/config/split-parts.json | 9 ++
generator/config/textures.json | 91 ++++++++++++++-----
.../bluemap/entities/generator/Geometry.java | 12 +++
.../bluemap/entities/generator/Main.java | 36 +++++++-
.../bluemap/entities/generator/ModelJson.java | 3 +-
.../entities/generator/ModelWriter.java | 5 +
.../entities/generator/TextureResolver.java | 57 ++++++++++--
.../entities/renderer/ArmadilloRenderer.java | 4 +-
.../entities/renderer/AxolotlRenderer.java | 4 +-
.../entities/renderer/BeeRenderer.java | 4 +-
.../entities/renderer/CamelRenderer.java | 8 +-
.../entities/renderer/CatRenderer.java | 4 +-
.../renderer/ChestedHorseRenderer.java | 35 ++++---
.../entities/renderer/ChickenRenderer.java | 10 +-
.../renderer/CopperGolemRenderer.java | 8 +-
.../entities/renderer/CowRenderer.java | 10 +-
.../entities/renderer/DolphinRenderer.java | 4 +-
.../entities/renderer/DrownedRenderer.java | 4 +-
.../entities/renderer/FoxRenderer.java | 8 +-
.../entities/renderer/FrogRenderer.java | 4 +-
.../entities/renderer/GhastRenderer.java | 6 +-
.../entities/renderer/HorseRenderer.java | 9 +-
.../renderer/HostileHorseRenderer.java | 8 +-
.../entities/renderer/HuskRenderer.java | 4 +-
.../entities/renderer/LlamaRenderer.java | 31 +++----
.../entities/renderer/OcelotRenderer.java | 4 +-
.../entities/renderer/PigRenderer.java | 10 +-
.../entities/renderer/SheepRenderer.java | 8 +-
.../entities/renderer/SkeletonRenderer.java | 10 +-
.../entities/renderer/SnowGolemRenderer.java | 2 +-
.../entities/renderer/SquidRenderer.java | 8 +-
.../renderer/TropicalFishRenderer.java | 2 +-
.../entities/renderer/ZombieRenderer.java | 4 +-
33 files changed, 271 insertions(+), 155 deletions(-)
create mode 100644 generator/config/split-parts.json
diff --git a/generator/config/split-parts.json b/generator/config/split-parts.json
new file mode 100644
index 0000000..c32bfa5
--- /dev/null
+++ b/generator/config/split-parts.json
@@ -0,0 +1,9 @@
+{
+ "donkey#main": { "chest": ["body/left_chest", "body/right_chest"] },
+ "mule#main": { "chest": ["body/left_chest", "body/right_chest"] },
+
+ "llama#main": { "chest": ["left_chest", "right_chest"] },
+ "llama_baby#main": { "chest": ["left_chest", "right_chest"] },
+ "trader_llama#main": { "chest": ["left_chest", "right_chest"] },
+ "trader_llama_baby#main": { "chest": ["left_chest", "right_chest"] }
+}
diff --git a/generator/config/textures.json b/generator/config/textures.json
index 3f494d6..cc16e34 100644
--- a/generator/config/textures.json
+++ b/generator/config/textures.json
@@ -28,28 +28,71 @@
"entity/fish/tropical_b_pattern_6"
],
- "copper_golem#main": [
- "entity/copper_golem/copper_golem",
- "entity/copper_golem/copper_golem_exposed",
- "entity/copper_golem/copper_golem_weathered",
- "entity/copper_golem/copper_golem_oxidized"
- ],
- "copper_golem_running#main": [
- "entity/copper_golem/copper_golem",
- "entity/copper_golem/copper_golem_exposed",
- "entity/copper_golem/copper_golem_weathered",
- "entity/copper_golem/copper_golem_oxidized"
- ],
- "copper_golem_sitting#main": [
- "entity/copper_golem/copper_golem",
- "entity/copper_golem/copper_golem_exposed",
- "entity/copper_golem/copper_golem_weathered",
- "entity/copper_golem/copper_golem_oxidized"
- ],
- "copper_golem_star#main": [
- "entity/copper_golem/copper_golem",
- "entity/copper_golem/copper_golem_exposed",
- "entity/copper_golem/copper_golem_weathered",
- "entity/copper_golem/copper_golem_oxidized"
- ]
+ "copper_golem#main": {
+ "unaffected": "entity/copper_golem/copper_golem",
+ "exposed": "entity/copper_golem/copper_golem_exposed",
+ "weathered": "entity/copper_golem/copper_golem_weathered",
+ "oxidized": "entity/copper_golem/copper_golem_oxidized"
+ },
+ "copper_golem_running#main": {
+ "unaffected": "entity/copper_golem/copper_golem",
+ "exposed": "entity/copper_golem/copper_golem_exposed",
+ "weathered": "entity/copper_golem/copper_golem_weathered",
+ "oxidized": "entity/copper_golem/copper_golem_oxidized"
+ },
+ "copper_golem_sitting#main": {
+ "unaffected": "entity/copper_golem/copper_golem",
+ "exposed": "entity/copper_golem/copper_golem_exposed",
+ "weathered": "entity/copper_golem/copper_golem_weathered",
+ "oxidized": "entity/copper_golem/copper_golem_oxidized"
+ },
+ "copper_golem_star#main": {
+ "unaffected": "entity/copper_golem/copper_golem",
+ "exposed": "entity/copper_golem/copper_golem_exposed",
+ "weathered": "entity/copper_golem/copper_golem_weathered",
+ "oxidized": "entity/copper_golem/copper_golem_oxidized"
+ },
+
+ "fox#main": {
+ "red": "entity/fox/fox",
+ "snow": "entity/fox/fox_snow"
+ },
+ "fox_baby#main": {
+ "red": "entity/fox/fox_baby",
+ "snow": "entity/fox/fox_snow_baby"
+ },
+
+ "camel#main": {
+ "camel": "entity/camel/camel",
+ "husk": "entity/camel/camel_husk"
+ },
+ "camel_baby#main": {
+ "camel": "entity/camel/camel_baby",
+ "husk": "entity/camel/camel_husk"
+ },
+
+ "llama#main": {
+ "creamy": "entity/llama/llama_creamy",
+ "white": "entity/llama/llama_white",
+ "brown": "entity/llama/llama_brown",
+ "gray": "entity/llama/llama_gray"
+ },
+ "llama_baby#main": {
+ "creamy": "entity/llama/llama_creamy_baby",
+ "white": "entity/llama/llama_white_baby",
+ "brown": "entity/llama/llama_brown_baby",
+ "gray": "entity/llama/llama_gray_baby"
+ },
+ "trader_llama#main": {
+ "creamy": "entity/llama/llama_creamy",
+ "white": "entity/llama/llama_white",
+ "brown": "entity/llama/llama_brown",
+ "gray": "entity/llama/llama_gray"
+ },
+ "trader_llama_baby#main": {
+ "creamy": "entity/llama/llama_creamy_baby",
+ "white": "entity/llama/llama_white_baby",
+ "brown": "entity/llama/llama_brown_baby",
+ "gray": "entity/llama/llama_gray_baby"
+ }
}
diff --git a/generator/src/main/java/de/bluecolored/bluemap/entities/generator/Geometry.java b/generator/src/main/java/de/bluecolored/bluemap/entities/generator/Geometry.java
index 6f17ce5..e160d0c 100644
--- a/generator/src/main/java/de/bluecolored/bluemap/entities/generator/Geometry.java
+++ b/generator/src/main/java/de/bluecolored/bluemap/entities/generator/Geometry.java
@@ -32,6 +32,18 @@
*/
record Geometry(int textureWidth, int textureHeight, List elements, List warnings) {
+ /** The same geometry with only the elements whose part-name matches one of the given part-paths. */
+ Geometry filter(List parts, boolean keepMatching) {
+ List filtered = elements.stream()
+ .filter(element -> matches(element.name(), parts) == keepMatching)
+ .toList();
+ return new Geometry(textureWidth, textureHeight, filtered, warnings);
+ }
+
+ private static boolean matches(String name, List parts) {
+ return parts.stream().anyMatch(part -> name.equals(part) || name.startsWith(part + "/") || name.startsWith(part + "_"));
+ }
+
/**
* @param rotation nullable, {x, y, z} in degrees, applied by bluemap as rotateYXZ around {@code rotationOrigin}
* @param faces direction ("north", "up", ...) to uv {x1, y1, x2, y2} in 16-space
diff --git a/generator/src/main/java/de/bluecolored/bluemap/entities/generator/Main.java b/generator/src/main/java/de/bluecolored/bluemap/entities/generator/Main.java
index e43229b..c5e4396 100644
--- a/generator/src/main/java/de/bluecolored/bluemap/entities/generator/Main.java
+++ b/generator/src/main/java/de/bluecolored/bluemap/entities/generator/Main.java
@@ -57,9 +57,11 @@ public final class Main {
private static final String CONFIG_TEXTURES = "textures.json";
private static final String CONFIG_VARIANT_MODELS = "variant-models.json";
private static final String CONFIG_EQUIPMENT = "equipment.json";
+ private static final String CONFIG_SPLIT_PARTS = "split-parts.json";
private final Arguments arguments;
private final Report report = new Report();
+ private Map>> splitParts = Map.of();
private Main(Arguments arguments) {
this.arguments = arguments;
@@ -76,6 +78,8 @@ private void run() throws IOException {
Map layers = new TreeMap<>(Main::compare);
layers.putAll(LayerDefinitions.createRoots());
+ splitParts = readConfig(CONFIG_SPLIT_PARTS, new TypeToken<>() {});
+
try (McAssets assets = new McAssets(arguments.clientJar)) {
Set models = new LinkedHashSet<>();
layers.keySet().forEach(layer -> models.add(layer.model().getPath()));
@@ -93,6 +97,11 @@ private void run() throws IOException {
for (Map.Entry entry : layers.entrySet())
generate(entry.getKey(), entry.getValue(), textures, written);
+ for (TextureResolver.Alias alias : textures.aliases()) {
+ if (!written.contains(alias.target() + ".json")) continue;
+ write(alias.path(), ModelWriter.writeAlias(alias.target()), written);
+ }
+
removeStale(written);
writeIndex(written);
}
@@ -107,7 +116,7 @@ private void generate(
) throws IOException {
String model = location.model().getPath();
String layer = location.layer();
- String key = location.model() + "#" + layer;
+ String key = model + "#" + layer;
if (!arguments.include.matcher(key).find()) return;
if (arguments.exclude != null && arguments.exclude.matcher(key).find()) return;
@@ -124,11 +133,30 @@ private void generate(
if (resolution.isEmpty()) report.unresolved(key);
else report.resolved(key, resolution.source(), resolution.variants().size());
- String base = model + "/" + layer;
- write(base, ModelWriter.write(geometry, resolution.texture(), arguments.minecraftVersion, key), written);
+ // parts minecraft only shows sometimes (the chest of a donkey, ...) become models of their own
+ Geometry remaining = geometry;
+ for (Map.Entry> group : splitParts.getOrDefault(key, Map.of()).entrySet()) {
+ Geometry parts = geometry.filter(group.getValue(), true);
+ if (parts.elements().isEmpty()) {
+ report.warning(key + ": no parts matched the split-group '" + group.getKey() + "'");
+ continue;
+ }
+
+ writeModel(model + "/" + group.getKey(), parts, resolution, key, written);
+ remaining = remaining.filter(group.getValue(), false);
+ }
+
+ writeModel(model + "/" + layer, remaining, resolution, key, written);
+ }
+
+ private void writeModel(
+ String path, Geometry geometry, TextureResolver.Resolution resolution,
+ String key, Set written
+ ) throws IOException {
+ write(path, ModelWriter.write(geometry, resolution.texture(), arguments.minecraftVersion, key), written);
for (TextureResolver.Variant variant : resolution.variants())
- write(base + "_" + variant.suffix(), ModelWriter.writeVariant(base, variant.texture()), written);
+ write(path + "_" + variant.suffix(), ModelWriter.writeVariant(path, variant.texture()), written);
}
private void write(String name, String generated, Set written) throws IOException {
diff --git a/generator/src/main/java/de/bluecolored/bluemap/entities/generator/ModelJson.java b/generator/src/main/java/de/bluecolored/bluemap/entities/generator/ModelJson.java
index 5126d50..6ec12c0 100644
--- a/generator/src/main/java/de/bluecolored/bluemap/entities/generator/ModelJson.java
+++ b/generator/src/main/java/de/bluecolored/bluemap/entities/generator/ModelJson.java
@@ -66,7 +66,8 @@ static ModelJson geometry(Geometry geometry, String texture, String minecraftVer
}
static ModelJson variant(String parent, String texture) {
- return new ModelJson(null, null, null, "minecraft:entity/" + parent, Map.of("0", texture), null);
+ Map textures = texture == null ? null : Map.of("0", texture);
+ return new ModelJson(null, null, null, "minecraft:entity/" + parent, textures, null);
}
private static Element element(Geometry.Element element) {
diff --git a/generator/src/main/java/de/bluecolored/bluemap/entities/generator/ModelWriter.java b/generator/src/main/java/de/bluecolored/bluemap/entities/generator/ModelWriter.java
index d891908..74da3f0 100644
--- a/generator/src/main/java/de/bluecolored/bluemap/entities/generator/ModelWriter.java
+++ b/generator/src/main/java/de/bluecolored/bluemap/entities/generator/ModelWriter.java
@@ -67,6 +67,11 @@ static String writeVariant(String parent, String texture) {
return write(ModelJson.variant(parent, texture));
}
+ /** A model that is nothing but a second name for an already written one. */
+ static String writeAlias(String target) {
+ return write(ModelJson.variant(target, null));
+ }
+
private static String write(ModelJson model) {
StringWriter target = new StringWriter();
try (JsonWriter writer = new JsonWriter(target)) {
diff --git a/generator/src/main/java/de/bluecolored/bluemap/entities/generator/TextureResolver.java b/generator/src/main/java/de/bluecolored/bluemap/entities/generator/TextureResolver.java
index 812bf2c..fe32581 100644
--- a/generator/src/main/java/de/bluecolored/bluemap/entities/generator/TextureResolver.java
+++ b/generator/src/main/java/de/bluecolored/bluemap/entities/generator/TextureResolver.java
@@ -50,11 +50,15 @@ final class TextureResolver {
private final Map equipmentOverrides;
private final Set knownModels;
private final Map> dataVariants = new TreeMap<>();
+ private final List aliases = new ArrayList<>();
private final List warnings = new ArrayList<>();
/** A texture-only variation of a model-layer, written as an additional model with the given name-suffix. */
record Variant(String suffix, String texture) {}
+ /** A model that only points to another one, so variants stay reachable under the entity they belong to. */
+ record Alias(String path, String target) {}
+
record Resolution(String texture, List variants, String source) {
boolean isEmpty() {
@@ -82,6 +86,10 @@ List warnings() {
return Collections.unmodifiableList(warnings);
}
+ List aliases() {
+ return Collections.unmodifiableList(aliases);
+ }
+
Resolution resolve(String model, String layer) {
JsonElement override = textureOverrides.get(model + "#" + layer);
if (override != null) return fromOverride(override);
@@ -118,11 +126,18 @@ Resolution resolve(String model, String layer) {
return new Resolution(null, List.of(), "unresolved");
}
+ /** A pinned texture: a single texture, an array (suffix = texture-name) or an object (suffix = key). */
private Resolution fromOverride(JsonElement override) {
- if (!override.isJsonArray()) return new Resolution(override.getAsString(), List.of(), "config");
-
List variants = new ArrayList<>();
- for (JsonElement texture : override.getAsJsonArray()) variants.add(variant(texture.getAsString(), null));
+
+ if (override.isJsonArray())
+ for (JsonElement texture : override.getAsJsonArray()) variants.add(variant(texture.getAsString(), null));
+ else if (override.isJsonObject())
+ override.getAsJsonObject().asMap()
+ .forEach((suffix, texture) -> variants.add(new Variant(suffix, texture.getAsString())));
+ else
+ return new Resolution(override.getAsString(), List.of(), "config");
+
return new Resolution(null, variants, "config");
}
@@ -262,25 +277,49 @@ private List fromRendererClasses(String model, String layer) {
private void indexDataVariants() {
assets.variants().forEach((entity, variants) -> {
for (McAssets.Variant variant : variants) {
- String adultModel = targetModel(entity, variant.model());
- if (adultModel == null) {
+ String model = targetModel(entity, variant.model());
+ if (model == null) {
if (knownModels.contains(entity))
warnings.add("variant " + entity + "/" + variant.name() + ": no model '" + variant.model() + "'");
continue;
}
- register(adultModel, variant, variant.adult());
- register(adultModel + BABY_SUFFIX, variant, variant.baby().isEmpty() ? variant.adult() : variant.baby());
+ Map adult = variant.adult();
+ Map baby = variant.baby().isEmpty() ? variant.adult() : variant.baby();
+
+ // not every variant-model has a baby-version (cold_pig has none), then the plain baby-model is used
+ String babyModel = model + BABY_SUFFIX;
+ if (!knownModels.contains(babyModel)) babyModel = entity + BABY_SUFFIX;
+
+ register(model, variant, adult);
+ register(babyModel, variant, baby);
+
+ // variants with an own model (cold_cow, cold_chicken, ...) also get an alias under the entity itself,
+ // so a renderer can always look up "entity//main_"
+ if (model.equals(entity)) continue;
+ alias(entity, model, variant, adult);
+ alias(entity + BABY_SUFFIX, babyModel, variant, baby);
}
});
}
+ private void alias(String model, String target, McAssets.Variant variant, Map textures) {
+ if (model.equals(target) || !knownModels.contains(model) || !knownModels.contains(target)) return;
+ textures.keySet().forEach(sub -> {
+ String suffix = suffix(variant, sub);
+ aliases.add(new Alias(model + "/main_" + suffix, target + "/main_" + suffix));
+ });
+ }
+
private void register(String model, McAssets.Variant variant, Map textures) {
if (!knownModels.contains(model)) return;
List list = dataVariants.computeIfAbsent(model, key -> new ArrayList<>());
- textures.forEach((sub, texture) ->
- list.add(new Variant(sub.isEmpty() ? variant.name() : variant.name() + "_" + sub, texture)));
+ textures.forEach((sub, texture) -> list.add(new Variant(suffix(variant, sub), texture)));
+ }
+
+ private static String suffix(McAssets.Variant variant, String sub) {
+ return sub.isEmpty() ? variant.name() : variant.name() + "_" + sub;
}
private String targetModel(String entity, String model) {
diff --git a/src/main/java/de/bluecolored/bluemap/entities/renderer/ArmadilloRenderer.java b/src/main/java/de/bluecolored/bluemap/entities/renderer/ArmadilloRenderer.java
index 8505996..c8f2df1 100644
--- a/src/main/java/de/bluecolored/bluemap/entities/renderer/ArmadilloRenderer.java
+++ b/src/main/java/de/bluecolored/bluemap/entities/renderer/ArmadilloRenderer.java
@@ -39,8 +39,8 @@
public class ArmadilloRenderer extends CustomResourceModelRenderer {
private final ResourcePath
- ARMADILLO_ADULT = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/armadillo/armadillo_adult"),
- ARMADILLO_BABY = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/armadillo/armadillo_baby"),
+ ARMADILLO_ADULT = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/armadillo/main"),
+ ARMADILLO_BABY = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/armadillo_baby/main"),
ARMADILLO_ADULT_SCARED = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/armadillo/armadillo_adult_scared"),
ARMADILLO_BABY_SCARED = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/armadillo/armadillo_baby_scared");
diff --git a/src/main/java/de/bluecolored/bluemap/entities/renderer/AxolotlRenderer.java b/src/main/java/de/bluecolored/bluemap/entities/renderer/AxolotlRenderer.java
index c149570..2415e52 100644
--- a/src/main/java/de/bluecolored/bluemap/entities/renderer/AxolotlRenderer.java
+++ b/src/main/java/de/bluecolored/bluemap/entities/renderer/AxolotlRenderer.java
@@ -46,10 +46,10 @@ public AxolotlRenderer(ResourcePack resourcePack, TextureGallery textureGallery,
public void render(Entity entity, BlockNeighborhood block, Part part, TileModelView tileModel) {
if (!(entity instanceof Axolotl axolotl)) return;
- // base model "entity/axolotl/color/axolotl_{age}_{variant}"
+ // base model "entity/axolotl{age}/main_{variant}"
boolean isBaby = axolotl.getAge() < 0;
String variantName = axolotl.getVariant().toString().toLowerCase();
- String modelPath = "entity/axolotl/color/axolotl_" + (isBaby ? "baby_" : "adult_") + variantName;
+ String modelPath = "entity/axolotl" + (isBaby ? "_baby" : "") + "/main_" + variantName;
ResourcePath model = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, modelPath);
// render chosen model
diff --git a/src/main/java/de/bluecolored/bluemap/entities/renderer/BeeRenderer.java b/src/main/java/de/bluecolored/bluemap/entities/renderer/BeeRenderer.java
index d1ef427..d81bfd3 100644
--- a/src/main/java/de/bluecolored/bluemap/entities/renderer/BeeRenderer.java
+++ b/src/main/java/de/bluecolored/bluemap/entities/renderer/BeeRenderer.java
@@ -39,8 +39,8 @@
public class BeeRenderer extends CustomResourceModelRenderer {
private final ResourcePath
- BEE_ADULT = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/bee/adult"),
- BEE_BABY = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/bee/baby");
+ BEE_ADULT = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/bee/main"),
+ BEE_BABY = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/bee_baby/main");
public BeeRenderer(ResourcePack resourcePack, TextureGallery textureGallery, RenderSettings renderSettings) {
super(resourcePack, textureGallery, renderSettings);
diff --git a/src/main/java/de/bluecolored/bluemap/entities/renderer/CamelRenderer.java b/src/main/java/de/bluecolored/bluemap/entities/renderer/CamelRenderer.java
index eea701b..eb498fd 100644
--- a/src/main/java/de/bluecolored/bluemap/entities/renderer/CamelRenderer.java
+++ b/src/main/java/de/bluecolored/bluemap/entities/renderer/CamelRenderer.java
@@ -40,10 +40,10 @@
public class CamelRenderer extends CustomResourceModelRenderer {
private final ResourcePath
- CAMEL_ADULT = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/camel/camel_adult"),
- CAMEL_BABY = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/camel/camel_baby"),
- CAMEL_HUSK_ADULT = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/camel/camel_husk_adult"),
- CAMEL_HUSK_BABY = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/camel/camel_husk_baby");
+ CAMEL_ADULT = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/camel/main_camel"),
+ CAMEL_BABY = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/camel_baby/main_camel"),
+ CAMEL_HUSK_ADULT = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/camel/main_husk"),
+ CAMEL_HUSK_BABY = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/camel_baby/main_husk");
public CamelRenderer(ResourcePack resourcePack, TextureGallery textureGallery, RenderSettings renderSettings) {
super(resourcePack, textureGallery, renderSettings);
diff --git a/src/main/java/de/bluecolored/bluemap/entities/renderer/CatRenderer.java b/src/main/java/de/bluecolored/bluemap/entities/renderer/CatRenderer.java
index 01205ab..296e274 100644
--- a/src/main/java/de/bluecolored/bluemap/entities/renderer/CatRenderer.java
+++ b/src/main/java/de/bluecolored/bluemap/entities/renderer/CatRenderer.java
@@ -46,8 +46,8 @@ public CatRenderer(ResourcePack resourcePack, TextureGallery textureGallery, Ren
public void render(Entity entity, BlockNeighborhood block, Part part, TileModelView tileModel) {
if (!(entity instanceof Cat cat)) return;
- // choose correct model "entity/cat/color/cat_{age}_{variant}"
- String modelPath = "entity/cat/color/cat_" + (cat.getAge() < 0 ? "baby_" : "adult_") + cat.getRawVariant();
+ // choose correct model "entity/cat{age}/main_{variant}"
+ String modelPath = "entity/cat" + (cat.getAge() < 0 ? "_baby" : "") + "/main_" + cat.getRawVariant();
ResourcePath model = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, modelPath);
// render chosen model
diff --git a/src/main/java/de/bluecolored/bluemap/entities/renderer/ChestedHorseRenderer.java b/src/main/java/de/bluecolored/bluemap/entities/renderer/ChestedHorseRenderer.java
index c081f86..9115198 100644
--- a/src/main/java/de/bluecolored/bluemap/entities/renderer/ChestedHorseRenderer.java
+++ b/src/main/java/de/bluecolored/bluemap/entities/renderer/ChestedHorseRenderer.java
@@ -39,12 +39,12 @@
public class ChestedHorseRenderer extends CustomResourceModelRenderer {
private final ResourcePath
- MULE_HORSE_ADULT = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/horse/color/horse_adult_mule"),
- MULE_HORSE_BABY = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/horse/color/horse_baby_mule"),
- DONKEY_HORSE_ADULT = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/horse/color/horse_adult_donkey"),
- DONKEY_HORSE_BABY = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/horse/color/horse_baby_donkey"),
- CHEST_ADULT = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/horse/horse_adult_chest"),
- CHEST_BABY = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/horse/horse_baby_chest");
+ MULE_HORSE_ADULT = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/mule/main"),
+ MULE_HORSE_BABY = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/mule_baby/main"),
+ DONKEY_HORSE_ADULT = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/donkey/main"),
+ DONKEY_HORSE_BABY = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/donkey_baby/main"),
+ MULE_CHEST = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/mule/chest"),
+ DONKEY_CHEST = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/donkey/chest");
public ChestedHorseRenderer(ResourcePack resourcePack, TextureGallery textureGallery, RenderSettings renderSettings) {
super(resourcePack, textureGallery, renderSettings);
@@ -55,28 +55,27 @@ public void render(Entity entity, BlockNeighborhood block, Part part, TileModelV
if (!(entity instanceof ChestedHorse horse)) return;
// render horse model
- ResourcePath model;
+ ResourcePath model, chestModel;
boolean isBaby = horse.getAge() < 0;
switch (horse) {
case Mule ignored -> {
- if (isBaby) model = MULE_HORSE_BABY;
- else model = MULE_HORSE_ADULT;
+ model = isBaby ? MULE_HORSE_BABY : MULE_HORSE_ADULT;
+ chestModel = MULE_CHEST;
}
case Donkey ignored -> {
- if (isBaby) model = DONKEY_HORSE_BABY;
- else model = DONKEY_HORSE_ADULT;
+ model = isBaby ? DONKEY_HORSE_BABY : DONKEY_HORSE_ADULT;
+ chestModel = DONKEY_CHEST;
+ }
+ default -> {
+ model = DONKEY_HORSE_ADULT;
+ chestModel = DONKEY_CHEST;
}
- default -> model = DONKEY_HORSE_ADULT;
}
super.render(entity, block, model.getResource(getModelProvider()), TintColorProvider.NO_TINT, tileModel);
- // render chest model if present
- if (horse.isChested()) {
- ResourcePath chestModel;
- if (isBaby) chestModel = CHEST_BABY;
- else chestModel = CHEST_ADULT;
+ // render chest model if present (babies never carry chests)
+ if (horse.isChested() && !isBaby)
super.render(entity, block, chestModel.getResource(getModelProvider()), TintColorProvider.NO_TINT, tileModel);
- }
// apply part transform
diff --git a/src/main/java/de/bluecolored/bluemap/entities/renderer/ChickenRenderer.java b/src/main/java/de/bluecolored/bluemap/entities/renderer/ChickenRenderer.java
index ecb6d5e..b9c3a8b 100644
--- a/src/main/java/de/bluecolored/bluemap/entities/renderer/ChickenRenderer.java
+++ b/src/main/java/de/bluecolored/bluemap/entities/renderer/ChickenRenderer.java
@@ -46,14 +46,8 @@ public ChickenRenderer(ResourcePack resourcePack, TextureGallery textureGallery,
public void render(Entity entity, BlockNeighborhood block, Part part, TileModelView tileModel) {
if (!(entity instanceof AgeVariantEntity chicken)) return;
- // craft model path based on "entity/chicken/{age}_{variant}"
- String modelPath = "entity/chicken/";
- if (chicken.getAge() < 0) {
- modelPath += "baby_";
- } else {
- modelPath += "adult_";
- }
- modelPath += chicken.getRawVariant();
+ // craft model path based on "entity/chicken{age}/main_{variant}"
+ String modelPath = "entity/chicken" + (chicken.getAge() < 0 ? "_baby" : "") + "/main_" + chicken.getRawVariant();
ResourcePath model = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, modelPath);
// render chosen model
diff --git a/src/main/java/de/bluecolored/bluemap/entities/renderer/CopperGolemRenderer.java b/src/main/java/de/bluecolored/bluemap/entities/renderer/CopperGolemRenderer.java
index 4276648..8764b12 100644
--- a/src/main/java/de/bluecolored/bluemap/entities/renderer/CopperGolemRenderer.java
+++ b/src/main/java/de/bluecolored/bluemap/entities/renderer/CopperGolemRenderer.java
@@ -39,10 +39,10 @@
public class CopperGolemRenderer extends CustomResourceModelRenderer {
private final ResourcePath
- GOLEM_UNAFFECTED = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/copper_golem/copper_golem_statue_alive"),
- GOLEM_OXIDIZED = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/copper_golem/oxidized_copper_golem_statue_alive"),
- GOLEM_WEATHERED = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/copper_golem/weathered_copper_golem_statue_alive"),
- GOLEM_EXPOSED = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/copper_golem/exposed_copper_golem_statue_alive");
+ GOLEM_UNAFFECTED = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/copper_golem/main_unaffected"),
+ GOLEM_OXIDIZED = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/copper_golem/main_oxidized"),
+ GOLEM_WEATHERED = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/copper_golem/main_weathered"),
+ GOLEM_EXPOSED = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/copper_golem/main_exposed");
public CopperGolemRenderer(ResourcePack resourcePack, TextureGallery textureGallery, RenderSettings renderSettings) {
super(resourcePack, textureGallery, renderSettings);
diff --git a/src/main/java/de/bluecolored/bluemap/entities/renderer/CowRenderer.java b/src/main/java/de/bluecolored/bluemap/entities/renderer/CowRenderer.java
index 97c41c6..707a4a8 100644
--- a/src/main/java/de/bluecolored/bluemap/entities/renderer/CowRenderer.java
+++ b/src/main/java/de/bluecolored/bluemap/entities/renderer/CowRenderer.java
@@ -46,14 +46,8 @@ public CowRenderer(ResourcePack resourcePack, TextureGallery textureGallery, Ren
public void render(Entity entity, BlockNeighborhood block, Part part, TileModelView tileModel) {
if (!(entity instanceof AgeVariantEntity cow)) return;
- // craft model path based on "entity/cow/{age}_{variant}"
- String modelPath = "entity/cow/";
- if (cow.getAge() < 0) {
- modelPath += "baby_";
- } else {
- modelPath += "adult_";
- }
- modelPath += cow.getRawVariant();
+ // craft model path based on "entity/cow{age}/main_{variant}"
+ String modelPath = "entity/cow" + (cow.getAge() < 0 ? "_baby" : "") + "/main_" + cow.getRawVariant();
ResourcePath model = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, modelPath);
// render chosen model
diff --git a/src/main/java/de/bluecolored/bluemap/entities/renderer/DolphinRenderer.java b/src/main/java/de/bluecolored/bluemap/entities/renderer/DolphinRenderer.java
index 3ab7dfc..03e9762 100644
--- a/src/main/java/de/bluecolored/bluemap/entities/renderer/DolphinRenderer.java
+++ b/src/main/java/de/bluecolored/bluemap/entities/renderer/DolphinRenderer.java
@@ -39,8 +39,8 @@
public class DolphinRenderer extends CustomResourceModelRenderer {
private final ResourcePath
- DOLPHIN_ADULT = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/dolphin/adult"),
- DOLPHIN_BABY = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/dolphin/baby");
+ DOLPHIN_ADULT = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/dolphin/main"),
+ DOLPHIN_BABY = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/dolphin_baby/main");
public DolphinRenderer(ResourcePack resourcePack, TextureGallery textureGallery, RenderSettings renderSettings) {
super(resourcePack, textureGallery, renderSettings);
diff --git a/src/main/java/de/bluecolored/bluemap/entities/renderer/DrownedRenderer.java b/src/main/java/de/bluecolored/bluemap/entities/renderer/DrownedRenderer.java
index 8f0e829..a420b2f 100644
--- a/src/main/java/de/bluecolored/bluemap/entities/renderer/DrownedRenderer.java
+++ b/src/main/java/de/bluecolored/bluemap/entities/renderer/DrownedRenderer.java
@@ -39,8 +39,8 @@
public class DrownedRenderer extends CustomResourceModelRenderer {
private final ResourcePath
- ZOMBIE_ADULT = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/zombie/drowned_adult"),
- ZOMBIE_BABY = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/zombie/drowned_baby");
+ ZOMBIE_ADULT = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/drowned/main"),
+ ZOMBIE_BABY = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/drowned_baby/main");
public DrownedRenderer(ResourcePack resourcePack, TextureGallery textureGallery, RenderSettings renderSettings) {
super(resourcePack, textureGallery, renderSettings);
diff --git a/src/main/java/de/bluecolored/bluemap/entities/renderer/FoxRenderer.java b/src/main/java/de/bluecolored/bluemap/entities/renderer/FoxRenderer.java
index 710322f..f06a429 100644
--- a/src/main/java/de/bluecolored/bluemap/entities/renderer/FoxRenderer.java
+++ b/src/main/java/de/bluecolored/bluemap/entities/renderer/FoxRenderer.java
@@ -39,10 +39,10 @@
public class FoxRenderer extends CustomResourceModelRenderer {
private final ResourcePath
- FOX_ADULT_RED = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/fox/adult"),
- FOX_ADULT_SNOW = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/fox/adult_snow"),
- FOX_BABY_RED = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/fox/baby"),
- FOX_BABY_SNOW = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/fox/baby_snow");
+ FOX_ADULT_RED = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/fox/main_red"),
+ FOX_ADULT_SNOW = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/fox/main_snow"),
+ FOX_BABY_RED = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/fox_baby/main_red"),
+ FOX_BABY_SNOW = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/fox_baby/main_snow");
public FoxRenderer(ResourcePack resourcePack, TextureGallery textureGallery, RenderSettings renderSettings) {
super(resourcePack, textureGallery, renderSettings);
diff --git a/src/main/java/de/bluecolored/bluemap/entities/renderer/FrogRenderer.java b/src/main/java/de/bluecolored/bluemap/entities/renderer/FrogRenderer.java
index 155483c..ba7b679 100644
--- a/src/main/java/de/bluecolored/bluemap/entities/renderer/FrogRenderer.java
+++ b/src/main/java/de/bluecolored/bluemap/entities/renderer/FrogRenderer.java
@@ -46,8 +46,8 @@ public FrogRenderer(ResourcePack resourcePack, TextureGallery textureGallery, Re
public void render(Entity entity, BlockNeighborhood block, Part part, TileModelView tileModel) {
if (!(entity instanceof AgeVariantEntity frog)) return;
- // craft model path based on "entity/frog/variant/frog_{variant}"
- String modelPath = "entity/frog/variant/frog_" + frog.getRawVariant();
+ // craft model path based on "entity/frog/main_{variant}"
+ String modelPath = "entity/frog/main_" + frog.getRawVariant();
ResourcePath model = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, modelPath);
// render chosen model
diff --git a/src/main/java/de/bluecolored/bluemap/entities/renderer/GhastRenderer.java b/src/main/java/de/bluecolored/bluemap/entities/renderer/GhastRenderer.java
index ac87500..b9ac0c0 100644
--- a/src/main/java/de/bluecolored/bluemap/entities/renderer/GhastRenderer.java
+++ b/src/main/java/de/bluecolored/bluemap/entities/renderer/GhastRenderer.java
@@ -41,9 +41,9 @@
public class GhastRenderer extends CustomResourceModelRenderer {
private final ResourcePath
- GHAST = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/ghast/ghast"),
- HAPPY_GHAST = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/ghast/happy_ghast_adult"),
- GHASTLING = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/ghast/happy_ghast_baby");
+ GHAST = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/ghast/main"),
+ HAPPY_GHAST = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/happy_ghast/main"),
+ GHASTLING = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/happy_ghast_baby/main");
public GhastRenderer(ResourcePack resourcePack, TextureGallery textureGallery, RenderSettings renderSettings) {
super(resourcePack, textureGallery, renderSettings);
diff --git a/src/main/java/de/bluecolored/bluemap/entities/renderer/HorseRenderer.java b/src/main/java/de/bluecolored/bluemap/entities/renderer/HorseRenderer.java
index 0ac8364..ca113af 100644
--- a/src/main/java/de/bluecolored/bluemap/entities/renderer/HorseRenderer.java
+++ b/src/main/java/de/bluecolored/bluemap/entities/renderer/HorseRenderer.java
@@ -48,8 +48,9 @@ public void render(Entity entity, BlockNeighborhood block, Part part, TileModelV
boolean isBaby = horse.getAge() < 0;
- // render base model "entity/horse/color/horse_{age}_{color}"
- String baseModelPath = "entity/horse/color/horse_" + (isBaby ? "baby_" : "adult_");
+ // render base model "entity/horse{age}/main_{color}"
+ String horseModel = "entity/horse" + (isBaby ? "_baby" : "");
+ String baseModelPath = horseModel + "/main_";
switch (horse.getBaseColor()) {
case 0 -> baseModelPath += "white";
case 1 -> baseModelPath += "creamy";
@@ -64,8 +65,8 @@ public void render(Entity entity, BlockNeighborhood block, Part part, TileModelV
super.render(entity, block, baseModel.getResource(getModelProvider()), TintColorProvider.NO_TINT, tileModel);
- // render markings model "entity/horse/markings/horse_markings_{age}_{markings}" if present
- String markingModelPath = "entity/horse/marking/horse_" + (isBaby ? "baby_" : "adult_");
+ // render markings model "entity/horse{age}/main_markings_{markings}" if present
+ String markingModelPath = horseModel + "/main_markings_";
switch (horse.getMarkings()) {
case 0 -> markingModelPath = null; // no markings
case 1 -> markingModelPath += "white";
diff --git a/src/main/java/de/bluecolored/bluemap/entities/renderer/HostileHorseRenderer.java b/src/main/java/de/bluecolored/bluemap/entities/renderer/HostileHorseRenderer.java
index c7339d2..2eef53b 100644
--- a/src/main/java/de/bluecolored/bluemap/entities/renderer/HostileHorseRenderer.java
+++ b/src/main/java/de/bluecolored/bluemap/entities/renderer/HostileHorseRenderer.java
@@ -41,10 +41,10 @@
public class HostileHorseRenderer extends CustomResourceModelRenderer {
private final ResourcePath
- ZOMBIE_HORSE_ADULT = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/horse/color/horse_adult_zombie"),
- ZOMBIE_HORSE_BABY = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/horse/color/horse_baby_zombie"),
- SKELETON_HORSE_ADULT = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/horse/color/horse_adult_skeleton"),
- SKELETON_HORSE_BABY = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/horse/color/horse_baby_skeleton");
+ ZOMBIE_HORSE_ADULT = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/zombie_horse/main"),
+ ZOMBIE_HORSE_BABY = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/zombie_horse_baby/main"),
+ SKELETON_HORSE_ADULT = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/skeleton_horse/main"),
+ SKELETON_HORSE_BABY = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/skeleton_horse_baby/main");
public HostileHorseRenderer(ResourcePack resourcePack, TextureGallery textureGallery, RenderSettings renderSettings) {
super(resourcePack, textureGallery, renderSettings);
diff --git a/src/main/java/de/bluecolored/bluemap/entities/renderer/HuskRenderer.java b/src/main/java/de/bluecolored/bluemap/entities/renderer/HuskRenderer.java
index e7014ab..821db0a 100644
--- a/src/main/java/de/bluecolored/bluemap/entities/renderer/HuskRenderer.java
+++ b/src/main/java/de/bluecolored/bluemap/entities/renderer/HuskRenderer.java
@@ -39,8 +39,8 @@
public class HuskRenderer extends CustomResourceModelRenderer {
private final ResourcePath
- ZOMBIE_ADULT = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/zombie/husk_adult"),
- ZOMBIE_BABY = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/zombie/husk_baby");
+ ZOMBIE_ADULT = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/husk/main"),
+ ZOMBIE_BABY = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/husk_baby/main");
public HuskRenderer(ResourcePack resourcePack, TextureGallery textureGallery, RenderSettings renderSettings) {
super(resourcePack, textureGallery, renderSettings);
diff --git a/src/main/java/de/bluecolored/bluemap/entities/renderer/LlamaRenderer.java b/src/main/java/de/bluecolored/bluemap/entities/renderer/LlamaRenderer.java
index e8b42cc..ebea90f 100644
--- a/src/main/java/de/bluecolored/bluemap/entities/renderer/LlamaRenderer.java
+++ b/src/main/java/de/bluecolored/bluemap/entities/renderer/LlamaRenderer.java
@@ -43,9 +43,6 @@
public class LlamaRenderer extends CustomResourceModelRenderer {
- private final ResourcePath
- LLAMA_CHEST = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/llama/llama_chest");
-
public LlamaRenderer(ResourcePack resourcePack, TextureGallery textureGallery, RenderSettings renderSettings) {
super(resourcePack, textureGallery, renderSettings);
}
@@ -57,28 +54,28 @@ public void render(Entity entity, BlockNeighborhood block, Part part, TileModelV
boolean isTraderLlama = entity instanceof TraderLlama;
boolean isBaby = llama.getAge() < 0;
- // base model "entity/llama/color/llama_{age}_{color}"
- String baseModelPath = "entity/llama/color/llama_" + (isBaby ? "baby_" : "adult_");
-
- switch (llama.getVariant()) {
- case CREAMY -> baseModelPath += "creamy";
- case WHITE -> baseModelPath += "white";
- case BROWN -> baseModelPath += "brown";
- case GRAY -> baseModelPath += "gray";
- }
+ // base model "entity/{llama|trader_llama}{age}/main_{color}"
+ String llamaModel = "entity/" + (isTraderLlama ? "trader_llama" : "llama") + (isBaby ? "_baby" : "");
+ String color = switch (llama.getVariant()) {
+ case CREAMY -> "creamy";
+ case WHITE -> "white";
+ case BROWN -> "brown";
+ case GRAY -> "gray";
+ };
- ResourcePath baseModel = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, baseModelPath);
+ ResourcePath baseModel = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, llamaModel + "/main_" + color);
super.render(entity, block, baseModel.getResource(getModelProvider()), TintColorProvider.NO_TINT, tileModel);
// chest model (only if adult)
if (llama.isWithChest() && !isBaby) {
- super.render(entity, block, LLAMA_CHEST.getResource(getModelProvider()), TintColorProvider.NO_TINT, tileModel);
+ ResourcePath chestModel = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, llamaModel + "/chest_" + color);
+ super.render(entity, block, chestModel.getResource(getModelProvider()), TintColorProvider.NO_TINT, tileModel);
}
- // decoration model "entity/llama/carpet/llama_{age}_{color}"
- String decorationModelPath = "entity/llama/carpet/llama_" + (isBaby ? "baby_" : "adult_");
+ // decoration model "entity/llama{age}/decor_{color}"
+ String decorationModelPath = "entity/llama" + (isBaby ? "_baby" : "") + "/decor_";
switch (
Optional.ofNullable(llama.getEquipment())
@@ -108,7 +105,7 @@ public void render(Entity entity, BlockNeighborhood block, Part part, TileModelV
if (decorationModelPath == null && isTraderLlama) {
// trader llama without carpet uses special decoration model
- decorationModelPath = "entity/llama/carpet/llama_" + (isBaby ? "baby" : "adult") + "_trader_llama";
+ decorationModelPath = "entity/llama" + (isBaby ? "_baby/decor_trader_llama_baby" : "/decor_trader_llama");
}
if (decorationModelPath != null) {
diff --git a/src/main/java/de/bluecolored/bluemap/entities/renderer/OcelotRenderer.java b/src/main/java/de/bluecolored/bluemap/entities/renderer/OcelotRenderer.java
index 1259688..67ec17b 100644
--- a/src/main/java/de/bluecolored/bluemap/entities/renderer/OcelotRenderer.java
+++ b/src/main/java/de/bluecolored/bluemap/entities/renderer/OcelotRenderer.java
@@ -39,8 +39,8 @@
public class OcelotRenderer extends CustomResourceModelRenderer {
private final ResourcePath
- OCELOT_ADULT = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/cat/color/cat_adult_ocelot"),
- OCELOT_BABY = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/cat/color/cat_baby_ocelot");
+ OCELOT_ADULT = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/ocelot/main"),
+ OCELOT_BABY = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/ocelot_baby/main");
public OcelotRenderer(ResourcePack resourcePack, TextureGallery textureGallery, RenderSettings renderSettings) {
super(resourcePack, textureGallery, renderSettings);
diff --git a/src/main/java/de/bluecolored/bluemap/entities/renderer/PigRenderer.java b/src/main/java/de/bluecolored/bluemap/entities/renderer/PigRenderer.java
index 1d44f56..fadee9a 100644
--- a/src/main/java/de/bluecolored/bluemap/entities/renderer/PigRenderer.java
+++ b/src/main/java/de/bluecolored/bluemap/entities/renderer/PigRenderer.java
@@ -46,14 +46,8 @@ public PigRenderer(ResourcePack resourcePack, TextureGallery textureGallery, Ren
public void render(Entity entity, BlockNeighborhood block, Part part, TileModelView tileModel) {
if (!(entity instanceof Pig pig)) return;
- // craft model path based on "entity/pig/{age}_{variant}"
- String modelPath = "entity/pig/";
- if (pig.getAge() < 0) {
- modelPath += "baby_";
- } else {
- modelPath += "adult_";
- }
- modelPath += pig.getRawVariant();
+ // craft model path based on "entity/pig{age}/main_{variant}"
+ String modelPath = "entity/pig" + (pig.getAge() < 0 ? "_baby" : "") + "/main_" + pig.getRawVariant();
ResourcePath model = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, modelPath);
// render chosen model
diff --git a/src/main/java/de/bluecolored/bluemap/entities/renderer/SheepRenderer.java b/src/main/java/de/bluecolored/bluemap/entities/renderer/SheepRenderer.java
index b60dd51..6fca3a1 100644
--- a/src/main/java/de/bluecolored/bluemap/entities/renderer/SheepRenderer.java
+++ b/src/main/java/de/bluecolored/bluemap/entities/renderer/SheepRenderer.java
@@ -39,10 +39,10 @@
public class SheepRenderer extends CustomResourceModelRenderer {
private final ResourcePath
- SHEEP_ADULT = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/sheep/adult"),
- SHEEP_BABY = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/sheep/baby"),
- SHEEP_ADULT_WOOL = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/sheep/adult_wool"),
- SHEEP_BABY_WOOL = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/sheep/baby_wool");
+ SHEEP_ADULT = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/sheep/main"),
+ SHEEP_BABY = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/sheep_baby/main"),
+ SHEEP_ADULT_WOOL = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/sheep/wool"),
+ SHEEP_BABY_WOOL = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/sheep_baby/wool");
public SheepRenderer(ResourcePack resourcePack, TextureGallery textureGallery, RenderSettings renderSettings) {
super(resourcePack, textureGallery, renderSettings);
diff --git a/src/main/java/de/bluecolored/bluemap/entities/renderer/SkeletonRenderer.java b/src/main/java/de/bluecolored/bluemap/entities/renderer/SkeletonRenderer.java
index cfbf845..708846e 100644
--- a/src/main/java/de/bluecolored/bluemap/entities/renderer/SkeletonRenderer.java
+++ b/src/main/java/de/bluecolored/bluemap/entities/renderer/SkeletonRenderer.java
@@ -39,11 +39,11 @@
public class SkeletonRenderer extends CustomResourceModelRenderer {
private final ResourcePath
- SKELETON = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/skeleton/skeleton"),
- WITHER_SKELETON = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/skeleton/wither_skeleton"),
- STRAY = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/skeleton/stray"),
- BOGGED = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/skeleton/bogged"),
- PARCHED = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/skeleton/parched");
+ SKELETON = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/skeleton/main"),
+ WITHER_SKELETON = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/wither_skeleton/main"),
+ STRAY = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/stray/main"),
+ BOGGED = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/bogged/main"),
+ PARCHED = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/parched/main");
public SkeletonRenderer(ResourcePack resourcePack, TextureGallery textureGallery, RenderSettings renderSettings) {
super(resourcePack, textureGallery, renderSettings);
diff --git a/src/main/java/de/bluecolored/bluemap/entities/renderer/SnowGolemRenderer.java b/src/main/java/de/bluecolored/bluemap/entities/renderer/SnowGolemRenderer.java
index 63f42a6..9fa12f4 100644
--- a/src/main/java/de/bluecolored/bluemap/entities/renderer/SnowGolemRenderer.java
+++ b/src/main/java/de/bluecolored/bluemap/entities/renderer/SnowGolemRenderer.java
@@ -39,7 +39,7 @@
public class SnowGolemRenderer extends CustomResourceModelRenderer {
private final ResourcePath
- SNOW_GOLEM = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/snow_golem");
+ SNOW_GOLEM = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/snow_golem/main");
public SnowGolemRenderer(ResourcePack resourcePack, TextureGallery textureGallery, RenderSettings renderSettings) {
super(resourcePack, textureGallery, renderSettings);
diff --git a/src/main/java/de/bluecolored/bluemap/entities/renderer/SquidRenderer.java b/src/main/java/de/bluecolored/bluemap/entities/renderer/SquidRenderer.java
index 40b21c2..15c0bfc 100644
--- a/src/main/java/de/bluecolored/bluemap/entities/renderer/SquidRenderer.java
+++ b/src/main/java/de/bluecolored/bluemap/entities/renderer/SquidRenderer.java
@@ -40,10 +40,10 @@
public class SquidRenderer extends CustomResourceModelRenderer {
private final ResourcePath
- SQUID_ADULT = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/squid/squid_adult"),
- SQUID_BABY = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/squid/squid_baby"),
- GLOW_SQUID_ADULT = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/squid/glow_squid_adult"),
- GLOW_SQUID_BABY = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/squid/glow_squid_baby");
+ SQUID_ADULT = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/squid/main"),
+ SQUID_BABY = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/squid_baby/main"),
+ GLOW_SQUID_ADULT = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/glow_squid/main"),
+ GLOW_SQUID_BABY = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/glow_squid_baby/main");
public SquidRenderer(ResourcePack resourcePack, TextureGallery textureGallery, RenderSettings renderSettings) {
super(resourcePack, textureGallery, renderSettings);
diff --git a/src/main/java/de/bluecolored/bluemap/entities/renderer/TropicalFishRenderer.java b/src/main/java/de/bluecolored/bluemap/entities/renderer/TropicalFishRenderer.java
index bdc8c6b..e6d9318 100644
--- a/src/main/java/de/bluecolored/bluemap/entities/renderer/TropicalFishRenderer.java
+++ b/src/main/java/de/bluecolored/bluemap/entities/renderer/TropicalFishRenderer.java
@@ -39,7 +39,7 @@
public class TropicalFishRenderer extends CustomResourceModelRenderer {
private final ResourcePath
- TROPICAL_FISH = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/tropical_fish/tropical_fish");
+ TROPICAL_FISH = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/tropical_fish_small/main");
public TropicalFishRenderer(ResourcePack resourcePack, TextureGallery textureGallery, RenderSettings renderSettings) {
super(resourcePack, textureGallery, renderSettings);
diff --git a/src/main/java/de/bluecolored/bluemap/entities/renderer/ZombieRenderer.java b/src/main/java/de/bluecolored/bluemap/entities/renderer/ZombieRenderer.java
index 4bea16f..7f87608 100644
--- a/src/main/java/de/bluecolored/bluemap/entities/renderer/ZombieRenderer.java
+++ b/src/main/java/de/bluecolored/bluemap/entities/renderer/ZombieRenderer.java
@@ -39,8 +39,8 @@
public class ZombieRenderer extends CustomResourceModelRenderer {
private final ResourcePath
- ZOMBIE_ADULT = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/zombie/zombie_adult"),
- ZOMBIE_BABY = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/zombie/zombie_baby");
+ ZOMBIE_ADULT = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/zombie/main"),
+ ZOMBIE_BABY = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/zombie_baby/main");
public ZombieRenderer(ResourcePack resourcePack, TextureGallery textureGallery, RenderSettings renderSettings) {
super(resourcePack, textureGallery, renderSettings);
From c6a5b43107c1fd314eeca2adc0bdc6da6400d517 Mon Sep 17 00:00:00 2001
From: miraculixx
Date: Tue, 11 Aug 2026 21:23:46 +0200
Subject: [PATCH 06/34] update bluemap
---
build.gradle.kts | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/build.gradle.kts b/build.gradle.kts
index e922ce4..784fcde 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -12,13 +12,13 @@ repositories {
}
dependencies {
- compileOnly ( "de.bluecolored:bluemap-core:5.13" )
- compileOnly ( "org.projectlombok:lombok:1.18.32" )
- annotationProcessor ( "org.projectlombok:lombok:1.18.32" )
+ compileOnly ( "de.bluecolored:bluemap-core:5.23" )
+ compileOnly ( "org.projectlombok:lombok:1.18.46" )
+ annotationProcessor ( "org.projectlombok:lombok:1.18.46" )
}
java {
- toolchain.languageVersion = JavaLanguageVersion.of(21)
+ toolchain.languageVersion = JavaLanguageVersion.of(25)
withSourcesJar()
}
From 5260c669d188599525769e4329d05257c016dc0d Mon Sep 17 00:00:00 2001
From: miraculixx
Date: Tue, 11 Aug 2026 21:24:00 +0200
Subject: [PATCH 07/34] add a test server that loads everything needed
---
.gitignore | 2 +
generator/build.gradle.kts | 93 +++++++++++++++++---------------------
2 files changed, 44 insertions(+), 51 deletions(-)
diff --git a/.gitignore b/.gitignore
index 0bb893c..a0a1176 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,5 @@
build
gradle.properties
logs
+run
+run-client
diff --git a/generator/build.gradle.kts b/generator/build.gradle.kts
index 5818057..451624f 100644
--- a/generator/build.gradle.kts
+++ b/generator/build.gradle.kts
@@ -1,73 +1,44 @@
-import groovy.json.JsonSlurper
-import java.net.URI
-
plugins {
java
+ id("net.fabricmc.fabric-loom") version "1.17.19"
}
val mcVersion = (findProperty("mcVersion") as String?) ?: "26.2"
-val mcDir = layout.buildDirectory.dir("minecraft/$mcVersion")
+val loaderVersion = (findProperty("loaderVersion") as String?) ?: "0.19.3"
+val serverDir = layout.projectDirectory.dir("run")
-// the generator is a tool, not part of the addon
-val requested = gradle.startParameter.taskNames.any { it.startsWith(":generator") || it == "generateEntityModels" }
-val available = { requested || mcDir.get().file("client.jar").asFile.isFile }
+val fabricApiVersion = "0.157.0+26.2"
+val bluemapVersion = "5.23-fabric"
java {
toolchain.languageVersion = JavaLanguageVersion.of(25)
}
-@Suppress("UNCHECKED_CAST")
-val downloadMinecraft by tasks.registering {
- description = "Downloads the vanilla minecraft client-jar and its libraries for version $mcVersion"
- val target = mcDir
- outputs.dir(target)
- onlyIf { available() }
- doLast {
- val dir = target.get().asFile
- val libs = dir.resolve("libs").apply { mkdirs() }
-
- fun fetch(url: String, file: File, size: Long) {
- if (file.isFile && file.length() == size) return
- logger.lifecycle("downloading ${file.name}")
- URI(url).toURL().openStream().use { input -> file.outputStream().use { input.copyTo(it) } }
+loom {
+ runs {
+ named("server") {
+ runDirectory = serverDir
+ jvmArguments.add("-Xmx2G")
}
-
- val manifest = JsonSlurper()
- .parse(URI("https://launchermeta.mojang.com/mc/game/version_manifest_v2.json").toURL()) as Map
- val entry = (manifest["versions"] as List>)
- .firstOrNull { it["id"] == mcVersion }
- ?: throw GradleException("unknown minecraft version: $mcVersion")
-
- val version = JsonSlurper().parse(URI(entry["url"] as String).toURL()) as Map
- val client = (version["downloads"] as Map)["client"] as Map
- fetch(client["url"] as String, dir.resolve("client.jar"), (client["size"] as Number).toLong())
-
- val wanted = mutableSetOf("client.jar")
- (version["libraries"] as List>).forEach { lib ->
- val name = lib["name"] as String
- if (name.contains("natives") || name.startsWith("org.lwjgl")) return@forEach
- val artifact = (lib["downloads"] as Map?)?.get("artifact") as Map? ?: return@forEach
- val file = libs.resolve(File(artifact["path"] as String).name)
- fetch(artifact["url"] as String, file, (artifact["size"] as Number).toLong())
- wanted += file.name
+ named("client") {
+ runDirectory = layout.projectDirectory.dir("run-client")
}
-
- libs.listFiles()?.forEach { if (it.name !in wanted) it.delete() }
}
}
-val minecraftFiles = files({
- val dir = mcDir.get().asFile
- listOf(dir.resolve("client.jar")) + (dir.resolve("libs").listFiles()?.sorted() ?: emptyList())
-})
+repositories {
+ maven("https://api.modrinth.com/maven")
+}
dependencies {
- compileOnly(minecraftFiles)
+ minecraft("com.mojang:minecraft:$mcVersion")
+ implementation("net.fabricmc:fabric-loader:$loaderVersion")
+
+ localRuntime("net.fabricmc.fabric-api:fabric-api:$fabricApiVersion")
+ localRuntime("maven.modrinth:bluemap:$bluemapVersion")
}
tasks.compileJava {
- dependsOn(downloadMinecraft)
- onlyIf { available() }
options.encoding = "utf-8"
}
@@ -77,7 +48,7 @@ tasks.register("generateEntityModels") {
dependsOn(tasks.compileJava)
javaLauncher = javaToolchains.launcherFor { languageVersion = JavaLanguageVersion.of(25) }
- classpath = sourceSets.main.get().runtimeClasspath + minecraftFiles
+ classpath = sourceSets.main.get().runtimeClasspath
mainClass = "de.bluecolored.bluemap.entities.generator.Main"
// -PoutDir= writes the models somewhere else (dry-run), -Players= generates a subset only
@@ -85,10 +56,11 @@ tasks.register("generateEntityModels") {
val out = outDir ?: rootProject.file("src/main/resources/assets/minecraft/models/entity").absolutePath
val index = if (outDir != null) layout.buildDirectory.file("generated-index.txt").get().asFile.absolutePath
else file("generated-index.txt").absolutePath
+ val clientJar = loom.namedMinecraftJars
argumentProviders.add(CommandLineArgumentProvider {
listOf(
- "--client", mcDir.get().file("client.jar").asFile.absolutePath,
+ "--client", clientJar.singleFile.absolutePath,
"--mc-version", mcVersion,
"--out", out,
"--index", index,
@@ -98,3 +70,22 @@ tasks.register("generateEntityModels") {
) + (findProperty("layers") as String?)?.let { listOf("--include", it) }.orEmpty()
})
}
+
+val installAddon = tasks.register("installAddon") {
+ group = "bluemap"
+ description = "Copies the built addon into the packs-folder of the test-server"
+ from(rootProject.tasks.named("jar"))
+ into(serverDir.dir("config/bluemap/packs"))
+}
+
+val installTestDatapack = tasks.register("installTestDatapack") {
+ group = "bluemap"
+ description = "Copies the test-datapack into the world of the test-server"
+ from(layout.projectDirectory.dir("datapack/bluemap-entity-test"))
+ into(serverDir.dir("world/datapacks/bluemap-entity-test"))
+}
+
+tasks.named("runServer") {
+ group = "bluemap"
+ dependsOn(installAddon, installTestDatapack)
+}
From 0d5b623a1b33a8df38af270f2b5947815e8c9014 Mon Sep 17 00:00:00 2001
From: miraculixx
Date: Tue, 11 Aug 2026 21:24:15 +0200
Subject: [PATCH 08/34] add a test server that loads everything needed
---
gradle/wrapper/gradle-wrapper.properties | 2 +-
settings.gradle.kts | 1 +
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 9144876..d03d4ec 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.14-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 2f1e384..8c41410 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -2,6 +2,7 @@ pluginManagement {
repositories {
gradlePluginPortal()
mavenCentral()
+ maven ( "https://maven.fabricmc.net/" )
}
}
From 77e4b57094a99f859e38352b4521b8c5179e9c8c Mon Sep 17 00:00:00 2001
From: miraculixx
Date: Tue, 11 Aug 2026 21:24:32 +0200
Subject: [PATCH 09/34] Updated mapping
---
.../bluemap/entities/renderer/CustomResourceModelRenderer.java | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/main/java/de/bluecolored/bluemap/entities/renderer/CustomResourceModelRenderer.java b/src/main/java/de/bluecolored/bluemap/entities/renderer/CustomResourceModelRenderer.java
index cca4185..69ca83f 100644
--- a/src/main/java/de/bluecolored/bluemap/entities/renderer/CustomResourceModelRenderer.java
+++ b/src/main/java/de/bluecolored/bluemap/entities/renderer/CustomResourceModelRenderer.java
@@ -39,6 +39,7 @@
import de.bluecolored.bluemap.core.resources.pack.resourcepack.model.Model;
import de.bluecolored.bluemap.core.resources.pack.resourcepack.texture.Texture;
import de.bluecolored.bluemap.core.util.Direction;
+import de.bluecolored.bluemap.core.util.Key;
import de.bluecolored.bluemap.core.util.math.Color;
import de.bluecolored.bluemap.core.util.math.MatrixM4f;
import de.bluecolored.bluemap.core.util.math.VectorM2f;
@@ -57,7 +58,7 @@
public class CustomResourceModelRenderer implements EntityRenderer {
private static final float SCALE = 1f / 16f;
- @Getter private final Function, Model> modelProvider;
+ @Getter private final Function modelProvider;
@Getter private final TextureGallery textureGallery;
@Getter private final RenderSettings renderSettings;
From 18068f982e9a0f39a1d42b48a9771b5a63ec8575 Mon Sep 17 00:00:00 2001
From: miraculixx
Date: Tue, 11 Aug 2026 21:31:03 +0200
Subject: [PATCH 10/34] Update info
---
generator/README.md | 62 +++++++++------------------------------------
1 file changed, 12 insertions(+), 50 deletions(-)
diff --git a/generator/README.md b/generator/README.md
index 134bb7b..a969be2 100644
--- a/generator/README.md
+++ b/generator/README.md
@@ -5,62 +5,24 @@ are compiled into the vanilla minecraft client-jar, so they do not have to be re
minecraft-version.
```bash
-./gradlew :generator:generateEntityModels # current version (see mcVersion below)
+./gradlew :generator:generateEntityModels
./gradlew :generator:generateEntityModels -PmcVersion=26.3 # a different minecraft-version
./gradlew :generator:generateEntityModels -Players='sheep|cow' # only some layers
-./gradlew :generator:generateEntityModels -PoutDir=/tmp/models # write somewhere else (dry-run)
+./gradlew :generator:generateEntityModels -PoutDir=/tmp/models # testing
```
-The client-jar and its libraries are downloaded from mojangs launcher-metadata into `generator/build/minecraft/`
-(~100 MB, once per version). Nothing of it is shipped with the addon, only the generated models are.
-
-Gradle itself has to run on a JDK **21-24** (its kotlin-dsl compiler does not run on 25 yet), the generator is compiled
-and run with a java-25 toolchain, since that is what the minecraft client-jar is built for.
-
-## How it works
-
-`LayerDefinitions.createRoots()` returns every entity-model-layer minecraft knows (358 in 26.2). Each layer is baked
-(`LayerDefinition.bakeRoot()`) and walked with `ModelPart.visit(...)`, which yields every cube together with the
-accumulated part-transform. Minecraft builds entity-models mirrored and upside-down (the entity-renderer applies a
-`scale(-1, -1, 1)`) with the origin 1.5 blocks above the feet, so every coordinate is converted with
-`x' = -x`, `y' = 24 - y`, `z' = z`, and the part-hierarchy is flattened into the single rotation a bluemap-element
-supports. Uv-coordinates are taken from the baked polygon-vertices, which also covers mirrored and deformed cubes.
-
-## Output
-
-One geometry-model per layer, plus a thin model per texture-variant that only overrides the texture:
-
-```
-entity/sheep/main.json minecraft:sheep#main
-entity/sheep/wool.json minecraft:sheep#wool (overlay-layer)
-entity/sheep_baby/main.json minecraft:sheep_baby#main (baby-model)
-entity/cat/main_tabby.json variant, parent: entity/cat/main
-entity/zombie/helmet_iron.json equipment-material of minecraft:zombie#helmet
-```
-
-Textures use the vanilla paths (`entity/sheep/sheep`) and are resolved, in this order, from
-`config/textures.json`, the data-driven variant-registries of the client-jar (cat, cow, pig, chicken, frog, wolf, ...),
-the equipment-textures (`entity/equipment/...`), the vanilla naming-convention, the texture-family of the entity
-(axolotl, horse, mooshroom, ...) and finally the texture-paths referenced by the compiled renderer-classes.
+`LayerDefinitions.createRoots()` returns every entity-model-layer minecraft knows (358 in 26.2).
+Each layer is baked (`LayerDefinition.bakeRoot()`) and walked with `ModelPart.visit(...)`.
`generator/build/generation-report.txt` lists which models were added, changed and removed, where every texture came
from, and which layers could not be resolved.
-## Configuration
-
-| file | purpose |
-|---|---|
-| `config/textures.json` | pins a texture (string) or a set of variant-textures (array) for `#` |
-| `config/variant-models.json` | maps the `model` field of a variant-registry-entry to its model-id |
-| `config/equipment.json` | maps `#` to a folder in `entity/equipment` |
-| `overrides/.json` | replaces a generated model entirely, e.g. for hand-fixed geometry |
-| `generated-index.txt` | the files of the last run, used to delete models that vanished from minecraft |
-
-## Limits
+## Config
-- Layers are generated in their base pose. Animations (`setupAnim`) and renderer-side tints are not part of the
- model-data and stay the job of the renderers in `de.bluecolored.bluemap.entities.renderer`.
-- Which layers an entity stacks (wool over sheep, saddle over pig, armor over zombie) is renderer-logic in minecraft,
- so it stays renderer-logic here too - the generator only provides the models.
-- Elements that need a rotation around more than one axis (60 models in 26.2) require **bluemap-core 5.14 or newer**,
- everything else also works with older versions.
+| file | purpose |
+|------------------------------|-------------------------------------------------------------------------------|
+| `config/textures.json` | pins a texture: string (one), array (variants), object (variants after key) |
+| `config/variant-models.json` | maps the `model` field to its model-id |
+| `config/equipment.json` | maps a layer to a folder in `entity/equipment` |
+| `config/split-parts.json` | in model variants (like chests on horse-ish mobs) |
+| `overrides/.json` | replacing for manual edited entities |
From 03d70a2a29749ef1c4876adfd675cd0ccf41c739 Mon Sep 17 00:00:00 2001
From: miraculixx
Date: Tue, 11 Aug 2026 21:34:22 +0200
Subject: [PATCH 11/34] Avoid double scaling ghasts
---
.../bluecolored/bluemap/entities/renderer/GhastRenderer.java | 5 -----
1 file changed, 5 deletions(-)
diff --git a/src/main/java/de/bluecolored/bluemap/entities/renderer/GhastRenderer.java b/src/main/java/de/bluecolored/bluemap/entities/renderer/GhastRenderer.java
index b9ac0c0..d213e8e 100644
--- a/src/main/java/de/bluecolored/bluemap/entities/renderer/GhastRenderer.java
+++ b/src/main/java/de/bluecolored/bluemap/entities/renderer/GhastRenderer.java
@@ -55,23 +55,18 @@ public void render(Entity entity, BlockNeighborhood block, Part part, TileModelV
// choose correct model & scale
ResourcePath model;
- Vector3f scale;
if (ghast instanceof HappyGhast) {
if (((HappyGhast) ghast).getAge() < 0) {
model = GHASTLING;
- scale = new Vector3f(0.9f, 0.9f, 0.9f);
} else {
model = HAPPY_GHAST;
- scale = new Vector3f(4f, 4f, 4f);
}
} else {
model = GHAST;
- scale = new Vector3f(4.5, 4.5, 4.5);
}
// render chosen model
super.render(entity, block, model.getResource(getModelProvider()), TintColorProvider.NO_TINT, tileModel);
- tileModel.scale(scale.getX(), scale.getY(), scale.getZ());
// apply part transform
if (part.isTransformed())
From 07c55b35590c6d2c850db5a98f9d746101c54591 Mon Sep 17 00:00:00 2001
From: miraculixx
Date: Tue, 11 Aug 2026 23:20:10 +0200
Subject: [PATCH 12/34] Ignore generic equipment for every model
---
generator/config/equipment.json | 3 ---
.../de/bluecolored/bluemap/entities/generator/Main.java | 9 +++++++++
2 files changed, 9 insertions(+), 3 deletions(-)
diff --git a/generator/config/equipment.json b/generator/config/equipment.json
index 4e2a3b2..433731b 100644
--- a/generator/config/equipment.json
+++ b/generator/config/equipment.json
@@ -1,7 +1,4 @@
{
- "elytra#main": "wings",
- "elytra_baby#main": "wings",
- "undead_horse_armor#main": "horse_body",
"happy_ghast_baby_harness#main": "happy_ghast_body",
"happy_ghast_ropes#main": "happy_ghast_body",
"happy_ghast_baby_ropes#main": "happy_ghast_body"
diff --git a/generator/src/main/java/de/bluecolored/bluemap/entities/generator/Main.java b/generator/src/main/java/de/bluecolored/bluemap/entities/generator/Main.java
index c5e4396..2e07bc9 100644
--- a/generator/src/main/java/de/bluecolored/bluemap/entities/generator/Main.java
+++ b/generator/src/main/java/de/bluecolored/bluemap/entities/generator/Main.java
@@ -59,6 +59,10 @@ public final class Main {
private static final String CONFIG_EQUIPMENT = "equipment.json";
private static final String CONFIG_SPLIT_PARTS = "split-parts.json";
+ /** worn equipment that is skipped for now */
+ private static final Set SKIPPED_LAYERS = Set.of("helmet", "chestplate", "boots", "leggings");
+ private static final Set SKIPPED_MODELS = Set.of("elytra", "elytra_baby");
+
private final Arguments arguments;
private final Report report = new Report();
private Map>> splitParts = Map.of();
@@ -120,6 +124,7 @@ private void generate(
if (!arguments.include.matcher(key).find()) return;
if (arguments.exclude != null && arguments.exclude.matcher(key).find()) return;
+ if (isEquipment(model, layer)) return;
Geometry geometry = LayerConverter.convert(definition);
geometry.warnings().forEach(warning -> report.warning(key + ": " + warning));
@@ -149,6 +154,10 @@ private void generate(
writeModel(model + "/" + layer, remaining, resolution, key, written);
}
+ private static boolean isEquipment(String model, String layer) {
+ return SKIPPED_LAYERS.contains(layer) || SKIPPED_MODELS.contains(model) || model.endsWith("_armor");
+ }
+
private void writeModel(
String path, Geometry geometry, TextureResolver.Resolution resolution,
String key, Set written
From 9c58c602cca2abd07ab03c1c9ba63ceee707e090 Mon Sep 17 00:00:00 2001
From: miraculixx
Date: Wed, 12 Aug 2026 17:22:02 +0200
Subject: [PATCH 13/34] Add tint for entities with multi-colors (sheep)
---
generator/README.md | 1 +
generator/config/tints.json | 5 ++++
.../bluemap/entities/generator/Main.java | 5 +++-
.../bluemap/entities/generator/ModelJson.java | 10 +++----
.../entities/generator/ModelWriter.java | 7 +++--
.../entities/generator/TextureResolver.java | 6 ++++
.../entities/renderer/SheepRenderer.java | 30 +++++++++++++++----
7 files changed, 50 insertions(+), 14 deletions(-)
create mode 100644 generator/config/tints.json
diff --git a/generator/README.md b/generator/README.md
index a969be2..061d81e 100644
--- a/generator/README.md
+++ b/generator/README.md
@@ -25,4 +25,5 @@ from, and which layers could not be resolved.
| `config/variant-models.json` | maps the `model` field to its model-id |
| `config/equipment.json` | maps a layer to a folder in `entity/equipment` |
| `config/split-parts.json` | in model variants (like chests on horse-ish mobs) |
+| `config/tints.json` | writes a `tintindex` on every face of a layer, colored by its renderer |
| `overrides/.json` | replacing for manual edited entities |
diff --git a/generator/config/tints.json b/generator/config/tints.json
new file mode 100644
index 0000000..f9801c7
--- /dev/null
+++ b/generator/config/tints.json
@@ -0,0 +1,5 @@
+{
+ "sheep#wool": 0,
+ "sheep#wool_undercoat": 0,
+ "sheep_baby#wool": 0
+}
diff --git a/generator/src/main/java/de/bluecolored/bluemap/entities/generator/Main.java b/generator/src/main/java/de/bluecolored/bluemap/entities/generator/Main.java
index 2e07bc9..304d3f5 100644
--- a/generator/src/main/java/de/bluecolored/bluemap/entities/generator/Main.java
+++ b/generator/src/main/java/de/bluecolored/bluemap/entities/generator/Main.java
@@ -58,6 +58,7 @@ public final class Main {
private static final String CONFIG_VARIANT_MODELS = "variant-models.json";
private static final String CONFIG_EQUIPMENT = "equipment.json";
private static final String CONFIG_SPLIT_PARTS = "split-parts.json";
+ private static final String CONFIG_TINTS = "tints.json";
/** worn equipment that is skipped for now */
private static final Set SKIPPED_LAYERS = Set.of("helmet", "chestplate", "boots", "leggings");
@@ -66,6 +67,7 @@ public final class Main {
private final Arguments arguments;
private final Report report = new Report();
private Map>> splitParts = Map.of();
+ private Map tints = Map.of();
private Main(Arguments arguments) {
this.arguments = arguments;
@@ -83,6 +85,7 @@ private void run() throws IOException {
layers.putAll(LayerDefinitions.createRoots());
splitParts = readConfig(CONFIG_SPLIT_PARTS, new TypeToken<>() {});
+ tints = readConfig(CONFIG_TINTS, new TypeToken<>() {});
try (McAssets assets = new McAssets(arguments.clientJar)) {
Set models = new LinkedHashSet<>();
@@ -162,7 +165,7 @@ private void writeModel(
String path, Geometry geometry, TextureResolver.Resolution resolution,
String key, Set written
) throws IOException {
- write(path, ModelWriter.write(geometry, resolution.texture(), arguments.minecraftVersion, key), written);
+ write(path, ModelWriter.write(geometry, resolution.texture(), arguments.minecraftVersion, key, tints.get(key)), written);
for (TextureResolver.Variant variant : resolution.variants())
write(path + "_" + variant.suffix(), ModelWriter.writeVariant(path, variant.texture()), written);
diff --git a/generator/src/main/java/de/bluecolored/bluemap/entities/generator/ModelJson.java b/generator/src/main/java/de/bluecolored/bluemap/entities/generator/ModelJson.java
index 6ec12c0..d03d589 100644
--- a/generator/src/main/java/de/bluecolored/bluemap/entities/generator/ModelJson.java
+++ b/generator/src/main/java/de/bluecolored/bluemap/entities/generator/ModelJson.java
@@ -51,10 +51,10 @@ record Element(String name, float[] from, float[] to, Rotation rotation, Map elements = geometry.elements().stream().map(ModelJson::element).toList();
+ static ModelJson geometry(Geometry geometry, String texture, String minecraftVersion, String layer, Integer tintindex) {
+ List elements = geometry.elements().stream().map(element -> element(element, tintindex)).toList();
return new ModelJson(
minecraftVersion,
"Generated from minecraft " + minecraftVersion + " (" + layer + ")",
@@ -70,9 +70,9 @@ static ModelJson variant(String parent, String texture) {
return new ModelJson(null, null, null, "minecraft:entity/" + parent, textures, null);
}
- private static Element element(Geometry.Element element) {
+ private static Element element(Geometry.Element element, Integer tintindex) {
Map faces = new LinkedHashMap<>();
- element.faces().forEach((direction, uv) -> faces.put(direction, new Face(uv, "#0")));
+ element.faces().forEach((direction, uv) -> faces.put(direction, new Face(uv, "#0", tintindex)));
return new Element(
element.name(),
diff --git a/generator/src/main/java/de/bluecolored/bluemap/entities/generator/ModelWriter.java b/generator/src/main/java/de/bluecolored/bluemap/entities/generator/ModelWriter.java
index 74da3f0..d093727 100644
--- a/generator/src/main/java/de/bluecolored/bluemap/entities/generator/ModelWriter.java
+++ b/generator/src/main/java/de/bluecolored/bluemap/entities/generator/ModelWriter.java
@@ -58,8 +58,8 @@ private static Gson gson() {
private ModelWriter() {}
- static String write(Geometry geometry, String texture, String minecraftVersion, String layer) {
- return write(ModelJson.geometry(geometry, texture, minecraftVersion, layer));
+ static String write(Geometry geometry, String texture, String minecraftVersion, String layer, Integer tintindex) {
+ return write(ModelJson.geometry(geometry, texture, minecraftVersion, layer, tintindex));
}
/** A texture-variant of an already written model, which only overrides its texture. */
@@ -84,7 +84,8 @@ private static String write(ModelJson model) {
}
private static String inline(ModelJson.Face face) {
- return "{\"uv\": " + inline(face.uv()) + ", \"texture\": \"" + face.texture() + "\"}";
+ return "{\"uv\": " + inline(face.uv()) + ", \"texture\": \"" + face.texture() + "\""
+ + (face.tintindex() == null ? "" : ", \"tintindex\": " + face.tintindex()) + "}";
}
private static String inline(float[] values) {
diff --git a/generator/src/main/java/de/bluecolored/bluemap/entities/generator/TextureResolver.java b/generator/src/main/java/de/bluecolored/bluemap/entities/generator/TextureResolver.java
index fe32581..f22cce7 100644
--- a/generator/src/main/java/de/bluecolored/bluemap/entities/generator/TextureResolver.java
+++ b/generator/src/main/java/de/bluecolored/bluemap/entities/generator/TextureResolver.java
@@ -185,6 +185,12 @@ private List conventions(String model, String layer) {
candidates.add("entity/" + base + "/" + base);
candidates.add("entity/" + base);
} else {
+ // baby-textures of a layer keep the baby-suffix last
+ if (!model.equals(base)) {
+ candidates.add("entity/" + base + "/" + base + "_" + layer + BABY_SUFFIX);
+ candidates.add("entity/" + base + "/" + layer + BABY_SUFFIX);
+ }
+
candidates.add("entity/" + base + "/" + model + "_" + layer);
candidates.add("entity/" + base + "/" + base + "_" + layer);
candidates.add("entity/" + model + "/" + model + "_" + layer);
diff --git a/src/main/java/de/bluecolored/bluemap/entities/renderer/SheepRenderer.java b/src/main/java/de/bluecolored/bluemap/entities/renderer/SheepRenderer.java
index 6fca3a1..2a6dfc3 100644
--- a/src/main/java/de/bluecolored/bluemap/entities/renderer/SheepRenderer.java
+++ b/src/main/java/de/bluecolored/bluemap/entities/renderer/SheepRenderer.java
@@ -38,11 +38,20 @@
public class SheepRenderer extends CustomResourceModelRenderer {
+ /** {@code DyeColor#getTextureDiffuseColor} darkened by 0.75 (mc does it) */
+ private static final int[] WOOL_COLORS = {
+ 0xE6E6E6, 0xBA6015, 0x953A8D, 0x2B86A3,
+ 0xBEA22D, 0x609517, 0xB6687F, 0x353B3D,
+ 0x757571, 0x107575, 0x66258A, 0x2D337F,
+ 0x623F25, 0x465D10, 0x84221C, 0x151518
+ };
+
private final ResourcePath
SHEEP_ADULT = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/sheep/main"),
SHEEP_BABY = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/sheep_baby/main"),
SHEEP_ADULT_WOOL = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/sheep/wool"),
- SHEEP_BABY_WOOL = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/sheep_baby/wool");
+ SHEEP_BABY_WOOL = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/sheep_baby/wool"),
+ SHEEP_ADULT_WOOL_UNDERCOAT = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/sheep/wool_undercoat");
public SheepRenderer(ResourcePack resourcePack, TextureGallery textureGallery, RenderSettings renderSettings) {
super(resourcePack, textureGallery, renderSettings);
@@ -52,24 +61,35 @@ public SheepRenderer(ResourcePack resourcePack, TextureGallery textureGallery, R
public void render(Entity entity, BlockNeighborhood block, Part part, TileModelView tileModel) {
if (!(entity instanceof Sheep sheep)) return;
+ boolean isBaby = sheep.getAge() < 0;
+
// render base body
ResourcePath baseModel;
- if (sheep.getAge() < 0) {
+ if (isBaby) {
baseModel = SHEEP_BABY;
} else {
baseModel = SHEEP_ADULT;
}
super.render(entity, block, baseModel.getResource(getModelProvider()), TintColorProvider.NO_TINT, tileModel);
- // render wool layer if not sheared (TODO wool tint)
+ // render wool layer if not sheared, tinted by the wool-color
if (!sheep.isSheared()) {
+ int color = sheep.getColor() & 0xFF;
+ if (color >= WOOL_COLORS.length) color = 0;
+ int woolColor = 0xFF000000 | WOOL_COLORS[color];
+ TintColorProvider woolTint = (index, target) -> target.set(woolColor, true);
+
ResourcePath woolModel;
- if (sheep.getAge() < 0) {
+ if (isBaby) {
woolModel = SHEEP_BABY_WOOL;
} else {
woolModel = SHEEP_ADULT_WOOL;
}
- super.render(entity, block, woolModel.getResource(getModelProvider()), TintColorProvider.NO_TINT, tileModel);
+ super.render(entity, block, woolModel.getResource(getModelProvider()), woolTint, tileModel);
+
+ if (!isBaby && color != 0) {
+ super.render(entity, block, SHEEP_ADULT_WOOL_UNDERCOAT.getResource(getModelProvider()), woolTint, tileModel);
+ }
}
// apply part transform
From d7c92f9b9ddd4d1584fce97a4a87a7f80a65f162 Mon Sep 17 00:00:00 2001
From: miraculixx
Date: Wed, 12 Aug 2026 17:36:53 +0200
Subject: [PATCH 14/34] Add tint for tropical fish
---
generator/config/tints.json | 7 +++-
.../function/group/tropical_fish.mcfunction | 23 +++++++------
.../bluemap/entities/entity/TropicalFish.java | 17 ++++++++++
.../renderer/TropicalFishRenderer.java | 32 +++++++++++++++----
4 files changed, 62 insertions(+), 17 deletions(-)
diff --git a/generator/config/tints.json b/generator/config/tints.json
index f9801c7..ebfb821 100644
--- a/generator/config/tints.json
+++ b/generator/config/tints.json
@@ -1,5 +1,10 @@
{
"sheep#wool": 0,
"sheep#wool_undercoat": 0,
- "sheep_baby#wool": 0
+ "sheep_baby#wool": 0,
+
+ "tropical_fish_small#main": 0,
+ "tropical_fish_small#pattern": 0,
+ "tropical_fish_large#main": 0,
+ "tropical_fish_large#pattern": 0
}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/tropical_fish.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/tropical_fish.mcfunction
index 0e7905f..9222f6d 100644
--- a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/tropical_fish.mcfunction
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/tropical_fish.mcfunction
@@ -1,10 +1,13 @@
-# tropical_fish (9 entities)
-summon minecraft:tropical_fish ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0}
-summon minecraft:tropical_fish ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:1}
-summon minecraft:tropical_fish ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:65536}
-summon minecraft:tropical_fish ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:65537}
-summon minecraft:tropical_fish ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:16777216}
-summon minecraft:tropical_fish ~15 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:16777217}
-summon minecraft:tropical_fish ~18 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:50790656}
-summon minecraft:tropical_fish ~21 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:67108865}
-summon minecraft:tropical_fish ~24 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:117506305}
+# tropical_fish (12 entities, every pattern of both body-sizes)
+summon minecraft:tropical_fish ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:251723776}
+summon minecraft:tropical_fish ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:262400}
+summon minecraft:tropical_fish ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:251986432}
+summon minecraft:tropical_fish ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:590592}
+summon minecraft:tropical_fish ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:252380160}
+summon minecraft:tropical_fish ~15 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:918784}
+summon minecraft:tropical_fish ~18 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:251723777}
+summon minecraft:tropical_fish ~21 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:262401}
+summon minecraft:tropical_fish ~24 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:251986433}
+summon minecraft:tropical_fish ~27 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:590593}
+summon minecraft:tropical_fish ~30 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:252380161}
+summon minecraft:tropical_fish ~33 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:918785}
diff --git a/src/main/java/de/bluecolored/bluemap/entities/entity/TropicalFish.java b/src/main/java/de/bluecolored/bluemap/entities/entity/TropicalFish.java
index 3a5af35..5345b38 100644
--- a/src/main/java/de/bluecolored/bluemap/entities/entity/TropicalFish.java
+++ b/src/main/java/de/bluecolored/bluemap/entities/entity/TropicalFish.java
@@ -38,4 +38,21 @@ public class TropicalFish extends MCAEntity {
@NBTName("Variant") int variant;
+ public boolean isLarge() {
+ return (variant & 0xFF) != 0;
+ }
+
+ /** 0 - 5 */
+ public int getPattern() {
+ return (variant >> 8) & 0xFF;
+ }
+
+ public int getBaseColor() {
+ return (variant >> 16) & 0xFF;
+ }
+
+ public int getPatternColor() {
+ return (variant >> 24) & 0xFF;
+ }
+
}
diff --git a/src/main/java/de/bluecolored/bluemap/entities/renderer/TropicalFishRenderer.java b/src/main/java/de/bluecolored/bluemap/entities/renderer/TropicalFishRenderer.java
index e6d9318..deae187 100644
--- a/src/main/java/de/bluecolored/bluemap/entities/renderer/TropicalFishRenderer.java
+++ b/src/main/java/de/bluecolored/bluemap/entities/renderer/TropicalFishRenderer.java
@@ -38,8 +38,15 @@
public class TropicalFishRenderer extends CustomResourceModelRenderer {
- private final ResourcePath
- TROPICAL_FISH = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/tropical_fish_small/main");
+ /** {@code DyeColor#getTextureDiffuseColor} */
+ private static final int[] DYE_COLORS = {
+ 0xF9FFFE, 0xF9801D, 0xC74EBD, 0x3AB3DA,
+ 0xFED83D, 0x80C71F, 0xF38BAA, 0x474F52,
+ 0x9D9D97, 0x169C9C, 0x8932B8, 0x3C44AA,
+ 0x835432, 0x5E7C16, 0xB02E26, 0x1D1D21
+ };
+
+ private static final int PATTERNS = 6;
public TropicalFishRenderer(ResourcePack resourcePack, TextureGallery textureGallery, RenderSettings renderSettings) {
super(resourcePack, textureGallery, renderSettings);
@@ -49,15 +56,28 @@ public TropicalFishRenderer(ResourcePack resourcePack, TextureGallery textureGal
public void render(Entity entity, BlockNeighborhood block, Part part, TileModelView tileModel) {
if (!(entity instanceof TropicalFish tropicalFish)) return;
- // choose correct model
- ResourcePath model = TROPICAL_FISH;
+ boolean isLarge = tropicalFish.isLarge();
+ int pattern = tropicalFish.getPattern();
+ if (pattern >= PATTERNS) pattern = 0;
+
+ // "entity/tropical_fish_{small|large}/main" tint base-color
+ String fishModel = "entity/tropical_fish_" + (isLarge ? "large" : "small") + "/";
+ ResourcePath baseModel = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, fishModel + "main");
+ super.render(entity, block, baseModel.getResource(getModelProvider()), tint(tropicalFish.getBaseColor()), tileModel);
- // render chosen model
- super.render(entity, block, model.getResource(getModelProvider()), TintColorProvider.NO_TINT, tileModel);
+ // "entity/tropical_fish_{small|large}/pattern_tropical_{a|b}_pattern_{1-6}"
+ String patternModel = fishModel + "pattern_tropical_" + (isLarge ? "b" : "a") + "_pattern_" + (pattern + 1);
+ ResourcePath patternPath = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, patternModel);
+ super.render(entity, block, patternPath.getResource(getModelProvider()), tint(tropicalFish.getPatternColor()), tileModel);
// apply part transform
if (part.isTransformed())
tileModel.transform(part.getTransformMatrix());
}
+ private TintColorProvider tint(int dyeColor) {
+ int color = 0xFF000000 | DYE_COLORS[dyeColor & 0xF];
+ return (index, target) -> target.set(color, true);
+ }
+
}
From 862d823a65f782d0bc8e53c96a3e9463b1da2d4c Mon Sep 17 00:00:00 2001
From: miraculixx
Date: Wed, 12 Aug 2026 18:13:54 +0200
Subject: [PATCH 15/34] Fix overlay for drowned and skeleton types
---
.../bluemap/entities/generator/TextureResolver.java | 4 ++++
.../bluemap/entities/renderer/DrownedRenderer.java | 11 ++++++++---
.../bluemap/entities/renderer/SkeletonRenderer.java | 13 ++++++++++++-
3 files changed, 24 insertions(+), 4 deletions(-)
diff --git a/generator/src/main/java/de/bluecolored/bluemap/entities/generator/TextureResolver.java b/generator/src/main/java/de/bluecolored/bluemap/entities/generator/TextureResolver.java
index f22cce7..a540693 100644
--- a/generator/src/main/java/de/bluecolored/bluemap/entities/generator/TextureResolver.java
+++ b/generator/src/main/java/de/bluecolored/bluemap/entities/generator/TextureResolver.java
@@ -218,6 +218,10 @@ private List names(String model, String layer) {
if (layer.equals("main")) {
names.add(alias);
} else {
+ if (alias.endsWith(BABY_SUFFIX)) {
+ names.add(base(alias) + "_" + layer + BABY_SUFFIX);
+ names.add(base(alias) + "_" + layer + "_layer" + BABY_SUFFIX);
+ }
names.add(alias + "_" + layer);
names.add(alias + "_" + layer + "_layer");
}
diff --git a/src/main/java/de/bluecolored/bluemap/entities/renderer/DrownedRenderer.java b/src/main/java/de/bluecolored/bluemap/entities/renderer/DrownedRenderer.java
index a420b2f..9deb654 100644
--- a/src/main/java/de/bluecolored/bluemap/entities/renderer/DrownedRenderer.java
+++ b/src/main/java/de/bluecolored/bluemap/entities/renderer/DrownedRenderer.java
@@ -40,7 +40,9 @@ public class DrownedRenderer extends CustomResourceModelRenderer {
private final ResourcePath
ZOMBIE_ADULT = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/drowned/main"),
- ZOMBIE_BABY = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/drowned_baby/main");
+ ZOMBIE_BABY = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/drowned_baby/main"),
+ ZOMBIE_ADULT_OUTER = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/drowned/outer"),
+ ZOMBIE_BABY_OUTER = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/drowned_baby/outer");
public DrownedRenderer(ResourcePack resourcePack, TextureGallery textureGallery, RenderSettings renderSettings) {
super(resourcePack, textureGallery, renderSettings);
@@ -51,15 +53,18 @@ public void render(Entity entity, BlockNeighborhood block, Part part, TileModelV
if (!(entity instanceof Zombie drowned)) return;
// choose correct model
- ResourcePath model;
+ ResourcePath model, outerModel;
if (drowned.isBaby()) {
model = ZOMBIE_BABY;
+ outerModel = ZOMBIE_BABY_OUTER;
} else {
model = ZOMBIE_ADULT;
+ outerModel = ZOMBIE_ADULT_OUTER;
}
- // render chosen model
+ // render chosen model and its overlay layer
super.render(entity, block, model.getResource(getModelProvider()), TintColorProvider.NO_TINT, tileModel);
+ super.render(entity, block, outerModel.getResource(getModelProvider()), TintColorProvider.NO_TINT, tileModel);
// apply part transform
if (part.isTransformed())
diff --git a/src/main/java/de/bluecolored/bluemap/entities/renderer/SkeletonRenderer.java b/src/main/java/de/bluecolored/bluemap/entities/renderer/SkeletonRenderer.java
index 708846e..bb2ec5f 100644
--- a/src/main/java/de/bluecolored/bluemap/entities/renderer/SkeletonRenderer.java
+++ b/src/main/java/de/bluecolored/bluemap/entities/renderer/SkeletonRenderer.java
@@ -43,7 +43,9 @@ public class SkeletonRenderer extends CustomResourceModelRenderer {
WITHER_SKELETON = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/wither_skeleton/main"),
STRAY = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/stray/main"),
BOGGED = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/bogged/main"),
- PARCHED = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/parched/main");
+ PARCHED = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/parched/main"),
+ STRAY_OUTER = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/stray/outer"),
+ BOGGED_OUTER = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, "entity/bogged/outer");
public SkeletonRenderer(ResourcePack resourcePack, TextureGallery textureGallery, RenderSettings renderSettings) {
super(resourcePack, textureGallery, renderSettings);
@@ -65,6 +67,15 @@ public void render(Entity entity, BlockNeighborhood block, Part part, TileModelV
// render chosen model
super.render(entity, block, model.getResource(getModelProvider()), TintColorProvider.NO_TINT, tileModel);
+ // render overlay layer, parched has no overlay-texture
+ ResourcePath outerModel = switch (skeleton) {
+ case Stray ignored -> STRAY_OUTER;
+ case Bogged ignored -> BOGGED_OUTER;
+ default -> null;
+ };
+ if (outerModel != null)
+ super.render(entity, block, outerModel.getResource(getModelProvider()), TintColorProvider.NO_TINT, tileModel);
+
// apply part transform
if (part.isTransformed())
tileModel.transform(part.getTransformMatrix());
From 7c9678b679bbfcdaa611c0b1c3ad0e77925ae69d Mon Sep 17 00:00:00 2001
From: miraculixx
Date: Wed, 12 Aug 2026 18:39:32 +0200
Subject: [PATCH 16/34] Add full datapack coverage (testing)
---
.../function/group/aquatic.mcfunction | 23 +++---
.../function/group/armadillo.mcfunction | 6 +-
.../function/group/axolotl.mcfunction | 10 +--
.../bme_test/function/group/beast.mcfunction | 11 +++
.../bme_test/function/group/bee.mcfunction | 6 +-
.../bme_test/function/group/boat.mcfunction | 11 +++
.../bme_test/function/group/cat.mcfunction | 26 +++----
.../function/group/cat_collar.mcfunction | 17 ++++
.../function/group/chest_boat.mcfunction | 11 +++
.../function/group/chested_horse.mcfunction | 10 +--
.../function/group/chicken.mcfunction | 10 +--
.../function/group/copper_golem.mcfunction | 6 +-
.../bme_test/function/group/cow.mcfunction | 10 +--
.../bme_test/function/group/cube.mcfunction | 8 ++
.../function/group/ender_dragon.mcfunction | 2 +
.../bme_test/function/group/fox.mcfunction | 10 +--
.../bme_test/function/group/frog.mcfunction | 4 +-
.../bme_test/function/group/ghast.mcfunction | 4 +-
.../bme_test/function/group/golem.mcfunction | 4 +-
.../bme_test/function/group/hoglin.mcfunction | 7 ++
.../function/group/horse_baby.mcfunction | 12 +--
.../group/horse_markings_0.mcfunction | 12 +--
.../group/horse_markings_1.mcfunction | 12 +--
.../group/horse_markings_2.mcfunction | 12 +--
.../group/horse_markings_3.mcfunction | 12 +--
.../group/horse_markings_4.mcfunction | 12 +--
.../bme_test/function/group/llama.mcfunction | 30 ++-----
.../function/group/llama_carpet.mcfunction | 17 ++++
.../bme_test/function/group/misc.mcfunction | 6 +-
.../function/group/monster.mcfunction | 16 ++++
.../function/group/monster_big.mcfunction | 6 ++
.../function/group/mooshroom.mcfunction | 5 ++
.../bme_test/function/group/ocelot.mcfunction | 4 +-
.../bme_test/function/group/parrot.mcfunction | 6 ++
.../bme_test/function/group/pig.mcfunction | 12 +--
.../bme_test/function/group/piglin.mcfunction | 6 ++
.../bme_test/function/group/prop.mcfunction | 11 +++
.../bme_test/function/group/rabbit.mcfunction | 9 +++
.../bme_test/function/group/salmon.mcfunction | 7 ++
.../bme_test/function/group/sheep.mcfunction | 30 +++----
.../function/group/sheep_states.mcfunction | 12 +--
.../function/group/skeleton.mcfunction | 8 +-
.../function/group/trader_llama.mcfunction | 10 +--
.../function/group/tropical_fish.mcfunction | 24 +++---
.../function/group/undead_horse.mcfunction | 6 +-
.../function/group/villager.mcfunction | 6 ++
.../bme_test/function/group/wolf.mcfunction | 12 +++
.../bme_test/function/group/zombie.mcfunction | 10 +--
.../bme_test/function/platform.mcfunction | 4 +-
.../data/bme_test/function/spawn.mcfunction | 78 ++++++++++++-------
50 files changed, 386 insertions(+), 217 deletions(-)
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/beast.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/boat.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/cat_collar.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/chest_boat.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/cube.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/ender_dragon.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/hoglin.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/llama_carpet.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/monster.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/monster_big.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/mooshroom.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/parrot.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/piglin.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/prop.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/rabbit.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/salmon.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/villager.mcfunction
create mode 100644 generator/datapack/bluemap-entity-test/data/bme_test/function/group/wolf.mcfunction
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/aquatic.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/aquatic.mcfunction
index d78f24e..7afbe28 100644
--- a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/aquatic.mcfunction
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/aquatic.mcfunction
@@ -1,14 +1,13 @@
-# aquatic (13 entities)
+# aquatic (12 entities)
summon minecraft:squid ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
-summon minecraft:squid ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Age:-24000}
-summon minecraft:glow_squid ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
-summon minecraft:glow_squid ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Age:-24000}
-summon minecraft:dolphin ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
-summon minecraft:dolphin ~15 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Age:-24000}
+summon minecraft:squid ~2 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Age:-24000}
+summon minecraft:glow_squid ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:glow_squid ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Age:-24000}
+summon minecraft:dolphin ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:dolphin ~10 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Age:-24000}
+summon minecraft:nautilus ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:nautilus ~14 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Age:-24000}
+summon minecraft:zombie_nautilus ~16 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
summon minecraft:cod ~18 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
-summon minecraft:salmon ~21 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
-summon minecraft:pufferfish ~24 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],PuffState:0}
-summon minecraft:pufferfish ~27 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],PuffState:1}
-summon minecraft:pufferfish ~30 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],PuffState:2}
-summon minecraft:guardian ~33 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
-summon minecraft:tadpole ~36 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:guardian ~20 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:tadpole ~22 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/armadillo.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/armadillo.mcfunction
index aa79a11..231a3a8 100644
--- a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/armadillo.mcfunction
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/armadillo.mcfunction
@@ -1,5 +1,5 @@
# armadillo (4 entities)
summon minecraft:armadillo ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],state:"idle"}
-summon minecraft:armadillo ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],state:"scared"}
-summon minecraft:armadillo ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],state:"idle",Age:-24000}
-summon minecraft:armadillo ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],state:"scared",Age:-24000}
+summon minecraft:armadillo ~2 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],state:"scared"}
+summon minecraft:armadillo ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],state:"idle",Age:-24000}
+summon minecraft:armadillo ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],state:"scared",Age:-24000}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/axolotl.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/axolotl.mcfunction
index 9e216b8..dfb0d71 100644
--- a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/axolotl.mcfunction
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/axolotl.mcfunction
@@ -1,7 +1,7 @@
# axolotl (6 entities)
summon minecraft:axolotl ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0}
-summon minecraft:axolotl ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:1}
-summon minecraft:axolotl ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:2}
-summon minecraft:axolotl ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:3}
-summon minecraft:axolotl ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:4}
-summon minecraft:axolotl ~15 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,Age:-24000}
+summon minecraft:axolotl ~2 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:1}
+summon minecraft:axolotl ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:2}
+summon minecraft:axolotl ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:3}
+summon minecraft:axolotl ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:4}
+summon minecraft:axolotl ~10 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,Age:-24000}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/beast.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/beast.mcfunction
new file mode 100644
index 0000000..89e8f2f
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/beast.mcfunction
@@ -0,0 +1,11 @@
+# beast (10 entities)
+summon minecraft:goat ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:goat ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Age:-24000}
+summon minecraft:panda ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:panda ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Age:-24000}
+summon minecraft:sniffer ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:sniffer ~15 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Age:-24000}
+summon minecraft:turtle ~18 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:turtle ~21 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Age:-24000}
+summon minecraft:polar_bear ~24 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:polar_bear ~27 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Age:-24000}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/bee.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/bee.mcfunction
index e973df6..0aedf13 100644
--- a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/bee.mcfunction
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/bee.mcfunction
@@ -1,5 +1,5 @@
# bee (4 entities)
summon minecraft:bee ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
-summon minecraft:bee ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],HasNectar:1}
-summon minecraft:bee ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],HasStung:1b}
-summon minecraft:bee ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Age:-24000}
+summon minecraft:bee ~2 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],HasNectar:1b}
+summon minecraft:bee ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],HasStung:1b}
+summon minecraft:bee ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Age:-24000}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/boat.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/boat.mcfunction
new file mode 100644
index 0000000..b016e99
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/boat.mcfunction
@@ -0,0 +1,11 @@
+# boat (10 entities)
+summon minecraft:oak_boat ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:spruce_boat ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:birch_boat ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:jungle_boat ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:acacia_boat ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:dark_oak_boat ~15 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:mangrove_boat ~18 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:cherry_boat ~21 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:pale_oak_boat ~24 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:bamboo_raft ~27 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/cat.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/cat.mcfunction
index eda3c59..611ca90 100644
--- a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/cat.mcfunction
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/cat.mcfunction
@@ -1,15 +1,15 @@
# cat (14 entities)
summon minecraft:cat ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:tabby"}
-summon minecraft:cat ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:black"}
-summon minecraft:cat ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:red"}
-summon minecraft:cat ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:siamese"}
-summon minecraft:cat ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:british_shorthair"}
-summon minecraft:cat ~15 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:calico"}
-summon minecraft:cat ~18 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:persian"}
-summon minecraft:cat ~21 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:ragdoll"}
-summon minecraft:cat ~24 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:white"}
-summon minecraft:cat ~27 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:jellie"}
-summon minecraft:cat ~30 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:all_black"}
-summon minecraft:cat ~33 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:tabby",Sitting:1b}
-summon minecraft:cat ~36 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:tabby",Age:-24000}
-summon minecraft:cat ~39 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:calico",Age:-24000}
+summon minecraft:cat ~2 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:black"}
+summon minecraft:cat ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:red"}
+summon minecraft:cat ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:siamese"}
+summon minecraft:cat ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:british_shorthair"}
+summon minecraft:cat ~10 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:calico"}
+summon minecraft:cat ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:persian"}
+summon minecraft:cat ~14 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:ragdoll"}
+summon minecraft:cat ~16 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:white"}
+summon minecraft:cat ~18 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:jellie"}
+summon minecraft:cat ~20 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:all_black"}
+summon minecraft:cat ~22 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:tabby",Sitting:1b}
+summon minecraft:cat ~24 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:tabby",Age:-24000}
+summon minecraft:cat ~26 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:calico",Age:-24000}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/cat_collar.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/cat_collar.mcfunction
new file mode 100644
index 0000000..712e6aa
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/cat_collar.mcfunction
@@ -0,0 +1,17 @@
+# cat_collar (16 entities)
+summon minecraft:cat ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:tabby",Owner:[I;1,2,3,4],CollarColor:0b}
+summon minecraft:cat ~2 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:tabby",Owner:[I;1,2,3,4],CollarColor:1b}
+summon minecraft:cat ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:tabby",Owner:[I;1,2,3,4],CollarColor:2b}
+summon minecraft:cat ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:tabby",Owner:[I;1,2,3,4],CollarColor:3b}
+summon minecraft:cat ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:tabby",Owner:[I;1,2,3,4],CollarColor:4b}
+summon minecraft:cat ~10 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:tabby",Owner:[I;1,2,3,4],CollarColor:5b}
+summon minecraft:cat ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:tabby",Owner:[I;1,2,3,4],CollarColor:6b}
+summon minecraft:cat ~14 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:tabby",Owner:[I;1,2,3,4],CollarColor:7b}
+summon minecraft:cat ~16 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:tabby",Owner:[I;1,2,3,4],CollarColor:8b}
+summon minecraft:cat ~18 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:tabby",Owner:[I;1,2,3,4],CollarColor:9b}
+summon minecraft:cat ~20 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:tabby",Owner:[I;1,2,3,4],CollarColor:10b}
+summon minecraft:cat ~22 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:tabby",Owner:[I;1,2,3,4],CollarColor:11b}
+summon minecraft:cat ~24 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:tabby",Owner:[I;1,2,3,4],CollarColor:12b}
+summon minecraft:cat ~26 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:tabby",Owner:[I;1,2,3,4],CollarColor:13b}
+summon minecraft:cat ~28 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:tabby",Owner:[I;1,2,3,4],CollarColor:14b}
+summon minecraft:cat ~30 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:tabby",Owner:[I;1,2,3,4],CollarColor:15b}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/chest_boat.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/chest_boat.mcfunction
new file mode 100644
index 0000000..2919885
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/chest_boat.mcfunction
@@ -0,0 +1,11 @@
+# chest_boat (10 entities)
+summon minecraft:oak_chest_boat ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:spruce_chest_boat ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:birch_chest_boat ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:jungle_chest_boat ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:acacia_chest_boat ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:dark_oak_chest_boat ~15 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:mangrove_chest_boat ~18 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:cherry_chest_boat ~21 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:pale_oak_chest_boat ~24 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:bamboo_chest_raft ~27 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/chested_horse.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/chested_horse.mcfunction
index 3f5b855..67bc5a0 100644
--- a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/chested_horse.mcfunction
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/chested_horse.mcfunction
@@ -1,7 +1,7 @@
# chested_horse (6 entities)
summon minecraft:donkey ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
-summon minecraft:donkey ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],ChestedHorse:1b}
-summon minecraft:donkey ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Age:-24000}
-summon minecraft:mule ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
-summon minecraft:mule ~16 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],ChestedHorse:1b}
-summon minecraft:mule ~20 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Age:-24000}
+summon minecraft:donkey ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],ChestedHorse:1b}
+summon minecraft:donkey ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Age:-24000}
+summon minecraft:mule ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:mule ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],ChestedHorse:1b}
+summon minecraft:mule ~15 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Age:-24000}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/chicken.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/chicken.mcfunction
index 298fdc8..f32da92 100644
--- a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/chicken.mcfunction
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/chicken.mcfunction
@@ -1,7 +1,7 @@
# chicken (6 entities)
summon minecraft:chicken ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:temperate"}
-summon minecraft:chicken ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:warm"}
-summon minecraft:chicken ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:cold"}
-summon minecraft:chicken ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:temperate",Age:-24000}
-summon minecraft:chicken ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:warm",Age:-24000}
-summon minecraft:chicken ~15 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:cold",Age:-24000}
+summon minecraft:chicken ~2 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:warm"}
+summon minecraft:chicken ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:cold"}
+summon minecraft:chicken ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:temperate",Age:-24000}
+summon minecraft:chicken ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:warm",Age:-24000}
+summon minecraft:chicken ~10 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:cold",Age:-24000}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/copper_golem.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/copper_golem.mcfunction
index 3c1fcc2..297530f 100644
--- a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/copper_golem.mcfunction
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/copper_golem.mcfunction
@@ -1,5 +1,5 @@
# copper_golem (4 entities)
summon minecraft:copper_golem ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],weather_state:"unaffected"}
-summon minecraft:copper_golem ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],weather_state:"exposed"}
-summon minecraft:copper_golem ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],weather_state:"weathered"}
-summon minecraft:copper_golem ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],weather_state:"oxidized"}
+summon minecraft:copper_golem ~2 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],weather_state:"exposed"}
+summon minecraft:copper_golem ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],weather_state:"weathered"}
+summon minecraft:copper_golem ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],weather_state:"oxidized"}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/cow.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/cow.mcfunction
index f14f47d..aa44cea 100644
--- a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/cow.mcfunction
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/cow.mcfunction
@@ -1,7 +1,7 @@
# cow (6 entities)
summon minecraft:cow ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:temperate"}
-summon minecraft:cow ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:warm"}
-summon minecraft:cow ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:cold"}
-summon minecraft:cow ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:temperate",Age:-24000}
-summon minecraft:cow ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:warm",Age:-24000}
-summon minecraft:cow ~15 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:cold",Age:-24000}
+summon minecraft:cow ~2 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:warm"}
+summon minecraft:cow ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:cold"}
+summon minecraft:cow ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:temperate",Age:-24000}
+summon minecraft:cow ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:warm",Age:-24000}
+summon minecraft:cow ~10 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:cold",Age:-24000}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/cube.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/cube.mcfunction
new file mode 100644
index 0000000..8a08bb6
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/cube.mcfunction
@@ -0,0 +1,8 @@
+# cube (7 entities)
+summon minecraft:slime ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Size:0}
+summon minecraft:slime ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Size:1}
+summon minecraft:slime ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Size:3}
+summon minecraft:magma_cube ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Size:0}
+summon minecraft:magma_cube ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Size:1}
+summon minecraft:magma_cube ~15 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Size:3}
+summon minecraft:sulfur_cube ~18 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/ender_dragon.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/ender_dragon.mcfunction
new file mode 100644
index 0000000..657ea1e
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/ender_dragon.mcfunction
@@ -0,0 +1,2 @@
+# ender_dragon (1 entities)
+summon minecraft:ender_dragon ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/fox.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/fox.mcfunction
index 2c98219..f41a4ea 100644
--- a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/fox.mcfunction
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/fox.mcfunction
@@ -1,7 +1,7 @@
# fox (6 entities)
summon minecraft:fox ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Type:"red"}
-summon minecraft:fox ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Type:"snow"}
-summon minecraft:fox ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Type:"red",Sitting:1b}
-summon minecraft:fox ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Type:"red",Sleeping:1b}
-summon minecraft:fox ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Type:"red",Age:-24000}
-summon minecraft:fox ~15 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Type:"snow",Age:-24000}
+summon minecraft:fox ~2 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Type:"snow"}
+summon minecraft:fox ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Type:"red",Sitting:1b}
+summon minecraft:fox ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Type:"red",Sleeping:1b}
+summon minecraft:fox ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Type:"red",Age:-24000}
+summon minecraft:fox ~10 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Type:"snow",Age:-24000}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/frog.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/frog.mcfunction
index 70d006e..e40e5d9 100644
--- a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/frog.mcfunction
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/frog.mcfunction
@@ -1,4 +1,4 @@
# frog (3 entities)
summon minecraft:frog ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:temperate"}
-summon minecraft:frog ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:warm"}
-summon minecraft:frog ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:cold"}
+summon minecraft:frog ~2 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:warm"}
+summon minecraft:frog ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:cold"}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/ghast.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/ghast.mcfunction
index 6a6a2b5..fcb6ab1 100644
--- a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/ghast.mcfunction
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/ghast.mcfunction
@@ -1,4 +1,4 @@
# ghast (3 entities)
summon minecraft:ghast ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
-summon minecraft:happy_ghast ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
-summon minecraft:happy_ghast ~24 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Age:-24000}
+summon minecraft:happy_ghast ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:happy_ghast ~16 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Age:-24000}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/golem.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/golem.mcfunction
index 9ffbc50..c7ddd73 100644
--- a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/golem.mcfunction
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/golem.mcfunction
@@ -1,4 +1,4 @@
# golem (3 entities)
summon minecraft:snow_golem ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Pumpkin:1b}
-summon minecraft:snow_golem ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Pumpkin:0b}
-summon minecraft:iron_golem ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:snow_golem ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Pumpkin:0b}
+summon minecraft:iron_golem ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/hoglin.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/hoglin.mcfunction
new file mode 100644
index 0000000..a19d8cc
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/hoglin.mcfunction
@@ -0,0 +1,7 @@
+# hoglin (6 entities)
+summon minecraft:hoglin ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:hoglin ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Age:-24000}
+summon minecraft:zoglin ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:zoglin ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Age:-24000}
+summon minecraft:strider ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:strider ~15 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Age:-24000}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/horse_baby.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/horse_baby.mcfunction
index 78e2df0..e9532f3 100644
--- a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/horse_baby.mcfunction
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/horse_baby.mcfunction
@@ -1,8 +1,8 @@
# horse_baby (7 entities)
summon minecraft:horse ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,Age:-24000}
-summon minecraft:horse ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:1,Age:-24000}
-summon minecraft:horse ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:2,Age:-24000}
-summon minecraft:horse ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:3,Age:-24000}
-summon minecraft:horse ~16 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:4,Age:-24000}
-summon minecraft:horse ~20 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:5,Age:-24000}
-summon minecraft:horse ~24 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:6,Age:-24000}
+summon minecraft:horse ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:1,Age:-24000}
+summon minecraft:horse ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:2,Age:-24000}
+summon minecraft:horse ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:3,Age:-24000}
+summon minecraft:horse ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:4,Age:-24000}
+summon minecraft:horse ~15 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:5,Age:-24000}
+summon minecraft:horse ~18 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:6,Age:-24000}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/horse_markings_0.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/horse_markings_0.mcfunction
index ad41d39..c5e966f 100644
--- a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/horse_markings_0.mcfunction
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/horse_markings_0.mcfunction
@@ -1,8 +1,8 @@
# horse_markings_0 (7 entities)
summon minecraft:horse ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0}
-summon minecraft:horse ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:1}
-summon minecraft:horse ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:2}
-summon minecraft:horse ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:3}
-summon minecraft:horse ~16 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:4}
-summon minecraft:horse ~20 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:5}
-summon minecraft:horse ~24 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:6}
+summon minecraft:horse ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:1}
+summon minecraft:horse ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:2}
+summon minecraft:horse ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:3}
+summon minecraft:horse ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:4}
+summon minecraft:horse ~15 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:5}
+summon minecraft:horse ~18 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:6}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/horse_markings_1.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/horse_markings_1.mcfunction
index 09200dc..d50153d 100644
--- a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/horse_markings_1.mcfunction
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/horse_markings_1.mcfunction
@@ -1,8 +1,8 @@
# horse_markings_1 (7 entities)
summon minecraft:horse ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:256}
-summon minecraft:horse ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:257}
-summon minecraft:horse ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:258}
-summon minecraft:horse ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:259}
-summon minecraft:horse ~16 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:260}
-summon minecraft:horse ~20 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:261}
-summon minecraft:horse ~24 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:262}
+summon minecraft:horse ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:257}
+summon minecraft:horse ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:258}
+summon minecraft:horse ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:259}
+summon minecraft:horse ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:260}
+summon minecraft:horse ~15 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:261}
+summon minecraft:horse ~18 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:262}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/horse_markings_2.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/horse_markings_2.mcfunction
index 5558aa1..d10f68a 100644
--- a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/horse_markings_2.mcfunction
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/horse_markings_2.mcfunction
@@ -1,8 +1,8 @@
# horse_markings_2 (7 entities)
summon minecraft:horse ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:512}
-summon minecraft:horse ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:513}
-summon minecraft:horse ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:514}
-summon minecraft:horse ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:515}
-summon minecraft:horse ~16 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:516}
-summon minecraft:horse ~20 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:517}
-summon minecraft:horse ~24 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:518}
+summon minecraft:horse ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:513}
+summon minecraft:horse ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:514}
+summon minecraft:horse ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:515}
+summon minecraft:horse ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:516}
+summon minecraft:horse ~15 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:517}
+summon minecraft:horse ~18 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:518}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/horse_markings_3.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/horse_markings_3.mcfunction
index a1698f9..cc41e4c 100644
--- a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/horse_markings_3.mcfunction
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/horse_markings_3.mcfunction
@@ -1,8 +1,8 @@
# horse_markings_3 (7 entities)
summon minecraft:horse ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:768}
-summon minecraft:horse ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:769}
-summon minecraft:horse ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:770}
-summon minecraft:horse ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:771}
-summon minecraft:horse ~16 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:772}
-summon minecraft:horse ~20 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:773}
-summon minecraft:horse ~24 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:774}
+summon minecraft:horse ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:769}
+summon minecraft:horse ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:770}
+summon minecraft:horse ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:771}
+summon minecraft:horse ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:772}
+summon minecraft:horse ~15 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:773}
+summon minecraft:horse ~18 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:774}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/horse_markings_4.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/horse_markings_4.mcfunction
index a0acca6..a91aab5 100644
--- a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/horse_markings_4.mcfunction
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/horse_markings_4.mcfunction
@@ -1,8 +1,8 @@
# horse_markings_4 (7 entities)
summon minecraft:horse ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:1024}
-summon minecraft:horse ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:1025}
-summon minecraft:horse ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:1026}
-summon minecraft:horse ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:1027}
-summon minecraft:horse ~16 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:1028}
-summon minecraft:horse ~20 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:1029}
-summon minecraft:horse ~24 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:1030}
+summon minecraft:horse ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:1025}
+summon minecraft:horse ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:1026}
+summon minecraft:horse ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:1027}
+summon minecraft:horse ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:1028}
+summon minecraft:horse ~15 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:1029}
+summon minecraft:horse ~18 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:1030}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/llama.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/llama.mcfunction
index f49ccde..a05a298 100644
--- a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/llama.mcfunction
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/llama.mcfunction
@@ -1,24 +1,8 @@
-# llama (23 entities)
+# llama (7 entities)
summon minecraft:llama ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0}
-summon minecraft:llama ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:1}
-summon minecraft:llama ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:2}
-summon minecraft:llama ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:3}
-summon minecraft:llama ~16 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,ChestedHorse:1b}
-summon minecraft:llama ~20 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,Age:-24000}
-summon minecraft:llama ~24 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:2,Age:-24000}
-summon minecraft:llama ~28 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,equipment:{body:{id:"minecraft:white_carpet",count:1}}}
-summon minecraft:llama ~32 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,equipment:{body:{id:"minecraft:orange_carpet",count:1}}}
-summon minecraft:llama ~36 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,equipment:{body:{id:"minecraft:magenta_carpet",count:1}}}
-summon minecraft:llama ~40 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,equipment:{body:{id:"minecraft:light_blue_carpet",count:1}}}
-summon minecraft:llama ~44 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,equipment:{body:{id:"minecraft:yellow_carpet",count:1}}}
-summon minecraft:llama ~48 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,equipment:{body:{id:"minecraft:lime_carpet",count:1}}}
-summon minecraft:llama ~52 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,equipment:{body:{id:"minecraft:pink_carpet",count:1}}}
-summon minecraft:llama ~56 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,equipment:{body:{id:"minecraft:gray_carpet",count:1}}}
-summon minecraft:llama ~60 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,equipment:{body:{id:"minecraft:light_gray_carpet",count:1}}}
-summon minecraft:llama ~64 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,equipment:{body:{id:"minecraft:cyan_carpet",count:1}}}
-summon minecraft:llama ~68 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,equipment:{body:{id:"minecraft:purple_carpet",count:1}}}
-summon minecraft:llama ~72 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,equipment:{body:{id:"minecraft:blue_carpet",count:1}}}
-summon minecraft:llama ~76 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,equipment:{body:{id:"minecraft:brown_carpet",count:1}}}
-summon minecraft:llama ~80 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,equipment:{body:{id:"minecraft:green_carpet",count:1}}}
-summon minecraft:llama ~84 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,equipment:{body:{id:"minecraft:red_carpet",count:1}}}
-summon minecraft:llama ~88 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,equipment:{body:{id:"minecraft:black_carpet",count:1}}}
+summon minecraft:llama ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:1}
+summon minecraft:llama ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:2}
+summon minecraft:llama ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:3}
+summon minecraft:llama ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,ChestedHorse:1b}
+summon minecraft:llama ~15 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,Age:-24000}
+summon minecraft:llama ~18 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:2,Age:-24000}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/llama_carpet.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/llama_carpet.mcfunction
new file mode 100644
index 0000000..f22e7dc
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/llama_carpet.mcfunction
@@ -0,0 +1,17 @@
+# llama_carpet (16 entities)
+summon minecraft:llama ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,equipment:{body:{id:"minecraft:white_carpet",count:1}}}
+summon minecraft:llama ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,equipment:{body:{id:"minecraft:orange_carpet",count:1}}}
+summon minecraft:llama ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,equipment:{body:{id:"minecraft:magenta_carpet",count:1}}}
+summon minecraft:llama ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,equipment:{body:{id:"minecraft:light_blue_carpet",count:1}}}
+summon minecraft:llama ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,equipment:{body:{id:"minecraft:yellow_carpet",count:1}}}
+summon minecraft:llama ~15 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,equipment:{body:{id:"minecraft:lime_carpet",count:1}}}
+summon minecraft:llama ~18 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,equipment:{body:{id:"minecraft:pink_carpet",count:1}}}
+summon minecraft:llama ~21 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,equipment:{body:{id:"minecraft:gray_carpet",count:1}}}
+summon minecraft:llama ~24 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,equipment:{body:{id:"minecraft:light_gray_carpet",count:1}}}
+summon minecraft:llama ~27 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,equipment:{body:{id:"minecraft:cyan_carpet",count:1}}}
+summon minecraft:llama ~30 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,equipment:{body:{id:"minecraft:purple_carpet",count:1}}}
+summon minecraft:llama ~33 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,equipment:{body:{id:"minecraft:blue_carpet",count:1}}}
+summon minecraft:llama ~36 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,equipment:{body:{id:"minecraft:brown_carpet",count:1}}}
+summon minecraft:llama ~39 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,equipment:{body:{id:"minecraft:green_carpet",count:1}}}
+summon minecraft:llama ~42 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,equipment:{body:{id:"minecraft:red_carpet",count:1}}}
+summon minecraft:llama ~45 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,equipment:{body:{id:"minecraft:black_carpet",count:1}}}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/misc.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/misc.mcfunction
index 399eec5..dedc83f 100644
--- a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/misc.mcfunction
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/misc.mcfunction
@@ -1,5 +1,5 @@
# misc (4 entities)
summon minecraft:bat ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
-summon minecraft:allay ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
-summon minecraft:vex ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
-summon minecraft:breeze ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:allay ~2 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:vex ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:breeze ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/monster.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/monster.mcfunction
new file mode 100644
index 0000000..83e4ec6
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/monster.mcfunction
@@ -0,0 +1,16 @@
+# monster (15 entities)
+summon minecraft:creeper ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:spider ~2 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:cave_spider ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:silverfish ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:endermite ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:enderman ~10 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:blaze ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:witch ~14 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:evoker ~16 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:vindicator ~18 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:pillager ~20 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:illusioner ~22 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:phantom ~24 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:shulker ~26 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:creaking ~28 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/monster_big.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/monster_big.mcfunction
new file mode 100644
index 0000000..df1db1c
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/monster_big.mcfunction
@@ -0,0 +1,6 @@
+# monster_big (5 entities)
+summon minecraft:ravager ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:warden ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:wither ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:elder_guardian ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:giant ~16 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/mooshroom.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/mooshroom.mcfunction
new file mode 100644
index 0000000..ce3ffb0
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/mooshroom.mcfunction
@@ -0,0 +1,5 @@
+# mooshroom (4 entities)
+summon minecraft:mooshroom ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:red"}
+summon minecraft:mooshroom ~2 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:brown"}
+summon minecraft:mooshroom ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:red",Age:-24000}
+summon minecraft:mooshroom ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:brown",Age:-24000}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/ocelot.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/ocelot.mcfunction
index 36ba7d1..bc3f897 100644
--- a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/ocelot.mcfunction
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/ocelot.mcfunction
@@ -1,4 +1,4 @@
# ocelot (3 entities)
summon minecraft:ocelot ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
-summon minecraft:ocelot ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Sitting:1}
-summon minecraft:ocelot ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Age:-24000}
+summon minecraft:ocelot ~2 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Sitting:1b}
+summon minecraft:ocelot ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Age:-24000}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/parrot.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/parrot.mcfunction
new file mode 100644
index 0000000..cf7075b
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/parrot.mcfunction
@@ -0,0 +1,6 @@
+# parrot (5 entities)
+summon minecraft:parrot ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:red_blue"}
+summon minecraft:parrot ~2 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:blue"}
+summon minecraft:parrot ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:green"}
+summon minecraft:parrot ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:yellow_blue"}
+summon minecraft:parrot ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:gray"}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/pig.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/pig.mcfunction
index ffcf950..778d55d 100644
--- a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/pig.mcfunction
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/pig.mcfunction
@@ -1,8 +1,8 @@
# pig (7 entities)
summon minecraft:pig ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:temperate"}
-summon minecraft:pig ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:warm"}
-summon minecraft:pig ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:cold"}
-summon minecraft:pig ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:temperate",Age:-24000}
-summon minecraft:pig ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:warm",Age:-24000}
-summon minecraft:pig ~15 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:cold",Age:-24000}
-summon minecraft:pig ~18 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:temperate",Saddle:1b}
+summon minecraft:pig ~2 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:warm"}
+summon minecraft:pig ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:cold"}
+summon minecraft:pig ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:temperate",Age:-24000}
+summon minecraft:pig ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:warm",Age:-24000}
+summon minecraft:pig ~10 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:cold",Age:-24000}
+summon minecraft:pig ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:temperate",Saddle:1b}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/piglin.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/piglin.mcfunction
new file mode 100644
index 0000000..3aab06d
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/piglin.mcfunction
@@ -0,0 +1,6 @@
+# piglin (5 entities)
+summon minecraft:piglin ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:piglin ~2 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],IsBaby:1b}
+summon minecraft:piglin_brute ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:zombified_piglin ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:zombified_piglin ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],IsBaby:1b}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/prop.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/prop.mcfunction
new file mode 100644
index 0000000..cf04053
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/prop.mcfunction
@@ -0,0 +1,11 @@
+# prop (10 entities)
+summon minecraft:armor_stand ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:armor_stand ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Small:1b}
+summon minecraft:end_crystal ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:minecart ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:chest_minecart ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:furnace_minecart ~15 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:hopper_minecart ~18 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:tnt_minecart ~21 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:spawner_minecart ~24 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:command_block_minecart ~27 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/rabbit.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/rabbit.mcfunction
new file mode 100644
index 0000000..39d7b77
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/rabbit.mcfunction
@@ -0,0 +1,9 @@
+# rabbit (8 entities)
+summon minecraft:rabbit ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:brown"}
+summon minecraft:rabbit ~2 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:white"}
+summon minecraft:rabbit ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:black"}
+summon minecraft:rabbit ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:white_splotched"}
+summon minecraft:rabbit ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:gold"}
+summon minecraft:rabbit ~10 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:salt"}
+summon minecraft:rabbit ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:evil"}
+summon minecraft:rabbit ~14 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:brown",Age:-24000}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/salmon.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/salmon.mcfunction
new file mode 100644
index 0000000..cd031ad
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/salmon.mcfunction
@@ -0,0 +1,7 @@
+# salmon (6 entities)
+summon minecraft:salmon ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],type:"small"}
+summon minecraft:salmon ~2 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],type:"medium"}
+summon minecraft:salmon ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],type:"large"}
+summon minecraft:pufferfish ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],PuffState:0}
+summon minecraft:pufferfish ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],PuffState:1}
+summon minecraft:pufferfish ~10 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],PuffState:2}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/sheep.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/sheep.mcfunction
index de7ee98..b2aa84c 100644
--- a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/sheep.mcfunction
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/sheep.mcfunction
@@ -1,17 +1,17 @@
# sheep (16 entities)
summon minecraft:sheep ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:0b}
-summon minecraft:sheep ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:1b}
-summon minecraft:sheep ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:2b}
-summon minecraft:sheep ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:3b}
-summon minecraft:sheep ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:4b}
-summon minecraft:sheep ~15 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:5b}
-summon minecraft:sheep ~18 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:6b}
-summon minecraft:sheep ~21 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:7b}
-summon minecraft:sheep ~24 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:8b}
-summon minecraft:sheep ~27 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:9b}
-summon minecraft:sheep ~30 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:10b}
-summon minecraft:sheep ~33 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:11b}
-summon minecraft:sheep ~36 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:12b}
-summon minecraft:sheep ~39 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:13b}
-summon minecraft:sheep ~42 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:14b}
-summon minecraft:sheep ~45 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:15b}
+summon minecraft:sheep ~2 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:1b}
+summon minecraft:sheep ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:2b}
+summon minecraft:sheep ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:3b}
+summon minecraft:sheep ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:4b}
+summon minecraft:sheep ~10 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:5b}
+summon minecraft:sheep ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:6b}
+summon minecraft:sheep ~14 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:7b}
+summon minecraft:sheep ~16 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:8b}
+summon minecraft:sheep ~18 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:9b}
+summon minecraft:sheep ~20 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:10b}
+summon minecraft:sheep ~22 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:11b}
+summon minecraft:sheep ~24 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:12b}
+summon minecraft:sheep ~26 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:13b}
+summon minecraft:sheep ~28 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:14b}
+summon minecraft:sheep ~30 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:15b}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/sheep_states.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/sheep_states.mcfunction
index a221624..93e62dc 100644
--- a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/sheep_states.mcfunction
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/sheep_states.mcfunction
@@ -1,8 +1,8 @@
# sheep_states (7 entities)
summon minecraft:sheep ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:0b,Sheared:1b}
-summon minecraft:sheep ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:15b,Sheared:1b}
-summon minecraft:sheep ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:14b,Sheared:1b}
-summon minecraft:sheep ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:0b,Age:-24000}
-summon minecraft:sheep ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:4b,Age:-24000}
-summon minecraft:sheep ~15 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:14b,Age:-24000}
-summon minecraft:sheep ~18 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:0b,Sheared:1b,Age:-24000}
+summon minecraft:sheep ~2 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:15b,Sheared:1b}
+summon minecraft:sheep ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:14b,Sheared:1b}
+summon minecraft:sheep ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:0b,Age:-24000}
+summon minecraft:sheep ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:4b,Age:-24000}
+summon minecraft:sheep ~10 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:14b,Age:-24000}
+summon minecraft:sheep ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Color:0b,Sheared:1b,Age:-24000}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/skeleton.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/skeleton.mcfunction
index 56fc6bb..a8e4c65 100644
--- a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/skeleton.mcfunction
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/skeleton.mcfunction
@@ -1,6 +1,6 @@
# skeleton (5 entities)
summon minecraft:skeleton ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
-summon minecraft:wither_skeleton ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
-summon minecraft:stray ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
-summon minecraft:bogged ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
-summon minecraft:parched ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:wither_skeleton ~2 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:stray ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:bogged ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:parched ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/trader_llama.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/trader_llama.mcfunction
index 4ee7db9..af42b64 100644
--- a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/trader_llama.mcfunction
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/trader_llama.mcfunction
@@ -1,7 +1,7 @@
# trader_llama (6 entities)
summon minecraft:trader_llama ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0}
-summon minecraft:trader_llama ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:1}
-summon minecraft:trader_llama ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:2}
-summon minecraft:trader_llama ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:3}
-summon minecraft:trader_llama ~16 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,ChestedHorse:1b}
-summon minecraft:trader_llama ~20 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,Age:-24000}
+summon minecraft:trader_llama ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:1}
+summon minecraft:trader_llama ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:2}
+summon minecraft:trader_llama ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:3}
+summon minecraft:trader_llama ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,ChestedHorse:1b}
+summon minecraft:trader_llama ~15 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0,Age:-24000}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/tropical_fish.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/tropical_fish.mcfunction
index 9222f6d..3a2fdf4 100644
--- a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/tropical_fish.mcfunction
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/tropical_fish.mcfunction
@@ -1,13 +1,13 @@
-# tropical_fish (12 entities, every pattern of both body-sizes)
+# tropical_fish (12 entities)
summon minecraft:tropical_fish ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:251723776}
-summon minecraft:tropical_fish ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:262400}
-summon minecraft:tropical_fish ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:251986432}
-summon minecraft:tropical_fish ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:590592}
-summon minecraft:tropical_fish ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:252380160}
-summon minecraft:tropical_fish ~15 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:918784}
-summon minecraft:tropical_fish ~18 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:251723777}
-summon minecraft:tropical_fish ~21 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:262401}
-summon minecraft:tropical_fish ~24 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:251986433}
-summon minecraft:tropical_fish ~27 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:590593}
-summon minecraft:tropical_fish ~30 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:252380161}
-summon minecraft:tropical_fish ~33 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:918785}
+summon minecraft:tropical_fish ~2 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:262400}
+summon minecraft:tropical_fish ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:251986432}
+summon minecraft:tropical_fish ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:590592}
+summon minecraft:tropical_fish ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:252380160}
+summon minecraft:tropical_fish ~10 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:918784}
+summon minecraft:tropical_fish ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:251723777}
+summon minecraft:tropical_fish ~14 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:262401}
+summon minecraft:tropical_fish ~16 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:251986433}
+summon minecraft:tropical_fish ~18 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:590593}
+summon minecraft:tropical_fish ~20 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:252380161}
+summon minecraft:tropical_fish ~22 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:918785}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/undead_horse.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/undead_horse.mcfunction
index 134e25d..8fff1db 100644
--- a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/undead_horse.mcfunction
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/undead_horse.mcfunction
@@ -1,5 +1,5 @@
# undead_horse (4 entities)
summon minecraft:skeleton_horse ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
-summon minecraft:skeleton_horse ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Age:-24000}
-summon minecraft:zombie_horse ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
-summon minecraft:zombie_horse ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Age:-24000}
+summon minecraft:skeleton_horse ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Age:-24000}
+summon minecraft:zombie_horse ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:zombie_horse ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Age:-24000}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/villager.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/villager.mcfunction
new file mode 100644
index 0000000..9e3b134
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/villager.mcfunction
@@ -0,0 +1,6 @@
+# villager (5 entities)
+summon minecraft:villager ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:villager ~2 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Age:-24000}
+summon minecraft:zombie_villager ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:zombie_villager ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],IsBaby:1b}
+summon minecraft:wandering_trader ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/wolf.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/wolf.mcfunction
new file mode 100644
index 0000000..3adc8e4
--- /dev/null
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/wolf.mcfunction
@@ -0,0 +1,12 @@
+# wolf (11 entities)
+summon minecraft:wolf ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:pale"}
+summon minecraft:wolf ~2 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:ashen"}
+summon minecraft:wolf ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:black"}
+summon minecraft:wolf ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:chestnut"}
+summon minecraft:wolf ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:rusty"}
+summon minecraft:wolf ~10 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:snowy"}
+summon minecraft:wolf ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:spotted"}
+summon minecraft:wolf ~14 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:striped"}
+summon minecraft:wolf ~16 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:woods"}
+summon minecraft:wolf ~18 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:pale",Age:-24000}
+summon minecraft:wolf ~20 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:pale",Owner:[I;1,2,3,4]}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/zombie.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/zombie.mcfunction
index b085d1e..139591f 100644
--- a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/zombie.mcfunction
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/zombie.mcfunction
@@ -1,7 +1,7 @@
# zombie (6 entities)
summon minecraft:zombie ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
-summon minecraft:zombie ~3 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],IsBaby:1b}
-summon minecraft:husk ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
-summon minecraft:husk ~9 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],IsBaby:1b}
-summon minecraft:drowned ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
-summon minecraft:drowned ~15 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],IsBaby:1b}
+summon minecraft:zombie ~2 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],IsBaby:1b}
+summon minecraft:husk ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:husk ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],IsBaby:1b}
+summon minecraft:drowned ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f]}
+summon minecraft:drowned ~10 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],IsBaby:1b}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/platform.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/platform.mcfunction
index 2e80c79..804f5c6 100644
--- a/generator/datapack/bluemap-entity-test/data/bme_test/function/platform.mcfunction
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/platform.mcfunction
@@ -1,3 +1,3 @@
-fill ~-2 ~-1 ~-2 ~90 ~-1 ~147 minecraft:white_concrete
-fill ~-2 ~ ~-2 ~90 ~8 ~147 minecraft:air
+fill ~-2 ~-1 ~-2 ~47 ~-1 ~193 minecraft:white_concrete
+fill ~-2 ~ ~-2 ~47 ~10 ~193 minecraft:air
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/spawn.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/spawn.mcfunction
index 8c86dac..7c60d6b 100644
--- a/generator/datapack/bluemap-entity-test/data/bme_test/function/spawn.mcfunction
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/spawn.mcfunction
@@ -2,35 +2,53 @@
function bme_test:kill
execute positioned ~ ~ ~0 run function bme_test:group/sheep
-execute positioned ~ ~ ~4 run function bme_test:group/sheep_states
-execute positioned ~ ~ ~8 run function bme_test:group/cow
+execute positioned ~ ~ ~3 run function bme_test:group/sheep_states
+execute positioned ~ ~ ~6 run function bme_test:group/cow
+execute positioned ~ ~ ~9 run function bme_test:group/mooshroom
execute positioned ~ ~ ~12 run function bme_test:group/pig
-execute positioned ~ ~ ~16 run function bme_test:group/chicken
-execute positioned ~ ~ ~20 run function bme_test:group/cat
-execute positioned ~ ~ ~24 run function bme_test:group/ocelot
-execute positioned ~ ~ ~28 run function bme_test:group/fox
-execute positioned ~ ~ ~32 run function bme_test:group/frog
-execute positioned ~ ~ ~36 run function bme_test:group/axolotl
-execute positioned ~ ~ ~40 run function bme_test:group/bee
-execute positioned ~ ~ ~44 run function bme_test:group/llama
-execute positioned ~ ~ ~49 run function bme_test:group/trader_llama
-execute positioned ~ ~ ~54 run function bme_test:group/horse_markings_0
-execute positioned ~ ~ ~59 run function bme_test:group/horse_markings_1
-execute positioned ~ ~ ~64 run function bme_test:group/horse_markings_2
-execute positioned ~ ~ ~69 run function bme_test:group/horse_markings_3
-execute positioned ~ ~ ~74 run function bme_test:group/horse_markings_4
-execute positioned ~ ~ ~79 run function bme_test:group/horse_baby
-execute positioned ~ ~ ~84 run function bme_test:group/chested_horse
-execute positioned ~ ~ ~89 run function bme_test:group/undead_horse
-execute positioned ~ ~ ~94 run function bme_test:group/zombie
-execute positioned ~ ~ ~98 run function bme_test:group/skeleton
-execute positioned ~ ~ ~102 run function bme_test:group/armadillo
-execute positioned ~ ~ ~106 run function bme_test:group/camel
-execute positioned ~ ~ ~111 run function bme_test:group/copper_golem
-execute positioned ~ ~ ~115 run function bme_test:group/golem
-execute positioned ~ ~ ~120 run function bme_test:group/aquatic
-execute positioned ~ ~ ~124 run function bme_test:group/tropical_fish
-execute positioned ~ ~ ~128 run function bme_test:group/ghast
-execute positioned ~ ~ ~142 run function bme_test:group/misc
+execute positioned ~ ~ ~15 run function bme_test:group/chicken
+execute positioned ~ ~ ~18 run function bme_test:group/rabbit
+execute positioned ~ ~ ~21 run function bme_test:group/parrot
+execute positioned ~ ~ ~24 run function bme_test:group/cat
+execute positioned ~ ~ ~27 run function bme_test:group/cat_collar
+execute positioned ~ ~ ~30 run function bme_test:group/ocelot
+execute positioned ~ ~ ~33 run function bme_test:group/fox
+execute positioned ~ ~ ~36 run function bme_test:group/wolf
+execute positioned ~ ~ ~39 run function bme_test:group/beast
+execute positioned ~ ~ ~43 run function bme_test:group/frog
+execute positioned ~ ~ ~46 run function bme_test:group/axolotl
+execute positioned ~ ~ ~49 run function bme_test:group/bee
+execute positioned ~ ~ ~52 run function bme_test:group/llama
+execute positioned ~ ~ ~56 run function bme_test:group/llama_carpet
+execute positioned ~ ~ ~60 run function bme_test:group/trader_llama
+execute positioned ~ ~ ~64 run function bme_test:group/horse_markings_0
+execute positioned ~ ~ ~68 run function bme_test:group/horse_markings_1
+execute positioned ~ ~ ~72 run function bme_test:group/horse_markings_2
+execute positioned ~ ~ ~76 run function bme_test:group/horse_markings_3
+execute positioned ~ ~ ~80 run function bme_test:group/horse_markings_4
+execute positioned ~ ~ ~84 run function bme_test:group/horse_baby
+execute positioned ~ ~ ~88 run function bme_test:group/chested_horse
+execute positioned ~ ~ ~92 run function bme_test:group/undead_horse
+execute positioned ~ ~ ~96 run function bme_test:group/camel
+execute positioned ~ ~ ~101 run function bme_test:group/armadillo
+execute positioned ~ ~ ~104 run function bme_test:group/zombie
+execute positioned ~ ~ ~107 run function bme_test:group/skeleton
+execute positioned ~ ~ ~110 run function bme_test:group/piglin
+execute positioned ~ ~ ~113 run function bme_test:group/hoglin
+execute positioned ~ ~ ~117 run function bme_test:group/villager
+execute positioned ~ ~ ~120 run function bme_test:group/monster
+execute positioned ~ ~ ~123 run function bme_test:group/monster_big
+execute positioned ~ ~ ~128 run function bme_test:group/cube
+execute positioned ~ ~ ~132 run function bme_test:group/copper_golem
+execute positioned ~ ~ ~135 run function bme_test:group/golem
+execute positioned ~ ~ ~139 run function bme_test:group/aquatic
+execute positioned ~ ~ ~142 run function bme_test:group/salmon
+execute positioned ~ ~ ~145 run function bme_test:group/tropical_fish
+execute positioned ~ ~ ~148 run function bme_test:group/ghast
+execute positioned ~ ~ ~158 run function bme_test:group/misc
+execute positioned ~ ~ ~161 run function bme_test:group/ender_dragon
+execute positioned ~ ~ ~181 run function bme_test:group/prop
+execute positioned ~ ~ ~185 run function bme_test:group/boat
+execute positioned ~ ~ ~189 run function bme_test:group/chest_boat
-tellraw @s {"text":"[bme] spawned 214 test-entities in 31 groups","color":"green"}
+tellraw @s {"text":"[bme] spawned 350 test-entities in 49 groups","color":"green"}
From 230b0c0da0b72c5d597c3efa376af33170728816 Mon Sep 17 00:00:00 2001
From: miraculixx
Date: Wed, 12 Aug 2026 19:20:24 +0200
Subject: [PATCH 17/34] Fix variant naming
---
.../bme_test/function/group/mooshroom.mcfunction | 8 ++++----
.../bme_test/function/group/parrot.mcfunction | 10 +++++-----
.../bme_test/function/group/rabbit.mcfunction | 16 ++++++++--------
3 files changed, 17 insertions(+), 17 deletions(-)
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/mooshroom.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/mooshroom.mcfunction
index ce3ffb0..df98bac 100644
--- a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/mooshroom.mcfunction
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/mooshroom.mcfunction
@@ -1,5 +1,5 @@
# mooshroom (4 entities)
-summon minecraft:mooshroom ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:red"}
-summon minecraft:mooshroom ~2 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:brown"}
-summon minecraft:mooshroom ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:red",Age:-24000}
-summon minecraft:mooshroom ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:brown",Age:-24000}
+summon minecraft:mooshroom ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Type:"red"}
+summon minecraft:mooshroom ~2 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Type:"brown"}
+summon minecraft:mooshroom ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Type:"red",Age:-24000}
+summon minecraft:mooshroom ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Type:"brown",Age:-24000}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/parrot.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/parrot.mcfunction
index cf7075b..85177bf 100644
--- a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/parrot.mcfunction
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/parrot.mcfunction
@@ -1,6 +1,6 @@
# parrot (5 entities)
-summon minecraft:parrot ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:red_blue"}
-summon minecraft:parrot ~2 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:blue"}
-summon minecraft:parrot ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:green"}
-summon minecraft:parrot ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:yellow_blue"}
-summon minecraft:parrot ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:gray"}
+summon minecraft:parrot ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:0}
+summon minecraft:parrot ~2 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:1}
+summon minecraft:parrot ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:2}
+summon minecraft:parrot ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:3}
+summon minecraft:parrot ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],Variant:4}
diff --git a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/rabbit.mcfunction b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/rabbit.mcfunction
index 39d7b77..f41c3bc 100644
--- a/generator/datapack/bluemap-entity-test/data/bme_test/function/group/rabbit.mcfunction
+++ b/generator/datapack/bluemap-entity-test/data/bme_test/function/group/rabbit.mcfunction
@@ -1,9 +1,9 @@
# rabbit (8 entities)
-summon minecraft:rabbit ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:brown"}
-summon minecraft:rabbit ~2 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:white"}
-summon minecraft:rabbit ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:black"}
-summon minecraft:rabbit ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:white_splotched"}
-summon minecraft:rabbit ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:gold"}
-summon minecraft:rabbit ~10 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:salt"}
-summon minecraft:rabbit ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:evil"}
-summon minecraft:rabbit ~14 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],variant:"minecraft:brown",Age:-24000}
+summon minecraft:rabbit ~0 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],RabbitType:0}
+summon minecraft:rabbit ~2 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],RabbitType:1}
+summon minecraft:rabbit ~4 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],RabbitType:2}
+summon minecraft:rabbit ~6 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],RabbitType:3}
+summon minecraft:rabbit ~8 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],RabbitType:4}
+summon minecraft:rabbit ~10 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],RabbitType:5}
+summon minecraft:rabbit ~12 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],RabbitType:99}
+summon minecraft:rabbit ~14 ~ ~ {Tags:["bme_test"],NoAI:1b,Silent:1b,Invulnerable:1b,PersistenceRequired:1b,Rotation:[0f,0f],RabbitType:0,Age:-24000}
From 35e4d4dfbf0b44e08a13ac7a2bb3c7b1d6ee63e5 Mon Sep 17 00:00:00 2001
From: miraculixx
Date: Wed, 12 Aug 2026 19:21:37 +0200
Subject: [PATCH 18/34] Render cat collars
---
.../de/bluecolored/bluemap/entities/entity/Cat.java | 6 ++++++
.../bluemap/entities/renderer/CatRenderer.java | 11 +++++++++++
2 files changed, 17 insertions(+)
diff --git a/src/main/java/de/bluecolored/bluemap/entities/entity/Cat.java b/src/main/java/de/bluecolored/bluemap/entities/entity/Cat.java
index 529c0c9..024a534 100644
--- a/src/main/java/de/bluecolored/bluemap/entities/entity/Cat.java
+++ b/src/main/java/de/bluecolored/bluemap/entities/entity/Cat.java
@@ -36,4 +36,10 @@
public class Cat extends AgeVariantEntity {
@NBTName("Sitting") boolean sitting;
+ @NBTName("CollarColor") byte collarColor;
+ @NBTName("Owner") int[] owner;
+
+ public boolean isTame() {
+ return owner != null && owner.length > 0;
+ }
}
diff --git a/src/main/java/de/bluecolored/bluemap/entities/renderer/CatRenderer.java b/src/main/java/de/bluecolored/bluemap/entities/renderer/CatRenderer.java
index 296e274..039e0fb 100644
--- a/src/main/java/de/bluecolored/bluemap/entities/renderer/CatRenderer.java
+++ b/src/main/java/de/bluecolored/bluemap/entities/renderer/CatRenderer.java
@@ -34,6 +34,7 @@
import de.bluecolored.bluemap.core.util.Key;
import de.bluecolored.bluemap.core.world.Entity;
import de.bluecolored.bluemap.core.world.block.BlockNeighborhood;
+import de.bluecolored.bluemap.entities.data.DyeColors;
import de.bluecolored.bluemap.entities.entity.Cat;
public class CatRenderer extends CustomResourceModelRenderer {
@@ -53,6 +54,16 @@ public void render(Entity entity, BlockNeighborhood block, Part part, TileModelV
// render chosen model
super.render(entity, block, model.getResource(getModelProvider()), TintColorProvider.NO_TINT, tileModel);
+ // render collar layer tinted color
+ if (cat.isTame()) {
+ int collarColor = DyeColors.argb(cat.getCollarColor());
+ TintColorProvider collarTint = (index, target) -> target.set(collarColor, true);
+
+ String collarPath = "entity/cat" + (cat.getAge() < 0 ? "_baby" : "") + "/collar";
+ ResourcePath collar = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, collarPath);
+ super.render(entity, block, collar.getResource(getModelProvider()), collarTint, tileModel);
+ }
+
// apply part transform
if (part.isTransformed())
tileModel.transform(part.getTransformMatrix());
From 23245dd18c244d7d00953d7705a906beb2ed4a75 Mon Sep 17 00:00:00 2001
From: miraculixx
Date: Wed, 12 Aug 2026 19:22:49 +0200
Subject: [PATCH 19/34] Add missing mobs & props
---
.../bluecolored/bluemap/entities/Addon.java | 31 +++++++++
.../bluemap/entities/data/DyeColors.java | 44 +++++++++++++
.../bluemap/entities/entity/ArmorStand.java | 40 ++++++++++++
.../bluemap/entities/entity/BabyEntity.java | 40 ++++++++++++
.../bluemap/entities/entity/Mooshroom.java | 45 +++++++++++++
.../bluemap/entities/entity/Parrot.java | 51 +++++++++++++++
.../bluemap/entities/entity/Pufferfish.java | 49 ++++++++++++++
.../bluemap/entities/entity/Rabbit.java | 53 +++++++++++++++
.../bluemap/entities/entity/Salmon.java | 50 ++++++++++++++
.../bluemap/entities/entity/Wolf.java | 44 +++++++++++++
.../bluemap/entities/entity/Zombie.java | 12 +---
.../entities/renderer/AgeRenderer.java | 60 +++++++++++++++++
.../entities/renderer/ArmorStandRenderer.java | 59 +++++++++++++++++
.../entities/renderer/BabyRenderer.java | 60 +++++++++++++++++
.../renderer/CustomResourceModelRenderer.java | 6 ++
.../entities/renderer/PufferfishRenderer.java | 57 ++++++++++++++++
.../entities/renderer/SalmonRenderer.java | 59 +++++++++++++++++
.../entities/renderer/VariantRenderer.java | 60 +++++++++++++++++
.../entities/renderer/WolfRenderer.java | 65 +++++++++++++++++++
.../minecraft/entitystates/acacia_boat.json | 5 ++
.../entitystates/acacia_chest_boat.json | 5 ++
.../minecraft/entitystates/armor_stand.json | 5 ++
.../entitystates/bamboo_chest_raft.json | 5 ++
.../minecraft/entitystates/bamboo_raft.json | 5 ++
.../minecraft/entitystates/birch_boat.json | 5 ++
.../entitystates/birch_chest_boat.json | 5 ++
.../assets/minecraft/entitystates/blaze.json | 5 ++
.../minecraft/entitystates/cave_spider.json | 5 ++
.../minecraft/entitystates/cherry_boat.json | 5 ++
.../entitystates/cherry_chest_boat.json | 5 ++
.../entitystates/chest_minecart.json | 5 ++
.../entitystates/command_block_minecart.json | 5 ++
.../minecraft/entitystates/creaking.json | 6 ++
.../minecraft/entitystates/creeper.json | 5 ++
.../minecraft/entitystates/dark_oak_boat.json | 5 ++
.../entitystates/dark_oak_chest_boat.json | 5 ++
.../entitystates/elder_guardian.json | 5 ++
.../minecraft/entitystates/end_crystal.json | 5 ++
.../minecraft/entitystates/ender_dragon.json | 5 ++
.../minecraft/entitystates/enderman.json | 5 ++
.../minecraft/entitystates/endermite.json | 5 ++
.../assets/minecraft/entitystates/evoker.json | 5 ++
.../minecraft/entitystates/evoker_fangs.json | 5 ++
.../entitystates/furnace_minecart.json | 5 ++
.../assets/minecraft/entitystates/giant.json | 5 ++
.../assets/minecraft/entitystates/goat.json | 5 ++
.../assets/minecraft/entitystates/hoglin.json | 5 ++
.../entitystates/hopper_minecart.json | 5 ++
.../minecraft/entitystates/illusioner.json | 5 ++
.../minecraft/entitystates/jungle_boat.json | 5 ++
.../entitystates/jungle_chest_boat.json | 5 ++
.../minecraft/entitystates/leash_knot.json | 5 ++
.../minecraft/entitystates/magma_cube.json | 5 ++
.../minecraft/entitystates/mangrove_boat.json | 5 ++
.../entitystates/mangrove_chest_boat.json | 5 ++
.../minecraft/entitystates/minecart.json | 5 ++
.../minecraft/entitystates/mooshroom.json | 5 ++
.../minecraft/entitystates/nautilus.json | 5 ++
.../minecraft/entitystates/oak_boat.json | 5 ++
.../entitystates/oak_chest_boat.json | 5 ++
.../minecraft/entitystates/pale_oak_boat.json | 5 ++
.../entitystates/pale_oak_chest_boat.json | 5 ++
.../assets/minecraft/entitystates/panda.json | 5 ++
.../assets/minecraft/entitystates/parrot.json | 5 ++
.../minecraft/entitystates/phantom.json | 5 ++
.../assets/minecraft/entitystates/piglin.json | 5 ++
.../minecraft/entitystates/piglin_brute.json | 5 ++
.../minecraft/entitystates/pillager.json | 5 ++
.../minecraft/entitystates/polar_bear.json | 5 ++
.../assets/minecraft/entitystates/rabbit.json | 5 ++
.../minecraft/entitystates/ravager.json | 5 ++
.../minecraft/entitystates/shulker.json | 5 ++
.../minecraft/entitystates/silverfish.json | 5 ++
.../assets/minecraft/entitystates/slime.json | 6 ++
.../minecraft/entitystates/sniffer.json | 5 ++
.../entitystates/spawner_minecart.json | 5 ++
.../assets/minecraft/entitystates/spider.json | 5 ++
.../minecraft/entitystates/spruce_boat.json | 5 ++
.../entitystates/spruce_chest_boat.json | 5 ++
.../minecraft/entitystates/strider.json | 5 ++
.../minecraft/entitystates/sulfur_cube.json | 5 ++
.../minecraft/entitystates/tnt_minecart.json | 5 ++
.../assets/minecraft/entitystates/turtle.json | 5 ++
.../minecraft/entitystates/villager.json | 5 ++
.../minecraft/entitystates/vindicator.json | 5 ++
.../entitystates/wandering_trader.json | 5 ++
.../assets/minecraft/entitystates/warden.json | 5 ++
.../assets/minecraft/entitystates/witch.json | 5 ++
.../assets/minecraft/entitystates/wither.json | 5 ++
.../assets/minecraft/entitystates/wolf.json | 5 ++
.../assets/minecraft/entitystates/zoglin.json | 5 ++
.../entitystates/zombie_nautilus.json | 5 ++
.../entitystates/zombie_villager.json | 5 ++
.../entitystates/zombified_piglin.json | 5 ++
94 files changed, 1253 insertions(+), 9 deletions(-)
create mode 100644 src/main/java/de/bluecolored/bluemap/entities/data/DyeColors.java
create mode 100644 src/main/java/de/bluecolored/bluemap/entities/entity/ArmorStand.java
create mode 100644 src/main/java/de/bluecolored/bluemap/entities/entity/BabyEntity.java
create mode 100644 src/main/java/de/bluecolored/bluemap/entities/entity/Mooshroom.java
create mode 100644 src/main/java/de/bluecolored/bluemap/entities/entity/Parrot.java
create mode 100644 src/main/java/de/bluecolored/bluemap/entities/entity/Pufferfish.java
create mode 100644 src/main/java/de/bluecolored/bluemap/entities/entity/Rabbit.java
create mode 100644 src/main/java/de/bluecolored/bluemap/entities/entity/Salmon.java
create mode 100644 src/main/java/de/bluecolored/bluemap/entities/entity/Wolf.java
create mode 100644 src/main/java/de/bluecolored/bluemap/entities/renderer/AgeRenderer.java
create mode 100644 src/main/java/de/bluecolored/bluemap/entities/renderer/ArmorStandRenderer.java
create mode 100644 src/main/java/de/bluecolored/bluemap/entities/renderer/BabyRenderer.java
create mode 100644 src/main/java/de/bluecolored/bluemap/entities/renderer/PufferfishRenderer.java
create mode 100644 src/main/java/de/bluecolored/bluemap/entities/renderer/SalmonRenderer.java
create mode 100644 src/main/java/de/bluecolored/bluemap/entities/renderer/VariantRenderer.java
create mode 100644 src/main/java/de/bluecolored/bluemap/entities/renderer/WolfRenderer.java
create mode 100644 src/main/resources/assets/minecraft/entitystates/acacia_boat.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/acacia_chest_boat.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/armor_stand.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/bamboo_chest_raft.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/bamboo_raft.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/birch_boat.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/birch_chest_boat.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/blaze.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/cave_spider.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/cherry_boat.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/cherry_chest_boat.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/chest_minecart.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/command_block_minecart.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/creaking.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/creeper.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/dark_oak_boat.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/dark_oak_chest_boat.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/elder_guardian.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/end_crystal.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/ender_dragon.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/enderman.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/endermite.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/evoker.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/evoker_fangs.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/furnace_minecart.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/giant.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/goat.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/hoglin.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/hopper_minecart.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/illusioner.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/jungle_boat.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/jungle_chest_boat.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/leash_knot.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/magma_cube.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/mangrove_boat.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/mangrove_chest_boat.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/minecart.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/mooshroom.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/nautilus.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/oak_boat.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/oak_chest_boat.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/pale_oak_boat.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/pale_oak_chest_boat.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/panda.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/parrot.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/phantom.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/piglin.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/piglin_brute.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/pillager.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/polar_bear.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/rabbit.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/ravager.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/shulker.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/silverfish.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/slime.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/sniffer.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/spawner_minecart.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/spider.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/spruce_boat.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/spruce_chest_boat.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/strider.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/sulfur_cube.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/tnt_minecart.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/turtle.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/villager.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/vindicator.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/wandering_trader.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/warden.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/witch.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/wither.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/wolf.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/zoglin.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/zombie_nautilus.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/zombie_villager.json
create mode 100644 src/main/resources/assets/minecraft/entitystates/zombified_piglin.json
diff --git a/src/main/java/de/bluecolored/bluemap/entities/Addon.java b/src/main/java/de/bluecolored/bluemap/entities/Addon.java
index 45fc3d8..a28d429 100644
--- a/src/main/java/de/bluecolored/bluemap/entities/Addon.java
+++ b/src/main/java/de/bluecolored/bluemap/entities/Addon.java
@@ -14,6 +14,15 @@ public class Addon implements Runnable {
public static Logger LOGGER = Logger.getLogger("BlueMap Entities Addon");
+ /** all baby/adult models where "Age" is a number */
+ private static final String[] AGE_TYPES = {
+ "goat", "hoglin", "panda", "sniffer", "turtle", "strider",
+ "zoglin", "polar_bear", "nautilus", "villager"
+ };
+
+ /** all baby/dault models where "IsBaby" is a bool */
+ private static final String[] BABY_TYPES = { "piglin", "zombified_piglin", "zombie_villager" };
+
@Override
public void run() {
EntityType.REGISTRY.register(new EntityType.Impl(Key.minecraft("llama"), Llama.class));
@@ -52,6 +61,17 @@ public void run() {
EntityType.REGISTRY.register(new EntityType.Impl(Key.minecraft("frog"), AgeVariantEntity.class));
EntityType.REGISTRY.register(new EntityType.Impl(Key.minecraft("ghast"), MCAEntity.class));
EntityType.REGISTRY.register(new EntityType.Impl(Key.minecraft("happy_ghast"), HappyGhast.class));
+ for (String type : AGE_TYPES)
+ EntityType.REGISTRY.register(new EntityType.Impl(Key.minecraft(type), AgeEntity.class));
+ for (String type : BABY_TYPES)
+ EntityType.REGISTRY.register(new EntityType.Impl(Key.minecraft(type), BabyEntity.class));
+ EntityType.REGISTRY.register(new EntityType.Impl(Key.minecraft("mooshroom"), Mooshroom.class));
+ EntityType.REGISTRY.register(new EntityType.Impl(Key.minecraft("rabbit"), Rabbit.class));
+ EntityType.REGISTRY.register(new EntityType.Impl(Key.minecraft("parrot"), Parrot.class));
+ EntityType.REGISTRY.register(new EntityType.Impl(Key.minecraft("wolf"), Wolf.class));
+ EntityType.REGISTRY.register(new EntityType.Impl(Key.minecraft("salmon"), Salmon.class));
+ EntityType.REGISTRY.register(new EntityType.Impl(Key.minecraft("armor_stand"), ArmorStand.class));
+ EntityType.REGISTRY.register(new EntityType.Impl(Key.minecraft("pufferfish"), Pufferfish.class));
EntityRendererType.REGISTRY.register(new EntityRendererType.Impl(Key.minecraft("llama"), LlamaRenderer::new));
@@ -90,6 +110,17 @@ public void run() {
EntityRendererType.REGISTRY.register(new EntityRendererType.Impl(Key.minecraft("frog"), FrogRenderer::new));
EntityRendererType.REGISTRY.register(new EntityRendererType.Impl(Key.minecraft("ghast"), GhastRenderer::new));
EntityRendererType.REGISTRY.register(new EntityRendererType.Impl(Key.minecraft("happy_ghast"), GhastRenderer::new));
+ for (String type : AGE_TYPES)
+ EntityRendererType.REGISTRY.register(new EntityRendererType.Impl(Key.minecraft(type), AgeRenderer::new));
+ for (String type : BABY_TYPES)
+ EntityRendererType.REGISTRY.register(new EntityRendererType.Impl(Key.minecraft(type), BabyRenderer::new));
+ EntityRendererType.REGISTRY.register(new EntityRendererType.Impl(Key.minecraft("mooshroom"), VariantRenderer::new));
+ EntityRendererType.REGISTRY.register(new EntityRendererType.Impl(Key.minecraft("rabbit"), VariantRenderer::new));
+ EntityRendererType.REGISTRY.register(new EntityRendererType.Impl(Key.minecraft("parrot"), VariantRenderer::new));
+ EntityRendererType.REGISTRY.register(new EntityRendererType.Impl(Key.minecraft("wolf"), WolfRenderer::new));
+ EntityRendererType.REGISTRY.register(new EntityRendererType.Impl(Key.minecraft("salmon"), SalmonRenderer::new));
+ EntityRendererType.REGISTRY.register(new EntityRendererType.Impl(Key.minecraft("armor_stand"), ArmorStandRenderer::new));
+ EntityRendererType.REGISTRY.register(new EntityRendererType.Impl(Key.minecraft("pufferfish"), PufferfishRenderer::new));
}
}
diff --git a/src/main/java/de/bluecolored/bluemap/entities/data/DyeColors.java b/src/main/java/de/bluecolored/bluemap/entities/data/DyeColors.java
new file mode 100644
index 0000000..1512751
--- /dev/null
+++ b/src/main/java/de/bluecolored/bluemap/entities/data/DyeColors.java
@@ -0,0 +1,44 @@
+/*
+ * This file is part of BlueMap, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) Blue (Lukas Rieger)
+ * Copyright (c) contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package de.bluecolored.bluemap.entities.data;
+
+/**
+ * The 16 dye-colors as minecraft tints them on textures (collars, harnesses, ...).
+ */
+public final class DyeColors {
+
+ private static final int[] COLORS = {
+ 0xF9FFFE, 0xF9801D, 0xC74EBD, 0x3AB3DA,
+ 0xFED83D, 0x80C71F, 0xF38BAA, 0x474F52,
+ 0x9D9D97, 0x169C9C, 0x8932B8, 0x3C44AA,
+ 0x835432, 0x5E7C16, 0xB02E26, 0x1D1D21
+ };
+
+ private DyeColors() {}
+
+ public static int argb(int color) {
+ return 0xFF000000 | COLORS[Math.floorMod(color, COLORS.length)];
+ }
+}
diff --git a/src/main/java/de/bluecolored/bluemap/entities/entity/ArmorStand.java b/src/main/java/de/bluecolored/bluemap/entities/entity/ArmorStand.java
new file mode 100644
index 0000000..943c5e1
--- /dev/null
+++ b/src/main/java/de/bluecolored/bluemap/entities/entity/ArmorStand.java
@@ -0,0 +1,40 @@
+/*
+ * This file is part of BlueMap, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) Blue (Lukas Rieger)
+ * Copyright (c) contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package de.bluecolored.bluemap.entities.entity;
+
+import de.bluecolored.bluemap.core.world.mca.entity.MCAEntity;
+import de.bluecolored.bluenbt.NBTName;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.ToString;
+
+@Getter
+@EqualsAndHashCode(callSuper = true)
+@ToString
+@SuppressWarnings("FieldMayBeFinal")
+public class ArmorStand extends MCAEntity {
+
+ @NBTName("Small") boolean small;
+}
diff --git a/src/main/java/de/bluecolored/bluemap/entities/entity/BabyEntity.java b/src/main/java/de/bluecolored/bluemap/entities/entity/BabyEntity.java
new file mode 100644
index 0000000..be9318b
--- /dev/null
+++ b/src/main/java/de/bluecolored/bluemap/entities/entity/BabyEntity.java
@@ -0,0 +1,40 @@
+/*
+ * This file is part of BlueMap, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) Blue (Lukas Rieger)
+ * Copyright (c) contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package de.bluecolored.bluemap.entities.entity;
+
+import de.bluecolored.bluemap.core.world.mca.entity.MCAEntity;
+import de.bluecolored.bluenbt.NBTName;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.ToString;
+
+@Getter
+@EqualsAndHashCode(callSuper = true)
+@ToString
+@SuppressWarnings("FieldMayBeFinal")
+public class BabyEntity extends MCAEntity {
+
+ @NBTName("IsBaby") boolean isBaby;
+}
diff --git a/src/main/java/de/bluecolored/bluemap/entities/entity/Mooshroom.java b/src/main/java/de/bluecolored/bluemap/entities/entity/Mooshroom.java
new file mode 100644
index 0000000..4af0a69
--- /dev/null
+++ b/src/main/java/de/bluecolored/bluemap/entities/entity/Mooshroom.java
@@ -0,0 +1,45 @@
+/*
+ * This file is part of BlueMap, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) Blue (Lukas Rieger)
+ * Copyright (c) contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package de.bluecolored.bluemap.entities.entity;
+
+import de.bluecolored.bluenbt.NBTName;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.ToString;
+
+@Getter
+@EqualsAndHashCode(callSuper = true)
+@ToString
+@SuppressWarnings("FieldMayBeFinal")
+public class Mooshroom extends AgeVariantEntity {
+
+ @NBTName("Type") String type;
+
+ /** Mooshrooms keep their variant in "Type" and without a namespace. */
+ @Override
+ public String getRawVariant() {
+ return type == null || type.isEmpty() ? "red" : type;
+ }
+}
diff --git a/src/main/java/de/bluecolored/bluemap/entities/entity/Parrot.java b/src/main/java/de/bluecolored/bluemap/entities/entity/Parrot.java
new file mode 100644
index 0000000..eb665ee
--- /dev/null
+++ b/src/main/java/de/bluecolored/bluemap/entities/entity/Parrot.java
@@ -0,0 +1,51 @@
+/*
+ * This file is part of BlueMap, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) Blue (Lukas Rieger)
+ * Copyright (c) contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package de.bluecolored.bluemap.entities.entity;
+
+import de.bluecolored.bluenbt.NBTName;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.ToString;
+
+@Getter
+@EqualsAndHashCode(callSuper = true)
+@ToString
+@SuppressWarnings("FieldMayBeFinal")
+public class Parrot extends AgeVariantEntity {
+
+ @NBTName("Variant") int parrotVariant;
+
+ /** Parrots keep their variant as a number, the grey one is spelled the british way. */
+ @Override
+ public String getRawVariant() {
+ return switch (parrotVariant) {
+ case 1 -> "blue";
+ case 2 -> "green";
+ case 3 -> "yellow_blue";
+ case 4 -> "grey";
+ default -> "red_blue";
+ };
+ }
+}
diff --git a/src/main/java/de/bluecolored/bluemap/entities/entity/Pufferfish.java b/src/main/java/de/bluecolored/bluemap/entities/entity/Pufferfish.java
new file mode 100644
index 0000000..3a1350b
--- /dev/null
+++ b/src/main/java/de/bluecolored/bluemap/entities/entity/Pufferfish.java
@@ -0,0 +1,49 @@
+/*
+ * This file is part of BlueMap, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) Blue (Lukas Rieger)
+ * Copyright (c) contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package de.bluecolored.bluemap.entities.entity;
+
+import de.bluecolored.bluemap.core.world.mca.entity.MCAEntity;
+import de.bluecolored.bluenbt.NBTName;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.ToString;
+
+@Getter
+@EqualsAndHashCode(callSuper = true)
+@ToString
+@SuppressWarnings("FieldMayBeFinal")
+public class Pufferfish extends MCAEntity {
+
+ @NBTName("PuffState") int puffState;
+
+ /** The three puff-states are separate models instead of a variant-suffix. */
+ public String getModel() {
+ return switch (puffState) {
+ case 1 -> "pufferfish_medium";
+ case 2 -> "pufferfish_big";
+ default -> "pufferfish_small";
+ };
+ }
+}
diff --git a/src/main/java/de/bluecolored/bluemap/entities/entity/Rabbit.java b/src/main/java/de/bluecolored/bluemap/entities/entity/Rabbit.java
new file mode 100644
index 0000000..6da87f2
--- /dev/null
+++ b/src/main/java/de/bluecolored/bluemap/entities/entity/Rabbit.java
@@ -0,0 +1,53 @@
+/*
+ * This file is part of BlueMap, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) Blue (Lukas Rieger)
+ * Copyright (c) contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package de.bluecolored.bluemap.entities.entity;
+
+import de.bluecolored.bluenbt.NBTName;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.ToString;
+
+@Getter
+@EqualsAndHashCode(callSuper = true)
+@ToString
+@SuppressWarnings("FieldMayBeFinal")
+public class Rabbit extends AgeVariantEntity {
+
+ @NBTName("RabbitType") int rabbitType;
+
+ /** Rabbits keep their variant as a number, the killer-bunny is 99. */
+ @Override
+ public String getRawVariant() {
+ return switch (rabbitType) {
+ case 1 -> "white";
+ case 2 -> "black";
+ case 3 -> "white_splotched";
+ case 4 -> "gold";
+ case 5 -> "salt";
+ case 99 -> "caerbannog";
+ default -> "brown";
+ };
+ }
+}
diff --git a/src/main/java/de/bluecolored/bluemap/entities/entity/Salmon.java b/src/main/java/de/bluecolored/bluemap/entities/entity/Salmon.java
new file mode 100644
index 0000000..f9e6e41
--- /dev/null
+++ b/src/main/java/de/bluecolored/bluemap/entities/entity/Salmon.java
@@ -0,0 +1,50 @@
+/*
+ * This file is part of BlueMap, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) Blue (Lukas Rieger)
+ * Copyright (c) contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package de.bluecolored.bluemap.entities.entity;
+
+import de.bluecolored.bluemap.core.world.mca.entity.MCAEntity;
+import de.bluecolored.bluenbt.NBTName;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.ToString;
+
+@Getter
+@EqualsAndHashCode(callSuper = true)
+@ToString
+@SuppressWarnings("FieldMayBeFinal")
+public class Salmon extends MCAEntity {
+
+ @NBTName("type") String type;
+
+ /** The model-folder of this salmon, sizes are separate models instead of a variant-suffix. */
+ public String getModel() {
+ if (type == null) return "salmon";
+ return switch (type) {
+ case "small" -> "salmon_small";
+ case "large" -> "salmon_large";
+ default -> "salmon";
+ };
+ }
+}
diff --git a/src/main/java/de/bluecolored/bluemap/entities/entity/Wolf.java b/src/main/java/de/bluecolored/bluemap/entities/entity/Wolf.java
new file mode 100644
index 0000000..d48fc44
--- /dev/null
+++ b/src/main/java/de/bluecolored/bluemap/entities/entity/Wolf.java
@@ -0,0 +1,44 @@
+/*
+ * This file is part of BlueMap, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) Blue (Lukas Rieger)
+ * Copyright (c) contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package de.bluecolored.bluemap.entities.entity;
+
+import de.bluecolored.bluenbt.NBTName;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.ToString;
+
+@Getter
+@EqualsAndHashCode(callSuper = true)
+@ToString
+@SuppressWarnings("FieldMayBeFinal")
+public class Wolf extends AgeVariantEntity {
+
+ @NBTName("Owner") int[] owner;
+ @NBTName("CollarColor") byte collarColor;
+
+ public boolean isTame() {
+ return owner != null && owner.length > 0;
+ }
+}
diff --git a/src/main/java/de/bluecolored/bluemap/entities/entity/Zombie.java b/src/main/java/de/bluecolored/bluemap/entities/entity/Zombie.java
index ca52d36..0ead023 100644
--- a/src/main/java/de/bluecolored/bluemap/entities/entity/Zombie.java
+++ b/src/main/java/de/bluecolored/bluemap/entities/entity/Zombie.java
@@ -24,15 +24,9 @@
*/
package de.bluecolored.bluemap.entities.entity;
-import de.bluecolored.bluemap.core.world.mca.entity.MCAEntity;
-import de.bluecolored.bluenbt.NBTName;
-import lombok.Getter;
+import lombok.EqualsAndHashCode;
import lombok.ToString;
-@Getter
+@EqualsAndHashCode(callSuper = true)
@ToString
-@SuppressWarnings("FieldMayBeFinal")
-public class Zombie extends MCAEntity {
-
- @NBTName("IsBaby") boolean isBaby;
-}
+public class Zombie extends BabyEntity {}
diff --git a/src/main/java/de/bluecolored/bluemap/entities/renderer/AgeRenderer.java b/src/main/java/de/bluecolored/bluemap/entities/renderer/AgeRenderer.java
new file mode 100644
index 0000000..d6f20e1
--- /dev/null
+++ b/src/main/java/de/bluecolored/bluemap/entities/renderer/AgeRenderer.java
@@ -0,0 +1,60 @@
+/*
+ * This file is part of BlueMap, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) Blue (Lukas Rieger)
+ * Copyright (c) contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package de.bluecolored.bluemap.entities.renderer;
+
+import de.bluecolored.bluemap.core.map.TextureGallery;
+import de.bluecolored.bluemap.core.map.hires.RenderSettings;
+import de.bluecolored.bluemap.core.map.hires.TileModelView;
+import de.bluecolored.bluemap.core.resources.ResourcePath;
+import de.bluecolored.bluemap.core.resources.pack.resourcepack.ResourcePack;
+import de.bluecolored.bluemap.core.resources.pack.resourcepack.entitystate.Part;
+import de.bluecolored.bluemap.core.resources.pack.resourcepack.model.Model;
+import de.bluecolored.bluemap.core.util.Key;
+import de.bluecolored.bluemap.core.world.Entity;
+import de.bluecolored.bluemap.core.world.block.BlockNeighborhood;
+import de.bluecolored.bluemap.entities.entity.AgeEntity;
+
+public class AgeRenderer extends CustomResourceModelRenderer {
+
+ public AgeRenderer(ResourcePack resourcePack, TextureGallery textureGallery, RenderSettings renderSettings) {
+ super(resourcePack, textureGallery, renderSettings);
+ }
+
+ @Override
+ public void render(Entity entity, BlockNeighborhood block, Part part, TileModelView tileModel) {
+ if (!(entity instanceof AgeEntity aged)) return;
+
+ // model-folder is the entity-type itself, "entity/{type}{age}/main"
+ String modelPath = "entity/" + entity.getId().getValue() + (aged.getAge() < 0 ? "_baby" : "") + "/main";
+ ResourcePath model = new ResourcePath<>(Key.MINECRAFT_NAMESPACE, modelPath);
+
+ super.render(entity, block, model.getResource(getModelProvider()), TintColorProvider.NO_TINT, tileModel);
+
+ // apply part transform
+ if (part.isTransformed())
+ tileModel.transform(part.getTransformMatrix());
+ }
+
+}
diff --git a/src/main/java/de/bluecolored/bluemap/entities/renderer/ArmorStandRenderer.java b/src/main/java/de/bluecolored/bluemap/entities/renderer/ArmorStandRenderer.java
new file mode 100644
index 0000000..bb6cb8a
--- /dev/null
+++ b/src/main/java/de/bluecolored/bluemap/entities/renderer/ArmorStandRenderer.java
@@ -0,0 +1,59 @@
+/*
+ * This file is part of BlueMap, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) Blue (Lukas Rieger)
+ * Copyright (c) contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package de.bluecolored.bluemap.entities.renderer;
+
+import de.bluecolored.bluemap.core.map.TextureGallery;
+import de.bluecolored.bluemap.core.map.hires.RenderSettings;
+import de.bluecolored.bluemap.core.map.hires.TileModelView;
+import de.bluecolored.bluemap.core.resources.ResourcePath;
+import de.bluecolored.bluemap.core.resources.pack.resourcepack.ResourcePack;
+import de.bluecolored.bluemap.core.resources.pack.resourcepack.entitystate.Part;
+import de.bluecolored.bluemap.core.resources.pack.resourcepack.model.Model;
+import de.bluecolored.bluemap.core.util.Key;
+import de.bluecolored.bluemap.core.world.Entity;
+import de.bluecolored.bluemap.core.world.block.BlockNeighborhood;
+import de.bluecolored.bluemap.entities.entity.ArmorStand;
+
+public class ArmorStandRenderer extends CustomResourceModelRenderer {
+
+ public ArmorStandRenderer(ResourcePack resourcePack, TextureGallery textureGallery, RenderSettings renderSettings) {
+ super(resourcePack, textureGallery, renderSettings);
+ }
+
+ @Override
+ public void render(Entity entity, BlockNeighborhood block, Part part, TileModelView tileModel) {
+ if (!(entity instanceof ArmorStand armorStand)) return;
+
+ ResourcePath model = new ResourcePath<>(Key.MINECRAFT_NAMESPACE,
+ armorStand.isSmall() ? "entity/armor_stand_small/main" : "entity/armor_stand/main");
+
+ super.render(entity, block, model.getResource(getModelProvider()), TintColorProvider.NO_TINT, tileModel);
+
+ // apply part transform
+ if (part.isTransformed())
+ tileModel.transform(part.getTransformMatrix());
+ }
+
+}
diff --git a/src/main/java/de/bluecolored/bluemap/entities/renderer/BabyRenderer.java b/src/main/java/de/bluecolored/bluemap/entities/renderer/BabyRenderer.java
new file mode 100644
index 0000000..153aa8e
--- /dev/null
+++ b/src/main/java/de/bluecolored/bluemap/entities/renderer/BabyRenderer.java
@@ -0,0 +1,60 @@
+/*
+ * This file is part of BlueMap, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) Blue (Lukas Rieger)