Allow for custom skin renderers
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m41s
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m41s
This commit is contained in:
parent
2f7b9f6b10
commit
16e23e82f9
@ -26,19 +26,21 @@ package me.braydon.mc.common.renderer;
|
|||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import me.braydon.mc.common.ImageUtils;
|
import me.braydon.mc.common.ImageUtils;
|
||||||
import me.braydon.mc.model.Skin;
|
import me.braydon.mc.model.skin.ISkinPart;
|
||||||
|
import me.braydon.mc.model.skin.Skin;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
|
import java.awt.*;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A renderer for a {@link Skin.Part}.
|
* A renderer for a {@link ISkinPart}.
|
||||||
*
|
*
|
||||||
* @author Braydon
|
* @author Braydon
|
||||||
* @param <T> the type of part to render
|
* @param <T> the type of part to render
|
||||||
*/
|
*/
|
||||||
public abstract class SkinPartRenderer<T extends Skin.IPart> {
|
public abstract class SkinRenderer<T extends ISkinPart> {
|
||||||
/**
|
/**
|
||||||
* Invoke this render to render the
|
* Invoke this render to render the
|
||||||
* given skin part for the provided skin.
|
* given skin part for the provided skin.
|
||||||
@ -60,7 +62,7 @@ public abstract class SkinPartRenderer<T extends Skin.IPart> {
|
|||||||
* @return the texture of the skin part
|
* @return the texture of the skin part
|
||||||
*/
|
*/
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
protected BufferedImage getSkinPart(@NonNull Skin skin, @NonNull Skin.Part part, double size) {
|
protected final BufferedImage getVanillaSkinPart(@NonNull Skin skin, @NonNull ISkinPart.Vanilla part, double size) {
|
||||||
return getSkinPartTexture(skin, part.getCoordinates().getX(), part.getCoordinates().getY(), part.getWidth(), part.getHeight(), size);
|
return getSkinPartTexture(skin, part.getCoordinates().getX(), part.getCoordinates().getY(), part.getWidth(), part.getHeight(), size);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,4 +92,29 @@ public abstract class SkinPartRenderer<T extends Skin.IPart> {
|
|||||||
|
|
||||||
return headTexture;
|
return headTexture;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply an overlay to a texture.
|
||||||
|
*
|
||||||
|
* @param overlayImage the part to overlay
|
||||||
|
*/
|
||||||
|
protected final void applyOverlay(@NonNull BufferedImage overlayImage) {
|
||||||
|
applyOverlay(overlayImage.createGraphics(), overlayImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply an overlay to a texture.
|
||||||
|
*
|
||||||
|
* @param graphics the graphics to overlay on
|
||||||
|
* @param overlayImage the part to overlay
|
||||||
|
*/
|
||||||
|
protected final void applyOverlay(@NonNull Graphics2D graphics, @NonNull BufferedImage overlayImage) {
|
||||||
|
try {
|
||||||
|
graphics.drawImage(overlayImage, 0, 0, null);
|
||||||
|
graphics.dispose();
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
// We can safely ignore this, legacy
|
||||||
|
// skins don't have overlays
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -24,18 +24,19 @@
|
|||||||
package me.braydon.mc.common.renderer.impl;
|
package me.braydon.mc.common.renderer.impl;
|
||||||
|
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import me.braydon.mc.common.renderer.SkinPartRenderer;
|
import me.braydon.mc.common.renderer.SkinRenderer;
|
||||||
import me.braydon.mc.model.Skin;
|
import me.braydon.mc.model.skin.ISkinPart;
|
||||||
|
import me.braydon.mc.model.skin.Skin;
|
||||||
|
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A basic 2D renderer for a {@link Skin.Part}.
|
* A basic 2D renderer for a {@link ISkinPart.Basic#BODY}.
|
||||||
*
|
*
|
||||||
* @author Braydon
|
* @author Braydon
|
||||||
*/
|
*/
|
||||||
public final class BasicSkinPartRenderer extends SkinPartRenderer<Skin.Part> {
|
public final class BodySkinPartRenderer extends SkinRenderer<ISkinPart.Basic> {
|
||||||
public static final BasicSkinPartRenderer INSTANCE = new BasicSkinPartRenderer();
|
public static final BodySkinPartRenderer INSTANCE = new BodySkinPartRenderer();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoke this render to render the
|
* Invoke this render to render the
|
||||||
@ -48,7 +49,7 @@ public final class BasicSkinPartRenderer extends SkinPartRenderer<Skin.Part> {
|
|||||||
* @return the rendered skin part
|
* @return the rendered skin part
|
||||||
*/
|
*/
|
||||||
@Override @NonNull
|
@Override @NonNull
|
||||||
public BufferedImage render(@NonNull Skin skin, @NonNull Skin.Part part, boolean overlays, int size) {
|
public BufferedImage render(@NonNull Skin skin, @NonNull ISkinPart.Basic part, boolean overlays, int size) {
|
||||||
return getSkinPart(skin, part, size / 8D);
|
return getVanillaSkinPart(skin, ISkinPart.Vanilla.FACE, size);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -24,19 +24,20 @@
|
|||||||
package me.braydon.mc.common.renderer.impl;
|
package me.braydon.mc.common.renderer.impl;
|
||||||
|
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import me.braydon.mc.common.renderer.SkinPartRenderer;
|
import me.braydon.mc.common.renderer.SkinRenderer;
|
||||||
import me.braydon.mc.model.Skin;
|
import me.braydon.mc.model.skin.ISkinPart;
|
||||||
|
import me.braydon.mc.model.skin.Skin;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.geom.AffineTransform;
|
import java.awt.geom.AffineTransform;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A isometric 3D renderer for a {@link Skin.Part}.
|
* A isometric 3D renderer for a {@link ISkinPart.Isometric}.
|
||||||
*
|
*
|
||||||
* @author Braydon
|
* @author Braydon
|
||||||
*/
|
*/
|
||||||
public final class IsometricSkinPartRenderer extends SkinPartRenderer<Skin.IsometricPart> {
|
public final class IsometricSkinPartRenderer extends SkinRenderer<ISkinPart.Isometric> {
|
||||||
public static final IsometricSkinPartRenderer INSTANCE = new IsometricSkinPartRenderer();
|
public static final IsometricSkinPartRenderer INSTANCE = new IsometricSkinPartRenderer();
|
||||||
|
|
||||||
private static final double SKEW_A = 26D / 45D; // 0.57777777
|
private static final double SKEW_A = 26D / 45D; // 0.57777777
|
||||||
@ -57,7 +58,7 @@ public final class IsometricSkinPartRenderer extends SkinPartRenderer<Skin.Isome
|
|||||||
* @return the rendered skin part
|
* @return the rendered skin part
|
||||||
*/
|
*/
|
||||||
@Override @NonNull
|
@Override @NonNull
|
||||||
public BufferedImage render(@NonNull Skin skin, @NonNull Skin.IsometricPart part, boolean overlays, int size) {
|
public BufferedImage render(@NonNull Skin skin, @NonNull ISkinPart.Isometric part, boolean overlays, int size) {
|
||||||
double scale = (size / 8D) / 2.5;
|
double scale = (size / 8D) / 2.5;
|
||||||
double zOffset = scale * 3.5D;
|
double zOffset = scale * 3.5D;
|
||||||
double xOffset = scale * 2D;
|
double xOffset = scale * 2D;
|
||||||
@ -65,9 +66,9 @@ public final class IsometricSkinPartRenderer extends SkinPartRenderer<Skin.Isome
|
|||||||
BufferedImage texture = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
|
BufferedImage texture = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
|
||||||
Graphics2D graphics = texture.createGraphics();
|
Graphics2D graphics = texture.createGraphics();
|
||||||
|
|
||||||
BufferedImage headTop = getSkinPart(skin, Skin.Part.HEAD_TOP, scale);
|
BufferedImage headTop = getVanillaSkinPart(skin, ISkinPart.Vanilla.HEAD_TOP, scale);
|
||||||
BufferedImage face = getSkinPart(skin, Skin.Part.FACE, scale);
|
BufferedImage face = getVanillaSkinPart(skin, ISkinPart.Vanilla.FACE, scale);
|
||||||
BufferedImage headLeft = getSkinPart(skin, Skin.Part.HEAD_LEFT, scale);
|
BufferedImage headLeft = getVanillaSkinPart(skin, ISkinPart.Vanilla.HEAD_LEFT, scale);
|
||||||
|
|
||||||
// Draw the top of the left
|
// Draw the top of the left
|
||||||
drawPart(graphics, headTop, HEAD_TOP_TRANSFORM, -0.5 - zOffset, xOffset + zOffset, headTop.getWidth(), headTop.getHeight() + 2);
|
drawPart(graphics, headTop, HEAD_TOP_TRANSFORM, -0.5 - zOffset, xOffset + zOffset, headTop.getWidth(), headTop.getHeight() + 2);
|
||||||
|
@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2024 Braydon (Rainnny).
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
package me.braydon.mc.common.renderer.impl;
|
||||||
|
|
||||||
|
import lombok.NonNull;
|
||||||
|
import me.braydon.mc.common.renderer.SkinRenderer;
|
||||||
|
import me.braydon.mc.model.skin.ISkinPart;
|
||||||
|
import me.braydon.mc.model.skin.Skin;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A basic 2D renderer for a {@link ISkinPart.Vanilla}.
|
||||||
|
*
|
||||||
|
* @author Braydon
|
||||||
|
*/
|
||||||
|
public final class VanillaSkinPartRenderer extends SkinRenderer<ISkinPart.Vanilla> {
|
||||||
|
public static final VanillaSkinPartRenderer INSTANCE = new VanillaSkinPartRenderer();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoke this render to render the
|
||||||
|
* given skin part for the provided skin.
|
||||||
|
*
|
||||||
|
* @param skin the skin to render the part for
|
||||||
|
* @param part the part to render
|
||||||
|
* @param overlays whether to render overlays
|
||||||
|
* @param size the size to scale the skin part to
|
||||||
|
* @return the rendered skin part
|
||||||
|
*/
|
||||||
|
@Override @NonNull
|
||||||
|
public BufferedImage render(@NonNull Skin skin, @NonNull ISkinPart.Vanilla part, boolean overlays, int size) {
|
||||||
|
double scale = size / 8D;
|
||||||
|
BufferedImage partImage = getVanillaSkinPart(skin, part, scale); // Get the part image
|
||||||
|
if (!overlays) { // Not rendering overlays
|
||||||
|
return partImage;
|
||||||
|
}
|
||||||
|
// Create a new image, draw our skin part texture, and then apply overlays
|
||||||
|
BufferedImage texture = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
|
||||||
|
Graphics2D graphics = texture.createGraphics();
|
||||||
|
graphics.drawImage(partImage, 0, 0, null);
|
||||||
|
|
||||||
|
// Draw part overlays
|
||||||
|
ISkinPart.Vanilla[] overlayParts = part.getOverlays();
|
||||||
|
if (overlayParts != null) {
|
||||||
|
for (ISkinPart.Vanilla overlay : overlayParts) {
|
||||||
|
applyOverlay(graphics, getVanillaSkinPart(skin, overlay, scale));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
}
|
@ -98,10 +98,11 @@ public final class PlayerController {
|
|||||||
@Parameter(description = "The skin part to get the texture of", example = "head") @PathVariable @NonNull String partName,
|
@Parameter(description = "The skin part to get the texture of", example = "head") @PathVariable @NonNull String partName,
|
||||||
@Parameter(description = "The player username or UUID to get", example = "Rainnny") @PathVariable @NonNull String query,
|
@Parameter(description = "The player username or UUID to get", example = "Rainnny") @PathVariable @NonNull String query,
|
||||||
@Parameter(description = "The image extension", example = "png") @PathVariable @NonNull String extension,
|
@Parameter(description = "The image extension", example = "png") @PathVariable @NonNull String extension,
|
||||||
|
@Parameter(description = "Whether to render skin overlays") @RequestParam(required = false, defaultValue = "true") boolean overlays,
|
||||||
@Parameter(description = "The size to scale the skin part texture to", example = "256") @RequestParam(required = false) String size
|
@Parameter(description = "The size to scale the skin part texture to", example = "256") @RequestParam(required = false) String size
|
||||||
) throws BadRequestException {
|
) throws BadRequestException {
|
||||||
return ResponseEntity.ok()
|
return ResponseEntity.ok()
|
||||||
.contentType(extension.equalsIgnoreCase("png") ? MediaType.IMAGE_PNG : MediaType.IMAGE_JPEG)
|
.contentType(extension.equalsIgnoreCase("png") ? MediaType.IMAGE_PNG : MediaType.IMAGE_JPEG)
|
||||||
.body(mojangService.getSkinPartTexture(partName, query, extension, size));
|
.body(mojangService.getSkinPartTexture(partName, query, extension, overlays, size));
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -24,6 +24,7 @@
|
|||||||
package me.braydon.mc.model;
|
package me.braydon.mc.model;
|
||||||
|
|
||||||
import lombok.*;
|
import lombok.*;
|
||||||
|
import me.braydon.mc.model.skin.Skin;
|
||||||
import me.braydon.mc.model.token.MojangProfileToken;
|
import me.braydon.mc.model.token.MojangProfileToken;
|
||||||
import org.springframework.data.annotation.Id;
|
import org.springframework.data.annotation.Id;
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ import lombok.ToString;
|
|||||||
import me.braydon.mc.model.Cape;
|
import me.braydon.mc.model.Cape;
|
||||||
import me.braydon.mc.model.Player;
|
import me.braydon.mc.model.Player;
|
||||||
import me.braydon.mc.model.ProfileAction;
|
import me.braydon.mc.model.ProfileAction;
|
||||||
import me.braydon.mc.model.Skin;
|
import me.braydon.mc.model.skin.Skin;
|
||||||
import me.braydon.mc.model.token.MojangProfileToken;
|
import me.braydon.mc.model.token.MojangProfileToken;
|
||||||
import org.springframework.data.redis.core.RedisHash;
|
import org.springframework.data.redis.core.RedisHash;
|
||||||
|
|
||||||
|
220
src/main/java/me/braydon/mc/model/skin/ISkinPart.java
Normal file
220
src/main/java/me/braydon/mc/model/skin/ISkinPart.java
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2024 Braydon (Rainnny).
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
package me.braydon.mc.model.skin;
|
||||||
|
|
||||||
|
import lombok.*;
|
||||||
|
import me.braydon.mc.common.renderer.SkinRenderer;
|
||||||
|
import me.braydon.mc.common.renderer.impl.BodySkinPartRenderer;
|
||||||
|
import me.braydon.mc.common.renderer.impl.IsometricSkinPartRenderer;
|
||||||
|
import me.braydon.mc.common.renderer.impl.VanillaSkinPartRenderer;
|
||||||
|
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A part of a {@link Skin}.
|
||||||
|
*
|
||||||
|
* @author Braydon
|
||||||
|
*/
|
||||||
|
public interface ISkinPart {
|
||||||
|
Enum<?>[][] TYPES = { Vanilla.values(), Basic.values(), Isometric.values() };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the name of this part.
|
||||||
|
*
|
||||||
|
* @return the part name
|
||||||
|
*/
|
||||||
|
@NonNull String name();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a part of a skin.
|
||||||
|
*
|
||||||
|
* @param skin the skin to render the part for
|
||||||
|
* @param overlays whether to render overlays
|
||||||
|
* @param size the size to scale the skin part to
|
||||||
|
* @return the rendered skin part
|
||||||
|
*/
|
||||||
|
@NonNull BufferedImage render(@NonNull Skin skin, boolean overlays, int size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a skin part by the given name.
|
||||||
|
*
|
||||||
|
* @param name the name of the part
|
||||||
|
* @return the part, null if none
|
||||||
|
*/
|
||||||
|
static ISkinPart getByName(@NonNull String name) {
|
||||||
|
name = name.toUpperCase();
|
||||||
|
for (Enum<?>[] type : TYPES) {
|
||||||
|
for (Enum<?> part : type) {
|
||||||
|
if (!part.name().equals(name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return (ISkinPart) part;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A part of a Vanilla skin texture.
|
||||||
|
*/
|
||||||
|
@AllArgsConstructor @RequiredArgsConstructor @Getter @ToString
|
||||||
|
enum Vanilla implements ISkinPart {
|
||||||
|
// Overlays
|
||||||
|
HEAD_OVERLAY_FACE(new Coordinates(40, 8), 8, 8),
|
||||||
|
|
||||||
|
// Head
|
||||||
|
HEAD_TOP(new Coordinates(8, 0), 8, 8),
|
||||||
|
FACE(new Coordinates(8, 8), 8, 8, HEAD_OVERLAY_FACE),
|
||||||
|
HEAD_LEFT(new Coordinates(0, 8), 8, 8),
|
||||||
|
HEAD_RIGHT(new Coordinates(16, 8), 8, 8),
|
||||||
|
HEAD_BOTTOM(new Coordinates(16, 0), 8, 8),
|
||||||
|
HEAD_BACK(new Coordinates(24, 8), 8, 8);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The coordinates of this part.
|
||||||
|
*/
|
||||||
|
@NonNull private final Coordinates coordinates;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The legacy coordinates of this part.
|
||||||
|
* <p>
|
||||||
|
* This is for older skin textures
|
||||||
|
* that use different positions.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
private LegacyCoordinates legacyCoordinates;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The size of this part.
|
||||||
|
*/
|
||||||
|
private final int width, height;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The overlay parts this part has.
|
||||||
|
*/
|
||||||
|
private Vanilla[] overlays;
|
||||||
|
|
||||||
|
Vanilla(@NonNull Coordinates coordinates, int width, int height, Vanilla... overlays) {
|
||||||
|
this(coordinates, null, width, height, overlays);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a part of a skin.
|
||||||
|
*
|
||||||
|
* @param skin the skin to render the part for
|
||||||
|
* @param overlays whether to render overlays
|
||||||
|
* @param size the size to scale the skin part to
|
||||||
|
* @return the rendered skin part
|
||||||
|
*/
|
||||||
|
@Override @NonNull
|
||||||
|
public BufferedImage render(@NonNull Skin skin, boolean overlays, int size) {
|
||||||
|
return VanillaSkinPartRenderer.INSTANCE.render(skin, this, overlays, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Coordinates of a part of a skin.
|
||||||
|
*/
|
||||||
|
@AllArgsConstructor @Getter @ToString
|
||||||
|
public static class Coordinates {
|
||||||
|
/**
|
||||||
|
* The X coordinate.
|
||||||
|
*/
|
||||||
|
private final int x;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Y coordinate.
|
||||||
|
*/
|
||||||
|
private final int y;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Legacy coordinates of a part of a skin.
|
||||||
|
*/
|
||||||
|
@Getter @ToString
|
||||||
|
public static class LegacyCoordinates extends Coordinates {
|
||||||
|
/**
|
||||||
|
* Whether the part at these coordinates is flipped.
|
||||||
|
*/
|
||||||
|
private final boolean flipped;
|
||||||
|
|
||||||
|
public LegacyCoordinates(int x, int y) {
|
||||||
|
this(x, y, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LegacyCoordinates(int x, int y, boolean flipped) {
|
||||||
|
super(x, y);
|
||||||
|
this.flipped = flipped;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A basic part of a skin.
|
||||||
|
* <p>
|
||||||
|
* These parts have custom renderers!
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
@AllArgsConstructor @Getter
|
||||||
|
enum Basic implements ISkinPart {
|
||||||
|
BODY(BodySkinPartRenderer.INSTANCE);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The custom renderer to use for this part.
|
||||||
|
*/
|
||||||
|
@NonNull private final SkinRenderer<Basic> renderer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a part of a skin.
|
||||||
|
*
|
||||||
|
* @param skin the skin to render the part for
|
||||||
|
* @param overlays whether to render overlays
|
||||||
|
* @param size the size to scale the skin part to
|
||||||
|
* @return the rendered skin part
|
||||||
|
*/
|
||||||
|
@Override @NonNull
|
||||||
|
public BufferedImage render(@NonNull Skin skin, boolean overlays, int size) {
|
||||||
|
return renderer.render(skin, this, overlays, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A isometric part of a skin.
|
||||||
|
*/
|
||||||
|
enum Isometric implements ISkinPart {
|
||||||
|
HEAD;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a part of a skin.
|
||||||
|
*
|
||||||
|
* @param skin the skin to render the part for
|
||||||
|
* @param overlays whether to render overlays
|
||||||
|
* @param size the size to scale the skin part to
|
||||||
|
* @return the rendered skin part
|
||||||
|
*/
|
||||||
|
@Override @NonNull
|
||||||
|
public BufferedImage render(@NonNull Skin skin, boolean overlays, int size) {
|
||||||
|
return IsometricSkinPartRenderer.INSTANCE.render(skin, this, overlays, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -21,12 +21,13 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package me.braydon.mc.model;
|
package me.braydon.mc.model.skin;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import lombok.*;
|
import lombok.*;
|
||||||
import me.braydon.mc.config.AppConfig;
|
import me.braydon.mc.config.AppConfig;
|
||||||
|
import me.braydon.mc.model.Player;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -64,13 +65,13 @@ public final class Skin {
|
|||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public Skin populatePartUrls(@NonNull String playerUuid) {
|
public Skin populatePartUrls(@NonNull String playerUuid) {
|
||||||
Consumer<IPart> addPart = part -> {
|
Consumer<ISkinPart> addPart = part -> {
|
||||||
partUrls.put(part.name(), AppConfig.INSTANCE.getServerPublicUrl() + "/player/" + part.name().toLowerCase() + "/" + playerUuid + ".png");
|
partUrls.put(part.name(), AppConfig.INSTANCE.getServerPublicUrl() + "/player/" + part.name().toLowerCase() + "/" + playerUuid + ".png");
|
||||||
};
|
};
|
||||||
for (Part part : Part.values()) {
|
for (ISkinPart part : ISkinPart.Vanilla.values()) {
|
||||||
addPart.accept(part);
|
addPart.accept(part);
|
||||||
}
|
}
|
||||||
for (IsometricPart part : IsometricPart.values()) {
|
for (ISkinPart part : ISkinPart.Isometric.values()) {
|
||||||
addPart.accept(part);
|
addPart.accept(part);
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
@ -101,89 +102,4 @@ public final class Skin {
|
|||||||
public enum Model {
|
public enum Model {
|
||||||
DEFAULT, SLIM
|
DEFAULT, SLIM
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a part of a skin.
|
|
||||||
*/
|
|
||||||
public interface IPart {
|
|
||||||
/**
|
|
||||||
* Get the name of this part.
|
|
||||||
*
|
|
||||||
* @return the part name
|
|
||||||
*/
|
|
||||||
@NonNull String name();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The part of a skin.
|
|
||||||
*/
|
|
||||||
@AllArgsConstructor @RequiredArgsConstructor @Getter @ToString
|
|
||||||
public enum Part implements IPart {
|
|
||||||
// Head
|
|
||||||
HEAD_TOP(new Coordinates(8, 0), 8, 8),
|
|
||||||
FACE(new Coordinates(8, 8), 8, 8),
|
|
||||||
HEAD_LEFT(new Coordinates(0, 8), 8, 8),
|
|
||||||
HEAD_RIGHT(new Coordinates(16, 8), 8, 8),
|
|
||||||
HEAD_BOTTOM(new Coordinates(16, 0), 8, 8),
|
|
||||||
HEAD_BACK(new Coordinates(24, 8), 8, 8);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The coordinates of this part.
|
|
||||||
*/
|
|
||||||
@NonNull private final Coordinates coordinates;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The legacy coordinates of this part.
|
|
||||||
* <p>
|
|
||||||
* This is for older skin textures
|
|
||||||
* that use different positions.
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
private LegacyCoordinates legacyCoordinates;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The size of this part.
|
|
||||||
*/
|
|
||||||
private final int width, height;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Coordinates of a part of a skin.
|
|
||||||
*/
|
|
||||||
@AllArgsConstructor @Getter @ToString
|
|
||||||
public static class Coordinates {
|
|
||||||
/**
|
|
||||||
* The X coordinate.
|
|
||||||
*/
|
|
||||||
private final int x;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Y coordinate.
|
|
||||||
*/
|
|
||||||
private final int y;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Legacy coordinates of a part of a skin.
|
|
||||||
*/
|
|
||||||
@Getter @ToString
|
|
||||||
public static class LegacyCoordinates extends Coordinates {
|
|
||||||
/**
|
|
||||||
* Whether the part at these coordinates is flipped.
|
|
||||||
*/
|
|
||||||
private final boolean flipped;
|
|
||||||
|
|
||||||
public LegacyCoordinates(int x, int y) {
|
|
||||||
this(x, y, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LegacyCoordinates(int x, int y, boolean flipped) {
|
|
||||||
super(x, y);
|
|
||||||
this.flipped = flipped;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum IsometricPart implements IPart {
|
|
||||||
HEAD
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -29,7 +29,7 @@ import lombok.*;
|
|||||||
import me.braydon.mc.config.AppConfig;
|
import me.braydon.mc.config.AppConfig;
|
||||||
import me.braydon.mc.model.Cape;
|
import me.braydon.mc.model.Cape;
|
||||||
import me.braydon.mc.model.ProfileAction;
|
import me.braydon.mc.model.ProfileAction;
|
||||||
import me.braydon.mc.model.Skin;
|
import me.braydon.mc.model.skin.Skin;
|
||||||
|
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
|
|
||||||
|
@ -23,14 +23,14 @@
|
|||||||
*/
|
*/
|
||||||
package me.braydon.mc.repository;
|
package me.braydon.mc.repository;
|
||||||
|
|
||||||
import me.braydon.mc.model.Skin.Part;
|
|
||||||
import me.braydon.mc.model.cache.CachedSkinPartTexture;
|
import me.braydon.mc.model.cache.CachedSkinPartTexture;
|
||||||
|
import me.braydon.mc.model.skin.ISkinPart;
|
||||||
import org.springframework.data.repository.CrudRepository;
|
import org.springframework.data.repository.CrudRepository;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A cache repository for skin texture parts.
|
* A cache repository for skin texture parts.
|
||||||
*
|
*
|
||||||
* @author Braydon
|
* @author Braydon
|
||||||
* @see Part for skin parts
|
* @see ISkinPart for skin parts
|
||||||
*/
|
*/
|
||||||
public interface SkinPartTextureCacheRepository extends CrudRepository<CachedSkinPartTexture, String> { }
|
public interface SkinPartTextureCacheRepository extends CrudRepository<CachedSkinPartTexture, String> { }
|
@ -32,8 +32,6 @@ import lombok.NonNull;
|
|||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import lombok.extern.log4j.Log4j2;
|
import lombok.extern.log4j.Log4j2;
|
||||||
import me.braydon.mc.common.*;
|
import me.braydon.mc.common.*;
|
||||||
import me.braydon.mc.common.renderer.impl.BasicSkinPartRenderer;
|
|
||||||
import me.braydon.mc.common.renderer.impl.IsometricSkinPartRenderer;
|
|
||||||
import me.braydon.mc.common.web.JsonWebException;
|
import me.braydon.mc.common.web.JsonWebException;
|
||||||
import me.braydon.mc.common.web.JsonWebRequest;
|
import me.braydon.mc.common.web.JsonWebRequest;
|
||||||
import me.braydon.mc.exception.impl.BadRequestException;
|
import me.braydon.mc.exception.impl.BadRequestException;
|
||||||
@ -42,7 +40,6 @@ import me.braydon.mc.exception.impl.ResourceNotFoundException;
|
|||||||
import me.braydon.mc.model.MinecraftServer;
|
import me.braydon.mc.model.MinecraftServer;
|
||||||
import me.braydon.mc.model.Player;
|
import me.braydon.mc.model.Player;
|
||||||
import me.braydon.mc.model.ProfileAction;
|
import me.braydon.mc.model.ProfileAction;
|
||||||
import me.braydon.mc.model.Skin;
|
|
||||||
import me.braydon.mc.model.cache.CachedMinecraftServer;
|
import me.braydon.mc.model.cache.CachedMinecraftServer;
|
||||||
import me.braydon.mc.model.cache.CachedPlayer;
|
import me.braydon.mc.model.cache.CachedPlayer;
|
||||||
import me.braydon.mc.model.cache.CachedPlayerName;
|
import me.braydon.mc.model.cache.CachedPlayerName;
|
||||||
@ -51,6 +48,8 @@ import me.braydon.mc.model.dns.DNSRecord;
|
|||||||
import me.braydon.mc.model.dns.impl.ARecord;
|
import me.braydon.mc.model.dns.impl.ARecord;
|
||||||
import me.braydon.mc.model.dns.impl.SRVRecord;
|
import me.braydon.mc.model.dns.impl.SRVRecord;
|
||||||
import me.braydon.mc.model.server.JavaMinecraftServer;
|
import me.braydon.mc.model.server.JavaMinecraftServer;
|
||||||
|
import me.braydon.mc.model.skin.ISkinPart;
|
||||||
|
import me.braydon.mc.model.skin.Skin;
|
||||||
import me.braydon.mc.model.token.MojangProfileToken;
|
import me.braydon.mc.model.token.MojangProfileToken;
|
||||||
import me.braydon.mc.model.token.MojangUsernameToUUIDToken;
|
import me.braydon.mc.model.token.MojangUsernameToUUIDToken;
|
||||||
import me.braydon.mc.repository.MinecraftServerCacheRepository;
|
import me.braydon.mc.repository.MinecraftServerCacheRepository;
|
||||||
@ -141,9 +140,6 @@ public final class MojangService {
|
|||||||
this.minecraftServerCache = minecraftServerCache;
|
this.minecraftServerCache = minecraftServerCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Autowired
|
|
||||||
|
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void onInitialize() {
|
public void onInitialize() {
|
||||||
// Schedule a task to fetch blocked
|
// Schedule a task to fetch blocked
|
||||||
@ -163,23 +159,19 @@ public final class MojangService {
|
|||||||
* @param partName the part of the player's skin texture to get
|
* @param partName the part of the player's skin texture to get
|
||||||
* @param query the query to search for the player by
|
* @param query the query to search for the player by
|
||||||
* @param extension the skin part image extension
|
* @param extension the skin part image extension
|
||||||
|
* @param overlays whether to render overlays
|
||||||
* @param sizeString the size of the skin part image
|
* @param sizeString the size of the skin part image
|
||||||
* @return the skin part texture
|
* @return the skin part texture
|
||||||
* @throws BadRequestException if the extension is invalid
|
* @throws BadRequestException if the extension is invalid
|
||||||
* @throws MojangRateLimitException if the Mojang API rate limit is reached
|
* @throws MojangRateLimitException if the Mojang API rate limit is reached
|
||||||
*/
|
*/
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
public byte[] getSkinPartTexture(@NonNull String partName, @NonNull String query, @NonNull String extension, String sizeString)
|
public byte[] getSkinPartTexture(@NonNull String partName, @NonNull String query, @NonNull String extension,
|
||||||
throws BadRequestException, MojangRateLimitException {
|
boolean overlays, String sizeString) throws BadRequestException, MojangRateLimitException {
|
||||||
partName = partName.toUpperCase(); // The part name to get
|
|
||||||
|
|
||||||
// Get the part from the given name
|
// Get the part from the given name
|
||||||
Skin.IPart part = EnumUtils.getEnumConstant(Skin.Part.class, partName); // The skin part to get
|
ISkinPart part = ISkinPart.getByName(partName); // The skin part to get
|
||||||
if (part == null) { // The given part is invalid, try a isometric part
|
|
||||||
part = EnumUtils.getEnumConstant(Skin.IsometricPart.class, partName);;
|
|
||||||
}
|
|
||||||
if (part == null) { // Default to the face
|
if (part == null) { // Default to the face
|
||||||
part = Skin.Part.FACE;
|
part = ISkinPart.Vanilla.FACE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the extension is valid
|
// Ensure the extension is valid
|
||||||
@ -217,9 +209,7 @@ public final class MojangService {
|
|||||||
if (target == null) { // Fallback to the default skin
|
if (target == null) { // Fallback to the default skin
|
||||||
target = Skin.DEFAULT_STEVE;
|
target = Skin.DEFAULT_STEVE;
|
||||||
}
|
}
|
||||||
BufferedImage texture = part instanceof Skin.IsometricPart isometricPart ?
|
BufferedImage texture = part.render(target, overlays, size); // Render the skin part
|
||||||
IsometricSkinPartRenderer.INSTANCE.render(target, isometricPart, true, size)
|
|
||||||
: BasicSkinPartRenderer.INSTANCE.render(target, (Skin.Part) part, true, size); // Render the skin part
|
|
||||||
|
|
||||||
// Convert BufferedImage to byte array
|
// Convert BufferedImage to byte array
|
||||||
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
|
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user