mirror of
https://github.com/GeyserMC/Geyser.git
synced 2024-08-14 23:57:35 +00:00
Auto generate skull resource pack
Change `davchoo` to `geyser` in geometry
This commit is contained in:
parent
49f7f6d2f9
commit
9e0af6e098
20 changed files with 651 additions and 24 deletions
|
@ -27,6 +27,7 @@ package org.geysermc.geyser.pack;
|
||||||
|
|
||||||
import org.geysermc.geyser.GeyserImpl;
|
import org.geysermc.geyser.GeyserImpl;
|
||||||
import org.geysermc.geyser.api.event.lifecycle.GeyserLoadResourcePacksEvent;
|
import org.geysermc.geyser.api.event.lifecycle.GeyserLoadResourcePacksEvent;
|
||||||
|
import org.geysermc.geyser.registry.BlockRegistries;
|
||||||
import org.geysermc.geyser.util.FileUtils;
|
import org.geysermc.geyser.util.FileUtils;
|
||||||
import org.geysermc.geyser.text.GeyserLocale;
|
import org.geysermc.geyser.text.GeyserLocale;
|
||||||
|
|
||||||
|
@ -77,13 +78,18 @@ public class ResourcePack {
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Path> resourcePacks;
|
List<Path> resourcePacks;
|
||||||
try {
|
try (Stream<Path> stream = Files.walk(directory)) {
|
||||||
resourcePacks = Files.walk(directory).collect(Collectors.toList());
|
resourcePacks = stream.collect(Collectors.toList());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
GeyserImpl.getInstance().getLogger().error("Could not list packs directory", e);
|
GeyserImpl.getInstance().getLogger().error("Could not list packs directory", e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Path skullResourcePack = SkullResourcePackManager.createResourcePack(BlockRegistries.CUSTOM_SKULLS.get().keySet());
|
||||||
|
if (skullResourcePack != null) {
|
||||||
|
resourcePacks.add(skullResourcePack);
|
||||||
|
}
|
||||||
|
|
||||||
GeyserLoadResourcePacksEvent event = new GeyserLoadResourcePacksEvent(resourcePacks);
|
GeyserLoadResourcePacksEvent event = new GeyserLoadResourcePacksEvent(resourcePacks);
|
||||||
GeyserImpl.getInstance().eventBus().fire(event);
|
GeyserImpl.getInstance().eventBus().fire(event);
|
||||||
|
|
||||||
|
@ -95,11 +101,8 @@ public class ResourcePack {
|
||||||
|
|
||||||
pack.sha256 = FileUtils.calculateSHA256(file);
|
pack.sha256 = FileUtils.calculateSHA256(file);
|
||||||
|
|
||||||
Stream<? extends ZipEntry> stream = null;
|
try (ZipFile zip = new ZipFile(file);
|
||||||
try {
|
Stream<? extends ZipEntry> stream = zip.stream()) {
|
||||||
ZipFile zip = new ZipFile(file);
|
|
||||||
|
|
||||||
stream = zip.stream();
|
|
||||||
stream.forEach((x) -> {
|
stream.forEach((x) -> {
|
||||||
if (x.getName().contains("manifest.json")) {
|
if (x.getName().contains("manifest.json")) {
|
||||||
try {
|
try {
|
||||||
|
@ -121,10 +124,6 @@ public class ResourcePack {
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.resource_pack.broken", file.getName()));
|
GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.resource_pack.broken", file.getName()));
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
} finally {
|
|
||||||
if (stream != null) {
|
|
||||||
stream.close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,7 +71,7 @@ public class ResourcePackManifest {
|
||||||
@ToString
|
@ToString
|
||||||
public static class Module {
|
public static class Module {
|
||||||
private String description;
|
private String description;
|
||||||
private String name;
|
private String type;
|
||||||
private UUID uuid;
|
private UUID uuid;
|
||||||
private int[] version;
|
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
|
* @author GeyserMC
|
||||||
* @link https://github.com/GeyserMC/Geyser
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.geysermc.geyser.registry.type;
|
package org.geysermc.geyser.registry.type;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
@ -47,8 +48,8 @@ public class CustomSkull {
|
||||||
|
|
||||||
private CustomBlockData customBlockData;
|
private CustomBlockData customBlockData;
|
||||||
|
|
||||||
private static final String DIRECTION_PROPERTY = "davchoo:direction";
|
private static final String DIRECTION_PROPERTY = "geyser_skull:direction";
|
||||||
private static final String TYPE_PROPERTY = "davchoo:type";
|
private static final String TYPE_PROPERTY = "geyser_skull:type";
|
||||||
|
|
||||||
private static final int[] ROTATIONS = {0, -90, 180, 90};
|
private static final int[] ROTATIONS = {0, -90, 180, 90};
|
||||||
|
|
||||||
|
@ -57,7 +58,7 @@ public class CustomSkull {
|
||||||
|
|
||||||
CustomBlockComponents components = new GeyserCustomBlockComponents.CustomBlockComponentsBuilder()
|
CustomBlockComponents components = new GeyserCustomBlockComponents.CustomBlockComponentsBuilder()
|
||||||
.destroyTime(1.5f)
|
.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)
|
.lightFilter(0)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
@ -67,7 +68,7 @@ public class CustomSkull {
|
||||||
addWallPermutations(permutations);
|
addWallPermutations(permutations);
|
||||||
|
|
||||||
customBlockData = new GeyserCustomBlockData.CustomBlockDataBuilder()
|
customBlockData = new GeyserCustomBlockData.CustomBlockDataBuilder()
|
||||||
.name("player_head_" + skinHash)
|
.name("player_skull_" + skinHash)
|
||||||
.components(components)
|
.components(components)
|
||||||
.intProperty(DIRECTION_PROPERTY, IntStream.rangeClosed(0, 15).boxed().toList())
|
.intProperty(DIRECTION_PROPERTY, IntStream.rangeClosed(0, 15).boxed().toList())
|
||||||
.intProperty(TYPE_PROPERTY, IntStream.rangeClosed(0, 2).boxed().toList())
|
.intProperty(TYPE_PROPERTY, IntStream.rangeClosed(0, 2).boxed().toList())
|
||||||
|
@ -106,7 +107,7 @@ public class CustomSkull {
|
||||||
|
|
||||||
private void addDefaultPermutation(List<CustomBlockPermutation> permutations) {
|
private void addDefaultPermutation(List<CustomBlockPermutation> permutations) {
|
||||||
CustomBlockComponents components = new GeyserCustomBlockComponents.CustomBlockComponentsBuilder()
|
CustomBlockComponents components = new GeyserCustomBlockComponents.CustomBlockComponentsBuilder()
|
||||||
.geometry("geometry.davchoo.player_head_hand")
|
.geometry("geometry.geyser.player_skull_hand")
|
||||||
.rotation(new RotationComponent(0, 180, 0))
|
.rotation(new RotationComponent(0, 180, 0))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
@ -132,7 +133,7 @@ public class CustomSkull {
|
||||||
CustomBlockComponents components = new GeyserCustomBlockComponents.CustomBlockComponentsBuilder()
|
CustomBlockComponents components = new GeyserCustomBlockComponents.CustomBlockComponentsBuilder()
|
||||||
.aimCollision(box)
|
.aimCollision(box)
|
||||||
.entityCollision(box)
|
.entityCollision(box)
|
||||||
.geometry("geometry.davchoo.player_head_floor_" + quadrantNames[i])
|
.geometry("geometry.geyser.player_skull_floor_" + quadrantNames[i])
|
||||||
.rotation(rotation)
|
.rotation(rotation)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
@ -158,7 +159,7 @@ public class CustomSkull {
|
||||||
CustomBlockComponents components = new GeyserCustomBlockComponents.CustomBlockComponentsBuilder()
|
CustomBlockComponents components = new GeyserCustomBlockComponents.CustomBlockComponentsBuilder()
|
||||||
.aimCollision(box)
|
.aimCollision(box)
|
||||||
.entityCollision(box)
|
.entityCollision(box)
|
||||||
.geometry("geometry.davchoo.player_head_wall")
|
.geometry("geometry.geyser.player_skull_wall")
|
||||||
.rotation(rotation)
|
.rotation(rotation)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
|
|
@ -82,6 +82,7 @@ public class SkullCache {
|
||||||
Skull skull = skulls.computeIfAbsent(position, Skull::new);
|
Skull skull = skulls.computeIfAbsent(position, Skull::new);
|
||||||
if (!texturesProperty.equals(skull.texturesProperty)) {
|
if (!texturesProperty.equals(skull.texturesProperty)) {
|
||||||
skull.texturesProperty = texturesProperty;
|
skull.texturesProperty = texturesProperty;
|
||||||
|
skull.skinHash = null;
|
||||||
try {
|
try {
|
||||||
SkinManager.GameProfileData gameProfileData = SkinManager.GameProfileData.loadFromJson(texturesProperty);
|
SkinManager.GameProfileData gameProfileData = SkinManager.GameProfileData.loadFromJson(texturesProperty);
|
||||||
if (gameProfileData != null && gameProfileData.skinUrl() != null) {
|
if (gameProfileData != null && gameProfileData.skinUrl() != null) {
|
||||||
|
@ -89,14 +90,12 @@ public class SkullCache {
|
||||||
skull.skinHash = skinUrl.substring(skinUrl.lastIndexOf('/') + 1);
|
skull.skinHash = skinUrl.substring(skinUrl.lastIndexOf('/') + 1);
|
||||||
} else {
|
} else {
|
||||||
session.getGeyser().getLogger().debug("Player skull with invalid Skin tag: " + position + " Textures: " + texturesProperty);
|
session.getGeyser().getLogger().debug("Player skull with invalid Skin tag: " + position + " Textures: " + texturesProperty);
|
||||||
skull.skinHash = null;
|
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
session.getGeyser().getLogger().debug("Player skull with invalid Skin tag: " + position + " Textures: " + texturesProperty);
|
session.getGeyser().getLogger().debug("Player skull with invalid Skin tag: " + position + " Textures: " + texturesProperty);
|
||||||
if (GeyserImpl.getInstance().getConfig().isDebugMode()) {
|
if (GeyserImpl.getInstance().getConfig().isDebugMode()) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
skull.skinHash = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
skull.blockState = blockState;
|
skull.blockState = blockState;
|
||||||
|
|
|
@ -406,7 +406,7 @@ public class SkinProvider {
|
||||||
|
|
||||||
private static Skin supplySkin(UUID uuid, String textureUrl) {
|
private static Skin supplySkin(UUID uuid, String textureUrl) {
|
||||||
try {
|
try {
|
||||||
byte[] skin = requestImage(textureUrl, null);
|
byte[] skin = requestImageData(textureUrl, null);
|
||||||
return new Skin(uuid, textureUrl, skin, System.currentTimeMillis(), false, false);
|
return new Skin(uuid, textureUrl, skin, System.currentTimeMillis(), false, false);
|
||||||
} catch (Exception ignored) {} // just ignore I guess
|
} catch (Exception ignored) {} // just ignore I guess
|
||||||
|
|
||||||
|
@ -416,7 +416,7 @@ public class SkinProvider {
|
||||||
private static Cape supplyCape(String capeUrl, CapeProvider provider) {
|
private static Cape supplyCape(String capeUrl, CapeProvider provider) {
|
||||||
byte[] cape = EMPTY_CAPE.getCapeData();
|
byte[] cape = EMPTY_CAPE.getCapeData();
|
||||||
try {
|
try {
|
||||||
cape = requestImage(capeUrl, provider);
|
cape = requestImageData(capeUrl, provider);
|
||||||
} catch (Exception ignored) {
|
} catch (Exception ignored) {
|
||||||
} // just ignore I guess
|
} // just ignore I guess
|
||||||
|
|
||||||
|
@ -473,7 +473,7 @@ public class SkinProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
@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;
|
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
|
// 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
|
// 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);
|
byte[] data = bufferedImageToImageData(image);
|
||||||
image.flush();
|
image.flush();
|
||||||
return data;
|
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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 35 KiB |
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"num_mip_levels": 4,
|
||||||
|
"padding": 8,
|
||||||
|
"resource_pack_name": "Geyser Player Skull Resource Pack",
|
||||||
|
"texture_data": {
|
||||||
|
${texture_data}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
Loading…
Reference in a new issue