Mirror von
https://github.com/GeyserMC/Geyser.git
synchronisiert 2024-11-19 22:40:18 +01:00
Auto generate skull resource pack
Change `davchoo` to `geyser` in geometry
Dieser Commit ist enthalten in:
Ursprung
49f7f6d2f9
Commit
9e0af6e098
@ -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<Path> resourcePacks;
|
||||
try {
|
||||
resourcePacks = Files.walk(directory).collect(Collectors.toList());
|
||||
try (Stream<Path> 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<? extends ZipEntry> stream = null;
|
||||
try {
|
||||
ZipFile zip = new ZipFile(file);
|
||||
|
||||
stream = zip.stream();
|
||||
try (ZipFile zip = new ZipFile(file);
|
||||
Stream<? extends ZipEntry> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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<String> 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<String> skins) throws IOException {
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(GeyserImpl.getInstance().getBootstrap().getResource("bedrock/skull_resource_pack_files.txt")))) {
|
||||
List<String> 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<String> 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<String> 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<String> 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<String> skins) {
|
||||
Pair<UUID, UUID> uuids = generatePackUUIDs(skins);
|
||||
return template.replace("${uuid1}", uuids.first().toString())
|
||||
.replace("${uuid2}", uuids.second().toString());
|
||||
}
|
||||
|
||||
private static String fillTerrainTextureJson(String template, Set<String> 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<UUID, UUID> generatePackUUIDs(Set<String> 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<String> skins, File packFile) {
|
||||
Pair<UUID, UUID> uuids = generatePackUUIDs(skins);
|
||||
try (ZipFile zipFile = new ZipFile(packFile)) {
|
||||
Optional<? extends ZipEntry> 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<UUID> 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;
|
||||
}
|
||||
}
|
@ -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<CustomBlockPermutation> 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();
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -0,0 +1,14 @@
|
||||
{
|
||||
"format_version": "1.8.0",
|
||||
"animations": {
|
||||
"animation.geyser.disable": {
|
||||
"loop": true,
|
||||
"override_previous_animation": true,
|
||||
"bones": {
|
||||
"root": {
|
||||
"scale": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
17
core/src/main/resources/bedrock/skull_resource_pack/manifest.json
Normale Datei
17
core/src/main/resources/bedrock/skull_resource_pack/manifest.json
Normale Datei
@ -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]
|
||||
}
|
||||
]
|
||||
}
|
@ -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}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -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}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -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}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -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}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -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}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -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}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -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}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
BIN
core/src/main/resources/bedrock/skull_resource_pack/pack_icon.png
Normale Datei
BIN
core/src/main/resources/bedrock/skull_resource_pack/pack_icon.png
Normale Datei
Binäre Datei nicht angezeigt.
Nachher Breite: | Höhe: | Größe: 35 KiB |
@ -0,0 +1,8 @@
|
||||
{
|
||||
"num_mip_levels": 4,
|
||||
"padding": 8,
|
||||
"resource_pack_name": "Geyser Player Skull Resource Pack",
|
||||
"texture_data": {
|
||||
${texture_data}
|
||||
}
|
||||
}
|
12
core/src/main/resources/bedrock/skull_resource_pack_files.txt
Normale Datei
12
core/src/main/resources/bedrock/skull_resource_pack_files.txt
Normale Datei
@ -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
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren