Compare commits

...

3 Commits

Author SHA1 Message Date
rtm516 46f19dd6f7
Add originalSkin method to event 2024-04-27 00:07:49 +01:00
rtm516 92e4439075
Get rid of array and preserve original skin data 2024-04-27 00:00:41 +01:00
rtm516 d2a9ee53e8
Ajust for review and bump version to 2.2.4 2024-04-26 22:29:01 +01:00
6 changed files with 51 additions and 57 deletions

View File

@ -1,5 +1,6 @@
<component name="CopyrightManager">
<copyright>
<option name="allowReplaceRegexp" value="Copyright" />
<option name="notice" value="Copyright (c) &amp;#36;originalComment.match(&quot;Copyright \(c\) (\d+)&quot;, 1, &quot;-&quot;)&amp;#36;today.year GeyserMC. http://geysermc.org&#10;&#10;Permission is hereby granted, free of charge, to any person obtaining a copy&#10;of this software and associated documentation files (the &quot;Software&quot;), to deal&#10;in the Software without restriction, including without limitation the rights&#10;to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&#10;copies of the Software, and to permit persons to whom the Software is&#10;furnished to do so, subject to the following conditions:&#10;&#10;The above copyright notice and this permission notice shall be included in&#10;all copies or substantial portions of the Software.&#10;&#10;THE SOFTWARE IS PROVIDED &quot;AS IS&quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&#10;IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&#10;FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&#10;AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&#10;LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&#10;OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN&#10;THE SOFTWARE.&#10;&#10;@author GeyserMC&#10;@link https://github.com/GeyserMC/Geyser" />
<option name="myName" value="Geyser" />
</copyright>

View File

@ -46,7 +46,7 @@ public abstract class SessionSkinApplyEvent extends ConnectionEvent {
private final UUID uuid;
private final boolean slim;
private final boolean bedrock;
private final SkinData skinData;
private final SkinData originalSkinData;
public SessionSkinApplyEvent(@NonNull GeyserConnection connection, String username, UUID uuid, boolean slim, boolean bedrock, SkinData skinData) {
super(connection);
@ -54,7 +54,7 @@ public abstract class SessionSkinApplyEvent extends ConnectionEvent {
this.uuid = uuid;
this.slim = slim;
this.bedrock = bedrock;
this.skinData = skinData;
this.originalSkinData = skinData;
}
/**
@ -93,14 +93,21 @@ public abstract class SessionSkinApplyEvent extends ConnectionEvent {
return bedrock;
}
/**
* The original skin data of the player.
*
* @return the original skin data of the player
*/
public SkinData originalSkin() {
return originalSkinData;
}
/**
* The skin data of the player.
*
* @return the skin data of the player
*/
public SkinData skinData() {
return skinData;
}
public abstract SkinData skinData();
/**
* Change the skin of the player.

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
* Copyright (c) 2019-2024 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
@ -116,7 +116,7 @@ public class SkullResourcePackManager {
return;
}
BufferedImage image = SkinProvider.requestImage(skinUrl, null);
BufferedImage image = SkinProvider.requestImage(skinUrl, false);
// Resize skins to 48x16 to save on space and memory
BufferedImage skullTexture = new BufferedImage(48, 16, image.getType());
// Reorder skin parts to fit into the space

View File

@ -94,6 +94,7 @@ public class FakeHeadProvider {
// Avoiding memory leak
fakeHeadEntry.setEntity(null);
fakeHeadEntry.setSession(null);
return new SkinData(mergedSkin, cape, geometry);
}

View File

@ -261,25 +261,31 @@ public class SkinProvider {
// Call event to allow extensions to modify the skin, cape and geo
boolean isBedrock = GeyserImpl.getInstance().connectionByUuid(entity.getUuid()) != null;
final SkinData[] skinData = {new SkinData(skin, cape, geometry)};
GeyserImpl.getInstance().eventBus().fire(new SessionSkinApplyEvent(session, entity.getUsername(), entity.getUuid(), data.isAlex(), isBedrock, skinData[0]) {
SkinData skinData = new SkinData(skin, cape, geometry);
final EventSkinData eventSkinData = new EventSkinData(skinData);
GeyserImpl.getInstance().eventBus().fire(new SessionSkinApplyEvent(session, entity.getUsername(), entity.getUuid(), data.isAlex(), isBedrock, skinData) {
@Override
public SkinData skinData() {
return eventSkinData.skinData();
}
@Override
public void skin(@NonNull Skin newSkin) {
skinData[0] = new SkinData(newSkin, skinData[0].cape(), skinData[0].geometry());
eventSkinData.skinData(new SkinData(newSkin, skinData.cape(), skinData.geometry()));
}
@Override
public void cape(@NonNull Cape newCape) {
skinData[0] = new SkinData(skinData[0].skin(), newCape, skinData[0].geometry());
eventSkinData.skinData(new SkinData(skinData.skin(), newCape, skinData.geometry()));
}
@Override
public void geometry(@NonNull SkinGeometry newGeometry) {
skinData[0] = new SkinData(skinData[0].skin(), skinData[0].cape(), newGeometry);
eventSkinData.skinData(new SkinData(skinData.skin(), skinData.cape(), newGeometry));
}
});
return skinData[0];
return eventSkinData.skinData();
} catch (Exception e) {
GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.skin.fail", entity.getUuid()), e);
}
@ -292,10 +298,9 @@ public class SkinProvider {
return CompletableFuture.supplyAsync(() -> {
long time = System.currentTimeMillis();
CapeProvider provider = capeUrl != null ? CapeProvider.MINECRAFT : null;
SkinAndCape skinAndCape = new SkinAndCape(
getOrDefault(requestSkin(playerId, skinUrl, false), EMPTY_SKIN, 5),
getOrDefault(requestCape(capeUrl, provider, false), EMPTY_CAPE, 5)
getOrDefault(requestCape(capeUrl, false), EMPTY_CAPE, 5)
);
GeyserImpl.getInstance().getLogger().debug("Took " + (System.currentTimeMillis() - time) + "ms for " + playerId);
@ -332,7 +337,7 @@ public class SkinProvider {
return future;
}
private static CompletableFuture<Cape> requestCape(String capeUrl, CapeProvider provider, boolean newThread) {
private static CompletableFuture<Cape> requestCape(String capeUrl, boolean newThread) {
if (capeUrl == null || capeUrl.isEmpty()) return CompletableFuture.completedFuture(EMPTY_CAPE);
CompletableFuture<Cape> requestedCape = requestedCapes.get(capeUrl);
if (requestedCape != null) {
@ -346,14 +351,14 @@ public class SkinProvider {
CompletableFuture<Cape> future;
if (newThread) {
future = CompletableFuture.supplyAsync(() -> supplyCape(capeUrl, provider), getExecutorService())
future = CompletableFuture.supplyAsync(() -> supplyCape(capeUrl), getExecutorService())
.whenCompleteAsync((cape, throwable) -> {
CACHED_JAVA_CAPES.put(capeUrl, cape);
requestedCapes.remove(capeUrl);
});
requestedCapes.put(capeUrl, future);
} else {
Cape cape = supplyCape(capeUrl, provider); // blocking
Cape cape = supplyCape(capeUrl); // blocking
future = CompletableFuture.completedFuture(cape);
CACHED_JAVA_CAPES.put(capeUrl, cape);
}
@ -377,17 +382,17 @@ public class SkinProvider {
private static Skin supplySkin(UUID uuid, String textureUrl) {
try {
byte[] skin = requestImageData(textureUrl, null);
byte[] skin = requestImageData(textureUrl, false);
return new Skin(textureUrl, skin);
} catch (Exception ignored) {} // just ignore I guess
return new Skin("empty", EMPTY_SKIN.skinData(), true);
}
private static Cape supplyCape(String capeUrl, CapeProvider provider) {
private static Cape supplyCape(String capeUrl) {
byte[] cape = EMPTY_CAPE.capeData();
try {
cape = requestImageData(capeUrl, provider);
cape = requestImageData(capeUrl, true);
} catch (Exception ignored) {
} // just ignore I guess
@ -402,7 +407,7 @@ public class SkinProvider {
}
@SuppressWarnings("ResultOfMethodCallIgnored")
public static BufferedImage requestImage(String imageUrl, CapeProvider provider) throws IOException {
public static BufferedImage requestImage(String imageUrl, boolean isCape) 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
@ -417,7 +422,7 @@ public class SkinProvider {
// If no image we download it
if (image == null) {
image = downloadImage(imageUrl, provider);
image = downloadImage(imageUrl);
GeyserImpl.getInstance().getLogger().debug("Downloaded " + imageUrl);
// Write to cache if we are allowed
@ -433,7 +438,7 @@ public class SkinProvider {
}
// if the requested image is a cape
if (provider != null) {
if (isCape) {
if (image.getWidth() > 64 || image.getHeight() > 32) {
// Prevent weirdly-scaled capes from being cut off
BufferedImage newImage = new BufferedImage(128, 64, BufferedImage.TYPE_INT_ARGB);
@ -465,8 +470,8 @@ public class SkinProvider {
return image;
}
private static byte[] requestImageData(String imageUrl, CapeProvider provider) throws Exception {
BufferedImage image = requestImage(imageUrl, provider);
private static byte[] requestImageData(String imageUrl, boolean isCape) throws Exception {
BufferedImage image = requestImage(imageUrl, isCape);
byte[] data = bufferedImageToImageData(image);
image.flush();
return data;
@ -529,7 +534,7 @@ public class SkinProvider {
});
}
private static BufferedImage downloadImage(String imageUrl, CapeProvider provider) throws IOException {
private static BufferedImage downloadImage(String imageUrl) throws IOException {
HttpURLConnection con = (HttpURLConnection) new URL(imageUrl).openConnection();
con.setRequestProperty("User-Agent", WebUtils.getUserAgent());
con.setConnectTimeout(10000);
@ -538,7 +543,7 @@ public class SkinProvider {
BufferedImage image = ImageIO.read(con.getInputStream());
if (image == null) {
throw new IllegalArgumentException("Failed to read image from: %s (cape provider=%s)".formatted(imageUrl, provider));
throw new IllegalArgumentException("Failed to read image from: %s".formatted(imageUrl));
}
return image;
}
@ -616,39 +621,19 @@ public class SkinProvider {
public record SkinAndCape(Skin skin, Cape cape) {
}
/*
* Sorted by 'priority'
*/
@AllArgsConstructor
@NoArgsConstructor
@Getter
public enum CapeProvider {
MINECRAFT;
public static class EventSkinData {
private SkinData skinData;
public static final CapeProvider[] VALUES = Arrays.copyOfRange(values(), 1, 5);
private String url;
private CapeUrlType type;
public String getUrlFor(String type) {
return String.format(url, type);
public EventSkinData(SkinData skinData) {
this.skinData = skinData;
}
public String getUrlFor(UUID uuid, String username) {
return getUrlFor(toRequestedType(type, uuid, username));
public SkinData skinData() {
return skinData;
}
public static String toRequestedType(CapeUrlType type, UUID uuid, String username) {
return switch (type) {
case UUID -> uuid.toString().replace("-", "");
case UUID_DASHED -> uuid.toString();
default -> username;
};
public void skinData(SkinData skinData) {
this.skinData = skinData;
}
}
public enum CapeUrlType {
USERNAME,
UUID,
UUID_DASHED
}
}

View File

@ -7,5 +7,5 @@ org.gradle.vfs.watch=false
group=org.geysermc
id=geyser
version=2.2.3-SNAPSHOT
version=2.2.4-SNAPSHOT
description=Allows for players from Minecraft: Bedrock Edition to join Minecraft: Java Edition servers.