mirror of
https://github.com/CitizensDev/Citizens2.git
synced 2025-02-27 17:52:03 +01:00
Fix NPE
This commit is contained in:
parent
7ff746a5b6
commit
ec3b184f72
@ -196,7 +196,6 @@ public class CitizensNPC extends AbstractNPC {
|
||||
Bukkit.getScheduler().scheduleSyncDelayedTask(CitizensAPI.getPlugin(), new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
if (getEntity() == null || !getEntity().isValid())
|
||||
return;
|
||||
|
||||
|
@ -4,11 +4,11 @@ package net.citizensnpcs.npc.profile;
|
||||
* Interface for a subscriber of the results of a profile fetch.
|
||||
*/
|
||||
public interface ProfileFetchHandler {
|
||||
|
||||
/**
|
||||
* Invoked when a result for a profile is ready.
|
||||
*
|
||||
* @param request The profile request that was handled.
|
||||
* @param request
|
||||
* The profile request that was handled.
|
||||
*/
|
||||
void onResult(ProfileRequest request);
|
||||
}
|
||||
|
@ -6,37 +6,40 @@ import java.util.Deque;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
import net.citizensnpcs.api.CitizensAPI;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
/**
|
||||
* Thread used to fetch profiles from the Mojang servers.
|
||||
*
|
||||
* <p>Maintains a cache of profiles so that no profile is ever requested more than once
|
||||
* during a single server session.</p>
|
||||
* <p>
|
||||
* Maintains a cache of profiles so that no profile is ever requested more than once during a single server session.
|
||||
* </p>
|
||||
*
|
||||
* @see ProfileFetcher
|
||||
*/
|
||||
class ProfileFetchThread implements Runnable {
|
||||
|
||||
private final ProfileFetcher profileFetcher = new ProfileFetcher();
|
||||
private final Deque<ProfileRequest> queue = new ArrayDeque<ProfileRequest>();
|
||||
private final Map<String, ProfileRequest> requested = new HashMap<String, ProfileRequest>(35);
|
||||
private final Object sync = new Object(); // sync for queue & requested fields
|
||||
|
||||
ProfileFetchThread() {}
|
||||
ProfileFetchThread() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a profile.
|
||||
*
|
||||
* @param name The name of the player the profile belongs to.
|
||||
* @param handler Optional handler to handle result fetch result.
|
||||
* Handler always invoked from the main thread.
|
||||
* @param name
|
||||
* The name of the player the profile belongs to.
|
||||
* @param handler
|
||||
* Optional handler to handle result fetch result. Handler always invoked from the main thread.
|
||||
*
|
||||
* @see ProfileFetcher#fetch
|
||||
*/
|
||||
@ -60,8 +63,7 @@ class ProfileFetchThread implements Runnable {
|
||||
|
||||
if (request.getResult() == ProfileFetchResult.PENDING) {
|
||||
addHandler(request, handler);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
sendResult(handler, request);
|
||||
}
|
||||
}
|
||||
@ -69,47 +71,34 @@ class ProfileFetchThread implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
List<ProfileRequest> requests;
|
||||
|
||||
synchronized (sync) {
|
||||
|
||||
if (queue.isEmpty())
|
||||
return;
|
||||
|
||||
requests = new ArrayList<ProfileRequest>(queue);
|
||||
|
||||
queue.clear();
|
||||
}
|
||||
|
||||
profileFetcher.fetchRequests(requests);
|
||||
}
|
||||
|
||||
private static void sendResult(final ProfileFetchHandler handler,
|
||||
final ProfileRequest request) {
|
||||
|
||||
Bukkit.getScheduler().scheduleSyncDelayedTask(CitizensAPI.getPlugin(),
|
||||
new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
handler.onResult(request);
|
||||
}
|
||||
}, 1);
|
||||
private static void addHandler(final ProfileRequest request, final ProfileFetchHandler handler) {
|
||||
Bukkit.getScheduler().scheduleSyncDelayedTask(CitizensAPI.getPlugin(), new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
request.addHandler(handler);
|
||||
}
|
||||
}, 1);
|
||||
}
|
||||
|
||||
private static void addHandler(final ProfileRequest request,
|
||||
final ProfileFetchHandler handler) {
|
||||
|
||||
Bukkit.getScheduler().scheduleSyncDelayedTask(CitizensAPI.getPlugin(),
|
||||
new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
request.addHandler(handler);
|
||||
}
|
||||
}, 1);
|
||||
private static void sendResult(final ProfileFetchHandler handler, final ProfileRequest request) {
|
||||
Bukkit.getScheduler().scheduleSyncDelayedTask(CitizensAPI.getPlugin(), new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
handler.onResult(request);
|
||||
}
|
||||
}, 1);
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,11 @@
|
||||
package net.citizensnpcs.npc.profile;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.mojang.authlib.Agent;
|
||||
import com.mojang.authlib.GameProfile;
|
||||
@ -13,39 +16,20 @@ import net.citizensnpcs.api.CitizensAPI;
|
||||
import net.citizensnpcs.api.util.Messaging;
|
||||
import net.citizensnpcs.util.NMS;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
/**
|
||||
* Fetches game profiles that include skin data from Mojang servers.
|
||||
*
|
||||
* @see ProfileFetchThread
|
||||
*/
|
||||
public class ProfileFetcher {
|
||||
|
||||
/**
|
||||
* Fetch a profile.
|
||||
*
|
||||
* @param name The name of the player the profile belongs to.
|
||||
* @param handler Optional handler to handle the result.
|
||||
* Handler always invoked from the main thread.
|
||||
*/
|
||||
public static void fetch(String name, @Nullable ProfileFetchHandler handler) {
|
||||
Preconditions.checkNotNull(name);
|
||||
|
||||
if (PROFILE_THREAD == null) {
|
||||
PROFILE_THREAD = new ProfileFetchThread();
|
||||
Bukkit.getScheduler().runTaskTimerAsynchronously(CitizensAPI.getPlugin(), PROFILE_THREAD,
|
||||
11, 20);
|
||||
}
|
||||
PROFILE_THREAD.fetch(name, handler);
|
||||
ProfileFetcher() {
|
||||
}
|
||||
|
||||
ProfileFetcher() {}
|
||||
|
||||
/**
|
||||
* Fetch one or more profiles.
|
||||
*
|
||||
* @param requests The profile requests.
|
||||
* @param requests
|
||||
* The profile requests.
|
||||
*/
|
||||
void fetchRequests(final Collection<ProfileRequest> requests) {
|
||||
Preconditions.checkNotNull(requests);
|
||||
@ -54,70 +38,85 @@ public class ProfileFetcher {
|
||||
|
||||
String[] playerNames = new String[requests.size()];
|
||||
|
||||
int i=0;
|
||||
int i = 0;
|
||||
for (ProfileRequest request : requests) {
|
||||
playerNames[i] = request.getPlayerName();
|
||||
i++;
|
||||
}
|
||||
|
||||
repo.findProfilesByNames(playerNames, Agent.MINECRAFT,
|
||||
new ProfileLookupCallback() {
|
||||
repo.findProfilesByNames(playerNames, Agent.MINECRAFT, new ProfileLookupCallback() {
|
||||
|
||||
@Override
|
||||
public void onProfileLookupFailed(GameProfile profile, Exception e) {
|
||||
@Override
|
||||
public void onProfileLookupFailed(GameProfile profile, Exception e) {
|
||||
|
||||
if (Messaging.isDebugging()) {
|
||||
Messaging.debug("Profile lookup for player '" +
|
||||
profile.getName() + "' failed: " + getExceptionMsg(e));
|
||||
}
|
||||
if (Messaging.isDebugging()) {
|
||||
Messaging.debug(
|
||||
"Profile lookup for player '" + profile.getName() + "' failed: " + getExceptionMsg(e));
|
||||
}
|
||||
|
||||
ProfileRequest request = findRequest(profile.getName(), requests);
|
||||
if (request == null)
|
||||
return;
|
||||
ProfileRequest request = findRequest(profile.getName(), requests);
|
||||
if (request == null)
|
||||
return;
|
||||
|
||||
if (isProfileNotFound(e)) {
|
||||
request.setResult(null, ProfileFetchResult.NOT_FOUND);
|
||||
} else if (isTooManyRequests(e)) {
|
||||
request.setResult(null, ProfileFetchResult.TOO_MANY_REQUESTS);
|
||||
} else {
|
||||
request.setResult(null, ProfileFetchResult.FAILED);
|
||||
}
|
||||
if (isProfileNotFound(e)) {
|
||||
request.setResult(null, ProfileFetchResult.NOT_FOUND);
|
||||
} else if (isTooManyRequests(e)) {
|
||||
request.setResult(null, ProfileFetchResult.TOO_MANY_REQUESTS);
|
||||
} else {
|
||||
request.setResult(null, ProfileFetchResult.FAILED);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProfileLookupSucceeded(final GameProfile profile) {
|
||||
|
||||
if (Messaging.isDebugging()) {
|
||||
Messaging.debug("Fetched profile " + profile.getId() + " for player " + profile.getName());
|
||||
}
|
||||
|
||||
ProfileRequest request = findRequest(profile.getName(), requests);
|
||||
if (request == null)
|
||||
return;
|
||||
|
||||
try {
|
||||
request.setResult(NMS.fillProfileProperties(profile, true), ProfileFetchResult.SUCCESS);
|
||||
} catch (Exception e) {
|
||||
|
||||
if (Messaging.isDebugging()) {
|
||||
Messaging.debug(
|
||||
"Profile lookup for player '" + profile.getName() + "' failed: " + getExceptionMsg(e));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProfileLookupSucceeded(final GameProfile profile) {
|
||||
|
||||
if (Messaging.isDebugging()) {
|
||||
Messaging.debug("Fetched profile " + profile.getId()
|
||||
+ " for player " + profile.getName());
|
||||
}
|
||||
|
||||
ProfileRequest request = findRequest(profile.getName(), requests);
|
||||
if (request == null)
|
||||
return;
|
||||
|
||||
try {
|
||||
request.setResult(NMS.fillProfileProperties(profile, true), ProfileFetchResult.SUCCESS);
|
||||
} catch (Exception e) {
|
||||
|
||||
if (Messaging.isDebugging()) {
|
||||
Messaging.debug("Profile lookup for player '" +
|
||||
profile.getName() + "' failed: " + getExceptionMsg(e));
|
||||
}
|
||||
|
||||
if (isTooManyRequests(e)) {
|
||||
request.setResult(null, ProfileFetchResult.TOO_MANY_REQUESTS);
|
||||
} else {
|
||||
request.setResult(null, ProfileFetchResult.FAILED);
|
||||
}
|
||||
}
|
||||
if (isTooManyRequests(e)) {
|
||||
request.setResult(null, ProfileFetchResult.TOO_MANY_REQUESTS);
|
||||
} else {
|
||||
request.setResult(null, ProfileFetchResult.FAILED);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a profile.
|
||||
*
|
||||
* @param name
|
||||
* The name of the player the profile belongs to.
|
||||
* @param handler
|
||||
* Optional handler to handle the result. Handler always invoked from the main thread.
|
||||
*/
|
||||
public static void fetch(String name, @Nullable ProfileFetchHandler handler) {
|
||||
Preconditions.checkNotNull(name);
|
||||
|
||||
if (PROFILE_THREAD == null) {
|
||||
PROFILE_THREAD = new ProfileFetchThread();
|
||||
Bukkit.getScheduler().runTaskTimerAsynchronously(CitizensAPI.getPlugin(), PROFILE_THREAD, 11, 20);
|
||||
}
|
||||
PROFILE_THREAD.fetch(name, handler);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static ProfileRequest findRequest(String name, Collection<ProfileRequest> requests) {
|
||||
|
||||
name = name.toLowerCase();
|
||||
|
||||
for (ProfileRequest request : requests) {
|
||||
@ -127,6 +126,12 @@ public class ProfileFetcher {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String getExceptionMsg(Exception e) {
|
||||
String message = e.getMessage();
|
||||
String cause = e.getCause() != null ? e.getCause().getMessage() : null;
|
||||
return cause != null ? cause : message;
|
||||
}
|
||||
|
||||
private static boolean isProfileNotFound(Exception e) {
|
||||
String message = e.getMessage();
|
||||
String cause = e.getCause() != null ? e.getCause().getMessage() : null;
|
||||
@ -135,12 +140,6 @@ public class ProfileFetcher {
|
||||
|| (cause != null && cause.contains("did not find"));
|
||||
}
|
||||
|
||||
private static String getExceptionMsg(Exception e) {
|
||||
String message = e.getMessage();
|
||||
String cause = e.getCause() != null ? e.getCause().getMessage() : null;
|
||||
return cause != null ? cause : message;
|
||||
}
|
||||
|
||||
private static boolean isTooManyRequests(Exception e) {
|
||||
|
||||
String message = e.getMessage();
|
||||
|
@ -2,75 +2,56 @@ package net.citizensnpcs.npc.profile;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.mojang.authlib.GameProfile;
|
||||
|
||||
import net.citizensnpcs.api.CitizensAPI;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
/**
|
||||
* Stores basic information about a single profile used to request
|
||||
* profiles from the Mojang servers.
|
||||
* Stores basic information about a single profile used to request profiles from the Mojang servers.
|
||||
*
|
||||
* <p>Also stores the result of the request.</p>
|
||||
* <p>
|
||||
* Also stores the result of the request.
|
||||
* </p>
|
||||
*/
|
||||
public class ProfileRequest {
|
||||
|
||||
private final String playerName;
|
||||
private Deque<ProfileFetchHandler> handlers;
|
||||
private final String playerName;
|
||||
private GameProfile profile;
|
||||
private volatile ProfileFetchResult result = ProfileFetchResult.PENDING;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param playerName The name of the player whose profile is being requested.
|
||||
* @param handler Optional handler to handle the result for the profile.
|
||||
* Handler always invoked from the main thread.
|
||||
* @param playerName
|
||||
* The name of the player whose profile is being requested.
|
||||
* @param handler
|
||||
* Optional handler to handle the result for the profile. Handler always invoked from the main thread.
|
||||
*/
|
||||
ProfileRequest(String playerName, @Nullable ProfileFetchHandler handler) {
|
||||
Preconditions.checkNotNull(playerName);
|
||||
|
||||
this.playerName = playerName;
|
||||
|
||||
if (handler != null)
|
||||
if (handler != null) {
|
||||
addHandler(handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the player the requested profile belongs to.
|
||||
*/
|
||||
public String getPlayerName() {
|
||||
return playerName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the game profile that was requested.
|
||||
*
|
||||
* @return The game profile or null if the profile has not been retrieved
|
||||
* yet or there was an error while retrieving the profile.
|
||||
*/
|
||||
@Nullable
|
||||
public GameProfile getProfile() {
|
||||
return profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the result of the profile fetch.
|
||||
*/
|
||||
public ProfileFetchResult getResult() {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add one time result handler.
|
||||
*
|
||||
* <p>Handler is always invoked from the main thread.</p>
|
||||
* <p>
|
||||
* Handler is always invoked from the main thread.
|
||||
* </p>
|
||||
*
|
||||
* @param handler The result handler.
|
||||
* @param handler
|
||||
* The result handler.
|
||||
*/
|
||||
public void addHandler(ProfileFetchHandler handler) {
|
||||
Preconditions.checkNotNull(handler);
|
||||
@ -86,20 +67,47 @@ public class ProfileRequest {
|
||||
handlers.addLast(handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the player the requested profile belongs to.
|
||||
*/
|
||||
public String getPlayerName() {
|
||||
return playerName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the game profile that was requested.
|
||||
*
|
||||
* @return The game profile or null if the profile has not been retrieved yet or there was an error while retrieving
|
||||
* the profile.
|
||||
*/
|
||||
@Nullable
|
||||
public GameProfile getProfile() {
|
||||
return profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the result of the profile fetch.
|
||||
*/
|
||||
public ProfileFetchResult getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked to set the profile result.
|
||||
*
|
||||
* <p>Can be invoked from any thread, always executes on the main thread.</p>
|
||||
* <p>
|
||||
* Can be invoked from any thread, always executes on the main thread.
|
||||
* </p>
|
||||
*
|
||||
* @param profile The profile. Null if there was an error.
|
||||
* @param result The result of the request.
|
||||
* @param profile
|
||||
* The profile. Null if there was an error.
|
||||
* @param result
|
||||
* The result of the request.
|
||||
*/
|
||||
void setResult(final @Nullable GameProfile profile, final ProfileFetchResult result) {
|
||||
|
||||
Bukkit.getScheduler().scheduleSyncDelayedTask(CitizensAPI.getPlugin(), new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
ProfileRequest.this.profile = profile;
|
||||
ProfileRequest.this.result = result;
|
||||
|
||||
|
@ -9,50 +9,34 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
import net.citizensnpcs.Settings;
|
||||
import net.citizensnpcs.api.CitizensAPI;
|
||||
import net.citizensnpcs.util.NMS;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
* Sends remove packets in batch per player.
|
||||
*
|
||||
* <p>Collects entities to remove and sends them all to the
|
||||
* player in a single packet.</p>
|
||||
* <p>
|
||||
* Collects entities to remove and sends them all to the player in a single packet.
|
||||
* </p>
|
||||
*/
|
||||
public class PlayerListRemover {
|
||||
|
||||
private final Map<UUID, PlayerEntry> pending =
|
||||
new HashMap<UUID, PlayerEntry>(Bukkit.getMaxPlayers() / 2);
|
||||
private final Map<UUID, PlayerEntry> pending = new HashMap<UUID, PlayerEntry>(Bukkit.getMaxPlayers() / 2);
|
||||
|
||||
PlayerListRemover() {
|
||||
Bukkit.getScheduler().runTaskTimer(CitizensAPI.getPlugin(), new Sender(), 2, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a remove packet to the specified player for the specified
|
||||
* skinnable entity.
|
||||
*
|
||||
* @param player The player to send the packet to.
|
||||
* @param entity The entity to remove.
|
||||
*/
|
||||
public void sendPacket(Player player, SkinnableEntity entity) {
|
||||
Preconditions.checkNotNull(player);
|
||||
Preconditions.checkNotNull(entity);
|
||||
|
||||
PlayerEntry entry = getEntry(player);
|
||||
|
||||
entry.toRemove.add(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel packets pending to be sent to the specified player.
|
||||
*
|
||||
* @param player The player.
|
||||
* @param player
|
||||
* The player.
|
||||
*/
|
||||
public void cancelPackets(Player player) {
|
||||
Preconditions.checkNotNull(player);
|
||||
@ -67,11 +51,12 @@ public class PlayerListRemover {
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel packets pending to be sent to the specified player
|
||||
* for the specified skinnable entity.
|
||||
* Cancel packets pending to be sent to the specified player for the specified skinnable entity.
|
||||
*
|
||||
* @param player The player.
|
||||
* @param skinnable The skinnable entity.
|
||||
* @param player
|
||||
* The player.
|
||||
* @param skinnable
|
||||
* The skinnable entity.
|
||||
*/
|
||||
public void cancelPackets(Player player, SkinnableEntity skinnable) {
|
||||
Preconditions.checkNotNull(player);
|
||||
@ -100,6 +85,23 @@ public class PlayerListRemover {
|
||||
return entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a remove packet to the specified player for the specified skinnable entity.
|
||||
*
|
||||
* @param player
|
||||
* The player to send the packet to.
|
||||
* @param entity
|
||||
* The entity to remove.
|
||||
*/
|
||||
public void sendPacket(Player player, SkinnableEntity entity) {
|
||||
Preconditions.checkNotNull(player);
|
||||
Preconditions.checkNotNull(entity);
|
||||
|
||||
PlayerEntry entry = getEntry(player);
|
||||
|
||||
entry.toRemove.add(entity);
|
||||
}
|
||||
|
||||
private class PlayerEntry {
|
||||
Player player;
|
||||
Set<SkinnableEntity> toRemove = new HashSet<SkinnableEntity>(25);
|
||||
@ -110,7 +112,6 @@ public class PlayerListRemover {
|
||||
}
|
||||
|
||||
private class Sender implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
@ -127,7 +128,7 @@ public class PlayerListRemover {
|
||||
|
||||
List<SkinnableEntity> skinnableList = new ArrayList<SkinnableEntity>(listSize);
|
||||
|
||||
int i =0;
|
||||
int i = 0;
|
||||
Iterator<SkinnableEntity> skinIterator = entry.toRemove.iterator();
|
||||
while (skinIterator.hasNext()) {
|
||||
|
||||
|
@ -4,6 +4,7 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
@ -11,11 +12,11 @@ import com.google.common.collect.Iterables;
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
|
||||
import net.citizensnpcs.Settings;
|
||||
import net.citizensnpcs.Settings.Setting;
|
||||
import net.citizensnpcs.api.event.DespawnReason;
|
||||
import net.citizensnpcs.api.npc.NPC;
|
||||
import net.citizensnpcs.npc.profile.ProfileFetchResult;
|
||||
import net.citizensnpcs.npc.profile.ProfileFetchHandler;
|
||||
import net.citizensnpcs.npc.profile.ProfileFetchResult;
|
||||
import net.citizensnpcs.npc.profile.ProfileFetcher;
|
||||
import net.citizensnpcs.npc.profile.ProfileRequest;
|
||||
|
||||
@ -23,20 +24,176 @@ import net.citizensnpcs.npc.profile.ProfileRequest;
|
||||
* Stores data for a single skin.
|
||||
*/
|
||||
public class Skin {
|
||||
|
||||
private final String skinName;
|
||||
private volatile Property skinData;
|
||||
private volatile UUID skinId;
|
||||
private volatile boolean isValid = true;
|
||||
private final Map<SkinnableEntity, Void> pending = new WeakHashMap<SkinnableEntity, Void>(15);
|
||||
private volatile Property skinData;
|
||||
private volatile UUID skinId;
|
||||
private final String skinName;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param skinName
|
||||
* The name of the player the skin belongs to.
|
||||
*/
|
||||
Skin(String skinName) {
|
||||
this.skinName = skinName.toLowerCase();
|
||||
|
||||
synchronized (CACHE) {
|
||||
if (CACHE.containsKey(this.skinName))
|
||||
throw new IllegalArgumentException("There is already a skin named " + skinName);
|
||||
|
||||
CACHE.put(this.skinName, this);
|
||||
}
|
||||
|
||||
ProfileFetcher.fetch(this.skinName, new ProfileFetchHandler() {
|
||||
@Override
|
||||
public void onResult(ProfileRequest request) {
|
||||
|
||||
if (request.getResult() == ProfileFetchResult.NOT_FOUND) {
|
||||
isValid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (request.getResult() == ProfileFetchResult.SUCCESS) {
|
||||
GameProfile profile = request.getProfile();
|
||||
setData(profile);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the skin data to the specified skinnable entity.
|
||||
*
|
||||
* <p>
|
||||
* If invoked before the skin data is ready, the skin is retrieved and the skin is automatically applied to the
|
||||
* entity at a later time.
|
||||
* </p>
|
||||
*
|
||||
* @param entity
|
||||
* The skinnable entity.
|
||||
*
|
||||
* @return True if the skin data was available and applied, false if the data is being retrieved.
|
||||
*/
|
||||
public boolean apply(SkinnableEntity entity) {
|
||||
Preconditions.checkNotNull(entity);
|
||||
|
||||
NPC npc = entity.getNPC();
|
||||
|
||||
if (!hasSkinData()) {
|
||||
// Use npc cached skin if available.
|
||||
// If npc requires latest skin, cache is used for faster
|
||||
// availability until the latest skin can be loaded.
|
||||
String cachedName = npc.data().get(CACHED_SKIN_UUID_NAME_METADATA);
|
||||
if (this.skinName.equals(cachedName)) {
|
||||
skinData = new Property(this.skinName,
|
||||
npc.data().<String> get(NPC.PLAYER_SKIN_TEXTURE_PROPERTIES_METADATA),
|
||||
npc.data().<String> get(NPC.PLAYER_SKIN_TEXTURE_PROPERTIES_SIGN_METADATA));
|
||||
|
||||
skinId = UUID.fromString(npc.data().<String> get(CACHED_SKIN_UUID_METADATA));
|
||||
setNPCSkinData(entity, skinName, skinId, skinData);
|
||||
|
||||
// check if NPC prefers to use cached skin over the latest skin.
|
||||
if (!entity.getNPC().data().get("update-skin", Setting.NPC_SKIN_UPDATE.asBoolean())) {
|
||||
// cache preferred
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!Setting.NPC_SKIN_UPDATE.asBoolean()) {
|
||||
// cache preferred
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
pending.put(entity, null);
|
||||
return false;
|
||||
}
|
||||
|
||||
setNPCSkinData(entity, skinName, skinId, skinData);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the skin data to the specified skinnable entity and respawn the NPC.
|
||||
*
|
||||
* @param entity
|
||||
* The skinnable entity.
|
||||
*/
|
||||
public void applyAndRespawn(SkinnableEntity entity) {
|
||||
Preconditions.checkNotNull(entity);
|
||||
|
||||
if (!apply(entity))
|
||||
return;
|
||||
|
||||
NPC npc = entity.getNPC();
|
||||
|
||||
if (npc.isSpawned()) {
|
||||
npc.despawn(DespawnReason.PENDING_RESPAWN);
|
||||
npc.spawn(npc.getStoredLocation());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ID of the player the skin belongs to.
|
||||
*
|
||||
* @return The skin ID or null if it has not been retrieved yet or the skin is invalid.
|
||||
*/
|
||||
@Nullable
|
||||
public UUID getSkinId() {
|
||||
return skinId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the skin.
|
||||
*/
|
||||
public String getSkinName() {
|
||||
return skinName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the skin data has been retrieved.
|
||||
*/
|
||||
public boolean hasSkinData() {
|
||||
return skinData != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the skin is valid.
|
||||
*/
|
||||
public boolean isValid() {
|
||||
return isValid;
|
||||
}
|
||||
|
||||
private void setData(@Nullable GameProfile profile) {
|
||||
if (profile == null) {
|
||||
isValid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!profile.getName().toLowerCase().equals(skinName)) {
|
||||
throw new IllegalArgumentException(
|
||||
"GameProfile name (" + profile.getName() + ") and " + "skin name (" + skinName + ") do not match.");
|
||||
}
|
||||
|
||||
skinId = profile.getId();
|
||||
skinData = Iterables.getFirst(profile.getProperties().get("textures"), null);
|
||||
|
||||
for (SkinnableEntity entity : pending.keySet()) {
|
||||
applyAndRespawn(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a skin for a skinnable entity.
|
||||
*
|
||||
* <p>If a Skin instance does not exist, a new one is created and the
|
||||
* skin data is automatically fetched.</p>
|
||||
* <p>
|
||||
* If a Skin instance does not exist, a new one is created and the skin data is automatically fetched.
|
||||
* </p>
|
||||
*
|
||||
* @param entity The skinnable entity.
|
||||
* @param entity
|
||||
* The skinnable entity.
|
||||
*/
|
||||
public static Skin get(SkinnableEntity entity) {
|
||||
Preconditions.checkNotNull(entity);
|
||||
@ -55,187 +212,26 @@ public class Skin {
|
||||
return skin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param skinName The name of the player the skin belongs to.
|
||||
*/
|
||||
Skin(String skinName) {
|
||||
|
||||
this.skinName = skinName.toLowerCase();
|
||||
|
||||
synchronized (CACHE) {
|
||||
if (CACHE.containsKey(this.skinName))
|
||||
throw new IllegalArgumentException("There is already a skin named " + skinName);
|
||||
|
||||
CACHE.put(this.skinName, this);
|
||||
}
|
||||
|
||||
ProfileFetcher.fetch(this.skinName, new ProfileFetchHandler() {
|
||||
|
||||
@Override
|
||||
public void onResult(ProfileRequest request) {
|
||||
|
||||
if (request.getResult() == ProfileFetchResult.NOT_FOUND) {
|
||||
isValid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (request.getResult() == ProfileFetchResult.SUCCESS) {
|
||||
GameProfile profile = request.getProfile();
|
||||
setData(profile);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the skin.
|
||||
*/
|
||||
public String getSkinName() {
|
||||
return skinName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ID of the player the skin belongs to.
|
||||
*
|
||||
* @return The skin ID or null if it has not been retrieved yet or
|
||||
* the skin is invalid.
|
||||
*/
|
||||
@Nullable
|
||||
public UUID getSkinId() {
|
||||
return skinId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the skin is valid.
|
||||
*/
|
||||
public boolean isValid() {
|
||||
return isValid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the skin data has been retrieved.
|
||||
*/
|
||||
public boolean hasSkinData() {
|
||||
return skinData != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the skin data to the specified skinnable entity.
|
||||
*
|
||||
* <p>If invoked before the skin data is ready, the skin is retrieved
|
||||
* and the skin is automatically applied to the entity at a later time.</p>
|
||||
*
|
||||
* @param entity The skinnable entity.
|
||||
*
|
||||
* @return True if the skin data was available and applied, false if
|
||||
* the data is being retrieved.
|
||||
*/
|
||||
public boolean apply(SkinnableEntity entity) {
|
||||
Preconditions.checkNotNull(entity);
|
||||
|
||||
NPC npc = entity.getNPC();
|
||||
|
||||
if (!hasSkinData()) {
|
||||
|
||||
// Use npc cached skin if available.
|
||||
// If npc requires latest skin, cache is used for faster
|
||||
// availability until the latest skin can be loaded.
|
||||
String cachedName = npc.data().get(CACHED_SKIN_UUID_NAME_METADATA);
|
||||
if (this.skinName.equals(cachedName)) {
|
||||
|
||||
skinData = new Property(this.skinName,
|
||||
npc.data().<String>get(PLAYER_SKIN_TEXTURE_PROPERTIES),
|
||||
npc.data().<String>get(PLAYER_SKIN_TEXTURE_PROPERTIES_SIGN));
|
||||
|
||||
skinId = UUID.fromString(npc.data().<String>get(CACHED_SKIN_UUID_METADATA));
|
||||
|
||||
setNPCSkinData(entity, skinName, skinId, skinData);
|
||||
|
||||
// check if NPC prefers to use cached skin over the latest skin.
|
||||
if (!entity.getNPC().data().get("update-skin",
|
||||
Settings.Setting.NPC_SKIN_UPDATE.asBoolean())) {
|
||||
// cache preferred
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!Settings.Setting.NPC_SKIN_UPDATE.asBoolean()) {
|
||||
// cache preferred
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
pending.put(entity, null);
|
||||
return false;
|
||||
}
|
||||
|
||||
setNPCSkinData(entity, skinName, skinId, skinData);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the skin data to the specified skinnable entity
|
||||
* and respawn the NPC.
|
||||
*
|
||||
* @param entity The skinnable entity.
|
||||
*/
|
||||
public void applyAndRespawn(SkinnableEntity entity) {
|
||||
Preconditions.checkNotNull(entity);
|
||||
|
||||
if (!apply(entity))
|
||||
return;
|
||||
|
||||
NPC npc = entity.getNPC();
|
||||
|
||||
if (npc.isSpawned()) {
|
||||
npc.despawn(DespawnReason.PENDING_RESPAWN);
|
||||
npc.spawn(npc.getStoredLocation());
|
||||
}
|
||||
}
|
||||
|
||||
private void setData(@Nullable GameProfile profile) {
|
||||
|
||||
if (profile == null) {
|
||||
isValid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!profile.getName().toLowerCase().equals(skinName)) {
|
||||
throw new IllegalArgumentException(
|
||||
"GameProfile name (" + profile.getName() + ") and "
|
||||
+ "skin name (" + skinName + ") do not match.");
|
||||
}
|
||||
|
||||
skinId = profile.getId();
|
||||
skinData = Iterables.getFirst(profile.getProperties().get("textures"), null);
|
||||
|
||||
for (SkinnableEntity entity : pending.keySet()) {
|
||||
applyAndRespawn(entity);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setNPCSkinData(SkinnableEntity entity,
|
||||
String skinName, UUID skinId, Property skinProperty) {
|
||||
|
||||
private static void setNPCSkinData(SkinnableEntity entity, String skinName, UUID skinId, Property skinProperty) {
|
||||
NPC npc = entity.getNPC();
|
||||
|
||||
// cache skins for faster initial skin availability
|
||||
npc.data().setPersistent(CACHED_SKIN_UUID_NAME_METADATA, skinName);
|
||||
npc.data().setPersistent(CACHED_SKIN_UUID_METADATA, skinId.toString());
|
||||
npc.data().setPersistent(PLAYER_SKIN_TEXTURE_PROPERTIES, skinProperty.getValue());
|
||||
npc.data().setPersistent(PLAYER_SKIN_TEXTURE_PROPERTIES_SIGN, skinProperty.getSignature());
|
||||
if (skinProperty.getValue() != null) {
|
||||
npc.data().setPersistent(NPC.PLAYER_SKIN_TEXTURE_PROPERTIES_METADATA, skinProperty.getValue());
|
||||
npc.data().setPersistent(NPC.PLAYER_SKIN_TEXTURE_PROPERTIES_SIGN_METADATA, skinProperty.getSignature());
|
||||
|
||||
GameProfile profile = entity.getProfile();
|
||||
profile.getProperties().removeAll("textures"); // ensure client does not crash due to duplicate properties.
|
||||
profile.getProperties().put("textures", skinProperty);
|
||||
GameProfile profile = entity.getProfile();
|
||||
profile.getProperties().removeAll("textures"); // ensure client does not crash due to duplicate properties.
|
||||
profile.getProperties().put("textures", skinProperty);
|
||||
} else {
|
||||
npc.data().remove(NPC.PLAYER_SKIN_TEXTURE_PROPERTIES_METADATA);
|
||||
npc.data().remove(NPC.PLAYER_SKIN_TEXTURE_PROPERTIES_SIGN_METADATA);
|
||||
}
|
||||
}
|
||||
|
||||
public static final String PLAYER_SKIN_TEXTURE_PROPERTIES = "player-skin-textures";
|
||||
public static final String PLAYER_SKIN_TEXTURE_PROPERTIES_SIGN = "player-skin-signature";
|
||||
private static final Map<String, Skin> CACHE = new HashMap<String, Skin>(20);
|
||||
public static final String CACHED_SKIN_UUID_METADATA = "cached-skin-uuid";
|
||||
public static final String CACHED_SKIN_UUID_NAME_METADATA = "cached-skin-uuid-name";
|
||||
|
||||
private static final Map<String, Skin> CACHE = new HashMap<String, Skin>(20);
|
||||
}
|
||||
|
@ -5,12 +5,6 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
import net.citizensnpcs.Settings;
|
||||
import net.citizensnpcs.api.CitizensAPI;
|
||||
import net.citizensnpcs.util.NMS;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
@ -19,25 +13,31 @@ import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
import net.citizensnpcs.Settings;
|
||||
import net.citizensnpcs.api.CitizensAPI;
|
||||
import net.citizensnpcs.util.NMS;
|
||||
|
||||
/**
|
||||
* Handles and synchronizes add and remove packets for Player type NPC's
|
||||
* in order to properly apply the NPC skin.
|
||||
* Handles and synchronizes add and remove packets for Player type NPC's in order to properly apply the NPC skin.
|
||||
*
|
||||
* <p>Used as one instance per NPC entity.</p>
|
||||
* <p>
|
||||
* Used as one instance per NPC entity.
|
||||
* </p>
|
||||
*/
|
||||
public class SkinPacketTracker {
|
||||
|
||||
private final SkinnableEntity entity;
|
||||
private final Map<UUID, PlayerEntry> inProgress =
|
||||
new HashMap<UUID, PlayerEntry>(Bukkit.getMaxPlayers() / 2);
|
||||
private final Map<UUID, PlayerEntry> inProgress = new HashMap<UUID, PlayerEntry>(Bukkit.getMaxPlayers() / 2);
|
||||
|
||||
private Skin skin;
|
||||
private boolean isRemoved;
|
||||
private Skin skin;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param entity The skinnable entity the instance belongs to.
|
||||
* @param entity
|
||||
* The skinnable entity the instance belongs to.
|
||||
*/
|
||||
public SkinPacketTracker(SkinnableEntity entity) {
|
||||
Preconditions.checkNotNull(entity);
|
||||
@ -59,68 +59,53 @@ public class SkinPacketTracker {
|
||||
}
|
||||
|
||||
/**
|
||||
* Send skin related packets to a player.
|
||||
* Notify the tracker that a remove packet has been sent to the specified player.
|
||||
*
|
||||
* @param player The player.
|
||||
* @param playerId
|
||||
* The ID of the player.
|
||||
*/
|
||||
public void updateViewer(final Player player) {
|
||||
Preconditions.checkNotNull(player);
|
||||
|
||||
if (isRemoved || player.hasMetadata("NPC"))
|
||||
return;
|
||||
|
||||
PlayerEntry entry = inProgress.get(player.getUniqueId());
|
||||
if (entry != null) {
|
||||
entry.cancel();
|
||||
}
|
||||
else {
|
||||
entry = new PlayerEntry(player);
|
||||
}
|
||||
|
||||
PLAYER_LIST_REMOVER.cancelPackets(player, entity);
|
||||
|
||||
inProgress.put(player.getUniqueId(), entry);
|
||||
skin.apply(entity);
|
||||
NMS.sendPlayerListAdd(player, entity.getBukkitEntity());
|
||||
|
||||
scheduleRemovePacket(entry, 2);
|
||||
void notifyRemovePacketCancelled(UUID playerId) {
|
||||
inProgress.remove(playerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send skin related packets to all nearby players within the specified block radius.
|
||||
* Notify the tracker that a remove packet has been sent to the specified player.
|
||||
*
|
||||
* @param radius The radius.
|
||||
* @param playerId
|
||||
* The ID of the player.
|
||||
*/
|
||||
public void updateNearbyViewers(double radius) {
|
||||
void notifyRemovePacketSent(UUID playerId) {
|
||||
PlayerEntry entry = inProgress.get(playerId);
|
||||
if (entry == null)
|
||||
return;
|
||||
|
||||
radius *= radius;
|
||||
if (entry.removeCount == 0)
|
||||
return;
|
||||
|
||||
org.bukkit.World world = entity.getBukkitEntity().getWorld();
|
||||
Player from = entity.getBukkitEntity();
|
||||
Location location = from.getLocation();
|
||||
|
||||
for (Player player : Bukkit.getServer().getOnlinePlayers()) {
|
||||
|
||||
if (player == null || player.hasMetadata("NPC"))
|
||||
continue;
|
||||
|
||||
if (world != player.getWorld() || !player.canSee(from))
|
||||
continue;
|
||||
|
||||
if (location.distanceSquared(player.getLocation(CACHE_LOCATION)) > radius)
|
||||
continue;
|
||||
|
||||
updateViewer(player);
|
||||
entry.removeCount -= 1;
|
||||
if (entry.removeCount == 0) {
|
||||
inProgress.remove(playerId);
|
||||
} else {
|
||||
scheduleRemovePacket(entry);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify that the NPC skin has been changed.
|
||||
*/
|
||||
public void notifySkinChange() {
|
||||
this.skin = Skin.get(entity);
|
||||
skin.applyAndRespawn(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke when the NPC entity is removed.
|
||||
*
|
||||
* <p>Sends remove packets to all players.</p>
|
||||
* <p>
|
||||
* Sends remove packets to all players.
|
||||
* </p>
|
||||
*/
|
||||
public void onRemoveNPC() {
|
||||
|
||||
isRemoved = true;
|
||||
|
||||
Collection<? extends Player> players = Bukkit.getOnlinePlayers();
|
||||
@ -136,50 +121,21 @@ public class SkinPacketTracker {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify that the NPC skin has been changed.
|
||||
*/
|
||||
public void notifySkinChange() {
|
||||
this.skin = Skin.get(entity);
|
||||
skin.applyAndRespawn(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the tracker that a remove packet has been sent to the
|
||||
* specified player.
|
||||
*
|
||||
* @param playerId The ID of the player.
|
||||
*/
|
||||
void notifyRemovePacketSent(UUID playerId) {
|
||||
|
||||
PlayerEntry entry = inProgress.get(playerId);
|
||||
if (entry == null)
|
||||
private void scheduleRemovePacket(final PlayerEntry entry) {
|
||||
if (isRemoved)
|
||||
return;
|
||||
|
||||
if (entry.removeCount == 0)
|
||||
return;
|
||||
|
||||
entry.removeCount -= 1;
|
||||
if (entry.removeCount == 0) {
|
||||
inProgress.remove(playerId);
|
||||
}
|
||||
else {
|
||||
scheduleRemovePacket(entry);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the tracker that a remove packet has been sent to the
|
||||
* specified player.
|
||||
*
|
||||
* @param playerId The ID of the player.
|
||||
*/
|
||||
void notifyRemovePacketCancelled(UUID playerId) {
|
||||
inProgress.remove(playerId);
|
||||
entry.removeTask = Bukkit.getScheduler().runTaskLater(CitizensAPI.getPlugin(), new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (shouldRemoveFromPlayerList()) {
|
||||
PLAYER_LIST_REMOVER.sendPacket(entry.player, entity);
|
||||
}
|
||||
}
|
||||
}, PACKET_DELAY_REMOVE);
|
||||
}
|
||||
|
||||
private void scheduleRemovePacket(PlayerEntry entry, int count) {
|
||||
|
||||
if (!shouldRemoveFromPlayerList())
|
||||
return;
|
||||
|
||||
@ -187,26 +143,7 @@ public class SkinPacketTracker {
|
||||
scheduleRemovePacket(entry);
|
||||
}
|
||||
|
||||
private void scheduleRemovePacket(final PlayerEntry entry) {
|
||||
|
||||
if (isRemoved)
|
||||
return;
|
||||
|
||||
entry.removeTask = Bukkit.getScheduler().runTaskLater(CitizensAPI.getPlugin(),
|
||||
new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
if (shouldRemoveFromPlayerList()) {
|
||||
PLAYER_LIST_REMOVER.sendPacket(entry.player, entity);
|
||||
}
|
||||
}
|
||||
}, PACKET_DELAY_REMOVE);
|
||||
}
|
||||
|
||||
private boolean shouldRemoveFromPlayerList() {
|
||||
|
||||
boolean isTablistDisabled = Settings.Setting.DISABLE_TABLIST.asBoolean();
|
||||
boolean isNpcRemoved = entity.getNPC().data().get("removefromplayerlist",
|
||||
Settings.Setting.REMOVE_PLAYERS_FROM_PLAYER_LIST.asBoolean());
|
||||
@ -214,12 +151,67 @@ public class SkinPacketTracker {
|
||||
return isNpcRemoved && isTablistDisabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send skin related packets to all nearby players within the specified block radius.
|
||||
*
|
||||
* @param radius
|
||||
* The radius.
|
||||
*/
|
||||
public void updateNearbyViewers(double radius) {
|
||||
radius *= radius;
|
||||
|
||||
org.bukkit.World world = entity.getBukkitEntity().getWorld();
|
||||
Player from = entity.getBukkitEntity();
|
||||
Location location = from.getLocation();
|
||||
|
||||
for (Player player : Bukkit.getServer().getOnlinePlayers()) {
|
||||
if (player == null || player.hasMetadata("NPC"))
|
||||
continue;
|
||||
|
||||
if (world != player.getWorld() || !player.canSee(from))
|
||||
continue;
|
||||
|
||||
if (location.distanceSquared(player.getLocation(CACHE_LOCATION)) > radius)
|
||||
continue;
|
||||
|
||||
updateViewer(player);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send skin related packets to a player.
|
||||
*
|
||||
* @param player
|
||||
* The player.
|
||||
*/
|
||||
public void updateViewer(final Player player) {
|
||||
Preconditions.checkNotNull(player);
|
||||
|
||||
if (isRemoved || player.hasMetadata("NPC"))
|
||||
return;
|
||||
|
||||
PlayerEntry entry = inProgress.get(player.getUniqueId());
|
||||
if (entry != null) {
|
||||
entry.cancel();
|
||||
} else {
|
||||
entry = new PlayerEntry(player);
|
||||
}
|
||||
|
||||
PLAYER_LIST_REMOVER.cancelPackets(player, entity);
|
||||
|
||||
inProgress.put(player.getUniqueId(), entry);
|
||||
skin.apply(entity);
|
||||
NMS.sendPlayerListAdd(player, entity.getBukkitEntity());
|
||||
|
||||
scheduleRemovePacket(entry, 2);
|
||||
}
|
||||
|
||||
private class PlayerEntry {
|
||||
Player player;
|
||||
int removeCount;
|
||||
BukkitTask removeTask;
|
||||
|
||||
PlayerEntry (Player player) {
|
||||
PlayerEntry(Player player) {
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
@ -233,10 +225,8 @@ public class SkinPacketTracker {
|
||||
}
|
||||
|
||||
private static class PlayerListener implements Listener {
|
||||
|
||||
@EventHandler
|
||||
private void onPlayerQuit(PlayerQuitEvent event) {
|
||||
|
||||
// this also causes any entries in the "inProgress" field to
|
||||
// be removed.
|
||||
PLAYER_LIST_REMOVER.cancelPackets(event.getPlayer());
|
||||
@ -244,7 +234,7 @@ public class SkinPacketTracker {
|
||||
}
|
||||
|
||||
private static final Location CACHE_LOCATION = new Location(null, 0, 0, 0);
|
||||
private static PlayerListener LISTENER;
|
||||
private static final int PACKET_DELAY_REMOVE = 1;
|
||||
private static final PlayerListRemover PLAYER_LIST_REMOVER = new PlayerListRemover();
|
||||
private static PlayerListener LISTENER;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user