diff --git a/core/src/main/java/org/geysermc/geyser/pack/ResourcePack.java b/core/src/main/java/org/geysermc/geyser/pack/ResourcePack.java index bef5c7418..55a95b53c 100644 --- a/core/src/main/java/org/geysermc/geyser/pack/ResourcePack.java +++ b/core/src/main/java/org/geysermc/geyser/pack/ResourcePack.java @@ -27,6 +27,7 @@ package org.geysermc.geyser.pack; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.event.lifecycle.GeyserLoadResourcePacksEvent; +import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.util.FileUtils; import org.geysermc.geyser.text.GeyserLocale; @@ -77,13 +78,18 @@ public class ResourcePack { } List resourcePacks; - try { - resourcePacks = Files.walk(directory).collect(Collectors.toList()); + try (Stream stream = Files.walk(directory)) { + resourcePacks = stream.collect(Collectors.toList()); } catch (IOException e) { GeyserImpl.getInstance().getLogger().error("Could not list packs directory", e); return; } + Path skullResourcePack = SkullResourcePackManager.createResourcePack(BlockRegistries.CUSTOM_SKULLS.get().keySet()); + if (skullResourcePack != null) { + resourcePacks.add(skullResourcePack); + } + GeyserLoadResourcePacksEvent event = new GeyserLoadResourcePacksEvent(resourcePacks); GeyserImpl.getInstance().eventBus().fire(event); @@ -95,11 +101,8 @@ public class ResourcePack { pack.sha256 = FileUtils.calculateSHA256(file); - Stream stream = null; - try { - ZipFile zip = new ZipFile(file); - - stream = zip.stream(); + try (ZipFile zip = new ZipFile(file); + Stream stream = zip.stream()) { stream.forEach((x) -> { if (x.getName().contains("manifest.json")) { try { @@ -121,10 +124,6 @@ public class ResourcePack { } catch (Exception e) { GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.resource_pack.broken", file.getName())); e.printStackTrace(); - } finally { - if (stream != null) { - stream.close(); - } } } } diff --git a/core/src/main/java/org/geysermc/geyser/pack/ResourcePackManifest.java b/core/src/main/java/org/geysermc/geyser/pack/ResourcePackManifest.java index 2b14eade3..0c3abf86d 100644 --- a/core/src/main/java/org/geysermc/geyser/pack/ResourcePackManifest.java +++ b/core/src/main/java/org/geysermc/geyser/pack/ResourcePackManifest.java @@ -71,7 +71,7 @@ public class ResourcePackManifest { @ToString public static class Module { private String description; - private String name; + private String type; private UUID uuid; private int[] version; } diff --git a/core/src/main/java/org/geysermc/geyser/pack/SkullResourcePackManager.java b/core/src/main/java/org/geysermc/geyser/pack/SkullResourcePackManager.java new file mode 100644 index 000000000..819811c9b --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/pack/SkullResourcePackManager.java @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.pack; + +import it.unimi.dsi.fastutil.Pair; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.skin.SkinProvider; +import org.geysermc.geyser.util.FileUtils; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.*; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.*; +import java.util.function.UnaryOperator; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; + +public class SkullResourcePackManager { + + private static final long RESOURCE_PACK_VERSION = 4; + + @SuppressWarnings("ResultOfMethodCallIgnored") + public static Path createResourcePack(Set skins) { + Path packPath = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("cache").resolve("player_skulls.mcpack"); + File packFile = packPath.toFile(); + if (skins.isEmpty()) { + packFile.delete(); // No need to keep resource pack + GeyserImpl.getInstance().getLogger().debug("No skins to create player skull resource pack."); + return null; + } + if (packFile.exists() && canReusePack(skins, packFile)) { + GeyserImpl.getInstance().getLogger().info("Reusing cached player skull resource pack."); + return packPath; + } + + // We need to create the resource pack from scratch + GeyserImpl.getInstance().getLogger().info("Creating skull resource pack."); + packFile.delete(); + try (ZipOutputStream zipOS = new ZipOutputStream(Files.newOutputStream(packPath, StandardOpenOption.WRITE, StandardOpenOption.CREATE))) { + addBaseResources(zipOS, skins); + addSkinTextures(zipOS, skins); + addAttachables(zipOS, skins); + GeyserImpl.getInstance().getLogger().info("Finished creating skull resource pack."); + return packPath; + } catch (IOException e) { + GeyserImpl.getInstance().getLogger().error("Unable to create player skull resource pack!", e); + packFile.delete(); + } + return null; + } + + private static void addBaseResources(ZipOutputStream zipOS, Set skins) throws IOException { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(GeyserImpl.getInstance().getBootstrap().getResource("bedrock/skull_resource_pack_files.txt")))) { + List lines = reader.lines().toList(); + for (String path : lines) { + ZipEntry entry = new ZipEntry(path); + + zipOS.putNextEntry(entry); + String resourcePath = "bedrock/" + path; + switch (path) { + case "skull_resource_pack/manifest.json" -> + fillTemplate(zipOS, resourcePath, template -> fillManifestJson(template, skins)); + case "skull_resource_pack/textures/terrain_texture.json" -> + fillTemplate(zipOS, resourcePath, template -> fillTerrainTextureJson(template, skins)); + default -> zipOS.write(FileUtils.readAllBytes(resourcePath)); + } + zipOS.closeEntry(); + } + } + } + + private static void addAttachables(ZipOutputStream zipOS, Set skins) throws IOException { + String template = new String(FileUtils.readAllBytes("bedrock/skull_resource_pack/attachables/template_attachable.json"), StandardCharsets.UTF_8); + for (String skinHash : skins) { + ZipEntry entry = new ZipEntry("skull_resource_pack/attachables/" + skinHash + ".json"); + zipOS.putNextEntry(entry); + zipOS.write(fillAttachableJson(template, skinHash).getBytes(StandardCharsets.UTF_8)); + zipOS.closeEntry(); + } + } + + private static void addSkinTextures(ZipOutputStream zipOS, Set skins) throws IOException { + Path skullSkinCache = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("cache").resolve("player_skulls"); + Files.createDirectories(skullSkinCache); + for (String skin : skins) { + Path skinPath = skullSkinCache.resolve(skin + ".png"); + if (!Files.exists(skinPath)) { + // TODO this should go somewhere else and be async somehow + BufferedImage image = SkinProvider.requestImage("http://textures.minecraft.net/texture/" + skin, null); + ImageIO.write(image, "png", skinPath.toFile()); + GeyserImpl.getInstance().getLogger().debug("Cached player skull to file " + skinPath + " for " + skin); + } + + ZipEntry entry = new ZipEntry("skull_resource_pack/textures/blocks/" + skin + ".png"); + + zipOS.putNextEntry(entry); + zipOS.write(FileUtils.readAllBytes(skinPath.toFile())); + zipOS.closeEntry(); + } + } + + private static void fillTemplate(ZipOutputStream zipOS, String path, UnaryOperator filler) throws IOException { + String template = new String(FileUtils.readAllBytes(path), StandardCharsets.UTF_8); + String result = filler.apply(template); + zipOS.write(result.getBytes(StandardCharsets.UTF_8)); + } + + private static String fillAttachableJson(String template, String skinHash) { + return template.replace("${identifier}", "geyser:player_skull_" + skinHash) // TOOD use CustomSkull for this + .replace("${texture}", skinHash); + } + + private static String fillManifestJson(String template, Set skins) { + Pair uuids = generatePackUUIDs(skins); + return template.replace("${uuid1}", uuids.first().toString()) + .replace("${uuid2}", uuids.second().toString()); + } + + private static String fillTerrainTextureJson(String template, Set skins) { + StringBuilder textures = new StringBuilder(); + for (String skinHash : skins) { + String texture = String.format("\"geyser.%s_player_skin\":{\"textures\":\"textures/blocks/%s\"},", skinHash, skinHash); + textures.append(texture); + } + if (textures.length() != 0) { + // Remove trailing comma + textures.deleteCharAt(textures.length() - 1); + } + return template.replace("${texture_data}", textures); + } + + private static Pair generatePackUUIDs(Set skins) { + UUID uuid1 = UUID.randomUUID(); + UUID uuid2 = UUID.randomUUID(); + try { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + for (int i = 0; i < 8; i++) { + md.update((byte) ((RESOURCE_PACK_VERSION >> (i * 8)) & 0xFF)); + } + skins.stream() + .sorted() + .map(hash -> hash.getBytes(StandardCharsets.UTF_8)) + .forEach(md::update); + + ByteBuffer skinHashes = ByteBuffer.wrap(md.digest()); + uuid1 = new UUID(skinHashes.getLong(), skinHashes.getLong()); + uuid2 = new UUID(skinHashes.getLong(), skinHashes.getLong()); + } catch (NoSuchAlgorithmException e) { + GeyserImpl.getInstance().getLogger().severe("Unable to get SHA-256 Message Digest instance! Bedrock players will have to re-downloaded the player skull resource pack after each server restart.", e); + } + + return Pair.of(uuid1, uuid2); + } + + private static boolean canReusePack(Set skins, File packFile) { + Pair uuids = generatePackUUIDs(skins); + try (ZipFile zipFile = new ZipFile(packFile)) { + Optional manifestEntry = zipFile.stream() + .filter(entry -> entry.getName().contains("manifest.json")) + .findFirst(); + if (manifestEntry.isPresent()) { + ResourcePackManifest manifest = FileUtils.loadJson(zipFile.getInputStream(manifestEntry.get()), ResourcePackManifest.class); + if (!uuids.first().equals(manifest.getHeader().getUuid())) { + return false; + } + Optional resourceUUID = manifest.getModules().stream() + .filter(module -> "resources".equals(module.getType())) + .findFirst() + .map(ResourcePackManifest.Module::getUuid); + return resourceUUID.isPresent() && uuids.second().equals(resourceUUID.get()); + } + } catch (IOException e) { + GeyserImpl.getInstance().getLogger().debug("Cached player skull resource pack was invalid! The pack will be recreated."); + } + return false; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/registry/type/CustomSkull.java b/core/src/main/java/org/geysermc/geyser/registry/type/CustomSkull.java index 54bbc6835..49486dcdc 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/type/CustomSkull.java +++ b/core/src/main/java/org/geysermc/geyser/registry/type/CustomSkull.java @@ -22,6 +22,7 @@ * @author GeyserMC * @link https://github.com/GeyserMC/Geyser */ + package org.geysermc.geyser.registry.type; import lombok.Data; @@ -47,8 +48,8 @@ public class CustomSkull { private CustomBlockData customBlockData; - private static final String DIRECTION_PROPERTY = "davchoo:direction"; - private static final String TYPE_PROPERTY = "davchoo:type"; + private static final String DIRECTION_PROPERTY = "geyser_skull:direction"; + private static final String TYPE_PROPERTY = "geyser_skull:type"; private static final int[] ROTATIONS = {0, -90, 180, 90}; @@ -57,7 +58,7 @@ public class CustomSkull { CustomBlockComponents components = new GeyserCustomBlockComponents.CustomBlockComponentsBuilder() .destroyTime(1.5f) - .materialInstances(Map.of("*", new MaterialInstance("davchoo." + skinHash + "_player_skin", "alpha_test", true, true))) + .materialInstances(Map.of("*", new MaterialInstance("geyser." + skinHash + "_player_skin", "alpha_test", true, true))) .lightFilter(0) .build(); @@ -67,7 +68,7 @@ public class CustomSkull { addWallPermutations(permutations); customBlockData = new GeyserCustomBlockData.CustomBlockDataBuilder() - .name("player_head_" + skinHash) + .name("player_skull_" + skinHash) .components(components) .intProperty(DIRECTION_PROPERTY, IntStream.rangeClosed(0, 15).boxed().toList()) .intProperty(TYPE_PROPERTY, IntStream.rangeClosed(0, 2).boxed().toList()) @@ -106,7 +107,7 @@ public class CustomSkull { private void addDefaultPermutation(List permutations) { CustomBlockComponents components = new GeyserCustomBlockComponents.CustomBlockComponentsBuilder() - .geometry("geometry.davchoo.player_head_hand") + .geometry("geometry.geyser.player_skull_hand") .rotation(new RotationComponent(0, 180, 0)) .build(); @@ -132,7 +133,7 @@ public class CustomSkull { CustomBlockComponents components = new GeyserCustomBlockComponents.CustomBlockComponentsBuilder() .aimCollision(box) .entityCollision(box) - .geometry("geometry.davchoo.player_head_floor_" + quadrantNames[i]) + .geometry("geometry.geyser.player_skull_floor_" + quadrantNames[i]) .rotation(rotation) .build(); @@ -158,7 +159,7 @@ public class CustomSkull { CustomBlockComponents components = new GeyserCustomBlockComponents.CustomBlockComponentsBuilder() .aimCollision(box) .entityCollision(box) - .geometry("geometry.davchoo.player_head_wall") + .geometry("geometry.geyser.player_skull_wall") .rotation(rotation) .build(); diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/SkullCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/SkullCache.java index 291790ed8..bd39143c4 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/SkullCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/SkullCache.java @@ -82,6 +82,7 @@ public class SkullCache { Skull skull = skulls.computeIfAbsent(position, Skull::new); if (!texturesProperty.equals(skull.texturesProperty)) { skull.texturesProperty = texturesProperty; + skull.skinHash = null; try { SkinManager.GameProfileData gameProfileData = SkinManager.GameProfileData.loadFromJson(texturesProperty); if (gameProfileData != null && gameProfileData.skinUrl() != null) { @@ -89,14 +90,12 @@ public class SkullCache { skull.skinHash = skinUrl.substring(skinUrl.lastIndexOf('/') + 1); } else { session.getGeyser().getLogger().debug("Player skull with invalid Skin tag: " + position + " Textures: " + texturesProperty); - skull.skinHash = null; } } catch (IOException e) { session.getGeyser().getLogger().debug("Player skull with invalid Skin tag: " + position + " Textures: " + texturesProperty); if (GeyserImpl.getInstance().getConfig().isDebugMode()) { e.printStackTrace(); } - skull.skinHash = null; } } skull.blockState = blockState; diff --git a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java index 43cf30b47..d6b16f6fd 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java @@ -406,7 +406,7 @@ public class SkinProvider { private static Skin supplySkin(UUID uuid, String textureUrl) { try { - byte[] skin = requestImage(textureUrl, null); + byte[] skin = requestImageData(textureUrl, null); return new Skin(uuid, textureUrl, skin, System.currentTimeMillis(), false, false); } catch (Exception ignored) {} // just ignore I guess @@ -416,7 +416,7 @@ public class SkinProvider { private static Cape supplyCape(String capeUrl, CapeProvider provider) { byte[] cape = EMPTY_CAPE.getCapeData(); try { - cape = requestImage(capeUrl, provider); + cape = requestImageData(capeUrl, provider); } catch (Exception ignored) { } // just ignore I guess @@ -473,7 +473,7 @@ public class SkinProvider { } @SuppressWarnings("ResultOfMethodCallIgnored") - private static byte[] requestImage(String imageUrl, CapeProvider provider) throws Exception { + public static BufferedImage requestImage(String imageUrl, CapeProvider provider) throws IOException { BufferedImage image = null; // First see if we have a cached file. We also update the modification stamp so we know when the file was last used @@ -533,6 +533,11 @@ public class SkinProvider { // TODO remove alpha channel } + return image; + } + + private static byte[] requestImageData(String imageUrl, CapeProvider provider) throws Exception { + BufferedImage image = requestImage(imageUrl, provider); byte[] data = bufferedImageToImageData(image); image.flush(); return data; diff --git a/core/src/main/resources/bedrock/skull_resource_pack/animations/disable.animation.json b/core/src/main/resources/bedrock/skull_resource_pack/animations/disable.animation.json new file mode 100644 index 000000000..70a348409 --- /dev/null +++ b/core/src/main/resources/bedrock/skull_resource_pack/animations/disable.animation.json @@ -0,0 +1,14 @@ +{ + "format_version": "1.8.0", + "animations": { + "animation.geyser.disable": { + "loop": true, + "override_previous_animation": true, + "bones": { + "root": { + "scale": 0 + } + } + } + } +} diff --git a/core/src/main/resources/bedrock/skull_resource_pack/animations/player_skull.animation.json b/core/src/main/resources/bedrock/skull_resource_pack/animations/player_skull.animation.json new file mode 100644 index 000000000..6da469665 --- /dev/null +++ b/core/src/main/resources/bedrock/skull_resource_pack/animations/player_skull.animation.json @@ -0,0 +1,80 @@ +{ + "format_version": "1.8.0", + "animations": { + "animation.geyser.player_skull.head": { + "loop": true, + "bones": { + "root_x": { + "position": [0, 15.95, 0], + "scale": [1.1875, 1.1875, 1.1875] + } + } + }, + "animation.geyser.player_skull.thirdperson_main_hand": { + "loop": true, + "bones": { + "root": { + "scale": [0.5, 0.5, 0.5], + "position": [0, 12, -4] + }, + "root_x": { + "rotation": [45, 0, 0] + }, + "root_y": { + "rotation": [0, 135, 0] + } + } + }, + "animation.geyser.player_skull.thirdperson_off_hand": { + "loop": true, + "bones": { + "root": { + "scale": [0.5, 0.5, 0.5], + "position": [0, 12, -4] + }, + "root_x": { + "rotation": [45, 0, 0] + }, + "root_y": { + "rotation": [0, -135, 0] + } + } + }, + "animation.geyser.player_skull.firstperson_main_hand": { + "loop": true, + "bones": { + "root": { + "scale": [0.5, 0.5, 0.5], + "position": [2, 16, 4] + }, + "root_x": { + "rotation": [180, 0, 0] + }, + "root_y": { + "rotation": [0, 135, 0] + }, + "root_z": { + "rotation": [45, 0, 0] + } + } + }, + "animation.geyser.player_skull.firstperson_off_hand": { + "loop": true, + "bones": { + "root": { + "scale": [0.5, 0.5, 0.5], + "position": [2, 16, 4] + }, + "root_x": { + "rotation": [180, 0, 0] + }, + "root_y": { + "rotation": [0, 135, 0] + }, + "root_z": { + "rotation": [45, 0, 0] + } + } + } + } +} diff --git a/core/src/main/resources/bedrock/skull_resource_pack/attachables/template_attachable.json b/core/src/main/resources/bedrock/skull_resource_pack/attachables/template_attachable.json new file mode 100644 index 000000000..169138191 --- /dev/null +++ b/core/src/main/resources/bedrock/skull_resource_pack/attachables/template_attachable.json @@ -0,0 +1,57 @@ +{ + "format_version": "1.10.0", + "minecraft:attachable": { + "description": { + "identifier": "${identifier}", + "materials": { + "default": "entity_alphatest", + "enchanted": "entity_alphatest" + }, + "textures": { + "default": "textures/blocks/${texture}", + "enchanted": "textures/misc/enchanted_item_glint" + }, + "geometry": { + "default": "geometry.geyser.player_skull" + }, + "scripts": { + "pre_animation": [ + "v.main_hand = c.item_slot == 'main_hand';", + "v.off_hand = c.item_slot == 'off_hand';", + "v.head = c.item_slot == 'head';" + ], + "animate": [ + { + "thirdperson_main_hand": "v.main_hand && !c.is_first_person" + }, + { + "thirdperson_off_hand": "v.off_hand && !c.is_first_person" + }, + { + "thirdperson_head": "v.head && !c.is_first_person" + }, + { + "firstperson_main_hand": "v.main_hand && c.is_first_person" + }, + { + "firstperson_off_hand": "v.off_hand && c.is_first_person" + }, + { + "firstperson_head": "v.head && c.is_first_person || query.is_owner_identifier_any('minecraft:player')" + } + ] + }, + "animations": { + "thirdperson_main_hand": "animation.geyser.player_skull.thirdperson_main_hand", + "thirdperson_off_hand": "animation.geyser.player_skull.thirdperson_off_hand", + "thirdperson_head": "animation.geyser.player_skull.head", + "firstperson_main_hand": "animation.geyser.player_skull.firstperson_main_hand", + "firstperson_off_hand": "animation.geyser.player_skull.firstperson_off_hand", + "firstperson_head": "animation.geyser.disable" + }, + "render_controllers": [ + "controller.render.item_default" + ] + } + } +} diff --git a/core/src/main/resources/bedrock/skull_resource_pack/manifest.json b/core/src/main/resources/bedrock/skull_resource_pack/manifest.json new file mode 100644 index 000000000..a7a6f9c1c --- /dev/null +++ b/core/src/main/resources/bedrock/skull_resource_pack/manifest.json @@ -0,0 +1,17 @@ +{ + "format_version": 2, + "header": { + "name": "Geyser Player Skull Resource Pack", + "description": "Auto-generated resource pack to support player skulls as custom blocks", + "uuid": "${uuid1}", + "version": [1, 0, 0], + "min_engine_version": [1, 16, 0] + }, + "modules": [ + { + "type": "resources", + "uuid": "${uuid2}", + "version": [1, 0, 0] + } + ] +} \ No newline at end of file diff --git a/core/src/main/resources/bedrock/skull_resource_pack/models/blocks/player_skull.geo.json b/core/src/main/resources/bedrock/skull_resource_pack/models/blocks/player_skull.geo.json new file mode 100644 index 000000000..2681b406f --- /dev/null +++ b/core/src/main/resources/bedrock/skull_resource_pack/models/blocks/player_skull.geo.json @@ -0,0 +1,50 @@ +{ + "format_version": "1.16.0", + "minecraft:geometry": [ + { + "description": { + "identifier": "geometry.geyser.player_skull", + "texture_width": 64, + "texture_height": 64 + }, + "bones": [ + { + "name": "root", + "binding": "c.item_slot == 'head' ? 'head' : q.item_slot_to_bone_name(c.item_slot)", + "pivot": [0, 8, 0] + }, + { + "name": "root_x", + "parent": "root", + "pivot": [0, 8, 0] + }, + { + "name": "root_y", + "parent": "root_x", + "pivot": [0, 8, 0] + }, + { + "name": "root_z", + "parent": "root_y", + "pivot": [0, 8, 0] + }, + { + "name": "player_skull", + "parent": "root_z", + "pivot": [0, 24, 0], + "cubes": [ + {"origin": [-4, 8, -4], "size": [8, 8, 8], "uv": [0, 0]} + ] + }, + { + "name": "player_skull_hat", + "parent": "player_skull", + "pivot": [0, 24, 0], + "cubes": [ + {"origin": [-4, 8, -4], "size": [8, 8, 8], "uv": [32, 0], "inflate": 0.25} + ] + } + ] + } + ] +} diff --git a/core/src/main/resources/bedrock/skull_resource_pack/models/blocks/player_skull_floor_a.geo.json b/core/src/main/resources/bedrock/skull_resource_pack/models/blocks/player_skull_floor_a.geo.json new file mode 100644 index 000000000..743236a5d --- /dev/null +++ b/core/src/main/resources/bedrock/skull_resource_pack/models/blocks/player_skull_floor_a.geo.json @@ -0,0 +1,29 @@ +{ + "format_version": "1.12.0", + "minecraft:geometry": [ + { + "description": { + "identifier": "geometry.geyser.player_skull_floor_a", + "texture_width": 64, + "texture_height": 64 + }, + "bones": [ + { + "name": "head", + "pivot": [0, 24, 0], + "cubes": [ + {"origin": [-4, 0.5, -4], "size": [8, 8, 8], "uv": [0, 0]} + ] + }, + { + "name": "hat", + "parent": "head", + "pivot": [0, 24, 0], + "cubes": [ + {"origin": [-4, 0.5, -4], "size": [8, 8, 8], "uv": [32, 0], "inflate": 0.5} + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/core/src/main/resources/bedrock/skull_resource_pack/models/blocks/player_skull_floor_b.geo.json b/core/src/main/resources/bedrock/skull_resource_pack/models/blocks/player_skull_floor_b.geo.json new file mode 100644 index 000000000..0bbe1c1f6 --- /dev/null +++ b/core/src/main/resources/bedrock/skull_resource_pack/models/blocks/player_skull_floor_b.geo.json @@ -0,0 +1,30 @@ +{ + "format_version": "1.12.0", + "minecraft:geometry": [ + { + "description": { + "identifier": "geometry.geyser.player_skull_floor_b", + "texture_width": 64, + "texture_height": 64 + }, + "bones": [ + { + "name": "head", + "pivot": [0, 24, 0], + "rotation": [0, 22.5, 0], + "cubes": [ + {"origin": [-4, 0.5, -4], "size": [8, 8, 8], "uv": [0, 0]} + ] + }, + { + "name": "hat", + "parent": "head", + "pivot": [0, 24, 0], + "cubes": [ + {"origin": [-4, 0.5, -4], "size": [8, 8, 8], "uv": [32, 0], "inflate": 0.5} + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/core/src/main/resources/bedrock/skull_resource_pack/models/blocks/player_skull_floor_c.geo.json b/core/src/main/resources/bedrock/skull_resource_pack/models/blocks/player_skull_floor_c.geo.json new file mode 100644 index 000000000..c633977ab --- /dev/null +++ b/core/src/main/resources/bedrock/skull_resource_pack/models/blocks/player_skull_floor_c.geo.json @@ -0,0 +1,30 @@ +{ + "format_version": "1.12.0", + "minecraft:geometry": [ + { + "description": { + "identifier": "geometry.geyser.player_skull_floor_c", + "texture_width": 64, + "texture_height": 64 + }, + "bones": [ + { + "name": "head", + "pivot": [0, 24, 0], + "rotation": [0, 45, 0], + "cubes": [ + {"origin": [-4, 0.5, -4], "size": [8, 8, 8], "uv": [0, 0]} + ] + }, + { + "name": "hat", + "parent": "head", + "pivot": [0, 24, 0], + "cubes": [ + {"origin": [-4, 0.5, -4], "size": [8, 8, 8], "uv": [32, 0], "inflate": 0.5} + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/core/src/main/resources/bedrock/skull_resource_pack/models/blocks/player_skull_floor_d.geo.json b/core/src/main/resources/bedrock/skull_resource_pack/models/blocks/player_skull_floor_d.geo.json new file mode 100644 index 000000000..7436944e7 --- /dev/null +++ b/core/src/main/resources/bedrock/skull_resource_pack/models/blocks/player_skull_floor_d.geo.json @@ -0,0 +1,30 @@ +{ + "format_version": "1.12.0", + "minecraft:geometry": [ + { + "description": { + "identifier": "geometry.geyser.player_skull_floor_d", + "texture_width": 64, + "texture_height": 64 + }, + "bones": [ + { + "name": "head", + "pivot": [0, 24, 0], + "rotation": [0, 67.5, 0], + "cubes": [ + {"origin": [-4, 0.5, -4], "size": [8, 8, 8], "uv": [0, 0]} + ] + }, + { + "name": "hat", + "parent": "head", + "pivot": [0, 24, 0], + "cubes": [ + {"origin": [-4, 0.5, -4], "size": [8, 8, 8], "uv": [32, 0], "inflate": 0.5} + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/core/src/main/resources/bedrock/skull_resource_pack/models/blocks/player_skull_hand.geo.json b/core/src/main/resources/bedrock/skull_resource_pack/models/blocks/player_skull_hand.geo.json new file mode 100644 index 000000000..6ad5bea1b --- /dev/null +++ b/core/src/main/resources/bedrock/skull_resource_pack/models/blocks/player_skull_hand.geo.json @@ -0,0 +1,29 @@ +{ + "format_version": "1.12.0", + "minecraft:geometry": [ + { + "description": { + "identifier": "geometry.geyser.player_skull_hand", + "texture_width": 64, + "texture_height": 64 + }, + "bones": [ + { + "name": "head", + "pivot": [0, 24, 0], + "cubes": [ + {"origin": [-4, 4, -4], "size": [8, 8, 8], "uv": [0, 0]} + ] + }, + { + "name": "hat", + "parent": "head", + "pivot": [0, 24, 0], + "cubes": [ + {"origin": [-4, 4, -4], "size": [8, 8, 8], "uv": [32, 0], "inflate": 0.5} + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/core/src/main/resources/bedrock/skull_resource_pack/models/blocks/player_skull_wall.geo.json b/core/src/main/resources/bedrock/skull_resource_pack/models/blocks/player_skull_wall.geo.json new file mode 100644 index 000000000..5c39733dd --- /dev/null +++ b/core/src/main/resources/bedrock/skull_resource_pack/models/blocks/player_skull_wall.geo.json @@ -0,0 +1,29 @@ +{ + "format_version": "1.12.0", + "minecraft:geometry": [ + { + "description": { + "identifier": "geometry.geyser.player_skull_wall", + "texture_width": 64, + "texture_height": 64 + }, + "bones": [ + { + "name": "head", + "pivot": [0, 24, 0], + "cubes": [ + {"origin": [-4, 4, -0.5], "size": [8, 8, 8], "uv": [0, 0]} + ] + }, + { + "name": "hat", + "parent": "head", + "pivot": [0, 24, 0], + "cubes": [ + {"origin": [-4, 4, -0.5], "size": [8, 8, 8], "uv": [32, 0], "inflate": 0.5} + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/core/src/main/resources/bedrock/skull_resource_pack/pack_icon.png b/core/src/main/resources/bedrock/skull_resource_pack/pack_icon.png new file mode 100644 index 000000000..f64fdce4b Binary files /dev/null and b/core/src/main/resources/bedrock/skull_resource_pack/pack_icon.png differ diff --git a/core/src/main/resources/bedrock/skull_resource_pack/textures/terrain_texture.json b/core/src/main/resources/bedrock/skull_resource_pack/textures/terrain_texture.json new file mode 100644 index 000000000..c10c4a1c8 --- /dev/null +++ b/core/src/main/resources/bedrock/skull_resource_pack/textures/terrain_texture.json @@ -0,0 +1,8 @@ +{ + "num_mip_levels": 4, + "padding": 8, + "resource_pack_name": "Geyser Player Skull Resource Pack", + "texture_data": { + ${texture_data} + } +} \ No newline at end of file diff --git a/core/src/main/resources/bedrock/skull_resource_pack_files.txt b/core/src/main/resources/bedrock/skull_resource_pack_files.txt new file mode 100644 index 000000000..a614edf2a --- /dev/null +++ b/core/src/main/resources/bedrock/skull_resource_pack_files.txt @@ -0,0 +1,12 @@ +skull_resource_pack/animations/disable.animation.json +skull_resource_pack/animations/player_skull.animation.json +skull_resource_pack/models/blocks/player_skull.geo.json +skull_resource_pack/models/blocks/player_skull_floor_a.geo.json +skull_resource_pack/models/blocks/player_skull_floor_c.geo.json +skull_resource_pack/models/blocks/player_skull_floor_b.geo.json +skull_resource_pack/models/blocks/player_skull_floor_d.geo.json +skull_resource_pack/models/blocks/player_skull_hand.geo.json +skull_resource_pack/models/blocks/player_skull_wall.geo.json +skull_resource_pack/textures/terrain_texture.json +skull_resource_pack/pack_icon.png +skull_resource_pack/manifest.json