Skip to content

Commit 5e5464d

Browse files
committed
Feat(Mod): Rematrix Player Skin Previews.
1 parent 767e22e commit 5e5464d

1 file changed

Lines changed: 140 additions & 17 deletions

File tree

RemotelyMod/src/main/java/redxax/oxy/remotely/rematrix/mc/RematrixContext.java

Lines changed: 140 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,19 @@
1212
import java.util.Deque;
1313
import java.util.LinkedHashMap;
1414
import java.util.List;
15+
import java.util.Locale;
1516
import java.util.Map;
1617
import java.util.Optional;
18+
import java.util.Set;
1719
import java.util.UUID;
1820
import java.util.WeakHashMap;
21+
import java.util.concurrent.CompletableFuture;
22+
import java.util.concurrent.ConcurrentHashMap;
1923
import net.minecraft.client.Minecraft;
2024
import net.minecraft.ChatFormatting;
2125
import net.minecraft.client.multiplayer.ClientLevel;
2226
import net.minecraft.client.player.RemotePlayer;
23-
//#if MC >= 1.21.11 || MC >= 26.1
27+
//#if MC >= 1.21.9 || MC >= 26.1
2428
import net.minecraft.core.ClientAsset;
2529
//#endif
2630
//#if MC >= 26.1
@@ -80,6 +84,9 @@
8084
import net.minecraft.world.item.component.ItemLore;
8185
import net.minecraft.world.inventory.tooltip.TooltipComponent;
8286
//#endif
87+
//#if MC >= 1.21.1 && MC < 1.21.9
88+
//$$ import net.minecraft.client.resources.PlayerSkin;
89+
//#endif
8390
import net.minecraft.world.entity.AgeableMob;
8491
import net.minecraft.world.entity.Entity;
8592
//#if MC >= 1.21.10 || MC >= 26.1
@@ -88,10 +95,14 @@
8895
import net.minecraft.world.entity.EntityType;
8996
import net.minecraft.world.entity.EquipmentSlot;
9097
import net.minecraft.world.entity.LivingEntity;
91-
//#if MC >= 1.21.11 || MC >= 26.1
98+
//#if MC >= 1.21.9 || MC >= 26.1
99+
import net.minecraft.world.entity.player.PlayerModelPart;
92100
import net.minecraft.world.entity.player.PlayerModelType;
93101
import net.minecraft.world.entity.player.PlayerSkin;
94102
//#endif
103+
//#if MC >= 1.21.1 && MC < 1.21.9
104+
//$$ import net.minecraft.world.entity.player.PlayerModelPart;
105+
//#endif
95106
import net.minecraft.world.item.Item;
96107
import net.minecraft.world.level.Level;
97108
//#if MC < 1.21.1
@@ -109,6 +120,7 @@
109120
import org.joml.Vector3f;
110121
import redxax.oxy.remotely.rematrix.*;
111122
import redxax.oxy.remotely.rematrix.ReContext;
123+
import redxax.restudio.Remodel.util.SkinFetcher;
112124
import restudio.rescreen.debug.DebugDrawStats;
113125
import restudio.rescreen.game.MinecraftGameItem;
114126
import restudio.rescreen.game.MinecraftGameEntity;
@@ -125,6 +137,10 @@
125137
public final class RematrixContext implements ReContext {
126138
private static final Map<BufferedImage, ReTextureHandle> TEXTURE_CACHE = Collections.synchronizedMap(new WeakHashMap<>());
127139
private static final Map<MinecraftRenderItem, ItemStack> ITEM_STACK_CACHE = Collections.synchronizedMap(new WeakHashMap<>());
140+
private static final long PLAYER_SKIN_RETRY_DELAY_MS = 60000L;
141+
private static final Map<String, BufferedImage> PLAYER_SKIN_CACHE = new ConcurrentHashMap<>();
142+
private static final Map<String, Long> PLAYER_SKIN_FAILURES = new ConcurrentHashMap<>();
143+
private static final Set<String> PLAYER_SKIN_FETCHING = ConcurrentHashMap.newKeySet();
128144
private static final int SELECTION_COLOR = 0xFF0000FF;
129145
private static final float PLAYER_HEAD_MOUSE_Y_OFFSET = 0.32f;
130146

@@ -603,37 +619,53 @@ private LivingEntity createPlayerEntity(MinecraftRenderEntity renderEntity) {
603619
if (isCurrentPlayerRequest(renderEntity)) {
604620
return minecraft.player;
605621
}
606-
Entity cached = entityCache.get(renderEntity);
607-
if (cached instanceof LivingEntity livingEntity) {
608-
applyRenderEntityData(livingEntity, renderEntity);
609-
prepareSyntheticPreviewEntity(livingEntity);
610-
return livingEntity;
611-
}
612622
if (minecraft.level == null) {
613623
return minecraft.player;
614624
}
615625
String name = renderEntity.name() != null && !renderEntity.name().isBlank() ? renderEntity.name() : "Player";
616-
UUID uuid = UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(StandardCharsets.UTF_8));
617-
//#if MC >= 1.21.11 || MC >= 26.1
626+
String skinUsername = previewPlayerSkinUsername(renderEntity);
627+
String profileName = skinUsername != null && !skinUsername.isBlank() ? skinUsername : name;
628+
UUID uuid = UUID.nameUUIDFromBytes(("OfflinePlayer:" + profileName).getBytes(StandardCharsets.UTF_8));
629+
Entity cached = entityCache.get(renderEntity);
630+
if (cached instanceof LivingEntity livingEntity) {
631+
if (shouldRecreatePlayerEntity(renderEntity, cached)) {
632+
entityCache.remove(renderEntity);
633+
} else {
634+
applyRenderEntityData(livingEntity, renderEntity);
635+
prepareSyntheticPreviewEntity(livingEntity);
636+
return livingEntity;
637+
}
638+
}
639+
//#if MC >= 1.21.1 || MC >= 26.1
618640
PlayerSkin skin = previewPlayerSkin(renderEntity);
619-
RemotePlayer player = skin != null ? new PreviewRemotePlayer(minecraft.level, new GameProfile(uuid, name), skin) : new RemotePlayer(minecraft.level, new GameProfile(uuid, name));
641+
RemotePlayer player = skin != null ? new PreviewRemotePlayer(minecraft.level, new GameProfile(uuid, profileName), skin) : new RemotePlayer(minecraft.level, new GameProfile(uuid, profileName));
620642
//#endif
621-
//#if MC < 1.21.11 && MC < 26.1
622-
//$$ RemotePlayer player = new RemotePlayer(minecraft.level, new GameProfile(uuid, name));
643+
//#if MC < 1.21.1 && MC < 26.1
644+
//$$ RemotePlayer player = new RemotePlayer(minecraft.level, new GameProfile(uuid, profileName));
623645
//#endif
624646
applyRenderEntityData(player, renderEntity);
625647
prepareSyntheticPreviewEntity(player);
626648
entityCache.put(renderEntity, player);
627649
return player;
628650
}
629651

630-
//#if MC >= 1.21.11 || MC >= 26.1
652+
private boolean shouldRecreatePlayerEntity(MinecraftRenderEntity renderEntity, Entity cached) {
653+
//#if MC >= 1.21.1 || MC >= 26.1
654+
return !(cached instanceof PreviewRemotePlayer) && previewPlayerSkinImage(renderEntity) != null;
655+
//#endif
656+
//#if MC < 1.21.1 && MC < 26.1
657+
//$$ return false;
658+
//#endif
659+
}
660+
661+
//#if MC >= 1.21.1 || MC >= 26.1
631662
private PlayerSkin previewPlayerSkin(MinecraftRenderEntity renderEntity) {
632-
BufferedImage image = renderEntity.skin();
663+
BufferedImage image = previewPlayerSkinImage(renderEntity);
633664
if (image == null) {
634665
return null;
635666
}
636667
ReTextureHandle handle = textures.getTexture(image);
668+
//#if MC >= 1.21.11 || MC >= 26.1
637669
if (!(handle.getId() instanceof Identifier id)) {
638670
return null;
639671
}
@@ -648,11 +680,97 @@ public Identifier texturePath() {
648680
return id;
649681
}
650682
};
683+
//#endif
684+
//#if MC >= 1.21.9 && MC < 1.21.11
685+
//$$ if (!(handle.getId() instanceof ResourceLocation id)) {
686+
//$$ return null;
687+
//$$ }
688+
//$$ ClientAsset.Texture texture = new ClientAsset.Texture() {
689+
//$$ @Override
690+
//$$ public ResourceLocation id() {
691+
//$$ return id;
692+
//$$ }
693+
//$$
694+
//$$ @Override
695+
//$$ public ResourceLocation texturePath() {
696+
//$$ return id;
697+
//$$ }
698+
//$$ };
699+
//#endif
700+
//#if MC >= 1.21.9 || MC >= 26.1
651701
PlayerModelType modelType = renderEntity.slim() ? PlayerModelType.SLIM : PlayerModelType.WIDE;
652-
return PlayerSkin.insecure(texture, null, null, modelType);
702+
return new PlayerSkin(texture, null, null, modelType, false);
703+
//#endif
704+
//#if MC >= 1.21.1 && MC < 1.21.9
705+
//$$ if (!(handle.getId() instanceof ResourceLocation id)) {
706+
//$$ return null;
707+
//$$ }
708+
//$$ PlayerSkin.Model modelType = renderEntity.slim() ? PlayerSkin.Model.SLIM : PlayerSkin.Model.WIDE;
709+
//$$ return new PlayerSkin(id, "", null, null, modelType, false);
710+
//#endif
653711
}
654712
//#endif
655713

714+
private BufferedImage previewPlayerSkinImage(MinecraftRenderEntity renderEntity) {
715+
BufferedImage image = renderEntity.skin();
716+
if (image != null) {
717+
return image;
718+
}
719+
String username = previewPlayerSkinUsername(renderEntity);
720+
if (username == null || username.isBlank()) {
721+
return null;
722+
}
723+
String key = username.toLowerCase(Locale.ROOT);
724+
BufferedImage cached = PLAYER_SKIN_CACHE.get(key);
725+
if (cached != null) {
726+
return cached;
727+
}
728+
long lastFailure = PLAYER_SKIN_FAILURES.getOrDefault(key, 0L);
729+
if (lastFailure > 0L && System.currentTimeMillis() - lastFailure < PLAYER_SKIN_RETRY_DELAY_MS) {
730+
return null;
731+
}
732+
if (PLAYER_SKIN_FETCHING.add(key)) {
733+
String fetchUsername = username;
734+
CompletableFuture.supplyAsync(() -> SkinFetcher.getSkin(fetchUsername))
735+
.thenAccept(skin -> {
736+
if (skin != null) {
737+
PLAYER_SKIN_CACHE.put(key, skin);
738+
PLAYER_SKIN_FAILURES.remove(key);
739+
} else {
740+
PLAYER_SKIN_FAILURES.put(key, System.currentTimeMillis());
741+
}
742+
PLAYER_SKIN_FETCHING.remove(key);
743+
})
744+
.exceptionally(error -> {
745+
PLAYER_SKIN_FAILURES.put(key, System.currentTimeMillis());
746+
PLAYER_SKIN_FETCHING.remove(key);
747+
return null;
748+
});
749+
}
750+
return null;
751+
}
752+
753+
private String previewPlayerSkinUsername(MinecraftRenderEntity renderEntity) {
754+
Map<String, Object> tag = renderEntity.tag();
755+
if (tag != null && !tag.isEmpty()) {
756+
Object skin = tag.get("skin");
757+
if (skin instanceof Map<?, ?> skinMap) {
758+
String username = stringValue(skinMap.get("username"));
759+
if (username != null && !username.isBlank()) {
760+
return username;
761+
}
762+
}
763+
String username = stringValue(tag.get("skin.username"));
764+
if (username == null || username.isBlank()) {
765+
username = stringValue(tag.get("skinUsername"));
766+
}
767+
if (username != null && !username.isBlank()) {
768+
return username;
769+
}
770+
}
771+
return renderEntity.name();
772+
}
773+
656774
private boolean isCurrentPlayerRequest(MinecraftRenderEntity renderEntity) {
657775
return renderEntity.name() == null
658776
&& renderEntity.texture() == null
@@ -2370,7 +2488,7 @@ private float[] getCurrentRaw() {
23702488
private record ArmorTrimTag(String material, String pattern) {
23712489
}
23722490

2373-
//#if MC >= 1.21.11 || MC >= 26.1
2491+
//#if MC >= 1.21.1 || MC >= 26.1
23742492
private static final class PreviewRemotePlayer extends RemotePlayer {
23752493
private final PlayerSkin skin;
23762494

@@ -2383,6 +2501,11 @@ private PreviewRemotePlayer(ClientLevel level, GameProfile profile, PlayerSkin s
23832501
public PlayerSkin getSkin() {
23842502
return skin;
23852503
}
2504+
2505+
@Override
2506+
public boolean isModelPartShown(PlayerModelPart part) {
2507+
return true;
2508+
}
23862509
}
23872510
//#endif
23882511

0 commit comments

Comments
 (0)