ProjectAres/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/nick/IdentityProviderImpl.java

203 lines
7.2 KiB
Java

package tc.oc.commons.bukkit.nick;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.event.EventBus;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.AsyncPlayerPreLoginEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import tc.oc.api.bukkit.friends.OnlineFriends;
import tc.oc.api.bukkit.users.BukkitUserStore;
import tc.oc.api.bukkit.users.OnlinePlayers;
import tc.oc.api.docs.PlayerId;
import tc.oc.api.docs.Session;
import tc.oc.api.docs.UserId;
import tc.oc.api.users.UserSearchResponse;
import tc.oc.commons.bukkit.event.UserLoginEvent;
import tc.oc.commons.bukkit.util.PlayerStates;
import tc.oc.commons.core.plugin.PluginFacet;
import tc.oc.minecraft.scheduler.SyncExecutor;
@Singleton
public class IdentityProviderImpl implements IdentityProvider, Listener, PluginFacet {
private static final String REVEAL_ALL_PERMISSION = "nick.see-through-all";
private final BukkitUserStore userStore;
private final EventBus eventBus;
private final OnlinePlayers onlinePlayers;
private final PlayerStates playerStates;
private final OnlineFriends friendMap;
private final SyncExecutor syncExecutor;
private final ConsoleIdentity consoleIdentity;
private final Map<Player, Identity> identities = new HashMap<>();
private final Map<String, Player> nicknames = new HashMap<>();
@Inject
IdentityProviderImpl(BukkitUserStore userStore, EventBus eventBus, OnlinePlayers onlinePlayers, PlayerStates playerStates, OnlineFriends friendMap, SyncExecutor syncExecutor, ConsoleIdentity consoleIdentity) {
this.userStore = userStore;
this.eventBus = eventBus;
this.onlinePlayers = onlinePlayers;
this.playerStates = playerStates;
this.friendMap = friendMap;
this.syncExecutor = syncExecutor;
this.consoleIdentity = consoleIdentity;
}
@Override
public boolean revealAll(CommandSender viewer) {
return viewer.hasPermission(REVEAL_ALL_PERMISSION);
}
@Override
public boolean reveal(CommandSender viewer, UserId userId) {
return revealAll(viewer) || friendMap.areFriends(viewer, userId);
}
@Override
public Identity createIdentity(PlayerId playerId, @Nullable String nickname) {
return new IdentityImpl(onlinePlayers, friendMap, playerStates, this, playerId, nickname);
}
@Override
public Identity createIdentity(Player player, @Nullable String nickname) {
return createIdentity(userStore.getUser(player), nickname);
}
@Override
public Identity createIdentity(CommandSender player) {
return player instanceof Player ? createIdentity((Player) player, null) : consoleIdentity();
}
@Override
public Identity consoleIdentity() {
return consoleIdentity;
}
@Override
public Identity createIdentity(Session session) {
return createIdentity(session.user(), session.nickname());
}
@Override
public Identity createIdentity(UserSearchResponse response) {
if(response.last_session != null) {
return createIdentity(response.user, response.last_session.nickname());
} else {
return createIdentity(response.user, null);
}
}
@Override
public Identity currentIdentity(CommandSender player) {
if(player instanceof Player) {
return currentIdentity(userStore.getUser((Player) player), (Player) player);
} else {
return consoleIdentity;
}
}
@Override
public Identity currentIdentity(PlayerId playerId) {
return currentIdentity(playerId, onlinePlayers.find(playerId));
}
private Identity currentIdentity(PlayerId playerId, @Nullable Player player) {
Identity identity = identities.get(player);
if(identity == null) {
identity = createIdentity(playerId, null);
if(player != null && player.willBeOnline()) {
identities.put(player, identity);
}
}
return identity;
}
@Override
public @Nullable Identity onlineIdentity(String name) {
Player player = nicknames.get(name);
if(player == null) player = onlinePlayers.find(name);
return player == null ? null : currentIdentity(player);
}
@Override
public void changeIdentity(Player player, @Nullable String nickname) {
final Identity oldIdentity = currentIdentity(player);
if(Objects.equals(oldIdentity.getNickname(), nickname)) return;
applyNickname(player, oldIdentity.getNickname(), nickname);
eventBus.callEvent(new PlayerIdentityChangeEvent(player, oldIdentity, currentIdentity(player)));
}
protected void validateNickname(@Nullable String nickname) {
if(nickname != null && onlinePlayers.find(nickname) != null) {
throw new IllegalArgumentException("Nickname '" + nickname + "' is the name of a real online player");
}
}
protected void applyNickname(Player player, @Nullable String oldNickname, @Nullable String newNickname) {
validateNickname(newNickname);
if(oldNickname != null) {
nicknames.remove(oldNickname);
}
final Identity identity = createIdentity(userStore.getUser(player), newNickname);
if(player.willBeOnline()) {
identities.put(player, identity);
if(newNickname != null) {
nicknames.put(newNickname, player);
}
}
}
/**
* If a player has a nickname set in their user document on login, apply it.
* This needs to run before {@link PlayerAppearanceListener#refreshNamesOnLogin}
*/
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void applyNicknameOnLogin(UserLoginEvent event) {
if(event.getUser().nickname() != null) {
applyNickname(event.getPlayer(), null, event.getUser().nickname());
}
}
/**
* Clean up after quitting players
*/
@EventHandler(priority = EventPriority.MONITOR)
public void deactivateNickOnQuit(PlayerQuitEvent event) {
final Identity identity = identities.remove(event.getPlayer());
if(identity != null && identity.getNickname() != null) {
nicknames.remove(identity.getNickname());
}
}
/**
* Clear any nickname that collides with the real name of a player logging in.
* This ensures that usernames + nicknames together contain no duplicates.
* The user who's nickname was cleared is not notified of this, but this
* should be an extremely rare situation, so it's not a big problem.
*/
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = false)
public void clearConflictingNicks(AsyncPlayerPreLoginEvent event) {
final String name = event.getName();
syncExecutor.execute(() -> {
final Player player = nicknames.get(name);
if(player != null) {
changeIdentity(player, null);
}
});
}
}