Compare commits
No commits in common. "d8962cee98c7a75e0ceace8bba906ee094df0ca1" and "dbeeb77fc8dfc1ce3b8f7eac9d8e5c7b35a16898" have entirely different histories.
d8962cee98
...
dbeeb77fc8
@ -15,8 +15,5 @@ ENV HOSTNAME "0.0.0.0"
|
|||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
ENV PORT 80
|
ENV PORT 80
|
||||||
|
|
||||||
# We're running in production
|
|
||||||
ENV APP_ENV "production"
|
|
||||||
|
|
||||||
# Start the app
|
# Start the app
|
||||||
CMD ["java", "-jar", "target/RESTfulMC.jar"]
|
CMD ["java", "-jar", "target/RESTfulMC.jar"]
|
@ -1,42 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.experimental.UtilityClass;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Braydon
|
|
||||||
*/
|
|
||||||
@UtilityClass
|
|
||||||
public final class EnvironmentUtils {
|
|
||||||
/**
|
|
||||||
* Is the app running in a production environment?
|
|
||||||
*/
|
|
||||||
@Getter private static final boolean production;
|
|
||||||
static { // Are we running on production?
|
|
||||||
String env = System.getenv("APP_ENV");
|
|
||||||
production = env != null && (env.equals("production"));
|
|
||||||
}
|
|
||||||
}
|
|
@ -39,7 +39,7 @@ public final class ImageUtils {
|
|||||||
* Scale the given image to the provided size.
|
* Scale the given image to the provided size.
|
||||||
*
|
*
|
||||||
* @param image the image to scale
|
* @param image the image to scale
|
||||||
* @param size the size to scale the image to
|
* @param size the size to scale the image to
|
||||||
* @return the scaled image
|
* @return the scaled image
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
@ -50,19 +50,4 @@ public final class ImageUtils {
|
|||||||
graphics.dispose();
|
graphics.dispose();
|
||||||
return scaled;
|
return scaled;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Flip the given image.
|
|
||||||
*
|
|
||||||
* @param image the image to flip
|
|
||||||
* @return the flipped image
|
|
||||||
*/
|
|
||||||
@NonNull
|
|
||||||
public static BufferedImage flip(@NonNull BufferedImage image) {
|
|
||||||
BufferedImage flipped = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB);
|
|
||||||
Graphics2D graphics = flipped.createGraphics();
|
|
||||||
graphics.drawImage(image, image.getWidth(), 0, 0, image.getHeight(), 0, 0, image.getWidth(), image.getHeight(), null);
|
|
||||||
graphics.dispose();
|
|
||||||
return flipped;
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -37,18 +37,18 @@ import java.net.URL;
|
|||||||
/**
|
/**
|
||||||
* A renderer for a {@link ISkinPart}.
|
* A renderer for a {@link ISkinPart}.
|
||||||
*
|
*
|
||||||
* @param <T> the type of part to render
|
|
||||||
* @author Braydon
|
* @author Braydon
|
||||||
|
* @param <T> the type of part to render
|
||||||
*/
|
*/
|
||||||
public abstract class SkinRenderer<T extends ISkinPart> {
|
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.
|
||||||
*
|
*
|
||||||
* @param skin the skin to render the part for
|
* @param skin the skin to render the part for
|
||||||
* @param part the part to render
|
* @param part the part to render
|
||||||
* @param overlays whether to render overlays
|
* @param overlays whether to render overlays
|
||||||
* @param size the size to scale the skin part to
|
* @param size the size to scale the skin part to
|
||||||
* @return the rendered skin part
|
* @return the rendered skin part
|
||||||
*/
|
*/
|
||||||
@NonNull public abstract BufferedImage render(@NonNull Skin skin, @NonNull T part, boolean overlays, int size);
|
@NonNull public abstract BufferedImage render(@NonNull Skin skin, @NonNull T part, boolean overlays, int size);
|
||||||
@ -63,37 +63,24 @@ public abstract class SkinRenderer<T extends ISkinPart> {
|
|||||||
*/
|
*/
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
protected final BufferedImage getVanillaSkinPart(@NonNull Skin skin, @NonNull ISkinPart.Vanilla part, double size) {
|
protected final BufferedImage getVanillaSkinPart(@NonNull Skin skin, @NonNull ISkinPart.Vanilla part, double size) {
|
||||||
BufferedImage skinImage = ImageIO.read(new URL(skin.getUrl())); // The skin texture
|
return getSkinPartTexture(skin, part.getCoordinates().getX(), part.getCoordinates().getY(), part.getWidth(), part.getHeight(), size);
|
||||||
ISkinPart.Vanilla.Coordinates coordinates = part.getCoordinates(); // The coordinates of the part
|
|
||||||
|
|
||||||
// The skin texture is legacy, use legacy coordinates
|
|
||||||
if (skinImage.getHeight() == 32 && part.hasLegacyCoordinates()) {
|
|
||||||
coordinates = part.getLegacyCoordinates();
|
|
||||||
}
|
|
||||||
int width = part.getWidth(); // The width of the part
|
|
||||||
if (skin.getModel() == Skin.Model.SLIM && part.isArm()) {
|
|
||||||
width--;
|
|
||||||
}
|
|
||||||
BufferedImage partTexture = getSkinPartTexture(skinImage, coordinates.getX(), coordinates.getY(), width, part.getHeight(), size);
|
|
||||||
if (coordinates instanceof ISkinPart.Vanilla.LegacyCoordinates legacyCoordinates && legacyCoordinates.isFlipped()) {
|
|
||||||
partTexture = ImageUtils.flip(partTexture);
|
|
||||||
}
|
|
||||||
return partTexture;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the texture of a specific part of a skin.
|
* Get the texture of a specific part of a skin.
|
||||||
*
|
*
|
||||||
* @param skinImage the skin image to get the part from
|
* @param skin the skin to get the part from
|
||||||
* @param x the x position of the part
|
* @param x the x position of the part
|
||||||
* @param y the y position of the part
|
* @param y the y position of the part
|
||||||
* @param width the width of the part
|
* @param width the width of the part
|
||||||
* @param height the height of the part
|
* @param height the height of the part
|
||||||
* @param size the size to scale the part to
|
* @param size the size to scale the part to
|
||||||
* @return the texture of the skin part
|
* @return the texture of the skin part
|
||||||
*/
|
*/
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
private BufferedImage getSkinPartTexture(@NonNull BufferedImage skinImage, int x, int y, int width, int height, double size) {
|
private BufferedImage getSkinPartTexture(@NonNull Skin skin, int x, int y, int width, int height, double size) {
|
||||||
|
BufferedImage skinImage = ImageIO.read(new URL(skin.getUrl())); // The skin texture
|
||||||
|
|
||||||
// Create a new BufferedImage for the part of the skin texture
|
// Create a new BufferedImage for the part of the skin texture
|
||||||
BufferedImage headTexture = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
|
BufferedImage headTexture = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
|
||||||
|
|
||||||
@ -101,9 +88,8 @@ public abstract class SkinRenderer<T extends ISkinPart> {
|
|||||||
headTexture.getGraphics().drawImage(skinImage, 0, 0, width, height, x, y, x + width, y + height, null);
|
headTexture.getGraphics().drawImage(skinImage, 0, 0, width, height, x, y, x + width, y + height, null);
|
||||||
|
|
||||||
// Scale the skin part texture
|
// Scale the skin part texture
|
||||||
if (size > 0D) {
|
headTexture = ImageUtils.resize(headTexture, size);
|
||||||
headTexture = ImageUtils.resize(headTexture, size);
|
|
||||||
}
|
|
||||||
return headTexture;
|
return headTexture;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,7 +105,7 @@ public abstract class SkinRenderer<T extends ISkinPart> {
|
|||||||
/**
|
/**
|
||||||
* Apply an overlay to a texture.
|
* Apply an overlay to a texture.
|
||||||
*
|
*
|
||||||
* @param graphics the graphics to overlay on
|
* @param graphics the graphics to overlay on
|
||||||
* @param overlayImage the part to overlay
|
* @param overlayImage the part to overlay
|
||||||
*/
|
*/
|
||||||
protected final void applyOverlay(@NonNull Graphics2D graphics, @NonNull BufferedImage overlayImage) {
|
protected final void applyOverlay(@NonNull Graphics2D graphics, @NonNull BufferedImage overlayImage) {
|
||||||
|
@ -24,20 +24,18 @@
|
|||||||
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.ImageUtils;
|
|
||||||
import me.braydon.mc.common.renderer.SkinRenderer;
|
import me.braydon.mc.common.renderer.SkinRenderer;
|
||||||
import me.braydon.mc.model.skin.ISkinPart;
|
import me.braydon.mc.model.skin.ISkinPart;
|
||||||
import me.braydon.mc.model.skin.Skin;
|
import me.braydon.mc.model.skin.Skin;
|
||||||
|
|
||||||
import java.awt.*;
|
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A basic 2D renderer for a {@link ISkinPart.Custom#BODY}.
|
* A basic 2D renderer for a {@link ISkinPart.Basic#BODY}.
|
||||||
*
|
*
|
||||||
* @author Braydon
|
* @author Braydon
|
||||||
*/
|
*/
|
||||||
public final class BodySkinPartRenderer extends SkinRenderer<ISkinPart.Custom> {
|
public final class BodySkinPartRenderer extends SkinRenderer<ISkinPart.Basic> {
|
||||||
public static final BodySkinPartRenderer INSTANCE = new BodySkinPartRenderer();
|
public static final BodySkinPartRenderer INSTANCE = new BodySkinPartRenderer();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -51,27 +49,7 @@ public final class BodySkinPartRenderer extends SkinRenderer<ISkinPart.Custom> {
|
|||||||
* @return the rendered skin part
|
* @return the rendered skin part
|
||||||
*/
|
*/
|
||||||
@Override @NonNull
|
@Override @NonNull
|
||||||
public BufferedImage render(@NonNull Skin skin, @NonNull ISkinPart.Custom part, boolean overlays, int size) {
|
public BufferedImage render(@NonNull Skin skin, @NonNull ISkinPart.Basic part, boolean overlays, int size) {
|
||||||
BufferedImage texture = new BufferedImage(16, 32, BufferedImage.TYPE_INT_ARGB); // The texture to return
|
return getVanillaSkinPart(skin, ISkinPart.Vanilla.FACE, size);
|
||||||
Graphics2D graphics = texture.createGraphics(); // Create the graphics for drawing
|
|
||||||
|
|
||||||
// Get the Vanilla skin parts to draw
|
|
||||||
BufferedImage face = getVanillaSkinPart(skin, ISkinPart.Vanilla.FACE, -1);
|
|
||||||
BufferedImage body = getVanillaSkinPart(skin, ISkinPart.Vanilla.BODY_FRONT, -1);
|
|
||||||
BufferedImage leftArm = getVanillaSkinPart(skin, ISkinPart.Vanilla.LEFT_ARM, -1);
|
|
||||||
BufferedImage rightArm = getVanillaSkinPart(skin, ISkinPart.Vanilla.RIGHT_ARM, -1);
|
|
||||||
BufferedImage leftLeg = getVanillaSkinPart(skin, ISkinPart.Vanilla.LEFT_LEG, -1);
|
|
||||||
BufferedImage rightLeg = getVanillaSkinPart(skin, ISkinPart.Vanilla.RIGHT_LEG, -1);
|
|
||||||
|
|
||||||
// Draw the body parts
|
|
||||||
graphics.drawImage(face, 4, 0, null);
|
|
||||||
graphics.drawImage(body, 4, 8, null);
|
|
||||||
graphics.drawImage(leftArm, skin.getModel() == Skin.Model.SLIM ? 1 : 0, 8, null);
|
|
||||||
graphics.drawImage(rightArm, 12, 8, null);
|
|
||||||
graphics.drawImage(leftLeg, 8, 20, null);
|
|
||||||
graphics.drawImage(rightLeg, 4, 20, null);
|
|
||||||
|
|
||||||
graphics.dispose();
|
|
||||||
return ImageUtils.resize(texture, size / 8D);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -33,12 +33,12 @@ import java.awt.geom.AffineTransform;
|
|||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A isometric 3D renderer for a {@link ISkinPart.Custom#HEAD}.
|
* A isometric 3D renderer for a {@link ISkinPart.Isometric}.
|
||||||
*
|
*
|
||||||
* @author Braydon
|
* @author Braydon
|
||||||
*/
|
*/
|
||||||
public final class IsometricHeadSkinPartRenderer extends SkinRenderer<ISkinPart.Custom> {
|
public final class IsometricSkinPartRenderer extends SkinRenderer<ISkinPart.Isometric> {
|
||||||
public static final IsometricHeadSkinPartRenderer INSTANCE = new IsometricHeadSkinPartRenderer();
|
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
|
||||||
private static final double SKEW_B = SKEW_A * 2D; // 1.15555555
|
private static final double SKEW_B = SKEW_A * 2D; // 1.15555555
|
||||||
@ -58,30 +58,28 @@ public final class IsometricHeadSkinPartRenderer extends SkinRenderer<ISkinPart.
|
|||||||
* @return the rendered skin part
|
* @return the rendered skin part
|
||||||
*/
|
*/
|
||||||
@Override @NonNull
|
@Override @NonNull
|
||||||
public BufferedImage render(@NonNull Skin skin, @NonNull ISkinPart.Custom 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;
|
||||||
|
|
||||||
BufferedImage texture = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB); // The texture to return
|
BufferedImage texture = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
|
||||||
Graphics2D graphics = texture.createGraphics(); // Create the graphics for drawing
|
Graphics2D graphics = texture.createGraphics();
|
||||||
|
|
||||||
// Get the Vanilla skin parts to draw
|
|
||||||
BufferedImage headTop = getVanillaSkinPart(skin, ISkinPart.Vanilla.HEAD_TOP, scale);
|
BufferedImage headTop = getVanillaSkinPart(skin, ISkinPart.Vanilla.HEAD_TOP, scale);
|
||||||
BufferedImage face = getVanillaSkinPart(skin, ISkinPart.Vanilla.FACE, scale);
|
BufferedImage face = getVanillaSkinPart(skin, ISkinPart.Vanilla.FACE, scale);
|
||||||
BufferedImage headLeft = getVanillaSkinPart(skin, ISkinPart.Vanilla.HEAD_LEFT, scale);
|
BufferedImage headLeft = getVanillaSkinPart(skin, ISkinPart.Vanilla.HEAD_LEFT, scale);
|
||||||
|
|
||||||
// Draw the top head part
|
// 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);
|
||||||
|
|
||||||
// Draw the face part
|
// Draw the face of the head
|
||||||
double x = xOffset + 8 * scale;
|
double x = xOffset + 8 * scale;
|
||||||
drawPart(graphics, face, FACE_TRANSFORM, x, x + zOffset - 0.5, face.getWidth(), face.getHeight());
|
drawPart(graphics, face, FACE_TRANSFORM, x, x + zOffset - 0.5, face.getWidth(), face.getHeight());
|
||||||
|
|
||||||
// Draw the left head part
|
// Draw the left side of the head
|
||||||
drawPart(graphics, headLeft, HEAD_LEFT_TRANSFORM, xOffset + 1, zOffset - 0.5, headLeft.getWidth(), headLeft.getHeight());
|
drawPart(graphics, headLeft, HEAD_LEFT_TRANSFORM, xOffset + 1, zOffset - 0.5, headLeft.getWidth(), headLeft.getHeight());
|
||||||
|
|
||||||
graphics.dispose();
|
|
||||||
return texture;
|
return texture;
|
||||||
}
|
}
|
||||||
|
|
@ -57,8 +57,8 @@ public final class VanillaSkinPartRenderer extends SkinRenderer<ISkinPart.Vanill
|
|||||||
return partImage;
|
return partImage;
|
||||||
}
|
}
|
||||||
// Create a new image, draw our skin part texture, and then apply overlays
|
// Create a new image, draw our skin part texture, and then apply overlays
|
||||||
BufferedImage texture = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB); // The texture to return
|
BufferedImage texture = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
|
||||||
Graphics2D graphics = texture.createGraphics(); // Create the graphics for drawing
|
Graphics2D graphics = texture.createGraphics();
|
||||||
graphics.drawImage(partImage, 0, 0, null);
|
graphics.drawImage(partImage, 0, 0, null);
|
||||||
|
|
||||||
// Draw part overlays
|
// Draw part overlays
|
||||||
@ -68,7 +68,6 @@ public final class VanillaSkinPartRenderer extends SkinRenderer<ISkinPart.Vanill
|
|||||||
applyOverlay(graphics, getVanillaSkinPart(skin, overlay, scale));
|
applyOverlay(graphics, getVanillaSkinPart(skin, overlay, scale));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
graphics.dispose();
|
|
||||||
return texture;
|
return texture;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -73,7 +73,7 @@ public final class PlayerController {
|
|||||||
public ResponseEntity<CachedPlayer> getPlayer(
|
public ResponseEntity<CachedPlayer> getPlayer(
|
||||||
@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
|
||||||
) throws BadRequestException, ResourceNotFoundException, MojangRateLimitException {
|
) throws BadRequestException, ResourceNotFoundException, MojangRateLimitException {
|
||||||
return ResponseEntity.ofNullable(mojangService.getPlayer(query));
|
return ResponseEntity.ofNullable(mojangService.getPlayer(query, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -26,7 +26,7 @@ package me.braydon.mc.model.skin;
|
|||||||
import lombok.*;
|
import lombok.*;
|
||||||
import me.braydon.mc.common.renderer.SkinRenderer;
|
import me.braydon.mc.common.renderer.SkinRenderer;
|
||||||
import me.braydon.mc.common.renderer.impl.BodySkinPartRenderer;
|
import me.braydon.mc.common.renderer.impl.BodySkinPartRenderer;
|
||||||
import me.braydon.mc.common.renderer.impl.IsometricHeadSkinPartRenderer;
|
import me.braydon.mc.common.renderer.impl.IsometricSkinPartRenderer;
|
||||||
import me.braydon.mc.common.renderer.impl.VanillaSkinPartRenderer;
|
import me.braydon.mc.common.renderer.impl.VanillaSkinPartRenderer;
|
||||||
|
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
@ -37,7 +37,7 @@ import java.awt.image.BufferedImage;
|
|||||||
* @author Braydon
|
* @author Braydon
|
||||||
*/
|
*/
|
||||||
public interface ISkinPart {
|
public interface ISkinPart {
|
||||||
Enum<?>[][] TYPES = { Vanilla.values(), Custom.values() };
|
Enum<?>[][] TYPES = { Vanilla.values(), Basic.values(), Isometric.values() };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the name of this part.
|
* Get the name of this part.
|
||||||
@ -78,7 +78,7 @@ public interface ISkinPart {
|
|||||||
/**
|
/**
|
||||||
* A part of a Vanilla skin texture.
|
* A part of a Vanilla skin texture.
|
||||||
*/
|
*/
|
||||||
@RequiredArgsConstructor @Getter @ToString
|
@AllArgsConstructor @RequiredArgsConstructor @Getter @ToString
|
||||||
enum Vanilla implements ISkinPart {
|
enum Vanilla implements ISkinPart {
|
||||||
// Overlays
|
// Overlays
|
||||||
HEAD_OVERLAY_FACE(new Coordinates(40, 8), 8, 8),
|
HEAD_OVERLAY_FACE(new Coordinates(40, 8), 8, 8),
|
||||||
@ -89,21 +89,7 @@ public interface ISkinPart {
|
|||||||
HEAD_LEFT(new Coordinates(0, 8), 8, 8),
|
HEAD_LEFT(new Coordinates(0, 8), 8, 8),
|
||||||
HEAD_RIGHT(new Coordinates(16, 8), 8, 8),
|
HEAD_RIGHT(new Coordinates(16, 8), 8, 8),
|
||||||
HEAD_BOTTOM(new Coordinates(16, 0), 8, 8),
|
HEAD_BOTTOM(new Coordinates(16, 0), 8, 8),
|
||||||
HEAD_BACK(new Coordinates(24, 8), 8, 8),
|
HEAD_BACK(new Coordinates(24, 8), 8, 8);
|
||||||
|
|
||||||
// Body
|
|
||||||
BODY_FRONT(new Coordinates(20, 20), 8, 12),
|
|
||||||
BODY_BACK(new Coordinates(20, 36), 8, 12),
|
|
||||||
BODY_LEFT(new Coordinates(32, 52), 4, 12),
|
|
||||||
BODY_RIGHT(new Coordinates(44, 20), 4, 12),
|
|
||||||
|
|
||||||
// Arms
|
|
||||||
LEFT_ARM(new Coordinates(44, 20), 4, 12),
|
|
||||||
RIGHT_ARM(new Coordinates(36, 52), new LegacyCoordinates(44, 20, true), 4, 12),
|
|
||||||
|
|
||||||
// Legs
|
|
||||||
LEFT_LEG(new Coordinates(4, 20), 4, 12),
|
|
||||||
RIGHT_LEG(new Coordinates(20, 52), new LegacyCoordinates(4, 20, true), 4, 12);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The coordinates of this part.
|
* The coordinates of this part.
|
||||||
@ -133,14 +119,6 @@ public interface ISkinPart {
|
|||||||
this(coordinates, null, width, height, overlays);
|
this(coordinates, null, width, height, overlays);
|
||||||
}
|
}
|
||||||
|
|
||||||
Vanilla(@NonNull Coordinates coordinates, LegacyCoordinates legacyCoordinates, int width, int height, Vanilla... overlays) {
|
|
||||||
this.coordinates = coordinates;
|
|
||||||
this.legacyCoordinates = legacyCoordinates;
|
|
||||||
this.width = width;
|
|
||||||
this.height = height;
|
|
||||||
this.overlays = overlays;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render a part of a skin.
|
* Render a part of a skin.
|
||||||
*
|
*
|
||||||
@ -154,24 +132,6 @@ public interface ISkinPart {
|
|||||||
return VanillaSkinPartRenderer.INSTANCE.render(skin, this, overlays, size);
|
return VanillaSkinPartRenderer.INSTANCE.render(skin, this, overlays, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Is this part an arm?
|
|
||||||
*
|
|
||||||
* @return whether this part is an arm
|
|
||||||
*/
|
|
||||||
public boolean isArm() {
|
|
||||||
return this == LEFT_ARM || this == RIGHT_ARM;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Does this part have legacy coordinates?
|
|
||||||
*
|
|
||||||
* @return whether this part has legacy coordinates
|
|
||||||
*/
|
|
||||||
public boolean hasLegacyCoordinates() {
|
|
||||||
return legacyCoordinates != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Coordinates of a part of a skin.
|
* Coordinates of a part of a skin.
|
||||||
*/
|
*/
|
||||||
@ -210,17 +170,19 @@ public interface ISkinPart {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A custom part of a skin.
|
* A basic part of a skin.
|
||||||
|
* <p>
|
||||||
|
* These parts have custom renderers!
|
||||||
|
* </p>
|
||||||
*/
|
*/
|
||||||
@AllArgsConstructor @Getter
|
@AllArgsConstructor @Getter
|
||||||
enum Custom implements ISkinPart {
|
enum Basic implements ISkinPart {
|
||||||
HEAD(IsometricHeadSkinPartRenderer.INSTANCE),
|
|
||||||
BODY(BodySkinPartRenderer.INSTANCE);
|
BODY(BodySkinPartRenderer.INSTANCE);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The custom renderer to use for this part.
|
* The custom renderer to use for this part.
|
||||||
*/
|
*/
|
||||||
@NonNull private final SkinRenderer<Custom> renderer;
|
@NonNull private final SkinRenderer<Basic> renderer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render a part of a skin.
|
* Render a part of a skin.
|
||||||
@ -235,4 +197,24 @@ public interface ISkinPart {
|
|||||||
return renderer.render(skin, this, overlays, 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -31,6 +31,7 @@ import me.braydon.mc.model.Player;
|
|||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A skin for a {@link Player}.
|
* A skin for a {@link Player}.
|
||||||
|
@ -168,15 +168,10 @@ public final class MojangService {
|
|||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
public byte[] getSkinPartTexture(@NonNull String partName, @NonNull String query, @NonNull String extension,
|
public byte[] getSkinPartTexture(@NonNull String partName, @NonNull String query, @NonNull String extension,
|
||||||
boolean overlays, String sizeString) throws BadRequestException, MojangRateLimitException {
|
boolean overlays, String sizeString) throws BadRequestException, MojangRateLimitException {
|
||||||
log.info("Requesting skin part {} with query {} (ext: {}, overlays: {}, size: {})",
|
|
||||||
partName, query, extension, overlays, sizeString
|
|
||||||
);
|
|
||||||
|
|
||||||
// Get the part from the given name
|
// Get the part from the given name
|
||||||
ISkinPart part = ISkinPart.getByName(partName); // The skin part to get
|
ISkinPart part = ISkinPart.getByName(partName); // The skin part to get
|
||||||
if (part == null) { // Default to the face
|
if (part == null) { // Default to the face
|
||||||
part = ISkinPart.Vanilla.FACE;
|
part = ISkinPart.Vanilla.FACE;
|
||||||
log.warn("Invalid skin part {}, defaulting to {}", partName, part.name());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the extension is valid
|
// Ensure the extension is valid
|
||||||
@ -195,41 +190,26 @@ public final class MojangService {
|
|||||||
}
|
}
|
||||||
if (size == null || size <= 0) { // Invalid size
|
if (size == null || size <= 0) { // Invalid size
|
||||||
size = DEFAULT_PART_TEXTURE_SIZE;
|
size = DEFAULT_PART_TEXTURE_SIZE;
|
||||||
log.warn("Invalid size {}, defaulting to {}", sizeString, size);
|
|
||||||
}
|
|
||||||
if (size > MAX_PART_TEXTURE_SIZE) { // Limit the size to 512
|
|
||||||
size = MAX_PART_TEXTURE_SIZE;
|
|
||||||
log.warn("Size {} is too large, defaulting to {}", sizeString, MAX_PART_TEXTURE_SIZE);
|
|
||||||
}
|
}
|
||||||
|
size = Math.min(size, MAX_PART_TEXTURE_SIZE); // Limit the size to 512
|
||||||
String id = "%s-%s-%s-%s-%s".formatted(query.toLowerCase(), part.name(), overlays, size, extension); // The id of the skin part
|
String id = "%s-%s-%s-%s-%s".formatted(query.toLowerCase(), part.name(), overlays, size, extension); // The id of the skin part
|
||||||
|
|
||||||
// In production, check the cache for the
|
Optional<CachedSkinPartTexture> cached = skinPartTextureCache.findById(id); // Get the cached texture
|
||||||
// skin part and return it if it's present
|
if (cached.isPresent()) { // Respond with the cache if present
|
||||||
if (EnvironmentUtils.isProduction()) {
|
return cached.get().getTexture();
|
||||||
Optional<CachedSkinPartTexture> cached = skinPartTextureCache.findById(id);
|
|
||||||
if (cached.isPresent()) { // Respond with the cache if present
|
|
||||||
log.info("Found skin part {} in cache: {}", part.name(), id);
|
|
||||||
return cached.get().getTexture();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Skin skin = null; // The target skin to get the skin part of
|
Skin skin = null; // The target skin to get the skin part of
|
||||||
long before = System.currentTimeMillis();
|
|
||||||
try {
|
try {
|
||||||
CachedPlayer player = getPlayer(query); // Retrieve the player
|
CachedPlayer player = getPlayer(query, false); // Retrieve the player
|
||||||
skin = player.getSkin(); // Use the player's skin
|
skin = player.getSkin(); // Use the player's skin
|
||||||
} catch (Exception ignored) {
|
} catch (Exception ignored) {
|
||||||
// Simply ignore, and fallback to the default skin
|
// Simply ignore, and fallback to the default skin
|
||||||
}
|
}
|
||||||
if (skin == null) { // Fallback to the default skin
|
if (skin == null) { // Fallback to the default skin
|
||||||
skin = Skin.DEFAULT_STEVE;
|
skin = Skin.DEFAULT_STEVE;
|
||||||
log.warn("Failed to get skin for player {}, defaulting to Steve", query);
|
|
||||||
} else {
|
|
||||||
log.info("Got skin for player {} in {}ms", query, System.currentTimeMillis() - before);
|
|
||||||
}
|
}
|
||||||
before = System.currentTimeMillis();
|
|
||||||
BufferedImage texture = part.render(skin, overlays, size); // Render the skin part
|
BufferedImage texture = part.render(skin, overlays, size); // Render the skin part
|
||||||
log.info("Render of skin part took {}ms: {}", System.currentTimeMillis() - before, id);
|
|
||||||
|
|
||||||
// Convert BufferedImage to byte array
|
// Convert BufferedImage to byte array
|
||||||
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
|
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
|
||||||
@ -238,7 +218,6 @@ public final class MojangService {
|
|||||||
|
|
||||||
byte[] bytes = outputStream.toByteArray();
|
byte[] bytes = outputStream.toByteArray();
|
||||||
skinPartTextureCache.save(new CachedSkinPartTexture(id, bytes)); // Cache the texture
|
skinPartTextureCache.save(new CachedSkinPartTexture(id, bytes)); // Cache the texture
|
||||||
log.info("Cached skin part texture: {}", id);
|
|
||||||
return bytes;
|
return bytes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -253,13 +232,15 @@ public final class MojangService {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param query the query to search for the player by
|
* @param query the query to search for the player by
|
||||||
|
* @param bypassCache should the cache be bypassed?
|
||||||
* @return the player
|
* @return the player
|
||||||
* @throws BadRequestException if the UUID or username is invalid
|
* @throws BadRequestException if the UUID or username is invalid
|
||||||
* @throws ResourceNotFoundException if the player is not found
|
* @throws ResourceNotFoundException if the player is not found
|
||||||
* @throws MojangRateLimitException if the Mojang API rate limit is reached
|
* @throws MojangRateLimitException if the Mojang API rate limit is reached
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public CachedPlayer getPlayer(@NonNull String query) throws BadRequestException, ResourceNotFoundException, MojangRateLimitException {
|
public CachedPlayer getPlayer(@NonNull String query, boolean bypassCache)
|
||||||
|
throws BadRequestException, ResourceNotFoundException, MojangRateLimitException {
|
||||||
log.info("Requesting player with query: {}", query);
|
log.info("Requesting player with query: {}", query);
|
||||||
|
|
||||||
UUID uuid; // The player UUID to lookup
|
UUID uuid; // The player UUID to lookup
|
||||||
@ -280,11 +261,12 @@ public final class MojangService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check the cache for the player
|
// Check the cache for the player
|
||||||
// and return it if it's present
|
if (!bypassCache) {
|
||||||
Optional<CachedPlayer> cached = playerCache.findById(uuid);
|
Optional<CachedPlayer> cached = playerCache.findById(uuid);
|
||||||
if (cached.isPresent()) { // Respond with the cache if present
|
if (cached.isPresent()) { // Respond with the cache if present
|
||||||
log.info("Found player in cache: {}", uuid);
|
log.info("Found player in cache: {}", uuid);
|
||||||
return cached.get();
|
return cached.get();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send a request to Mojang requesting
|
// Send a request to Mojang requesting
|
||||||
@ -298,14 +280,18 @@ public final class MojangService {
|
|||||||
ProfileAction[] profileActions = token.getProfileActions();
|
ProfileAction[] profileActions = token.getProfileActions();
|
||||||
|
|
||||||
// Build our player model, cache it, and then return it
|
// Build our player model, cache it, and then return it
|
||||||
CachedPlayer player = new CachedPlayer(uuid, token.getName(),
|
CachedPlayer player = new CachedPlayer(
|
||||||
|
uuid, token.getName(),
|
||||||
skinProperties.getSkin() == null ? Skin.DEFAULT_STEVE : skinProperties.getSkin(),
|
skinProperties.getSkin() == null ? Skin.DEFAULT_STEVE : skinProperties.getSkin(),
|
||||||
skinProperties.getCape(), token.getProperties(), profileActions.length == 0 ? null : profileActions,
|
skinProperties.getCape(),
|
||||||
|
token.getProperties(),
|
||||||
|
profileActions.length == 0 ? null : profileActions,
|
||||||
System.currentTimeMillis()
|
System.currentTimeMillis()
|
||||||
);
|
);
|
||||||
// Store in the cache
|
if (!bypassCache) { // Store in the cache
|
||||||
playerCache.save(player);
|
playerCache.save(player);
|
||||||
log.info("Cached player: {}", uuid);
|
log.info("Cached player: {}", uuid);
|
||||||
|
}
|
||||||
|
|
||||||
player.setCached(-1L); // Set to -1 to indicate it's not cached in the response
|
player.setCached(-1L); // Set to -1 to indicate it's not cached in the response
|
||||||
return player;
|
return player;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user