Skip to content

Commit

Permalink
improve player NPC skins
Browse files Browse the repository at this point in the history
add skin packages with skin classes

add profile package with profile fetcher classes

update commands

update EventListen

update NMS

fix radius squared

fix exception message

fix npc sometimes not removed from playerlist

fix cannot add npc to playerlist

code/comment cleanup and refactoring

remove unused skin settings

removed NPC_SKIN_RETRY_DELAY, MAX_NPC_SKIN_RETRIES

current code uses cached textures if a skin profile request fails.

add setting for updating skin

added NPC_SKIN_UPDATE: true to always get the latest skin, false to use cached skin if available, default is false.

minor code fixes, refactoring, add settings

removed assert

removed thread checks

added setting: NPC_SKIN_VIEW_DISTANCE

added setting: NPC_SKIN_UPDATE_DISTANCE

added setting: MAX_PACKET_ENTRIES

invoke EventListen#SkinUpdateTracker#reset from within #shouldUpdate instead of requiring it to be invoked manually.

fix cached locations not used in EventListen#getNearbySkinnableNPCs

clamp yaw in EventListen.SkinUpdateTracker

use static constants

rename EntitySkinnable to SkinnableEntity

add SkinnableEntity interface to PlayerNPC (CraftPlayer)

remove unused code from PlayerListRemover

replace Subscriber with direct notification to entity via method

Undo EntityController interface changes

moved skin code from HumanController to CitizensNPC

fix npcs sometimes do not show

... due to packet tracker not being notified that remove packets have been cancelled

fix imports rearranged by incorrect IDE settings
  • Loading branch information
JCThePants committed Aug 25, 2015
1 parent b0dabb4 commit 6d31467
Show file tree
Hide file tree
Showing 18 changed files with 1,529 additions and 454 deletions.
245 changes: 158 additions & 87 deletions src/main/java/net/citizensnpcs/EventListen.java
Expand Up @@ -2,46 +2,12 @@

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;

import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.event.entity.EntityCombustByBlockEvent;
import org.bukkit.event.entity.EntityCombustByEntityEvent;
import org.bukkit.event.entity.EntityCombustEvent;
import org.bukkit.event.entity.EntityDamageByBlockEvent;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.EntityDeathEvent;
import org.bukkit.event.entity.EntityTargetEvent;
import org.bukkit.event.player.PlayerChangedWorldEvent;
import org.bukkit.event.player.PlayerInteractEntityEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.player.PlayerRespawnEvent;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.event.vehicle.VehicleEnterEvent;
import org.bukkit.event.world.ChunkLoadEvent;
import org.bukkit.event.world.ChunkUnloadEvent;
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.scoreboard.Team;

import com.google.common.base.Predicates;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Iterables;
Expand Down Expand Up @@ -74,17 +40,53 @@
import net.citizensnpcs.api.util.DataKey;
import net.citizensnpcs.api.util.Messaging;
import net.citizensnpcs.editor.Editor;
import net.citizensnpcs.npc.skin.SkinnableEntity;
import net.citizensnpcs.trait.Controllable;
import net.citizensnpcs.trait.CurrentLocation;
import net.citizensnpcs.util.Messages;
import net.citizensnpcs.util.NMS;
import net.minecraft.server.v1_8_R3.EntityPlayer;
import net.minecraft.server.v1_8_R3.PacketPlayOutPlayerInfo;

import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.event.entity.EntityCombustByBlockEvent;
import org.bukkit.event.entity.EntityCombustByEntityEvent;
import org.bukkit.event.entity.EntityCombustEvent;
import org.bukkit.event.entity.EntityDamageByBlockEvent;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.EntityDeathEvent;
import org.bukkit.event.entity.EntityTargetEvent;
import org.bukkit.event.player.PlayerChangedWorldEvent;
import org.bukkit.event.player.PlayerInteractEntityEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.player.PlayerRespawnEvent;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.event.vehicle.VehicleEnterEvent;
import org.bukkit.event.world.ChunkLoadEvent;
import org.bukkit.event.world.ChunkUnloadEvent;
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.scoreboard.Team;

public class EventListen implements Listener {
private final NPCRegistry npcRegistry = CitizensAPI.getNPCRegistry();
private final Map<String, NPCRegistry> registries;
private final ListMultimap<ChunkCoord, NPC> toRespawn = ArrayListMultimap.create();
private final Map<UUID, SkinUpdateTracker> skinUpdateTrackers =
new HashMap<UUID, SkinUpdateTracker>(Bukkit.getMaxPlayers() / 2);

EventListen(Map<String, NPCRegistry> registries) {
this.registries = registries;
Expand Down Expand Up @@ -338,7 +340,7 @@ public void onPlayerChangedWorld(PlayerChangedWorldEvent event) {

@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerChangeWorld(PlayerChangedWorldEvent event) {
recalculatePlayer(event.getPlayer());
recalculatePlayer(event.getPlayer(), 20, true);
}

@EventHandler(ignoreCancelled = true)
Expand All @@ -360,7 +362,7 @@ public void onPlayerInteractEntity(PlayerInteractEntityEvent event) {

@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerJoin(PlayerJoinEvent event) {
recalculatePlayer(event.getPlayer());
recalculatePlayer(event.getPlayer(), 20, true);
}

@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
Expand All @@ -372,16 +374,17 @@ public void onPlayerQuit(PlayerQuitEvent event) {
event.getPlayer().leaveVehicle();
}
}
skinUpdateTrackers.remove(event.getPlayer().getUniqueId());
}

@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerRespawn(PlayerRespawnEvent event) {
recalculatePlayer(event.getPlayer());
recalculatePlayer(event.getPlayer(), 15, true);
}

@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerTeleport(PlayerTeleportEvent event) {
recalculatePlayer(event.getPlayer());
recalculatePlayer(event.getPlayer(), 15, true);
}

@EventHandler(ignoreCancelled = true)
Expand Down Expand Up @@ -423,34 +426,68 @@ public void onWorldUnload(WorldUnloadEvent event) {
}
}

public void recalculatePlayer(final Player player) {
// recalculate player NPCs the first time a player moves and every time
// 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;

if (!updateTracker.shouldUpdate(event.getPlayer()))
return;

recalculatePlayer(event.getPlayer(), 10, false);
}

public void recalculatePlayer(final Player player, long delay, final boolean isInitial) {

if (isInitial) {
skinUpdateTrackers.put(player.getUniqueId(), new SkinUpdateTracker(player));
}

new BukkitRunnable() {

@Override
public void run() {
final List<EntityPlayer> nearbyNPCs = new ArrayList<EntityPlayer>();
for (NPC npc : getAllNPCs()) {
Entity npcEntity = npc.getEntity();
if (npcEntity instanceof Player && player.canSee((Player) npcEntity)
&& player.getWorld().equals(npcEntity.getWorld())
&& player.getLocation().distanceSquared(npcEntity.getLocation()) < 100 * 100) {
nearbyNPCs.add(((CraftPlayer) npcEntity).getHandle());
}

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);
}
new BukkitRunnable() {
@Override
public void run() {
sendToPlayer(player, nearbyNPCs);
}
}.runTaskLater(CitizensAPI.getPlugin(), 30);
new BukkitRunnable() {
@Override
public void run() {
sendToPlayer(player, nearbyNPCs);
}
}.runTaskLater(CitizensAPI.getPlugin(), 70);
}
}.runTaskLater(CitizensAPI.getPlugin(), 10);
}.runTaskLater(CitizensAPI.getPlugin(), delay);
}

private List<SkinnableEntity> getNearbySkinnableNPCs(Player player) {

List<SkinnableEntity> results = new ArrayList<SkinnableEntity>();

double viewDistance = Setting.NPC_SKIN_VIEW_DISTANCE.asDouble();
viewDistance *= viewDistance;

for (NPC npc : getAllNPCs()) {

Entity npcEntity = npc.getEntity();
if (npcEntity instanceof Player
&& player.canSee((Player) npcEntity)
&& player.getWorld().equals(npcEntity.getWorld())
&& player.getLocation(CACHE_LOCATION)
.distanceSquared(npc.getStoredLocation()) < viewDistance) {

SkinnableEntity skinnable = NMS.getSkinnableNPC(npcEntity);

results.add(skinnable);
}
}
return results;
}

private void respawnAllFromCoord(ChunkCoord coord) {
Expand All @@ -472,30 +509,6 @@ private void respawnAllFromCoord(ChunkCoord coord) {
}
}

void sendToPlayer(final Player player, final List<EntityPlayer> nearbyNPCs) {
if (!player.isValid())
return;
for (EntityPlayer nearbyNPC : nearbyNPCs) {
if (nearbyNPC.isAlive())
NMS.sendPacket(player, new PacketPlayOutPlayerInfo(
PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER, nearbyNPC));
}
if (Setting.DISABLE_TABLIST.asBoolean()) {
new BukkitRunnable() {
@Override
public void run() {
if (!player.isValid())
return;
for (EntityPlayer nearbyNPC : nearbyNPCs) {
if (nearbyNPC.isAlive())
NMS.sendPacket(player, new PacketPlayOutPlayerInfo(
PacketPlayOutPlayerInfo.EnumPlayerInfoAction.REMOVE_PLAYER, nearbyNPC));
}
}
}.runTaskLater(CitizensAPI.getPlugin(), 2);
}
}

private boolean spawn(NPC npc) {
Location spawn = npc.getTrait(CurrentLocation.class).getLocation();
if (spawn == null) {
Expand Down Expand Up @@ -559,4 +572,62 @@ public int hashCode() {
return prime * (prime * (prime + ((worldName == null) ? 0 : worldName.hashCode())) + x) + z;
}
}

private class SkinUpdateTracker {
float initialYaw;
final Location location = new Location(null, 0, 0, 0);
boolean hasMoved;
int rotationCount;

SkinUpdateTracker(Player player) {
reset(player);
}

boolean shouldUpdate(Player player) {

// check if this is the first time the player has moved
if (!hasMoved) {
hasMoved = true;
reset(player);
return true;
}

Location currentLoc = player.getLocation(YAW_LOCATION);
float currentYaw = currentLoc.getYaw();

float rotationDegrees = Setting.NPC_SKIN_ROTATION_UPDATE_DEGREES.asFloat();

boolean hasRotated =
Math.abs(NMS.clampYaw(currentYaw - this.initialYaw)) < rotationDegrees;

// update the first 2 times the player rotates. helps load skins around player
// after the player logs/teleports.
if (hasRotated && rotationCount < 2) {
rotationCount++;
reset(player);
return true;
}

// update every time a player moves a certain distance
double distance = currentLoc.distanceSquared(this.location);
if (distance > MOVEMENT_SKIN_UPDATE_DISTANCE) {
reset(player);
return true;
}
else {
return false;
}
}

// resets initial yaw and location to the players
// current location and yaw.
void reset(Player player) {
player.getLocation(location);
this.initialYaw = location.getYaw();
}
}

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 int MOVEMENT_SKIN_UPDATE_DISTANCE = 50 * 50;
}
6 changes: 4 additions & 2 deletions src/main/java/net/citizensnpcs/Settings.java
Expand Up @@ -88,14 +88,16 @@ public void loadFromKey(DataKey root) {
KEEP_CHUNKS_LOADED("npc.chunks.always-keep-loaded", false),
LOCALE("general.translation.locale", ""),
MAX_NPC_LIMIT_CHECKS("npc.limits.max-permission-checks", 100),
MAX_NPC_SKIN_RETRIES("npc.skins.max-retries", -1),
MAX_PACKET_ENTRIES("npc.limits.max-packet-entries", 15),
MAX_SPEED("npc.limits.max-speed", 100),
MAX_TEXT_RANGE("npc.chat.options.max-text-range", 500),
MESSAGE_COLOUR("general.color-scheme.message", "<a>"),
NEW_PATHFINDER_OPENS_DOORS("npc.pathfinding.new-finder-open-doors", false),
NPC_ATTACK_DISTANCE("npc.pathfinding.attack-range", 1.75 * 1.75),
NPC_COST("economy.npc.cost", 100D),
NPC_SKIN_RETRY_DELAY("npc.skins.retry-delay", 120),
NPC_SKIN_UPDATE("npc.skins.update", false),
NPC_SKIN_VIEW_DISTANCE("npc.skins.view-distance", 100D),
NPC_SKIN_ROTATION_UPDATE_DEGREES("npc.skins.rotation-update-degrees", 90f),
PACKET_UPDATE_DELAY("npc.packets.update-delay", 30),
QUICK_SELECT("npc.selection.quick-select", false),
REMOVE_PLAYERS_FROM_PLAYER_LIST("npc.player.remove-from-list", true),
Expand Down
14 changes: 10 additions & 4 deletions src/main/java/net/citizensnpcs/commands/NPCCommands.java
Expand Up @@ -5,6 +5,7 @@
import java.util.Comparator;
import java.util.List;

import net.citizensnpcs.npc.skin.SkinnableEntity;
import org.apache.commons.lang3.StringUtils;
import org.bukkit.Bukkit;
import org.bukkit.DyeColor;
Expand Down Expand Up @@ -992,11 +993,13 @@ public void playerlist(CommandContext args, CommandSender sender, NPC npc) {
boolean remove = !npc.data().get("removefromplayerlist", Setting.REMOVE_PLAYERS_FROM_PLAYER_LIST.asBoolean());
if (args.hasFlag('a')) {
remove = false;
} else if (args.hasFlag('r'))
} else if (args.hasFlag('r')) {
remove = true;
}
npc.data().setPersistent("removefromplayerlist", remove);
if (npc.isSpawned()) {
NMS.addOrRemoveFromPlayerList(npc.getEntity(), remove);
npc.despawn(DespawnReason.PENDING_RESPAWN);
npc.spawn(npc.getTrait(CurrentLocation.class).getLocation());
}
Messaging.sendTr(sender, remove ? Messages.REMOVED_FROM_PLAYERLIST : Messages.ADDED_TO_PLAYERLIST,
npc.getName());
Expand Down Expand Up @@ -1315,8 +1318,11 @@ public void skin(final CommandContext args, final CommandSender sender, final NP
}
Messaging.sendTr(sender, Messages.SKIN_SET, npc.getName(), skinName);
if (npc.isSpawned()) {
npc.despawn(DespawnReason.PENDING_RESPAWN);
npc.spawn(npc.getStoredLocation());

SkinnableEntity skinnable = NMS.getSkinnableNPC(npc.getEntity());
if (skinnable != null) {
skinnable.setSkinName(skinName);
}
}
}

Expand Down
Expand Up @@ -2,7 +2,6 @@

import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.util.NMS;

import org.bukkit.Location;
import org.bukkit.entity.Entity;

Expand Down

0 comments on commit 6d31467

Please sign in to comment.