[BLEEDING][BREAKING] Store PlayerData by UUID, use a PlayerTickListener.

Instead of maps for each individual purpose, and the rather expensive
TickListener adding and removing, player specific task will be done via
one PlayerTickListener that can be registered with the TickTask. Thus
PlayerData has the access methods requestUpdateInventory and
requestPlayerSetBack, and so on, later more. For the
DataManager.playerData map it'll be UUID first now.

Consequently some calls have been altered to prefer passing Player or
UUID for PlayerData getting.
This commit is contained in:
asofold 2017-04-08 15:22:27 +02:00
parent 0cd0d508d1
commit 9a4b3f6f91
8 changed files with 268 additions and 205 deletions

View File

@ -84,7 +84,7 @@ public class FastConsume extends Check implements Listener, INotifyReload {
final long time = System.currentTimeMillis();
if (check(player, event.getItem(), time, data)){
event.setCancelled(true);
DataManager.getPlayerData(player).task.updateInventory();
DataManager.getPlayerData(player).requestUpdateInventory();
}
}

View File

@ -104,6 +104,7 @@ import fr.neatmonster.nocheatplus.logging.Streams;
import fr.neatmonster.nocheatplus.logging.debug.DebugUtil;
import fr.neatmonster.nocheatplus.permissions.Permissions;
import fr.neatmonster.nocheatplus.players.DataManager;
import fr.neatmonster.nocheatplus.players.PlayerData;
import fr.neatmonster.nocheatplus.stats.Counters;
import fr.neatmonster.nocheatplus.utilities.CheckUtils;
import fr.neatmonster.nocheatplus.utilities.PotionUtil;
@ -527,7 +528,6 @@ public class MovingListener extends CheckListener implements TickListener, IRemo
private boolean handleTeleportedOnMove(final Player player, final PlayerMoveEvent event,
final MovingData data, final MovingConfig cc) {
// This could also happen with a packet based set back such as with cancelling move events.
// TODO: Alter the move from location and let it get through?
if (data.isTeleportedPosition(event.getFrom())) {
// Treat as ACK (!).
// Adjust.
@ -538,8 +538,9 @@ public class MovingListener extends CheckListener implements TickListener, IRemo
}
return false;
}
else if (TickTask.isPlayerGoingToBeSetBack(player.getUniqueId())) {
else if (DataManager.getPlayerData(player).isPlayerSetBackScheduled()) {
// A set back has been scheduled, but the player is moving randomly.
// TODO: Instead alter the move from location and let it get through? +- when
event.setCancelled(true);
if (data.debug) {
debug(player, "Cancel move, due to a scheduled teleport (set back).");
@ -1448,16 +1449,16 @@ public class MovingListener extends CheckListener implements TickListener, IRemo
}
if (method.shouldSchedule()) {
// Schedule the teleport, because it might be faster than the next incoming packet.
final UUID playerId = player.getUniqueId();
if (!TickTask.isPlayerGoingToBeSetBack(playerId)) {
TickTask.requestPlayerSetBack(playerId);
final PlayerData pd = DataManager.getPlayerData(player);
if (pd.isPlayerSetBackScheduled()) {
debug(player, "Teleport (set back) already scheduled to: " + ref);
}
else if (mData.debug) {
pd.requestPlayerSetBack();
if (mData.debug) {
debug(player, "Schedule teleport (set back) to: " + ref);
}
}
else if (mData.debug) {
debug(player, "Teleport (set back) already scheduled to: " + ref);
}
}
// (Position adaption will happen with the teleport on tick, or with the next move.)
}
@ -1526,14 +1527,15 @@ public class MovingListener extends CheckListener implements TickListener, IRemo
if (mData.debug) {
debug(player, "Event not cancelled, with teleported (set back) set, assume legacy behavior.");
}
return;
}
else if (TickTask.isPlayerGoingToBeSetBack(player.getUniqueId())) {
else if (DataManager.getPlayerData(player).isPlayerSetBackScheduled()) {
// Skip, because the scheduled teleport has been overridden.
// TODO: Only do this, if cancel is set, because it is not an un-cancel otherwise.
if (mData.debug) {
debug(player, "Event not cancelled, despite a set back has been scheduled. Ignore set back.");
debug(player, "Event not cancelled, despite a set back has been scheduled. Cancel set back.");
}
mData.resetTeleported(); // (TickTask will notice it's not set.)
mData.resetTeleported(); // (PlayerTickListener will notice it's not set.)
}
else {
if (mData.debug) {
@ -1921,7 +1923,7 @@ public class MovingListener extends CheckListener implements TickListener, IRemo
debugTeleportMessage(player, event, "No target location (to) set.");
}
if (data.hasTeleported()) {
if (TickTask.isPlayerGoingToBeSetBack(player.getUniqueId())) {
if (DataManager.getPlayerData(player).isPlayerSetBackScheduled()) {
// Assume set back event following later.
event.setCancelled(true);
if (data.debug) {

View File

@ -32,6 +32,9 @@ import fr.neatmonster.nocheatplus.checks.moving.MovingData;
import fr.neatmonster.nocheatplus.checks.moving.magic.Magic;
import fr.neatmonster.nocheatplus.checks.moving.model.MoveData;
import fr.neatmonster.nocheatplus.checks.moving.model.PlayerMoveData;
import fr.neatmonster.nocheatplus.checks.moving.player.PlayerSetBackMethod;
import fr.neatmonster.nocheatplus.checks.net.NetData;
import fr.neatmonster.nocheatplus.checks.net.model.CountableLocation;
import fr.neatmonster.nocheatplus.compat.Bridge1_9;
import fr.neatmonster.nocheatplus.compat.BridgeMisc;
import fr.neatmonster.nocheatplus.compat.MCAccess;
@ -39,8 +42,8 @@ import fr.neatmonster.nocheatplus.components.debug.IDebugPlayer;
import fr.neatmonster.nocheatplus.hooks.NCPExemptionManager;
import fr.neatmonster.nocheatplus.logging.StaticLog;
import fr.neatmonster.nocheatplus.permissions.Permissions;
import fr.neatmonster.nocheatplus.players.DataManager;
import fr.neatmonster.nocheatplus.utilities.CheckUtils;
import fr.neatmonster.nocheatplus.utilities.TickTask;
import fr.neatmonster.nocheatplus.utilities.location.PlayerLocation;
import fr.neatmonster.nocheatplus.utilities.location.RichBoundsLocation;
import fr.neatmonster.nocheatplus.utilities.location.TrigUtil;
@ -411,8 +414,65 @@ public class MovingUtil {
* @return
*/
public static boolean hasScheduledPlayerSetBack(final UUID playerId, final MovingData data) {
return TickTask.isPlayerGoingToBeSetBack(playerId)
&& data.hasTeleported();
return data.hasTeleported() && DataManager.getPlayerData(playerId).isPlayerSetBackScheduled();
}
/**
*
* @param player
* @param debugMessagePrefix
* @return
*/
public static boolean processStoredSetBack(final Player player, final String debugMessagePrefix) {
final MovingData data = MovingData.getData(player);
if (!data.hasTeleported()) {
if (data.debug) {
CheckUtils.debug(player, CheckType.MOVING, debugMessagePrefix + "No stored location available.");
}
return false;
}
// (teleported is set.).
final Location loc = player.getLocation(useLoc);
if (data.isTeleportedPosition(loc)) {
// Skip redundant teleport.
if (data.debug) {
CheckUtils.debug(player, CheckType.MOVING, debugMessagePrefix + "Skip teleport, player is there, already.");
}
useLoc.setWorld(null);
return false;
}
useLoc.setWorld(null);
// (player is somewhere else.)
// TODO: Consider to skip checking for packet level, if not available (plus optimize access).
// TODO: Consider a config flag, so this can be turned off (set back method).
final PlayerSetBackMethod method = MovingConfig.getConfig(player).playerSetBackMethod;
if (method.shouldCancel() || method.shouldSetTo()) {
/*
* Another leniency option: Skip, if we have already received an
* ACK for this position on packet level.
*/
// (CANCEL + UPDATE_FROM mean a certain teleport to the set back, still could be repeated tp.)
final CountableLocation cl = ((NetData) CheckType.NET.getDataFactory().getData(player)).teleportQueue.getLastAck();
if (data.isTeleportedPosition(cl)) {
if (data.debug) {
CheckUtils.debug(player, CheckType.MOVING, debugMessagePrefix + "Skip teleport, having received an ACK for the teleport on packet level.");
}
return false;
}
}
// (No ACK received yet.)
final Location teleported = data.getTeleported();
// (Data resetting is done during PlayerTeleportEvent handling.)
if (player.teleport(teleported, BridgeMisc.TELEPORT_CAUSE_CORRECTION_OF_POSITION)) {
return true;
}
else {
if (data .debug) {
CheckUtils.debug(player, CheckType.MOVING, "Player set back on tick: Teleport failed.");
}
return false;
}
}
}

View File

@ -90,7 +90,7 @@ public class DataManager implements Listener, INeedConfig, ComponentRegistry<IRe
// TODO: Switch to UUIDs as keys, get data by uuid when possible, use PlayerMap for getting
/** PlayerData storage. */
private final Map<String, PlayerData> playerData = new LinkedHashMap<String, PlayerData>(100);
private final Map<UUID, PlayerData> playerData = new LinkedHashMap<UUID, PlayerData>(100);
/**
* Access order for playerName (exact) -> ms time of logout.
@ -671,8 +671,7 @@ public class DataManager implements Listener, INeedConfig, ComponentRegistry<IRe
* @return
*/
public static PlayerData getPlayerData(final UUID playerId, final String playerName, final boolean create) {
final String lcName = playerName.toLowerCase(); // TODO: Store by both lower case and exact case (also store the Player reference).
final PlayerData data = instance.playerData.get(lcName);
final PlayerData data = instance.playerData.get(playerId);
if (data != null) {
return data;
}
@ -681,18 +680,30 @@ public class DataManager implements Listener, INeedConfig, ComponentRegistry<IRe
}
else {
final PlayerData newData = new PlayerData(playerId, playerName);
instance.playerData.put(lcName, newData);
instance.playerData.put(playerId, newData);
return newData;
}
}
/**
* Get the player data, if present.
*
* @param playerName
* @return The PlayerData instance if present, null otherwise.
*/
public static PlayerData getPlayerData(final String playerName) {
return instance.playerData.get(playerName.toLowerCase());
final UUID playerId = getUUID(playerName);
return playerId == null ? null : instance.playerData.get(playerId);
}
/**
* Get the player data, if present.
*
* @param playerID
* @return The PlayerData instance if present, null otherwise.
*/
public static PlayerData getPlayerData(final UUID playerID) {
return instance.playerData.get(playerID);
}
/**

View File

@ -18,7 +18,11 @@ import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import org.bukkit.entity.Player;
import fr.neatmonster.nocheatplus.checks.moving.util.MovingUtil;
import fr.neatmonster.nocheatplus.components.data.IData;
import fr.neatmonster.nocheatplus.utilities.TickTask;
/**
* Central player data object.
@ -26,33 +30,81 @@ import fr.neatmonster.nocheatplus.components.data.IData;
* On the medium run this is intended to carry all data for the player...
* <li>Checks data objects.</li>
* <li>Time stamps for logged out players</li>
* <li>Data to be persisted, like set backs, xray.</li>
* <br>Might contain...
* <li>Data to be persisted, like set backs, xray.</li> <br>
* Might contain...
* <li>References of configs.</li>
* <li>Exemption entries.</li>
* <li>Player references<li>
* <li>Player references
* <li>
* <hr>
* Main reasons are...
* <li>Faster cross-check data access both for check and data management.</li>
* <li>Have the data in one place, easy to control and manage.</li>
* <li>Easier transition towards non-static access, if it should ever happen.</li>
* <li>Easier transition towards non-static access, if it should ever
* happen.</li>
* <hr>
* (not complete)<br>
* Might contain individual settings such as debug flags, exemption, notification settings, task references.
* Might contain individual settings such as debug flags, exemption,
* notification settings, task references.
*
* @author mc_dev
*
*/
public class PlayerData implements IData {
/**
* Interface for TickTask. Uses the UUID for hash and equals, equals accepts
* UUID instances as well.
*
* @author asofold
*
*/
public static final class PlayerTickListener {
private final PlayerData data;
private final int hashCode;
private PlayerTickListener(PlayerData data) {
this.data = data;
this.hashCode = data.playerId.hashCode();
}
/**
*
* @param tick
* @param timeLast
* @return True , if the listener is to be removed, false otherwise.
*/
public boolean processOnTick(int tick, long timeLast) {
return data.processOnTick(tick, timeLast);
}
@Override
public int hashCode() {
return hashCode;
}
@Override
public boolean equals(final Object obj) {
if (obj instanceof PlayerTickListener) {
return this.data.playerId.equals(((PlayerTickListener) obj).data.playerId);
}
else if (obj instanceof UUID) {
return this.data.playerId.equals((UUID) obj);
}
else {
return false;
}
}
}
// TODO: Prefer this data over an extra structure (PlayerMap)?
// TODO: Functionality of PlayerTask in here.
public static final String TAG_NOTIFY_OFF = "notify_off";
public final PlayerTask task;
/** Not sure this is the future of extra properties. */
protected Set<String> tags = null;
private Set<String> tags = null;
/** Unique id of the player. */
final UUID playerId;
@ -61,19 +113,54 @@ public class PlayerData implements IData {
/** Lower case name of the player. */
final String lcName;
private final PlayerTickListener playerTickListener;
private boolean requestUpdateInventory = false;
private boolean requestPlayerSetBack = false;
/**
*
* @param playerName Accurate case not (yet) demanded.
* @param playerName
* Accurate case not (yet) demanded.
*/
public PlayerData(final UUID playerId, final String playerName) {
this.playerId = playerId;
this.playerName = playerName;
this.lcName = playerName.toLowerCase();
this.task = new PlayerTask(playerId, playerName);
this.playerTickListener = new PlayerTickListener(this);
}
/**
* Run with TickTask.
* @param tick
* @param timeLast
* @return True, of the listener is to be removed, false otherwise.
*/
@SuppressWarnings("deprecation")
private boolean processOnTick(final int tick, final long timeLast) {
final Player player = DataManager.getPlayer(playerId);
if (player != null) { // Common criteria ...
if (requestPlayerSetBack) {
MovingUtil.processStoredSetBack(player, "Player set back on tick: ");
}
if (player.isOnline()) {
if (requestUpdateInventory) {
player.updateInventory();
}
} // (The player is online.)
} // (The player is not null.)
// Reset request flags.
requestPlayerSetBack = requestUpdateInventory = false;
return true;
}
private void registerPlayerTickListener() {
TickTask.addPlayerTickListener(playerTickListener);
}
/**
* Test if present.
*
* @param tag
* @return
*/
@ -83,6 +170,7 @@ public class PlayerData implements IData {
/**
* Add the tag.
*
* @param tag
*/
public void addTag(final String tag) {
@ -94,6 +182,7 @@ public class PlayerData implements IData {
/**
* Remove the tag.
*
* @param tag
*/
public void removeTag(final String tag) {
@ -107,8 +196,11 @@ public class PlayerData implements IData {
/**
* Add tag or remove tag, based on arguments.
*
* @param tag
* @param add The tag will be added, if set to true. If set to false, the tag will be removed.
* @param add
* The tag will be added, if set to true. If set to false, the
* tag will be removed.
*/
public void setTag(final String tag, final boolean add) {
if (add) {
@ -120,7 +212,9 @@ public class PlayerData implements IData {
}
/**
* Check if notifications are turned off, this does not bypass permission checks.
* Check if notifications are turned off, this does not bypass permission
* checks.
*
* @return
*/
public boolean getNotifyOff() {
@ -128,11 +222,41 @@ public class PlayerData implements IData {
}
/**
* Allow or turn off notifications. A player must have the admin.notify permission to receive notifications.
* @param notifyOff set to true to turn off notifications.
* Allow or turn off notifications. A player must have the admin.notify
* permission to receive notifications.
*
* @param notifyOff
* set to true to turn off notifications.
*/
public void setNotifyOff(final boolean notifyOff) {
setTag(TAG_NOTIFY_OFF, notifyOff);
}
/**
* Let the inventory be updated (run in TickTask).
*/
public void requestUpdateInventory() {
this.requestUpdateInventory = true;
registerPlayerTickListener();
}
/**
* Let the player be set back to the location stored in moving data (run in
* TickTask). Only applies if it's set there.
*/
public void requestPlayerSetBack() {
this.requestPlayerSetBack = true;
registerPlayerTickListener();
}
/**
* Test if it's set to process a player set back on tick. This does not
* check MovingData.hasTeleported().
*
* @return
*/
public boolean isPlayerSetBackScheduled() {
return this.requestPlayerSetBack && TickTask.isPlayerTiskListenerThere(this.playerTickListener);
}
}

View File

@ -1,88 +0,0 @@
/*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.neatmonster.nocheatplus.players;
import java.util.UUID;
import org.bukkit.entity.Player;
import fr.neatmonster.nocheatplus.utilities.OnDemandTickListener;
/**
* Player specific task.
* @author mc_dev
*
*/
public class PlayerTask extends OnDemandTickListener {
// TODO: Transform/remove (TickTask instead).
// TODO: Merge with player-specific TickTask logic - (e.g. store playerId-> PlayerTask there).
// TODO: Exact case name / rather !?
// TODO: Consider overriding some logic, because it is used in the main thread only (context: isRegisterd + register).
public final String name;
public final UUID id;
protected boolean updateInventory = false;
// protected boolean correctDirection = false;
/**
*
* @param id The unique id of the player.
* @param name Preferable the original exact case name.
*/
public PlayerTask(final UUID id, final String name) {
this.name = name;
this.id = id;
}
@SuppressWarnings("deprecation")
@Override
public boolean delegateTick(final int tick, final long timeLast) {
final Player player = DataManager.getPlayer(id);
if (player != null) {
if (player.isOnline()) {
// if (correctDirection) {
// final MCAccess access = NCPAPIProvider.getNoCheatPlusAPI().getMCAccess();
// access.correctDirection(player);
// }
if (updateInventory) {
player.updateInventory();
}
}
}
// Reset values (players logging back in should be fine or handled differently).
updateInventory = false;
// correctDirection = false;
return false;
}
public void updateInventory() {
// TODO: Might not allow registering every tick.
updateInventory = true;
register();
}
// public void correctDirection() {
// correctDirection = true;
// register();
// }
// TODO: updateHunger
}

View File

@ -15,6 +15,7 @@
package fr.neatmonster.nocheatplus.utilities;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
@ -26,7 +27,6 @@ import java.util.UUID;
import java.util.concurrent.locks.ReentrantLock;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
@ -34,15 +34,10 @@ import fr.neatmonster.nocheatplus.checks.CheckType;
import fr.neatmonster.nocheatplus.checks.ViolationData;
import fr.neatmonster.nocheatplus.checks.access.ICheckData;
import fr.neatmonster.nocheatplus.checks.combined.Improbable;
import fr.neatmonster.nocheatplus.checks.moving.MovingConfig;
import fr.neatmonster.nocheatplus.checks.moving.MovingData;
import fr.neatmonster.nocheatplus.checks.moving.player.PlayerSetBackMethod;
import fr.neatmonster.nocheatplus.checks.net.NetData;
import fr.neatmonster.nocheatplus.checks.net.model.CountableLocation;
import fr.neatmonster.nocheatplus.compat.BridgeMisc;
import fr.neatmonster.nocheatplus.components.registry.feature.TickListener;
import fr.neatmonster.nocheatplus.logging.StaticLog;
import fr.neatmonster.nocheatplus.players.DataManager;
import fr.neatmonster.nocheatplus.players.PlayerData.PlayerTickListener;
import fr.neatmonster.nocheatplus.utilities.ds.count.ActionFrequency;
// TODO: Auto-generated Javadoc
@ -134,8 +129,9 @@ public class TickTask implements Runnable {
/** Improbable entries to update. */
private static Map<UUID, ImprobableUpdateEntry> improbableUpdates = new LinkedHashMap<UUID, TickTask.ImprobableUpdateEntry>(50);
/** UUIDs of players who are to be set back. Primary thread only, so far. */
private static final Set<UUID> playerSetBackIds = new LinkedHashSet<UUID>(); // Could use a list, though.
/** PlayerTickListener instances, run player specific tasks on tick. */
// TODO: For thread-safe adding : another set under synchronization update under lock.
private static final Set<PlayerTickListener> playerTickListeners = new LinkedHashSet<PlayerTickListener>();
/** The Constant improbableLock. */
private static final ReentrantLock improbableLock = new ReentrantLock();
@ -277,26 +273,23 @@ public class TickTask implements Runnable {
}
/**
* Using the getTeleported() Location instance in MovingData, in order to
* set back the player on tick. This is for PlayerMoveEvent-like handling,
* not for vehicles. Primary thread only, so far.
*
* @param playerId
* Run player specific tasks on tick.
* @param playerTickListener
*/
public static void requestPlayerSetBack(final UUID playerId) {
public static void addPlayerTickListener(final PlayerTickListener playerTickListener) {
if (!locked) {
playerSetBackIds.add(playerId);
playerTickListeners.add(playerTickListener);
}
}
/**
* Test if a player set back is scheduled (MovingData).
* Test if a player specific task is scheduled.
*
* @param playerId
* @param playerTickListener
* @return
*/
public static boolean isPlayerGoingToBeSetBack(final UUID playerId) {
return playerSetBackIds.contains(playerId);
public static boolean isPlayerTiskListenerThere(final PlayerTickListener playerTickListener) {
return playerTickListeners.contains(playerTickListener);
}
/**
@ -633,7 +626,7 @@ public class TickTask implements Runnable {
tickListeners.clear();
}
if (Bukkit.isPrimaryThread()) {
playerSetBackIds.clear();
playerTickListeners.clear();
}
}
@ -654,9 +647,6 @@ public class TickTask implements Runnable {
// Instance methods (meant private).
/** Temporary use only, beware of nesting. Cleanup with setWorld(null). */
private final Location useLoc = new Location(null, 0, 0, 0);
/**
*
* Notify all listeners. A copy of the listeners under lock, then processed without lock. Theoretically listeners can get processed though they have already been unregistered.
@ -687,62 +677,21 @@ public class TickTask implements Runnable {
}
}
private void processPlayerSetBackIds() {
// TODO: Might design as a permanent tick listener (access API elsewhere) to minimize TickListener.
for (final UUID id : playerSetBackIds) {
final Player player = DataManager.getPlayer(id);
if (player == null) {
// (Should be intercepted elsewhere, e.g. on quit/kick.)
continue;
}
final MovingData data = MovingData.getData(player);
if (!data.hasTeleported()) {
if (data.debug) {
CheckUtils.debug(player, CheckType.MOVING, "Player set back on tick: No stored location available.");
}
continue;
}
// (teleported is set.).
final Location loc = player.getLocation(useLoc);
if (data.isTeleportedPosition(loc)) {
// Skip redundant teleport.
if (data.debug) {
CheckUtils.debug(player, CheckType.MOVING, "Player set back on tick: Skip teleport, player is there, already.");
}
continue;
}
// (player is somewhere else.)
// TODO: Consider to skip packet level, if not available (plus optimize access to the information).
final PlayerSetBackMethod method = MovingConfig.getConfig(player).playerSetBackMethod;
if (method.shouldCancel() || method.shouldSetTo()) {
/*
* Another leniency option: Skip, if we have already received an
* ACK for this position on packet level.
*/
// (CANCEL + UPDATE_FROM mean a certain teleport to the set back, still could be repeated tp.)
final CountableLocation cl = ((NetData) CheckType.NET.getDataFactory().getData(player)).teleportQueue.getLastAck();
if (data.isTeleportedPosition(cl)) {
if (data.debug) {
CheckUtils.debug(player, CheckType.MOVING, "Player set back on tick: Skip teleport, having received an ACK for the teleport on packet level.");
}
continue;
private void processPlayerTickListeners(final int tick, final long timeLast) {
// TODO: Not sure: Make a copy list, clear original. Add to original if to stay. [Concurrent modification.]
final Iterator<PlayerTickListener> it = playerTickListeners.iterator();
while (it.hasNext()) {
final PlayerTickListener listener = it.next();
try {
if (listener.processOnTick(tick, timeLast)) {
it.remove();
}
}
// (No ACK received yet.)
final Location teleported = data.getTeleported();
if (!player.teleport(teleported, BridgeMisc.TELEPORT_CAUSE_CORRECTION_OF_POSITION)) {
if (data .debug) {
CheckUtils.debug(player, CheckType.MOVING, "Player set back on tick: Teleport failed.");
}
catch (Throwable t) {
StaticLog.logSevere("(TickTask) PlayerTickListener generated an exception:");
StaticLog.logSevere(t);
}
// Cleanup.
useLoc.setWorld(null);
// (Data resetting is done during PlayerTeleportEvent handling.)
}
// (There could be ids kept on errors !?)
playerSetBackIds.clear();
}
/* (non-Javadoc)
@ -755,8 +704,8 @@ public class TickTask implements Runnable {
// Actions.
executeActions();
// Set back (after actions, for now, because actions may contain a set back action later on).
if (!playerSetBackIds.isEmpty()) {
processPlayerSetBackIds();
if (!playerTickListeners.isEmpty()) {
processPlayerTickListeners(tick, timeLast);
}
// Permissions.
updatePermissions();

View File

@ -353,11 +353,16 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
}
}
@Deprecated
private boolean hasTurnedOffNotifications(final String playerName) {
final PlayerData data = DataManager.getPlayerData(playerName);
return data != null && data.getNotifyOff();
}
private boolean hasTurnedOffNotifications(final Player player) {
return DataManager.getPlayerData(player).getNotifyOff();
}
/**
* Send notification to players with stored notify-permission (world changes, login, permissions are not re-checked here).
* @param message
@ -393,7 +398,7 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
for (final Permissible permissible : permissibles) {
if (permissible instanceof CommandSender && permissible.hasPermission(Permissions.NOTIFY)) {
final CommandSender sender = (CommandSender) permissible;
if ((sender instanceof Player) && hasTurnedOffNotifications(((Player) sender).getName())) {
if ((sender instanceof Player) && hasTurnedOffNotifications((Player) sender)) {
continue;
}
@ -407,7 +412,7 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
if (!done.contains(name)) {
final Player player = DataManager.getPlayerExact(name);
if (player != null && player.hasPermission(Permissions.NOTIFY)) {
if (hasTurnedOffNotifications(player.getName())) {
if (hasTurnedOffNotifications(player)) {
continue;
}
player.sendMessage(message);
@ -1357,7 +1362,7 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
final String playerName = player.getName();
if (nameSetPerms.hasPermission(playerName, Permissions.NOTIFY)) {
// Login notifications...
final PlayerData data = DataManager.getPlayerData(player.getUniqueId(), playerName, true);
final PlayerData data = DataManager.getPlayerData(player);
// // Update available.
// if (updateAvailable) player.sendMessage(ChatColor.RED + "NCP: " + ChatColor.WHITE + "A new update of NoCheatPlus is available.\n" + "Download it at http://nocheatplus.org/update");