mirror of
https://github.com/CitizensDev/Citizens2.git
synced 2025-01-12 11:21:05 +01:00
Merge pull request #502 from JCThePants/skins2-2
bug fix and optimizations in NPC skins
This commit is contained in:
commit
dd37eb25d3
@ -34,6 +34,7 @@ import net.citizensnpcs.api.event.NPCDeathEvent;
|
||||
import net.citizensnpcs.api.event.NPCDespawnEvent;
|
||||
import net.citizensnpcs.api.event.NPCLeftClickEvent;
|
||||
import net.citizensnpcs.api.event.NPCRightClickEvent;
|
||||
import net.citizensnpcs.api.event.NPCSpawnEvent;
|
||||
import net.citizensnpcs.api.event.PlayerCreateNPCEvent;
|
||||
import net.citizensnpcs.api.npc.NPC;
|
||||
import net.citizensnpcs.api.npc.NPCRegistry;
|
||||
@ -81,6 +82,7 @@ import org.bukkit.event.world.WorldLoadEvent;
|
||||
import org.bukkit.event.world.WorldUnloadEvent;
|
||||
import org.bukkit.inventory.meta.SkullMeta;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
import org.bukkit.scoreboard.Team;
|
||||
|
||||
public class EventListen implements Listener {
|
||||
@ -322,6 +324,16 @@ public class EventListen implements Listener {
|
||||
toRespawn.put(coord, event.getNPC());
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onNPCSpawn(NPCSpawnEvent event) {
|
||||
SkinnableEntity skinnable = NMS.getSkinnable(event.getNPC().getEntity());
|
||||
if (skinnable == null)
|
||||
return;
|
||||
|
||||
// reset nearby players in case they are not looking at the NPC when it spawns.
|
||||
resetNearbyPlayers(skinnable);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onNPCDespawn(NPCDespawnEvent event) {
|
||||
if (event.getReason() == DespawnReason.PLUGIN || event.getReason() == DespawnReason.REMOVAL) {
|
||||
@ -441,7 +453,6 @@ public class EventListen implements Listener {
|
||||
// a player moves a certain distance from their last position.
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onPlayerMove(final PlayerMoveEvent event) {
|
||||
|
||||
SkinUpdateTracker updateTracker = skinUpdateTrackers.get(event.getPlayer().getUniqueId());
|
||||
if (updateTracker == null)
|
||||
return;
|
||||
@ -460,17 +471,25 @@ public class EventListen implements Listener {
|
||||
if (player.hasMetadata("NPC"))
|
||||
continue;
|
||||
|
||||
skinUpdateTrackers.put(player.getUniqueId(), new SkinUpdateTracker(player));
|
||||
SkinUpdateTracker tracker = skinUpdateTrackers.get(player.getUniqueId());
|
||||
if (tracker == null)
|
||||
continue;
|
||||
|
||||
tracker.hardReset(player);
|
||||
}
|
||||
}
|
||||
|
||||
public void recalculatePlayer(final Player player, long delay, final boolean isInitial) {
|
||||
|
||||
public void recalculatePlayer(final Player player, long delay, boolean reset) {
|
||||
if (player.hasMetadata("NPC"))
|
||||
return;
|
||||
|
||||
if (isInitial) {
|
||||
skinUpdateTrackers.put(player.getUniqueId(), new SkinUpdateTracker(player));
|
||||
SkinUpdateTracker tracker = skinUpdateTrackers.get(player.getUniqueId());
|
||||
if (tracker == null) {
|
||||
tracker = new SkinUpdateTracker(player);
|
||||
skinUpdateTrackers.put(player.getUniqueId(), tracker);
|
||||
}
|
||||
else if (reset) {
|
||||
tracker.hardReset(player);
|
||||
}
|
||||
|
||||
new BukkitRunnable() {
|
||||
@ -480,20 +499,42 @@ public class EventListen implements Listener {
|
||||
|
||||
List<SkinnableEntity> nearbyNPCs = getNearbySkinnableNPCs(player);
|
||||
for (SkinnableEntity npc : nearbyNPCs) {
|
||||
npc.getSkinTracker().updateViewer(player);
|
||||
}
|
||||
|
||||
if (!nearbyNPCs.isEmpty() && isInitial) {
|
||||
// one more time to help when resource pack load times
|
||||
// prevent immediate skin loading
|
||||
recalculatePlayer(player, 40, false);
|
||||
npc.getSkinTracker().updateViewer(player);
|
||||
}
|
||||
}
|
||||
}.runTaskLater(CitizensAPI.getPlugin(), delay);
|
||||
}
|
||||
|
||||
private List<SkinnableEntity> getNearbySkinnableNPCs(Player player) {
|
||||
// hard reset skin update trackers for players near a skinnable NPC
|
||||
private void resetNearbyPlayers(SkinnableEntity skinnable) {
|
||||
Entity entity = skinnable.getBukkitEntity();
|
||||
if (entity == null || !entity.isValid())
|
||||
return;
|
||||
|
||||
double viewDistance = Setting.NPC_SKIN_VIEW_DISTANCE.asDouble();
|
||||
viewDistance *= viewDistance;
|
||||
Location location = entity.getLocation(NPC_LOCATION);
|
||||
List<Player> players = entity.getWorld().getPlayers();
|
||||
for (Player player : players) {
|
||||
if (player.hasMetadata("NPC"))
|
||||
continue;
|
||||
|
||||
double distanceSquared = player.getLocation(CACHE_LOCATION).distanceSquared(location);
|
||||
if (distanceSquared > viewDistance)
|
||||
continue;
|
||||
|
||||
SkinUpdateTracker tracker = skinUpdateTrackers.get(player.getUniqueId());
|
||||
if (tracker == null) {
|
||||
tracker = new SkinUpdateTracker(player);
|
||||
skinUpdateTrackers.put(player.getUniqueId(), tracker);
|
||||
}
|
||||
else {
|
||||
tracker.hardReset(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<SkinnableEntity> getNearbySkinnableNPCs(Player player) {
|
||||
List<SkinnableEntity> results = new ArrayList<SkinnableEntity>();
|
||||
|
||||
double viewDistance = Setting.NPC_SKIN_VIEW_DISTANCE.asDouble();
|
||||
@ -506,7 +547,7 @@ public class EventListen implements Listener {
|
||||
&& player.canSee((Player) npcEntity)
|
||||
&& player.getWorld().equals(npcEntity.getWorld())
|
||||
&& player.getLocation(CACHE_LOCATION)
|
||||
.distanceSquared(npc.getStoredLocation()) < viewDistance) {
|
||||
.distanceSquared(npc.getStoredLocation()) < viewDistance) {
|
||||
|
||||
SkinnableEntity skinnable = NMS.getSkinnable(npcEntity);
|
||||
|
||||
@ -600,21 +641,25 @@ public class EventListen implements Listener {
|
||||
}
|
||||
|
||||
private class SkinUpdateTracker {
|
||||
float initialYaw;
|
||||
final Location location = new Location(null, 0, 0, 0);
|
||||
int rotationCount;
|
||||
boolean hasMoved;
|
||||
float upperBound;
|
||||
float lowerBound;
|
||||
|
||||
SkinUpdateTracker(Player player) {
|
||||
reset(player);
|
||||
hardReset(player);
|
||||
}
|
||||
|
||||
boolean shouldUpdate(Player player) {
|
||||
Location currentLoc = player.getLocation(CACHE_LOCATION);
|
||||
|
||||
Location currentLoc = player.getLocation(YAW_LOCATION);
|
||||
if (!hasMoved) {
|
||||
hasMoved = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (rotationCount < 2) {
|
||||
if (rotationCount < 3) {
|
||||
float yaw = NMS.clampYaw(currentLoc.getYaw());
|
||||
boolean hasRotated = upperBound < lowerBound
|
||||
? yaw > upperBound && yaw < lowerBound
|
||||
@ -643,15 +688,23 @@ public class EventListen implements Listener {
|
||||
// resets initial yaw and location to the players
|
||||
// current location and yaw.
|
||||
void reset(Player player) {
|
||||
player.getLocation(location);
|
||||
this.initialYaw = NMS.clampYaw(location.getYaw());
|
||||
float rotationDegrees = Setting.NPC_SKIN_ROTATION_UPDATE_DEGREES.asFloat();
|
||||
this.upperBound = NMS.clampYaw(this.initialYaw + rotationDegrees);
|
||||
this.lowerBound = NMS.clampYaw(this.initialYaw - rotationDegrees);
|
||||
player.getLocation(this.location);
|
||||
if (rotationCount < 3) {
|
||||
float rotationDegrees = Setting.NPC_SKIN_ROTATION_UPDATE_DEGREES.asFloat();
|
||||
float yaw = NMS.clampYaw(this.location.getYaw());
|
||||
this.upperBound = NMS.clampYaw(yaw + rotationDegrees);
|
||||
this.lowerBound = NMS.clampYaw(yaw - rotationDegrees);
|
||||
}
|
||||
}
|
||||
|
||||
void hardReset(Player player) {
|
||||
this.hasMoved = false;
|
||||
this.rotationCount = 0;
|
||||
reset(player);
|
||||
}
|
||||
}
|
||||
|
||||
private static final Location YAW_LOCATION = new Location(null, 0, 0, 0);
|
||||
private static final Location CACHE_LOCATION = new Location(null, 0, 0, 0);
|
||||
private static final Location NPC_LOCATION = new Location(null, 0, 0, 0);
|
||||
private static final int MOVEMENT_SKIN_UPDATE_DISTANCE = 50 * 50;
|
||||
}
|
||||
|
@ -189,23 +189,11 @@ public class CitizensNPC extends AbstractNPC {
|
||||
boolean couldSpawn = !Util.isLoaded(at) ? false : mcEntity.world.addEntity(mcEntity, SpawnReason.CUSTOM);
|
||||
|
||||
// send skin packets, if applicable, before other NMS packets are sent
|
||||
SkinnableEntity skinnable = NMS.getSkinnable(getEntity());
|
||||
if (skinnable != null) {
|
||||
final double viewDistance = Settings.Setting.NPC_SKIN_VIEW_DISTANCE.asDouble();
|
||||
//skinnable.getSkinTracker().updateNearbyViewers(viewDistance);
|
||||
Bukkit.getScheduler().scheduleSyncDelayedTask(CitizensAPI.getPlugin(), new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (getEntity() == null || !getEntity().isValid())
|
||||
return;
|
||||
|
||||
SkinnableEntity npc = NMS.getSkinnable(getEntity());
|
||||
if (npc == null)
|
||||
return;
|
||||
|
||||
npc.getSkinTracker().updateNearbyViewers(viewDistance);
|
||||
}
|
||||
}, 20);
|
||||
if (couldSpawn) {
|
||||
SkinnableEntity skinnable = NMS.getSkinnable(getEntity());
|
||||
if (skinnable != null) {
|
||||
skinnable.getSkinTracker().onSpawnNPC();
|
||||
}
|
||||
}
|
||||
|
||||
mcEntity.setPositionRotation(at.getX(), at.getY(), at.getZ(), at.getYaw(), at.getPitch());
|
||||
|
@ -182,7 +182,6 @@ public class Skin {
|
||||
}
|
||||
|
||||
private void fetch() {
|
||||
|
||||
final int maxRetries = Setting.MAX_NPC_SKIN_RETRIES.asInt();
|
||||
if (maxRetries > -1 && fetchRetries >= maxRetries) {
|
||||
if (Messaging.isDebugging()) {
|
||||
@ -215,7 +214,7 @@ public class Skin {
|
||||
}, delay);
|
||||
|
||||
if (Messaging.isDebugging()) {
|
||||
Messaging.debug("Retrying skin fetch for '" + skinName + "' in " + delay + "ticks.");
|
||||
Messaging.debug("Retrying skin fetch for '" + skinName + "' in " + delay + " ticks.");
|
||||
}
|
||||
break;
|
||||
case SUCCESS:
|
||||
@ -227,6 +226,33 @@ public class Skin {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a player skin.
|
||||
*
|
||||
* <p>
|
||||
* If a Skin instance does not exist, a new one is created and the skin data is automatically fetched.
|
||||
* </p>
|
||||
*
|
||||
* @param skinName
|
||||
* The name of the skin.
|
||||
*/
|
||||
public static Skin get(String skinName) {
|
||||
Preconditions.checkNotNull(skinName);
|
||||
|
||||
skinName = skinName.toLowerCase();
|
||||
|
||||
Skin skin;
|
||||
synchronized (CACHE) {
|
||||
skin = CACHE.get(skinName);
|
||||
}
|
||||
|
||||
if (skin == null) {
|
||||
skin = new Skin(skinName);
|
||||
}
|
||||
|
||||
return skin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a skin for a skinnable entity.
|
||||
*
|
||||
@ -241,17 +267,7 @@ public class Skin {
|
||||
Preconditions.checkNotNull(entity);
|
||||
|
||||
String skinName = entity.getSkinName().toLowerCase();
|
||||
|
||||
Skin skin;
|
||||
synchronized (CACHE) {
|
||||
skin = CACHE.get(skinName);
|
||||
}
|
||||
|
||||
if (skin == null) {
|
||||
skin = new Skin(skinName);
|
||||
}
|
||||
|
||||
return skin;
|
||||
return get(skinName);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -272,7 +288,8 @@ public class Skin {
|
||||
private static void setNPCSkinData(SkinnableEntity entity, String skinName, UUID skinId, Property skinProperty) {
|
||||
NPC npc = entity.getNPC();
|
||||
|
||||
// cache skins for faster initial skin availability
|
||||
// cache skins for faster initial skin availability and
|
||||
// for use when the latest skin is not required.
|
||||
npc.data().setPersistent(CACHED_SKIN_UUID_NAME_METADATA, skinName);
|
||||
npc.data().setPersistent(CACHED_SKIN_UUID_METADATA, skinId.toString());
|
||||
if (skinProperty.getValue() != null) {
|
||||
@ -287,6 +304,16 @@ public class Skin {
|
||||
|
||||
private static void setNPCTexture(SkinnableEntity entity, Property skinProperty) {
|
||||
GameProfile profile = entity.getProfile();
|
||||
|
||||
// don't set property if already set since this sometimes causes
|
||||
// packet errors that disconnect the client.
|
||||
Property current = Iterables.getFirst(profile.getProperties().get("textures"), null);
|
||||
if (current != null
|
||||
&& current.getValue().equals(skinProperty.getValue())
|
||||
&& current.getSignature().equals(skinProperty.getSignature())) {
|
||||
return;
|
||||
}
|
||||
|
||||
profile.getProperties().removeAll("textures"); // ensure client does not crash due to duplicate properties.
|
||||
profile.getProperties().put("textures", skinProperty);
|
||||
}
|
||||
|
@ -5,12 +5,14 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import net.citizensnpcs.npc.CitizensNPC;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
@ -98,6 +100,23 @@ public class SkinPacketTracker {
|
||||
skin.applyAndRespawn(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke when the NPC entity is spawned.
|
||||
*/
|
||||
public void onSpawnNPC() {
|
||||
isRemoved = false;
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!entity.getNPC().isSpawned())
|
||||
return;
|
||||
|
||||
double viewDistance = Settings.Setting.NPC_SKIN_VIEW_DISTANCE.asDouble();
|
||||
updateNearbyViewers(viewDistance);
|
||||
}
|
||||
}.runTaskLater(CitizensAPI.getPlugin(), 20);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke when the NPC entity is removed.
|
||||
*
|
||||
@ -164,11 +183,11 @@ public class SkinPacketTracker {
|
||||
Player from = entity.getBukkitEntity();
|
||||
Location location = from.getLocation();
|
||||
|
||||
for (Player player : Bukkit.getServer().getOnlinePlayers()) {
|
||||
for (Player player : world.getPlayers()) {
|
||||
if (player == null || player.hasMetadata("NPC"))
|
||||
continue;
|
||||
|
||||
if (world != player.getWorld() || !player.canSee(from))
|
||||
if (!player.canSee(from))
|
||||
continue;
|
||||
|
||||
if (location.distanceSquared(player.getLocation(CACHE_LOCATION)) > radius)
|
||||
|
Loading…
Reference in New Issue
Block a user