Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 19 additions & 7 deletions buildSrc/src/main/kotlin/buildlogic.java-conventions.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,32 @@ java {

repositories {
mavenCentral()
maven("https://repo.pgm.fyi/snapshots") // Sportpaper & other pgm-specific stuff
maven("https://repo.papermc.io/repository/maven-public/") // PaperMC repo
maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") // Spigot repo
maven("https://repo.aikar.co/content/groups/aikar/") // aikar repo
maven("https://repo.pgm.fyi/snapshots") // SportPaper & other PGM-specific stuff
maven("https://repo.papermc.io/repository/maven-public/") // Paper builds & paperweight plugin
maven("https://repo.aikar.co/content/groups/aikar/") // Aikar repo
maven("https://repo.codemc.io/repository/maven-releases/") // PacketEvents
exclusiveContent {
forRepository {
maven("https://jitpack.io")
}
filter {
includeGroup("com.github.OvercastCommunity.adventure-platform")
includeGroup("com.github.MinusKube")
}
}
mavenLocal() // Local last
}

dependencies {
api("com.zaxxer:HikariCP:2.4.1") { isTransitive = false }
api("fr.minuskube.inv:smart-invs:1.2.7") { isTransitive = false }
// Latest SmartInvs commit
api("com.github.MinusKube:SmartInvs:9c9dbbee16") { isTransitive = false }
api("redis.clients:jedis:3.5.1")
api("net.kyori:adventure-api:4.26.1")
api("net.kyori:adventure-text-serializer-plain:4.26.1")
api("net.kyori:adventure-platform-bukkit:4.4.1")
// adventure-platform fork with ViaVersion and 1.21.11+ fixes
// https://github.com/OvercastCommunity/adventure-platform
api("com.github.OvercastCommunity.adventure-platform:adventure-platform-bukkit:04de657e85")
api("org.reflections:reflections:0.10.2")

// Annotations
Expand All @@ -37,7 +49,7 @@ dependencies {
compileOnly("tc.oc.pgm:util:0.16-SNAPSHOT")
compileOnly("tc.oc.occ:Environment:1.0.0-SNAPSHOT")
compileOnly("org.incendo:cloud-annotations:2.0.0")
compileOnly("net.dmulloy2:ProtocolLib:5.4.0")
compileOnly("com.github.retrooper:packetevents-spigot:2.12.0")

// Paper and SportPaper include these (or equivalents)
compileOnly("it.unimi.dsi:fastutil:8.5.15")
Expand Down
10 changes: 8 additions & 2 deletions core/src/main/java/dev/pgm/community/Community.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package dev.pgm.community;

import static dev.pgm.community.nick.identity.PlayerIdentity.PLAYER_IDENTITY;

import dev.pgm.community.commands.graph.CommunityCommandGraph;
import dev.pgm.community.events.CommunityEvent;
import dev.pgm.community.feature.FeatureManager;
Expand Down Expand Up @@ -51,16 +53,21 @@ public void onEnable() {
} catch (Throwable t) {
getLogger().log(Level.SEVERE, "Failed to initialize Community platform", t);
getServer().getPluginManager().disablePlugin(this);
return;
}

Platform.MANIFEST.onEnable(this);

this.setupConfig();
getLogger().info(dev.pgm.community.database.DatabaseExecutor.describeBackend());
this.setupFeatures();
}

@Override
public void onDisable() {
features.disable();
Platform.MANIFEST.onDisable();
if (features != null) features.disable();
PLAYER_IDENTITY.clearAll();
dev.pgm.community.database.DatabaseExecutor.shutdown();
}

Expand Down Expand Up @@ -97,7 +104,6 @@ private void setupTranslations() {
}

public void registerListener(Listener listener) {
Platform.MANIFEST.onEnable(this);
getServer().getPluginManager().registerEvents(listener, this);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package dev.pgm.community.nick.feature;

import static dev.pgm.community.nick.identity.PlayerIdentity.PLAYER_IDENTITY;
import static net.kyori.adventure.text.Component.text;

import dev.pgm.community.utils.PGMUtils;
Expand All @@ -10,11 +11,13 @@
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration;
import org.bukkit.entity.Player;
import org.jspecify.annotations.NonNull;
import tc.oc.pgm.api.PGM;
import tc.oc.pgm.api.integration.Integration;
import tc.oc.pgm.api.integration.NickIntegration;
import tc.oc.pgm.api.match.Match;
import tc.oc.pgm.api.player.MatchPlayer;
import tc.oc.pgm.util.skin.Skin;

public class PGMNickIntegration implements NickIntegration {

Expand All @@ -36,6 +39,11 @@ public String getNick(Player player) {
return nick.getOnlineNick(player.getUniqueId());
}

@Override
public Skin getPlayerSkin(@NonNull Player player, Player viewer) {
return PLAYER_IDENTITY.getSkin(player, viewer);
}

public void cancelTask() {
hotbarTask.cancel(true);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,21 @@ public SkinManager getSkinManager() {
@Override
public void enable() {
super.enable();
skins.enable();
integrate();
}

@Override
public void disable() {
if (pgmNicks != null) {
pgmNicks.cancelTask();
pgmNicks = null;
}

skins.disable();
super.disable();
}

private void integrate() {
if (isPGMEnabled()) {
pgmNicks = new PGMNickIntegration(this);
Expand Down Expand Up @@ -183,12 +195,12 @@ public void onJoin(PlayerJoinEvent event) {
.thenAcceptAsync(name -> this.setNick(player.getUniqueId(), name)
.thenAcceptAsync(success -> {
if (success) {
nickedPlayers.put(player.getUniqueId(), nick.getName());
nickedPlayers.put(player.getUniqueId(), name);
Audience.get(player)
.sendWarning(text(
"You had no nickname, so a random one has been assigned",
NamedTextColor.GREEN));
sendLoginNotification(player, nick.getName(), true);
sendLoginNotification(player, name, true);
}
}));
}
Expand Down
216 changes: 216 additions & 0 deletions core/src/main/java/dev/pgm/community/nick/identity/PlayerIdentity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
package dev.pgm.community.nick.identity;

import static dev.pgm.community.util.PlayerUtils.PLAYER_UTILS;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.UUID;
import org.bukkit.entity.Player;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import tc.oc.pgm.util.skin.Skin;

@NullMarked
public final class PlayerIdentity {
public static final int MAX_NICK_LENGTH = 16;
public static final PlayerIdentity PLAYER_IDENTITY = new PlayerIdentity();

private final Map<UUID, Map<UUID, Skin>> playerSkins = new HashMap<>();
private final Map<UUID, Map<UUID, String>> playerNames = new HashMap<>();
private final Map<UUID, Map<UUID, String>> playerDisplayNames = new HashMap<>();
private final Map<UUID, Map<String, HashSet<String>>> viewerTeamEntries = new HashMap<>();
private final Map<UUID, Map<String, String>> viewerVisibleNames = new HashMap<>();

private PlayerIdentity() {}

public synchronized void set(
Player player,
Player viewer,
@Nullable String displayName,
@Nullable String nick,
@Nullable Skin skin) {
validateNick(nick);

UUID playerId = player.getUniqueId();
UUID viewerId = viewer.getUniqueId();

set(playerSkins, playerId, viewerId, skin);
set(playerNames, playerId, viewerId, nick);
set(playerDisplayNames, playerId, viewerId, displayName);
setVisibleName(viewerId, player.getName(), getName(player, viewer));
}

private static <T> void set(
Map<UUID, Map<UUID, T>> identities, UUID playerId, UUID viewerId, @Nullable T value) {
if (value == null) {
Map<UUID, T> viewers = identities.get(playerId);
if (viewers == null) return;

viewers.remove(viewerId);
if (viewers.isEmpty()) identities.remove(playerId);
return;
}

identities.computeIfAbsent(playerId, k -> new HashMap<>()).put(viewerId, value);
}

public static void validateNick(@Nullable String name) {
if (name != null && name.length() > MAX_NICK_LENGTH) {
throw new IllegalArgumentException(
"Player nick names are limited to " + MAX_NICK_LENGTH + " characters in length");
}
}

public synchronized void clearPlayer(UUID playerId, String realName) {
playerSkins.remove(playerId);
playerNames.remove(playerId);
playerDisplayNames.remove(playerId);
viewerVisibleNames.values().removeIf(visibleNames -> {
visibleNames.remove(realName);
return visibleNames.isEmpty();
});
}

public synchronized void clearViewer(UUID viewerId) {
clearViewer(playerSkins, viewerId);
clearViewer(playerNames, viewerId);
clearViewer(playerDisplayNames, viewerId);
viewerTeamEntries.remove(viewerId);
viewerVisibleNames.remove(viewerId);
}

private static <T> void clearViewer(Map<UUID, Map<UUID, T>> identities, UUID viewerId) {
identities.values().removeIf(viewers -> {
viewers.remove(viewerId);
return viewers.isEmpty();
});
}

public synchronized void clearAll() {
playerSkins.clear();
playerNames.clear();
playerDisplayNames.clear();
viewerTeamEntries.clear();
viewerVisibleNames.clear();
}

public synchronized @Nullable String getVisibleName(UUID viewerId, String realName) {
Map<String, String> visibleNames = viewerVisibleNames.get(viewerId);
return visibleNames == null ? null : visibleNames.get(realName);
}

private void setVisibleName(UUID viewerId, String realName, String visibleName) {
if (realName.equals(visibleName)) {
clearVisibleName(viewerId, realName);
return;
}

viewerVisibleNames.computeIfAbsent(viewerId, k -> new HashMap<>()).put(realName, visibleName);
}

private void clearVisibleName(UUID viewerId, String realName) {
Map<String, String> visibleNames = viewerVisibleNames.get(viewerId);
if (visibleNames == null) return;

visibleNames.remove(realName);
if (visibleNames.isEmpty()) viewerVisibleNames.remove(viewerId);
}

public synchronized @Nullable String getTeamName(UUID viewerId, String entry) {
Map<String, HashSet<String>> teamEntries = viewerTeamEntries.get(viewerId);
if (teamEntries == null) return null;

for (Map.Entry<String, HashSet<String>> team : teamEntries.entrySet()) {
if (team.getValue().contains(entry)) return team.getKey();
}

return null;
}

public synchronized boolean hasTeamEntry(UUID viewerId, String teamName, String entry) {
Map<String, HashSet<String>> teamEntries = viewerTeamEntries.get(viewerId);
if (teamEntries == null) return false;

HashSet<String> entries = teamEntries.get(teamName);
return entries != null && entries.contains(entry);
}

public synchronized void createTeam(UUID viewerId, String teamName, Collection<String> entries) {
removeTeam(viewerId, teamName);
addTeamEntries(viewerId, teamName, entries);
}

public synchronized void removeTeam(UUID viewerId, String teamName) {
Map<String, HashSet<String>> teamEntries = viewerTeamEntries.get(viewerId);
if (teamEntries == null) return;

teamEntries.remove(teamName);
if (teamEntries.isEmpty()) viewerTeamEntries.remove(viewerId);
}

public synchronized void addTeamEntries(
UUID viewerId, String teamName, Collection<String> entries) {
if (entries.isEmpty()) return;

Map<String, HashSet<String>> teamEntries =
viewerTeamEntries.computeIfAbsent(viewerId, k -> new HashMap<>());
teamEntries.values().forEach(team -> team.removeAll(entries));
teamEntries.values().removeIf(Collection::isEmpty);
teamEntries.computeIfAbsent(teamName, k -> new HashSet<>()).addAll(entries);
}

public synchronized void removeTeamEntries(
UUID viewerId, String teamName, Collection<String> entries) {
Map<String, HashSet<String>> teamEntries = viewerTeamEntries.get(viewerId);
if (teamEntries == null) return;

HashSet<String> team = teamEntries.get(teamName);
if (team == null) return;

team.removeAll(entries);
if (team.isEmpty()) teamEntries.remove(teamName);
if (teamEntries.isEmpty()) viewerTeamEntries.remove(viewerId);
}

public synchronized String getDisplayName(Player player, Player viewer) {
String displayName = get(playerDisplayNames, player.getUniqueId(), viewer.getUniqueId());
if (displayName != null) return displayName;

return player.getDisplayName();
}

public synchronized boolean hasDisplayName(Player player, Player viewer) {
return get(playerDisplayNames, player.getUniqueId(), viewer.getUniqueId()) != null;
}

public synchronized String getName(Player player, Player viewer) {
String name = get(playerNames, player.getUniqueId(), viewer.getUniqueId());
if (name != null) return name;

return player.getName();
}

public synchronized boolean hasName(Player player, Player viewer) {
return get(playerNames, player.getUniqueId(), viewer.getUniqueId()) != null;
}

public synchronized Skin getSkin(Player player, Player viewer) {
Skin skin = get(playerSkins, player.getUniqueId(), viewer.getUniqueId());
if (skin != null && !skin.isEmpty()) return skin;

return PLAYER_UTILS.getPlayerSkin(player);
}

public synchronized boolean hasSkin(Player player, Player viewer) {
Skin skin = get(playerSkins, player.getUniqueId(), viewer.getUniqueId());
return skin != null && !skin.isEmpty();
}

private static <T> @Nullable T get(
Map<UUID, Map<UUID, T>> identities, UUID playerId, UUID viewerId) {
Map<UUID, T> viewers = identities.get(playerId);
return viewers == null ? null : viewers.get(viewerId);
}
}
Loading
Loading