Mirror von
https://github.com/GeyserMC/Geyser.git
synchronisiert 2024-11-19 22:40:18 +01:00
Move custom skull config stuff to its own file
Custom skulls can now be added by username, uuid, and textures Move skull nbt stuff from requestTexturesFromUsername to SkullBlockEntityTranslator Add requestTexturesFromUUID
Dieser Commit ist enthalten in:
Ursprung
a577512dac
Commit
49550a513c
@ -95,9 +95,6 @@ public interface GeyserConfiguration {
|
||||
|
||||
boolean isAddCustomSkullBlocks();
|
||||
|
||||
// TODO this should probably go in a different file?
|
||||
List<String> getCustomSkullProfiles();
|
||||
|
||||
boolean isAboveBedrockNetherBuilding();
|
||||
|
||||
boolean isForceResourcePacks();
|
||||
|
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@SuppressWarnings("FieldMayBeFinal") // Jackson requires that the fields are not final
|
||||
public class GeyserCustomSkullConfiguration {
|
||||
@JsonProperty("player-usernames")
|
||||
private List<String> playerUsernames;
|
||||
|
||||
@JsonProperty("player-uuids")
|
||||
private List<String> playerUUIDs;
|
||||
|
||||
@JsonProperty("textures")
|
||||
private List<String> textures;
|
||||
|
||||
public List<String> getPlayerUsernames() {
|
||||
return Objects.requireNonNullElse(playerUsernames, Collections.emptyList());
|
||||
}
|
||||
|
||||
public List<String> getPlayerUUIDs() {
|
||||
return Objects.requireNonNullElse(playerUUIDs, Collections.emptyList());
|
||||
}
|
||||
|
||||
public List<String> getTextures() {
|
||||
return Objects.requireNonNullElse(textures, Collections.emptyList());
|
||||
}
|
||||
}
|
@ -148,9 +148,6 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
|
||||
@JsonProperty("add-custom-skull-blocks")
|
||||
boolean addCustomSkullBlocks = false;
|
||||
|
||||
@JsonProperty("custom-skull-profiles")
|
||||
List<String> customSkullProfiles = Collections.emptyList();
|
||||
|
||||
@JsonProperty("force-resource-packs")
|
||||
private boolean forceResourcePacks = true;
|
||||
|
||||
|
@ -27,45 +27,100 @@ package org.geysermc.geyser.registry.populator;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectMaps;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import org.geysermc.geyser.GeyserBootstrap;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.configuration.GeyserCustomSkullConfiguration;
|
||||
import org.geysermc.geyser.pack.SkullResourcePackManager;
|
||||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
import org.geysermc.geyser.registry.type.CustomSkull;
|
||||
import org.geysermc.geyser.skin.SkinManager;
|
||||
import org.geysermc.geyser.skin.SkinProvider;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
import org.geysermc.geyser.util.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class CustomSkullRegistryPopulator {
|
||||
|
||||
public static void populate() {
|
||||
SkullResourcePackManager.SKULL_SKINS.clear(); // Remove skins after reloading
|
||||
BlockRegistries.CUSTOM_SKULLS.set(Object2ObjectMaps.emptyMap());
|
||||
|
||||
if (!GeyserImpl.getInstance().getConfig().isAddCustomSkullBlocks()) {
|
||||
BlockRegistries.CUSTOM_SKULLS.set(Object2ObjectMaps.emptyMap());
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, CustomSkull> customSkulls = new Object2ObjectOpenHashMap<>();
|
||||
for (String skullProfile : GeyserImpl.getInstance().getConfig().getCustomSkullProfiles()) {
|
||||
GeyserCustomSkullConfiguration skullConfig;
|
||||
try {
|
||||
GeyserBootstrap bootstrap = GeyserImpl.getInstance().getBootstrap();
|
||||
Path skullConfigPath = bootstrap.getConfigFolder().resolve("custom-skulls.yml");
|
||||
File skullConfigFile = FileUtils.fileOrCopiedFromResource(skullConfigPath.toFile(), "custom-skulls.yml", Function.identity(), bootstrap);
|
||||
skullConfig = FileUtils.loadConfig(skullConfigFile, GeyserCustomSkullConfiguration.class);
|
||||
} catch (IOException e) {
|
||||
GeyserImpl.getInstance().getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.config.failed"), e);
|
||||
return;
|
||||
}
|
||||
|
||||
BlockRegistries.CUSTOM_SKULLS.set(new Object2ObjectOpenHashMap<>());
|
||||
|
||||
List<String> textures = new ArrayList<>(skullConfig.getTextures());
|
||||
// TODO see if this can be cleaned up any better
|
||||
for (String username : skullConfig.getPlayerUsernames()) {
|
||||
try {
|
||||
SkinManager.GameProfileData profileData = SkinManager.GameProfileData.loadFromJson(skullProfile);
|
||||
String texture = SkinProvider.requestTexturesFromUsername(username).get();
|
||||
if (texture == null) {
|
||||
GeyserImpl.getInstance().getLogger().error("Unable to request skull textures for " + username + " This skull will not be added as a custom block.");
|
||||
continue;
|
||||
}
|
||||
textures.add(texture);
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
GeyserImpl.getInstance().getLogger().error("Unable to request skull textures for " + username + " This skull will not be added as a custom block.", e);
|
||||
}
|
||||
}
|
||||
for (String uuid : skullConfig.getPlayerUUIDs()) {
|
||||
try {
|
||||
String uuidDigits = uuid.replace("-", "");
|
||||
if (uuidDigits.length() != 32) {
|
||||
GeyserImpl.getInstance().getLogger().error("Invalid skull uuid " + uuid + " This skull will not be added as a custom block.");
|
||||
continue;
|
||||
}
|
||||
String texture = SkinProvider.requestTexturesFromUUID(uuid).get();
|
||||
if (texture == null) {
|
||||
GeyserImpl.getInstance().getLogger().error("Unable to request skull textures for " + uuid + " This skull will not be added as a custom block.");
|
||||
continue;
|
||||
}
|
||||
textures.add(texture);
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
GeyserImpl.getInstance().getLogger().error("Unable to request skull textures for " + uuid + " This skull will not be added as a custom block.", e);
|
||||
}
|
||||
}
|
||||
|
||||
for (String texture : textures) {
|
||||
try {
|
||||
SkinManager.GameProfileData profileData = SkinManager.GameProfileData.loadFromJson(texture);
|
||||
if (profileData == null) {
|
||||
GeyserImpl.getInstance().getLogger().warning("Skull profile " + skullProfile + " contained no skins and will not be added as a custom block.");
|
||||
GeyserImpl.getInstance().getLogger().warning("Skull texture " + texture + " contained no skins and will not be added as a custom block.");
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
String skinUrl = profileData.skinUrl();
|
||||
String skinHash = skinUrl.substring(skinUrl.lastIndexOf("/") + 1);
|
||||
SkullResourcePackManager.cacheSkullSkin(skinUrl, skinHash);
|
||||
customSkulls.put(skinHash, new CustomSkull(skinHash));
|
||||
BlockRegistries.CUSTOM_SKULLS.register(skinHash, new CustomSkull(skinHash));
|
||||
} catch (IOException e) {
|
||||
GeyserImpl.getInstance().getLogger().error("Failed to cache skin for skull profile " + skullProfile + " This skull will not be added as a custom block.", e);
|
||||
GeyserImpl.getInstance().getLogger().error("Failed to cache skin for skull texture " + texture + " This skull will not be added as a custom block.", e);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
GeyserImpl.getInstance().getLogger().error("Skull profile " + skullProfile + " is invalid and will not be added as a custom block.", e);
|
||||
GeyserImpl.getInstance().getLogger().error("Skull texture " + texture + " is invalid and will not be added as a custom block.", e);
|
||||
}
|
||||
}
|
||||
GeyserImpl.getInstance().getLogger().debug("Registered " + customSkulls.size() + " custom skulls as custom blocks.");
|
||||
BlockRegistries.CUSTOM_SKULLS.set(customSkulls);
|
||||
|
||||
GeyserImpl.getInstance().getLogger().debug("Registered " + BlockRegistries.CUSTOM_SKULLS.get().size() + " custom skulls as custom blocks.");
|
||||
}
|
||||
}
|
||||
|
@ -544,48 +544,23 @@ public class SkinProvider {
|
||||
}
|
||||
|
||||
/**
|
||||
* If a skull has a username but no textures, request them.
|
||||
* Request textures from a player's UUID
|
||||
*
|
||||
* @param skullOwner the CompoundTag of the skull with no textures
|
||||
* @param uuid the player's UUID without any hyphens
|
||||
* @return a completable GameProfile with textures included
|
||||
*/
|
||||
public static CompletableFuture<String> requestTexturesFromUsername(CompoundTag skullOwner) {
|
||||
public static CompletableFuture<String> requestTexturesFromUUID(String uuid) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
Tag uuidTag = skullOwner.get("Id");
|
||||
String uuidToString = "";
|
||||
JsonNode node;
|
||||
boolean retrieveUuidFromInternet = !(uuidTag instanceof IntArrayTag); // also covers null check
|
||||
|
||||
if (!retrieveUuidFromInternet) {
|
||||
int[] uuidAsArray = ((IntArrayTag) uuidTag).getValue();
|
||||
// thank u viaversion
|
||||
UUID uuid = new UUID((long) uuidAsArray[0] << 32 | ((long) uuidAsArray[1] & 0xFFFFFFFFL),
|
||||
(long) uuidAsArray[2] << 32 | ((long) uuidAsArray[3] & 0xFFFFFFFFL));
|
||||
retrieveUuidFromInternet = uuid.version() != 4;
|
||||
uuidToString = uuid.toString().replace("-", "");
|
||||
}
|
||||
|
||||
try {
|
||||
if (retrieveUuidFromInternet) {
|
||||
// Offline skin, or no present UUID
|
||||
node = WebUtils.getJson("https://api.mojang.com/users/profiles/minecraft/" + skullOwner.get("Name").getValue());
|
||||
JsonNode id = node.get("id");
|
||||
if (id == null) {
|
||||
GeyserImpl.getInstance().getLogger().debug("No UUID found in Mojang response for " + skullOwner.get("Name").getValue());
|
||||
return null;
|
||||
}
|
||||
uuidToString = id.asText();
|
||||
}
|
||||
|
||||
// Get textures from UUID
|
||||
node = WebUtils.getJson("https://sessionserver.mojang.com/session/minecraft/profile/" + uuidToString);
|
||||
JsonNode node = WebUtils.getJson("https://sessionserver.mojang.com/session/minecraft/profile/" + uuid);
|
||||
JsonNode properties = node.get("properties");
|
||||
if (properties == null) {
|
||||
GeyserImpl.getInstance().getLogger().debug("No properties found in Mojang response for " + uuidToString);
|
||||
GeyserImpl.getInstance().getLogger().debug("No properties found in Mojang response for " + uuid);
|
||||
return null;
|
||||
}
|
||||
return node.get("properties").get(0).get("value").asText();
|
||||
} catch (Exception e) {
|
||||
GeyserImpl.getInstance().getLogger().debug("Unable to request textures for " + uuid);
|
||||
if (GeyserImpl.getInstance().getConfig().isDebugMode()) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
@ -594,6 +569,37 @@ public class SkinProvider {
|
||||
}, EXECUTOR_SERVICE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Request textures from a player's username
|
||||
*
|
||||
* @param username the player's username
|
||||
* @return a completable GameProfile with textures included
|
||||
*/
|
||||
public static CompletableFuture<String> requestTexturesFromUsername(String username) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
// Offline skin, or no present UUID
|
||||
JsonNode node = WebUtils.getJson("https://api.mojang.com/users/profiles/minecraft/" + username);
|
||||
JsonNode id = node.get("id");
|
||||
if (id == null) {
|
||||
GeyserImpl.getInstance().getLogger().debug("No UUID found in Mojang response for " + username);
|
||||
return null;
|
||||
}
|
||||
return id.asText();
|
||||
} catch (Exception e) {
|
||||
if (GeyserImpl.getInstance().getConfig().isDebugMode()) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}, EXECUTOR_SERVICE).thenCompose(uuid -> {
|
||||
if (uuid == null) {
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
return requestTexturesFromUUID(uuid);
|
||||
});
|
||||
}
|
||||
|
||||
private static BufferedImage downloadImage(String imageUrl, CapeProvider provider) throws IOException {
|
||||
if (provider == CapeProvider.FIVEZIG)
|
||||
return readFiveZigCape(imageUrl);
|
||||
|
@ -27,6 +27,7 @@ package org.geysermc.geyser.translator.level.block.entity;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType;
|
||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.IntArrayTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.ListTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.StringTag;
|
||||
import com.nukkitx.math.vector.Vector3i;
|
||||
@ -39,6 +40,7 @@ import org.geysermc.geyser.session.cache.SkullCache;
|
||||
import org.geysermc.geyser.skin.SkinProvider;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
@ -62,7 +64,21 @@ public class SkullBlockEntityTranslator extends BlockEntityTranslator implements
|
||||
if (owner != null) {
|
||||
CompoundTag properties = owner.get("Properties");
|
||||
if (properties == null) {
|
||||
return SkinProvider.requestTexturesFromUsername(owner);
|
||||
if (owner.get("Id") instanceof IntArrayTag uuidTag) {
|
||||
int[] uuidAsArray = uuidTag.getValue();
|
||||
// thank u viaversion
|
||||
UUID uuid = new UUID((long) uuidAsArray[0] << 32 | ((long) uuidAsArray[1] & 0xFFFFFFFFL),
|
||||
(long) uuidAsArray[2] << 32 | ((long) uuidAsArray[3] & 0xFFFFFFFFL));
|
||||
if (uuid.version() == 4) {
|
||||
String uuidString = uuid.toString().replace("-", "");
|
||||
return SkinProvider.requestTexturesFromUUID(uuidString);
|
||||
}
|
||||
}
|
||||
if (owner.get("Name") instanceof StringTag nameTag) {
|
||||
// Fall back to username if UUID was missing or was an offline mode UUID
|
||||
return SkinProvider.requestTexturesFromUsername(nameTag.getValue());
|
||||
}
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
ListTag textures = properties.get("textures");
|
||||
|
@ -165,14 +165,11 @@ add-non-bedrock-items: true
|
||||
# This should only need to be disabled if using a proxy that does not use the "transfer packet" style of server switching.
|
||||
add-custom-blocks: true
|
||||
|
||||
# Whether to allow some custom skulls to be translated as custom blocks. This requires `add-custom-blocks` and `allow-custom-skulls` to be
|
||||
# enabled. This will generate a resource pack for Bedrock players and enables `force-resource-packs`
|
||||
add-custom-skull-blocks: true
|
||||
|
||||
# List of custom skull profiles to translate as custom blocks. This requires `add-custom-skull-blocks` to be enabled.
|
||||
# This is the Value stored in the SkullOwner of the custom skull
|
||||
custom-skull-profiles:
|
||||
- ewogICJ0aW1lc3RhbXAiIDogMTY1NzMyMjIzOTgzMywKICAicHJvZmlsZUlkIiA6ICJjZGRiZTUyMGQwNDM0YThiYTFjYzlmYzkyZmRlMmJjZiIsCiAgInByb2ZpbGVOYW1lIiA6ICJkYXZjaG9vIiwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlL2E5MDc5MGM1N2UxODFlZDEzYWRlZDE0YzQ3ZWUyZjdjOGRlMzUzM2UwMTdiYTk1N2FmN2JkZjlkZjFiZGU5NGYiLAogICAgICAibWV0YWRhdGEiIDogewogICAgICAgICJtb2RlbCIgOiAic2xpbSIKICAgICAgfQogICAgfQogIH0KfQ
|
||||
# Whether to allow some custom skulls to be translated as custom blocks and displayed in the inventory and on entities.
|
||||
# This requires `add-custom-blocks` and `allow-custom-skulls` to be enabled.
|
||||
# This will generate a resource pack for Bedrock players and enables `force-resource-packs`.
|
||||
# Custom skulls can be added in `custom-skulls.yml`
|
||||
add-custom-skull-blocks: false
|
||||
|
||||
# Bedrock prevents building and displaying blocks above Y127 in the Nether.
|
||||
# This config option works around that by changing the Nether dimension ID to the End ID.
|
||||
|
25
core/src/main/resources/custom-skulls.yml
Normale Datei
25
core/src/main/resources/custom-skulls.yml
Normale Datei
@ -0,0 +1,25 @@
|
||||
# --------------------------------
|
||||
# Geyser Custom Skull Configuration Files
|
||||
#
|
||||
# This file is ignored with `add-custom-skull-blocks` disabled.
|
||||
# See `config.yml` for the main set of configuration values
|
||||
#
|
||||
# Custom skulls with the player username, UUID, or texture specified in this file
|
||||
# will be translated as custom blocks and be displayed in the inventory and on entities.
|
||||
# --------------------------------
|
||||
|
||||
# Java player usernames
|
||||
# Skins will be updated when Geyser starts and players will have to re-download
|
||||
# the resource pack if any players had changed their skin.
|
||||
player-usernames:
|
||||
# - GeyserMC
|
||||
|
||||
# Java player UUIDs
|
||||
# Skins will be updated when Geyser starts and players will have to re-download
|
||||
# the resource pack if any players had changed their skin.
|
||||
player-uuids:
|
||||
# - 8b8d8e8f-2759-47c6-acb5-5827de8a72b8
|
||||
|
||||
# The long string of characters found in the NBT of custom player heads
|
||||
textures:
|
||||
# - ewogICJ0aW1lc3RhbXAiIDogMTY1NzMyMjIzOTgzMywKICAicHJvZmlsZUlkIiA6ICJjZGRiZTUyMGQwNDM0YThiYTFjYzlmYzkyZmRlMmJjZiIsCiAgInByb2ZpbGVOYW1lIiA6ICJkYXZjaG9vIiwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlL2E5MDc5MGM1N2UxODFlZDEzYWRlZDE0YzQ3ZWUyZjdjOGRlMzUzM2UwMTdiYTk1N2FmN2JkZjlkZjFiZGU5NGYiLAogICAgICAibWV0YWRhdGEiIDogewogICAgICAgICJtb2RlbCIgOiAic2xpbSIKICAgICAgfQogICAgfQogIH0KfQ
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren