Mirror von
https://github.com/GeyserMC/Geyser.git
synchronisiert 2024-12-26 00:00:41 +01:00
Merge branch 'master' of https://github.com/GeyserMC/Geyser into server-inventory
Dieser Commit ist enthalten in:
Commit
aa47e75da6
@ -20,13 +20,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.fasterxml.jackson.dataformat</groupId>
|
<groupId>com.fasterxml.jackson.dataformat</groupId>
|
||||||
<artifactId>jackson-dataformat-yaml</artifactId>
|
<artifactId>jackson-dataformat-yaml</artifactId>
|
||||||
<version>2.9.8</version>
|
<version>2.10.2</version>
|
||||||
<scope>compile</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
|
||||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
|
||||||
<version>2.9.8</version>
|
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
@ -175,25 +169,25 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>net.kyori</groupId>
|
<groupId>net.kyori</groupId>
|
||||||
<artifactId>adventure-api</artifactId>
|
<artifactId>adventure-api</artifactId>
|
||||||
<version>4.3.0</version>
|
<version>4.5.0</version>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>net.kyori</groupId>
|
<groupId>net.kyori</groupId>
|
||||||
<artifactId>adventure-text-serializer-gson</artifactId>
|
<artifactId>adventure-text-serializer-gson</artifactId>
|
||||||
<version>4.3.0</version>
|
<version>4.5.0</version>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>net.kyori</groupId>
|
<groupId>net.kyori</groupId>
|
||||||
<artifactId>adventure-text-serializer-legacy</artifactId>
|
<artifactId>adventure-text-serializer-legacy</artifactId>
|
||||||
<version>4.3.0</version>
|
<version>4.5.0</version>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>net.kyori</groupId>
|
<groupId>net.kyori</groupId>
|
||||||
<artifactId>adventure-text-serializer-gson-legacy-impl</artifactId>
|
<artifactId>adventure-text-serializer-gson-legacy-impl</artifactId>
|
||||||
<version>4.3.0</version>
|
<version>4.5.0</version>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
|
@ -77,7 +77,8 @@ public class GeyserConnector {
|
|||||||
.enable(JsonParser.Feature.IGNORE_UNDEFINED)
|
.enable(JsonParser.Feature.IGNORE_UNDEFINED)
|
||||||
.enable(JsonParser.Feature.ALLOW_COMMENTS)
|
.enable(JsonParser.Feature.ALLOW_COMMENTS)
|
||||||
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
|
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
|
||||||
.enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES);
|
.enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES)
|
||||||
|
.enable(JsonParser.Feature.ALLOW_SINGLE_QUOTES);
|
||||||
|
|
||||||
public static final String NAME = "Geyser";
|
public static final String NAME = "Geyser";
|
||||||
public static final String GIT_VERSION = "DEV"; // A fallback for running in IDEs
|
public static final String GIT_VERSION = "DEV"; // A fallback for running in IDEs
|
||||||
|
@ -42,6 +42,15 @@ import java.net.InetSocketAddress;
|
|||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
public class ConnectorServerEventHandler implements BedrockServerEventHandler {
|
public class ConnectorServerEventHandler implements BedrockServerEventHandler {
|
||||||
|
/*
|
||||||
|
The following constants are all used to ensure the ping does not reach a length where it is unparsable by the Bedrock client
|
||||||
|
*/
|
||||||
|
private static final int MINECRAFT_VERSION_BYTES_LENGTH = BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion().getBytes(StandardCharsets.UTF_8).length;
|
||||||
|
private static final int BRAND_BYTES_LENGTH = GeyserConnector.NAME.getBytes(StandardCharsets.UTF_8).length;
|
||||||
|
/**
|
||||||
|
* The MOTD, sub-MOTD and Minecraft version ({@link #MINECRAFT_VERSION_BYTES_LENGTH}) combined cannot reach this length.
|
||||||
|
*/
|
||||||
|
private static final int MAGIC_RAKNET_LENGTH = 338;
|
||||||
|
|
||||||
private final GeyserConnector connector;
|
private final GeyserConnector connector;
|
||||||
|
|
||||||
@ -69,16 +78,16 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler {
|
|||||||
|
|
||||||
BedrockPong pong = new BedrockPong();
|
BedrockPong pong = new BedrockPong();
|
||||||
pong.setEdition("MCPE");
|
pong.setEdition("MCPE");
|
||||||
pong.setGameType("Default");
|
pong.setGameType("Survival"); // Can only be Survival or Creative as of 1.16.210.59
|
||||||
pong.setNintendoLimited(false);
|
pong.setNintendoLimited(false);
|
||||||
pong.setProtocolVersion(BedrockProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion());
|
pong.setProtocolVersion(BedrockProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion());
|
||||||
pong.setVersion(null); // Server tries to connect either way and it looks better
|
pong.setVersion(BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion()); // Required to not be empty as of 1.16.210.59. Can only contain . and numbers.
|
||||||
pong.setIpv4Port(config.getBedrock().getPort());
|
pong.setIpv4Port(config.getBedrock().getPort());
|
||||||
|
|
||||||
if (config.isPassthroughMotd() && pingInfo != null && pingInfo.getDescription() != null) {
|
if (config.isPassthroughMotd() && pingInfo != null && pingInfo.getDescription() != null) {
|
||||||
String[] motd = MessageTranslator.convertMessageLenient(pingInfo.getDescription()).split("\n");
|
String[] motd = MessageTranslator.convertMessageLenient(pingInfo.getDescription()).split("\n");
|
||||||
String mainMotd = motd[0]; // First line of the motd.
|
String mainMotd = motd[0]; // First line of the motd.
|
||||||
String subMotd = (motd.length != 1) ? motd[1] : ""; // Second line of the motd if present, otherwise blank.
|
String subMotd = (motd.length != 1) ? motd[1] : GeyserConnector.NAME; // Second line of the motd if present, otherwise default.
|
||||||
|
|
||||||
pong.setMotd(mainMotd.trim());
|
pong.setMotd(mainMotd.trim());
|
||||||
pong.setSubMotd(subMotd.trim()); // Trimmed to shift it to the left, prevents the universe from collapsing on us just because we went 2 characters over the text box's limit.
|
pong.setSubMotd(subMotd.trim()); // Trimmed to shift it to the left, prevents the universe from collapsing on us just because we went 2 characters over the text box's limit.
|
||||||
@ -95,15 +104,28 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler {
|
|||||||
pong.setMaximumPlayerCount(config.getMaxPlayers());
|
pong.setMaximumPlayerCount(config.getMaxPlayers());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fallbacks to prevent errors and allow Bedrock to see the server
|
||||||
|
if (pong.getMotd() == null || pong.getMotd().trim().isEmpty()) {
|
||||||
|
pong.setMotd(GeyserConnector.NAME);
|
||||||
|
}
|
||||||
|
if (pong.getSubMotd() == null || pong.getSubMotd().trim().isEmpty()) {
|
||||||
|
// Sub-MOTD cannot be empty as of 1.16.210.59
|
||||||
|
pong.setSubMotd(GeyserConnector.NAME);
|
||||||
|
}
|
||||||
|
|
||||||
// The ping will not appear if the MOTD + sub-MOTD is of a certain length.
|
// The ping will not appear if the MOTD + sub-MOTD is of a certain length.
|
||||||
// We don't know why, though
|
// We don't know why, though
|
||||||
byte[] motdArray = pong.getMotd().getBytes(StandardCharsets.UTF_8);
|
byte[] motdArray = pong.getMotd().getBytes(StandardCharsets.UTF_8);
|
||||||
if (motdArray.length + pong.getSubMotd().getBytes(StandardCharsets.UTF_8).length > 338) {
|
int subMotdLength = pong.getSubMotd().getBytes(StandardCharsets.UTF_8).length;
|
||||||
// Remove the sub-MOTD first since that only appears locally
|
if (motdArray.length + subMotdLength > (MAGIC_RAKNET_LENGTH - MINECRAFT_VERSION_BYTES_LENGTH)) {
|
||||||
pong.setSubMotd("");
|
// Shorten the sub-MOTD first since that only appears locally
|
||||||
if (motdArray.length > 338) {
|
if (subMotdLength > BRAND_BYTES_LENGTH) {
|
||||||
|
pong.setSubMotd(GeyserConnector.NAME);
|
||||||
|
subMotdLength = BRAND_BYTES_LENGTH;
|
||||||
|
}
|
||||||
|
if (motdArray.length > (MAGIC_RAKNET_LENGTH - MINECRAFT_VERSION_BYTES_LENGTH - subMotdLength)) {
|
||||||
// If the top MOTD is still too long, we chop it down
|
// If the top MOTD is still too long, we chop it down
|
||||||
byte[] newMotdArray = new byte[339];
|
byte[] newMotdArray = new byte[MAGIC_RAKNET_LENGTH - MINECRAFT_VERSION_BYTES_LENGTH - subMotdLength];
|
||||||
System.arraycopy(motdArray, 0, newMotdArray, 0, newMotdArray.length);
|
System.arraycopy(motdArray, 0, newMotdArray, 0, newMotdArray.length);
|
||||||
pong.setMotd(new String(newMotdArray, StandardCharsets.UTF_8));
|
pong.setMotd(new String(newMotdArray, StandardCharsets.UTF_8));
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,7 @@ import com.nukkitx.protocol.bedrock.packet.PlayerListPacket;
|
|||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import org.geysermc.connector.GeyserConnector;
|
import org.geysermc.connector.GeyserConnector;
|
||||||
|
import org.geysermc.connector.common.AuthType;
|
||||||
import org.geysermc.connector.entity.player.PlayerEntity;
|
import org.geysermc.connector.entity.player.PlayerEntity;
|
||||||
import org.geysermc.connector.network.session.GeyserSession;
|
import org.geysermc.connector.network.session.GeyserSession;
|
||||||
import org.geysermc.connector.network.session.auth.BedrockClientData;
|
import org.geysermc.connector.network.session.auth.BedrockClientData;
|
||||||
@ -163,7 +164,7 @@ public class SkinManager {
|
|||||||
geometry = SkinProvider.SkinGeometry.getEars(data.isAlex());
|
geometry = SkinProvider.SkinGeometry.getEars(data.isAlex());
|
||||||
|
|
||||||
// Store the skin and geometry for the ears
|
// Store the skin and geometry for the ears
|
||||||
SkinProvider.storeEarSkin(entity.getUuid(), skin);
|
SkinProvider.storeEarSkin(skin);
|
||||||
SkinProvider.storeEarGeometry(entity.getUuid(), data.isAlex());
|
SkinProvider.storeEarGeometry(entity.getUuid(), data.isAlex());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -267,7 +268,10 @@ public class SkinManager {
|
|||||||
|
|
||||||
return new GameProfileData(skinUrl, capeUrl, isAlex);
|
return new GameProfileData(skinUrl, capeUrl, isAlex);
|
||||||
} catch (Exception exception) {
|
} catch (Exception exception) {
|
||||||
GeyserConnector.getInstance().getLogger().debug("Something went wrong while processing skin for " + profile.getName() + ": " + exception.getMessage());
|
GeyserConnector.getInstance().getLogger().debug("Something went wrong while processing skin for " + profile.getName());
|
||||||
|
if (GeyserConnector.getInstance().getConfig().isDebugMode()) {
|
||||||
|
exception.printStackTrace();
|
||||||
|
}
|
||||||
return loadBedrockOrOfflineSkin(profile);
|
return loadBedrockOrOfflineSkin(profile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -282,7 +286,7 @@ public class SkinManager {
|
|||||||
|
|
||||||
String skinUrl = isAlex ? SkinProvider.EMPTY_SKIN_ALEX.getTextureUrl() : SkinProvider.EMPTY_SKIN.getTextureUrl();
|
String skinUrl = isAlex ? SkinProvider.EMPTY_SKIN_ALEX.getTextureUrl() : SkinProvider.EMPTY_SKIN.getTextureUrl();
|
||||||
String capeUrl = SkinProvider.EMPTY_CAPE.getTextureUrl();
|
String capeUrl = SkinProvider.EMPTY_CAPE.getTextureUrl();
|
||||||
if ("steve".equals(skinUrl) || "alex".equals(skinUrl)) {
|
if (("steve".equals(skinUrl) || "alex".equals(skinUrl)) && GeyserConnector.getInstance().getAuthType() != AuthType.ONLINE) {
|
||||||
GeyserSession session = GeyserConnector.getInstance().getPlayerByUuid(profile.getId());
|
GeyserSession session = GeyserConnector.getInstance().getPlayerByUuid(profile.getId());
|
||||||
|
|
||||||
if (session != null) {
|
if (session != null) {
|
||||||
|
@ -79,13 +79,12 @@ public class SkinProvider {
|
|||||||
.build();
|
.build();
|
||||||
private static final Map<String, CompletableFuture<Cape>> requestedCapes = new ConcurrentHashMap<>();
|
private static final Map<String, CompletableFuture<Cape>> requestedCapes = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
public static final SkinGeometry EMPTY_GEOMETRY = SkinProvider.SkinGeometry.getLegacy(false);
|
|
||||||
private static final Map<UUID, SkinGeometry> cachedGeometry = new ConcurrentHashMap<>();
|
private static final Map<UUID, SkinGeometry> cachedGeometry = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
public static final boolean ALLOW_THIRD_PARTY_EARS = GeyserConnector.getInstance().getConfig().isAllowThirdPartyEars();
|
public static final boolean ALLOW_THIRD_PARTY_EARS = GeyserConnector.getInstance().getConfig().isAllowThirdPartyEars();
|
||||||
public static String EARS_GEOMETRY;
|
public static final String EARS_GEOMETRY;
|
||||||
public static String EARS_GEOMETRY_SLIM;
|
public static final String EARS_GEOMETRY_SLIM;
|
||||||
public static SkinGeometry SKULL_GEOMETRY;
|
public static final SkinGeometry SKULL_GEOMETRY;
|
||||||
|
|
||||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||||
|
|
||||||
@ -229,15 +228,15 @@ public class SkinProvider {
|
|||||||
return CompletableFuture.completedFuture(officialCape);
|
return CompletableFuture.completedFuture(officialCape);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CompletableFuture<Skin> requestEars(String earsUrl, EarsProvider provider, boolean newThread, Skin skin) {
|
public static CompletableFuture<Skin> requestEars(String earsUrl, boolean newThread, Skin skin) {
|
||||||
if (earsUrl == null || earsUrl.isEmpty()) return CompletableFuture.completedFuture(skin);
|
if (earsUrl == null || earsUrl.isEmpty()) return CompletableFuture.completedFuture(skin);
|
||||||
|
|
||||||
CompletableFuture<Skin> future;
|
CompletableFuture<Skin> future;
|
||||||
if (newThread) {
|
if (newThread) {
|
||||||
future = CompletableFuture.supplyAsync(() -> supplyEars(skin, earsUrl, provider), EXECUTOR_SERVICE)
|
future = CompletableFuture.supplyAsync(() -> supplyEars(skin, earsUrl), EXECUTOR_SERVICE)
|
||||||
.whenCompleteAsync((outSkin, throwable) -> { });
|
.whenCompleteAsync((outSkin, throwable) -> { });
|
||||||
} else {
|
} else {
|
||||||
Skin ears = supplyEars(skin, earsUrl, provider); // blocking
|
Skin ears = supplyEars(skin, earsUrl); // blocking
|
||||||
future = CompletableFuture.completedFuture(ears);
|
future = CompletableFuture.completedFuture(ears);
|
||||||
}
|
}
|
||||||
return future;
|
return future;
|
||||||
@ -255,7 +254,7 @@ public class SkinProvider {
|
|||||||
public static CompletableFuture<Skin> requestUnofficialEars(Skin officialSkin, UUID playerId, String username, boolean newThread) {
|
public static CompletableFuture<Skin> requestUnofficialEars(Skin officialSkin, UUID playerId, String username, boolean newThread) {
|
||||||
for (EarsProvider provider : EarsProvider.VALUES) {
|
for (EarsProvider provider : EarsProvider.VALUES) {
|
||||||
Skin skin1 = getOrDefault(
|
Skin skin1 = getOrDefault(
|
||||||
requestEars(provider.getUrlFor(playerId, username), provider, newThread, officialSkin),
|
requestEars(provider.getUrlFor(playerId, username), newThread, officialSkin),
|
||||||
officialSkin, 4
|
officialSkin, 4
|
||||||
);
|
);
|
||||||
if (skin1.isEars()) {
|
if (skin1.isEars()) {
|
||||||
@ -295,12 +294,11 @@ public class SkinProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores the ajusted skin with the ear texture to the cache
|
* Stores the adjusted skin with the ear texture to the cache
|
||||||
*
|
*
|
||||||
* @param playerID The UUID to cache it against
|
|
||||||
* @param skin The skin to cache
|
* @param skin The skin to cache
|
||||||
*/
|
*/
|
||||||
public static void storeEarSkin(UUID playerID, Skin skin) {
|
public static void storeEarSkin(Skin skin) {
|
||||||
cachedSkins.put(skin.getTextureUrl(), skin);
|
cachedSkins.put(skin.getTextureUrl(), skin);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -324,7 +322,7 @@ public class SkinProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static Cape supplyCape(String capeUrl, CapeProvider provider) {
|
private static Cape supplyCape(String capeUrl, CapeProvider provider) {
|
||||||
byte[] cape = new byte[0];
|
byte[] cape = EMPTY_CAPE.getCapeData();
|
||||||
try {
|
try {
|
||||||
cape = requestImage(capeUrl, provider);
|
cape = requestImage(capeUrl, provider);
|
||||||
} catch (Exception ignored) {} // just ignore I guess
|
} catch (Exception ignored) {} // just ignore I guess
|
||||||
@ -334,7 +332,7 @@ public class SkinProvider {
|
|||||||
return new Cape(
|
return new Cape(
|
||||||
capeUrl,
|
capeUrl,
|
||||||
urlSection[urlSection.length - 1], // get the texture id and use it as cape id
|
urlSection[urlSection.length - 1], // get the texture id and use it as cape id
|
||||||
cape.length > 0 ? cape : EMPTY_CAPE.getCapeData(),
|
cape,
|
||||||
System.currentTimeMillis(),
|
System.currentTimeMillis(),
|
||||||
cape.length == 0
|
cape.length == 0
|
||||||
);
|
);
|
||||||
@ -345,10 +343,9 @@ public class SkinProvider {
|
|||||||
*
|
*
|
||||||
* @param existingSkin The players current skin
|
* @param existingSkin The players current skin
|
||||||
* @param earsUrl The URL to get the ears texture from
|
* @param earsUrl The URL to get the ears texture from
|
||||||
* @param provider The ears texture provider
|
|
||||||
* @return The updated skin with ears
|
* @return The updated skin with ears
|
||||||
*/
|
*/
|
||||||
private static Skin supplyEars(Skin existingSkin, String earsUrl, EarsProvider provider) {
|
private static Skin supplyEars(Skin existingSkin, String earsUrl) {
|
||||||
try {
|
try {
|
||||||
// Get the ears texture
|
// Get the ears texture
|
||||||
BufferedImage ears = ImageIO.read(new URL(earsUrl));
|
BufferedImage ears = ImageIO.read(new URL(earsUrl));
|
||||||
@ -415,14 +412,15 @@ public class SkinProvider {
|
|||||||
|
|
||||||
// if the requested image is a cape
|
// if the requested image is a cape
|
||||||
if (provider != null) {
|
if (provider != null) {
|
||||||
while(image.getWidth() > 64) {
|
if (image.getWidth() > 64) {
|
||||||
image = scale(image);
|
image = scale(image, 64, 32);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Very rarely, skins can be larger than Minecraft's default.
|
||||||
|
// Bedrock will not render anything above a width of 128.
|
||||||
|
if (image.getWidth() > 128) {
|
||||||
|
image = scale(image, 128, image.getHeight() / (image.getWidth() / 128));
|
||||||
}
|
}
|
||||||
BufferedImage newImage = new BufferedImage(64, 32, BufferedImage.TYPE_INT_ARGB);
|
|
||||||
Graphics g = newImage.createGraphics();
|
|
||||||
g.drawImage(image, 0, 0, image.getWidth(), image.getHeight(), null);
|
|
||||||
g.dispose();
|
|
||||||
image = newImage;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] data = bufferedImageToImageData(image);
|
byte[] data = bufferedImageToImageData(image);
|
||||||
@ -506,12 +504,13 @@ public class SkinProvider {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static BufferedImage scale(BufferedImage bufferedImage) {
|
private static BufferedImage scale(BufferedImage bufferedImage, int newWidth, int newHeight) {
|
||||||
BufferedImage resized = new BufferedImage(bufferedImage.getWidth() / 2, bufferedImage.getHeight() / 2, BufferedImage.TYPE_INT_ARGB);
|
BufferedImage resized = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_ARGB);
|
||||||
Graphics2D g2 = resized.createGraphics();
|
Graphics2D g2 = resized.createGraphics();
|
||||||
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||||
g2.drawImage(bufferedImage, 0, 0, bufferedImage.getWidth() / 2, bufferedImage.getHeight() / 2, null);
|
g2.drawImage(bufferedImage, 0, 0, newWidth, newHeight, null);
|
||||||
g2.dispose();
|
g2.dispose();
|
||||||
|
bufferedImage.flush();
|
||||||
return resized;
|
return resized;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -579,17 +578,17 @@ public class SkinProvider {
|
|||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@Getter
|
@Getter
|
||||||
public static class SkinAndCape {
|
public static class SkinAndCape {
|
||||||
private Skin skin;
|
private final Skin skin;
|
||||||
private Cape cape;
|
private final Cape cape;
|
||||||
}
|
}
|
||||||
|
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@Getter
|
@Getter
|
||||||
public static class Skin {
|
public static class Skin {
|
||||||
private UUID skinOwner;
|
private UUID skinOwner;
|
||||||
private String textureUrl;
|
private final String textureUrl;
|
||||||
private byte[] skinData;
|
private final byte[] skinData;
|
||||||
private long requestedOn;
|
private final long requestedOn;
|
||||||
private boolean updated;
|
private boolean updated;
|
||||||
private boolean ears;
|
private boolean ears;
|
||||||
|
|
||||||
@ -603,19 +602,19 @@ public class SkinProvider {
|
|||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@Getter
|
@Getter
|
||||||
public static class Cape {
|
public static class Cape {
|
||||||
private String textureUrl;
|
private final String textureUrl;
|
||||||
private String capeId;
|
private final String capeId;
|
||||||
private byte[] capeData;
|
private final byte[] capeData;
|
||||||
private long requestedOn;
|
private final long requestedOn;
|
||||||
private boolean failed;
|
private final boolean failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@Getter
|
@Getter
|
||||||
public static class SkinGeometry {
|
public static class SkinGeometry {
|
||||||
private String geometryName;
|
private final String geometryName;
|
||||||
private String geometryData;
|
private final String geometryData;
|
||||||
private boolean failed;
|
private final boolean failed;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate generic geometry
|
* Generate generic geometry
|
||||||
|
@ -27,65 +27,42 @@ package org.geysermc.connector.skin;
|
|||||||
|
|
||||||
import com.nukkitx.protocol.bedrock.data.skin.ImageData;
|
import com.nukkitx.protocol.bedrock.data.skin.ImageData;
|
||||||
import com.nukkitx.protocol.bedrock.data.skin.SerializedSkin;
|
import com.nukkitx.protocol.bedrock.data.skin.SerializedSkin;
|
||||||
import com.nukkitx.protocol.bedrock.packet.PlayerListPacket;
|
import com.nukkitx.protocol.bedrock.packet.PlayerSkinPacket;
|
||||||
import org.geysermc.connector.GeyserConnector;
|
import org.geysermc.connector.GeyserConnector;
|
||||||
import org.geysermc.connector.entity.player.PlayerEntity;
|
import org.geysermc.connector.entity.player.PlayerEntity;
|
||||||
import org.geysermc.connector.network.session.GeyserSession;
|
import org.geysermc.connector.network.session.GeyserSession;
|
||||||
import org.geysermc.connector.utils.LanguageUtils;
|
import org.geysermc.connector.utils.LanguageUtils;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
public class SkullSkinManager extends SkinManager {
|
public class SkullSkinManager extends SkinManager {
|
||||||
|
|
||||||
public static PlayerListPacket.Entry buildSkullEntryManually(UUID uuid, String username, long geyserId,
|
public static SerializedSkin buildSkullEntryManually(String skinId, byte[] skinData) {
|
||||||
String skinId, byte[] skinData) {
|
|
||||||
// Prevents https://cdn.discordapp.com/attachments/613194828359925800/779458146191147008/unknown.png
|
// Prevents https://cdn.discordapp.com/attachments/613194828359925800/779458146191147008/unknown.png
|
||||||
skinId = skinId + "_skull";
|
skinId = skinId + "_skull";
|
||||||
SerializedSkin serializedSkin = SerializedSkin.of(
|
return SerializedSkin.of(
|
||||||
skinId, SkinProvider.SKULL_GEOMETRY.getGeometryName(), ImageData.of(skinData), Collections.emptyList(),
|
skinId, SkinProvider.SKULL_GEOMETRY.getGeometryName(), ImageData.of(skinData), Collections.emptyList(),
|
||||||
ImageData.of(SkinProvider.EMPTY_CAPE.getCapeData()), SkinProvider.SKULL_GEOMETRY.getGeometryData(),
|
ImageData.of(SkinProvider.EMPTY_CAPE.getCapeData()), SkinProvider.SKULL_GEOMETRY.getGeometryData(),
|
||||||
"", true, false, false, SkinProvider.EMPTY_CAPE.getCapeId(), skinId
|
"", true, false, false, SkinProvider.EMPTY_CAPE.getCapeId(), skinId
|
||||||
);
|
);
|
||||||
|
|
||||||
PlayerListPacket.Entry entry = new PlayerListPacket.Entry(uuid);
|
|
||||||
entry.setName(username);
|
|
||||||
entry.setEntityId(geyserId);
|
|
||||||
entry.setSkin(serializedSkin);
|
|
||||||
entry.setXuid("");
|
|
||||||
entry.setPlatformChatId("");
|
|
||||||
entry.setTeacher(false);
|
|
||||||
entry.setTrustedSkin(true);
|
|
||||||
return entry;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void requestAndHandleSkin(PlayerEntity entity, GeyserSession session,
|
public static void requestAndHandleSkin(PlayerEntity entity, GeyserSession session,
|
||||||
Consumer<SkinProvider.Skin> skinConsumer) {
|
Consumer<SkinProvider.Skin> skinConsumer) {
|
||||||
GameProfileData data = GameProfileData.from(entity.getProfile());
|
GameProfileData data = GameProfileData.from(entity.getProfile());
|
||||||
|
|
||||||
SkinProvider.requestSkin(entity.getUuid(), data.getSkinUrl(), false)
|
SkinProvider.requestSkin(entity.getUuid(), data.getSkinUrl(), true)
|
||||||
.whenCompleteAsync((skin, throwable) -> {
|
.whenCompleteAsync((skin, throwable) -> {
|
||||||
try {
|
try {
|
||||||
if (session.getUpstream().isInitialized()) {
|
if (session.getUpstream().isInitialized()) {
|
||||||
PlayerListPacket.Entry updatedEntry = buildSkullEntryManually(
|
PlayerSkinPacket packet = new PlayerSkinPacket();
|
||||||
entity.getUuid(),
|
packet.setUuid(entity.getUuid());
|
||||||
entity.getUsername(),
|
packet.setOldSkinName("");
|
||||||
entity.getGeyserId(),
|
packet.setNewSkinName(skin.getTextureUrl());
|
||||||
skin.getTextureUrl(),
|
packet.setSkin(buildSkullEntryManually(skin.getTextureUrl(), skin.getSkinData()));
|
||||||
skin.getSkinData()
|
packet.setTrustedSkin(true);
|
||||||
);
|
session.sendUpstreamPacket(packet);
|
||||||
|
|
||||||
PlayerListPacket playerAddPacket = new PlayerListPacket();
|
|
||||||
playerAddPacket.setAction(PlayerListPacket.Action.ADD);
|
|
||||||
playerAddPacket.getEntries().add(updatedEntry);
|
|
||||||
session.sendUpstreamPacket(playerAddPacket);
|
|
||||||
|
|
||||||
// It's a skull. We don't want them in the player list.
|
|
||||||
PlayerListPacket playerRemovePacket = new PlayerListPacket();
|
|
||||||
playerRemovePacket.setAction(PlayerListPacket.Action.REMOVE);
|
|
||||||
playerRemovePacket.getEntries().add(updatedEntry);
|
|
||||||
session.sendUpstreamPacket(playerRemovePacket);
|
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.skin.fail", entity.getUuid()), e);
|
GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.skin.fail", entity.getUuid()), e);
|
||||||
|
@ -18,8 +18,9 @@ bedrock:
|
|||||||
# This option is for the plugin version only.
|
# This option is for the plugin version only.
|
||||||
clone-remote-port: false
|
clone-remote-port: false
|
||||||
# The MOTD that will be broadcasted to Minecraft: Bedrock Edition clients. This is irrelevant if "passthrough-motd" is set to true
|
# The MOTD that will be broadcasted to Minecraft: Bedrock Edition clients. This is irrelevant if "passthrough-motd" is set to true
|
||||||
motd1: "GeyserMC"
|
# If either of these are empty, the respective string will default to "Geyser"
|
||||||
motd2: "Another GeyserMC forced host."
|
motd1: "Geyser"
|
||||||
|
motd2: "Another Geyser server."
|
||||||
# The Server Name that will be sent to Minecraft: Bedrock Edition clients. This is visible in both the pause menu and the settings menu.
|
# The Server Name that will be sent to Minecraft: Bedrock Edition clients. This is visible in both the pause menu and the settings menu.
|
||||||
server-name: "Geyser"
|
server-name: "Geyser"
|
||||||
remote:
|
remote:
|
||||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren