From 6bddb1a83448644fae11ae095d828d5234c77c12 Mon Sep 17 00:00:00 2001 From: asofold Date: Fri, 20 Apr 2018 11:49:22 +0200 Subject: [PATCH] Optimistic player data creation. Updating of world / player name. (+) Create PlayerData instances if events allow proceeding: * AsyncPlayerPreLogin. * PlayerLogin (schedule for removal if denied). Update world data: * PlayerLogin * (PlayerJoin, ...) Update player name and log: * PlayerLogin * PlayerJoin (+) PlayerData.updateCurrentWorld -> only do something if the WorldData instance has changed. --- .../nocheatplus/players/PlayerData.java | 22 ++-- .../players/PlayerDataManager.java | 101 +++++++++++++++++- .../neatmonster/nocheatplus/NoCheatPlus.java | 2 +- 3 files changed, 114 insertions(+), 11 deletions(-) diff --git a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/players/PlayerData.java b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/players/PlayerData.java index 15d260aa..45f3b99d 100644 --- a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/players/PlayerData.java +++ b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/players/PlayerData.java @@ -112,7 +112,10 @@ public class PlayerData implements IPlayerData { private static float heavyLoad = 500000f / (float) ticksMonitored; // Default tags. + // TODO: Move elsewhere (API?) public static final String TAG_NOTIFY_OFF = "notify_off"; + /** Optimistic player data creation. */ + public static final String TAG_OPTIMISTIC_CREATE = "optimistic_create"; private static final short frequentTaskLazyDefaultDelay = 10; private static final short frequentTaskUnregisterDefaultDelay = 2; @@ -142,9 +145,9 @@ public class PlayerData implements IPlayerData { // TODO: Names could/should get updated. (In which case?) /** Exact case name of the player. */ - private final String playerName; + private String playerName; /** Lower case name of the player. */ - private final String lcName; + private String playerNameLowerCase; private long lastJoinTime = 0; @@ -195,10 +198,15 @@ public class PlayerData implements IPlayerData { final PermissionRegistry permissionRegistry) { this.playerId = playerId; this.playerName = playerName; - this.lcName = playerName.toLowerCase(); + this.playerNameLowerCase = playerName.toLowerCase(); this.permissionRegistry = permissionRegistry; } + void updatePlayerName(final String exactPlayerName) { + this.playerName = exactPlayerName; + this.playerNameLowerCase = exactPlayerName.toLowerCase(); + } + /** * Run with DataManager frequent tasks (each tick). * @@ -486,8 +494,10 @@ public class PlayerData implements IPlayerData { */ void updateCurrentWorld(final IWorldData worldData) { // TODO: Consider storing last world too. - currentWorldData = worldData; - checkTypeTree.getNode(CheckType.ALL).updateDebug(worldData); + if (currentWorldData != worldData) { + currentWorldData = worldData; + checkTypeTree.getNode(CheckType.ALL).updateDebug(worldData); + } } private void invalidateOffline() { @@ -627,7 +637,7 @@ public class PlayerData implements IPlayerData { @Override public String getPlayerNameLowerCase() { - return lcName; + return playerNameLowerCase; } @Override diff --git a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/players/PlayerDataManager.java b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/players/PlayerDataManager.java index 9bcab596..77e0e711 100644 --- a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/players/PlayerDataManager.java +++ b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/players/PlayerDataManager.java @@ -33,9 +33,11 @@ import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; +import org.bukkit.event.player.AsyncPlayerPreLoginEvent; import org.bukkit.event.player.PlayerChangedWorldEvent; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerKickEvent; +import org.bukkit.event.player.PlayerLoginEvent; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.world.WorldUnloadEvent; @@ -219,6 +221,31 @@ public class PlayerDataManager implements IPlayerDataManager, ComponentWithName playerLeaves(event.getPlayer()); } }, + new MiniListener() { + @EventHandler(priority = EventPriority.MONITOR) + @RegisterMethodWithOrder(tag = "system.nocheatplus.datamanager", beforeTag = ".*") + @Override + public void onEvent(final AsyncPlayerPreLoginEvent event) { + // TODO: Maintain a flag for precondition (e.g. ProtocolLib present). + if (event.getLoginResult() == AsyncPlayerPreLoginEvent.Result.ALLOWED) { + onAsyncPlayerPreLogin(event); + } + } + }, + new MiniListener() { + @EventHandler(priority = EventPriority.MONITOR) + @RegisterMethodWithOrder(tag = "system.nocheatplus.datamanager", beforeTag = ".*") + @Override + public void onEvent(final PlayerLoginEvent event) { + // TODO: Maintain a flag for precondition (e.g. ProtocolLib present). + if (event.getResult() == PlayerLoginEvent.Result.ALLOWED) { + onPlayerLogin(event); + } + else { + onPlayerLoginDenied(event); + } + } + }, new MiniListener() { @EventHandler(priority = EventPriority.LOWEST) @RegisterMethodWithOrder(tag = "system.nocheatplus.datamanager", beforeTag = ".*") @@ -555,6 +582,70 @@ public class PlayerDataManager implements IPlayerDataManager, ComponentWithName playerMap.remove(player); } + /** + * Ensure a PlayerData instance exists for later use. + * + * @param event + */ + private void onAsyncPlayerPreLogin(final AsyncPlayerPreLoginEvent event) { + final UUID playerId = event.getUniqueId(); // Treat carefully :). + if (playerData.containsKey(playerId)) { + // Skip if a PlayerData instance already exists. + return; + } + else { + // Create with default world data. + getPlayerData(playerId, event.getName(), true, worldDataManager.getDefaultWorldData()).addTag(PlayerData.TAG_OPTIMISTIC_CREATE); + } + } + + private void onPlayerLoginDenied(final PlayerLoginEvent event) { + // Consistency check existing data. + final UUID playerId = event.getPlayer().getUniqueId(); + final PlayerData pData = getPlayerData(playerId); + if (pData != null && pData.hasTag(PlayerData.TAG_OPTIMISTIC_CREATE)) { + bulkPlayerDataRemoval.add(playerId); + } + } + + /** + * Just update the world data for later use. + * + * @param event + */ + private void onPlayerLogin(final PlayerLoginEvent event) { + // Consistency check existing data. + final Player player = event.getPlayer(); + final UUID playerId = player.getUniqueId(); + final PlayerData pData = getPlayerData(playerId); + if (pData == null) { + // Create an instance. + // TODO: Legacy server compatibility with world getting? + getPlayerData(player); + } + else { + // Consistency check. + final String playerName = pData.getPlayerName(); + if (!playerName.equals(player.getName())) { + updatePlayerName(playerId, playerName, pData, "login"); + } + // Update world. + pData.updateCurrentWorld(worldDataManager.getWorldData(player.getWorld())); + } + pData.removeTag(PlayerData.TAG_OPTIMISTIC_CREATE); + } + + private void updatePlayerName(final UUID playerId, final String playerName, + final PlayerData pData, String tag) { + // Name change. + pData.updatePlayerName(playerName); + NCPAPIProvider.getNoCheatPlusAPI().getLogManager().info(Streams.STATUS, + CheckUtils.getLogMessagePrefix(playerName, null) + + " Update player name for id " + playerId + ": " + playerName + + "(" + tag + (pData.hasTag(PlayerData.TAG_OPTIMISTIC_CREATE) ? + ", optimistically created data" : "") + ")"); + } + private void playerJoins(final PlayerJoinEvent event) { final long timeNow = System.currentTimeMillis(); final Player player = event.getPlayer(); @@ -564,10 +655,12 @@ public class PlayerDataManager implements IPlayerDataManager, ComponentWithName addOnlinePlayer(player); // final PlayerData pData = getPlayerData(player, true); - /* - * TODO: Now we update the world already with getPlayerData, in case - * it's just been created... - */ + // Consistency check. + final String playerName = pData.getPlayerName(); + if (!playerName.equals(player.getName())) { + updatePlayerName(playerId, playerName, pData, "login"); + } + // Data stuff. final Collection> types = factoryRegistry.getGroupedTypes(IDataOnJoin.class); pData.onPlayerJoin(player, player.getWorld(), timeNow, worldDataManager, types); pData.getGenericInstance(CombinedData.class).lastJoinTime = timeNow; diff --git a/NCPPlugin/src/main/java/fr/neatmonster/nocheatplus/NoCheatPlus.java b/NCPPlugin/src/main/java/fr/neatmonster/nocheatplus/NoCheatPlus.java index 168d5f94..a2eacf58 100644 --- a/NCPPlugin/src/main/java/fr/neatmonster/nocheatplus/NoCheatPlus.java +++ b/NCPPlugin/src/main/java/fr/neatmonster/nocheatplus/NoCheatPlus.java @@ -1278,7 +1278,7 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI { } final Player player = event.getPlayer(); // Check if login is denied (plus expiration check). - // TODO: Store by id + HashMapLOW. + // TODO: Store by id + HashMapLOW + AsyncPlayerPreLogin. if (checkDenyLoginsNames(player.getName())) { if (DataManager.getPlayerData(player).hasPermission(Permissions.BYPASS_DENY_LOGIN, player)) { return;