diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/FlairRenderer.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/FlairRenderer.java index de5aa57..f6cbe88 100644 --- a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/FlairRenderer.java +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/FlairRenderer.java @@ -1,6 +1,7 @@ package tc.oc.commons.bukkit.chat; import java.util.Set; +import java.util.stream.Stream; import javax.inject.Inject; import javax.inject.Singleton; @@ -29,7 +30,15 @@ public class FlairRenderer implements PartialNameRenderer { @Override public String getLegacyName(Identity identity, NameType type) { if(!(type.style.contains(NameFlag.FLAIR) && type.reveal)) return ""; + return getFlairs(identity).reduce("", String::concat); + } + @Override + public BaseComponent getComponentName(Identity identity, NameType type) { + return Components.fromLegacyText(getLegacyName(identity, type)); + } + + public Stream getFlairs(Identity identity) { final UserDoc.Identity user; if(identity.getPlayerId() instanceof UserDoc.Identity) { // Flair may already be stashed inside the Identity @@ -37,19 +46,18 @@ public class FlairRenderer implements PartialNameRenderer { } else { user = userStore.tryUser(identity.getPlayerId()); } - if(user == null) return ""; + if(user == null) return Stream.empty(); final Set realms = ImmutableSet.copyOf(minecraftService.getLocalServer().realms()); return user.minecraft_flair() - .stream() - .filter(flair -> realms.contains(flair.realm)) - .map(flair -> flair.text) - .reduce("", String::concat); + .stream() + .filter(flair -> realms.contains(flair.realm)) + .map(flair -> flair.text); } - @Override - public BaseComponent getComponentName(Identity identity, NameType type) { - return Components.fromLegacyText(getLegacyName(identity, type)); + public int getNumberOfFlairs(Identity identity) { + return (int) getFlairs(identity).count(); } + } diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/nick/PlayerAppearanceChanger.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/nick/PlayerAppearanceChanger.java index 25714e7..73c4727 100644 --- a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/nick/PlayerAppearanceChanger.java +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/nick/PlayerAppearanceChanger.java @@ -4,6 +4,8 @@ import javax.annotation.Nullable; import javax.inject.Inject; import javax.inject.Singleton; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; import org.bukkit.Skin; import org.bukkit.entity.Player; import org.bukkit.scoreboard.Scoreboard; @@ -14,6 +16,10 @@ import tc.oc.commons.bukkit.chat.NameStyle; import tc.oc.commons.bukkit.chat.NameType; import tc.oc.commons.core.scheduler.Scheduler; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + /** * Manages the Bukkit aspects of a player's name and appearance */ @@ -29,6 +35,8 @@ public class PlayerAppearanceChanger { private final FullNameRenderer nameRenderer; private final UsernameRenderer usernameRenderer; private final FlairRenderer flairRenderer; + private final Map skinAssigned; + private final Cache skinPool; @Inject PlayerAppearanceChanger(IdentityProvider identityProvider, NicknameConfiguration config, Scheduler scheduler, FullNameRenderer nameRenderer, UsernameRenderer usernameRenderer, FlairRenderer flairRenderer) { @@ -38,6 +46,11 @@ public class PlayerAppearanceChanger { this.nameRenderer = nameRenderer; this.usernameRenderer = usernameRenderer; this.flairRenderer = flairRenderer; + this.skinAssigned = new HashMap<>(); + this.skinPool = CacheBuilder.newBuilder() + .expireAfterWrite(1, TimeUnit.HOURS) + .maximumSize(30) + .build(); } /** @@ -83,6 +96,7 @@ public class PlayerAppearanceChanger { * Release any resources being used to maintain the given player's appearance */ public void cleanupAfterPlayer(Player player) { + skinPool.put(player.getRealSkin(), flairRenderer.getNumberOfFlairs(identityProvider.createIdentity(player))); if(config.overheadFlair()) { // Remove players from their "overhead flair team" on quit final Team team = player.getServer().getScoreboardManager().getMainScoreboard().getPlayerTeam(player); @@ -100,7 +114,7 @@ public class PlayerAppearanceChanger { player.setFakeNameAndSkin(viewer, null, null); player.setFakeDisplayName(viewer, null); } else { - player.setFakeNameAndSkin(viewer, identity.getNickname(), Skin.EMPTY); + player.setFakeNameAndSkin(viewer, identity.getNickname(), getRandomSkin(fakeDisplayName)); player.setFakeDisplayName(viewer, fakeDisplayName); } } @@ -125,4 +139,34 @@ public class PlayerAppearanceChanger { private @Nullable String renderLegacyNickname(Identity identity) { return identity.getNickname() == null ? null : nameRenderer.getLegacyName(identity, NICKNAME_TYPE); } + + /** + * Get a random skin from users that have recently disconnected. + * Prioritize skins that come from players with fewer flairs. + */ + private Skin getRandomSkin(@Nullable String nickname) { + Skin skin; + if(skinAssigned.containsKey(nickname)) { + skin = skinAssigned.get(nickname); + } else { + skin = skinPool.asMap() + .entrySet() + .stream() + .sorted(Map.Entry.comparingByValue() + .reversed() + .thenComparing(entry -> entry.getKey().hashCode())) + .map(Map.Entry::getKey) + .findAny() + .orElse(null); + if(skin != null) { + skinPool.invalidate(skin); + } else { + skin = Skin.EMPTY; + } + if(nickname != null) { + skinAssigned.put(nickname, skin); + } + } + return skin; + } } diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/tablist/PlayerTabEntry.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/tablist/PlayerTabEntry.java index b568d4f..0980189 100644 --- a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/tablist/PlayerTabEntry.java +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/tablist/PlayerTabEntry.java @@ -76,7 +76,7 @@ public class PlayerTabEntry extends DynamicTabEntry { @Override public @Nullable Skin getSkin(TabView view) { final Identity identity = identityProvider.currentIdentity(player); - return identity.isDisguised(view.getViewer()) ? null : player.getSkin(); + return identity.isDisguised(view.getViewer()) ? player.getFakeSkin(view.getViewer()) : player.getSkin(); } // Dispatched by TabManager