173 lines
6.9 KiB
Java
173 lines
6.9 KiB
Java
package tc.oc.commons.bukkit.nick;
|
|
|
|
import com.google.common.cache.Cache;
|
|
import com.google.common.cache.CacheBuilder;
|
|
import java.util.HashMap;
|
|
import java.util.Map;
|
|
import java.util.concurrent.TimeUnit;
|
|
import javax.annotation.Nullable;
|
|
import javax.inject.Inject;
|
|
import javax.inject.Singleton;
|
|
import org.bukkit.Skin;
|
|
import org.bukkit.entity.Player;
|
|
import org.bukkit.scoreboard.Scoreboard;
|
|
import org.bukkit.scoreboard.Team;
|
|
import tc.oc.commons.bukkit.chat.FullNameRenderer;
|
|
import tc.oc.commons.bukkit.chat.NameStyle;
|
|
import tc.oc.commons.bukkit.chat.NameType;
|
|
import tc.oc.commons.bukkit.flairs.FlairConfiguration;
|
|
import tc.oc.commons.bukkit.flairs.FlairRenderer;
|
|
import tc.oc.commons.core.scheduler.Scheduler;
|
|
|
|
/**
|
|
* Manages the Bukkit aspects of a player's name and appearance
|
|
*/
|
|
@Singleton
|
|
public class PlayerAppearanceChanger {
|
|
|
|
private static final NameType REAL_NAME_TYPE = new NameType(NameStyle.VERBOSE, true, true, false, false, false);
|
|
private static final NameType NICKNAME_TYPE = new NameType(NameStyle.VERBOSE, true, false, false, false, false);
|
|
|
|
private final IdentityProvider identityProvider;
|
|
private final FlairConfiguration flairConfig;
|
|
private final Scheduler scheduler;
|
|
private final FullNameRenderer nameRenderer;
|
|
private final UsernameRenderer usernameRenderer;
|
|
private final FlairRenderer flairRenderer;
|
|
private final Map<String, Skin> skinAssigned;
|
|
private final Cache<Skin, Integer> skinPool;
|
|
|
|
@Inject
|
|
PlayerAppearanceChanger(IdentityProvider identityProvider, FlairConfiguration flairConfig, Scheduler scheduler, FullNameRenderer nameRenderer, UsernameRenderer usernameRenderer, FlairRenderer flairRenderer) {
|
|
this.identityProvider = identityProvider;
|
|
this.flairConfig = flairConfig;
|
|
this.scheduler = scheduler;
|
|
this.nameRenderer = nameRenderer;
|
|
this.usernameRenderer = usernameRenderer;
|
|
this.flairRenderer = flairRenderer;
|
|
this.skinAssigned = new HashMap<>();
|
|
this.skinPool = CacheBuilder.newBuilder()
|
|
.expireAfterWrite(1, TimeUnit.HOURS)
|
|
.maximumSize(30)
|
|
.build();
|
|
}
|
|
|
|
/**
|
|
* Refresh the given player's appearance for all viewers.
|
|
*/
|
|
public void refreshPlayer(Player player) {
|
|
refreshPlayer(player, identityProvider.currentIdentity(player));
|
|
}
|
|
|
|
/**
|
|
* Refresh the given player's appearance for all viewers, assuming the given identity is their current one.
|
|
*
|
|
* This is necessary if the player's identity changes, or if their current identity's name is invalidated.
|
|
*/
|
|
public void refreshPlayer(final Player player, final Identity identity) {
|
|
player.setDisplayName(nameRenderer.getLegacyName(identity, REAL_NAME_TYPE));
|
|
|
|
final String legacyNickname = renderLegacyNickname(identity);
|
|
for(Player viewer : player.getServer().getOnlinePlayers()) {
|
|
refreshFakeNameAndSkin(player, identity, legacyNickname, viewer);
|
|
}
|
|
|
|
if(flairConfig.overheadFlair()) {
|
|
String prefix = usernameRenderer.getColor(identity, REAL_NAME_TYPE).toString();
|
|
if(identity.getNickname() == null) {
|
|
prefix = flairRenderer.getLegacyName(identity, REAL_NAME_TYPE) + prefix;
|
|
}
|
|
setOverheadNamePrefix(player, prefix);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Refresh the appearance of all players for the given viewer
|
|
*/
|
|
public void refreshViewer(final Player viewer) {
|
|
for(Player player : viewer.getServer().getOnlinePlayers()) {
|
|
final Identity identity = identityProvider.currentIdentity(player);
|
|
refreshFakeNameAndSkin(player, identity, renderLegacyNickname(identity), viewer);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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(flairConfig.overheadFlair()) {
|
|
// Remove players from their "overhead flair team" on quit
|
|
final Team team = player.getServer().getScoreboardManager().getMainScoreboard().getPlayerTeam(player);
|
|
if(team != null) {
|
|
scheduler.debounceTask(() -> team.removePlayer(player));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Refresh the given player's fake appearance for the given viewer, assuming the given identity
|
|
*/
|
|
private void refreshFakeNameAndSkin(Player player, Identity identity, @Nullable String fakeDisplayName, Player viewer) {
|
|
if(identity.isRevealed(viewer)) {
|
|
player.setFakeNameAndSkin(viewer, null, null);
|
|
player.setFakeDisplayName(viewer, null);
|
|
} else {
|
|
player.setFakeNameAndSkin(viewer, identity.getNickname(), getRandomSkin(fakeDisplayName));
|
|
player.setFakeDisplayName(viewer, fakeDisplayName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets a prefix for a player's overhead name by adding them to a scoreboard team.
|
|
* Don't use this if scoreboard teams are being used for any other purpose.
|
|
*/
|
|
private static void setOverheadNamePrefix(Player player, String prefix) {
|
|
final Scoreboard scoreboard = player.getServer().getScoreboardManager().getMainScoreboard();
|
|
prefix = prefix.substring(0, Math.min(prefix.length(), 14));
|
|
|
|
Team team = scoreboard.getTeam(prefix);
|
|
if(team == null) {
|
|
team = scoreboard.registerNewTeam(prefix);
|
|
team.setPrefix(prefix);
|
|
team.setOption(Team.Option.COLLISION_RULE, Team.OptionStatus.NEVER);
|
|
}
|
|
team.addPlayer(player);
|
|
}
|
|
|
|
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.<Skin, Integer>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;
|
|
}
|
|
}
|