1212import java .util .Deque ;
1313import java .util .LinkedHashMap ;
1414import java .util .List ;
15+ import java .util .Locale ;
1516import java .util .Map ;
1617import java .util .Optional ;
18+ import java .util .Set ;
1719import java .util .UUID ;
1820import java .util .WeakHashMap ;
21+ import java .util .concurrent .CompletableFuture ;
22+ import java .util .concurrent .ConcurrentHashMap ;
1923import net .minecraft .client .Minecraft ;
2024import net .minecraft .ChatFormatting ;
2125import net .minecraft .client .multiplayer .ClientLevel ;
2226import net .minecraft .client .player .RemotePlayer ;
23- //#if MC >= 1.21.11 || MC >= 26.1
27+ //#if MC >= 1.21.9 || MC >= 26.1
2428import net .minecraft .core .ClientAsset ;
2529//#endif
2630//#if MC >= 26.1
8084import net .minecraft .world .item .component .ItemLore ;
8185import 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
8390import net .minecraft .world .entity .AgeableMob ;
8491import net .minecraft .world .entity .Entity ;
8592//#if MC >= 1.21.10 || MC >= 26.1
8895import net .minecraft .world .entity .EntityType ;
8996import net .minecraft .world .entity .EquipmentSlot ;
9097import 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 ;
92100import net .minecraft .world .entity .player .PlayerModelType ;
93101import 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
95106import net .minecraft .world .item .Item ;
96107import net .minecraft .world .level .Level ;
97108//#if MC < 1.21.1
109120import org .joml .Vector3f ;
110121import redxax .oxy .remotely .rematrix .*;
111122import redxax .oxy .remotely .rematrix .ReContext ;
123+ import redxax .restudio .Remodel .util .SkinFetcher ;
112124import restudio .rescreen .debug .DebugDrawStats ;
113125import restudio .rescreen .game .MinecraftGameItem ;
114126import restudio .rescreen .game .MinecraftGameEntity ;
125137public 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